Repo: lemonfiber-web
Status: Accepted
The web surface: a static single-page application drawn from the JSON API the
lemonfiber binary serves. TypeScript, Hippocratic 3.0.
Implements: the web side of G1, G3, and the web API contract.
What this repo is
Section titled “What this repo is”A static application with no server of its own. It is built to a directory of
files, tagged, and embedded into the binary as a pinned submodule
(ADR-0012).
At run time it talks to lemonfiber over the
web API and to nothing else.
That constraint is the point, not a limitation. G1-R2 says no surface may
implement behaviour independently; an application whose only capability is to ask
the core and draw the answer cannot violate it.
Why a separate repo
Section titled “Why a separate repo”ADR-0011 records the argument. In short: a Node toolchain inside a Rust workspace whose gates, lints and coverage rules all assume Rust is a poor fit, and component review wants a cadence of its own. The contract being avoided is one already published and versioned for scripts.
What it consumes
Section titled “What it consumes”| From | What | How |
|---|---|---|
lemonfiber |
State and actions | Through @lemonfiber/sdk-ts, which speaks the web API |
brand |
Colour, type, spacing, radii, the logo | @lemonfiber/brand, at build time |
It hardcodes no colour, size or spacing — those are the brand’s to own, and this application consumes them as data.
Both are taken at an exact revision, so both are copies that go stale in silence.
What keeps them from doing so is Q-R68 and Q-R69: the pin is compared against
what its repository serves, and the lockfile is held to the revision the manifest
names rather than to whatever a build once resolved.
The two surfaces
Section titled “The two surfaces”The application serves two audiences, and the spec marks every feature for one or both. They are not the same interface with things hidden:
- The console — an operator’s view. Everything: state, checks, logs, services, setup, household administration.
- The household view — what everyone else gets. Asking for something, seeing whether it is ready, and nothing else. No logs, no services, no settings.
Both are built from one component library and one set of tokens, so they cannot drift apart visually; what differs is which of them a given person is served.
Quality bar
Section titled “Quality bar”The Rust workspace’s standards apply from the first commit, in their web equivalents:
| The workspace requires | Here |
|---|---|
| 100% line coverage | Vitest thresholds at 100 for lines, statements, branches and functions |
unwrap/panic denied |
strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes; any and non-null assertions banned |
pedantic, -D warnings |
typescript-eslint strictTypeChecked, zero warnings tolerated |
| architecture tests | dependency-cruiser layering rules and a file-size guard, in CI |
cargo fmt --check |
Prettier --check as its own gate |
Beyond them, because this surface has requirements the CLI does not: accessibility assertions in component and end-to-end tests (G3 is a goal, not a courtesy), a bundle-size budget, and a content-security policy that permits no external origin.
What it must not do
Section titled “What it must not do”- Reach anything but
lemonfiber. No CDN, no font host, no analytics, no telemetry. Typefaces and assets are embedded at build time, which is also what makes the interface identical on Linux, Windows and macOS. - Implement behaviour. If the answer is not in an envelope, the surface does not know it.
- Talk to the API directly. Transport, the event stream and version negotiation
belong to
sdk-ts; this repo renders what the SDK returns (ADR-0013). - Invent an action. Everything it can do, the CLI can do
(
ARCH-R48).
Related
Section titled “Related”- ADR-0011 — why it is its own repo
- ADR-0012 — how it ships
- web-api.md · design-tokens.md
- lemonfiber.md — the binary that serves it
- 40-quality/tooling.md — why a pin needs something watching it
This page lives in another repository Rendered from lemonfiber/spec at 1d10402, 2026-09-09. Read the source of this page