@gemboss/ui 0.47.0 → 0.50.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 (68) hide show
  1. package/CHANGELOG.md +315 -0
  2. package/README.md +162 -18
  3. package/catalog/editor.html +1 -1
  4. package/catalog/index.html +53 -49
  5. package/gemboss-ui.css +4 -4
  6. package/package.json +23 -2
  7. package/parts/components.css +3 -3
  8. package/parts/core.css +1 -1
  9. package/parts/shell.css +2 -2
  10. package/src/app-shell.css +1 -1
  11. package/src/assistant/chat.tsx +50 -6
  12. package/src/assistant/host.tsx +19 -7
  13. package/src/assistant/index.ts +1 -1
  14. package/src/assistant/sessions.tsx +2 -1
  15. package/src/assistant-chat.css +1 -1
  16. package/src/assistant.css +1 -1
  17. package/src/auth-signin-plain.js +1 -0
  18. package/src/auth-sso-plain.js +1 -1
  19. package/src/auth.tsx +105 -8
  20. package/src/briefing-card.css +1 -1
  21. package/src/collections.css +1 -1
  22. package/src/create-shell.css +1 -0
  23. package/src/create-shell.tsx +221 -0
  24. package/src/editor-insert.css +1 -0
  25. package/src/editor.css +1 -1
  26. package/src/empty-stage.css +1 -0
  27. package/src/empty-stage.tsx +31 -0
  28. package/src/icons.tsx +2 -0
  29. package/src/index-table.tsx +7 -4
  30. package/src/index.ts +16 -2
  31. package/src/kit-plain.css +1 -1
  32. package/src/kit-plain.js +1 -1
  33. package/src/language-switcher-plain.js +1 -1
  34. package/src/language-switcher.css +1 -1
  35. package/src/language-switcher.tsx +10 -41
  36. package/src/list-page.css +1 -0
  37. package/src/list-page.tsx +239 -0
  38. package/src/menu-extras.css +1 -0
  39. package/src/menu-keys.ts +199 -0
  40. package/src/menu-layer.ts +2 -1
  41. package/src/menu-plain.js +1 -0
  42. package/src/menu.css +1 -0
  43. package/src/menu.tsx +83 -35
  44. package/src/pager-extras.css +1 -0
  45. package/src/pager.css +1 -0
  46. package/src/pager.tsx +88 -0
  47. package/src/portal.css +1 -1
  48. package/src/portal.tsx +19 -4
  49. package/src/segmented.tsx +12 -2
  50. package/src/selection.tsx +3 -1
  51. package/src/session-rail-plain.js +1 -1
  52. package/src/settings-dialog.tsx +29 -2
  53. package/src/shell.css +1 -1
  54. package/src/stage-path.css +1 -0
  55. package/src/stage-path.tsx +89 -0
  56. package/src/stage.css +1 -0
  57. package/src/stage.tsx +52 -0
  58. package/src/starting-point.css +1 -0
  59. package/src/starting-point.tsx +93 -0
  60. package/src/status-list.css +1 -0
  61. package/src/status-list.tsx +65 -0
  62. package/src/status-matrix.css +1 -0
  63. package/src/status-matrix.tsx +131 -0
  64. package/src/team-chat.css +1 -1
  65. package/src/topnav-fold.css +1 -0
  66. package/src/translation-row.css +1 -0
  67. package/src/translation-row.tsx +113 -0
  68. package/src/ui.css +1 -1
package/CHANGELOG.md CHANGED
@@ -2,8 +2,323 @@
2
2
 
3
3
  Versions up to 0.9.0 are described in the README, each in the section that introduced it.
4
4
 
5
+ ## Unreleased
6
+
7
+ ## 0.50.0 (24/10/2026)
8
+
9
+ **Continue with GemBoss says which name the page is on** (24/10/2026). Every internal tool answers to a flat name and an .ops
10
+ name; GemBoss sent the token to the one registered URL, so a person on the other name signed in there and the name they used
11
+ kept asking (review of gemlabs #1163).
12
+
13
+ Changed
14
+ - `gemBossSignInUrl(service, next, issuer, host?)` adds `&host=`; `AuthContinueWithGemBoss` passes the browser's
15
+ `location.host` once mounted (the server-rendered first paint links without it), and auth-sso-plain.js always does.
16
+ GemBoss only CHOOSES among the tool's registered URLs with it (`FLEET_SSO_SERVICES[...].alias`, apps/admin).
17
+ - `AuthSignIn` renders `method="post"` always and takes `action`: on a server-rendered page a press before React loaded was a
18
+ native GET, with the password in the URL, history and server logs (measured 24/10/2026: 8 Next sign-in pages in gemlabs).
19
+ - `?sso=not_member` now reads "Your GemBoss account is not a member here. Ask an admin to add your email." It said "Use the
20
+ password", but since 23/10/2026 both doors check the same member list, so the password door refuses that person too.
21
+
22
+ **StartingPoint: a chat door where the person asks** (24/10/2026). The chat door's picture always drew Gem asking, the person
23
+ answering and a "Drafting" tile, so a door that asks Gem a question (the empty Funnels page's "Which funnel should I build?")
24
+ read as Gem drafting pages. `chat.from: "me"` puts the person's question first and Gem's answer under it; `chat.step` names the
25
+ tile. Both optional: a door that passes neither draws as before.
26
+
27
+ **AuthSignIn: the whole sign-in form of a fleet tool** (23/10/2026). Every tool wrote its own form around the kit's pieces, and
28
+ the words drifted: "Your lark email", "Team password", "Access password", placeholders of dots, buttons saying `Enter →`, `Vào`,
29
+ `Continue`, `Log in` (measured on the live sign-in pages of 60 services, 23/10/2026: one of them, GemPress's, had the whole
30
+ standard). Chris: "cái này chắc phải đưa vào gemboss ui".
31
+
32
+ Added
33
+ - `AuthSignIn` (auth.tsx): Continue with GemBoss (with `service`), then Email, Password with its eye, the error and Sign in, in
34
+ the kit's words and with no placeholder. The tool passes `service`, `next`, `onSignIn({ email, password })` (returns the
35
+ server's sentence on a refusal, nothing once it has moved the browser on; a throw reads as the network) and an optional `foot`.
36
+ `SIGN_IN_OFFLINE` is that network sentence.
37
+ - `auth-signin-plain.js` (`@gemboss/ui/auth-signin-plain.js`), its own weight entry: `gembossUI.initSignIn()` fills an empty
38
+ `<form data-gb-signin>` with the same markup, for a page with no React. Loaded after auth-sso-plain.js. `data-submit="json"`
39
+ posts `{email, password}` and follows `data-next`; otherwise the browser posts the form and the server's sentence comes back
40
+ in `data-error`. A form written without `method` is posted (a GET would put the password in the URL). A redirect the server
41
+ answers with is followed as a page, never read as success. With `data-sso` but no auth-sso-plain.js it draws no button and
42
+ says so in the console. Its eye stops its click at the button, so kit-plain.js's delegated `initPasswordFields()` does not flip
43
+ it a second time.
44
+ - `initContinueWithGemBoss()` (auth-sso-plain.js) can be called again: a link it already set up gets no second click handler.
45
+ - `auth-signin-browser.test.ts`: the React and the plain form compared element by element in Chromium (a word changed on one
46
+ side is red), what each does on submit, the eye with kit-plain.js on the page; two negative controls.
47
+
48
+ **ListPage: the frame of a list page** (23/10/2026, step 3 of the list standard, Gems/CLAUDE.md §13). Every admin list page
49
+ drew its own frame on Polaris `Page`, and its states its own way: five kinds of empty state, four labels on a retry, a skeleton
50
+ of 3 or 4 lines or a spinner or nothing (survey of 14 pages, 23/10/2026).
51
+
52
+ Added
53
+ - `ListPage` (+ `ListPageAction`, `ListPageState`): the header (a way back, the title and its badge, one line under it, the page's
54
+ primary button, secondary actions), notices, a toolbar, and ONE card holding the list or the state it is in (`state`:
55
+ `loading` placeholder rows, `empty` the page's own empty state, `no-match` with Clear filters, `error` with Try again).
56
+ Polaris Page's numbers as the admin draws it, measured at the six widths. From a page column of 560px two secondary actions
57
+ are buttons and the rest are behind ⋯; under it, and below 768px, all of them. Below 768 the header is two rows and its
58
+ controls 44px. Its CSS is an entry, `list-page.css` (`@gemboss/ui/list-page.css`, imported by list-page.tsx).
59
+ `list-page-browser.test.ts`, four cases with negative controls.
60
+ - `Icon` `arrowLeft` (ListPage's way back, the arrow Polaris' Page draws there).
61
+ - ListPage, steps 3c and 3d (Pages, Themes, Plugins, Media, Offers, Funnels): `card={false}` for a list that draws its own cards
62
+ (its own empty stands on the page; loading, no match and error are still boxed), `cardProps` (stay on the card in every
63
+ state: a drop zone), `cardMeta` (beside the card's heading), and `cardLead`: rows at the top of the card in every state, the
64
+ state's body after them (Funnels' Website row, which does not wait for the list's read); a page with a lead always has its
65
+ card, so the lead stays the same element. Notices or a toolbar that draw nothing take no gap. The page's status region
66
+ ("Loading…") is one element in every state and only its words change (one added with its words in it is often not read out).
67
+ `list-page-browser.test.ts` cases 6–8, with negative controls.
68
+ - ListPage, step 3e (Tests): an `external` action (a new tab, a download) is a plain link, as a button and as a ⋯ row, not the
69
+ app's `linkAs`: Next prefetches its Link, so the Tests page's Export CSV built its file on every visit. Case 5.
70
+ - ListPage, step 3f (Products): `actionGroups` (Polaris Page's name), choices of one kind always behind ⋯ at every width, a
71
+ title and the one that is on marked `active` (Products' Auto-refresh: Every day, Off). Case 9, with a negative control.
72
+ - `MenuItem` `checked`: a choice (`menuitemradio`, `aria-checked`) with a check before the one that is on and the same space
73
+ before the others; `MenuGroup`: a titled `role="group"` of such rows. Their CSS is in the `menu-extras.css` entry (the core
74
+ is frozen). list-page-browser.test.ts now loads menu-extras.css, as the admin does through menu.tsx.
75
+ - A closed ⋯ has no layout (`menu-extras.css`: `.ui-menu:not([open]) > .ui-menu__list { display: none }`). A closed <details>
76
+ only skips painting: asked for geometry, its rows were laid out, so the admin's phone-width gate read a closed ListPage ⋯ at
77
+ 320px as rows cut by the left edge, and find-in-page matched them and opened the menu. menu-browser.test.ts case 23.
78
+ - `Pager` (+ `pageWindow`): the numbered pager (`ui-pager`, README "Long lists") as a React component, links (`href`, through
79
+ `linkAs`) or, for a list paged in state, buttons (`onPage`), `busy` while a page is on its way. With Chris's OK (24/10/2026)
80
+ the pager's rules moved out of kit-plain.css into `pager.css`, just before it in the core (same rules, same order, no new
81
+ weight; the cascade check paints 0 boxes differently); the buttons' look is the new `pager-extras.css` entry.
82
+ `pager-browser.test.ts`, four cases with negative controls. The admin's Products list uses it.
83
+
84
+ ### Before: the row ⋯ (step 2)
85
+
86
+ **The admin's row ⋯ is the kit's Menu** (23/10/2026, step 2 of 3: `RowMenu` in apps/admin swaps Polaris's Popover + ActionList for
87
+ `Menu`; step 3 is the "list page" component).
88
+
89
+ Added
90
+ - `Menu` takes `disabled` and `loading`: the ⋯ takes no click and no key and leaves the Tab order (`aria-disabled`, drawn by
91
+ ui.css); `loading` puts a ring in place of the dots and `aria-busy`. The ring (`.ui-menu__busy`) is in a new entry,
92
+ `menu-extras.css` (`@gemboss/ui/menu-extras.css`, imported by menu.tsx): menu.css is in the core and the frozen
93
+ gemboss-ui.css had no bytes left (+24 B gzip over), and the admin loads no kit-plain.css (its `.ui-spinner`). Turned off
94
+ while open, it closes, with focus that was in it back on ⋯; opened while off by anything but its button (find-in-page, a
95
+ script), it closes again. `menu-browser.test.ts` case 20, with a negative control.
96
+ - A long hint wraps (`menu-extras.css`): in a column of at most 32ch, under the label when the row has no room beside it, and
97
+ the list is never wider than the screen less 16px. One nowrap line of 80 characters made the list ~600px wide and ran it
98
+ past a phone screen. A short hint stays on one line right of the label. `menu-browser.test.ts` case 21.
99
+
100
+ Changed
101
+ - Escape that closes an open React menu from the page-wide listener (focus on the ⋯, or nowhere in the menu) is taken
102
+ (default prevented), as kit-plain.js already takes it, so a dialog listening after it does not close on the same Escape.
103
+ `menu-browser.test.ts` case 22.
104
+
105
+ **The ⋯ menu is ready to be the admin's row menu** (23/10/2026, step 1 of 3: the kit only; the admin's `RowMenu` moves onto it next).
106
+
107
+ Added
108
+ - `menu.css`, its own file (`@gemboss/ui/menu.css`), imported by `menu.tsx`: the `.ui-menu` rules (placement, the floated list,
109
+ the summary marker, `--end`), the hint, the separator, the disabled row and the icon button (`ui-btn--icon`, with its 44px
110
+ rules under a finger), moved out of `kit-plain.css` unchanged. The admin loads `ui.css` and the shell, never `kit-plain.css`,
111
+ so a `Menu` there had no placement and no square ⋯. The core carries the file where the rules stood, just before
112
+ `kit-plain.css` (`packages/ui/scripts/parts.mjs` CORE and the frozen copy in `packages/ui/scripts/parts-gate.mjs`: a split, not new weight), so
113
+ `parts/components.css` and `gemboss-ui.css` hold the same rules in the same order.
114
+ - A menu's semantics and keys, `menu-keys.ts`, shared by `Menu` and `LanguageSwitcher` (whose own key handler and `openMenu`
115
+ it replaces). The list is `role="menu"` named by the trigger's label, rows `menuitem` with `tabindex="-1"`, the trigger
116
+ `aria-haspopup`. An opening by a click, Enter or Space lands on the first row that can act (↓ on ⋯ the first, ↑ the last;
117
+ a script's `open` moves no focus), with or without the popover API. On a row (a field in the list keeps its own keys):
118
+ ↑ ↓ wrap and skip disabled rows; Home End; Space acts on a link row; Escape closes with focus back on ⋯ and is taken; Tab
119
+ closes and goes on. A chosen row runs with focus already back on ⋯ (a popup it opens returns there), then the menu
120
+ closes, link rows too. One menu open at a time, whatever opened the next one (a key is not a click outside); a menu that
121
+ holds the one opening stays open.
122
+ - `menu-plain.js` (`@gemboss/ui/menu-plain.js`), the same for plain markup: a SECOND script after `kit-plain.js`, which has no
123
+ room for it under its weight ceiling. It adds the roles the markup leaves out and runs by itself. **A plain surface that
124
+ wants a menu's keys adds this script** (or `import "@gemboss/ui/menu-plain.js"` after the kit-plain.js import); without it
125
+ a plain menu works as before.
126
+ - `menu-browser.test.ts` (`test:ui-browser`): 19 cases on `Menu` and on plain markup, each with a negative control: roles,
127
+ keys, where an opening lands, choosing a row, one open, a menu in a table row that opens on click, the last row of a
128
+ scrolling table, Escape and a selection, the admin's own sheets + `menu.css` against the core, a field in the list, a menu
129
+ in a menu or the account menu, a menu with no list, a popup's Tab trap, a prevented opening, no popover API, and a React
130
+ Menu on a page that also runs kit-plain.js + menu-plain.js, Tab from an open menu's ⋯, an Escape something else took, and
131
+ the plain LanguageSwitcher beside menu-plain.js. `plain-ssr.test.ts` (`test:packages`): every `*-plain.js` loads in Node
132
+ without throwing.
133
+ - Review of #5179: Tab from the ⋯ of an open menu closes it and focus goes on (its rows are out of the Tab order, so the list
134
+ stayed open over the page); the page-wide Escape leaves an Escape something else took (`defaultPrevented`: a dialog, a
135
+ combobox) alone; a close clears the one-open mark an opening left (an opening closed before its toggle let the next one skip
136
+ the rule); menu-plain.js leaves the plain LanguageSwitcher (`.gb-lang`, its own keys and checked-row focus) to
137
+ language-switcher-plain.js and only closes it with the others (two scripts put focus twice).
138
+ - editor-insert.css: the insert menu's "Ask AI: …" row (E2), in the accent colour, highlighted with the accent wash when
139
+ selected, and one line however long the request.
140
+ - Four pieces GemPress's redesign (approved by Chris 23/10/2026) drew with its own CSS, each its own entry, presentational
141
+ (no state, no inline style) and written by hand from plain HTML with the classes listed at the top of its file (README "Review
142
+ pieces"):
143
+ - `StagePath` (`stage-path.css`): stages that unlock in order, an `<ol>` of cards with a number (a green ✓ when
144
+ `done >= total`), the name, "3 / 35 pages approved" and a `<progress>` bar; the current stage (default the first not done)
145
+ has an ink outline and an ink number, `aria-current="step"`. `stagePathDone`.
146
+ - `StatusMatrix` (`status-matrix.css`): rows × columns on the kit table, each cell "live / total" with what waits under it
147
+ ("5 draft · 2 changed"), toned all live (green) or has drafts (amber), "—" where there is nothing to count.
148
+ `statusMatrixTone`.
149
+ - `TranslationRow` (`translation-row.css`): one string under review, key + state badge + who, source beside target (one
150
+ column under 900px or in a row narrower than 600px), the checks and the surface's action; `current` draws an ink border
151
+ and a 3px ink bar; focusable (`tabIndex={-1}`) for a J / K list. The editable control is the surface's (`target`, a node or
152
+ a function given the ids for `aria-describedby`). `TRANSLATION_STATE_LABELS`, `translationRowId`.
153
+ - `StatusList` (`status-list.css`): a quiet system-health strip, a dot (success / warning / danger, or an empty ring for
154
+ muted), a bold name and one muted sentence, a link when it has `href`.
155
+ - `CreateIcon`, `CreateStepper`, `CreateRail`, `CreateDoors`, `CreateFilter`, `CreateGrid`, `CreateCard`, `CreateParts`,
156
+ `CreatePath` (`create-shell.css`, its own entry): the create shell, one shape for making a page, a funnel or a section inside
157
+ the frame each already opens in: an icon tile and "Choose → Make it yours → Gem builds it" in the header, a rail with the doors
158
+ (ask the assistant, the library, blank: at most three) and chip questions ("Who it is for": cold, warm, ready, each with its
159
+ colour's dot; "What it does"), cards with a Stage picture, who it is for, a name, one line and what is inside. `CREATE_AUDIENCE`
160
+ holds the words for the three temperatures (23/10/2026, drawings A1/A3/A4).
161
+
162
+ Changed
163
+ - shell.css is the one owner of the menu row: `.gemboss-account-menu-item, .ui-menu__item` keep their shared declarations,
164
+ the account row stays `display: block`, and `.ui-menu__item` is the flex row (label, then the hint on the right; `gap: 8px`,
165
+ nowrap, the small radius) that app-shell.css used to lay over it. app-shell.css drops its `.ui-menu__item` rule. A plain
166
+ `details.ui-menu` on the core: 0 differences in 2.3 million computed values against main (bundle and parts, built and
167
+ minified; 1280 and 390, mouse and finger, light, dark, the `data-gb-menus` flag). In the admin, which loads shell.css and
168
+ not app-shell.css, the assistant dock's ⋯ rows become that flex row too (their boxes unchanged); the account menu's rows
169
+ do not change.
170
+ - `initMenus()` (kit-plain.js) puts focus back on a menu's button BEFORE a chosen row's own handler, then closes the menu as
171
+ before. Still opt-in: a page that never calls it keeps its menus open on a chosen row. `closeMenus(except)` leaves open a
172
+ menu that holds `except` (a kit menu in an open account menu).
173
+ - kit-plain.js: a popup's Tab trap leaves `tabindex="-1"` out (a menu's rows): counted as its last control, Tab from the real
174
+ last one left the popup.
175
+ - `escapeTaken` (selection.tsx, kit-plain.js): a menu or a dialog takes Escape when it is SHOWN (`checkVisibility`), not when
176
+ it has a box. A closed `<details>`' content keeps its boxes, so with every row's ⋯ list `role="menu"` the box test would
177
+ have handed every Escape in a selectable table to its closed menus.
178
+ - `Menu`: one page-wide set of listeners for every mounted menu (`registerMenu`: outside click, Escape, the float, one open),
179
+ not a click and a keydown listener per menu; the summary opens the menu itself with or without the popover API.
180
+ - kit-plain.js stays under its ceiling (8 784 B gzip of 9 189); menu-plain.js weighs 1 047 B (ceiling 1 559).
181
+ - collections-board.test.ts: the keyboard case loads menu-plain.js and walks the rows with ↓ (it walked them with Tab); the
182
+ two "no popover API" negatives open the menu by script, since a click now focuses its first row and, without the top
183
+ layer, that focus scrolls the table's frame to it (menu-browser.test.ts case 15 holds the click).
184
+
185
+ ## 0.49.0 (23/10/2026)
186
+
187
+ Fixed
188
+ - collections.css: a folded index row's primary cell wraps, as the fold always meant to. Its `white-space: normal` was
189
+ (0,2,1) and lost to the fold's own nowrap on every cell (0,2,2), so free text under a title (GemPress's "missing: …"
190
+ note) and an unlinked title stayed on one line, widened the row and scrolled the list sideways (384px at 320 on
191
+ GemPress). A linked title (`.ui-table__title`) and its second line keep their own nowrap and are cut as before; a token
192
+ too long for the row (a URL, a slug) breaks anywhere. A cell whose content fits is unchanged (gemcare People at
193
+ 320/390/768, 50 rows: no row taller).
194
+ - ui.css: `.ui-sr-only` is pinned on the inline axis (`left: 0`; also portal.css `.gb-sr-only`, team-chat.css
195
+ `.gb-tc-sr`, shell.css `.gemboss-assistant-sronly`, assistant.css `.gb-dock-sr`). It stood where it would have been in the
196
+ flow, so inside a frame that scrolls sideways (a table, a board, a segmented row) it could stand far past the frame and push
197
+ the PAGE sideways: the "Actions" header of a 2105px IndexTable, 792px at 1280; search-radar /aeo 428px; a bare
198
+ `ui-table-wrap` on a page with ui.css only, 1109px. It keeps its line, so a screen reader's cursor stays by the row.
199
+ (#5176 had made `.ui-table-wrap` `position: relative` instead and put `noreferrer` on a row's Cmd-click; both are withdrawn
200
+ before release: a positioned frame paints over an earlier dropdown or sticky bar with no z-index, and the referrer is the
201
+ page's to set, see the README's collection rules.)
202
+ - shell.css: the topbar's actions column never goes narrower than what it holds (`minmax(max-content, 1fr)`, and
203
+ `max-content` under 1120px), and the search column gives way instead (`minmax(0, …)`). With a 200px minimum the admin's
204
+ right-hand chips (~512px) spilled left over the search: 160px at 1280, 239 at 1121 (22/10/2026). The brand column is
205
+ unchanged (a surface's tabs there keep their scroller); with room to spare the search is centred as before. The
206
+ `max-width: 1040px` topbar rule, which the base rule after it always overrode, is removed.
207
+
208
+ Added
209
+ - `@gemboss/ui/editor-insert.css` (23/10/2026), its own entry: the page editor's insert menu, the + on a picked element (admin
210
+ `InsertElementMenu`). It is a list to search, with the row in focus described beside it, 32px rows (44px under a finger), and a
211
+ sheet along the bottom under 640px. It's not in editor.css because every editor surface loads that.
212
+ - IndexTable `flush` (root `.ui-index--flush`): inside a card that is already the frame, as DataTable's and ResourceList's. The
213
+ table drops its own border, radius and shadow; the bulk bar covers the 37px header and draws its rule; folded, the bar sits on
214
+ the first row and the sort chips are inset 12px like the rows. partner-radar and talent-radar drew two frames (23/10/2026).
215
+ - `Segmented` takes `scroll`: text tabs keep one line and the group scrolls sideways inside itself when it is wider than its
216
+ room, what kit-plain.css already does for plain surfaces. Set as layout styles on the component (gemboss-ui.css had no
217
+ bytes left under its weight ceiling); opt-in; first user the admin's Plugins filter (five tabs broke to three lines each at
218
+ 390, 22/10/2026).
219
+ - `AssistantHost` takes `waiting`: the chat's last answer ends on choices only the person can make. The status bar and the
220
+ header say "Waiting on you" with an amber dot (`--gemboss-warning`, set on the dot: the bundle is at its gzip ceiling), ahead of "Done" and "Ready". It is a prop, not dock
221
+ state, so the plain frame's reducer copy is unchanged. New label `waiting`.
222
+ - assistant-chat.css `.gemboss-ec-decide` / `-decide-label`: the box that holds a waiting question's choices over the message box
223
+ (22/10/2026).
224
+ - `Stage`, `StageWindow`, `StageTile`, `StageBubble`, `StageLine` (`stage.css`, its own entry): the one way the app draws a
225
+ picture, taken from gemboss.ai's mega menu: a night-blue stage lit with iris, a dot grid fading out, glass tiles (one picked,
226
+ white and glowing), white windows lifted off it showing a real screenshot. Its colours are named once on `.ui-stage`
227
+ (an illustration may light itself, website/DESIGN-LANGUAGE.md §3). A stage is hidden from screen readers unless
228
+ `decorative={false}` (a panel holding words a reader needs); `StageWindow` then takes `alt` for its screenshot.
229
+ - `EmptyStage` (`empty-stage.css`, its own entry): an area with nothing in it yet, centred like an admin empty state: a
230
+ Stage picture drawn from the area's own nouns, a name, one line, the page's own buttons (`.ui-empty-stage__row` lays tiles
231
+ side by side). Replaces the picture-less empty states (22/10/2026).
232
+ - `StartingPoint` (`starting-point.css`, its own entry): "Choose your starting point", the same three doors wherever something new
233
+ is made: ask the assistant (a two-line chat on the stage), a template (two real pages fanned on the stage, `images`), blank (an
234
+ empty window with "+ Add a section"). Each door is a picture, a name, one line and a button; at most three. Learned from
235
+ Replo's Plans, Tasks, Skills and reports (22/10/2026).
236
+ - `ChatStarter` takes `sub` (one line under the label) and `icon`; a list where any starter has either is drawn as a column
237
+ of cards (`.gb-chat-starters-cards`: icon in an iris tile, label, the line under it). Pills are unchanged.
238
+ - `firstBlank(text)` and `selectBlank(textarea, text)`: a starter with `fill` now selects the first `[…]` in its prompt, so
239
+ typing replaces it ("Build a funnel for [which product?]"); with no blank the caret goes to the end (22/10/2026).
240
+
241
+ Changed
242
+ - collections.css: an index table's header is the data table's (the surface, 13px regular, 12px above and below: 45 tall,
243
+ opaque) and its cells 8px 6px (a one-line row 37), taken from the data table's rules in ui.css rather than restated. It was Polaris IndexTable's grey band, 12px / 550, 36 tall, over 6px cells: two
244
+ looks for one thing, the heavier one on the lists people open (Chris 23/10/2026: "GemPress's table reads lighter than
245
+ Funnels"). Its bulk bar is 46 (the wrap's top line + the header), 45 flush, 38 folded below 768, wherever in the index
246
+ it sits; a resource list's stays 38. The admin's unselectable Polaris IndexTables take the same (gemboss #5185).
247
+ - `SessionRail` (and `sessionRail` in session-rail-plain.js): a conversation's `preview` follows its time on the same line,
248
+ "3 hr ago · Nothing is published yet…", one line cut with an ellipsis (`.gemboss-assistant-thread-meta`, shell.css). The
249
+ admin now sends the answer's closing sentence as `preview`, how the conversation ended (23/10/2026, learned from Replo).
250
+ - editor.css: the page editor's column (`.gemboss-pageedit .gemboss-ec-dock-body`: its section list and the element settings) is
251
+ 44px tall under a finger and at a phone's width only; under a mouse from 48em its buttons, fields and selects are the kit's own
252
+ sizes, as the same settings panel on the canvas (they were 44 at every width: 44 against 32 for the same panel, 22/10/2026).
253
+ - editor.css: the page editor's icon buttons (`.gemboss-pageedit .gemboss-pv-device`: ↶ ↷, ⋯) are 44 × 44 under a finger and
254
+ at a phone's width only; under a mouse from 48em they are the kit's 28 × 28, as on every other editor bar (they were 44 at
255
+ every width, 21/10/2026).
256
+
257
+ **The briefing card leaves its picture out when the card is too narrow to hold it** (Market, 22/10/2026: with the assistant pinned beside the page the
258
+ card was 693px wide and 510px tall, the gem under the buttons).
259
+
260
+ Changed
261
+ - `.ui-briefing` is a size container (`container-type: inline-size`) and `@container (max-width: 631px)` hides `.ui-briefing__art`. 631 is the
262
+ content width the picture's row needs (380 text + 32 gap + 220 picture = 632) less one: a container query measures the content box, so it
263
+ is the card's 720px at the desktop padding. It is the CARD that is measured, not the screen: the same card is wide on a desktop and
264
+ narrow beside a pinned panel. Measured in Chromium at a 1440px window: picture beside the text at 720px and wider, none at 719px and
265
+ narrower, and the card 258px tall at 693px instead of about 430. `briefing-card.test.tsx` holds the rule.
266
+
267
+ ## 0.48.0 (21/10/2026)
268
+
269
+ **LanguageSwitcher: what its first three surfaces had to work around** (21/10/2026; GemCommunity #333, GemNews #2,
270
+ GemFeedback #82).
271
+
272
+ Added
273
+ - `gembossUI.languageSwitcher()` (plain JS) returns `update({ value, labels, suggested, languages })` and
274
+ `setLabels(labels)`: the switcher changes where it is, no new element. The button's name and label, the menu's name and
275
+ the suggested hint change in place; the rows are drawn again only when `languages` or `suggested` change, and the row
276
+ that had focus has it again. An open menu stays open (placed again), focus stays where it was. `labels` given replace
277
+ those words only; `suggested: null` takes the row away. GemNews's `paintLang()` rebuilt the switcher (destroy + new,
278
+ focus carried by hand) every time its language changed. `set()`, `value()`, `open()`, `close()`, `destroy()` unchanged.
279
+ - `TopbarNav` `foot`: the app's own section at the foot of the FOLDED tab list, under a line (`.gb-topnav-foot`), and the
280
+ class `gb-topnav-unfolded` for its copy in the topbar's end, shown only while the tabs are unfolded. A portal topbar
281
+ (brand, tab menu, search, theme, Sign in) has no room for a 55px language button at 320–390: GemFeedback hid it and
282
+ printed links at the foot of the page. Now `foot={<LanguageSwitcher variant="inline" end={false} …/>}` plus
283
+ `<LanguageSwitcher className="gb-topnav-unfolded" …/>` in the end slot: one language control in the bar at every width.
284
+ - `TopbarNav` `foldAt={760}`: the tabs fold over the whole phone topbar (≤760px) instead of under 600px, for a bar whose end
285
+ holds more (GemFeedback's three tabs ran under the switcher at 601–687). Default 600, unchanged.
286
+ - `topnav-fold.css` (`@gemboss/ui/topnav-fold.css`), a new entry `TopbarNav` imports after `portal.css`: the rules for `foot`,
287
+ `foldAt`, `gb-topnav-unfolded` and the ellipsis below. `portal.css` is in the frozen `gemboss-ui.css`, which had 24 bytes
288
+ left under its ceiling. 0.3 KB gzip minified (ceiling 789 in `weight-budget.json`).
289
+
290
+ Changed
291
+ - The LanguageSwitcher's rows, hint and separator are one class (0,1,0) instead of zero specificity (`:where`), with the
292
+ core's values: an app's `button { font: inherit }` (0,0,1) beat the 13px rows (GemCommunity 14 / 16px, GemNews 16px; both
293
+ restated 13px). An app restyles them on purpose with one class of its own. The surface and the box stay `:where`.
294
+ - Every `--gemboss-*` token `language-switcher.css` reads has its light value as a fallback: a page with neither the core nor
295
+ `gemboss-tokens.css` draws the switcher right (light).
296
+ - `TopbarNav`'s folded button: the open tab's name sits in `span.gb-topnav-fold-label` and gives way with an ellipsis before
297
+ it runs under the topbar's end ("Changelog" at 320 overlapped the search button by 40px, with or without a switcher).
298
+
299
+ Fixed
300
+ - The switcher's tick touched the language's name on a page with app-shell.css and no core (GemCommunity's /welcome, for the
301
+ brand): app-shell.css's `.ui-menu__item` (0,1,0) reads `--ui-space-2` / `--ui-radius-sm`, unset there, and beat the row's
302
+ gap. The row's gap and corners are at two classes (`.gb-lang__item.ui-menu__item`, 8px / 6px). app-shell.css itself is
303
+ unchanged: the frozen gemboss-ui.css has 5 bytes left under its ceiling, and two fallbacks there cost 3.
304
+ - README: "the switcher fits the phone bar at 320–390" is AppShell's bar; a portal Topbar + TopbarNav puts it in the tab
305
+ menu (`foot`).
306
+
5
307
  ## 0.47.0 (21/10/2026)
6
308
 
309
+ **The kit's menu rows at the admin's density** (Chris 21/10/2026, of a menu drawn 44px tall: "nó đang to hơn bình thường";
310
+ 0.43.0 did this for the editor's menus).
311
+
312
+ Changed
313
+ - `.ui-menu__item` (`Menu` / `MenuItem`, kit-plain menus, the assistant header's ⋯) and `.gemboss-account-menu-item`
314
+ (`AccountMenu`, the admin's account corner included) under a mouse: a Polaris ActionList item, 32px tall with 4px padding
315
+ above and below (were 44px with 12px, for every pointer). A kit menu's rows sit 6px in from a list padded 6
316
+ (`.ui-menu__list`, `.gb-dock-menu-list`: were 4), as an ActionList item in its section; the account menu's rows keep 16px at
317
+ the sides, under its head. Under a finger or on a screen 767px or narrower: 44px with 12px 16px, as before. The row states
318
+ `box-sizing: border-box` itself. `language-switcher.css` repeats the core's row for a page without the core (gemboss.ai), so it
319
+ follows: list padded 6, rows 32 / 4px 6px under a mouse, 44 / 12px 16px under a finger. The density gate measures both (`menuItem`, `menuSurface` in polaris-reference.json), red
320
+ on 0.46's rows. Measured on the admin (local): the account menu's four rows 176px → 128px, the assistant's ⋯ rows 44 → 32.
321
+
7
322
  **A choice made by looking, and a logo sheet** (GemBoss Brand › Logo, 21/10/2026: the seller picks one of three AI logos from
8
323
  GemMedia, sees each the way it will be used, and confirms one).
9
324