- 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.
