@gemboss/ui 0.48.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.
- package/CHANGELOG.md +260 -0
- package/README.md +127 -11
- package/catalog/editor.html +1 -1
- package/catalog/index.html +10 -10
- package/gemboss-ui.css +3 -3
- package/package.json +22 -2
- package/parts/components.css +3 -3
- package/parts/core.css +1 -1
- package/parts/shell.css +2 -2
- package/src/app-shell.css +1 -1
- package/src/assistant/chat.tsx +50 -6
- package/src/assistant/host.tsx +19 -7
- package/src/assistant/index.ts +1 -1
- package/src/assistant/sessions.tsx +2 -1
- package/src/assistant-chat.css +1 -1
- package/src/assistant.css +1 -1
- package/src/auth-signin-plain.js +1 -0
- package/src/auth-sso-plain.js +1 -1
- package/src/auth.tsx +105 -8
- package/src/briefing-card.css +1 -1
- package/src/collections.css +1 -1
- package/src/create-shell.css +1 -0
- package/src/create-shell.tsx +221 -0
- package/src/editor-insert.css +1 -0
- package/src/editor.css +1 -1
- package/src/empty-stage.css +1 -0
- package/src/empty-stage.tsx +31 -0
- package/src/icons.tsx +2 -0
- package/src/index-table.tsx +7 -4
- package/src/index.ts +16 -2
- package/src/kit-plain.css +1 -1
- package/src/kit-plain.js +1 -1
- package/src/language-switcher.tsx +10 -41
- package/src/list-page.css +1 -0
- package/src/list-page.tsx +239 -0
- package/src/menu-extras.css +1 -0
- package/src/menu-keys.ts +199 -0
- package/src/menu-layer.ts +2 -1
- package/src/menu-plain.js +1 -0
- package/src/menu.css +1 -0
- package/src/menu.tsx +83 -35
- package/src/pager-extras.css +1 -0
- package/src/pager.css +1 -0
- package/src/pager.tsx +88 -0
- package/src/portal.css +1 -1
- package/src/segmented.tsx +12 -2
- package/src/selection.tsx +3 -1
- package/src/session-rail-plain.js +1 -1
- package/src/settings-dialog.tsx +29 -2
- package/src/shell.css +1 -1
- package/src/stage-path.css +1 -0
- package/src/stage-path.tsx +89 -0
- package/src/stage.css +1 -0
- package/src/stage.tsx +52 -0
- package/src/starting-point.css +1 -0
- package/src/starting-point.tsx +93 -0
- package/src/status-list.css +1 -0
- package/src/status-list.tsx +65 -0
- package/src/status-matrix.css +1 -0
- package/src/status-matrix.tsx +131 -0
- package/src/team-chat.css +1 -1
- package/src/translation-row.css +1 -0
- package/src/translation-row.tsx +113 -0
- package/src/ui.css +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,266 @@ Versions up to 0.9.0 are described in the README, each in the section that intro
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
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
|
+
|
|
7
267
|
## 0.48.0 (21/10/2026)
|
|
8
268
|
|
|
9
269
|
**LanguageSwitcher: what its first three surfaces had to work around** (21/10/2026; GemCommunity #333, GemNews #2,
|
package/README.md
CHANGED
|
@@ -54,7 +54,8 @@ its CSS out of the build with it (`sideEffects`). gzip of the minified file, as
|
|
|
54
54
|
| Part | Import (Node / bundler) | File in the tarball | What | gzip | Who loads it |
|
|
55
55
|
|---|---|---|---|---:|---|
|
|
56
56
|
| core | `@gemboss/ui/core.css` | `parts/core.css` | the box-sizing reset + the tokens | 1.0 KB | every surface, first |
|
|
57
|
-
| components | `@gemboss/ui/components.css` | `parts/components.css` | the kit (`ui.css`), the field look, the plain-HTML primitives (`kit-plain.css`:
|
|
57
|
+
| components | `@gemboss/ui/components.css` | `parts/components.css` | the kit (`ui.css`), the field look, the ⋯ menu and the icon button (`menu.css`), the plain-HTML primitives (`kit-plain.css`: chips, report pieces, popup body) | 8.2 KB | every surface, second |
|
|
58
|
+
| menu | `@gemboss/ui/menu.css` | `packages/ui/src/menu.css` | the ⋯ menu and its icon button (23/10/2026, moved out of `kit-plain.css`, same rules): part of the core (components, just before `kit-plain.css`, where its rules stood) AND its own file | 0.6 KB | `Menu` imports it, so a surface without the core draws the menu: the admin loads `ui.css` and the shell, never `kit-plain.css` |
|
|
58
59
|
| shell | `@gemboss/ui/shell.css` | `parts/shell.css` | rail, topbar, main, mobile drawer, the thread rail, topbar slots, base text, page widths (`app-shell.css`) | 5.5 KB | every surface, third |
|
|
59
60
|
| auth | `@gemboss/ui/auth.css` | `packages/ui/src/auth.css` | the sign-in screen (+ the grid backdrop, 0.30.0) | 2.8 KB | `AuthLayout` & co. import it |
|
|
60
61
|
| assistant | `@gemboss/ui/assistant.css` | `packages/ui/src/assistant.css` | the dock and the launcher | 2.2 KB | `AssistantHost`, the launcher import it |
|
|
@@ -66,6 +67,7 @@ its CSS out of the build with it (`sideEffects`). gzip of the minified file, as
|
|
|
66
67
|
| editor | `@gemboss/ui/editor.css` | `packages/ui/src/editor.css` | the editors | 7.9 KB | the `@gemboss/ui/editor` components import it |
|
|
67
68
|
| editor-repeater | `@gemboss/ui/editor-repeater.css` | `packages/ui/src/editor-repeater.css` | `RepeaterList`: a setting that is a list, as rows | 0.9 KB | `RepeaterList` imports it |
|
|
68
69
|
| editor-tiles | `@gemboss/ui/editor-tiles.css` | `packages/ui/src/editor-tiles.css` | `ChoiceTiles`: a closed choice of shapes as picture tiles | 0.5 KB | `ChoiceTiles` imports it |
|
|
70
|
+
| editor-insert | `@gemboss/ui/editor-insert.css` | `packages/ui/src/editor-insert.css` | the page editor's insert menu (the + on a picked element) | 0.6 KB | admin `InsertElementMenu` imports it |
|
|
69
71
|
| reactions | `@gemboss/ui/reactions.css` | `packages/ui/src/reactions.css` | the emoji reactions and their picker | 0.8 KB | `Reactions` / `ReactionPicker` import it |
|
|
70
72
|
| assistant-engine | `@gemboss/ui/assistant-engine.css` | `packages/ui/src/assistant-engine.css` | the engine picker (chip + Engine/Setup/Status popup) and the bridge notice | 1.9 KB | `EnginePicker` / `EnginePanel` / `BridgeNotice` import it |
|
|
71
73
|
| chat | `@gemboss/ui/chat.css` + `chat-composer.css` | `packages/ui/src/chat.css`, `packages/ui/src/chat-composer.css` | the chat body, the composer | 3.0 + 0.9 KB | the chat components (0.29.0); a page writing the markup links them |
|
|
@@ -82,6 +84,10 @@ its CSS out of the build with it (`sideEffects`). gzip of the minified file, as
|
|
|
82
84
|
| progress-ring | `@gemboss/ui/progress-ring.css` | `packages/ui/src/progress-ring.css` | `ProgressRing`: "3 of 5" as a ring (0.43.0) | 0.2 KB | `ProgressRing` imports it |
|
|
83
85
|
| spot-art | `@gemboss/ui/spot-art.css` | `packages/ui/src/spot-art.css` | `SpotArt`: six brand pictures for pauses and empty states (0.43.0) | 0.4 KB | `SpotArt` imports it |
|
|
84
86
|
| logo-sheet | `@gemboss/ui/logo-sheet.css` | `packages/ui/src/logo-sheet.css` | `LogoSheet`: one logo as a person judges it, the mark on a checkerboard, the lockup on a light and a dark header, the favicon at its real 32px (unreleased) | 0.5 KB | `LogoSheet` imports it |
|
|
87
|
+
| stage-path | `@gemboss/ui/stage-path.css` | `packages/ui/src/stage-path.css` | `StagePath`: stages that unlock in order, each a card with its number (✓ when done), its count and a thin bar; the current one in ink (unreleased) | 0.7 KB | `StagePath` imports it; a plain-HTML page links it (README "Review pieces") |
|
|
88
|
+
| status-matrix | `@gemboss/ui/status-matrix.css` | `packages/ui/src/status-matrix.css` | `StatusMatrix`: rows × columns of "live / total" cells on the kit table, all live or has drafts (unreleased) | 0.3 KB | `StatusMatrix` imports it; a plain-HTML page links it |
|
|
89
|
+
| translation-row | `@gemboss/ui/translation-row.css` | `packages/ui/src/translation-row.css` | `TranslationRow`: one string under review, source beside target, the checks and the action (unreleased) | 0.6 KB | `TranslationRow` imports it; a plain-HTML page links it |
|
|
90
|
+
| status-list | `@gemboss/ui/status-list.css` | `packages/ui/src/status-list.css` | `StatusList`: a quiet system-health strip, a dot, a name and one sentence each (unreleased) | 0.5 KB | `StatusList` imports it; a plain-HTML page links it |
|
|
85
91
|
| bundle | `@gemboss/ui/gemboss-ui.css` | `gemboss-ui.css` | core + components + shell + auth + assistant + chat + portal + theme-dark | 21.9 KB | compatibility only (see CHANGELOG 0.23.0) |
|
|
86
92
|
|
|
87
93
|
The core alone is 14.7 KB against 21.7 for the bundle (each part opens with a short header naming the gemboss commit, kept through minify, so a surface serving the parts can still say which commit its CSS is); a list page adds collections (4.6), a sign-in page auth (2.6).
|
|
@@ -130,7 +136,7 @@ in order and reversed, and holds every element's computed style to the bundle's
|
|
|
130
136
|
CI; negative control: two entries that only agree by order). An entry loaded BEFORE the core is not supported: measured,
|
|
131
137
|
`.ui-empty--no-match` loses its 32px padding to `.ui-empty`. Two kit-plain.css rules that overrode shell.css by coming
|
|
132
138
|
after it (the menu row's flex, the thread rail's 44px buttons at phone width) moved to the end of app-shell.css, same
|
|
133
|
-
weight, so they still follow it.
|
|
139
|
+
weight, so they still follow it. (The menu row's flex is shell.css's own since 23/10/2026: one owner for the row.)
|
|
134
140
|
|
|
135
141
|
**A new component** gets its own entry: a file in `src/`, listed in `ENTRIES` in `packages/ui/scripts/parts.mjs`, exported,
|
|
136
142
|
imported by the component that draws it, with a ceiling in `weight-budget.json`. It never goes into the core or into
|
|
@@ -142,6 +148,37 @@ component that draws an entry's class without importing it.
|
|
|
142
148
|
built part now). Its editor, assistant and sign-in modules already imported the same entry files the components now
|
|
143
149
|
import, so nothing moves.
|
|
144
150
|
|
|
151
|
+
## ListPage (23/10/2026): the frame of a list page
|
|
152
|
+
|
|
153
|
+
One component for a page that lists one kind of thing (the admin's Rules first; the other lists move one PR at a time):
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
<ListPage title="Rules" subtitle="12 rules · 9 on." back={{ href: "/app/content", label: "Types" }} linkAs={Link}
|
|
157
|
+
primaryAction={<Button variant="primary">New rule</Button>}
|
|
158
|
+
secondaryActions={[{ content: "Simulator", onAction }, { content: "Export CSV", url: "/export" }]}
|
|
159
|
+
notices={<Banner …/>} toolbar={<ListToolbar …/>}
|
|
160
|
+
state={saved === null ? (unread ? "error" : "loading") : saved.length ? "ready" : "empty"}
|
|
161
|
+
empty={<EmptyStage …/>} noMatch={{ onClear }} error={{ title: "Your rules couldn’t be read.", message, onRetry }}
|
|
162
|
+
cardTitle="Your rules" after={<PlaybookLibrary …/>}>
|
|
163
|
+
{rows}
|
|
164
|
+
</ListPage>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
- The page says WHICH state its list is in; the kit draws it, the same on every list: placeholder rows while loading, the
|
|
168
|
+
page's own empty state, "Nothing matches" + Clear filters, a read that failed + Try again. A change that failed over a list
|
|
169
|
+
that IS there is a notice, not the error state.
|
|
170
|
+
- `primaryAction` is the page's own button (so a Publish button that counts changes stays itself). Secondary actions are data:
|
|
171
|
+
two stand as buttons from a 560px page column, the rest are behind ⋯; under 560 or below 768px, all are behind ⋯.
|
|
172
|
+
- `cardLead`: rows at the top of the card in every state, before the state's loading, empty or error (a row that does not
|
|
173
|
+
wait for the list's read, as Funnels' Website). A page with a lead always has its card, so the row stays the same element.
|
|
174
|
+
`card={false}` is for a list that draws its own cards (a grid); `cardProps` stay on the card in every state (a drop zone).
|
|
175
|
+
- `actionGroups`: choices of one kind (a cadence, a view) behind ⋯ at every width, titled, the one that is on `active`; never
|
|
176
|
+
header buttons. The rows are `MenuItem checked` in a `MenuGroup` (`menuitemradio`, a check before the chosen one).
|
|
177
|
+
- Numbers: Polaris Page in the admin (title 20/24/700 on a 28px row, the line under it 12/16 muted 2px below, actions 28px 8
|
|
178
|
+
apart, 24px above and under the header from 768, 16 below). Below 768 the header is two rows and its controls are 44px.
|
|
179
|
+
Colours are Polaris' where Polaris is loaded (`--p-color-text`, `--p-color-text-secondary`, `--p-color-bg-fill-tertiary`),
|
|
180
|
+
the kit's tokens elsewhere.
|
|
181
|
+
|
|
145
182
|
## Page width (0.8.1)
|
|
146
183
|
|
|
147
184
|
Chris, 04/10/2026: the rail on the left, and on the right a content column with the same max width on every
|
|
@@ -924,6 +961,16 @@ gembossUI.initShell(); // AppShell markup written by hand: ☰ drawer,
|
|
|
924
961
|
gembossUI.initPasswordFields(); // the sign-in's reveal button; resetPasswordFields() on sign-out
|
|
925
962
|
```
|
|
926
963
|
|
|
964
|
+
Menus (23/10/2026): a menu's keys are a SECOND script, `menu-plain.js`, loaded after this one (`<script src>`, inlined, or
|
|
965
|
+
`import "@gemboss/ui/menu-plain.js"` after the kit-plain.js import): kit-plain.js has no room for them under its weight
|
|
966
|
+
ceiling. It runs by itself and gives every `details.ui-menu` what the React `Menu` does (README "Menu"): the roles its
|
|
967
|
+
markup leaves out, the keys on a row (↑ ↓ wrapping past disabled rows, Home End, Escape with focus back on ⋯, Tab leaves;
|
|
968
|
+
↓ / ↑ on ⋯ open it), the first row focused on an opening by a click, Enter or Space, one menu open at a time. Without it a
|
|
969
|
+
plain menu works as before (Tab walks its rows). `initMenus()` (opt-in, as before) closes a menu on a chosen row, now with
|
|
970
|
+
focus back on ⋯ BEFORE the row's own handler, and on an outside click, the window's blur and Escape from anywhere; a page
|
|
971
|
+
that never calls it keeps its menus open on a chosen row. Both scripts return at once where there is no DOM, so a Next
|
|
972
|
+
client module that imports them renders on the server (`plain-ssr.test.ts`).
|
|
973
|
+
|
|
927
974
|
In a bundled app (Next, Vite) load it once with a bare `import "@gemboss/ui/kit-plain.js";` in a client module: the
|
|
928
975
|
package lists the file in `sideEffects`, so the bundler keeps it (before 0.5.0 it dropped it and `gembossUI` was
|
|
929
976
|
undefined in production builds). Loading it writes nothing on `<html>` (0.37.1): the `data-gb-menus` flag kit-plain.css
|
|
@@ -1663,9 +1710,23 @@ import { AppShell, BrandLockup, AccountMenu, AccountMenuItem, Icon, RailItem } f
|
|
|
1663
1710
|
outranks an attribute on the `<svg>` itself and paints an outline icon solid.
|
|
1664
1711
|
- `Menu` / `MenuItem` / `MenuSeparator` (0.2.1): the "⋯" action menu. Same `<details>` rule as the account
|
|
1665
1712
|
menu (works as plain markup; with React it closes on an outside click, on Escape with focus back on the
|
|
1666
|
-
trigger, and after a
|
|
1667
|
-
(`href`) or a button; `disabled` on a link drops the href and sets `aria-disabled`; `className` is added
|
|
1713
|
+
trigger, and after a row is chosen). Rows share the account menu's row style. `MenuItem` is a link
|
|
1714
|
+
(`href`, `target`, `rel`) or a button; `disabled` on a link drops the href and sets `aria-disabled`; `className` is added
|
|
1668
1715
|
to the row's class.
|
|
1716
|
+
A menu (23/10/2026, `menu-keys.ts`, shared with `LanguageSwitcher`; `menu-plain.js` after kit-plain.js for plain markup):
|
|
1717
|
+
|
|
1718
|
+
| | |
|
|
1719
|
+
|---|---|
|
|
1720
|
+
| Semantics | the list is `role="menu"` named by `label` (the trigger's name), the trigger `aria-haspopup="menu"`, each row `role="menuitem"` with `tabindex="-1"`: the arrows move between rows, Tab does not |
|
|
1721
|
+
| Opening | a click, Enter or Space on ⋯ puts focus on the first row that can act; ↓ on ⋯ opens on the first, ↑ on the last; a script's `open = true` moves no focus |
|
|
1722
|
+
| Keys | on a ROW (a field or a checkbox in the list keeps its own): ↑ ↓ wrap and skip disabled rows (`disabled`, `aria-disabled="true"`); Home / End; Enter and Space act on the row (Space on a link row too); Escape closes with focus back on ⋯ and is taken (a popup around the menu stays open); Tab / Shift+Tab close it and focus goes on from ⋯ |
|
|
1723
|
+
| Choosing | focus goes back on ⋯ BEFORE the row's handler (a popup it opens returns focus there, not to a hidden row; a handler that moves focus on purpose keeps it), then the menu closes, link rows included. Plain markup: with `initMenus()`, opt-in as before |
|
|
1724
|
+
| One open | opening a menu closes every other open `details.ui-menu`, by a click, a key or a script (a key is not a click outside); a menu that holds the one opening (a menu in a menu, a kit menu in the account menu) stays open |
|
|
1725
|
+
| Listeners | one page-wide set for every mounted `Menu` (`registerMenu`): outside click, Escape, the float on its toggle, one open. A 100-row table has 100 Menus and one set of listeners, not 200 |
|
|
1726
|
+
| In a row that opens on click | `IndexTable` rows with `href` and `tr[data-href]` leave the menu be: opening, moving and choosing never follow the row |
|
|
1727
|
+
| CSS | `menu.css`, imported by `menu.tsx`: placement, the floated list, hint, separator, disabled row, the square ⋯ (`ui-btn--icon`). The surface and the row are shell.css's (which the admin loads too): `.ui-menu__item` a flex row (label, then the hint on the right; the account menu's `.gemboss-account-menu-item` stays a block), 32px under a mouse, 44 under a finger or below 768. So a page with `ui.css` + `shell.css` + `menu.css` and no `app-shell.css` (the admin), in either order, draws what the core draws |
|
|
1728
|
+
|
|
1729
|
+
`menu-browser.test.ts` runs each of these on `Menu` and on plain markup with kit-plain.js, each with a negative control.
|
|
1669
1730
|
|
|
1670
1731
|
## Topbar search (0.6.3)
|
|
1671
1732
|
|
|
@@ -2602,6 +2663,41 @@ thumb and at phone width.
|
|
|
2602
2663
|
]} />
|
|
2603
2664
|
```
|
|
2604
2665
|
|
|
2666
|
+
## Review pieces (unreleased, after 0.49.0): `StagePath`, `StatusMatrix`, `TranslationRow`, `StatusList`
|
|
2667
|
+
|
|
2668
|
+
GemPress's redesign (Chris approved it 23/10/2026) drew four things with its own CSS (`gp-unlock`, `gp-cov`, `gp-str`, `gp-sys`)
|
|
2669
|
+
that are not about GemPress: a path of stages that unlock in order, a map of "how much is live" per row and column, one string
|
|
2670
|
+
under review, and a strip of system health. GemNews and GemCommunity translations review the same way, and every service has a
|
|
2671
|
+
health line to show, so they live here. Each is its own entry, presentational and server-renderable (no state, no effect, no
|
|
2672
|
+
inline style), painted by tokens (the dark palette follows), and usable from plain HTML: link `@gemboss/ui/<name>.css` beside the
|
|
2673
|
+
core and write the classes listed at the top of that file. The tests hold those lists to the components' markup.
|
|
2674
|
+
|
|
2675
|
+
| Piece | What it draws |
|
|
2676
|
+
| --- | --- |
|
|
2677
|
+
| `StagePath` | `stages` (`{ key, name, done, total, unit?, href? }`) as an `<ol>` of cards in a grid (`repeat(auto-fit, minmax(190px, 1fr))`): a number badge (a green ✓ once `done >= total`; a stage with nothing in it is done), the name (a link when `href`, the whole card its target), "3 / 35 pages approved" and a thin `<progress>` bar (hidden from a screen reader: the words say it). `current` (default the first stage not done, `null` for none) has an ink outline and an ink number, and is `aria-current="step"`. `label` names the list (default "Unlock path"); `caption` and `format` make the words the surface's. `stagePathDone` is the rule, pure. |
|
|
2678
|
+
| `StatusMatrix` | the kit table (`ui-table-wrap` + `ui-table`) with `columns` across and `rows` down, each row's name its `th scope="row"` (a link when `href`). A cell is "**live** / total" with a second line for what waits ("5 draft · 2 changed", "3 missing"); its tone is all live (green number) or has drafts (amber number), never colour alone. A `null` or absent cell, or a row whose `total` is 0, is "—". `rowHeading`, `totalHeading` (a column of the rows' totals, e.g. the source language), `caption` (what the numbers mean, under the table), `format`, `labels`. `statusMatrixTone` is the rule, pure. |
|
|
2679
|
+
| `TranslationRow` | one string: a head (`keyName` in code, `state` as a kit badge: `draft` "To review", `approved`, `stale` "Source changed", `missing` "Not translated"; `by`), the `source` beside the `target` (two columns; one under 900px, or when the row is narrower than 600px: it is a size container), and a foot with `checks` (`{ ok, text }`: a green "✓ …" or the problems in amber words) on the left and the surface's `action` on the right. The editable control is the surface's: `target` is a node, or a function given the ids of the key, the source and the checks so the field can say `aria-describedby`. `current` draws an ink border and a 3px ink bar inside the left edge (`aria-current`). The row is `tabIndex={-1}`: a J / K list moves focus to it (pass `id`, or find it by `data-*` the row passes through), and it is never a Tab stop. `as="li"` inside an `<ol>`. |
|
|
2680
|
+
| `StatusList` | `items` (`{ key, label, text, tone, href? }`) as a grid (`repeat(auto-fill, minmax(260px, 1fr))`): a dot, a bold name, one muted sentence (a link when `href`). `ok`, `warning`, `critical` fill the dot with the success, warning and danger tokens; `muted` is an empty ring (off, not applicable); an unknown tone is muted. |
|
|
2681
|
+
| `stage-path.css`, `status-matrix.css`, `translation-row.css`, `status-list.css` | one entry each, loaded by its component; `status-matrix.css` needs the core's table (`ui.css`), and `TranslationRow`'s badge tones are the core's (`kit-plain.css`, in `components.css`) |
|
|
2682
|
+
|
|
2683
|
+
**Quiet when fine, a banner is for when a person must act.** The status list is the line a page ends on: nothing in it asks for
|
|
2684
|
+
anything. Something a person must do today goes in a `ui-banner` above the page, not in an amber dot at the bottom of it.
|
|
2685
|
+
|
|
2686
|
+
```tsx
|
|
2687
|
+
import { StagePath, StatusMatrix, TranslationRow, StatusList } from "@gemboss/ui";
|
|
2688
|
+
|
|
2689
|
+
<StagePath label="Unlock path" stages={[
|
|
2690
|
+
{ key: "frame", name: "Header & footer", done: 2, total: 2, unit: "parts" },
|
|
2691
|
+
{ key: "pages", name: "Pages", done: 3, total: 35, unit: "pages", href: "#/review?stage=pages" },
|
|
2692
|
+
]} />
|
|
2693
|
+
<StatusMatrix rowHeading="Collection" totalHeading="EN" caption="Live / published." columns={[{ key: "vi", label: "VI" }]}
|
|
2694
|
+
rows={[{ key: "pages", label: "Pages", total: 35, cells: { vi: { live: 30, draft: 5, changed: 2 } } }]} />
|
|
2695
|
+
<TranslationRow as="li" id="vi-hero" keyName="hero.title" state="draft" source="Ship in 3 days" sourceLang="en" by="gpt-4o" current={at === 0}
|
|
2696
|
+
target={(ids) => <textarea className="ui-textarea" lang="vi" aria-describedby={`${ids.source} ${ids.checks}`} value={text} onChange={…} />}
|
|
2697
|
+
checks={{ ok: true, text: "Numbers and tags match" }} action={<button className="ui-btn ui-btn--sm" onClick={approve}>Approve</button>} />
|
|
2698
|
+
<StatusList label="System" items={[{ key: "backups", label: "Backups", text: "nightly · last 22 Oct", tone: "ok", href: "#/settings" }]} />
|
|
2699
|
+
```
|
|
2700
|
+
|
|
2605
2701
|
## Collections (0.13.0): tables, lists, grids, boards
|
|
2606
2702
|
|
|
2607
2703
|
Chris settled these on 05/10/2026 after an inventory of 136 hand-made collections on 11 surfaces (86 tables scrolling
|
|
@@ -2643,17 +2739,28 @@ the admin's first load (`kit-plain-bundles.test.ts` checks the shell import carr
|
|
|
2643
2739
|
|
|
2644
2740
|
### The rules, and what checks them
|
|
2645
2741
|
|
|
2742
|
+
- **One header for both tables (23/10/2026).** An index table's header is the data table's: the surface, 13px regular, 12px
|
|
2743
|
+
above and below (45 tall), opaque so the sticky headers hide what scrolls under them; its cells 8px 6px. The index table
|
|
2744
|
+
takes these from the data table's rules (ui.css), so the two cannot drift. Polaris draws an index header as a grey 12px /
|
|
2745
|
+
550 band over 6px cells; the admin's unselectable Polaris IndexTables (all of them on 23/10/2026) take the kit's instead
|
|
2746
|
+
(gemboss-shell.css, #5185).
|
|
2747
|
+
|
|
2646
2748
|
- **The whole row opens the item; the title stays a real link** (Cmd-click, a screen reader). A click on a checkbox, a
|
|
2647
2749
|
button, the ⋯ menu, a select or a second link does only that; so does a click that ends a text selection. A row with
|
|
2648
2750
|
two or more in-place editors is not linked (only its title is). HTML: `tr[data-href]` + kit-plain.js; React:
|
|
2649
2751
|
`IndexTable` rows with `href`; a resource list's title link covers its row (no script).
|
|
2752
|
+
- **The referrer is the page's to set, not the row's.** A row opens with `noopener` and the browser's default policy
|
|
2753
|
+
(`strict-origin-when-cross-origin`): another site learns this site's origin, never the path. A surface whose rows open
|
|
2754
|
+
other sites and must send nothing sets it once for every link: `Referrer-Policy: same-origin` (header) or
|
|
2755
|
+
`<meta name="referrer" content="same-origin">`. Per-link `noreferrer` in the kit was tried (#5176, then #5180's first
|
|
2756
|
+
draft) and dropped: it missed ways in (a Cmd-click straight on the title is the browser's) and every other link type.
|
|
2650
2757
|
- **One visible button per row at most**, the rest in the ⋯ menu (`ui-menu`). **Never shown only on hover.**
|
|
2651
2758
|
- **An open ⋯ menu is never cut by its frame (0.18.0).** It floats in the browser's top layer (`popover="manual"`,
|
|
2652
2759
|
`position: fixed` against its button): below the button, above it when the room below is short and the room above is
|
|
2653
2760
|
larger, 8px from the viewport's edges, scrolling inside when taller than the room. It closes when a scroll moves its
|
|
2654
2761
|
button (the page, or a frame around the menu) or the window resizes; a scroll inside the list or elsewhere (a log
|
|
2655
|
-
following its output) does not count.
|
|
2656
|
-
menu is not a click on the row" are unchanged: the list is still the details' child. React `Menu` does it itself;
|
|
2762
|
+
following its output) does not count. The keys (README "Menu", 23/10/2026: arrows, Escape with focus back on ⋯) and "a
|
|
2763
|
+
click in the menu is not a click on the row" are unchanged: the list is still the details' child. React `Menu` does it itself;
|
|
2657
2764
|
plain HTML gets it from kit-plain.js (`initMenuLayer()`, runs by itself). No popover API (Safari < 17) or no script:
|
|
2658
2765
|
the absolute list, as before. GemFactory Members (#197) is the case: at 820 / 900 / 1280 the last rows' menus were
|
|
2659
2766
|
half hidden inside the table's scrolling frame.
|
|
@@ -2667,7 +2774,8 @@ the admin's first load (`kit-plain-bundles.test.ts` checks the shell import carr
|
|
|
2667
2774
|
activity's detail wrap instead of ending in "…", because the cut text's `title` is mouse-only. A side rail of short
|
|
2668
2775
|
labels keeps one line. `CompactItem.body` (activity only) is the event's own text under them (`.ui-list__content`,
|
|
2669
2776
|
body size, never cut; paragraphs as `<p>`); `labelText` puts the full label in `title`.
|
|
2670
|
-
- **Below 768 of its container an index table becomes a list** (title line
|
|
2777
|
+
- **Below 768 of its container an index table becomes a list** (title line: a linked title is cut, an unlinked one and free
|
|
2778
|
+
text under it wrap, a long token breaks; the other cells on one caption line,
|
|
2671
2779
|
`data-label` prefixes, `data-condensed="hide"` drops; checkbox and actions kept; rows ≥ 44); sort moves to the chips
|
|
2672
2780
|
of `ui-index__sortbar`. **A data table never folds:** it scrolls in its frame with `--sticky-first`.
|
|
2673
2781
|
- **A data table reads from the top of its rows (0.16.0)**, as Polaris DataTable (`verticalAlign` "top"): one-line
|
|
@@ -2678,7 +2786,8 @@ the admin's first load (`kit-plain-bundles.test.ts` checks the shell import carr
|
|
|
2678
2786
|
`minWidth` (DataTable) is the floor of a column whose cells wrap, and replaces the row header's (`0` for a row
|
|
2679
2787
|
header that is always short: a date, an id). A link alone on its
|
|
2680
2788
|
line is 24px in the row header and in a list inside a cell (`li > a`), as `td > a` already was.
|
|
2681
|
-
- **A data table in a card is `flush
|
|
2789
|
+
- **A data table or an index table in a card is `flush`** (DataTable 0.34.0; IndexTable unreleased, its bulk bar
|
|
2790
|
+
then lies on the card's top edge), as a resource list in a card: the card is the frame, so the wrap drops its
|
|
2682
2791
|
own border, radius and shadow (two frames otherwise). The last row has no bottom rule, row header included. The card
|
|
2683
2792
|
clips its corners (`overflow: hidden`, as GemWatcher's Panel): the last row's hover, the totals and the footer are square.
|
|
2684
2793
|
- **A media card is 16:10, 4:3 or 1:1 (0.18.0)** (`MediaCard ratio`, `data-ratio="4/3" | "1/1"`): 1:1 for a square
|
|
@@ -2766,6 +2875,11 @@ gap. Under 576px only Previous · the current page · Next stay and the range wr
|
|
|
2766
2875
|
took three to four rows at 320). The old form `<span aria-current="page">2 / 9</span>` keeps its muted look. The surface computes which numbers to
|
|
2767
2876
|
show (first, last, current ± 1, a gap only where it hides two or more pages: GemFactory `pageWindow`).
|
|
2768
2877
|
|
|
2878
|
+
React: `<Pager page pageCount range href={(n) => \`?p=${n}\`} linkAs={Link} />` draws that markup (`pageWindow` is exported
|
|
2879
|
+
too). A list that pages in STATE (its search and filters not in the URL, as the admin's Products) passes `onPage` instead of
|
|
2880
|
+
`href` and gets buttons with the same look (pager-extras.css), Previous / Next marked `data-rel`; `busy` while a page is on its
|
|
2881
|
+
way. The rules are `pager.css` (in the core, moved out of kit-plain.css 24/10/2026 with Chris's OK) and `pager-extras.css`.
|
|
2882
|
+
|
|
2769
2883
|
`ui-more`: one centred row. Idle: a `ui-btn` ("Show 24 more"). Loading: `<span class="ui-spinner" aria-hidden="true">` +
|
|
2770
2884
|
`<span role="status">Loading…</span>`. Error: `<span role="alert">Couldn't load.</span>` + "Try again". End: a line. As an
|
|
2771
2885
|
IntersectionObserver sentinel it has height from its padding; zero the padding and it has none.
|
|
@@ -2817,10 +2931,12 @@ something the base cannot express, add a token or a prop here, and every surface
|
|
|
2817
2931
|
| Path | What |
|
|
2818
2932
|
|---|---|
|
|
2819
2933
|
| `parts/core.css`, `parts/components.css`, `parts/shell.css` | 0.23.0: the core, built by `packages/ui/scripts/build-css.mjs` from the same sections as `gemboss-ui.css` (see "Load only what you use"). |
|
|
2820
|
-
| `gemboss-ui.css` | built at stage time and shipped minified (0.19.0); since 0.23.0 the COMPATIBILITY BUNDLE, frozen: `apps/admin/scripts/export-ui-css.mjs` (tokens + kit + field + `packages/ui/src/shell.css`, with Polaris `--p-*` resolved to their fallbacks) + `packages/ui/src/app-shell.css` + `auth.css` + `assistant.css` + `kit-plain.css` + `chat.css` + `chat-composer.css` (`packages/ui/scripts/build-css.mjs`) |
|
|
2934
|
+
| `gemboss-ui.css` | built at stage time and shipped minified (0.19.0); since 0.23.0 the COMPATIBILITY BUNDLE, frozen: `apps/admin/scripts/export-ui-css.mjs` (tokens + kit + field + `packages/ui/src/shell.css`, with Polaris `--p-*` resolved to their fallbacks) + `packages/ui/src/app-shell.css` + `auth.css` + `assistant.css` + `menu.css` + `kit-plain.css` + `chat.css` + `chat-composer.css` (`packages/ui/scripts/build-css.mjs`) |
|
|
2821
2935
|
| `packages/ui/src/shell.css` | the SHARED SHELL (rail, topbar, main, mobile drawer, the assistant's thread rail, popover surface, account chip). Exported as `@gemboss/ui/shell-source.css` since 0.23.0 (`@gemboss/ui/shell.css` is the built part). The admin's `gemboss-shell.css` @imports it on line 2; the export refuses a shared-shell rule in the admin's sheet or an admin-only rule here (one owner). |
|
|
2822
|
-
| `packages/ui/src/
|
|
2823
|
-
| `packages/ui/src/
|
|
2936
|
+
| `packages/ui/src/menu-plain.js` | 23/10/2026: `gembossUI.initMenuKeys()`, a menu's roles and keys and one menu open at a time for plain HTML (`@gemboss/ui/menu-plain.js`), the same as `menu-keys.ts`. After `kit-plain.js`; runs by itself. See "Plain-HTML behaviour". |
|
|
2937
|
+
| `packages/ui/src/menu.css` | 23/10/2026: the ⋯ menu and its icon button, moved out of `kit-plain.css` (same rules, same order in the core). Imported by `Menu`; see "Menu". |
|
|
2938
|
+
| `packages/ui/src/kit-plain.css` | primitives for surfaces that render plain HTML, not React (0.2.1): key/value list (the menu and the icon button are `menu.css` since 23/10/2026); 0.3.0 adds `ui-link` / `ui-prose`, the media card (`ui-card--media`, `__media`, `__body`, `__foot`) and the popup's `ui-modal__body` / `__foot`. Shipped inside `gemboss-ui.css`; the admin does not import it (its first-load CSS budget has ~0.1 KB headroom).; (0.4.0) report pages: delta, large KPI grid, bar list, column chart, legend, funnel, card grid, section head. |
|
|
2939
|
+
| `packages/ui/src/kit-plain.js` | 0.3.0: the behaviour those surfaces each wrote by hand: `gembossUI.openModal` (Escape, backdrop, Tab trap, stacking, ask-before-close, focus back), `toast` (text, error tone), `initMenus` (outside click, item with focus back on ⋯ first, blur, Escape closes the menu before the popup); 0.5.0: `confirm`, `initShell`, `initPasswordFields` / `resetPasswordFields`. A classic script: inline it or `<script src>`; see "Plain-HTML behaviour" above. |
|
|
2824
2940
|
| `packages/ui/src/collections.css` | 0.13.0: tables, lists, grids, boards and their states (see "Collections"). A separate entry, NOT inside `gemboss-ui.css`; the admin does not load it. |
|
|
2825
2941
|
| `packages/ui/src/editor/` | 0.14.0, `@gemboss/ui/editor`: `EditorFrame`, `EditorPaneTabs`, `EditorTopbar`, `EditorBack`, `EditorChatToggle`, `EditorSaveState`, `DeviceToggle`. See "Editor frame". 0.17.0: `PanelTabs`, `PanelHeader`, `PanelAction`, `InspectorGroup`, `Field`, `Seg`, `ColorControl`, `RangeControl`, `EdgeBox`, `ImageControl`, `ChipButton`, `swatchState` (`./editor/swatch` too); 0.20.1 `cssUrl`. See "Settings panel". 0.22.0: `LayerList`, `SortableLayerList`, `LayerTree`, `EditorDrawer`. See "Layers and drawer". |
|
|
2826
2942
|
| `packages/ui/src/editor-plain.js` | 0.14.0: `gembossUI.initEditorFrame()`, the frame's behaviour for plain HTML; 0.17.0: `gembossUI.initEditorPanel()`, the settings panel's. Load after `kit-plain.js`. |
|
package/catalog/editor.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
.cat-code summary{padding:10px 16px;font-size:13px;cursor:pointer;color:var(--gemboss-text-muted);border-top:1px solid var(--gemboss-divider);min-height:24px}
|
|
9
9
|
.cat-code pre,.cat-api{margin:0;padding:12px 16px;overflow:auto;background:#0f1012;color:#e8e8e6;font-size:12px;line-height:1.5;border-radius:0}
|
|
10
10
|
.cat-api{border-radius:12px;margin:0 0 16px}</style></head><body><div class="cat">
|
|
11
|
-
<h1>@gemboss/ui 0.
|
|
11
|
+
<h1>@gemboss/ui 0.50.0 · Editor</h1>
|
|
12
12
|
<p>Khung của mọi editor: <code>EditorFrame</code> (cột trái · khe kéo · preview · cột phải dock trên mép phải preview) và <code>EditorTopbar</code> (← · tiêu đề · cụm giữa · cụm phải · dòng trạng thái). Là đúng khung của theme / page / section editor trong admin. Gói chỉ vẽ KHUNG; chat, Configure, canvas, page picker, menu, Publish là của editor, đi vào bằng slot. Trang HTML thuần nạp <code>editor.css</code> + <code>editor-plain.js</code> rồi gọi <code>gembossUI.initEditorFrame()</code>: kéo khe (chuột, chạm, phím mũi tên / Home / End), đổi thiết bị, mở ⋯ và Publish ▾, ẩn cột trái, Chat | Preview trên điện thoại. <a href="index.html">← Catalog</a></p>
|
|
13
13
|
<h2>React</h2><pre class="cat-api"><code>import "@gemboss/ui/editor.css";
|
|
14
14
|
import { EditorFrame, EditorTopbar, EditorBack, EditorChatToggle, EditorPaneTabs, EditorSaveState, DeviceToggle } from "@gemboss/ui/editor";
|