LITE-034 DONE: lite serve — the repo browser over HTTP, no CommonMark yet

Now
DONE
Sev
MED
Ask
gritzko
Rep
///lite
On1
QJAB-004
See
LITE-035

The lite verbs already produce everything a repo browser shows — list, log, commit, diff, cat, tree, blob — but only the terminal can see it: view/ spells dog tok tags as ANSI SGR through theme.js. This ticket adds lite serve: an HTTP server on quickjab's net/pol loop (QJAB-004 parses the requests), a router mapping URLs onto the existing index/ verbs, and an HTML twin of the painter — the same tok tags become <span class>, the same theme palette becomes CSS. Markdown stays UNRENDERED here: .md/.mkd serve as syntax-painted source like any file; the CommonMark/StrictMark render (LITE-030, LITE-031, be/verbs/mark/render.js) is a follow-up ticket, not this one.

Input

Context

Transport and content both exist; only the emitter and the glue are missing.

Goals

lite serve in a git repo answers a browser with the same views the pager shows, links intact.

Constraints

WIP

Design decisions

TODOs

Blockers and bummers

Outcome

Built in //LITE-034, staged, not committed. Based on ///lite#4cf63f4a; an EXTERNAL get moved the worktree onto ///lite#3b0fdb24 (LITE-031's GFM) mid-run — none of the nine staged files is a mark/ one, and the whole suite (that ticket's own new test/mark/gfm leg, 49 checks, included) was re-run green on the new base.

The URL table as implemented

The first path segment names the verb, everything after it is the ARG, verbatim — ?<rev> rides across as the URL's own query. index/serve.js ROUTE is the whole table; urlOf/argUrl is the same table backwards (a pager click target -> its URL), so a ruling on the scheme is an edit in that one file.

/ list the root list /list/<path>?<rev> list /log/<path|hex> log /commit/<hex> commit /diff/<hex|path> diff /cat/<path>?<rev> cat /tree/<path|hex> tree /blob/<hexlet> blob /style.css the ONE generated stylesheet anything else 404, in the verb's own plain words

A pager target that is NOT a <verb> <arg> line is a REFERENCE, and a reference is RESOLVED WHILE THE PAGE IS PAINTED (ruling 2026-08-15): the emitted href is already final.

<path> -> /cat/<resolved path>#b<landed token byte> <path>:<line>:<col> -> the same, anchored on the line's first token

(or the column's own token)

<path>:<off>:<hashlet> -> index/perma.js follows it; the anchor is the

token's byte TODAY, deleted-line case included

a directory -> /list/<resolved path>/ nothing / SEVERAL -> NO href: plain painted text, never a 404 link

The resolution is main.js seatOf — the pager's own door, split out of its opening so both callers share one mechanism and one fallback order (permalink follow, else the path the fs answers, else the LITE-011 FSEG partial, else the LITE-024 worktree scan). lite serve owns no resolver.

Files

Tests

test/serve/run.sh — 341 lines, new; test/serve/url.js — 291 lines, new. *PASS serve 174 checks* (91 over the wire + the 83-check headless leg). The shell leg builds a THREE-commit git fixture under $HOME/tmp (a subdir, an uncommitted edit, a .mkd carrying a path reference and a ticket code, a name with a SPACE in it, and a fixed-width target.c so every byte offset below is stated arithmetic, not a mined number), refuses five bad invocations in the foreground, then starts ONE server and walks every URL asserting the status line, the content type and the paint: the tok spans and their byte-offset ids, the <a href> targets, the side-in/side-rm wash, the generated .tok-* and :target rules, POST/PUT/DELETE refused with the worktree provably unmoved, HEAD answering GET's own length.

The REFERENCE leg is the bug's own: target.c is seeded with six 13-byte lines, then two lines are prepended and MARK002 is deleted, and two permalinks are minted against the FIRST blob (index/perma.js's own packing, so the test mints no format itself). A ref.c carrying all four forms is served and the RENDERED hrefs asserted:

target.c:6 -> /cat/target.c#b63 the line's first token target.c:<off>:<hl> -> /cat/target.c#b54 MARK004, moved down two lines target.c:<off>:<hl> -> /cat/target.c#b37 MARK002's line, now deleted nosuch.c:3 -> no href at all, still painted as source

then /cat/target.c is fetched and each of those three anchors asserted to name a REAL token of it (id="b54">MARK004, id="b37">int, id="b63">int), plus "no raw reference reaches a url" (no /cat/target.c: and no %3A anywhere) and "exactly three resolvable references linked".

url.js is the headless half — the URL table both ways INCLUDING a round trip, theme.js -> CSS, the shared landing math (landAt/tokSpanAt over a known three-line text), the painter over hand-built hunks, and every branch of reference resolution driven through a STUB door: file:line, file:line:col, a permalink's own token bytes, a directory, a miss, an ambiguity, a target outside the repo, an untokenised target, the per-page cache (a repeat follows once) and the budget (past it a reference paints plain).

All existing legs still pass, unchanged from the pre-work baseline: 21 of 23 PASS, and lite/diff (1 of 34) + lite/pager (1 of 33) fail identically BEFORE and AFTER this work — both are environment artefacts of this box ($HOME is itself a git repo, which breaks diff's "a non-repository is refused" leg; the pager leg loses pre-queued pty keys). Runtime: a quickjab built from ///quickjab with -DJAB_JSRC=<this tree> -DQUICKJAB_JSRC_PACK=ON ($HOME/bin/lite carries the leaves but packs the OLD lite tree, so it cannot run a modified one). A 220 KB source served as a 2.9 MB page intact, which is the net write-FIFO growth path.

Open question for gritzko

A ticket code resolves to no file (the door tries the path, then the FSEG partial — a code is neither), so it renders as plain painted text. If a ticket code should instead reach lindex <code>'s backlink suspects, that is one row in ROUTE plus one branch in refResolve. An AMBIGUOUS reference (several files answer) is likewise plain text: the pager shows a chooser there, and a chooser page would need a route of its own.

Suggested commit message:

LITE-034: lite serve — the repo browser over HTTP