Skip to the main content

cache invalidation · a personal blog by christoph lühr

Gemtext

A kitchen sink for a very small kitchen. Gemini's gemtext has seven line types and no inline markup at all, so this page can only produce seven kinds of element — and that turns out to be enough for everything below. Amber phosphor, one minified stylesheet, one optional script, one prefetch hint, no divs, one class.

Index

Text lines

Any line that starts with something other than a marker is a text line, and a run of them is a paragraph. That is the whole rule. There is no bold, no italic, no small caps, no inline link, no footnote — the format has no way to change a word in the middle of a sentence, so emphasis has to be carried by word order and by what you choose to say.

Writing under that constraint is closer to writing for a teleprinter than for the web. Sentences carry their own stress. Paragraphs stay short because there is no typographic scaffolding to hold a long one up.

Heading lines

One, two, or three number signs, and no deeper. The markers you see to the left of these headings are drawn by the stylesheet from the element type, not typed into the markup — and each is given an empty alternative text, so it renders as syntax on screen and stays silent in a screen reader. The fragment after each sub-heading is derived from its own words, which is what makes the index above possible without a single hand-written id.

The title at the top of this page carries its single number sign the same way. It was the last line here to get one: a heading that large reads as a title whether or not it is marked, so the omission took a while to notice, and the page was quietly the one thing on the site not dressed as its own source.

Third level, and the floor

A document that wants a fourth level of nesting wants to be two documents. The format simply declines to provide one, and the processor says so on standard error if you try.

A link occupies a whole line: an arrow, a URL, and an optional label. The arrow and the scheme tag are drawn by the stylesheet, matched on the href attribute rather than on a class, and both sit outside the underline — the rule runs under the label only, because the label is the part you are being asked to read.

Because a link is never embedded in a sentence, every label has to make sense read aloud on its own — which is exactly what WCAG asks for at AAA, arrived at by accident.

Links that point inside this capsule are rewritten on the way out: a .gmi target becomes .html, a bare directory becomes its index, and an absolute gemini:// URL back to this same capsule becomes a relative path. Everything else is left exactly as it was written.

Two of the six links above go to other pages of this capsule; the rest are a fragment, two other hosts and an address. The head of this document names those two and nothing else, in a speculation rules block. Hover either for a fifth of a second and the browser fetches that page in the background; click and it is already there. Nothing is fetched because this page loaded, and the image link further down is not in the list — the processor knew it was a picture rather than a page, so it never offered it.

List lines

An asterisk, a space, an item. Unordered only; there are no numbered lists and no nesting.

Quote lines

A greater-than sign quotes a line. Consecutive quote lines make one block, and an empty quote line starts a new paragraph inside it — which is the only way to write a two-paragraph quotation, since a real blank line would end the block outright.

The amber screen was never a design decision anyone agonised over. P3 phosphor persisted a little longer than green, flickered a little less, and cost about the same.

Forty years on, the thing people remember is the colour.

Preformatted blocks

Three backticks open and close a block, and the opening line may carry alternative text. What that alt text says decides what the block becomes. A phrase reads as a description, so the block is marked as an image and reads out as its caption instead of as four hundred box-drawing characters; it is also the one block set at a line height of exactly 1, so the box glyphs meet. The double rule around it is drawn by the stylesheet; the single rule inside it is typed.

   ┌──────────────────────────────┐
   │                              │
   │    B E R N S T E I N         │
   │    P3 AMBER PHOSPHOR         │
   │    ─────────────────         │
   │    > gemtext ready █         │
   │                              │
   └──────────────────────────────┘
      ╱                        ╲
     ╱__________________________╲

A single bare word reads as a language tag instead, and the block becomes a code element carrying it. That is the one class attribute in the document, and the only thing on the page that a gemtext author can add which the stylesheet has to be told about in advance.

echo "a language tag, not a description";

With no alt text at all, the block is left as an ordinary preformatted block: text a reader might actually want, such as source, a table, or a transcript. This is very nearly the entire syntax of the page you are reading:

# Heading
## Sub-heading
### Sub-sub-heading

A plain line becomes a paragraph.

=> gemini://geminiprotocol.net/  A link with a label
=> /relative/path

* a list item
* another list item

> a quoted line

Very nearly, because one line is missing from that sample and cannot be put back: the fence itself. A block is closed by the first fence inside it, so gemtext is the one format on this page that cannot quote its own syntax.

Images

Gemtext has no inline image syntax — a picture is a link line, and the client decides whether to fetch it. So this is the one place the page steps outside the seven line types, and it does it on the format's own terms. The link points at the full-size original, exactly as it would over Gemini. The plate below it was not written by anyone: the processor generated it from that original, one bit per pixel, two colours of which one is transparent. There is no greyscale to spend, so the tones are ordered 8 × 8 Bayer dither, which both looks like 1983 and compresses about three times smaller than error diffusion.

Full-colour original
Beam intensity against persistence time, 0 to 16 ms, sampled across the vertical scan. Choose the plate for the full-colour original, and again to come back.

One line of gemtext, four values in the HTML: the plate is inlined as a data URI, the original is copied alongside and linked, the dimensions come from the generated file so nothing shifts as it loads, and the label becomes the caption.

The image carries a fifth value the gemtext never mentions, and it is the one the picture cannot do without. A plate is wrapped in a link, and a link whose only content is an image with no alternative text has no accessible name at all — a screen reader announces "link" and then spells out the address. The caption does not rescue it, because a caption names the figure and takes no part in naming the anchor inside it. So the alt says where the link goes rather than what the picture shows: two strings, two jobs, and the caption still read exactly once.

The colour is baked into the two-entry palette rather than applied by the stylesheet, so the file arrives already amber, and the palette it is tinted with is the same one the stylesheet is built from. Paper mode burns it down to brown with a filter instead of shipping a second copy.

What makes this AAA

The constraint and the conformance mostly agree with each other. A format with no inline links, no rich text and almost no colour has already made most of the hard choices.

The markup underneath is the same seven elements throughout, plus the figure, plus the list that a run of links is gathered into, plus the header, main and footer that hold them all: no div, no span, no id except the anchors the headings name themselves, one class on the one code block that asked for it, and one script whose only job is to swap the plate for its full-colour original in place — remove it and the link still works.

There is a second script element, and it contains no script: the speculation rules block is a JSON data island the browser reads and never executes. A browser that has not heard of it sees an unknown script type and does nothing at all, which is the whole of the fallback story. The stylesheet arrives minified, its comments stripped down to a single banner pointing at the source they came from — the reasoning is still written out in full, just not in every copy of every page.