Files
roro9stack/docs/milestones/G1.md
T
twislaandClaude Opus 5.5 2d384f5cff G1 step 1: gemini get, and two TLS connections measured
`gemini get <url>` fetches on a short-lived task (a handshake would trip
the main loop's watchdog; an idle client should cost no stack) and
reports the header, size, certificate fingerprint and heap. First page:
geminiprotocol.net, 20 text/gemini, 1,184 bytes in 0.7-1.1 s.

With IRC connected over TLS, a fetch dips to about 24 KB free during its
handshake, about 36-40 KB after it; nothing leaks. Antenna is down, so
the default aggregator becomes Cosmos.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-05 00:27:05 +02:00

5.3 KiB
Raw Blame History

G1 — Gemini client

Goal: browse Geminispace from the Cardputer: fetch and read gemtext over TLS, follow links, answer input prompts, keep bookmarks, and save pages to the SD card to read later, offline. A side milestone between M2 and M3, tagged v0.5.0 when done.

Gemini (geminiprotocol.net): one request per TLS connection on port 1965, the request is the URL and CRLF, the response a <status> <meta> header line, then the body. Most capsules use self-signed certificates: trust on first use is the norm.

Decisions (design round 2026-10-05)

# Decision
Q70 A milestone of its own, G1, before M3: plan, tests first, measured on the device, tagged v0.5.0.
Q71 TOFU: the first certificate seen for a host is pinned (SHA-256, in NVS). If it changes, the page isn't shown; a dialog shows both fingerprints and asks whether to trust the new one. Self-signed or expired certificates are fine; only a change counts.
Q72 Responses: 1x input (11 hidden, for passwords), 2x content, up to 5 redirects (3x), 4x/5x errors with the server's message. 6x (client certificates): "not supported".
Q73 text/gemini is rendered, other text/* shown as plain text. Anything else can be saved to /gemini/downloads/, not shown.
Q74 Up to 64 KB on screen, larger pages truncated with a notice. Saving streams to the card, so a larger page is saved whole.
Q75 Gemtext rendering: text wrapped to the 40-column screen; #/##/### headings in bold and accent; * lists with bullets; > quotes indented and muted; preformatted blocks unwrapped, Left/Right to scroll; => links with their label, numbered.
Q76 Up/Down scroll; Tab and Shift+Tab move between links; Enter follows; Backspace goes back; g opens the address line. Links to other protocols show their URL and aren't followed.
Q77 Back history of 20 URLs in RAM, with scroll positions; going back refetches (or reopens a Saved Page). Bookmarks in /gemini/bookmarks.gmi (a gemtext page, shown on the start page); b adds the current page. Without a card, a built-in start page.
Q78 Start page: bookmarks, then Saved Pages, then defaults: geminiprotocol.net, a search engine (kennedy.gemi.dev), an aggregator (Cosmos; Antenna was down when measured).
Q79 UTF-8 decoded; characters outside the Latin-1 fonts shown as ?.
Q80 IRC and Gemini can run together: each fetch opens one connection, reads and closes it. If there isn't memory for a second TLS connection, the fetch fails with a clear message and IRC is untouched. Measured in step 1.
Q81 Debug aid: gemini get <url> prints the status, MIME type, size, certificate fingerprint and the first lines. URL resolution (RFC 3986), the response header and gemtext parsing are host-tested.
Q82 s saves the page on screen as a Saved Page: /gemini/saved/<host>/<path>.gmi, the gemtext as received plus a first line with its URL and save date. Saving again replaces it, and says so.
Q83 S saves the page and the pages it links to, one level deep: gemtext only, same host only, at most 30 pages, in the background with a progress Toast.
Q84 The start page lists Saved Pages, newest first, grouped by capsule; they open with no network. In a Saved Page, a link to another Saved Page opens the saved copy; other links fetch online if Wi-Fi is up, or say "not saved, offline". A Saved Page shows when it was saved; r refreshes it.
Q85 Saved Pages are deleted from the App only (d, with confirmation), never by Storage Clean-up's age rules, like Notes.

Measured (step 1)

  • gemini://geminiprotocol.net/: 20 text/gemini, 1,184 bytes, TLS handshake 0.7–1.1 s, whole fetch 0.7–1.1 s; kennedy.gemi.dev 1.9 s. The fetch task's stack peaks at about 3.6 KB of 6.
  • Heap, Debug Build, IRC connected over TLS: about 68 KB free before a fetch. After the handshake the fetch holds about 32 KB (36–40 KB left); the handshake itself (certificate chain parsed with the 16 KB receive buffer allocated) dips to about 24 KB for a second or two. Nothing leaks: the heap after matches the heap before.
  • Antenna (warmedal.se) doesn't answer, from the PC either; the default aggregator becomes Cosmos (gemini://skyjake.fi/~Cosmos/, which redirects to cosmos.skyjake.fi).

Done when

  • gemini get gemini://geminiprotocol.net/ prints the header, size and fingerprint on the console.
  • The Gemini App opens the start page, follows links (relative ones included), goes back, and follows redirects.
  • An input prompt (e.g. a search) takes a query and shows the results.
  • A changed certificate stops the page and asks.
  • s saves a page, S a page and its links; with Wi-Fi off, Saved Pages open and their saved links work.
  • Bookmarks are added with b and listed on the start page.
  • With IRC connected over TLS, a fetch still works, and the free heap stays above 40 KB.

Work breakdown

  1. Two TLS connections: measure the heap with IRC connected while a Gemini fetch runs.
  2. Parsers (host-tested): URL parsing and relative resolution, the response header, gemtext lines.
  3. Fetch on its own task, with TOFU and gemini get.
  4. Gemini App: rendering, scrolling, links, history, the address line.
  5. Input prompts, redirects, bookmarks, downloads.
  6. Saved Pages, then saving with linked pages.