How a rule lives here
Every rule in this section says what would reverse it. That sentence is the mechanism, not a flourish — a rule you cannot falsify is a rule nobody can reopen.
This section is for whoever works on the library. The pages before it teach the components; the pages after this one say why the components are shaped that way, what was measured, and — for every rule — the evidence that would say it is wrong. Three of them are about what sits beside the library rather than in it: the services, the auth layer and the navigation layer.
That last part is the whole of why the section exists. Any decision here may be reopened, and reopening one should be editing a paragraph rather than winning an argument again. So each rule ends the same way: what would reverse it. Sometimes that is a measurement with a threshold, sometimes a second consumer, sometimes a change upstream. Where the honest answer is nothing, it says nothing and says why the constraint is physical rather than chosen.
Read the layers and the seams first. It is the map the other pages
hang from: which package may import which, and the one rule that governs each place two packages
meet. Three pages own the transversal rules it names: the kernel (the one
failure class, the one wait, the read port, disposal, the timeline mark), the engine
(one read-only door onto DuckDB, abort that interrupts, a worker that may die) and
relations (JoinGraph as the one read-model of a catalog). Then the
arguments, in the order the sidebar lists them: admission,
the references and names and slots; the surfaces
(colour, preferences, the graph and its hardening, analytics and its
timeline and crossfilter, reads and fields); the model (the AI surfaces and tasks); the layers beside
the library (services, auth, how it fails and its hardening, navigation, the shell);
and last how a failure leaves the library, the findings and the tests.
A page whose opening line says to be built states a design that is decided and not yet in the tree. Where such a page and the tree disagree the tree wins, and the disagreement is the work.
Beside a rule, where one exists, is the guard that fails when the code stops matching, written as a Held by line. A rule with a test behind it is a rule you do not have to remember; a rule without one is a rule under review.
Two spellings in a Held by line are load-bearing, and packages/ui/src/documented-evidence.test.ts
runs them. A test cited by its quoted title must be a real it() or describe() in the file
beside it — not a comment, and not a title as you remember it. And `!Something` means the file
beside it asserts that name is off @kanzo-tech/ui's public surface; the guard imports the barrel
and checks. Both exist because the sentence that reaches outside the page making it is the one that
rots: no page here has been wrong about its own subject, and twenty-six were wrong about something
else — a test holds this, that export was removed, this is the only one. Write the declaration
whenever an argument depends on an absence: prose saying "the export has since been removed" is
unfalsifiable, it was wrong about ProgressTrack within hours, and a reader who believed it deleted
a shipped export.
A rule broken three times becomes a test
When a mistake recurs, write the guard rather than the paragraph, and put the reason in the test file, beside the assertion. The document carries a pointer and no restatement.
The rule this library broke most — an untokenised colour — stopped recurring on the day it became a
test, and has not returned. A read of a token no theme declares is the same shape: it fails
silently, and theme-tokens.test.ts is what makes it fail out loud. The rules
still being broken are the ones that exist only as prose. no-literal-hues.test.ts is the model:
the promise, its reason, its enforcement and its own blind spots in one file. data-slot.test.tsx
is the same shape at length, and it was written in the same commit as this rule, over the case that
argued for it — three rounds of a slot being omitted, one regression, a written rule, and nothing
enforcing it.
What this does not yet cover: a claim that reaches outside the file making it. That recurred
twenty-six times across the guidance corpus before anything caught it, and the fix was the same move
one rung further out — packages/ui/src/documented-evidence.test.ts checks that a test cited by
name is a real assertion and that a name declared absent is really absent.
What would reverse it. A guard that costs more to maintain than the defect costs to fix. None on
the roster has; the roster is the *.test.ts files beside the source they scan, as
Conventions says, and ls is the set to recount against.
Held by packages/ui/src/no-literal-hues.test.ts and packages/ui/src/data-slot.test.tsx, the two
that carry their own reasoning and their own blind spots.
A count belongs in a script, not in prose
No guidance document carries a measurement. If a number matters, a test prints it or asserts it; if
no test wants it, it was noise. The same goes for a file:line — cite a file and a symbol.
The export census was written into the guidance as the authoritative correction of an earlier wrong count, and three careful independent recounts then produced three different answers, each wrong in a different direction. Prose survives; numbers and paths rot.
What would reverse it: nothing, and the recounts disagreed for reasons that generalise. The
repository carries whole working copies of itself under .claude/worktrees/, so a scan that does not
exclude them reports every symbol as consumed and no export as dead. And an intra-module reference
join has to key on absolute declaration position: keying on the name misses aliased exports, keying
on the line collides with same-line parameters. A count that three passes get wrong three ways is a
count nobody should retype. packages/ui/src/index.test.ts pins the surface it cares about by name,
and lib/token-color.test.ts counts the slots in every shipped theme rather than trusting either
constant that declares it.
Held by packages/ui/src/index.test.ts, the pinned surface; packages/ui/src/lib/token-color.test.ts,
"declares a full set of slots, or none at all, in every theme".
An audit is a map, not an oracle
Verify every finding against the code before acting on it — including findings on this page, and including your own. Audit a commit, never a working tree, and say which commit in the report's first line.
The first architecture audit did not survive contact with the code: the status-token rename it asked
for was applied and fully reverted, the "dead" Appearance type was live and load-bearing, and one
migration step broke at runtime when applied literally. Every later round has had to correct its own
premises.
The sharpest instance is a failure mode of its own, and it is why the commit clause is here. One audit ran against another session's uncommitted working tree, so several findings quote strings that exist nowhere on this branch — a ramp count, a test fixture, a whole file. Those findings were not stale; they were measuring a different tree, and no amount of care in the reading would have caught it, because the text they cite is real somewhere. The corollary for a reviewer: when a finding cites a string you cannot find, the first hypothesis is not that the finding is wrong, but that you and the auditor are looking at different trees.
What would reverse it: nothing. Each round of auditing has produced its own falsified findings, and the rule has never had a counter-example.
Held by packages/theme/src/index.ts, Appearance — the type an audit called dead, still exported
and still load-bearing.
A defect visible in docs/ is a library defect until proven otherwise
Fix it at the source. A containment rule in the consumer is a fix for one consumer.
The library's stylesheet once shipped the typography plugin under .prose, whose generated
selectors outrank Preflight — so every consumer that happens to use that class name (fumadocs does,
and it is a common choice) had its anchors, headings, lists and tables repainted by a design system
it had only asked for a Button. The first fix was a containment rule in the docs app; the same bug
came back, and the real fix was renaming the plugin's class in packages/ui/src/tailwind.css, where
the @plugin "@tailwindcss/typography" block still carries the reason.
The standing corollary: docs/ consumes dist/, not src/. A rename typechecks clean while
the docs build fails, and a fix in src/ does not reach the docs app until the package is rebuilt.
What would reverse it for a given defect: one that is genuinely the docs app's own — its MDX pipeline, its routing, its Shiki rendering. The test is whether a consumer who never opens our docs would hit it.
Held by packages/ui/src/tailwind.css, the @plugin "@tailwindcss/typography" block and the comment
above it.
The packages ship Tailwind source, and the page is one build
@kanzo-tech/ui and @kanzo-tech/ai publish tailwind.css, an entry the consumer imports after
its own @import "tailwindcss". Each declares its own plugin, variant, tokens and @source,
relative to where it sits in the tarball; the consumer compiles once. It is the model Tailwind v4
describes for libraries and the one daisyUI, HeroUI and Flowbite ship.
They used to ship compiled sheets, and three Tailwind builds on one page cannot be ordered. ai's
.hidden came after ui's md:block and the sidebar was display: none at every width — putting
both in the same layer does not help, because two separately compiled sheets still sort by which
loaded last. A @custom-variant does not survive compilation either, so every host restated
dark, and every host @sourced streamdown, a dependency of ai's. None of it showed here: the
docs ran one build over packages/*/src, so the tested path was not the shipped one. Now
docs/app/global.css is the three lines a consumer writes, and the entries' @source reads dist/
in this workspace exactly as it does in a tarball.
streamdown is a dependency of ai's, so its @source is written for the two places a dependency of
the package can be installed — its own node_modules (a nested install, and this workspace) and the
node_modules it is installed in (npm's hoisted tree and pnpm's virtual store alike). @source takes a path, not a specifier, so
this is the resolution Tailwind leaves to the package; absent, neither path exists and nothing is
emitted.
What would reverse it. A consumer that is not Tailwind v4 — the decision is that there is none.
Or @source gaining module resolution, which would collapse streamdown's two lines to one.
Held by nothing. The install smoke test compiled these three lines against the packed tarballs
until 2026-09-29, when it was deleted; a wrong @source path now shows up as a consumer's missing
styles, not as a red gate.
A chart fails silently and well-painted
Chart work is verified in a browser against a real coordinator. A unit test is not evidence that a chart works.
The failures this layer produces render cleanly: an empty plot with correct axes, a legend for a
scale nothing uses, a brush that publishes into a selection no client reads. A DuckDB binder error
surfaces as a chart with no marks, and jsdom has no DuckDB, so the test covering the component
passes. charts/chart-inputs.test.tsx and charts/chart-stat.test.tsx say so in their own headers:
the coordinator there is a double that plays the moves a real one makes.
Two operational consequences. The docs app builds and serves charts under --webpack only — vgplot
trips a temporal-dead-zone error under Turbopack. And one coordinator serves a whole page: the
active-coordinator setter is process-wide, so a second one leaves the first page's charts empty.
Each MosaicProvider still owns its own selections, so one example's brush never reaches the next.
What would reverse it. A harness that can run the real coordinator headlessly and assert on marks rather than on React output.
Held by packages/ui/src/charts/chart-inputs.test.tsx and packages/ui/src/charts/chart-stat.test.tsx,
whose own headers state the limit.
The size limit moves when an adoption crosses it, in the commit that crosses it
A gauge that a newly adopted component pushes over is raised in the same commit, with the before and after figures written down and the component named. It is never raised on a tree that adopted nothing.
A Tailwind utility only compiles if it is written out as literal text, so a component's appearance
is source that cannot be compressed, and the barrel is their sum. ImageCropperHandle is why:
eight compass positions, each with its own cursor, its own two border sides and its own hover,
written as sixteen literal class strings because that is the only form Tailwind's scanner reads. A
template literal or a lookup keyed on position would compress to nothing in the bundle and emit no
CSS at all — a defect that renders as an unstyled handle rather than as a build error.
It has fired twice, and the second time taught something the first did not. image-cropper crossed
42.5 kB (41.94 → 42.60 kB brotlied, measured by deleting its one export * line and rebuilding),
and the limit went to 43 kB. Suggestions then crossed 43 kB (42.86 → 43.06 kB) and it went to
43.5 kB. Both times the new limit keeps the same ~1% of headroom the crossed one had, so the entry
stays a tripwire rather than becoming a ceiling nobody meets. The overage sat in the tree failing
on purpose for a session before it moved, because the component that crossed it arrived alongside
a compound rewrite, and raising a budget in the middle of that would have been raising it for the
wrong reason. And the figure the rule wants is not the component's own size: what the barrel pays is
what did not already exist in it.
This licenses nothing about the other entries, and it excuses no component from admission. A component with no renderer costs the same bytes and buys nothing; the budget is not the gate, admission is.
What would reverse it. The same measurement rising on a tree where no module was added — that is a regression the budget exists to catch, and no adoption pays for it. Or a build step that emitted class lists from something Tailwind could scan, which would make the strings compressible after all.
Held by packages/ui/package.json, gauges; packages/ui/src/simples/image-cropper.tsx,
ImageCropperHandle.
A gauge per entry, and its raises written here
Every published entry of every package has one gauge, and it measures what that entry adds: the
entry bundled with every package it imports left out, and for a subpath the root's modules left out
too. A root gauge measures the root; a subpath gauge measures the subpath over the root, which is
what an application that imports both pays for it. Beside them stays the gauge of a tree-shaken import
— { Button }, { Dashboard } — which is the cost a consumer actually meets. The entries are read
from each manifest's exports by scripts/gauges.mjs, which every package's .size-limit.mjs
calls, so a subpath published without a limit fails pnpm size rather than going unmeasured; the
limits are each manifest's gauges.
The gauges this replaced, until 13 October 2026, measured something else. The analytics subpath's
counted, in isolation, every root simples/ module its inputs render — about a third of it — which
dedupe in any application that imports the root, so each adoption raised a figure that did not say
what anybody pays: over the root it is 27.42 kB, where the isolated gauge read 38.22. Five entries
had no gauge at all — @kanzo-tech/ui/editor and /table, @kanzo-tech/graph/section,
@kanzo-tech/llm and /local, and @kanzo-tech/theme. And each gauge's ignore list was a hand-kept
copy of the build's externals; every package import is left out now, by rule, because what is
measured is our code — the analytics gauge counted valibot and every @uwdata/* package until they
were added to its list, and that was somebody else's code.
The reason for each raise is written on this page, not in package.json. The //size-limit
notes in the manifests had grown into dated ledgers — @kanzo-tech/ui's past twenty raises — the
record of every decision about a budget in the one place a reader of the decisions never looks, and a
changelog in all but name, which this repository does not keep. They are the table below, one row
per raise; a manifest keeps its limits and one sentence pointing here. The rule above, the size
limit moves when an adoption crosses it, is unchanged; only where its record lives moved.
What would reverse it: a consumer whose bundler does not tree-shake, for whom the whole barrel is the cost — then a whole-library gauge comes back for that entry, named as that consumer's.
Held by scripts/gauges.mjs, gauges; packages/ui/package.json, gauges.
What each package's gauges watch for
@kanzo-tech/ui: the root is the whole barrel, a change detector for what the library ships;{ Button }is the regression gate for a consumer's cost, and has stayed near 1.3 kB through every raise above it.{ Dashboard }counts the lazily imported editor too: that a read-only dashboard loads no editor is held bycharts/dashboard-layering.test.ts, not by a number.@kanzo-tech/ai:AssistandChattree-shake apart, so a host that only assists fields does not ship the transcript, and the markdown parser comes withChatalone.@kanzo-tech/authand@kanzo-tech/navigation: a door reaching through another. The root must never learn a protocol or a router, and the Node doors must never learn React; a jump of kilobytes on any of them means one of those happened.@kanzo-tech/mosaic: the only gauge that measures it, since graph and ui leave it out. esbuild warns thatimport.metais empty under the format it measures with; that isengine.ts'snew URL(…, import.meta.url), which changes what a URL resolves to and not the size.- Every first limit is set just above its first measurement: a budget with 70% headroom detects nothing.
The raises
Sizes are minified and brotlied. Before 13 October 2026 a subpath was measured in isolation, so its rows are not comparable with the gauges above them.
| Date | Gauge | Limit | Measured | What crossed it |
|---|---|---|---|---|
| before 2026-07-31 | ui root | just above | 35.27 kB | first measurement |
| before 2026-07-31 | ui analytics (then charts) | 4 → 6 kB | 5.47 kB | the five preset charts became a composable grammar |
| before 2026-07-31 | ui analytics | 6 → 60 kB | 56.01 kB | ChartFilter, ChartSearch, ChartSlider brought Select, Combobox, Input, Field and Slider into the subpath — modules that dedupe with the root |
| before 2026-07-31 | ui root | 38 → 42 kB | 40.66 kB | the AI-assist compounds |
| 2026-07-31 | ui analytics | 60 → 68 kB | 66.30 kB | rebaselined: the gate was already red, and the growth predates the branch that measured it (useThemeTick moved it 0.03 kB) |
| 2026-08-18 | ui root | 42 → 42.5 kB | 41.54 → 42.12 kB | the core's axes as a generated declaration the barrel carries (/docs/design/colour) |
| 2026-08-20 | ui root | 42.5 kB, not raised | 42.12 → 42.32 kB | complete, suggest, use-ai and the Suggestion type left for @kanzo-tech/ai; Diagnostic and Questionnaire came in |
| — | ui root | 42.5 → 43 kB | 41.94 → 42.60 kB | image-cropper (above) |
| — | ui root | 43 → 43.5 kB | 42.86 → 43.06 kB | Suggestions (above) |
| 2026-09-30 | ui root | 43.5 → 44 kB | 43.57 kB | FieldRequiredIndicator's fallback, the Empty*, Stat* and Gated* parts — four issues from one consumer |
| 2026-09-30 | ui root | 44 → 45.5 kB | 45.09 kB | LanguagePicker, useAsyncCollection, useDebouncedCommit, the Field bridge on DatePicker and SegmentGroup, FieldHelper's tone, FieldGroup's columns, LocaleProvider |
| 2026-09-30 | ui root | 45.5 → 46.5 kB | 45.98 → 46.41 kB | the shared control height, DatePicker's granularity and segmented DateInput, DatePickerTimer |
| 2026-09-30 | graph root | — | 19.07 kB; 19.13 → 15.29 kB | first measurement; the whole-graph store deleted the tileset, and the budget followed it down |
| 2026-10-01 | ui analytics | 68 → 89 kB | 66.96 → 88.37 kB | the BI kit: Dashboard, DashboardFilters, DashboardStat, ChartCard, DetailTable, FilterChips, useFieldStats |
| 2026-10-01 | ui root | 46.5 → 47 kB | 46.83 kB | ThemePicker and ThemePreview |
| 2026-10-15 | kernel root | 1.5 → 1.7 kB | 1.58 kB | Problem read clause by clause from RFC 9457: the derived type, the cycle guard and depth cap, the foreign-data filter, a rebuild that ignores a wrong-typed member |
| 2026-10-01 | graph root | → 18 kB | 17.71 kB | GraphCounts, GraphSearch, GraphLooks moved in from the workspace showcase; drag-to-fix |
| 2026-10-01 | auth root, ./browser, ./server, ./next | 2.75, 2.25, 3.9, 5.25 kB | 2.22 → 2.6, 1.77 → 2.1, 3.4 → 3.73, 4.59 → 5.03 kB | the failure guarantees: a failed status, a 30 s deadline on every wait, coded IdP and session failures, the problem-page redirect |
| 2026-10-02 | ui analytics | 89 → 89.5 kB | 89.19 kB | v0.19.0's failure model met the BI kit |
| 2026-10-02 | ui analytics | 89.5 → 90 kB | 89.74 kB | DenseStackMark, DetailTable formatting dates by type |
| 2026-10-02 | ui root | 47 → 47.5 kB | 47.20 kB | ThemePicker's swatches preview under manual activation |
| 2026-10-02 | ui root | 47.5 → 48 kB | 47.67 kB | Findings |
| 2026-10-02 | ui root | 48 → 49 kB | 47.73 → 48.55 kB | Problem |
| 2026-10-02 | graph root | → 18.5 kB | 18.01 kB | the square placement, the camera that follows a running layout |
| 2026-10-03 | ui analytics | 90 → 100 kB | 89.39 → 99.91 kB | dashboard spec v2: the editor as a side sheet, the RelationPicker, the chip filter bar |
| 2026-10-03 | graph root | → 19 kB | 18.58 kB | GraphSelect moved in from the hosts |
| 2026-10-03 | graph root | → 22.5 kB | 18.8 → 22.04 kB | the Discover integration: GraphPlacement, label levels, the arrangement kept across canvases, GraphSearch as a palette, GraphInspector |
| 2026-10-03 | ai ./data | 6 → 6.5 kB | 5.12 → 6.18 kB | gateStatement, a security fix |
| 2026-10-05 | auth root, ./server, ./next | 2.5 (lowered), 5, 7 kB | 2.6 → 2.37, 3.73 → 4.75, 5.03 → 6.65 kB | ./browser gone; back-channel logout, a transaction cookie per state, renewal in place, the proxy as session authority |
| 2026-10-05 | graph root | → 22.3 kB (lowered) | 22.29 kB | the named looks left GraphLooks |
| 2026-10-06 | ui root | 49 → 49.5 kB | 48.73 → 49.14 kB | Pref, usePref, SectionProvider (#79) |
| 2026-10-06 | ui analytics, { Dashboard } | 32.5, 31.5 kB | 31.22 → 32.31, 29.88 → 31.09 kB | re-gauged without what the build externalises, then block A: the schema, parseDashboards, the staged recommender, the band of figures |
| 2026-10-06 | ui root | 49.5 → 50 kB | 49.42 → 49.51 kB | CommandDialog's hotkey, one useHotkey (#77) |
| 2026-10-06 | auth ./server, ./next | 5.5, 7.5 kB | 4.78 → 5.25, 6.71 → 7.29 kB | one credential model: per-call token exchange (RFC 8693) (#97) |
| 2026-10-07 | ui analytics | 32.5 → 32.6 kB | 32.31 → 32.54 kB | MosaicClients, a bridged clause as its parts |
| 2026-10-07 | ui analytics | 32.6 → 32.9 kB | 32.54 → 32.8 kB | FilterBar |
| 2026-10-07 | graph root | → 22.2 kB (lowered), then 22.5 kB | 22.07 → 22.12, 22.49 kB | GraphSelect left for usePick; the search reading the subset |
| 2026-10-09 | ui analytics, { Dashboard } | 33.4, 31.8 kB | 33.37, 31.77 kB | ChartTimeline (#143) |
| 2026-10-09 | ui analytics, { Dashboard } | 33.9, 32.3 kB | 33.8, 32.25 kB | the timeline's sticky window, one bar a tick, years read as years |
| 2026-10-09 | graph root | → 22.75 kB | 22.71 kB | the tools on what is in full colour (#112) |
| 2026-10-09 | graph root | → 23 kB | 22.71 → 22.95 kB | the test hook's support |
| 2026-10-09 | graph root | → 23.5 kB | 22.95 → 23.3 kB | GraphTimeline (#143) |
| 2026-10-09 | ai ./data | 6.5 → 6.75 kB | 6.22 → 6.68 kB | AnswerCard reading the host's dashboards; answerRelationsOf |
| 2026-10-10 | ui root | 50 → 51 kB | 49.88 → 50.75 kB | the findings model |
| 2026-10-10 | ui analytics | 33.9 → 34.8 kB | 33.82 → 34.70 kB | RelationPicker reading each hop as a sentence (#160) |
| 2026-10-10 | ui analytics, { Dashboard } | 34.2 → 34.8 → 35.6, 32.5 → 33.1 → 33.9 kB | 34.19 → 34.72 → 35.54, 32.48 → 33.04 → 33.88 kB | brushes handed over on a rebuild; one clause source per chart; play inside the brushed range (#154) |
| 2026-10-10 | ui analytics | 35.6 → 36.5 kB | — | #154 met #160 on main |
| 2026-10-10 | graph root | → 24 kB, then 24.1 kB | 23.41 → 23.84, 24.07 kB | the map saying what it places; #154 met #156 |
| 2026-10-10 | ai ./data | 6.75 → 7.25 kB | 6.68 → 7.08 kB | reading through Engine.query with the chat's signal (ui#55, ui#47) |
| 2026-10-10 | ai ./data | 7.25 → 8.5 kB | 7.15 → 8.39 kB | each type described once, the context budget |
| 2026-10-10 | mosaic root | 3.5 kB | 3.27 kB | first measurement |
| 2026-10-11 | ui analytics, { Dashboard } | 37.7, 34.4 kB | 36.39 → 37.6, 33.86 → 34.32 kB | FilterBar on one line: ScrollArea, Tooltip, Separator |
| 2026-10-11 | ui analytics, { Dashboard } | 37.9, 34.5 kB | 37.87, 34.44 kB | every part reading a relation over one page selection |
| 2026-10-11 | ui analytics, { Dashboard } | 38, 34.6 kB | 37.99, 34.56 kB | the timeline's range from the brush; a chart withdrawing its last clause |
| 2026-10-11 | ai ./data | 8.75 → 9 kB | 8.58 → 8.94 kB | an answer's verbs as icon buttons |
| 2026-10-11 | mosaic root | 3.5 → 3.8 → 3.85 kB | 3.32 → 3.77 → 3.82 kB | one crossfilter across relations; clauseAntiJoin (#175) |
| 2026-10-12 | ai ./data | 9 → 10.2 → 9 → 9.5 → 9.75 kB | 10.14 → 8.85 → 9.46 → 9.57 kB | Ask as a Task; dataAgent gone; AskChips; verify and a grammar |
| 2026-10-12 | ai ./task | 850 B → 1 → 1.2 kB | 799 B → 979 B → 1.1 kB | propose; verify and a grammar |
| 2026-10-12 | mosaic root | 3.85 → 4.1 kB | 3.80 → 4.09 kB | resolveRelation |
| 2026-10-13 | ui analytics, { Dashboard } | 38.3, 34.9 kB | 37.84 → 38.22, 34.53 → 34.86 kB | a clause outlives the part that published it |
| 2026-10-13 | mosaic root | 4.1 → 4.15 kB | 4.09 → 4.11 kB | the json extension |
| 2026-10-13 | ai ./data | 9.75 → 9.9 kB, approved by the owner | 9.57 → 9.85 kB | an answer's read became a query on the host's QueryClient — the statement split from the read so it can key it, the key on the engine, Stop cancelling the query (/docs/design/reads); @tanstack/react-query left out like every other peer, and the root did not move (12.67 kB) |
| 2026-10-13 | ui { Dashboard } | 34.9 → 35 kB, approved by the owner | 34.86 → 34.99 kB | useFieldStats became a TanStack Query query on the host's client — the key on the coordinator, the exclude applied as select, the cancel of a queued summary — less queryFieldStats (/docs/design/reads); analytics went 38.22 → 38.24 kB under its 38.3 kB, and the root (50.99 kB) and { Button } (1.29 kB) did not move |
| 2026-10-14 | ui root, then measured whole | 51 → 51.1 kB, approved by the owner (standing approval, 2026-10-14) | 50.99 → 51.016 kB | #200's Eyebrow landed with the unused names of cleanup PR 1 leaving the barrel (KanzoTheme stays in the bundle, drawn by ThemePreview); { Button } stayed at 1.29 kB, analytics measured 38.29 kB and { Dashboard } 34.99 kB, under their limits |
| 2026-10-14 | auth ./next, over the root | 7.1 → 7.75 kB, approved by the owner (standing approval, 2026-10-14) | 7.03 → 7.72 kB | cleanup PR 6: every refusal the routes, the forwarder and the proxy send is an RFC 9457 problem body under one status table, the refresh route answers the session with its tenant, and a required session's outage redirects to the problem page; the root went 2.46 → 2.38 kB (the session a query on the host's client, claims, readSession and singleFlight off it) and ./server 5.14 → 5.18 kB (claims on it, organizationGroups deleted), under their limits |
| 2026-10-14 | llm ./local, ./testing | 1.6 → 2.7 kB, approved by the owner (standing approval, 2026-10-14); 1.2 kB (new) | 1.55 → 2.66 kB; 1.15 kB | cleanup PR 4: localModel answering from a script a test registered on the hook. The gauge follows /local's lazy import("./testing.js"), so the rise is the double, which a production page never fetches, more than the branch that reaches it; ./testing is the double itself, over ai/test, left out like every peer. The root went 415 → 413 B with the AI SDK's names gone, under its 450 B; testing's root 5.14 → 5.27 kB and ./dom 914 → 929 B with CDK's names and the model scripts, under their 10 kB |
| 2026-10-13 | every entry | one gauge each, over the root | see below | a gauge per entry (this section) |
| 2026-10-14 | ui root, ai root | 51 kB and 20 kB, not raised | 50.56 → 50.69 kB, 12.84 → 12.56 kB | one question part (cleanup PR 7): Question and its parts came into the ui root from Questionnaire's file with translations, and ai's Question left; { Button } stayed at 1.29 kB |
| 2026-10-14 | auth ./next, over the root | 7.75 → 7.8 kB, approved by the owner (standing approval, 2026-10-14) | 7.73 → 7.78 kB | the forwarder answers an API that does not answer as api/unreachable, a 502 problem, rather than Next's bare 500 (keasy, migrating to v0.46); the root (2.38 kB) and ./server (5.18 kB) did not move |
| 2026-10-14 | llm ./local and ./testing, testing root and ./dom | 2.7 kB, 1.2 kB, 10 kB and 10 kB, not raised | 2.66 → 2.14 kB, 1.15 kB → 237 B, 5.27 → 6.37 kB, 925 → 924 B | a model resolved through one provider registry: /local lost its lazy import of the double and its branch for a test, and gained the registry and localProvider's app-config.json; the double's answering moved from llm/testing into testing's root as scripted, sent into a page as source text, so ./testing is now the SDK's mock over it. The limits stay where they were, with headroom no change here asked for |
| 2026-10-14 | mosaic root | 4.15 → 4.6 kB, approved by the owner (standing approval, 2026-10-14) | 4.11 → 4.53 kB | joins and answers (cleanup PR 9): JoinResolver became the one door to the joins — steps, paths, relation, field, at and crossing's at — and resolve.ts went |
| 2026-10-14 | ui analytics, { Dashboard } | 27.6 → 28.6 kB, 35 → 36.1 kB, approved by the owner (standing approval, 2026-10-14) | 27.3 → 28.51 kB, 34.81 → 36.02 kB | joins and answers (cleanup PR 9): a tile's where and top and tileQuery, the one compiler every tile view reads through; a count of the measured type's keys; #199's whole-number count axis and wider bar labels. ai ./data went 8.97 → 8.61 kB, the answer schema and its second compiler gone |
| 2026-10-14 | mosaic root, ui analytics, { Dashboard }, graph root | 4.6 → 5.2 kB, 28.6 → 29.2 kB, 36.1 → 36.75 kB, 25.25 → 26 kB, approved by the owner (standing approval, 2026-10-14) | 4.53 → 5.14 kB, 28.5 → 29.11 kB, 36.02 → 36.68 kB, 25.15 → 25.97 kB | what a field is (cleanup PR 10, /docs/design/fields): JoinColumn descriptions and relationColumns, a multi-valued property as a step joined once, labelOf and namingColumn; fieldStats over descriptions, useFieldStats reading a counted graph with no query and keeping the joins' facts over a summary, titles from labels; readCatalog, the one read of fossil's four catalog relations and the engine's column types. ai ./data went 8.61 → 8.2 kB, readAnswerRelations and the card's own read gone — 9.39 → 8.97 kB once merged with PR 11's kept chat, under its 9.5 kB, not raised; ui root (50.73 kB), ./editor, ./table and { Button } did not move. auth ./next measures 7.81 kB against its 7.8 kB on this machine on main too, untouched here |
| 2026-10-14 | ai ./data | 9.1 → 9.5 kB, approved by the owner (standing approval, 2026-10-14) | 8.61 → 9.39 kB | a chat kept (cleanup PR 11): Ask's answer and ask parts read back through schemas of their own, each rebuilt from its fields with its tile through parseTile. The root went 12.53 → 13.29 kB with load, save, keep and useKeptChat, under its 20 kB |
| 2026-10-14 | graph root | 26 → 26.5 kB, approved by the owner (standing approval, 2026-10-14) | 25.97 → 26.35 kB | the graph on GraphAr (U1): the id arithmetic and the footers' counts — two more statements in readCatalog, the folding of row groups' nulls and extents, and the one-chunk rule (the catalog's own reading came out 0.6 kB smaller than the manifest's) |
| 2026-10-14 | llm ./local | 2.7 → 3.1 kB, approved by the owner (standing approval, 2026-10-14) | 2.14 → 2.83 kB | an honest finish, a turn per call and a warm load: the finish check, the queue, the stream wrapper, the warm-up and its performance spans. The warm-up and its spans are the separable part (its own commit) |
| 2026-10-14 | mosaic root | 5.2 → 6 kB, approved by the owner (2026-10-15) | 5.14 → 5.90 kB | a graph described by its mapping (/docs/design/fields): withMapping (with the maps fossil writes over a query), typeWords and edgeWords, and splitWords as the one split the picker and Ask both read. ui analytics (29.17 → 28.99 kB) and { Dashboard } (36.68 → 36.65 kB) went down with the picker's own split and Ask's own label gone; ai ./data 8.97 → 8.94 kB; ui root and graph did not move |
| 2026-10-15 | mosaic root | 6 → 7 kB, approved by the owner (2026-10-15) | 5.97 → 6.82 kB | the engine's lifecycle (/docs/design/engine): the statement is interrupted when its signal aborts, engine/lost and memory_limit, and one scanner that redacts every literal from a refused statement before it reaches onFailure |
| 2026-10-15 | graph root | 26 → 26.5 kB, approved by the owner (standing approval, 2026-10-14) | 25.97 → 26.29 kB | the reference grid drawn on a canvas (/docs/design/graph): the 1-2-5 ladder, the lattice and its draw, the ink and the canvas's size, against the CSS layer and its per-frame style writes gone. auth ./next measures 7802 B against its 7.8 kB on main here too, untouched |
| 2026-10-15 | graph root | 26.5 → 28 kB, approved by the owner (2026-10-15) | 26.34 → 27.99 kB | a graph that survives (/docs/design/graph-hardening): graph/too-large, a restored WebGL context, reads that abort and retry, and a canvas a keyboard and a screen reader can use (about 1.35 kB of the 1.65) |
| 2026-10-15 | graph root | 28 → 28.5 kB, approved by the owner (2026-10-15) | 27.99 → 28.4 kB | the graph reads a GraphAr graph (/docs/design/graph): the catalog from graphar_*, the global id computed from the per-type index, the footer reads for counts, on top of the hardening |
| 2026-10-15 | auth ./server, ./next, ./testing | 5.25 → 5.45 kB, 7.8 → 8.71 kB, approved by the owner (standing approval, 2026-10-14); 0.2 kB (new) | 5.18 → 5.41 kB, 7.78 → 8.69 kB; 172 B | the hardening layer (/docs/design/auth-hardening): the CSP nonce, the forwarder's bound and api/silent, a closed same-site check, the static-extension list, the logout token's cap, typ and jti replay check, and the one session reader; ./next includes ./server, so the two rise together; the root went 2.38 → 2.41 kB under its 2.5; ./testing is the new fakeAuth door |
| 2026-10-15 | ui analytics, { Dashboard } | 29.2 → 30 kB, 36.75 → 37.5 kB, approved by the owner (2026-10-15) | 29.09 → 29.82 kB, 36.64 → 37.43 kB | every chart's text alternative (/docs/design/analytics): a visually hidden table of the answers the marks hold, or a summary past 50 rows; the chart probe went and took about 0.13 kB with it |
| 2026-10-15 | mosaic root, ./engine | 7 kB, not lowered; 2.25 kB (new) | 6.78 → 4.88 kB; 2.17 kB over the root | the engine behind its own subpath (/docs/design/engine): engine() and its DuckDB-WASM boot left the root, which is the @uwdata door; the root's limit waits for the rest of the split |
| 2026-10-15 | mosaic ./engine | 2.25 → 2.3 kB, approved by the owner (standing approval, 2026-10-15) | 2.17 → 2.26 kB | the engine keeps the coordinator private (/docs/design/engine): internalCoordinatorOf and internalEngineOver, so GraphRoot, useGraph and MosaicProvider take an engine and the graph's reads stop their statement |
| 2026-10-15 | auth root, ./server, ./next; ./testing gone | 2.5 → 0.8, 5.45 → 1.25, 8.71 → 3.35 kB (lowered) | 2.3 kB → 785 B, 5.44 → 1.24 kB, 8.48 → 3.32 kB | the relying party moved to the platform's edge (/docs/design/auth, The session is the platform's edge): the routes, the stores, the sealed cookie, the issuer, renewal, back-channel logout and the exchange went; ./server is jose's verification of the forwarded token, the root a provider handed the session as a prop |
| 2026-10-15 | auth ./server | 1.25 → 1.3 kB, approved by the owner (standing approval, 2026-10-15) | 1.24 → 1.26 kB | the forwarded token must be an access token (/docs/design/auth): typ: "Bearer" is checked, so an ID token signed by the same realm is refused as token/refused; ./next stayed under its 3.35 kB at 3.34 |
| 2026-10-16 | relations root | 5.9 → 6.2 kB, approved by the owner (standing approval, 2026-10-15) | → 6.1 kB | a JoinGraph refused where it is made (/docs/design/relations): checked() at the end of readJoinGraph and withMapping — duplicate type or edge names, a key not among its columns, an edge end or a union member that is not a type, as relations/invalid — on top of readCounts reading fossil:stats as fossil writes it |
A difference of a few bytes on a package the change did not touch is the measurement, not the code.
The components index is generated, and keeps no second list
The docs' component grid is derived from the page tree, in docs/components/components-list.tsx.
Shark's version keeps a hand-maintained thumbnail map beside it; that half is deliberately not
copied. A hand-kept map is a second list of every component, and this repository's dominant failure
is a second way to say one thing — it goes stale on the first rename and nothing fails.
What would reverse it. A thumbnail that cannot be derived and is worth more than the drift. None has been asked for.
Held by docs/components/components-list.tsx.
One changeset per change a consumer can see
A changeset says what a consumer must do differently, in the consumer's words; it is release notes waiting for a release and nothing else. The git tag decides the version, so a changeset carries no bump level. The reason a decision was taken belongs on these pages, not in a changeset.
What would reverse it. A release process in which the changeset, not the tag, decides the version.
Held by .changeset/README.md, which states it beside the changesets themselves.
useDataTableContext
The table instance of the enclosing DataTableRoot, typed by your row type — so a part of your own is a component like any other.
The layers, and the one rule at each seam
Five layers with imports pointing down, a dependency-free kernel at the bottom, one rule for each place two packages meet, and the guards that make the direction a test instead of a sentence.