- Feature Name: `local-mode` (`quarry ui`: one repository, one browser tab,
nothing to configure — the `fossil ui` answer, launched from `zit ui`)
- Start Date: 2026-08-29
- Status: Draft — not built; `quarry ui` does 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 `ui` verb is reserved on the zit side and today only
explains)
## Summary
[summary]: #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
[motivation]: #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
[guide-level-explanation]: #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
[reference-level-explanation]: #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 as
`quarry serve` locates a single positional repository.
- `--addr` defaults to `127.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 runs `quarry serve`.
- `--data` defaults 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-open` skips the browser. Otherwise the URL is opened with the
platform opener (`open` on macOS, `xdg-open` elsewhere) 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.
- `--user` names 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.db` is created or opened. The
`sync_user` table'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 `/xfer` endpoint 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-interval`
default, single-writer lease held by this process. A second `quarry ui`
on 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
[drawbacks]: #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 DIR` warm-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
[rationale-and-alternatives]: #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.0` with
"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
[prior-art]: #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
[unresolved-questions]: #unresolved-questions
- Whether `zit ui` should wait for quarry to exit or detach after the URL
is printed. Waiting matches `fossil ui` and 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
[future-possibilities]: #future-possibilities
- `zit ui --port N` as a documented pass-through once quarry's `--addr`
accepts a bare port.
- Shipping quarry in zit's release archives so the launcher's install
message is never seen on a packaged install.