@olddevs/ui 4.2.2 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/components/navigation/MegaMenu.d.ts +43 -23
  3. package/dist/components/navigation/MegaMenu.d.ts.map +1 -1
  4. package/dist/components/navigation/MegaMenu.js +110 -47
  5. package/dist/components/navigation/MegaMenu.js.map +1 -1
  6. package/dist/components/navigation/useMenuAim.d.ts +8 -8
  7. package/dist/components/navigation/useMenuAim.d.ts.map +1 -1
  8. package/dist/components/overlays/Menu.d.ts +4 -2
  9. package/dist/components/overlays/Menu.d.ts.map +1 -1
  10. package/dist/components/overlays/Menu.js.map +1 -1
  11. package/dist/editor/RichTextEditor.d.ts.map +1 -1
  12. package/dist/editor/RichTextEditor.js +38 -4
  13. package/dist/editor/RichTextEditor.js.map +1 -1
  14. package/dist/editor/core/handle.d.ts +1 -5
  15. package/dist/editor/core/handle.d.ts.map +1 -1
  16. package/dist/editor/core/handle.js +1 -13
  17. package/dist/editor/core/handle.js.map +1 -1
  18. package/dist/editor/core/handleWatchers.d.ts +6 -0
  19. package/dist/editor/core/handleWatchers.d.ts.map +1 -0
  20. package/dist/editor/core/handleWatchers.js +14 -0
  21. package/dist/editor/core/handleWatchers.js.map +1 -0
  22. package/dist/editor/core/tools.d.ts +7 -2
  23. package/dist/editor/core/tools.d.ts.map +1 -1
  24. package/dist/editor/core/tools.js +2 -1
  25. package/dist/editor/core/tools.js.map +1 -1
  26. package/dist/editor/features/toc/RichTextToc.d.ts +63 -0
  27. package/dist/editor/features/toc/RichTextToc.d.ts.map +1 -0
  28. package/dist/editor/features/toc/RichTextToc.js +372 -0
  29. package/dist/editor/features/toc/RichTextToc.js.map +1 -0
  30. package/dist/editor/features/toc/headings.d.ts +31 -0
  31. package/dist/editor/features/toc/headings.d.ts.map +1 -0
  32. package/dist/editor/features/toc/headings.js +25 -0
  33. package/dist/editor/features/toc/headings.js.map +1 -0
  34. package/dist/editor/features/toc/stickyOffset.d.ts +3 -0
  35. package/dist/editor/features/toc/stickyOffset.d.ts.map +1 -0
  36. package/dist/editor/features/toc/stickyOffset.js +14 -0
  37. package/dist/editor/features/toc/stickyOffset.js.map +1 -0
  38. package/dist/editor/index.d.ts +2 -0
  39. package/dist/editor/index.d.ts.map +1 -1
  40. package/dist/editor/index.js +1 -0
  41. package/dist/editor/index.js.map +1 -1
  42. package/dist/i18n/defaults.d.ts.map +1 -1
  43. package/dist/i18n/defaults.js +1 -0
  44. package/dist/i18n/defaults.js.map +1 -1
  45. package/dist/i18n/messages.d.ts +5 -0
  46. package/dist/i18n/messages.d.ts.map +1 -1
  47. package/dist/locales/ru.d.ts.map +1 -1
  48. package/dist/locales/ru.js +1 -0
  49. package/dist/locales/ru.js.map +1 -1
  50. package/docs/api/messages.en.md +743 -0
  51. package/docs/api/messages.md +1 -0
  52. package/docs/api/utilities.en.md +233 -0
  53. package/docs/components/content/rich-text-toc.en.md +130 -0
  54. package/docs/components/content/rich-text-toc.md +129 -0
  55. package/docs/components/index.en.md +3 -2
  56. package/docs/components/index.md +2 -1
  57. package/docs/components/inputs/rich-text-editor.en.md +52 -34
  58. package/docs/components/inputs/rich-text-editor.md +20 -2
  59. package/docs/components/navigation/mega-menu.en.md +86 -61
  60. package/docs/components/navigation/mega-menu.md +81 -56
  61. package/docs/components/overlays/menu.en.md +8 -4
  62. package/docs/components/overlays/menu.md +6 -3
  63. package/docs/conventions.en.md +318 -0
  64. package/docs/getting-started.en.md +5 -5
  65. package/docs/getting-started.md +4 -4
  66. package/docs/glossary.en.md +24 -23
  67. package/docs/glossary.md +23 -22
  68. package/docs/index.en.md +53 -0
  69. package/llms.txt +2 -1
  70. package/package.json +1 -1
  71. package/src/components/navigation/MegaMenu.tsx +300 -140
  72. package/src/components/navigation/useMenuAim.ts +19 -19
  73. package/src/components/overlays/Menu.tsx +12 -2
  74. package/src/editor/RichTextEditor.tsx +107 -3
  75. package/src/editor/core/handle.ts +2 -23
  76. package/src/editor/core/handleWatchers.ts +27 -0
  77. package/src/editor/core/tools.ts +9 -2
  78. package/src/editor/features/toc/RichTextToc.tsx +712 -0
  79. package/src/editor/features/toc/headings.ts +70 -0
  80. package/src/editor/features/toc/stickyOffset.ts +21 -0
  81. package/src/editor/index.ts +2 -0
  82. package/src/i18n/defaults.ts +1 -0
  83. package/src/i18n/messages.ts +5 -0
  84. package/src/locales/ru.ts +1 -0
@@ -0,0 +1,318 @@
1
+ ---
2
+ sourceHash: 84104315608a
3
+ ---
4
+
5
+ # General conventions for `@olddevs/ui`
6
+
7
+ ## Public contract
8
+
9
+ The public API is the runtime exports from [`src/index.ts`](../src/index.ts), the types, the entry points
10
+ described in the documentation, and `styles.css`. The sources in `src/components/` prove the behavior, but they
11
+ are not an import path that consumers may use.
12
+
13
+ ## Semantic prop vocabulary
14
+
15
+ Unless a specific page says otherwise, primitives use three independent axes:
16
+
17
+ | Prop | Values | Question it answers |
18
+ | --------- | --------------------------------------------------- | ---------------------------- |
19
+ | `size` | `sm`, `md`, `lg` | How large is the element? |
20
+ | `tone` | `neutral`, `accent`, `success`, `warning`, `danger` | What does the state mean? |
21
+ | `variant` | `solid`, `soft`, `outline`, `ghost` | How prominent is this value? |
22
+
23
+ States are named positively: `selected`, `disabled`, `loading`. A native ARIA state may use its own correct name,
24
+ for example `checked` on a switch. Do not create alternatives such as `kind`, `status` or negative boolean props
25
+ without a separate public decision. `active` already has one meaning, see below.
26
+
27
+ Props outside the three axes repeat across several components. One concept, one name: a second component with the
28
+ same meaning takes the name from here, and does not invent its own.
29
+
30
+ | Prop | Meaning | Where |
31
+ | ------------ | ------------------------------------------------------------------------------------------- | ------------------------------------- |
32
+ | `appearance` | The form of a variant: the markup changes, not the prominence of the tone | `RadioGroup`, `CheckboxGroup` |
33
+ | `active` | The current navigation item: `aria-current="page"` | `NavItem`, `MobileTabBarItem` |
34
+ | `activeKey` | The same for a data collection: the key of the current node, `aria-current="page"` | `TreeView` |
35
+ | `level` | Heading level, `2`–`4`: the component renders a real `<h2>`–`<h4>` | `SectionHeader`, `NotificationCenter` |
36
+ | `kind` | The kind of **data**, not of an element: what value the field describes and how to enter it | `FilterField` (ADR-0063) |
37
+
38
+ - **`kind` on `FilterField` is a public decision (ADR-0063), not a workaround for the axis.**
39
+ `FilterField` describes a data field, not a UI element: `"values"`, `"number-range"` and `"date-range"` say
40
+ what value the field holds and what input sets it. It is not a presentation (`variant`) or a variant form
41
+ (`appearance`): a description of data has no tone and no markup. Components that draw an element still do not
42
+ get `kind`.
43
+ - **`appearance`, not `variant`.** `variant` is the axis for how a tone is presented, with a closed set
44
+ `solid|soft|outline|ghost` across the whole kit. `appearance="card"` changes the form of the variant itself:
45
+ the border, the indicator and the description inside the label. Under `variant`, such a value would give the
46
+ axis a second meaning: `variant="card"` on one component would sit next to `variant="soft"` on another.
47
+ - **`active`, not `selected`.** `selected` is a choice in a set (`aria-selected`, `aria-checked`): the user
48
+ changes it. The current section is `aria-current`: the route sets it, and the item stays a link. `active` has
49
+ no other values, and it does not become a synonym for `selected`. Where there are no item elements but keyed
50
+ data (`TreeView`), the same concept is called `activeKey`, after `selectedKeys` and `expandedKeys`: one value
51
+ instead of a set.
52
+ - **`level`, not `headingLevel`.** The level depends on where the component is placed, and the consumer knows
53
+ that. `SectionHeader` already had a prop called `level`. A second name for the same concept would silently
54
+ drift from it.
55
+
56
+ ## Accessible name
57
+
58
+ The rule comes from `Toolbar`, `Carousel`, `MobileTabBar` and `TagGroup`. Before it, these components handled the
59
+ required name in different ways: in one, `label` was shown on screen, and in another it went into an attribute.
60
+
61
+ - **`label` is only a visible caption.** A prop with this name draws text on screen. A name that is not drawn is
62
+ set with its own ARIA attribute.
63
+ - **Without a visible caption, use exactly one of `aria-label` and `aria-labelledby`.** The type is
64
+ [`AccessibleName`](../src/lib/accessible-name.ts), a union with `never`: TypeScript rejects a call with no name
65
+ and a call with both. Two sources of one name drift apart silently when you edit.
66
+ - **A name with a default from the catalog** uses `Partial<AccessibleName>`: you can pass nothing, but not both
67
+ (`MobileTabBar`). The default is announced even when an empty string is passed.
68
+ - **Visible caption or attribute:** `{ label; "aria-label"?: never;
69
+ "aria-labelledby"?: never } | ({ label?: never } & AccessibleName)` (`TagGroup`). A caption together with an
70
+ attribute is not allowed: the attribute would override the visible text, and a screen reader would give the
71
+ component a different name than the one on screen.
72
+ - The test locks the shape with `@ts-expect-error`: for "no name" and for "both at once".
73
+
74
+ Components released before this rule use `label` as the name string without a visible caption (`SideNav`,
75
+ `TopBar`, `FilterBar`, `ScrollArea` and others). Renaming it there is a breaking change and a separate decision.
76
+ A new component follows the rule.
77
+
78
+ ## Empty value
79
+
80
+ An empty string is neither a caption nor a name. The rule and its history are in
81
+ [`src/lib/empty.ts`](../src/lib/empty.ts). For a prop of type `string`, write the fallback with `||`, not `??`.
82
+ For `ReactNode`, use `hasContent`, not `!= null`. `??` and `!= null` let `""` through: the component renders an
83
+ empty node, stays without a name, or labels a button "Удалить: ". Each such branch is covered by a test with an
84
+ empty string.
85
+
86
+ ## What the tests check
87
+
88
+ A test renders the component as a consumer does (Testing Library and user-event), and checks what can be observed:
89
+ roles, names, ARIA attributes, focus, handler calls and text. Internal state and hooks are not tested.
90
+
91
+ **A class is tested only when it is the observable contract that jsdom would not otherwise see**: jsdom does not
92
+ compute the cascade, the layout or `env()`. Examples are the tone from the dictionary (`TONE_TOKENS`,
93
+ `CHOICE_CARD_SELECTED_BORDER`), the slide width and column count from the component dictionary, and the bar
94
+ background inside and outside the slot (`BACKDROP_CLASS` of `MobileTabBar`). Take the expected value **from the
95
+ dictionary, not as a literal**: a literal checks that nobody touched the string, while the dictionary checks that
96
+ the component picked the right cell.
97
+
98
+ Arbitrary layout classes (`h-7`, `rounded-full`, `ring-bg-1`) are not locked by a test. Visual snapshots guard
99
+ them, and [`tests/class-coverage.test.ts`](../tests/class-coverage.test.ts) checks through a matrix that each
100
+ class reaches the CSS. If such a class has an observable effect (a description inside the label, a node in the
101
+ DOM, an attribute), test the effect.
102
+
103
+ ## State management
104
+
105
+ Components mostly take the value and the callback from the consumer. The consumer is the only owner of domain
106
+ state, the request, the route and validation. On a component page, always state:
107
+
108
+ - the shape of the value and of the empty value (`null`, an array, a string, and so on);
109
+ - controlled props and the callback;
110
+ - allowed state transitions;
111
+ - the behavior of disabled and loading;
112
+ - the integration with a native form, if there is one.
113
+
114
+ Do not infer the API from how a story looks: check the type and the source.
115
+
116
+ ## How to read a component page
117
+
118
+ Every page must contain:
119
+
120
+ 1. A short answer for AI and the limits of use.
121
+ 2. The closest alternatives and what not to do.
122
+ 3. A minimal working example with the correct import.
123
+ 4. An explanation of the public props, states and events.
124
+ 5. Accessibility, the keyboard, focus and the boundary with forms.
125
+ 6. Anti-patterns, special cases and links to the source and Storybook.
126
+
127
+ ## Source layout
128
+
129
+ A component folder in `src/components/<category>/` matches its page folder in `docs/components/<category>/`.
130
+ There are eight categories (`actions`, `inputs`, `date-time`, `overlays`, `navigation`, `content`, `table`,
131
+ `app-shell`). There are no files at the root of `src/components/`. Inside a category, files are flat: an internal
132
+ part lives next to its owner. When a component changes its category, its page changes with it.
133
+
134
+ In `src/editor/`, only the entry points stay at the root: `RichTextEditor`, `RichTextView` and their stories.
135
+ Everything else is in subfolders: `core/` holds the engine and the Tool registry, `ui/` holds the shared glue code,
136
+ `styles/` holds the styling, `hooks/` holds the hooks, and `features/<feature>/` holds the node, commands, Tool and
137
+ UI of one feature.
138
+
139
+ `index.ts` files are not allowed in categories, layers or features: imports go directly to the file. Barrel files
140
+ are allowed only as entry points of the package. `tests/structure.test.ts` checks the layout.
141
+
142
+ ## Changing the API
143
+
144
+ When you add or change a runtime export, update its page, its entry in the catalog and the registry in the same
145
+ change set. A breaking change also needs an entry in `docs/api/migration-notes.md` with the action for the
146
+ consumer. Do not hide a change behind behavior that looks similar.
147
+
148
+ ## User-visible strings
149
+
150
+ Every label that a user sees or hears goes through the message catalog: `prop ?? messages.family.key`. The prop
151
+ stays: it is a targeted override, not the only way.
152
+
153
+ A string literal in JSX or in a prop default fails `npm test`
154
+ ([`tests/i18n-gate.test.ts`](../tests/i18n-gate.test.ts)). The exceptions are an explicit list, with the reason
155
+ given in the gate itself (three reasons are described below), not a regexp.
156
+
157
+ To add a key, see [i18n.md](i18n.md).
158
+
159
+ ## Language: what is in Russian, what is in English
160
+
161
+ The boundary follows the audience, not the file type: this is the decision in ADR-0034.
162
+
163
+ | In Russian: the kit explains to itself | In English: the kit shows to the outside |
164
+ | -------------------------------------- | ----------------------------------------------------------- |
165
+ | Comments in the sources | Showcase: sections, story names, demo content, `aria-label` |
166
+ | This documentation and the ADRs | Tests: names, fixtures, data |
167
+ | Text in the Figma mockups | Script output to the console |
168
+
169
+ Comments stay in Russian on purpose, and the i18n gate does not check them. A Russian comment above an English
170
+ story is normal, not unfinished work.
171
+
172
+ Russian is legitimate in tests where it is the **subject** of the check: the plural and case forms and the catalog
173
+ words live in `tests/locale-ru.test.tsx`. In other tests, the expected values come from the English fallback.
174
+
175
+ The gate's allowlist covers **three reasons, not three files**: the keyboard layout as input data (`Keyboard.tsx`
176
+ and its test), the Russian catalog (`locales/ru.ts`), and a Russian word as the subject of a check
177
+ (`locale-ru.test.tsx`, plus the `SortableList` scenario in `tests-visual/interactions.spec.ts`, where react-aria
178
+ localizes the announcements itself). A fourth reason is a case for review, not a reason to change the number.
179
+
180
+ ## Showcase story names
181
+
182
+ Section and story names are in Latin script. They make up the story `id`, and the `id` gives the name of the
183
+ snapshot file and its address in `tests-visual`. Renaming costs a baseline regeneration, so pick the name once.
184
+
185
+ A component's section is its category: the folder of its page in the Reference and of its source (ADR-0060). The
186
+ story `src/components/overlays/Dialog.stories.tsx` is named `Overlays/Dialog`. Editor stories take their section
187
+ from the folder of their own page: `Inputs/RichTextEditor`, `Content/RichTextView`.
188
+
189
+ | Section | What is inside |
190
+ | ---------------- | ----------------------------------------------------------- |
191
+ | `Actions/` | Actions and feedback: `Actions/Button`, `Actions/Alert` |
192
+ | `Inputs/` | Fields and choice: `Inputs/Select`, `Inputs/RichTextEditor` |
193
+ | `Date and time/` | Calendars and date and time pickers |
194
+ | `Overlays/` | Dialogs, drawers, popovers, menus, tooltips |
195
+ | `Navigation/` | Tabs, breadcrumbs, page and section headers |
196
+ | `Content/` | Typography, cards, data, states, layout |
197
+ | `Table/` | The data table and its panels |
198
+ | `App shell/` | App shell: `AppShell`, `SideNav`, `TopBar` |
199
+ | `Foundations/` | Basics: icons, motion |
200
+ | `Screens/` | Assembled screens |
201
+
202
+ **Dictionary of repeated names.** A name that appears a second time must be added here. Otherwise the same case
203
+ gets two different names in two files, and they drift apart silently.
204
+
205
+ | Name | When |
206
+ | ----------------- | ------------------------------------------------------ |
207
+ | `Playground` | The main story with controls |
208
+ | `Matrix` | All variants side by side; captured by the visual test |
209
+ | `Empty` | Empty state |
210
+ | `Disabled` | Disabled state |
211
+ | `WithError` | Validation error |
212
+ | `WithAction` | With an action button |
213
+ | `WithSelection` | With a selection |
214
+ | `Sizes` | Size scale |
215
+ | `Horizontal` | Horizontal layout |
216
+ | `Compact` | Dense layout |
217
+ | `SizesAndStates` | Sizes and states side by side |
218
+ | `Collapsed` | Collapsed navigation state |
219
+ | `Submitting` | The form is being submitted |
220
+ | `InForm` | The component inside a form |
221
+ | `AsPage` | Full-screen variant |
222
+ | `WithoutTitle` | Without a title |
223
+ | `States` | A set of states side by side |
224
+ | `Filled` | The field is completely filled |
225
+ | `Open` | An expanded list or calendar |
226
+ | `Tones` | Tone scale |
227
+ | `WithLabel` | With a visible label |
228
+ | `Cards` | Choice variants shown as cards (`appearance="card"`) |
229
+ | `CardsHorizontal` | Choice cards in a row |
230
+ | `CardsStates` | States of choice cards side by side |
231
+ | `CardsTones` | Tone scale of choice cards |
232
+ | `Controlled` | Controlled mode: the story holds the value |
233
+ | `Wrapping` | Wrapping at a narrow width |
234
+ | `Vertical` | Vertical layout |
235
+ | `Badges` | All kinds of badge side by side |
236
+
237
+ Single-word names describe the **case**, not translate a word: a story about how a component differs from its
238
+ neighbors is called `VersusNeighbours`, not a transliteration of a Russian heading.
239
+
240
+ **Snapshot width is set by a story tag.** The visual test takes a snapshot of a story at 1280×900. A story that
241
+ shows a narrow screen sets the tag `tags: ["viewport-phone"]` on itself, and it is captured at 375×812 with an
242
+ emulated screen cutout at the bottom (34px). It is a tag, not a parameter: the spec reads stories from
243
+ `storybook-static/index.json`, which has no parameters. The first such story is `AppShell` → `WithBottomBar`: at
244
+ 1280px the slot hides the bar.
245
+
246
+ **A lazy chunk that loads after the story is shown is also set by a tag.** The snapshot waits for two identical
247
+ frames in a row. While a chunk is in transit, the screen does not change, and the snapshot takes a story that is
248
+ not fully loaded for a finished one. A story part of which arrives as a lazy chunk after it is shown sets the tag
249
+ `tags: ["network-idle"]`: the snapshot and the axe audit wait for the network to be quiet. The first such stories
250
+ are `RichTextEditor` → `TextCardOpen` and `TextCardRussian`: the chunk of the Text card is requested only after the
251
+ engine reaches Readiness.
252
+
253
+ **A story name must not match the name of the imported component.** A story about `Separator` in
254
+ `Separator.stories.tsx` must not be called `Separator`: the export shadows the import, and `tsc` fails with
255
+ `cannot be used as a JSX component`. It fails only where the component is used as JSX. Hence the names
256
+ `Separators`, `Scrolling`, `HiddenContent`, `BasicTable`.
257
+
258
+ ## Warnings in development mode
259
+
260
+ The first such warning is on `NotificationItem`, which gets both `onAction` and `actions`. The rules come from it,
261
+ and they are written here so the next author does not invent their own.
262
+
263
+ **When to warn.** Only when a contract is broken that **types catch but a consumer in JS does not**, and the
264
+ component silently picks one reading of it (mutually exclusive props, where one of them is ignored). Do not warn
265
+ about style, non-optimal use, deprecation (for that, use `migration-notes.md`), or what React or `react-aria`
266
+ already catches itself. A violation after which the component cannot work is not a warning but an exception (like
267
+ `HoverCardContent` outside `HoverCard`).
268
+
269
+ **How.**
270
+
271
+ - Through [`devWarn(message)`](../src/lib/dev-warn.ts), not a bare `console.warn`. The helper shows the message
272
+ **once** (the key is the text) and **does not write in production**. The call sits in the render body, so
273
+ showing once is required: a list row renders hundreds of times, and `StrictMode` calls the render twice.
274
+ - The text is **in English**, because it is output to the consumer's console (ADR-0034). It does not go into the
275
+ message catalog, because it is not an interface string. The text names the component, the value that was
276
+ passed, the value that won, and how to fix it. Keep changing data out of it: the deduplication key is the text
277
+ itself.
278
+ - The mode is read as `process.env.NODE_ENV !== "production"` inside `try/catch`, on every call. The package
279
+ ships as plain `tsc` output, and the value is not baked into `dist`. The consumer's build sets it (Vite, Next,
280
+ webpack) and removes the dead branch in production. Without a bundler, `process` is not defined: a reference to
281
+ it throws a `ReferenceError`, and the helper stays silent. The check `typeof process !== "undefined"` does not
282
+ work: the bundler does not replace it, so in a Vite browser build the warning would never fire.
283
+ - Test: `vi.spyOn(console, "warn")`, `resetDevWarnings()` in `beforeEach` (the "already shown" state lives at
284
+ module level), and four cases: it warns in development, it warns once, it stays silent in production
285
+ (`vi.stubEnv`), and it stays silent on a correct call.
286
+
287
+ **An error instead of a warning: `devError(message, cause?)`.** Use it when a violation breaks the whole component
288
+ but not the page: the Tool set of `RichTextEditor` is built incorrectly, the loader of an engine part failed,
289
+ `rangeAt` of the Text card threw an exception. The field shows the author a catalog string on its own, and the
290
+ cause goes to the developer through `console.error`, with the same rules as `devWarn`: in development, once per
291
+ text, in English.
292
+
293
+ - The text starts with the component name (`RichTextEditor: …`), because the console is shared by the whole page.
294
+ A Tool name or an entry name in the text is allowed: it comes from the Tool's description and does not change
295
+ from call to call.
296
+ - A caught exception goes in as the second argument (`cause`), not in the text. Its message would make every error
297
+ new for the once-per-text key. The console also shows the stack for an `Error` object.
298
+ - The test follows the same pattern with `vi.spyOn(console, "error")`: an error in development with the exception
299
+ as a separate argument, once; silence in production (`vi.stubEnv`); and silence on a correct call
300
+ (`tests/rich-text-editor-dev-errors.test.tsx`).
301
+
302
+ ## `readOnly` and `disabled` are separate axes, not shades of one
303
+
304
+ The rule comes from `Rating` (wave 25). It is written here because the next author would not find it in one
305
+ component's docstring, and a second `readOnly` in the kit would then mean something of its own.
306
+
307
+ - **`readOnly` is data on display.** The value is visible, and it cannot be changed. Such a value must not be
308
+ dimmed: that would hide what is shown on purpose. The control leaves the tab order, but stays at full strength.
309
+ - **`disabled` is an action that is unavailable right now.** It is dimmed, because dimming is the signal that it
310
+ is unavailable.
311
+
312
+ **The role does not change.** A disabled control stays what it was and is marked with `aria-disabled`. Swapping the
313
+ role (for example, to `img`) would be read as a picture, not as an unavailable action. The role is a property of
314
+ the control, not of its availability right now.
315
+
316
+ ## Editor glossary
317
+
318
+ The Editor terms moved to the kit's shared [glossary](glossary.md#редактор).
@@ -1,5 +1,5 @@
1
1
  ---
2
- sourceHash: 5f89459633fb
2
+ sourceHash: d61bd1f3cca7
3
3
  ---
4
4
 
5
5
  # Setting up `@olddevs/ui`
@@ -132,10 +132,10 @@ peer dependencies are treated as external, and the metric is gzip:
132
132
  | `cn` and `tableStateToParams` | 0.6 kB |
133
133
  | one `Button` | 1.6 kB |
134
134
  | `DataTable` | 9.0 kB |
135
- | the whole kit (`import *`) | 76.4 kB |
136
- | `RichTextEditor` from `@olddevs/ui/editor`, without the engine, "start" | 70.6 kB |
137
- | `RichTextEditor` with the Tiptap engine, "start" (static graph, `basic`) | 295.3 kB |
138
- | `RichTextEditor` with the Tiptap engine, "full" (with all lazy chunks, `full`) | 339.1 kB |
135
+ | the whole kit (`import *`) | 77.1 kB |
136
+ | `RichTextEditor` from `@olddevs/ui/editor`, without the engine, "start" | 71.3 kB |
137
+ | `RichTextEditor` with the Tiptap engine, "start" (static graph, `basic`) | 296 kB |
138
+ | `RichTextEditor` with the Tiptap engine, "full" (with all lazy chunks, `full`) | 344.2 kB |
139
139
  | `RichTextView` from `@olddevs/ui/editor`, Tiptap engine included in the measurement | 12.0 kB |
140
140
  | `sanitizeRichText` and `richTextSanitizeConfig` from `@olddevs/ui/editor/schema`, Tiptap engine included | 3.6 kB |
141
141
  | `highlightCodeBlocks` from `@olddevs/ui/editor/highlight`, with lowlight | 22.6 kB |
@@ -129,10 +129,10 @@ peer-зависимости внешние, метрика gzip:
129
129
  | `cn` и `tableStateToParams` | 0,6 кБ |
130
130
  | одна `Button` | 1,6 кБ |
131
131
  | `DataTable` | 9,0 кБ |
132
- | весь кит целиком (`import *`) | 76,4 кБ |
133
- | `RichTextEditor` из `@olddevs/ui/editor`, без движка, «старт» | 70,6 кБ |
134
- | `RichTextEditor` вместе с движком Tiptap, «старт» (статический граф, `basic`) | 295,3 кБ |
135
- | `RichTextEditor` вместе с движком Tiptap, «полный» (со всеми ленивыми чанками, `full`) | 339,1 кБ |
132
+ | весь кит целиком (`import *`) | 77,1 кБ |
133
+ | `RichTextEditor` из `@olddevs/ui/editor`, без движка, «старт» | 71,3 кБ |
134
+ | `RichTextEditor` вместе с движком Tiptap, «старт» (статический граф, `basic`) | 296 кБ |
135
+ | `RichTextEditor` вместе с движком Tiptap, «полный» (со всеми ленивыми чанками, `full`) | 344,2 кБ |
136
136
  | `RichTextView` из `@olddevs/ui/editor`, движок Tiptap в замер включён | 12,0 кБ |
137
137
  | `sanitizeRichText` и `richTextSanitizeConfig` из `@olddevs/ui/editor/schema`, движок Tiptap в замер включён | 3,6 кБ |
138
138
  | `highlightCodeBlocks` из `@olddevs/ui/editor/highlight`, вместе с lowlight | 22,6 кБ |
@@ -1,5 +1,5 @@
1
1
  ---
2
- sourceHash: 2524e6589eaa
2
+ sourceHash: 88701a1373b4
3
3
  ---
4
4
 
5
5
  # Glossary of `@olddevs/ui`
@@ -44,7 +44,7 @@ Prop names are a separate vocabulary:
44
44
  | Term | Meaning | Not to be confused with |
45
45
  | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
46
46
  | **the Reference** | Markdown in the package's `docs/`: component, API and convention pages — the source of truth about the public contract. Ships in the package and is shown on the site as is | The wiki: it retells the Reference for people and is wrong when they disagree. ADRs: they explain why a decision was made |
47
- | **showcase** | The kit's Storybook: stories, snapshots and accessibility checks; published to GitHub Pages | The documentation site: the showcase is a workshop, not text for a reader |
47
+ | **showcase** | The kit's Storybook: stories, snapshots and accessibility checks; published to GitHub Pages | The documentation site: the showcase is a workshop, not text for a reader. The flyout of a mega menu |
48
48
  | **snapshot** | A reference image of a showcase story in `tests-visual/`; taken and compared only locally (ADR-0013) | A screenshot for documentation: it checks nothing |
49
49
  | **gate** | One automatic check that fails the run: the contrast gate (`npm run contrast`), the i18n gate, `format:check` | The release checklist: a gate is one check from its list |
50
50
  | **release checklist** | The full list of checks before a merge and a release — the "Checks" section of `AGENTS.md`. Run locally: CI does not check pushes | A GitHub Actions workflow: it publishes the showcase and the package, but checks nothing |
@@ -108,27 +108,27 @@ The words pages use to describe how a component is operated with a mouse, a fing
108
108
  How a component is named in prose. Do not use the words listed after "Do not write:": readers
109
109
  search for a single word.
110
110
 
111
- | Term | Meaning | Not to be confused with |
112
- | --------------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
113
- | **dialog** | `Dialog`: a modal surface with a scrim | A drawer. A browser window, which is simply called a "browser window". Do not write: modal |
114
- | **drawer** | `Drawer`: a modal panel that slides in from an edge | A dialog. Do not write: sheet, slide-out panel |
115
- | **popover** | `Popover`: a non-modal panel next to the button that opened it | A tooltip: it has no interactivity. Do not write: popup |
116
- | **tooltip** | `Tooltip`: short text on hover and focus | A popover |
117
- | **hint** | `hint` on `FormField` and neighboring fields: text under the field that helps to fill it in | A tooltip and a placeholder |
118
- | **placeholder** | `placeholder`: an example value shown in an empty field that disappears on input | A hint and a tooltip |
119
- | **toast** | A global notification `toast()` in `Toaster` | The Editor's Field notice: it is tied to a field |
120
- | **checkbox** | `Checkbox`: a yes/no choice within a set | The `Toggle` switch (`role="switch"`): one boolean property, not a choice within a set |
121
- | **table bar** | The `toolbar` slot of `DataTable`, usually `TableToolbar` | The Editor's Toolbar and the `Toolbar` primitive. Do not write: toolbar |
122
- | **menu trigger** | The button that opens a menu: the right part of `SplitButton`, the `Menu` button | A popover: that is the panel that opened. Do not write: disclosure |
123
- | **segmented control** | `Segmented`: a row of buttons of which exactly one is selected | `Tabs` (they switch panels) and `ToggleGroup` (zero or more selected) |
124
- | **tag** | `Tag` in `TagGroup` and `TagInput`: a compact element with text and an optional remove button | The Editor's Mark and a field label |
125
- | **close button** | The button with an "×" that closes a dialog, drawer, popover or notification; in a search field it is the **clear button** | A "Cancel" button. Do not write: cross |
126
- | **shortcut list** | `ShortcutsHelp`: a dialog with a list of keyboard shortcuts | The Editor's Shortcuts reference: that one opens from the Toolbar and describes the Editor's shortcuts |
127
- | **mega menu** | `MegaMenu`: a category column and the showcase of the hovered category, an Amazon-style catalog | `Menu`: that one is for commands, not navigation. Do not write: mega dropdown |
128
- | **category** | `MegaMenuCategory`: a row of the category column with a label, an icon and its own showcase | A menu item (`MenuItem`) |
129
- | **category column** | The vertical list of categories in a mega menu | `SideNav`: that one expands branches in place |
130
- | **showcase** | The area next to the category column with the open category's content: link columns, promos | The Editor toolbar, a popover |
131
- | **aim** | Deferring the category switch while the pointer cuts diagonally toward the open showcase | An opening delay: aim never slows down a direct hover |
111
+ | Term | Meaning | Not to be confused with |
112
+ | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
113
+ | **dialog** | `Dialog`: a modal surface with a scrim | A drawer. A browser window, which is simply called a "browser window". Do not write: modal |
114
+ | **drawer** | `Drawer`: a modal panel that slides in from an edge | A dialog. Do not write: sheet, slide-out panel |
115
+ | **popover** | `Popover`: a non-modal panel next to the button that opened it | A tooltip: it has no interactivity. Do not write: popup |
116
+ | **tooltip** | `Tooltip`: short text on hover and focus | A popover |
117
+ | **hint** | `hint` on `FormField` and neighboring fields: text under the field that helps to fill it in | A tooltip and a placeholder |
118
+ | **placeholder** | `placeholder`: an example value shown in an empty field that disappears on input | A hint and a tooltip |
119
+ | **toast** | A global notification `toast()` in `Toaster` | The Editor's Field notice: it is tied to a field |
120
+ | **checkbox** | `Checkbox`: a yes/no choice within a set | The `Toggle` switch (`role="switch"`): one boolean property, not a choice within a set |
121
+ | **table bar** | The `toolbar` slot of `DataTable`, usually `TableToolbar` | The Editor's Toolbar and the `Toolbar` primitive. Do not write: toolbar |
122
+ | **menu trigger** | The button that opens a menu: the right part of `SplitButton`, the `Menu` button | A popover: that is the panel that opened. Do not write: disclosure |
123
+ | **segmented control** | `Segmented`: a row of buttons of which exactly one is selected | `Tabs` (they switch panels) and `ToggleGroup` (zero or more selected) |
124
+ | **tag** | `Tag` in `TagGroup` and `TagInput`: a compact element with text and an optional remove button | The Editor's Mark and a field label |
125
+ | **close button** | The button with an "×" that closes a dialog, drawer, popover or notification; in a search field it is the **clear button** | A "Cancel" button. Do not write: cross |
126
+ | **shortcut list** | `ShortcutsHelp`: a dialog with a list of keyboard shortcuts | The Editor's Shortcuts reference: that one opens from the Toolbar and describes the Editor's shortcuts |
127
+ | **mega menu** | `MegaMenu`: a category column and the flyout of the hovered category, an Amazon-style catalog | `Menu`: that one is for commands, not navigation. Do not write: mega dropdown |
128
+ | **category** | `MegaMenuCategory`: a row of the category column with a label, an icon and its own flyout | A menu item (`MenuItem`) |
129
+ | **category column** | The vertical list of categories in a mega menu | `SideNav`: that one expands branches in place |
130
+ | **flyout** | The area next to the category column with the open category's content: link columns, promos. Before 4.3.0 it was called the showcase; the width prop is still `showcaseWidth` | The showcase: that is the kit's Storybook. The Editor toolbar, a popover |
131
+ | **aim** | Deferring the category switch while the pointer cuts diagonally toward the open flyout | An opening delay: aim never slows down a direct hover |
132
132
 
133
133
  ## Editor
134
134
 
@@ -140,6 +140,7 @@ introduces a second meaning.
140
140
  | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
141
141
  | **Editor** | `RichTextEditor` — a rich text input field | A code editor: the kit has none, code is displayed with `CodeView` |
142
142
  | **View** | `RichTextView` — read-only display of a saved Document | The Editor's `readOnly` mode: that one carries the engine, the View does not |
143
+ | **Table of contents** | `RichTextToc`: the list of a Document's headings next to the View or the Editor. Collapsed, it is a strip of strokes; expanded, a list; a click on an item goes to the heading | The kit's `Heading` and the navigation of the documentation site |
143
144
  | **Document** | The Editor's value: an HTML string constrained by the Schema | The ProseMirror JSON tree in the engine — it is internal; it comes out only as a JSON envelope |
144
145
  | **JSON envelope** | A Document as JSON for a file and a template: `{ format: "olddevs-rich-text", version, doc }` (`getJSON()`, `setJSON()`, `defaultValue`), lossless and through the Schema | The Editor's value — the value stays HTML; the bare tree `editor.getJSON()` without an envelope |
145
146
  | **Schema** | An allowlist of Blocks, Marks and attributes that the Editor accepts and returns | Server-side sanitization — the consumer does it (`sanitizeRichText`) |
package/docs/glossary.md CHANGED
@@ -40,7 +40,7 @@
40
40
  | Термин | Значение | Не путать с |
41
41
  | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
42
42
  | **Справочник** | Markdown в `docs/` пакета: страницы компонентов, API и соглашения — источник правды о публичном контракте. Едет в пакете и показывается на сайте без правок | Вики: та пересказывает для людей и при расхождении неправа. ADR: те объясняют, почему так решено |
43
- | **Витрина** | Storybook кита: истории, снимки и проверки доступности; публикуется на GitHub Pages | Сайтом документации: витрина — мастерская, а не текст для читателя |
43
+ | **Витрина** | Storybook кита: истории, снимки и проверки доступности; публикуется на GitHub Pages | Сайтом документации: витрина — мастерская, а не текст для читателя. Полкой мегаменю |
44
44
  | **Снимок** | Эталонная картинка истории витрины в `tests-visual/`; снимается и сверяется только локально (ADR-0013) | Скриншотом для документации: он ничего не проверяет |
45
45
  | **Гейт** | Одна автоматическая проверка, которая роняет прогон: гейт контраста (`npm run contrast`), гейт i18n, `format:check` | Воротами: гейт — одна проверка из их списка |
46
46
  | **Ворота** | Полный список проверок перед слиянием и релизом — раздел «Checks» в `AGENTS.md`. Запускаются локально: CI пуши не проверяет | Воркфлоу GitHub Actions: те публикуют витрину и пакет, но не проверяет |
@@ -104,27 +104,27 @@
104
104
  Как компонент называется по-русски в прозе. Слова после «Не пишем:» в тексте не употребляются: читатель
105
105
  ищет по одному слову.
106
106
 
107
- | Термин | Значение | Не путать с |
108
- | -------------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
109
- | **Окно** | `Dialog`: модальная поверхность с затемнением | Ящиком. Окном браузера — его так и называем: «окно браузера». Не пишем: модалка |
110
- | **Ящик** | `Drawer`: модальная панель, которая выезжает от края | Окном. Не пишем: шторка, выдвижная панель |
111
- | **Поповер** | `Popover`: немодальная панель у кнопки, которая её открыла | Подсказкой: в той нет интерактива. Не пишем: попап |
112
- | **Подсказка** | `Tooltip`: короткий текст при наведении и фокусе | Поповером. Не пишем: тултип |
113
- | **Пояснение** | `hint` у `FormField` и соседних полей: текст под полем, который помогает его заполнить | Подсказкой (`Tooltip`) и плейсхолдером. Не пишем: подсказка поля |
114
- | **Плейсхолдер** | `placeholder`: пример значения, который виден в пустом поле и исчезает при вводе | Пояснением и подсказкой. Не пишем: подсказка в поле |
115
- | **Тост** | Глобальное уведомление `toast()` в `Toaster` | Сообщением поля Редактора: то привязано к полю |
116
- | **Флажок** | `Checkbox`: выбор «да / нет» в наборе | Переключателем `Toggle` (`role="switch"`): одно булево свойство, а не выбор в наборе. Не пишем: чекбокс |
117
- | **Полоса над таблицей** | Слот `toolbar` у `DataTable`, обычно `TableToolbar` | Панелью Редактора и примитивом `Toolbar`. Не пишем: тулбар |
118
- | **Раскрыватель** | Кнопка, которая раскрывает меню: правая часть `SplitButton`, кнопка `Menu` | Поповером: тот — сама раскрывшаяся панель. Не пишем: disclosure, стрелка-меню |
119
- | **Сегментированный выбор** | `Segmented`: ряд кнопок, из которых выбрана ровно одна | Вкладками `Tabs` (те переключают панели) и `ToggleGroup` (там выбрано ноль или несколько) |
120
- | **Тег** | `Tag` в `TagGroup` и `TagInput`: компактный элемент с текстом и необязательной кнопкой удаления | Меткой Редактора (`Mark`) и подписью поля. Не пишем: таг |
121
- | **Кнопка закрытия** | Кнопка с иконкой «×», которая закрывает окно, ящик, поповер или уведомление; у поля поиска — **кнопка очистки** | Кнопкой «Отмена». Не пишем: крестик |
122
- | **Свод сочетаний** | `ShortcutsHelp`: окно со списком сочетаний клавиш | Справочником Редактора: тот открывается из Панели и описывает сочетания Редактора |
123
- | **Мегаменю** | `MegaMenu`: Столбец категорий и Витрина наведённой Категории — каталог в духе Amazon | `Menu`: тот для команд, а не для навигации. Не пишем: мега-дропдаун |
124
- | **Категория** | `MegaMenuCategory`: строка Столбца с подписью, иконкой и своей Витриной | Пунктом меню (`MenuItem`) |
125
- | **Столбец категорий** | Вертикальный список Категорий Мегаменю | `SideNav`: тот раскрывает ветки на месте |
126
- | **Витрина** | Область рядом со Столбцом с содержимым открытой Категории: колонки ссылок, промо | Панелью Редактора и поповером |
127
- | **Прицел** | Отложенная смена Категории, пока курсор идёт наискосок к открытой Витрине | Задержкой открытия: прямое наведение Прицел не тормозит |
107
+ | Термин | Значение | Не путать с |
108
+ | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
109
+ | **Окно** | `Dialog`: модальная поверхность с затемнением | Ящиком. Окном браузера — его так и называем: «окно браузера». Не пишем: модалка |
110
+ | **Ящик** | `Drawer`: модальная панель, которая выезжает от края | Окном. Не пишем: шторка, выдвижная панель |
111
+ | **Поповер** | `Popover`: немодальная панель у кнопки, которая её открыла | Подсказкой: в той нет интерактива. Не пишем: попап |
112
+ | **Подсказка** | `Tooltip`: короткий текст при наведении и фокусе | Поповером. Не пишем: тултип |
113
+ | **Пояснение** | `hint` у `FormField` и соседних полей: текст под полем, который помогает его заполнить | Подсказкой (`Tooltip`) и плейсхолдером. Не пишем: подсказка поля |
114
+ | **Плейсхолдер** | `placeholder`: пример значения, который виден в пустом поле и исчезает при вводе | Пояснением и подсказкой. Не пишем: подсказка в поле |
115
+ | **Тост** | Глобальное уведомление `toast()` в `Toaster` | Сообщением поля Редактора: то привязано к полю |
116
+ | **Флажок** | `Checkbox`: выбор «да / нет» в наборе | Переключателем `Toggle` (`role="switch"`): одно булево свойство, а не выбор в наборе. Не пишем: чекбокс |
117
+ | **Полоса над таблицей** | Слот `toolbar` у `DataTable`, обычно `TableToolbar` | Панелью Редактора и примитивом `Toolbar`. Не пишем: тулбар |
118
+ | **Раскрыватель** | Кнопка, которая раскрывает меню: правая часть `SplitButton`, кнопка `Menu` | Поповером: тот — сама раскрывшаяся панель. Не пишем: disclosure, стрелка-меню |
119
+ | **Сегментированный выбор** | `Segmented`: ряд кнопок, из которых выбрана ровно одна | Вкладками `Tabs` (те переключают панели) и `ToggleGroup` (там выбрано ноль или несколько) |
120
+ | **Тег** | `Tag` в `TagGroup` и `TagInput`: компактный элемент с текстом и необязательной кнопкой удаления | Меткой Редактора (`Mark`) и подписью поля. Не пишем: таг |
121
+ | **Кнопка закрытия** | Кнопка с иконкой «×», которая закрывает окно, ящик, поповер или уведомление; у поля поиска — **кнопка очистки** | Кнопкой «Отмена». Не пишем: крестик |
122
+ | **Свод сочетаний** | `ShortcutsHelp`: окно со списком сочетаний клавиш | Справочником Редактора: тот открывается из Панели и описывает сочетания Редактора |
123
+ | **Мегаменю** | `MegaMenu`: Столбец категорий и Полка наведённой Категории — каталог в духе Amazon | `Menu`: тот для команд, а не для навигации. Не пишем: мега-дропдаун |
124
+ | **Категория** | `MegaMenuCategory`: строка Столбца с подписью, иконкой и своей Полкой | Пунктом меню (`MenuItem`) |
125
+ | **Столбец категорий** | Вертикальный список Категорий Мегаменю | `SideNav`: тот раскрывает ветки на месте |
126
+ | **Полка** | Область рядом со Столбцом с содержимым открытой Категории: колонки ссылок, промо. До 4.3.0 — «Витрина»; проп ширины по-прежнему `showcaseWidth` | Витриной — это Storybook кита; Панелью Редактора и поповером |
127
+ | **Прицел** | Отложенная смена Категории, пока курсор идёт наискосок к открытой Полке | Задержкой открытия: прямое наведение Прицел не тормозит |
128
128
 
129
129
  ## Редактор
130
130
 
@@ -135,6 +135,7 @@
135
135
  | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
136
136
  | **Редактор** | `RichTextEditor` — поле ввода форматированного текста | Редактором кода: его в ките нет, показ кода — `CodeView` |
137
137
  | **Просмотр** | `RichTextView` — только чтение сохранённого Документа | `readOnly`-режимом Редактора: тот держит движок, Просмотр — нет |
138
+ | **Оглавление** | `RichTextToc`: список заголовков Документа рядом с Просмотром или Редактором. Свёрнуто — полоса штрихов, раскрыто — список, клик по пункту ведёт к заголовку | `Heading` кита и навигацией сайта документации |
138
139
  | **Документ** | Значение Редактора: HTML-строка, ограниченная Схемой | JSON-деревом ProseMirror в движке — оно внутреннее; наружу оно выходит только JSON-конвертом |
139
140
  | **JSON-конверт** | Документ как JSON для файла и шаблона: `{ format: "olddevs-rich-text", version, doc }` (`getJSON()`, `setJSON()`, `defaultValue`), без потерь и через Схему | Значением Редактора — значение остаётся HTML; голым деревом `editor.getJSON()` без конверта |
140
141
  | **Схема** | Белый список Блоков, Меток и атрибутов, которые Редактор принимает и отдаёт | Санитизацией на сервере — её делает потребитель (`sanitizeRichText`) |
@@ -0,0 +1,53 @@
1
+ ---
2
+ sourceHash: 7fafe8d11f89
3
+ ---
4
+
5
+ # `@olddevs/ui` Reference
6
+
7
+ This Reference is the contract for developers and AI agents who use the package's public API. The source of
8
+ truth for the exports is [`src/index.ts`](../src/index.ts). Component sources, tests and Storybook confirm the
9
+ behavior claims on the pages.
10
+
11
+ ## Start here
12
+
13
+ 1. [Install the package](getting-started.md): imports, CSS and Tailwind 4.
14
+ 2. [Choose a component](component-selection.md): pick the meaning first, not a similar look.
15
+ 3. [Shared conventions](conventions.md): the prop vocabulary, controlled state and the limits of the public API.
16
+ 4. [Glossary](glossary.md): the terms of the kit and the Editor, and what not to confuse them with.
17
+ 5. [Accessibility](accessibility.md): the required rules for names, focus and the keyboard.
18
+ 6. [Themes, tokens and brands](theming.md): the four token layers and how to add your own brand in one CSS block.
19
+ 7. [Palette](palette.md): the palette primitive values and the roles by theme, in one table.
20
+ 8. [Localization](i18n.md): the provider, the message catalog and how to connect to the consumer's `I18nProvider`.
21
+ 9. [Component catalog](components/index.md): the full index of the public components.
22
+
23
+ ## Rules for AI
24
+
25
+ - **Start with `llms.txt`** at the package root. It is an index of all Reference pages, with a description of
26
+ each one. A script builds it from `docs/`, so it cannot fall behind them.
27
+ - Import only the public entry points of the package (the list is in the [glossary](glossary.md)). Do not
28
+ import files from the package's `src/` directly.
29
+ - Before you read a page's API, read "When to use" and "When not to use". Components that look alike can
30
+ express different actions.
31
+ - Do not replace a semantic component with hand-written markup if the package already provides a suitable
32
+ public export.
33
+ - Follow the controlled and uncontrolled contract described in the Reference, and the requirements for the
34
+ label (`label`), focus and Escape. A look in a screenshot does not replace these contracts.
35
+ - Document a change to the public API in the same change as the code. The full maintenance rules are in the
36
+ `CLAUDE.md` of the kit's repository. That file does not ship in the package, and there is no link to it here
37
+ on purpose.
38
+
39
+ ## Scope
40
+
41
+ The documents live in the package repository. **Since 3.8.0 they ship with the package** to the registry: the
42
+ whole `docs/` folder and the `llms.txt` index at the package root (ADR-0044). An agent in someone else's
43
+ repository no longer needs a link to that repository or access to it. The Reference is in
44
+ `node_modules/@olddevs/ui/docs/`, in the version that is installed, not the version that is on `main` now.
45
+
46
+ The kit has a public documentation site, but the index does not link to it. The index has no `https://…`
47
+ links. All its paths are relative and point inside the package.
48
+
49
+ **What the package does not include is the ADRs.** Reference pages refer to decisions by number, `ADR-NNNN`,
50
+ many times. But the decisions are kept apart from the code and are not in the package. This does not stop the
51
+ Reference from being complete: the contract, the API and accessibility are described on the pages themselves.
52
+ "Why it was decided this way" stays in the decision records. The trade-off was made on purpose (ADR-0044). If
53
+ you need the reasoning, ask in the repository's issues.