Artifact 0231bea210cac72b4be5015e27915eb319e878d49d8d62c10a545a5ecbd2d09b:
- File docs/rfcs/0010-local-mode.md — part of check-in [356a7bb487] at 2026-09-10 16:53:49 on branch rfc-index — RFC index with true statuses; retire 0002 into 0003 §8 (salvage and provenance rules) (user: dmestas, size: 9620) [annotate] [blame] [check-ins using] [more...]
- Feature Name:
local-mode(quarry ui: one repository, one browser tab, nothing to configure — thefossil uianswer, launched fromzit ui) - Start Date: 2026-08-29
- Status: Draft — not built;
quarry uidoes not exist yet - Depends on: RFC 0001 §2 (store access through the zit binary), §8 (OLTP —
what this mode leaves out), §11 (disposability — what this mode must still
pass); zit#165 (the
uiverb is reserved on the zit side and today only explains)
Summary
quarry ui [PATH] serves the one zit repository at PATH (default: the
checkout the command runs in) on a loopback port, with no OLTP database, no
accounts, no tokens, and derived data in a throwaway directory, and opens a
browser tab on the timeline. zit ui is a launcher for it: find the binary,
run it, say what to install when it is absent. Together they give a person
what fossil ui gives — timeline with a graph, side-by-side diffs, file
browser, annotate, branches, wiki, tickets, forum — from one command, on
one machine, for one repository.
Everything the page shows is derived from the store through zit's read
verbs exactly as quarry serve derives it. Local mode is a profile of
serve, not a second server: the same ingest, the same renderers, the same
ops layer, with the fleet, the OLTP, and the auth gates switched off at the
door.
Motivation
RFC 0001 chose a separate binary over browser pages inside zit so the forge
could scale and change without zit. The cost of that choice, unpaid until
now, is the single-developer case: fossil ui is the largest single reason
people name for staying with Fossil, and it costs them nothing — no data
directory, no user table, no port to pick. zit's answer has been "run
quarry", which means quarry serve, which means a data directory, an OLTP
file, and a listen address. Most of that is fleet furniture. A person
looking at their own repository needs none of it.
zit#165 weighed three shapes and rejected two: a browser inside zit serve
(a second timeline renderer, forever slightly behind) and static renders
alone (diff --by and timeline -F html shipped; they cover the two pages
opened most and no more). This RFC is the third: the real pages, from the
real renderer, with the configuration removed rather than hidden.
Guide-level explanation
$ zit ui
quarry ui: serving /Users/me/proj on http://127.0.0.1:52173/ (Ctrl-C stops)
The browser opens on the timeline. Every page quarry serve renders for a
repository renders here. Writes that need an author — a wiki edit, a new
ticket, a forum post — are made as the local operator ($USER, or zit's
user setting when set) with no password asked, because the only person who
can reach a loopback port is the one who started the process.
When quarry is not installed:
$ zit ui
zit ui: quarry is not on PATH; the browser view is quarry's single-repository local mode
install it from https://github.com/danmestas/quarry, or set ZIT_QUARRY to the binary
meanwhile: 'zit diff --by' renders a side-by-side page and 'zit timeline -F html' renders the timeline
Stopping the process removes everything it made: the derived data lived
under the system temporary directory and is deleted on exit. Starting again
re-derives it from the store. That is the whole recovery story, and it is
the same story quarry serve has — only the directory is shorter-lived.
Reference-level explanation
1. quarry ui
quarry ui [PATH] [--addr ADDR] [--data DIR] [--no-open] [--user NAME]
- PATH defaults to the current directory. It must hold a zit store (
.zit) or be a checkout whose root does; the store is located exactly asquarry servelocates a single positional repository. --addrdefaults to127.0.0.1:0: the loopback interface and a kernel- chosen free port. Binding anything but a loopback address is refused, since the mode removes authentication; a person who wants to expose a repository runsquarry serve.--datadefaults to a fresh directory under the system temporary root, removed when the process exits. Naming a directory keeps it (a warm cache for a large repository); it stays derived state and can be deleted at any time.--no-openskips the browser. Otherwise the URL is opened with the platform opener (openon macOS,xdg-openelsewhere) once the first ingest round has completed, so the tab lands on a rendered timeline rather than an empty one; the URL is printed either way.--usernames the author for writes; default$USER.
Everything else in serve's flag set is off and unavailable: no --nats
(embedded only), no --notify, no --dagnats (CI status still renders when
the store carries verdict tags per RFC 0009; nothing is triggered), no
mirrors, no bulk operations, no analytics store, no error log file, no
--tracker, no --forum-hook.
2. What is switched off, and how
- OLTP (RFC 0001 §8). No
oltp.dbis created or opened. Thesync_usertable's only job — HTTP Basic for write ops — is replaced by a fixed identity: every request is the local operator. This is the one place local mode differs from serve in behaviour rather than configuration, and it is implemented as an auth provider, not a branch in each handler: the ops layer (§9) already receives the authenticated user through context; local mode supplies a provider that always answers--user. - Sync fronting (§7). The
/xferendpoint is not registered. A local page has no peer to front for, and registering it would expose an unauthenticated push. - Fleet.
/fleet, the repo registry, accounts, webhooks, and the API routes that create repositories are not registered. The single repository is the whole namespace; its pages live at/as they do under serve with one repository. - Ingest. Runs as under serve: one poll loop,
--sync-intervaldefault, single-writer lease held by this process. A secondquarry uion the same store gets its own data directory and its own lease; they do not conflict because neither writes the store except through zit verbs.
3. zit ui (the launcher, zit#165)
zit ui [ARGS...] runs quarry ui <checkout root> ARGS... with stdio
inherited and returns quarry's exit status. The binary is $ZIT_QUARRY
when set, else quarry on PATH. When it cannot be started the verb
prints the three-line explanation above and exits 1; when it runs, zit adds
nothing — every line on the terminal is quarry's.
zit keeps no knowledge of quarry's flags: ARGS pass through verbatim, so a new quarry flag never needs a zit release. The only zit-side contract is the positional PATH and the exit status.
4. Disposability (§11), applied
Local mode's data directory is the §11 test's step 2 (rm -rf the data
directory) performed on every exit. The mode therefore adds one row to the
§11 corpus run: serve a fixture store in local mode, snapshot every page
class, restart, snapshot again, semantically diff. Nothing local mode
renders may come from anywhere but the store and the derived directory.
Drawbacks
- A second binary to install. The launcher's job is to make the absence a one-line, actionable message rather than a search; it cannot make the install disappear. Packaging quarry beside zit in the release workflow is the mitigation, and a separate change.
- First-run latency on a large store: ingest before the first page. The
--data DIRwarm-cache escape exists for that case; the default stays disposable because a stale warm cache that silently diverges is the worse failure.
Rationale and alternatives
- A profile, not a fork. Every page class that exists under serve exists under ui, because they are the same handlers. The alternative — a curated subset for local use — starts a second product.
- Loopback only, no auth, fixed identity. The three go together: the
network boundary is the auth boundary. Offering
--addr 0.0.0.0with "be careful" would be the shallow-module trap in security form. - Delete on exit rather than keep a cache. Chosen for the default because it is the §11 test made habitual. The flag keeps the cache for those who measure the cost.
Prior art
fossil ui: the reference behaviour — loopback, no login, browser opened, one repository. Fossil also serves the repository's own skin and TH1; local mode serves quarry's one skin.git instaweb: the same shape for git, configured through git config; the launcher here takes an environment variable instead so nothing lands in the repository.
Unresolved questions
- Whether
zit uishould wait for quarry to exit or detach after the URL is printed. Waiting matchesfossil uiand keeps Ctrl-C meaningful; it is the default proposed here. - Whether the first-ingest wait before opening the browser needs a progress line on large stores, or whether the timeline page's own ingest banner (already rendered under serve) is enough.
Future possibilities
zit ui --port Nas a documented pass-through once quarry's--addraccepts a bare port.- Shipping quarry in zit's release archives so the launcher's install message is never seen on a packaged install.