@gemboss/ui 0.12.0 → 0.21.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 CHANGED
@@ -2,6 +2,399 @@
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
+ ## 0.21.0 (07/10/2026)
6
+
7
+ **Reactions: one emoji set and one chip for the fleet.** GemCare's team chat reactions, moved into the kit so GemFeedback
8
+ (requests, comments) and GemCare draw and accept the same thing (Chris 06/10/2026). README "Reactions".
9
+
10
+ Added
11
+ - `@gemboss/ui/reactions-data`: `REACTION_GROUPS` (137 emoji, GemCare's set unchanged), `REACTION_EMOJI`,
12
+ `isReactionEmoji`, `searchReactions`, `reactionLabel`, `toggleReaction`, `QUICK_REACTIONS`, type `Reaction`.
13
+ - `Reactions` and `ReactionPicker` (client components), `reactions.css` (its own entry `./reactions.css`, not inside `gemboss-ui.css`: the shared sheet stays as it was).
14
+ - Icon `reaction` (a face with a plus).
15
+ - Catalog section "Reactions".
16
+ - `packages/ui/weight-budget.json`: a ceiling for `packages/ui/src/reactions.css`; `gemboss-ui.css` unchanged.
17
+
18
+ ## 0.20.1 (07/10/2026)
19
+
20
+ **Two faults the admin's settings panel had before step 3, fixed.** Nothing else changes on screen. (The third listed
21
+ under 0.17.0, Show / Hide and On / Off rows without `aria-pressed`, went with 0.20.0, #3846.)
22
+
23
+ Fixed
24
+ - The inspector's groups that arrive folded (the shared frame's: Motion, …) opened on the SECOND click: the admin's
25
+ toggle flipped the stored value, and a flip of "unset" is "closed", where the group already was. It now stores what
26
+ the click asks for (`InspectorGroup`'s `onOpenChange(next)`). Reproduced on a production build first (theme editor,
27
+ Hero banner, Motion: `aria-expanded` false after one click, true after two), the same steps open it in one click after.
28
+ - The image thumbnail's `url()` is quoted (`cssUrl`, new export, plain TypeScript, also `./editor/css-url`), in
29
+ `ImageControl` and in the admin's library grid: unquoted, a URL holding ")" or a space ended the token, the
30
+ declaration was dropped and the thumbnail stayed empty. `"` and `\` get a backslash and a control character a CSS hex
31
+ escape; `initEditorPanel()` used JSON.stringify, whose `\n` CSS reads as the letter n, and now writes the same as
32
+ `cssUrl` (`gembossUI.cssUrl`).
33
+
34
+ Known, kept as it was
35
+ - The inspector remembers open / closed per group TITLE, so two groups with the same title (the Design tab's
36
+ Typography and an element frame's Typography) share it.
37
+
38
+ ## 0.20.0 (07/10/2026)
39
+
40
+ **One switch, one component.** The admin drew "pick one of these" nine ways: a dark 44px pill (the Studio's
41
+ Funnel · Canvas · Page and the funnel lens copied from it), a dark joined group (the canvas's device, the campaign
42
+ editor's mode), a grey track with a raised white tab (the inspector's `.gemboss-ei-seg`, which 0.17.0 moved here as
43
+ `Seg`), a brand-fill joined group (post-purchase, twice), separate tinted boxes (`DeviceToggle`), and this package's own
44
+ `.ui-seg`, which no editor used. Two of them sat side by side in one bar (Chris 06/10: "switch canvas mode page mode đó
45
+ cũng dùng khác nhau là không được"). Now there is one look, `.ui-seg`, and one component.
46
+
47
+ Added
48
+ - `Segmented` / `IconSegmented` (`@gemboss/ui/segmented`, also from `@gemboss/ui`): moved from the admin
49
+ (`components/ui/Segmented.tsx` re-exports it). Options take a ReactNode label, `title`, `ariaLabel`, `disabled`; the group
50
+ takes `fill`, a hook `className` and `style`; each button carries `data-value`. Its own plain module: a surface that draws
51
+ a switch does not load the shell.
52
+ - `.ui-seg--fill` (the row's width, segments equal), `.ui-seg-n` (a count inside a segment, read by eye; the segment's
53
+ `aria-label` says the sentence), a disabled segment (.45, `not-allowed`).
54
+
55
+ Changed
56
+ - `Seg` (editor/controls) is `Segmented` stretched to its row. `gemboss-ei-seg` stays on the group as the hook
57
+ `editor-plain.js` finds; the pressed button is `.active` (the driver toggles `.active` and, for markup written before,
58
+ `.on`).
59
+ - `DeviceToggle` is a `.ui-seg` (28px under a mouse, 44px under a finger). `.gemboss-pv-devices` / `.gemboss-pv-device`
60
+ stay as hooks; inside the switch the segment's size wins over the icon-button sizes those classes carry elsewhere
61
+ (undo, redo, layers, ⋯). Each device button has an `aria-label`.
62
+ - `.is-text` segments lay their children in a row: a label and its count, or two icons, stacked in the grid.
63
+ - A disabled segment keeps the picked one's fill (a switch that is saving still says which option is on; review #3846).
64
+ - `.ui-seg--fill` outranks kit-plain.css's `.is-text { flex: none }` (gemboss-ui.css carries it after ui.css): at equal
65
+ weight the segments kept their own widths outside the admin (review #3846).
66
+
67
+ Fixed
68
+ - `.ui-seg svg` filled a stroke icon solid (`fill: currentColor` over `fill="none"`): the monitor and phone glyphs drew
69
+ as blocks. A `fill="none"` svg keeps its outline.
70
+
71
+ Removed
72
+ - The `.gemboss-ei-seg` look from `editor.css`.
73
+ ## 0.19.0 (06/10/2026)
74
+
75
+ **The published CSS and plain scripts are minified, and each file has a wire-weight ceiling.** README "Minified on
76
+ publish, weighed per file".
77
+
78
+ Changed
79
+ - The tarball carries every stylesheet and `kit-plain.js` / `editor-plain.js` minified (esbuild, comments out; the
80
+ sources keep them). `gemboss-ui.css` keeps its generated header. gzip, as measured on this change:
81
+ `gemboss-ui.css` 78.1 → 21.9 KB, `kit-plain.js` 17.8 → 8.5 KB, `editor.css` 25.8 → 7.9 KB, `collections.css`
82
+ 12.4 → 4.6 KB, `editor-plain.js` 7.5 → 3.6 KB. A surface that serves the files from `node_modules` as they are
83
+ (GemFactory, taskdesk, gemcare, gempress) sends the smaller file on its next bump with no change of its own; Next and
84
+ Vite surfaces minified already and see no difference.
85
+ - Release: `npm run stage` then `npm publish .pack --auth-type=web`. `npm pack` / `npm publish` straight in
86
+ `packages/ui` are refused (they would ship the unminified sources).
87
+ - Computed style is unchanged: every element and `::before` / `::after` of `catalog/index.html` and
88
+ `catalog/editor.html`, all frames, at 1280 and 390, compared source vs minified copy. The only text that differs is in
89
+ custom properties (`0.18` → `.18`, `'Inter'` → `"Inter"`), whose computed value is their token text; every property
90
+ that reads them computes the same.
91
+
92
+ Checks
93
+ - `packages/ui/scripts/weight-gate.mjs` (CI: `--ci`) replaces the export test's raw 90 KB cap: gzip of each minified file against
94
+ `weight-budget.json` (measured + 512 B); red over the ceiling, more than 1 KB under it, with comments left, or without
95
+ a ceiling. Negative controls run with it in CI. `fleet.mjs` compares main and npm minified on both sides.
96
+ - `esbuild` is a pinned root devDependency (0.21.5, the version already installed): the minifier decides the bytes.
97
+
98
+ ## 0.18.0 (06/10/2026)
99
+
100
+ **Five collections gaps surface migrations found** (GemFactory Members #197 and its board, content-radar ideas,
101
+ GemCommunity admin Content #270). README "Collections" has the rules.
102
+
103
+ Fixed
104
+ - A sort button (IndexTable / DataTable header, `button|a.ui-table__sort`) is 44 WIDE under a finger and at phone width,
105
+ not only 44 tall: "ID" and "Views" were ~30 wide on an iPad in landscape (GemCommunity admin Content #270). Under a
106
+ mouse above 768 nothing changes. The sort chips below 768 already had it (`ui-chip`).
107
+ - An open ⋯ menu (`Menu`, `details.ui-menu`) is no longer cut by a scrolling frame. Its list floats in the top layer
108
+ (`popover="manual"`, `position: fixed` against the button): below it, or above when the room below is short and the
109
+ room above is larger; 8px from the viewport's edges; scrolls inside when taller than the room; closes when a scroll
110
+ moves its button (the page, or a frame around it; not a scroll inside the list or of something elsewhere) or the
111
+ window resizes. One page-wide pair of listeners, however many menus; a menu already open when the script loads is
112
+ floated, not hidden. Tab order, Escape and row clicks are unchanged (the list is still the details' child).
113
+ React `Menu` (new `menu-layer.ts`, floats in its own summary click, sets nothing page-wide) and kit-plain.js
114
+ (`initMenuLayer()`, runs by itself) carry the same code. No popover
115
+ API or no script: the 0.17 absolute list. GemFactory Members at 820 / 900 / 1280 showed half of the last rows' menus
116
+ and asked for a scroll inside the table.
117
+
118
+ Added
119
+ - `MediaCard ratio="1/1"` (`data-ratio="1/1"`) beside 16:10 and 4:3, for square pictures (content-radar's moodboard).
120
+ No free ratio; an unknown value draws 16:10.
121
+ - Board server paging: `BoardColumn.hasMore` (default: true when `moreHref` is set), `moreHref`, `loading`,
122
+ `Board onLoadMore(columnId)`, `linkAs`, `moreLinkProps`, `fetchMore` (plain HTML only), `boardShown(page)` (12, then 24
123
+ a page; a missing or non-numeric page is page 1). A server-paged column renders the cards it was given and
124
+ ONE `ui-more` "Show N more" (`a[data-gb-more]`), not the nested `<details>` that need every card in the HTML.
125
+ With `fetchMore` (`a[data-gb-more]`) kit-plain.js fetches the link and appends the cards not yet on the board (by
126
+ `data-id`), takes the next link, the count and the URL, announces and focuses the first new card; "Couldn't load." +
127
+ Try again on failure. A paged column whose loaded cards all moved away keeps its list and its link.
128
+ - Board moves, optional: `Board onMove(cardId, toColumn, toIndex)`; plain HTML `.ui-board[data-gb-move]` with
129
+ `li[data-id][tabindex="0"][draggable="true"]` and the `gb:move` event (cancelable; not cancelled, the `li` moves).
130
+ Drag, or Space / arrows / Space / Escape on a focused card, every step said in `[data-gb-live]`; the held card is
131
+ `data-gb-lifted="key|drag"` (no `aria-pressed`: a list item does not take it). `board-move.ts`
132
+ (React, through a callback ref: `collections.tsx` stays a plain module) and kit-plain.js carry the same code; a page
133
+ with both drives a React board once.
134
+ - `BoardColumn.items` (`{ id, card, name? }`): cards with ids, needed by moves and server paging. `cards` still works.
135
+ `Board id` is now the root's DOM id too (kit-plain.js reads a fetched page's column from the board with the same id).
136
+ - Review #3845 (before merge): a drag that left the board and came back to the same slot was cancelled; kit-plain.js
137
+ would have fetched into a board React owns; a menu closed on any scroll on the page. Each has a test that fails on the
138
+ first commit of this PR.
139
+ - collections-gate counts boards (`seen.boards`): a page that is only a board (content-radar ideas) read "NOT MEASURED".
140
+ - Catalog: a movable board (live with kit-plain.js), a server-paged board, a 1:1 card grid, a table to open the last
141
+ row's ⋯ in.
142
+
143
+ Checks
144
+ - `collections-board.test.ts` (new, in `test:ui-browser`): the last row's menu whole in the viewport at 820 / 900 /
145
+ 1280 / 390, plain HTML and React, above its button near the bottom of the screen, keyboard (Enter, Tab, Escape back
146
+ to ⋯), closes on page scroll and resize but not on its own scroll, an item click does not open the row; negative:
147
+ without the popover API the same menus are cut (plain and React). Ratios 1.6 / 1.33 / 1 / fallback 1.6; negative:
148
+ without the 1/1 rule the square card is 16:10. A 2,000-card column server-rendered = 12 `li` + one link (the
149
+ client-paged board: 2,000 `li`); kit-plain fetch in place to the last page (36 → 60 → 80, no reload, URL, count,
150
+ focus, live text), failure + Try again, no-script link navigates, React `onLoadMore`. Moves: keyboard and drag on
151
+ plain HTML (`gb:move` detail, DOM, counts, focus), Escape and a drop in place report nothing, a cancelled `gb:move`
152
+ leaves the DOM to the page, React `onMove` (keyboard + drag), React + kit-plain on one page fire once; negative: a
153
+ board without `onMove` has no tab stops or drag and moves nothing. collections-gate clean on all three.
154
+
155
+ ## 0.17.0 (06/10/2026)
156
+
157
+ **The settings panel and its controls as components (editor kit, step 3 of 5).** Nothing changes on screen or in
158
+ behaviour in the admin: its Configure column, element inspector and schema form now render these, measured equal on a
159
+ production build (below). README "Settings panel (0.17.0)".
160
+
161
+ Added
162
+ - `@gemboss/ui/editor`, two more modules behind the entry (`panel`, `controls`, both `"use client"`):
163
+ `PanelTabs` (`column`: Configure · Chat; `inspector`: Content · Design · …; roving ←/→ by default, `roving={false}`
164
+ for a row of plain Tab stops; `controls` per tab for `aria-controls`), `PanelHeader` + `PanelAction`, `InspectorGroup` (collapsible, controlled or not;
165
+ `collapsible={false}` is the schema form's static group), `Field` (label, `labelValue`, hint), `Seg`, `ColorControl`,
166
+ `RangeControl`, `EdgeBox`, `ImageControl` + `ChipButton`, and `swatchState` (moved from the admin, which re-exports it;
167
+ also `@gemboss/ui/editor/swatch`, plain TypeScript).
168
+ - `editor-plain.js`: `gembossUI.initEditorPanel()`: tabs (+ arrows on a roving row, `aria-controls` panels), group
169
+ fold, Seg, colour (swatch and box kept in step, swatch off for a non-hex), range (label value + `data-suffix`), edges
170
+ (all four on blur), image (thumbnail, `data-gb-image-clear`); an event for each.
171
+ - editor.css: `.gemboss-ei-gh-note`, the group header's aside (the declarations the inspector had inline).
172
+ - `catalog/editor.html`: the panel with every control, rendered from the components and running, at 1280 and 390.
173
+
174
+ Measured in the admin (two production builds, before = main 23644fabb and after, pinned SENTRY_RELEASE / GIT_COMMIT_SHA /
175
+ GEMBOSS_BUILD_STAMP, served by `next start` on the same local data; every write the harness could cause was held)
176
+ - DOM and computed style (+ ::before / ::after, box, control values) of every element, and :hover / :focus-visible /
177
+ :active forced on every control of the panel, for 19 states (theme editor inspector: Design, groups folded, Content,
178
+ Advanced, ←/→ on the tabs, typing in an edge box, mobile breakpoint, busy with every control disabled; Global styles:
179
+ default, slider dragged, colour typed, swatch clicked; page editor Configure, edge committed with Enter; section editor:
180
+ hero, image slot, slider, slider moved with the keys, image URL committed) at 320, 390, 768 (touch), 1280, 1366,
181
+ 1920: 114 captures, 41,506 element renders, 18,018 forced-state checks. 0 differences in computed style or box. In
182
+ markup, the intended ones: the inspector's tabs gain `type="button"` (no form around them), the group note's inline
183
+ style became `.gemboss-ei-gh-note` (computed equal), and (after review) every tab and Seg button carries
184
+ `data-value`, which no stylesheet selects; re-captured at 1280 and 390 after it, the same. Two more that are not the change: the brand mark's
185
+ `url()` resolves to each server's own port, and one forced `:active` in Global styles differs between two captures of
186
+ the SAME build too. A negative control (one declaration laid over the after build) is reported in 10 of 19 states.
187
+ - Server-rendered markup of the admin's controls before and after with the same props (FieldControl of every kind,
188
+ FieldRenderer schemas, the inspector's Seg, EdgeBox, ImageField, ElementPropField; disabled, values, hints): 330
189
+ renders: 282 byte-identical, 20 identical but for attribute order (the element edge box), 28 identical but for the
190
+ added `data-value`, 0 different. A changed aria-label laid over one render is caught.
191
+ - First load (brotli): the shell 239,475 → 239,439 B (−36); theme editor +659, page editor +875, section editor +991,
192
+ campaign editor +653, product studio +644; plugin editor −32. Budget gate: shell 233.8 / 237 KB.
193
+
194
+ Known, kept as it was (this step moves markup, not behaviour)
195
+ - The inspector's groups that arrive folded (the shared frame's) stay folded on the first click and open on the second:
196
+ the toggle flips the stored value from "unset" to "closed".
197
+ - The schema form's Show / Hide rows and the inspector's Visibility rows have no `aria-pressed`, so they are not `Seg`.
198
+ - The image thumbnail's `url(…)` is unquoted in the React control, as in the admin (a URL with ")" or a space shows no
199
+ thumbnail); `initEditorPanel()` quotes it.
200
+ - `InspectorGroup collapsible={false}` (schema form) and the inspector's group differ a little (letter-spacing .08em vs
201
+ .1em, divider above vs below), as in the admin.
202
+
203
+ ## 0.16.0 (06/10/2026)
204
+ **Four collections fixes a surface migration asked for**, each patched by hand on a surface until now (partner-radar
205
+ `SortNav` + `cells.js`, search-radar `SortNav`, talent-radar's bar apart from its list). README "Collections" has the
206
+ rules.
207
+
208
+ Fixed
209
+ - Sort links are `linkAs` (IndexTable header and the sort chips below 768; DataTable header), the element the row
210
+ titles already used. 0.13.0 drew them as plain `<a>`, so on a Next surface every sort reloaded the whole page (frame,
211
+ open filters, scroll): partner-radar /apps and search-radar /keywords wrapped the table in a `SortNav` that caught the
212
+ click. New `sortLinkProps` (plain data) go to the sort links only: `{ scroll: false }` for a Next `Link` keeps the
213
+ scroll position, while opening a row still scrolls.
214
+ - Data table cells sit at the TOP of their row (`vertical-align: top`), as Polaris DataTable (its `verticalAlign`
215
+ defaults to "top", Polaris 13.9.5 Cell.js). A 24px cell link is pulled up by `calc((1lh - 24px) / 2)` so its text is on
216
+ the line of the cells beside it (top-aligned, the 24px box put it 2-4px low). The index table keeps `middle`.
217
+ In collections.css, not ui.css: the admin loads ui.css on every screen and draws no ui-table.
218
+ - The row header (`th.ui-table__rowhead`) wraps (Polaris `firstColumn`), with Polaris's first-column width as a floor
219
+ (9.0625rem) so a narrow frame scrolls instead of squeezing it to a word a line; `truncate` / `ui-clamp-2` cut it when
220
+ it must stay short. A link alone on its line is 24px in the row header and in a list inside a cell (`li > a`), as
221
+ `td > a` was. 0.13.0 kept ui.css's `nowrap` and matched `td > a` only: partner-radar wrapped 17 cells on five pages in a
222
+ `CELL()` box and set 24px by hand on 12 links (`cells.js`).
223
+ - `ResourceItem selected` no longer puts `aria-selected` on the `li` (a listitem does not take it; jsx-a11y
224
+ `role-supports-aria-props`): with a checkbox the box says it, without one (an item open beside the list) it is
225
+ `aria-current="true"`. kit-plain.js sets aria-selected on table rows only.
226
+ - Escape clears a selection unless a menu or dialog in the table / list is OPEN and shown: a closed `<dialog>` or a
227
+ hidden `role=menu` kept in a row used to swallow every Escape (IndexTable, kit-plain.js, and the new list; review
228
+ #3831).
229
+
230
+ Added
231
+ - `SelectableResourceList` (client module `resource-list-select.tsx`): a resource list with selection and bulk actions,
232
+ the IndexTable's rules: a checkbox first in each item's media slot, Shift+click range (anchor by id), Escape clears
233
+ (an open ⋯ menu or dialog takes it first), the actions get the ids in the list's order, the whole row still opens
234
+ the item. The bar is the list's header from the start ("0 selected", actions disabled): a list has no header to cover,
235
+ and a bar that appears on the first check pushed talent-radar /pool's rows 74px (1280) / 194px (390) under the finger.
236
+ `ResourceList` stays plain (server). `ResourceItem.check` is the slot it fills; an item's `id` is the selection key
237
+ only, never the `li`'s DOM id. Loading keeps each row's box (`CollectionSkeleton check` for a list), so the media
238
+ does not move when rows arrive. The selection rules and the bar are one module (`selection.tsx`) that IndexTable now
239
+ uses too.
240
+ - `CollectionColumn.minWidth` (DataTable): the column's floor, for cells that wrap (Polaris `firstColumnMinWidth`, any
241
+ column); replaces the row header's floor, and `0` removes it for a row header that is always short (a date, an id).
242
+ - Plain HTML: `div.ui-resource > div.ui-bulkbar + ul.ui-list`, `label.ui-list__check > input[data-gb-select]`;
243
+ kit-plain.js drives a `.ui-resource` like a `.ui-index` (action buttons enabled from 1 instead of the bar shown;
244
+ `gb:selection` fires after, for an action with a rule of its own).
245
+ - collections-gate: `select-without-bulk` also reports a resource list with selection boxes (in `__media`, or
246
+ `data-gb-select`) and no bar; a checklist's done box or a switch is not a selection box.
247
+ - Catalog: a multi-line job table at the catalog width and at 390, a selectable list (two selected) and at 390.
248
+
249
+ Measured (gemlabs partner-radar on the local proof DB, `next dev`, published 0.13.0 vs the 0.16.0 tarball with
250
+ `SortNav` and `cells.js` deleted, `CELL(n)` floors moved to `minWidth: n`, `style={LINK24}` removed from 12 links; same
251
+ rows; 320 / 390 / 768 / 1280)
252
+ | Page | rows whose cells' first lines differ > 1.5px | links < 24 outside a sentence | gate | page overflow |
253
+ |---|---|---|---|---|
254
+ | /ops (24 rows) | 24 → 3 (the 3: a status pill, its text 4px into its own padding) | 0 → 0 | 0 → 0 | none |
255
+ | /sources (193) | 183 → 3 (same pill) at 320-768, 175 → 3 at 1280 | 0 → 0 | 0 → 0 | none |
256
+ | /feed (27) | 26 → 0 | 0 → 0 | 0 → 0 | none |
257
+ | /competitors (93) | 80 → 0 | 0 → 0 | 0 → 0 | none |
258
+ | /search?q=shop (60) | 60 / 53 → 0 | 0 → 0 | 0 → 0 | none |
259
+
260
+ Total row height: /search 7,318 → 5,448 at 320, /sources 18,426 → 15,440 at 320-768; /feed +2% and /competitors +4% at
261
+ 320 / 390 (a row header now wraps at its 145 floor instead of running one line wide). No table wider than its frame at
262
+ 1280. Density gate: same count of differences or fewer on every page (competitors 48 → 36 at 1280).
263
+ Sort: /apps (partner-radar) and /keywords (search-radar) without `SortNav`, a header link at 1280 and a chip at 390:
264
+ 0 document requests, the page's state kept, scroll 120 → 120. Negative: published 0.13.0 without `SortNav`: 1 document
265
+ request, state lost, scroll 120 → 0.
266
+
267
+ Checks
268
+ - `collections.test.ts`: sort links go through `linkAs` with `sortLinkProps` (header, chip, DataTable; the row title
269
+ keeps linkAs without them), negative: no `linkAs` = the browser navigates; first lines of a three-line row header, a
270
+ plain cell, a link cell and a number on one line, negatives with the top rule or the link rule cut out; the row header
271
+ wraps in a 560 frame, keeps its 145 floor at 390 (the frame scrolls), `minWidth` replaces it, a `truncate` column stays
272
+ one line, negative: the 0.15.0 nowrap overflows the frame and its link is a bare inline box; SelectableResourceList:
273
+ bar from the start, rows do not move on the first check, Shift range, ids to the action, select-all + Escape, a row
274
+ click opens the item through linkAs, Escape with a menu open keeps the selection, boxes 24 / 44 aligned with the
275
+ bar's, rows stay 64; kit-plain on `.ui-resource`; the gate on a list with and without its bar.
276
+ - Admin first load: the names the admin imports from `@gemboss/ui` bundle to the same bytes (7,967 minified, 2,685
277
+ gzip) before and after; ui.css, gemboss-ui.css and the admin's other stylesheets unchanged (this release touches
278
+ collections.css, the collections modules and kit-plain.js, none of which the admin loads).
279
+
280
+ ## 0.15.0 (06/10/2026)
281
+
282
+ **Resource list in a narrow frame, and a body + wrap for the compact list.** Two package bugs a surface migration
283
+ (partner-radar, gemlabs #895) found in the 0.13.0 collections. README "Collections" has the rules.
284
+
285
+ Fixed
286
+ - Resource list when the LIST's frame is 400 or less. 0.13.0 wrapped the flex row, so the body's max-content (a long
287
+ subtitle) pushed it under the media, and the aside and the actions took a line each: partner-radar /hot rows went
288
+ from 221 (its old table) to 284 at 390. Now a grid with one rule for every row: the body stays beside the media
289
+ with all the width right of it; the aside and the actions share one line under the body (aside left, actions
290
+ right); the media spans both lines; without media the body takes the whole row. The `flex: 1 1 0` patch tried on
291
+ partner-radar fixed /hot but made /ugc-operators taller (308 → 332 at 320), because the actions sat beside the
292
+ body and narrowed it; the grid does neither. A body that is only its title keeps its aside and actions on that line
293
+ (one band, never taller than 0.13.0). Gaps are margins, so a row without media needs no `:has()`. The row's direct
294
+ children are the four slots (a selection checkbox goes inside `__media`); the loading skeleton keeps its own row.
295
+ Frames wider than 400 are unchanged.
296
+
297
+ Added
298
+ - `CompactItem.body` (activity): the event's own text under the label and the detail, `.ui-list__content` (body
299
+ size, wraps, never cut; `<p>` margins reset).
300
+ - `CompactList wrap` (`ui-list--wrap`, either variant): the label and the detail wrap instead of ending in "…". For a
301
+ list whose label carries data a finger must read (the cut text's `title` is mouse-only).
302
+ Under a finger the wrapped row is padded 12 + 20 + 12, so a one-line label stays centred in its 44px row.
303
+ - `CompactItem.labelText`: the full label in `title` (left out under `wrap`, where nothing is cut). `CompactListProps`
304
+ is exported.
305
+ - Catalog: the two row shapes at 390, and a timeline with body + wrap at 390.
306
+
307
+ Measured (gemlabs partner-radar on a local proof DB, `next dev`, 0.13.0 vs the 0.15.0 tarball, same rows; median /
308
+ total of all rows, px)
309
+ | Page | 320 | 390 | 768 | 1280 |
310
+ |---|---|---|---|---|
311
+ | /hot (100 rows) | 284 → **191** (28,747 → 20,123) | 284 → **191** (28,003 → 19,187) | 143 = 143 | 107 = 107 |
312
+ | /ugc-operators (40 rows) | 308 → **267** (12,067 → 10,427) | 284 → **243** (11,243 → 9,603) | 199 = 199 | 159 = 159 |
313
+
314
+ Row by row: 0 rows taller at any width; every row lower at 320 and 390; identical at 768 and 1280. collections-gate:
315
+ 0 broken, page overflow 0, at every width. The four partner-radar timelines on `CompactList variant="activity" wrap`
316
+ with `body` instead of its own `.pr-tl-*` (profile with 4 events, profile with 15 jobs, an organization): every item
317
+ the same height as before at all four widths, 0 labels or details cut, 0 `.pr-tl-*` left.
318
+
319
+ Checks
320
+ - `collections.test.ts`: geometry of both row shapes (avatar + long subtitle + score + 44px link; no media + many meta
321
+ lines + badges + score + button + ⋯) at 320 / 390 / 768: body beside the media and as wide as the room, aside and
322
+ actions on one line under the body, the row exactly as tall as that layout asks, nothing sideways; no taller than
323
+ 0.13.0 at any width and lower at 390; a title-only row stays one band; the skeleton keeps its block beside the bars.
324
+ Negative controls: the 0.13.0 rule and the `flex: 1 1 0` patch, planted in the
325
+ same CSS, break the checks. Compact list: with `wrap` nothing is cut at 320 / 390 / 768, the body follows the detail
326
+ at 13px, the time stays top right; without `wrap` the same label is one line and cut (control).
327
+ - Admin first load: the names the admin imports from `@gemboss/ui` bundle to the same bytes (7,967 minified,
328
+ 2,685 gzip) before and after; `gemboss-ui.css`, `ui.css` and the other admin stylesheets are unchanged (this release
329
+ touches `collections.css` and `collections.tsx` only, which the admin does not load).
330
+
331
+ ## 0.14.0 (06/10/2026)
332
+
333
+ **The editor frame as components (editor kit, step 2 of 5).** Nothing changes on screen in the admin: its theme, page and
334
+ section editors now render these, measured equal on a production build (below). README "Editor frame (0.14.0)".
335
+
336
+ Added
337
+ - `@gemboss/ui/editor` (its own entry; two modules, `frame` and `panes`, see README): `EditorFrame` (top · body with left column, seam and preview
338
+ pane · children; `split`, `right` docked over the preview, `mobilePane`, `embedded`), `EditorPaneTabs`, `EditorTopbar`
339
+ (`theme` / `page` / `guest`; back, chat toggle, title, three clusters, status line), `EditorBack`, `EditorChatToggle`,
340
+ `EditorSaveState`, and `DeviceToggle` / `MonitorGlyph` / `PhoneGlyph` (moved from the admin unchanged).
341
+ - `editor-plain.js`: `gembossUI.initEditorFrame()` for plain HTML (seam drag + keys clamped to the box, chat toggle,
342
+ device, phone pane, ⋯ / Publish ▾ menus; an event for each). Load after `kit-plain.js`.
343
+ - `catalog/editor.html`: the frame rendered from the components and running; `catalog/index.html` links it.
344
+ - editor.css: `.gemboss-editor-dock` (the right column; the declarations the theme editor's Global styles dock had
345
+ inline), its pane as containing block, and the frame's base geometry as zero-specificity `:where(…)` rules (left
346
+ column sized by `--gemboss-chat-w`, stacked at 1000px and below; preview takes the rest) so a page with only the
347
+ package lays out. In the admin every one of those properties is set more specifically, so none applies there.
348
+
349
+ Measured in the admin (two production builds of e3f3f92ec, before and after, pinned SENTRY_RELEASE / GIT_COMMIT_SHA /
350
+ GEMBOSS_BUILD_STAMP, served by `next start`)
351
+ - DOM and computed style (+ ::before / ::after and box) of every element on the page, and :hover / :focus-visible /
352
+ :active forced on every frame control, for 122 states of the three editors (default, Configure, chat collapsed,
353
+ device mobile / tablet, ⋯ open, Publish ▾ open, page picker open, status line, Global styles, phone Preview pane,
354
+ Layers, page settings, section insert menu) at 320, 390, 768 (touch), 1280, 1366, 1920: 27,606 element renders,
355
+ 4,080 forced-state checks, 0 differences in computed style or box. The one markup change is the intended one (the
356
+ Global styles dock's inline style became its class, 6 states, computed style equal). Two states differed by the
357
+ canvas still loading at the moment of capture, one in each direction; re-captured, 0. A negative control (one
358
+ declaration laid over the after build) is reported (52 differences on one screen).
359
+ - The theme top bar and the try door's bar rendered on the server before and after with the same props (662
360
+ combinations of save state, chat toggle, status line, tools, device): byte-identical markup.
361
+ - First load: the shell's raw bytes are unchanged (the webpack runtime 7,981 to 7,979 bytes; brotli +1 B from four
362
+ lazy-chunk hashes). Editor routes: theme +684 B, page +681 B, section +625 B, A/B campaign editor +680 B brotli
363
+ (CSS +106 B of each, the dock and the `:where` geometry); product studio +107 B (the same CSS).
364
+
365
+ ## 0.13.0 (06/10/2026)
366
+
367
+ **Collections: six patterns for tables, lists, grids and boards** (Chris 05/10/2026), each with its states. The CSS is
368
+ a NEW entry, `@gemboss/ui/collections.css`, outside `gemboss-ui.css` and outside the admin's sheets. One part did go
369
+ into `gemboss-ui.css`: the media card's own pieces (below, kit-plain.css), because the card has one owner. README
370
+ "Collections".
371
+
372
+ Changed
373
+ - `ui-card--media` (kit-plain.css) is `position: relative`, and gains `__link` (the title, its overlay covers the card),
374
+ `__menu` (⋯ in the picture's corner; the card stops clipping so the menu is not cut off), `__meta`, `data-ratio="4/3"`.
375
+ Admin not affected (it does not load kit-plain.css).
376
+ - `ui-chip[aria-current="true"]` draws like a current chip (a sort chip is not a page).
377
+
378
+ Added
379
+ - `collections.css`: Data table modifiers (`ui-table--sticky-first`, `--compact`, totals, footer, sort with
380
+ `aria-sort`), Index table (`ui-index`, `ui-table--index`: Polaris IndexTable's 12/550 header on #f7f7f7, 6px cells;
381
+ selected #f1f1f1; the bulk bar over the header; folds into a list below 768 of its container), Resource list
382
+ (`ui-list`, 12px padding, a 64px two-line row, the title link covers the row), Compact list (`ui-list--compact`,
383
+ 36 / 44 under a finger) and `ui-list--activity` (one timeline), Card grid (`ui-cardgrid`, auto-fill at 200 / 240 /
384
+ 320, gap 16, `ui-card__link`), Board (`ui-board`, 12 a column then nested "Show 24 more"), thumbnails (`ui-thumb`),
385
+ states (`ui-empty--no-match`, `ui-coll-error`, `aria-busy`, skeletons per pattern, `ui-trunc`, `ui-clamp-2`).
386
+ - React: `DataTable`, `ResourceList`, `ResourceItem`, `CompactList`, `CardGrid`, `MediaCard`, `Board`,
387
+ `CollectionState`, `CollectionSkeleton` (plain module, server-safe) and `IndexTable` (client: whole-row links,
388
+ selection + bulk actions, Shift range, Escape, sort links or `onSort`).
389
+ - `kit-plain.js` `initCollections()` (runs by itself): `[data-href]` rows open on a click that is not on a control;
390
+ `.ui-index` selection, bulk bar, `gembossUI.selection()`, `gb:selection`; client sort and `gb:sort`, only in a table marked `data-gb-sort` (a React table owns its own sort buttons).
391
+ - `packages/ui/scripts/collections-gate.mjs`: the ruled-out combinations (uppercase header, zebra, card per row, more than one
392
+ visible button, hover-only actions, checkboxes without bulk actions, a linked row over two editors, media cards in a
393
+ per-viewport grid, a table scrolling at 1280), with a page that breaks each rule in `collections.test.ts`.
394
+ - Density gate: eleven new roles from Polaris 13.9.5 rendered (`polaris-reference.json`); `tableHead` / `tableCell`
395
+ now leave the index table to its own roles.
396
+ - Catalog: section "Bảng, list, lưới, board", every pattern and state, with 390px frames where the shape changes.
397
+
5
398
  ## 0.12.0 (05/10/2026)
6
399
 
7
400
  **A change you SEE on every surface, and on the admin's /admin console.** The kit now draws the admin's (Polaris')
@@ -13,7 +406,8 @@ Changed (under a mouse, at 768px and wider)
13
406
  - `ui-btn`: 28px tall (was 32), padding 0 12 (was 16), 12px (was 13). New `--ui-btn-h: 28px`; `--ui-control-h` stays 32
14
407
  for `gemboss-field` and `ui-select` (Polaris TextField / Select). `ui-btn--icon` is 28 wide. The popup's own 28px rule
15
408
  is gone (every button is 28). **A surface that raised `--ui-control-h` to grow its buttons must now set `--ui-btn-h`
16
- as well** (gemcare `public/assets/inbox.css` sets `--ui-control-h: 44px` for that reason).
409
+ as well**. (This line used to name gemcare's `inbox.css` as such a surface: that rule was removed on 03/10/2026, and
410
+ gemcare takes 0.12.0 with no override, 28 under a mouse and 44 under a finger, measured on its inbox 06/10/2026, gemcare #312.)
17
411
  - ui.css (what the admin loads): buttons and segments are 44 at phone width with a mouse too (`max-width: 47.9975em`),
18
412
  as they already were under a finger; they were 32 there.
19
413
  - `ui-seg`: 28px group, labels 12px/550. `ui-tabs`: 28px (was 44 under every pointer), 12px/550 selected or not, 8px