diff --git a/app-guide.gmi b/app-guide.gmi index ce3b871..4f2497e 100644 --- a/app-guide.gmi +++ b/app-guide.gmi @@ -28,6 +28,7 @@ To get a sense of what Gemini apps can be like in practice, here is a sampling o ## Overview of the guide  * (Summary of each section.) +* Section 4 "User interface": how your app appears and behaves from the user's point of view.  # 1. Basic Example  @@ -71,21 +72,21 @@ User input:  A central design decision is whether your application needs to have per-session and per-user data, and how these will be stored and maintained.  -While many applications, such as search engines, weather services, and simple games, can function without any knowledge about the user, more sophisticated applications typically have some per-user state that needs to be tracked in a private and secure manner. For example, the application could have per-user preferences, a player inventory, or an internal messaging service, and this data needs to be stored persistently on the server. +While many applications, such as search engines, weather services, and simple games, can function without any knowledge about the user, more sophisticated applications typically have some per-user state that needs to be tracked in a private and secure manner. For example, the application could have per-user preferences, a player inventory, or an internal messaging system, and this data needs to be stored persistently on the server.  # 3.1 Anonymous usage  Anonymous usage means that the Gemini server and your application have no way of identifying the user who sent a particular request, apart from the ever-present but ambiguous IP address.  -You should consider whether fully anonymous usage of your application is possible. This way, users are not required to go through the extra step of creating and/or activating a client certificate before they can access your application, making it more convenient to use. Some users may also not be comfortable with the idea that each action they take is possibly being recorded and associated with an identity — consider a search engine, for example. But even if anonymous usage is impractical or undesirable, your application may still have some parts that can be accessed anonymously. For example, anonymous visitors could see a Top 10 scoreboard or a feed of public posts. As a general guideline, you should design your application to have both public/anonymous and private/authenticated parts, as applicable. +You should consider whether fully anonymous usage of your application is possible. This way, users are not required to go through the extra step of creating and/or activating a client certificate before they can access your application, making it more convenient to use. Some users may also not be comfortable with the idea that each action they take is possibly being recorded and associated with an identity — consider a search engine, for example. Even if anonymous usage is impractical or undesirable, your application may still have some parts that can be accessed anonymously. For example, anonymous visitors could see a Top 10 scoreboard or a feed of public posts. As a general guideline, you should design your application to have both public/anonymous and private/authenticated parts, as applicable.  -However, most non-trivial Gemini apps should not rely on fully anonymous access and instead use TLS client certificates for keeping track of users and sessions, because that is the most secure and privacy-respecting solution that the protocol offers. +Most non-trivial Gemini apps should not rely on fully anonymous access but instead use TLS client certificates for keeping track of users and sessions, because that is the most secure and privacy-respecting solution that the protocol offers.  -Putting that aside for a moment, let's consider what anonymous usage means for a Gemini app. Gemini has no cookies so the only way to completely anonymously store per-session data is to encode it somehow inside URLs. A trivial example is a search engine where the URL query string contains the search terms provided by the user. However, URLs are able to contain much more information than that. For instance, the URL path may include parts that do not map to any actual location in a file system. (See the CGI "PATH_INFO" variable.) The important point to note is that these URLs must be guaranteed to be unique, or they must fully contain the entire application state. Otherwise, sessions of two users could be accidentally mixed up. Even so, nothing prevents a user from entering such URLs manually in their client, which means this method is insecure. +Putting that aside for a moment, let's consider what anonymous usage means for a Gemini app. Gemini has no cookies so the only way to store per-session data is to encode it somehow inside URLs. This is because the server has no way of associating server-side data with a particular session: all per-session information must be contained in the response sent to the client, and must then be returned back in subsequent requests made by the client. A trivial example of this is a search engine where the URL query string contains the search terms provided by the user, and page navigation links on the result page have the same query string so that the search terms are preserved when changing the page. In addition to query strings, link URLs may contain information in other ways. For instance, the URL path may include parts that do not map to any actual location in a file system. (See the CGI "PATH_INFO" variable.)  -A really simple application like a Tic Tac Toe game may get away with not keeping any server-side data, instead storing all state inside URLs. However, Gemini URLs have a maximum length of 1024 bytes, so you only have so much space for your state and parameters. The user may also change the state manually by editing the URL. Obfuscation via a simple cipher like ROT13 or Base64 might be warranted, but these are not foolproof methods. +A really simple application like a Tic Tac Toe game could get away with not keeping any server-side data, instead storing all state inside URLs. However, Gemini URLs have a maximum length of 1024 bytes, so you only have so much space for your state and parameters. Also, the user may also change the state manually by editing the URL. Obfuscation via a simple cipher like ROT13 or Base64 might be warranted, but these are not foolproof methods.  -Randomized tokens can be used for ensuring session-specific URLs are unique. For example, to start a new game session, the following link could be used: +A further important point to note is that URLs that contain with session state must be guaranteed to be unique, or they must fully contain the entire application state. Otherwise, sessions of two users could be accidentally mixed up, as requests come in at random times from various users around the world. In practice, randomized tokens can be used for ensuring session-specific URLs remain unique. For example, to start a new game session, the following link could be used: ``` => /start-game/{TOKEN} Start a New Game ``` @@ -94,33 +95,33 @@ Here, {TOKEN} would be a random string of characters that changes on every load => /{TOKEN}/move Move Player => /{TOKEN}/end-turn End Turn ``` -Or, simplified using relative paths, when the current page's URL (gemini://example.com/myapp/{TOKEN}/) already contains the token: +Or, simplified using relative paths, if the current page's URL (gemini://example.com/myapp/{TOKEN}/) already contains the token: ``` => move Move Player => end-turn End Turn ``` This way, the token and any other data encoded in the URLs survive the roundtrips between the client and server. We recommend generating lengthy random tokens as session identifiers to prevent guessing or brute-forcing them. For example, simple sequential numbering of sessions would be a very bad idea.  -You may also take advantage URL query string for user requests. For example, you could do this: +You may also take advantage of the URL query string for user requests. For example, you could do this: ``` => /{TOKEN}?move Move Player => /{TOKEN}?end-turn End Turn ``` -This may simplify processing the requests in your application code. However, note that storing the session token in the query string is inadvisable, because this makes it impossible to query the user for input via status 1x: the server has no way to pre-filling the input prompt that a client displays to the user, which would be required for specifing the correct session token. +This may simplify processing the requests in your application code because the URL parser can extract the query string for you and you don't manually have to split the path into different parts. However, note that storing the session token in the query string is inadvisable, because this makes it impossible to query the user for input via status 1x: the server has no way to pre-filling the input prompt that a client displays to the user, which would be required for specifing the correct session token.  -You may be tempted to incorporate the client's IP address somehow in the generated token. For example, this could work if your application only runs on your local network and only receives requests from IP addresses that you fully own and control. However, on the public internet this has several drawbacks: +You may be tempted to incorporate the client's IP address somehow in the generated token. This could work if your application only runs on your local network and only receives requests from IP addresses that you fully own and control. However, on the public internet this has several drawbacks:  * IP addresses may not be unique per user. For example, multiple people behind one NAT router will be using the same IP address. * Bookmarking such a URL and opening it on another device may cause problems because the IP address may change. * Residential and cellular IP addresses are dynamic so a user may find their tokens suddenly become obsolete.  -Anonymous usage means that search engine crawlers and other bots can freely access your application and may request each of the URLs on your pages. We recommend setting up robots.txt to limit access to any parts of your application where URLs are used for tracking sessions. But even so, bots may not respect your rules and you may find it necessary to implement additional manual restrictions, ad-hoc bot detection heuristics (for example, requests coming in too frequently per IP address), or clean up your database manually after each onslaught. +A notable potential downside of anonymous usage is that search engine crawlers and other bots can freely access your application and may request each of the URLs on your pages. We recommend setting up robots.txt to limit access to any parts of your application where URLs are used for tracking sessions or performing actions. But even so, bots may not respect your rules and you may find it necessary to implement additional manual restrictions, ad-hoc bot detection heuristics (for example, requests coming in too frequently per IP address), or clean up your database manually after each onslaught.  => gemini://gemini.circumlunar.space/docs/companion/robots.gmi robots.txt for Gemini  # 3.2 Client certificates  -You should already be familiar with how and why Gemini uses TLS: +You should already be familiar with how and why Gemini uses TLS. When it comes to applications, the most interesting part of TLS is client certificates.  => gemini://gemini.circumlunar.space/docs/tls-tutorial.gmi A gentle, Gemini-centric guide to TLS certificates  @@ -149,11 +150,16 @@ Your application may need user accounts so it has a way to manage per-user data.  # 4. User interface  -* a key part of app design is to consider the public interface separately from the internal implementation; wise to optimize each for their respective needs -* this section is all about how your app appears and behaves from the user's point of view +A key part of application design, and also programming in general, is to separate the public interface from the internal implementation. The needs of the human user and the internal technical implementation are very different and often at odds with each other, but both facets of an application must still be optimized for their respective needs. + +Gemtext is quite limited as a format for presenting a user interface, which makes Gemini app UI design an interesting challenge. One issue is that the UI of your application should work (or at least strive to work) equally well on every Gemini client. Especially be wary of only testing your application on a high-end graphical client, where you can have sophisticated page layout, multiple fonts, and color schemes that clarify the structure of the UI. When you view such a UI in a terminal-based client, things may look different in unexpected ways. Still, Gemtext is simple enough that by following a few basic rules, you can achieve good results everywhere. + +In this section, we will take a closer look at the options and tools at your disposal when it comes to the UI.  ## 4.1 Structure  +On the web, as sites grow more and more complex, browsers have been gradually phasing out visibility of the current page's URL since it may mostly appear as visual noise to the user. Gemini clients typically do not do this, and one should consider the URL and its structure as part of the UI of the application. + * URL path structure, usage of query strings -- if we are not serving static files, no need to adhere to any actual file system structure; keep the "/cgi-bin/"s out of the path... * URL authority: username and password (?) -- Gemini spec says to not use? * navigation structures: query strings vs. directory structure ("Go Up" / "Go to Parent" navigation!), "Back to X" pages vs. knowing where the user came from @@ -162,7 +168,7 @@ Gemtext UI considerations: * menus vs. content, length of menus, top vs. bottom of page * when to use preformatted content (avoid pointless graphics/clutter), include alt text * use of Emoji for actions (client support may vary, esp. with TUI clients and retro computers) -* content lists vs. nav links +* content lists vs. nav links; the "social sandwich"  ## 4.2 Security considerations