Files
roro9stack/docs/milestones/G1.md
T
twislaandClaude Opus 5.5 fd306d5013 G1 steps 5-6: input prompts, bookmarks, downloads, Saved Pages
Everything touching the network or the card is a job on the Gemini
task (a missing card can block 5 s, past the main loop's watchdog):
about:start is composed from /gemini/bookmarks.gmi and the Saved Pages
(by capsule, newest first); file:// opens a Saved Page; s, S, r, d, b
and downloads report one line to the URL bar, S with progress Toasts.

A Saved Page is the cached body with a first line "> Saved from <url>
on <date>": it shows as a quote and gives relative links their base;
inside one, links to other Saved Pages open the saved copy, others go
online or say "Not saved, and offline". Input prompts (11 masked)
request the same URL with the answer as its query.

Fixed on the way: re-wrapping indented lines rebuilt the text from its
rows, inserting a space where a long word had been cut; refreshing
loaded the whole Saved Page next to a TLS connection (heap down to 436
bytes), now it reads one line and every fetch checks the 55 KB floor.

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

7.4 KiB
Raw Blame History

G1 — Gemini client

Status: steps 1–6 built and checked on the device (branch g1); one item left for a check by hand: reading Saved Pages with Wi-Fi off.

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.
Q86 Decided after step 1. Two floors: free heap stays above 40 KB in steady state, and above 20 KB for the second or two of a TLS handshake (measured: 24 KB with IRC connected). A fetch refuses to start below 55 KB free ("not enough memory: stop IRC or retry"), so nothing pushes lower.
Q87 Decided in step 3. With a card, every page streams to /gemini/cache/page.gmi in 1 KB pieces while its TLS connection is open; once the connection closes and its ~45 KB is back, the page is loaded into RAM as far as the 40 KB floor allows. The whole page stays on the card (Saved Pages copy it). Without a card, the page goes straight to RAM under the same two floors. Pages are held as lines in 4 KB chunks, never one large block (the largest free block with IRC connected is about 31 KB).
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.
  • Step 3, Cosmos (31.6 KB) with IRC connected: first stopped at 4.6 KB (RAM only, the transfer's 20 KB floor). Streamed to the card: the whole page on the card, 20 KB of it loaded, lowest free heap 19.5 KB during the transfer and 43 KB once loaded. Without IRC: the whole page in RAM. Redirects (Cosmos 31), input (10), not found (51) and a changed certificate (refused, both fingerprints shown) all checked on the device.
  • Steps 4–6 on the device: Project Gemini and its relative links, Back with the scroll restored, a refused YouTube link; b bookmarks, s saves (and says when it replaced an older copy), S saved 6 of 6 pages, the start page lists both; a Saved Page opens from the card with its origin, its saved links open saved copies, r refreshes, d asks first; Kennedy's input prompt sent "cardputer" and got 87 results; emoji drawn as ?.
  • A bug found there: refreshing first loaded the whole Saved Page into RAM just to read its origin, next to the App's copy and a TLS connection: the heap fell to 436 bytes. Now only the first line is read, and every fetch (pages, saves, refreshes) checks the 55 KB start floor. Lowest since boot afterwards: 53.8 KB.
  • 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.