# @lotics/ui — the UI reference index

The component kit for Lotics custom-code apps and the product frontend. One code path, the web one.
It pairs with **`@lotics/app-sdk`** (data + RPC — read `node_modules/@lotics/app-sdk/AGENTS.md`):
the SDK fetches and mutates, this kit draws.

**Read in this order, and stop as soon as you have the answer.**

1. [`llms.txt`](./llms.txt) — every published entry with the data role it answers, its import, and
   the one question that says where it does NOT go. One call, ~12k tokens. Start here, always.
2. `dist/<module>.d.ts` — the props of the entry you picked, carrying the source's own doc
   comments. **Never guess a prop; open the file.**
3. `lotics docs ui/catalog/<Entry>` — that entry's full wrong-place test, one row of
   [docs/catalog.md](./docs/catalog.md). Never the whole file.
4. The owning area doc below, for the shape you are building.
5. `examples/tpl_*.tsx` — a whole screen, as source, for a screen no shape covers. Copy and
   adapt; never import.

**You customize through TOKENS and PROPS**, plus `className`/`style` on an entry's root for
placement in your own layout. The DOM, the class names and the stylesheets are private.

| Doc | Read it for |
|---|---|
| [docs/constitution.md](./docs/constitution.md) | **Every number the kit is built on, on one page — read it first.** Each rule names the suite that checks it, and every other doc derives its numbers from here. |
| [docs/composition.md](./docs/composition.md) | Which shape carries which meaning — the laws a screen breaks while every test passes. |
| [docs/hierarchy.md](./docs/hierarchy.md) | What a screen shows and at what weight — the three tiers, one primary act, one fact in one place, and the budgets a reviewer counts. |
| [docs/data_entry.md](./docs/data_entry.md) | Which editing pattern for which job. |
| [docs/ai_patterns.md](./docs/ai_patterns.md) | AI acts, the human stays in charge. |
| [docs/theming.md](./docs/theming.md) | How a screen is painted, and how you repaint it. |
| [docs/reviewing.md](./docs/reviewing.md) | The six checks before you commit, then the gates and probes for when a screen has to be right. |
| [docs/templates.md](./docs/templates.md) | Which app-screen shape your job is, and the record its rows open. |
| [docs/testing.md](./docs/testing.md) | What a unit runner must be told before it can load a kit module, and the three anatomies where a driver must do something other than what the a11y tree suggests. |

## Iron rules

- **`import "@lotics/ui/styles.css"` in the app entry, once.** It carries the tokens every kit
  colour, size and radius reads; without it each `var()` is invalid and the screen renders
  unstyled but working. Theme by declaring the shadcn variables in your own CSS — there is no theme
  function. → [theming.md](./docs/theming.md).
- **Reuse first.** Before hand-rolling ANY capability, read `llms.txt`. A component the kit lacks
  is built INTO the kit, never hand-rolled beside it.
- **An entry from an UNPUBLISHED build is not installable.** `npm view @lotics/ui version` lists
  what exists; never pin one it does not, because the sandbox build resolves the same registry and
  fails on it.
- **A screen the plan names is a SHAPE, imported** — one per registry row, each with its own entry
  in `llms.txt` and its own section in [docs/templates.md](./docs/templates.md) § The shape
  taxonomy. Hand one the rows and its slots; swap a slot's device through `render`. Never a
  hand-rolled `Table` for a register the registry names.
- **The record that door opens is `RecordPage`**, each section's body the component its data KIND
  names — `RecordProgress`, `RecordExpectedSet`, `RecordChildren`, `RecordFiles`, `RecordFacts`.
  → [templates.md](./docs/templates.md) § The record.
- **One canonical entry per data role.** `llms.txt` names it; a row reading "folds into X" is an
  entry the next major absorbs, so write the call site as X.
- **Responsive layout measures the CONTAINER, not the screen.** `useContainerSize()` reports the
  nearest `SizeBoundary`; wrap one around any region whose width stops tracking its parent's.
  `useScreenSize()` answers a question about the DEVICE.
- **A view the reader ARRANGED survives the reload.** Filters, grouping, sort and folded bands go
  through `usePersistedState(key, fallback, codec)` — browser storage, per app origin, so it is a
  per-viewer convenience and never a shared or durable fact. Its `codec.read` is a required
  ALLOWLIST: a stored option key whose option has since been retired must restore as "off", not as
  an empty register.
- **No user-facing string is a literal in `src/` when a locale slice already names it** — every one
  resolves prop → `useLoticsLocale()` slice → the English pack.
  → [composition.md](./docs/composition.md).
- **A section's ADD rides its heading row, right edge** — a truly EMPTY region stands its toolbar
  down and moves that one act into the state. → [composition.md](./docs/composition.md).
- **A section heading's RUNG is the surface's answer, never the call site's.** There is ONE
  `SectionHeadingTitle`, and the container publishes the altitude it renders at.
- **A region has ONE state entry, and a FAILED read is not an empty one.** `RegionState`'s
  `state` is what the region can ASSERT: `loading` / `error` / `empty` / `done`.
- **Progress: ONE compound, TWO axes, and both are about meaning.** A run of markable rows is the
  checklist family. **`connected`** asks *is this a sequence*; **`mark`** asks *does ticking mean
  DONE or CHOSEN*.
- **A repeating event is ONE row with a `repeat`, and there is no `timeZone` prop.** A stored
  Lotics datetime is a naive WALL CLOCK — "14:30" means 14:30 to the business — so there is nothing
  to convert and a calendar that re-projected it would move every event. Only *what day it is*
  depends on the reader's location: that is the `now` prop.
- **A wash gives a surface its body; a border — or an accent stripe down one side — is that same
  edge asserted twice.** Light ground with dark ink of one family, never the solid shade under
  white text; `solid()` is for a DOT, where a few pixels carry identity.
  → [constitution.md](./docs/constitution.md), [composition.md](./docs/composition.md).
- **Layout is `Box` / `Stack` (`columns` for equal peers) / `ScrollArea` / `PageContent`, and every spacing prop is a
  RUNG**: 2 / 4 / 6 / 8 / 10 / 12 / 16 / 24 / 32 / 48, each named by the pixel it is, so `gap={12}`
  IS the rung and `gap={14}` does not compile. `className`/`style` stay the escape.
- **A subject over one qualifying line is `CellStack`, never two `Text`s in a `Box`** — it owns the
  caption's rung, the tightening and the zero gap. → [composition.md](./docs/composition.md).
- **The kit's fonts, colours and icons ARE the design system** — never a custom font, icon set or
  hand-picked palette shade; ONE accent per screen.
- **An overlay paints in the order it was OPENED, and a scroller that swaps content opens the new
  content at the top.** Always render the overlay and toggle `open`. The z-index rungs are
  published in `overlay_layer`, so never hand-pick a number to clear a Lotics overlay.
  → [composition.md](./docs/composition.md).
- **ONE VALUE, ONE RENDERING** — the chart draws the entity its table draws, a value's filter
  renders it the way its cell does, and a record's fact draws with the register's own cell.
  → [composition.md](./docs/composition.md).
- **Hand-typed type is off-system, and it always lands too small.** Every run of language is
  `<Text size= weight= color=>`. **Grep the diff: `fontSize:` / `lineHeight:` / `letterSpacing:`
  outside `src/` is a bug.**
- **A `Label`'s `over` is WHAT IT LABELS.** A value takes the default rung; a BLOCK someone acts on
  takes `over="block"`, a rung up and in the page's own ink. It takes no `weight` or `transform` —
  the omitted props are the rule, and all-caps is banned.
- **A fact every row needs is a COLUMN; a fact one row needs expands in place.**
- **ONE LEFT EDGE — a row that paints a wash BLEEDS it, and never insets its content.** Inside a
  `Dialog` that edge is `var(--lotics-dialog-gutter)`, read and never re-derived.
- **Documents get a LIST; images get a GRID**, and a mixed set follows its MAJORITY — never split a
  pile by file type. **Files land THREE ways on any surface that holds them**: the section's Add
  CTA, and a drag OR paste from ONE drop target wrapping the region.
  → [data_entry.md](./docs/data_entry.md).
- **An outcome the reader needs to DO nothing about is `useToast()`**, one they can take back is
  its `undo`, and one that DESTROYS asks through `Alert.confirm` — in the row's ⋯, the selection
  bar's `danger`, or the `DangerSection`. The card is gone in five seconds, so anything carrying a
  verb is a `Callout` in the flow, and anything that persists is a `Status`.
  → [composition.md](./docs/composition.md).
- **A fact the reader may CHANGE carries `onSave`, and the frame it draws IS the permission** —
  no Edit act and no mode. → [data_entry.md](./docs/data_entry.md).

## Keeping this reference current

These docs publish to npm and are read from `node_modules` by authors who cannot see this repo, so
the set is BUDGETED and a paragraph earns its place by stating what the code, a test, a type or a
template cannot. Everything here describes the CURRENT major only.
