fix(e10): review round 1, part 2 — the docs the epic invalidated, and three contracts made real
Finishes round 1. Part 1 took the runtime bugs; this takes the prose that had gone false and the three "the comment says so" gaps behind it. CLAUDE.md is auto-loaded into every agent session and ui/README.md is not, so an architecture paragraph that still described a flat components bag was the most expensive stale sentence in the repo: the next agent adding a popover reads it, sees no platform layer, and writes the generic component into components/ — the invisible-layer failure this epic exists to end. It now names components/ui/ and says the boundary is lint-enforced. The canonical-patterns table gained the three rows the epic established: ui/Button over a hand-rolled <button>, a keymap row over a private keydown listener, and disabled-when-busy vs inert-when-inapplicable. ui/README.md's inventory was written in this branch's first commit and never learned about CommandPalette, ContextMenu/useContextMenu, KeyboardHelp or gridColumns — in a section whose own closing rule is that a second component painting the same thing is how the last inconsistency happened. It also claimed "everything is rem-derived" while VirtualGrid's minCol/GRID_GAP are deliberately px; that is now stated as the exception it is, and a new section records which promises here are tests rather than sentences. Three contracts stopped being prose: - A text-less Button must carry a name. "Required for iconOnly" was a doc comment, which is why the migration shipped two nameless icon buttons through eight green lanes. It is a union now, covering both spellings (iconOnly, and size="icon"/"icon-sm"), satisfied by label, aria-label or title. It caught one more on the way in: Banner's dismiss took an optional dismissTitle, so it could render unnamed — it falls back to common.close now. - gridColumns' gutter arithmetic. Every existing assertion survived replacing floor((w + GAP) / (minCol + GAP)) with the naive floor(w / (minCol + GAP)): the fixed widths agree, the capped cases are cap-bound, and the loops compare columnsFor against itself. Pinned at the exact-fit boundaries where the two first disagree (536 and 828). - ShareDialog's link row. Only Copy was migrated in S1, leaving one 2rem button between two 1.75rem ones — and on a coarse pointer .touch-target grew only the migrated one to 2.75rem while the destructive Revoke stayed small. Also: the maxCols docstring said 8 columns of 384px where the test it shipped with asserts 369.
This commit is contained in:
@@ -29,13 +29,13 @@ domain-free.
|
||||
|
||||
## What lives here
|
||||
|
||||
| area | components |
|
||||
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| overlays & anchoring | `AnchoredPopover` (the popover engine: arrows, typeahead, Escape, anchor-flip), `ActionMenu`, `Modal`, `Overlay`, `SidePanel`, `Tooltip`, `Toaster` |
|
||||
| actions & inputs | `Button`, `form`, `DraftSaveBar`, `Combobox`, `FilterSelect`, `SegmentedToggle`, `SortControl`, `ColumnSortControl`, `PriorityStepper`, `UndoToolbar` |
|
||||
| data display | `DataTable`, `tableHeader`, `Pager`, `VirtualGrid`, `readAhead`, `Carousel`, `Avatar`, `CountBadge`, `ItemCount`, `FilterChipGroup` |
|
||||
| page & panel structure | `PageShell`, `PageScroller`, `PanelGroup`, `PanelGroups`, `Tabs`, `BackToTop` |
|
||||
| state & messaging | `QueryState`, `Banner`, `InlineNotice` |
|
||||
| area | components |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| overlays & anchoring | `AnchoredPopover` (the popover engine: arrows, typeahead, Escape, anchor-flip), `ActionMenu`, `ContextMenu` + `useContextMenu` (the SAME menu, opened by right-click / long-press / Shift+F10), `Modal`, `Overlay`, `SidePanel`, `Tooltip`, `Toaster`, `CommandPalette`, `KeyboardHelp` |
|
||||
| actions & inputs | `Button`, `form`, `DraftSaveBar`, `Combobox`, `FilterSelect`, `SegmentedToggle`, `SortControl`, `ColumnSortControl`, `PriorityStepper`, `UndoToolbar` |
|
||||
| data display | `DataTable`, `tableHeader`, `Pager`, `VirtualGrid` + `gridColumns` (the column/width maths, incl. the ultrawide `maxCols` cap), `readAhead`, `Carousel`, `Avatar`, `CountBadge`, `ItemCount`, `FilterChipGroup` |
|
||||
| page & panel structure | `PageShell`, `PageScroller`, `PanelGroup`, `PanelGroups`, `Tabs`, `BackToTop` |
|
||||
| state & messaging | `QueryState`, `Banner`, `InlineNotice` |
|
||||
|
||||
## The size contract (E10 S1c)
|
||||
|
||||
@@ -46,9 +46,11 @@ somewhere:
|
||||
|
||||
- **Geometry is a ladder, not a free choice.** `Button`'s `SIZES` are the rungs, derived from what
|
||||
the app actually used. Pick a rung; do not hand-roll padding on a primitive.
|
||||
- **Everything is rem-derived**, so the Settings text-size slider (`--font-scale` on the root
|
||||
font-size) scales boxes and padding with the label. A `text-[Npx]` font size is an eslint error for
|
||||
exactly this reason.
|
||||
- **Type, padding and box geometry are rem-derived**, so the Settings text-size slider (`--font-scale`
|
||||
on the root font-size) scales boxes and padding with the label. A `text-[Npx]` font size is an
|
||||
eslint error for exactly this reason. The deliberate exception is `VirtualGrid`'s `minCol` /
|
||||
`colMaxWidth` / `GRID_GAP`, which are px: those are layout-engine measurements compared against a
|
||||
container's own pixel width, not text boxes.
|
||||
- **Minimum target: 2.75rem, on coarse pointers only** (`.touch-target`, index.css). A 32px icon
|
||||
button is fine with a mouse and not with a thumb, so the box grows where there is no precise
|
||||
pointer. It grows the real box rather than painting an invisible hit area: two expanded hit areas
|
||||
@@ -58,6 +60,20 @@ somewhere:
|
||||
Controls outside this directory (chips, list rows, the player's overlay buttons) are **not** covered
|
||||
yet -- they are the next thing to bring under the contract, not evidence that there isn't one.
|
||||
|
||||
## Keeping it honest
|
||||
|
||||
Two of this directory's promises are load-bearing enough that prose was not enough, so each has a
|
||||
test rather than a paragraph:
|
||||
|
||||
- the import boundary above → `lib/uiBoundary.contract.test.ts` runs eslint over fixture imports.
|
||||
It exists because the first cut of that rule wrote its regexes in plain `"..."` literals, where
|
||||
`\.` collapses to the any-char `.` — it read like a boundary and let the two commonest violations
|
||||
through, with a green gate saying nothing (E10 review round 1).
|
||||
- the chip lanes' clip → `lib/chipLane.contract.test.ts` counts measurers against lanes per file.
|
||||
|
||||
If you add a rule here, ask what would fail if it stopped being true — and if the answer is
|
||||
"nothing", write the test instead of the sentence.
|
||||
|
||||
## Adding one
|
||||
|
||||
Move a component in when a second module needs it and it is domain-free — not on speculation. Adding
|
||||
|
||||
Reference in New Issue
Block a user