@zeyos/zx 2.2.1 → 3.0.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 (117) hide show
  1. package/CHANGELOG.md +197 -0
  2. package/DESIGN-SYSTEM.md +136 -0
  3. package/MIGRATION.md +155 -4
  4. package/README.md +51 -14
  5. package/dist/types/compat/map/data.d.ts +3 -2
  6. package/dist/types/components/account-menu/account-menu.d.ts +144 -0
  7. package/dist/types/components/app-rail/app-rail.d.ts +250 -0
  8. package/dist/types/components/app-sidebar/app-sidebar.d.ts +169 -0
  9. package/dist/types/components/avatar/avatar.d.ts +100 -0
  10. package/dist/types/components/chart/chart.d.ts +321 -0
  11. package/dist/types/components/date-picker/month-picker.d.ts +6 -0
  12. package/dist/types/components/dialog/dialog.d.ts +9 -0
  13. package/dist/types/components/dock/dock.d.ts +495 -0
  14. package/dist/types/components/dropdown/dropdown.d.ts +2 -2
  15. package/dist/types/components/field/field.d.ts +14 -0
  16. package/dist/types/components/field-upload/field-upload.d.ts +37 -4
  17. package/dist/types/components/form/form.d.ts +25 -3
  18. package/dist/types/components/grid/grid.d.ts +172 -0
  19. package/dist/types/components/launcher/launcher.d.ts +300 -0
  20. package/dist/types/components/layout/layout.d.ts +4 -4
  21. package/dist/types/components/menu-button/menu-button.d.ts +10 -2
  22. package/dist/types/components/modal/modal.d.ts +25 -0
  23. package/dist/types/components/multi-value-editor/multi-value-editor.d.ts +74 -9
  24. package/dist/types/components/questionnaire/items.d.ts +327 -0
  25. package/dist/types/components/questionnaire/questionnaire.d.ts +449 -0
  26. package/dist/types/components/select/select.d.ts +36 -1
  27. package/dist/types/components/sheet/sheet-stack.d.ts +167 -0
  28. package/dist/types/components/sheet/sheet.d.ts +294 -0
  29. package/dist/types/components/split-view/split-view.d.ts +2 -21
  30. package/dist/types/components/tabbox/tabbox.d.ts +12 -3
  31. package/dist/types/components/table/table.d.ts +219 -12
  32. package/dist/types/components/tooltip/tooltip.d.ts +2 -2
  33. package/dist/types/components/value-list/value-list.d.ts +24 -3
  34. package/dist/types/core/dom.d.ts +114 -0
  35. package/dist/types/core/drag-axis.d.ts +111 -0
  36. package/dist/types/core/position.d.ts +4 -2
  37. package/dist/types/index.d.ts +12 -2
  38. package/dist/zx-compat.esm.js +943 -311
  39. package/dist/zx-compat.esm.min.js +1 -1
  40. package/dist/zx-compat.global.js +7893 -2252
  41. package/dist/zx-compat.global.min.js +3 -3
  42. package/dist/zx-zeyos.esm.js +826 -114
  43. package/dist/zx-zeyos.esm.min.js +1 -1
  44. package/dist/zx.css +1720 -243
  45. package/dist/zx.esm.js +8227 -2599
  46. package/dist/zx.esm.min.js +3 -3
  47. package/dist/zx.global.js +8227 -2599
  48. package/dist/zx.global.min.js +3 -3
  49. package/dist/zx.min.css +1 -1
  50. package/docs/api.json +2597 -255
  51. package/docs/llms.md +475 -37
  52. package/docs/llms.txt +21 -9
  53. package/package.json +4 -2
  54. package/src/compat/globals.js +3 -1
  55. package/src/compat/map/data.js +31 -9
  56. package/src/components/account-menu/account-menu.css +70 -0
  57. package/src/components/account-menu/account-menu.js +208 -0
  58. package/src/components/app-rail/app-rail.css +107 -0
  59. package/src/components/app-rail/app-rail.js +668 -0
  60. package/src/components/app-sidebar/app-sidebar.css +68 -0
  61. package/src/components/app-sidebar/app-sidebar.js +388 -0
  62. package/src/components/avatar/avatar.css +61 -0
  63. package/src/components/avatar/avatar.js +222 -0
  64. package/src/components/button/button.css +0 -1
  65. package/src/components/chart/chart.css +61 -0
  66. package/src/components/chart/chart.js +518 -0
  67. package/src/components/date-picker/date-range-picker.css +7 -0
  68. package/src/components/date-picker/month-picker.js +9 -0
  69. package/src/components/dialog/dialog.js +11 -4
  70. package/src/components/dock/dock.css +228 -0
  71. package/src/components/dock/dock.js +977 -0
  72. package/src/components/dropdown/dropdown.js +1 -1
  73. package/src/components/field/field.js +2 -0
  74. package/src/components/field-upload/field-upload.js +9 -4
  75. package/src/components/fieldset/fieldset.css +7 -0
  76. package/src/components/form/form.css +7 -0
  77. package/src/components/form/form.js +6 -3
  78. package/src/components/grid/grid.css +6 -0
  79. package/src/components/grid/grid.js +213 -0
  80. package/src/components/launcher/launcher.css +100 -0
  81. package/src/components/launcher/launcher.js +557 -0
  82. package/src/components/layout/layout.js +2 -2
  83. package/src/components/menu-button/menu-button.js +27 -2
  84. package/src/components/modal/modal.css +5 -1
  85. package/src/components/modal/modal.js +37 -2
  86. package/src/components/multi-value-editor/multi-value-editor.css +99 -15
  87. package/src/components/multi-value-editor/multi-value-editor.js +237 -19
  88. package/src/components/navigation-bar/navigation-bar.css +7 -0
  89. package/src/components/questionnaire/items.js +370 -0
  90. package/src/components/questionnaire/questionnaire.css +387 -0
  91. package/src/components/questionnaire/questionnaire.js +1033 -0
  92. package/src/components/search/search.css +10 -2
  93. package/src/components/select/select.css +6 -1
  94. package/src/components/select/select.js +99 -11
  95. package/src/components/sheet/sheet-stack.js +304 -0
  96. package/src/components/sheet/sheet.css +351 -0
  97. package/src/components/sheet/sheet.js +551 -0
  98. package/src/components/split-view/split-view.js +76 -161
  99. package/src/components/stepper/stepper.css +12 -2
  100. package/src/components/tabbox/tabbox.css +13 -3
  101. package/src/components/tabbox/tabbox.js +34 -10
  102. package/src/components/table/table.css +43 -0
  103. package/src/components/table/table.js +523 -45
  104. package/src/components/tooltip/tooltip.js +5 -2
  105. package/src/components/value-list/value-list.js +6 -3
  106. package/src/core/dom.js +302 -0
  107. package/src/core/drag-axis.js +203 -0
  108. package/src/core/position.js +68 -22
  109. package/src/index.js +19 -2
  110. package/styles/tokens/dark.css +15 -13
  111. package/styles/tokens/global.css +17 -5
  112. package/styles/tokens/semantic.css +14 -8
  113. package/styles/tokens/themes.css +72 -0
  114. package/styles/zx.css +11 -1
  115. package/dist/types/components/permission/permission.d.ts +0 -93
  116. package/src/components/permission/permission.css +0 -47
  117. package/src/components/permission/permission.js +0 -198
package/CHANGELOG.md CHANGED
@@ -8,6 +8,203 @@ and friends remain the complete record.
8
8
 
9
9
  ## Unreleased
10
10
 
11
+ ## 3.0.0 — 2026-08-24
12
+
13
+ ### Added
14
+
15
+ - **Xenon application shell components.** `Launcher` combines deterministic local ranking with
16
+ abortable grouped sources and a Cmd/Ctrl+K dialog; `Avatar` provides a stable initials fallback;
17
+ `AccountMenu` composes identity with an APG menu; `AppSidebar` is expanded vertical navigation;
18
+ and minimized `AppRail` keeps descendants in pointer-, focus-, and keyboard-accessible flyouts
19
+ for both vertical and horizontal placement. All route, permission, session, and persistence work
20
+ remains application-owned behind cancelable events.
21
+ - **`Grid` and `Grid.BillingItems()`.** The public Table specialization keeps every Table feature
22
+ while providing a conventional editable billing schema for currencies, units, line totals, and
23
+ flat parent rows. The default line formula is quantity × unit price; tax, discount, rounding,
24
+ subtotal, validation, and persistence policy remain caller-owned.
25
+ - **Engine-neutral `Chart` and injected `ChartJsAdapter`.** Chart owns responsive canvas lifecycle,
26
+ loading/empty/error states, Xenon theme defaults, point-selection events, and a synchronized
27
+ semantic data table. Chart.js 4.5.1 is pinned only as a development/documentation asset; the Zx
28
+ runtime has no chart import and no runtime dependency.
29
+ - **Lateral anchored-overlay placement.** `position()`, `Dropdown`, `MenuButton`, and `Tooltip` now
30
+ accept left/right start/end variants for rail and account surfaces.
31
+
32
+ - **`Questionnaire`** — a guided, one-question-at-a-time flow for onboarding intake, service
33
+ checklists, audits and surveys, where a `Form` full of fieldsets is the wrong shape. The root is
34
+ a real `<form>` and the choices are native radios and checkboxes carrying the item name, so
35
+ `new FormData(questionnaire.toElement())` reads the answers and the keyboard contract inside a
36
+ choice group is the browser's. Three things go beyond a stack of fieldsets: an item carries a
37
+ `when` predicate and a `next` target, so the flow **branches** — conditions cascade, an
38
+ abandoned branch's answers drop out of `getAnswers()`, progress counts the questions actually
39
+ reachable, and Back retraces the path walked rather than an array index; an item's answer control
40
+ can be **any registered `Field` type** (`field: {type: 'date'}`), which is what lets a question
41
+ ask for a date, a number, a rating or an upload; and `validate` may return a **promise**, so a
42
+ question can be gated on a server check. `review: true` adds a summary screen with jump-back-to-
43
+ edit, `advance: 'auto'` moves on by itself after a single choice, and `1`–`9`/`a`–`z` shortcuts
44
+ are assigned and shown on each answer.
45
+
46
+ - **`Sheet`** — an edge-anchored `Dialog` covering both side-sheet and mobile-drawer patterns;
47
+ the two differ only in which edge they take, so `side` (`'start' | 'end' | 'top' | 'bottom'`,
48
+ logical) replaces a second component. `modal` is three-way rather than a boolean because the
49
+ three real behaviours do not collapse into two: `true` hands focus containment, page inertness,
50
+ Escape and the backdrop to the browser via `showModal()`, while `'trap-focus'` and `false` open
51
+ non-modally and re-implement only what they still want. `backdrop: 'dim'|'blur'|'none'` applies
52
+ to modal sheets alone — `::backdrop` does not render for a non-modal dialog, and a painted
53
+ stand-in would make a non-blocking sheet look blocking. Entry **and exit** animate, which a
54
+ `<dialog>` normally cannot do, via `@starting-style` and `transition-behavior: allow-discrete`
55
+ on `display`/`overlay`. `resizable`, `snap` detents and swipe-to-dismiss are deliberately one
56
+ gesture whose meaning is decided when the pointer settles, so the sheet follows the pointer the
57
+ whole way instead of pinning at its minimum and then vanishing.
58
+ - **`SheetStack`** — several sheets as one drill-down. Small on purpose: nested dialogs already
59
+ stack in the top layer by open order and Escape already closes only the topmost, so no z-index
60
+ bookkeeping and no unwinding logic. `stack` slides covered sheets back, scales them down and
61
+ makes them `inert`; `cascade` shifts each clear of the ones in front by their **measured** size
62
+ so they sit side by side and all stay usable — usually the right one for an ERP screen, where
63
+ the parent record should stay readable while a line item is edited.
64
+ - **`Dock`** — a stack of collapsible, resizable panes: the docked inspector column of a design
65
+ tool, and the detail side of a master–detail screen. A pane is either titled or a **tab group**
66
+ whose strip replaces the title. Giving the dock a `content` turns it into a region with panes on
67
+ either `side` of it, and nesting one dock in another's pane gives a workbench. Sizes, collapsed
68
+ panes and active tabs persist through `storageKey`; content factories run on first reveal.
69
+ - **`Dock.adopt(sheet)`** — a dock can take over a `Sheet`'s positioning, so the sheet becomes a
70
+ track in the dock's flow instead of an overlay. A dock is a flex container by construction and
71
+ therefore always a valid host, which is why docking needs no arrangement from the application.
72
+ `dockAt` hands the sheet back to a free overlay below a breakpoint measured on the **dock's own
73
+ width**, so a dock inside a split pane behaves correctly where a viewport media query would not.
74
+ Nothing is rebuilt across the handoff: the element moves and reopens in place, so DOM state and
75
+ listeners survive and no `open`/`close` is emitted for what is only a change of address.
76
+ - **`--zx-overlay-blur`** — tier-2 semantic token behind `Sheet`'s `'blur'` backdrop, default `8px`.
77
+
78
+ ### Changed
79
+
80
+ - **`SplitView` now shares one axis-drag engine with `Sheet` and `Dock`**, extracted to
81
+ `core/drag-axis.js`: pointer capture, one write per frame, telling a click from a zero-distance
82
+ drag, and the WAI-ARIA keyboard map. Behaviour is unchanged. `resolveSize` moved to the kernel
83
+ and is re-exported from `split-view.js`, so every existing import keeps working.
84
+ - **`Modal` gained three protected seams** — `mountTarget()`, `_show()` and `_isRealClose()` — so a
85
+ subclass can live somewhere other than `document.body`, open non-modally, and re-host itself.
86
+ `Dialog`'s size application became the overridable `_applySize()`. No public behaviour changed.
87
+ - **The modal scroll lock keys off `:modal` rather than `[open]`**, so a non-modal dialog no longer
88
+ locks the page behind it. `Modal` and `Dialog` always open modally, so neither is affected.
89
+
90
+ ### Removed
91
+
92
+ - **`Permission` is removed**, not just deprecated. 2.3.0 kept the class around for
93
+ `gx.zeyos.Permission` while pointing new code at `Select.permission()`; there is no longer a
94
+ second implementation to keep in sync, and `gx.zeyos.Permission` now builds directly on
95
+ `Select.permission()` instead of wrapping the removed class. The legacy constructor, options,
96
+ `get()`/`set()`, and the `change` event are unchanged — only the `zx.Permission` export is gone.
97
+
98
+ ## 2.3.0 — 2026-08-23
99
+
100
+ ### Added
101
+
102
+ - **Six standard themes, as an attribute.** `data-zx-preset="zx" | "zeyos" | "ocean" | "violet" |
103
+ "rose" | "slate"` picks the accent ramp, orthogonally to `data-zx-theme` picking the polarity.
104
+ A preset is five tier-1 declarations — `--zx-accent-300` … `--zx-accent-700` — because the accent
105
+ roles now resolve through that ramp rather than through the green palette directly, so one block
106
+ rethemes light, dark, and auto together with no second list to keep in sync. Every shipped
107
+ preset keeps its 600 stop above 4.5:1 against white and its 400 stop above 4.5:1 against the dark
108
+ page, which is what lets `--zx-color-on-accent` stay a single value per theme. Status colours are
109
+ deliberately not part of a preset.
110
+ - **A theme studio at `/theme.html`.** Every component on one page under a live theme: cycle the
111
+ standard presets (or press `[` and `]`), set a custom accent that is derived into a full ramp in
112
+ OKLab with the contrast ratio reported as you go, tint the neutral ramp, and change radius,
113
+ control height, base text size, type stack, and density — then copy the result out as CSS.
114
+ What you pick applies to the whole documentation site until you reset it.
115
+
116
+ - **`Select` takes fixed choices.** `fixedItems` pins entries to the top of the list, above a rule,
117
+ and treats them as part of the control rather than of its data: a `filter` function replaces the
118
+ item list on every query, so a pinned entry is narrowed locally against the same query instead,
119
+ survives `setItems()`, shows below `minQuery`, and stays resolvable by `set()` once the first
120
+ results have landed. That is what a choice with no row in the source needs — "Unassigned",
121
+ "Everyone", "Private". `setFixedItems()` replaces them later.
122
+ - **`Select.permission()`**, the record-access picker: Private and Public pinned above the groups a
123
+ record may be shared with, carrying the tri-state ZeyOS stores — `false`, `true`, or a group ID —
124
+ so it binds straight to the record field. `groups` takes an array, or a loader `(query) => items`
125
+ for an installation whose group list is too long to ship with the form.
126
+
127
+ - **`MultiValueEditor` rows reorder by dragging.** Each row grows a handle that drags it to a new
128
+ position, with a line showing where it will land. `reorder` picks what a row offers — `'both'`
129
+ (the default), `'drag'` for the handle alone, `'buttons'` for the arrows alone, or `'none'`.
130
+ Dragging goes through the handle rather than the whole row, so selecting text inside a row's
131
+ input still works, and the handle is a keyboard control too (arrow keys, Home, End) — which is
132
+ what keeps `reorder: 'drag'` operable without a pointer.
133
+
134
+ ### Changed
135
+
136
+ - **The documentation shows the demo data behind an example.** `items: catalogue()` documented
137
+ nothing on its own — the shape the component wants was in a helper the reader could only find by
138
+ opening the whole module. Every example's code block now carries a tab per module-level
139
+ declaration it uses, transitively, extracted from the demo module's own text. Nothing about
140
+ writing a demo changes: declare the data at the top of the module and it appears.
141
+
142
+ ### Deprecated
143
+
144
+ - **`Permission`.** Its three radios and a disabled group picker spend a form row on a choice
145
+ `Select.permission()` makes in one control, and the group list could only ever be an array held
146
+ in memory. The class stays for `gx.zeyos.Permission` and goes away in 3.0; the value and the
147
+ `groups` option are identical, so replacing it is a one-line change.
148
+
149
+ ### Fixed
150
+
151
+ - **`MultiValueEditor` no longer collapses in a host that sizes to its contents.** It is no longer
152
+ a size container at all. `inline-size: 100%` does not rescue one: the percentage resolves against
153
+ a containing block the host measured from the contribution containment had already zeroed, so the
154
+ editor still came out 26px wide in an inline-block, a table cell, or a shrink-to-fit flex row,
155
+ with the row spilling out to the left of it. The rows reflow on their own now — the controls wrap
156
+ under the value when the row runs out of room — which also reads the width that decides the
157
+ layout, the row's, rather than the editor's. Verified from a 1000px grid down to a 100px pane
158
+ with nothing overflowing. `DateRangePicker`, `Fieldset`, and `NavigationBar` still use
159
+ containment and are only partly rescued — see below.
160
+ - **`MultiValueEditor` keeps focus on the move button.** Up and down disabled themselves at the
161
+ ends of the list, so a row that reached the top took focus down to the document with it, and a
162
+ move sent focus to the value input rather than leaving it on the button that had just moved the
163
+ row — pressing up twice was impossible without going back for it. The ends now mark themselves
164
+ `aria-disabled` and stay focusable.
165
+ - **`MultiValueEditor` keeps a stored value its option list has lost.** A value with no matching
166
+ option displayed as the first option while `getValues()` went on returning the stored one, so a
167
+ form that was only opened and saved rewrote the field. The unknown value is now an option in its
168
+ own right.
169
+
170
+ - **The × on a closable `Tabbox` tab closes it.** It was drawn as a decorative glyph with no
171
+ handler, so clicking it merely activated the tab and closing was reachable only through the
172
+ Delete key. It is now a real hit target that emits `close` and hands focus to the neighbouring
173
+ tab, with Delete unchanged. It stays a `<span>` rather than a nested `<button>`, which buttons
174
+ cannot contain.
175
+ - **Size containers fill their host instead of collapsing inside it.** `DateRangePicker`,
176
+ `Fieldset`, and `NavigationBar` declare `container-type: inline-size` to pick their own layout,
177
+ which means they contribute nothing to their own intrinsic width — so in a flex row, an
178
+ inline-block, or a grid `auto` track they were squeezed to a few pixels and their contents
179
+ spilled out on top of each other. They now take `inline-size: 100%`, and `Form` does too, because
180
+ containment travels upward: a form shrink-wrapped to its action buttons while the fieldset inside
181
+ it overflowed. Eight examples on the documentation site were rendering this way.
182
+
183
+ This fixes every host that has a width of its own, which is the case that was actually biting.
184
+ It cannot fix a host that shrink-wraps its contents — an `inline-block` or a `fit-content` track
185
+ with no width of its own — because there 100% resolves against a containing block the collapse
186
+ has already zeroed. Only dropping containment does, as `MultiValueEditor` now has;
187
+ `DateRangePicker`, `Fieldset`, and `NavigationBar` still need a definite width from their host.
188
+ `tests/unit/size-containers.test.js` keeps every `container-type` rule declaring a width.
189
+ - **A focused `Search` field is ringed as one control.** The input carried its own focus ring on
190
+ top of the ring the field already draws on `:focus-within`, so a smaller rounded box appeared
191
+ inside the field and cut the submit button out of it — the field read as an input with a
192
+ detached button beside it. The input now rides the field's ring, and the clear and submit
193
+ buttons mark themselves with an inset ring so tabbing still shows which one has focus without
194
+ breaking the outline.
195
+
196
+ - **The generated API reference stopped losing methods to inline type casts.** `/** @type {X} */
197
+ (expr)` is a doc comment with no method behind it, and the scanner in `tools/build-api.js` was
198
+ allowed to run past it to the next one — swallowing that method's own comment, and with it the
199
+ method. Sixty-three public methods across forty-four components had no row on the documentation
200
+ site, `Dialog.open()` and `Table.commitEdit()` among them, and a few of the rows that did appear
201
+ had absorbed a neighbour's parameters into signatures like
202
+ `destroy(options, column, index, node)`.
203
+ - **A horizontal `Stepper` spans its container.** The connectors were already written to absorb
204
+ whatever space was left over, but the steps themselves could not grow, so there was never any
205
+ slack to absorb and the rail bunched up on the left of a wide card. The labels still size to
206
+ themselves; the rules between them now stretch.
207
+
11
208
  ## 2.2.1 — 2026-08-21
12
209
 
13
210
  ### Fixed
@@ -0,0 +1,136 @@
1
+ # Xenon design-system audit
2
+
3
+ Audit date: 2026-08-24. Xenon is the design system; Zx is its dependency-free JavaScript
4
+ implementation. This document records the current coverage against ZeyOS Classic and the work
5
+ needed for an incremental adoption that keeps the existing routing and business logic.
6
+
7
+ ## System boundary
8
+
9
+ Xenon has four layers:
10
+
11
+ 1. **Foundations** — semantic tokens, typography, spacing, density, themes, icons, motion, i18n,
12
+ accessibility rules, and the safe DOM kernel.
13
+ 2. **Components** — lifecycle-safe controls and containers with one options/events/destroy model.
14
+ 3. **Patterns** — repeatable business interactions such as global launch, account/preferences,
15
+ record detail, transaction entry, filtering, and empty/loading/error handling.
16
+ 4. **Application layouts** — rails, bars, workspaces, master/detail, dashboards, inboxes, wizards,
17
+ and responsive shell geometry.
18
+
19
+ This separation follows the enterprise-system intent described by the
20
+ [Carbon Design System](https://carbondesignsystem.com/all-about-carbon/what-is-carbon/) and
21
+ [SAP Fiori design principles](https://experience.sap.com/fiori-design-web/design-principles/),
22
+ without copying either system's code, assets, or component contracts.
23
+
24
+ Zx currently has more than 50 public component classes, 63 registered documentation/demo entries,
25
+ a generated API reference, bundled local icons, and no runtime dependency. The foundations,
26
+ component layer, and persistent application shell now cover the first ZeyOS adoption wave; domain
27
+ patterns remain intentionally application-owned until their business invariants are specified.
28
+
29
+ ## ZeyOS adoption boundary
30
+
31
+ ZeyOS Classic does not use `gx`. Across its 30 module files it has roughly 2,500 `UI.*` calls and
32
+ 2,600 `PG.*` calls. `PG` already owns navigation, transport, history, exit guards, and page
33
+ lifecycle and remains authoritative.
34
+
35
+ The existing compact DOM surface has 2,668 executable builder calls: 871 bare `__()` calls, 1,710
36
+ parent-returning `parent.__()` calls, and 87 child-returning `parent.$__()` calls. Zx now provides
37
+ the exact compact builder plus pure append/property/event helpers. Existing global and prototype
38
+ names remain application-owned delegation seams; Zx does not install or replace them.
39
+
40
+ Migration therefore replaces `UI.new*` implementations one family at a time, keeps their current
41
+ DOM/value contracts until call sites reach zero, and leaves `PG`, `MD`, routes, and server payloads
42
+ in place. See [MIGRATION.md](./MIGRATION.md) for the ordering and shell invariants.
43
+
44
+ ## Coverage ledger
45
+
46
+ | Capability in current ZeyOS | Xenon/Zx status | Decision |
47
+ | --- | --- | --- |
48
+ | Buttons, inputs, toggles, dates, selects, checklists, tags | Covered by public components | Replace `UI.new*` families incrementally and preserve value/event contracts. |
49
+ | Forms and fieldsets | Covered, including component-backed field adapters | Use for record editors; keep validation/business rules application-owned. |
50
+ | Dialogs, sheets, dropdowns, context menus, tooltips, messages | Covered | Consolidate duplicate menu behavior before building account/launcher surfaces. |
51
+ | Tabs, panels, toolbar, breadcrumbs, stepper, split view, dock | Covered | Compose layouts; do not let navigation components own routes. |
52
+ | Sorting, selection, filtering, pagination, trees/finder | Covered | Suitable for generic data screens. |
53
+ | Editable billing/transaction rows | Covered at generic-grid level | `Grid.BillingItems()` composes typed editable currency/unit columns and flat hierarchy over `Table`. Tax, subtotal, posting, rounding, and persistence stay domain code. |
54
+ | Global launcher/search | Covered | `Launcher` combines ranked local applications with abortable grouped sources. ZeyOS still filters the catalogue and resolves selection through `PG`. |
55
+ | Account/profile/preferences menu | Covered at presentation level | `Avatar` and `AccountMenu` provide identity and action UI. ZeyOS supplies role-filtered actions and owns preferences, authentication, and logout. |
56
+ | Persistent app rail / side navigation | Covered | `AppSidebar` is the expanded vertical tree; minimized `AppRail` uses flyouts in every orientation. Both are route- and persistence-agnostic. |
57
+ | Charts and analytical summaries | Covered through an adapter | `Chart` is engine-neutral and accessible; `ChartJsAdapter` accepts an injected Chart.js 4 engine. Chart.js is documentation/test tooling only. |
58
+ | Local product/module icons | Partial | Use bundled icons or registered local assets; do not require the optional remote Font Awesome kit. |
59
+ | Domain entity selectors/cards/links | Partial | Build patterns on `Select`, Table, Panel, and ContextMenu after the shell blockers. |
60
+ | Persistent notification/activity center | Missing, lower priority | `Message` covers ephemeral feedback, not read/unread activity history. |
61
+
62
+ ### Launcher contract
63
+
64
+ The current launcher searches applications, recent records, and grouped remote record types. A
65
+ replacement supplies pinned-order ranking, request cancellation, Cmd/Ctrl+K, listbox keyboard
66
+ behavior, native link metadata, and a cancelable action. Permission filtering, active-route state,
67
+ cache and recent-history policy remain inputs from ZeyOS, which resolves actions through `PG.go()`.
68
+ The component is deliberately not a second router.
69
+
70
+ ### Account contract
71
+
72
+ The current account surface includes identity, role-dependent links, language, authentication and
73
+ logout, theme and menu-position preferences, and About/version content. A shared `Menu` should
74
+ first centralize the keyboard/typeahead/selection behavior currently duplicated by `MenuButton`
75
+ and `ContextMenu`; `AccountMenu` then composes that primitive with `Avatar`/`Identity` and emits
76
+ actions without owning routes or session state. `AccountMenu` now provides that composition over
77
+ `MenuButton`; a future shared low-level `Menu` remains an internal consolidation opportunity, not
78
+ an adoption blocker.
79
+
80
+ ## Table and transaction follow-ups
81
+
82
+ The enhanced `Table` now keeps values numeric while formatting per-row locales, currencies, units,
83
+ and decimals; numeric types infer `NumberField` editors. Flat `parentId` data is projected into a
84
+ cycle-safe ARIA treegrid with local sibling sorting and expand/collapse APIs.
85
+
86
+ `Grid.BillingItems()` now packages the conventional description, quantity, unit, unit-price,
87
+ currency, line-total, and hierarchy schema while retaining Table's editing contract. Useful
88
+ follow-ups, deliberately separate from this generic preset:
89
+
90
+ - summary/footer rows and grouped subtotals;
91
+ - column reorder and user-persisted column state;
92
+ - product-specific invoice/transaction presets with tax, discount, rounding, and formula golden tests;
93
+ - server persistence, optimistic rejection, and undo owned by the application/controller layer.
94
+
95
+ ## Chart-engine candidates
96
+
97
+ Chart.js was selected for the first adapter. It is pinned at 4.5.1 as a development dependency for
98
+ tests and the documentation asset, but is never imported by Zx runtime source. Applications inject
99
+ their chosen engine into the adapter. Published browser artifact sizes below are approximate and
100
+ remain useful when choosing a different future adapter.
101
+
102
+ | Engine | Current state at audit | Approx. browser cost | Best fit | Main trade-off |
103
+ | --- | --- | ---: | --- | --- |
104
+ | [Chart.js](https://github.com/chartjs/Chart.js/releases) 4.5.1 | Mature, active, MIT | 209 kB / 71 kB gzip (UMD) | Broad default: bar, line, doughnut, mixed business charts | Larger than focused engines; canvas requires a separate accessible data summary. |
105
+ | [Frappe Charts](https://github.com/frappe/charts/releases) 1.6.x | Zero-dependency SVG, ERP-oriented, MIT | 69 kB / 18 kB gzip (UMD) | Lean dashboards with common chart types | GitHub release and npm publication were out of step; ecosystem and extension surface are smaller. |
106
+ | [uPlot](https://github.com/leeoniya/uPlot) 1.6.32 | Active, MIT, highly optimized | 51 kB JS + 2 kB CSS / about 23 kB gzip | Dense time-series and operational telemetry | Deliberately narrow; not a general categorical/stacked business-chart toolkit. |
107
+ | [Apache ECharts](https://echarts.apache.org/en/changelog.html) 6.1.0 | Very capable, active, Apache-2.0 | 1.12 MB / 369 kB gzip (full build) | Advanced analytical visualization | Too large as Xenon's default; only sensible as an opt-in advanced adapter or custom build. |
108
+
109
+ Implemented architecture: `Chart` accepts an injected engine adapter, maps Xenon semantic tokens,
110
+ owns loading/empty/error states, and mirrors its data in an accessible table. Chart.js is the broad
111
+ default adapter; Frappe Charts remains the lean ERP-oriented alternative, uPlot a specialized
112
+ time-series candidate, and ECharts an opt-in advanced adapter. Any additional package still
113
+ requires explicit dependency approval.
114
+
115
+ ## Review corrections completed
116
+
117
+ - Generated API records no longer let helper typedefs overwrite component options; inline JSDoc
118
+ tags are parsed, and audited runtime events are present in types and documentation.
119
+ - Form documentation exposes `Form`, `Fieldset`, and `Field`; `MonthPicker` has its own constructor
120
+ type.
121
+ - Theme Studio destroys registered component instances on every rebuild and describes its honest
122
+ family-level coverage.
123
+ - Documentation separates Presets from green-accent Examples and uses callable preset names.
124
+ - Current source and documentation no longer carry branding from the retired framework experiment.
125
+
126
+ ## Recommended sequence
127
+
128
+ 1. Ship the Zx runtime and delegate the current compact DOM/event seams; verify no navigation or
129
+ listener regressions.
130
+ 2. Mount `Launcher`, `Avatar`/`AccountMenu`, and `AppSidebar`/`AppRail` behind the current shell
131
+ fixtures, permission filters, and preference store.
132
+ 3. Replace high-volume `UI.new*` families, one discriminating fixture set at a time.
133
+ 4. Introduce `Grid.BillingItems()` where its generic schema fits; add domain presets only with
134
+ formula and rounding fixtures.
135
+ 5. Inject Chart.js into `ChartJsAdapter` only on analytical surfaces that need it; keep the engine
136
+ out of the base Zx runtime bundle.
package/MIGRATION.md CHANGED
@@ -4,6 +4,136 @@ Zx replaces the MooTools-era `gx` UI libraries with dependency-free ES2022 modul
4
4
  compatibility bundle lets mapped legacy code keep running while classes are migrated one at a
5
5
  time. Compatibility constructors warn once per legacy class so remaining call sites are visible.
6
6
 
7
+ Xenon is the ZeyOS design system; Zx is its browser implementation. A complete migration therefore
8
+ means more than swapping constructors: foundations, components, application patterns, and layout
9
+ contracts must move together while routing and business behavior stay authoritative.
10
+
11
+ ## Introducing Xenon into ZeyOS Classic
12
+
13
+ The current ZeyOS Classic application is **not** a `gx.*` application. Its modules call the global
14
+ `UI` factories, use `PG` for routing and page lifecycle, and rely on an extended DOM API including a
15
+ CSS-like `__()` element builder. Do not call `gx.compat.installGlobals()` there: that opt-in helper
16
+ preserves an existing `__()` but installs unrelated `gx` globals and prototype compatibility that
17
+ the current application does not need.
18
+
19
+ The migration does not need a separate application bridge. Zx now exports the established builder
20
+ directly, including its exact compact tag and property conventions:
21
+
22
+ ```js
23
+ const node = zx.__('button#save.primary', {
24
+ Daction: 'save',
25
+ onclick: () => save()
26
+ }, 'Save');
27
+ ```
28
+
29
+ Zx does not install globals or alter DOM prototypes. The current runtime keeps owning its global
30
+ `__`, `_`, `Node._`, `Node.__`, `Node.$__`, and `Element.be` seams during the migration and delegates
31
+ their bodies to `zx.compactElement()`, `zx.appendContent()`, `zx.appendCompact()`,
32
+ `zx.appendCompactChild()`, and `zx.applyCompactProperties()`. Its `DOM.on`, `DOM.remEvt`, and
33
+ `DOM.fireEvt` facade likewise delegates to Zx's shared compact-event helpers, so registration,
34
+ removal, and synthetic firing always see the same handlers. Replace the implementations of
35
+ `UI.new*` component families progressively with Zx components; do not insert a second routing or
36
+ application abstraction between those components and `PG`.
37
+
38
+ ### Keep these authorities in place
39
+
40
+ - `PG` remains responsible for URL construction, history, transport, exit guards, page dispatch,
41
+ and the `page:beforeUnmount` / `page:mounted` lifecycle.
42
+ - Existing `MD` handlers and business logic continue receiving the same page data.
43
+ - Existing `UI.new*` call sites keep their DOM return values and `getV` / `setV` / chaining
44
+ contracts while each factory implementation is replaced. New code uses the Zx component API
45
+ directly; the old factory is removed when its call-site count reaches zero.
46
+ - Zx owns only the mounted component subtree and tears that subtree down exactly once.
47
+ - New client/schema-driven screens may use `@zeyos/zx/zeyos`; adopting that optional layer is not a
48
+ prerequisite for converting current server-rendered screens.
49
+
50
+ Safe bootstrap order is the normal Zx bundle, the current Classic runtime delegating its compact
51
+ DOM seams to Zx, then the existing `Z(data)` application bootstrap. Component actions must call
52
+ `PG.go()` for internal routes; do not let a navigation component fall back to `location.assign()`.
53
+
54
+ ### Recommended adoption waves
55
+
56
+ 1. Mount persistent shell components behind current lifecycle events: launcher, account menu,
57
+ application rail, detail chrome, and overlays.
58
+ 2. Replace the highest-volume `UI.new*` implementations—inputs, Selects, tabs, menus, dialogs, and
59
+ tables—one family at a time while preserving their existing call-site contract.
60
+ 3. Move record and transaction patterns only after old/new behavior has discriminating fixtures.
61
+ 4. Use direct Zx and the ZeyOS schema binding for new surfaces; retire an old factory only after its
62
+ remaining call-site count reaches zero.
63
+
64
+ ### Shell components and ownership
65
+
66
+ `Search` remains a field-level input. `Launcher` is the shell-level application/record surface: it
67
+ ranks local items, debounces and aborts grouped sources, ignores stale responses, and supplies the
68
+ Cmd/Ctrl+K dialog/listbox interaction. ZeyOS filters permissions, supplies apps/recent records,
69
+ decides caching, and handles the cancelable selection through `PG`; Launcher never becomes a
70
+ router.
71
+
72
+ `Avatar` and `AccountMenu` provide the identity trigger, profile header, grouped actions, focus
73
+ return, and compact rail presentation. ZeyOS still decides permission-dependent routes, language
74
+ and theme choices, authentication/logout, and About/version data. A selection is a request to the
75
+ application, not a session mutation.
76
+
77
+ `AppSidebar` is the expanded vertical application tree and is the only presentation with inline
78
+ children. Its collapsed state renders `AppRail`. A standalone rail may be vertical or horizontal,
79
+ but every child collection remains a hover/focus/keyboard flyout. Both components accept native
80
+ links or cancelable actions and expose active/collapsed/expanded state without persisting it.
81
+
82
+ Use bundled Zx icons or explicitly registered local assets. The optional ZeyOS icon helper's
83
+ remote Font Awesome kit is not appropriate for a dependency-free production shell.
84
+
85
+ ### Persistent shell contract
86
+
87
+ These invariants were validated while prototyping the replacement shell and remain binding for a
88
+ Xenon implementation:
89
+
90
+ - Application-menu position is `top`, `bottom`, `left`, `right`, or `none`, defaulting to `left`.
91
+ Persist it under `zeyos.appMenu.position` (scope-suffixed where separate workspaces share an
92
+ origin) and publish the resolved value on `:root[data-app-menu-effective]`.
93
+ - Sidebar minimization is a user choice, never a hover side effect. Keep the existing
94
+ `zeyos.appRail.expanded` preference while migrating and map it to
95
+ `AppSidebar.setCollapsed(!expanded)`. `AppRail` itself never expands labels or inline children.
96
+ - The minimized rail is 48 px and the expanded sidebar 184 px. The application bar is 48 px; tiles
97
+ are 30 px, glyphs 22 px, and compact controls 31 px. Workspace tools in a side layout remain
98
+ inside the rail, and page insets derive from the effective rail/bar geometry.
99
+ - Theme preference is `auto`, `light`, or `dark`, persisted under `zeyos.theme` and applied before
100
+ paint. `auto` follows the system color-scheme media query.
101
+ - The dark neutral ramp is a coal surface rebased from Carbon Gray 100 toward `#0b0c0e`. Avoid a
102
+ cyan/slate cast that competes with ZeyOS module colors; module accents must retain clear contrast
103
+ on both shell and content surfaces.
104
+
105
+ ### Migration proof points
106
+
107
+ Before replacing any Classic surface, verify that internal navigation and browser history still use
108
+ `PG.go()` without a full reload; page-scoped instances are destroyed exactly once; persistent shell
109
+ listeners do not multiply across navigation or language changes; launcher output matches permission,
110
+ app, recent, and search fixtures; account content matches ordinary/admin/developer roles; and the
111
+ production shell loads no remote UI or icon dependency.
112
+
113
+ The shell wiring stays direct:
114
+
115
+ ```js
116
+ const launcher = new zx.Launcher(null, {
117
+ items: applicationCatalogue,
118
+ sources: recordSources,
119
+ onselect(event) {
120
+ event.preventDefault();
121
+ PG.go(resolveLauncherRoute(event.detail.item));
122
+ }
123
+ });
124
+
125
+ const sidebar = new zx.AppSidebar(shellHost, {
126
+ items: applicationTree,
127
+ active: currentRouteId,
128
+ collapsed: !savedAppRailExpanded,
129
+ footer: accountMenu,
130
+ onselect(event) {
131
+ event.preventDefault();
132
+ PG.go(event.detail.href);
133
+ }
134
+ });
135
+ ```
136
+
7
137
  ## Deployment script swap
8
138
 
9
139
  For deployments that serve the old libraries directly from
@@ -25,8 +155,9 @@ with this order:
25
155
  ```
26
156
 
27
157
  The compatibility entry exposes both `window.zx` and `window.gx`. Element
28
- `store`/`retrieve`/`eliminate` is installed automatically. If the application uses `__()`, `_()`,
29
- `String.prototype.htmlSpecialChars`, or `Array.prototype.findBy`, opt in after both scripts load:
158
+ `store`/`retrieve`/`eliminate` is installed automatically. If a genuine gx application uses its
159
+ free parser, `_()`, `String.prototype.htmlSpecialChars`, or `Array.prototype.findBy`, opt in after
160
+ both scripts load. An existing `__()` is deliberately never replaced:
30
161
 
31
162
  ```js
32
163
  gx.compat.installGlobals();
@@ -251,14 +382,14 @@ list.setValues([1, 2]);
251
382
 
252
383
  For remote data pass `load: () => Promise<items>` and listen for `loaded`.
253
384
 
254
- ### `gx.zeyos.Permission` → `zx.Permission`
385
+ ### `gx.zeyos.Permission` → `zx.Select.permission()`
255
386
 
256
387
  ```js
257
388
  // gx
258
389
  const permission = new gx.zeyos.Permission(host, { value: true, groups });
259
390
 
260
391
  // Zx
261
- const permission = new zx.Permission(host, { value: 'public', groups });
392
+ const permission = zx.Select.permission(host, { value: true, groups });
262
393
  permission.on('change', (event) => save(event.detail.value));
263
394
  ```
264
395
 
@@ -295,6 +426,26 @@ table.addData([{ ID: 1, name: 'Ada' }]);
295
426
 
296
427
  Use `setData`, `addData`, `updateRow`, `removeRow`, and `getData` for row operations.
297
428
 
429
+ ### Billing/transaction rows → `zx.Grid.BillingItems()`
430
+
431
+ Use the generic preset when the existing screen has description, quantity, unit, unit price,
432
+ currency, total, and optional parent rows:
433
+
434
+ ```js
435
+ const items = zx.Grid.BillingItems(host, {
436
+ data: rows,
437
+ units: unitChoices,
438
+ currencies: currencyChoices,
439
+ oneditcommit(event) {
440
+ if (!controller.accept(event.detail.changes)) event.preventDefault();
441
+ }
442
+ });
443
+ ```
444
+
445
+ The default line total is quantity × unit price. Keep taxes, discounts, subtotal/group updates,
446
+ rounding policy, posting, server persistence, and undo in the existing controller. Override
447
+ `lineTotal` only when that calculation is a stable screen-level rule.
448
+
298
449
  ### `gx.zeyos.Tabbox` and `gx.bootstrap.Tabbox` → `zx.Tabbox`
299
450
 
300
451
  ```js