# @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-runtime`** (its SDK is data + RPC — read
`node_modules/@lotics/app-runtime/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.

**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/screen.md](./docs/screen.md) | The app screen — one register and the record its rows open — and which device answers which question. |
| [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.
- **An app screen is one register and the record its rows open**, composed from the kit — the
  way `@lotics/app-runtime` composes every app built from a model. The register is `FilterBand`
  (search, `StatusChips`, `filters`, `GroupByMenu` as its `menu`, `SortMenu` as its `sort`) over a `Table` whose first column is a `Row`;
  the record is `RecordFrame` — `RecordHeader`, `ChecksCallout`, then its sections stacked (fields,
  an `ActGroup` under them, blocks) beside a rail and its comments — at a page or in a drawer, and a linked row, picked or chosen, is one `LinkValue`; a
  screen scoped to one row of another entity names it with `FolderSwitcher`.
  → [screen.md](./docs/screen.md).
- **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 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
  datetime is a naive WALL CLOCK — "14:30" means 14:30 to the business — so a calendar never
  re-projects it; *what day it is* is the `now` prop. An INSTANT (an ISO string with an offset,
  e.g. `__created_at`) is dated in `LoticsLocale.zone`, the workspace's; unset, the viewer's.
- **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.
- **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.
- **Render an overlay always and toggle `open`**; to clear a Lotics overlay, read the z-index rungs
  `overlay_layer` publishes, never a hand-picked number.
- **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.**
- **A picture is its thumbnail; a document its badge and filename**: pictures a GRID, documents a
  LIST, a mixed set both (`FilesEditor`). **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 does nothing about is `useToast()`** — gone in five seconds, so anything
  carrying a verb is a `Callout` in the flow, and anything that persists is a `Status`.

Everything here describes the CURRENT major only.
