@gemboss/ui 0.7.1 → 0.8.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/README.md CHANGED
@@ -85,17 +85,27 @@ A live run is never thrown off screen (`assistant/dock.ts` holds that rule). The
85
85
  its top edge and slides along the bottom, by pointer or keyboard, and remembers both under
86
86
  `storagePrefix`.
87
87
 
88
- The side panel is opt-in: `<AssistantHost allowSide>` puts a "Pin to the side" button in the popup
89
- header, and the panel carries "Float as a popup" back. It stands under the topbar on the right, and
88
+ The side panel is the DEFAULT shape since 0.8.0 (Chris, 04/10/2026): a browser that never chose opens the assistant
89
+ there, and `allowSide` defaults to true — a surface whose layout cannot make room passes `allowSide={false}` and gets
90
+ the popup. The popup header has "Pin to the side", and the panel carries "Float as a popup" back; the choice is kept
91
+ under `<prefix>.shape` (the older `floatShape` key recorded the old default for everyone, so it is not read). The rail
92
+ link reopens in that shape unless the person last worked full screen. A popup opens as tall as the window allows,
93
+ 12px under the topbar (it was min(480, viewport − 176)); a height the person dragged to is kept, and dragging it back
94
+ to the top returns to "as tall as the window allows".
95
+
96
+ **Upgrading to 0.8.0:** a host that never passed `allowSide` now offers the panel. Its page must make room — render
97
+ inside `AppShell` (`.gemboss-platform-main` takes the margin) or pad by `var(--gb-assistant-side-w)` while
98
+ `<prefix>-side` is on `<body>` — or pass `allowSide={false}`. A full-screen "shrink" handler should call `reopen()`
99
+ (back to the working shape), not `openDock()`, which records "popup" as the person's choice. It stands under the topbar on the right, and
90
100
  the page moves over for it instead of being covered: the provider puts `<prefix>-side` on `<body>` and
91
101
  the width in `--gb-assistant-side-w` (`SIDE_WIDTH_VAR`), and `.gemboss-platform-main` takes that as
92
102
  `margin-right`. A surface with its own layout reads the same variable. Its left edge is a
93
103
  `.ui-resizer` (drag, or ←/→, Shift for 64px, Home/End), clamped to 380–720px and never leaving the page
94
104
  less than 480px; the width is remembered. Panel and page only fit side by side from 1100px (rail 240
95
- + 380 + 480). Between 761 and 1099 the panel covers the content area and is not offered; on a phone
96
- it takes the screen. The status bar, a busy ×, leaving full screen mid-run and full
105
+ + 380 + 480). Between 761 and 1099 the panel covers the content area and is not offered — the host
106
+ withdraws `allowSide` there (`SIDE_FITS_QUERY`), so even the default opens the popup; on a phone it takes the screen. The status bar, a busy ×, leaving full screen mid-run and full
97
107
  screen's own way back ("Back to the side panel") all return to whichever shape the person last
98
- worked in (`floatShape`, remembered across reloads), so a pinned panel never pops back as a popup.
108
+ worked in (`floatShape`, remembered across reloads under `<prefix>.shape`), so a pinned panel never pops back as a popup.
99
109
  The host registers `allowSide` with the provider, and every way into the panel checks it, including
100
110
  a "side" remembered in storage: a host without it gets the popup.
101
111
 
@@ -201,6 +211,7 @@ surfaces on the package, the same gaps were filled six ways. What 0.5.0 settles,
201
211
  |---|---|---|
202
212
  | Control height: `gemboss-field`, `ui-select`, `ui-btn`, `ui-seg` | **32px** (Polaris TextField / Select), `--ui-control-h` | **44px**, `ui-btn--sm` included |
203
213
  | `ui-btn--sm` | 28px | 44px |
214
+ | `ui-btn` inside a popup's head or foot (0.7.3) | 28px, 12px | 44px |
204
215
  | Focus ring, every control | 2px iris `--ui-focus` (= `--gemboss-accent`, the admin's `--p-color-border-focus`) | same |
205
216
  | Body text (`html`, `body`, at zero specificity) | 13px / 20px, weight 450, `--gemboss-text`, antialiased (Polaris bodyMd) | same |
206
217
  | Kit text (tabs, table, notice, pager, empty, page-head lead) | `--ui-fs-body`, 13px | 16px in fields (iOS zoom) |
@@ -248,6 +259,42 @@ If your surface still sets any of these itself (a `body { font-size }`, a `min-h
248
259
  cascade over the package's zero-specificity base. `packages/ui/src/kit-look.test.ts` measures the table above in
249
260
  Chromium on the built CSS.
250
261
 
262
+ ## Popup density (0.7.3)
263
+
264
+ The kit popup (`.ui-overlay > .ui-modal`, `<dialog class="ui-modal">`, `openModal`, `Dialog`) is drawn at Polaris
265
+ Modal's density, the popup the admin opens. Chris, 04/10/2026, on GemFactory's detail popup: the admin's buttons are
266
+ smaller and save room (GemFeedback #1099). Measured at 1280 with a mouse:
267
+
268
+ | | Polaris Modal (admin) | 0.7.1 / 0.7.3 | 0.7.3 |
269
+ |---|---|---|---|
270
+ | × | 28×28 | 44×44 at every pointer | **28×28**; 44×44 under a thumb or ≤ 767px wide (head stays 53) |
271
+ | Head | 53px, padding 12/16 | 61px, padding 16/20 | **53px**, padding 12/16, `align-items: center` |
272
+ | Title | 14px / 650 | 15px / 620 | **14px / 650** |
273
+ | `ui-btn` in `__head` / `__foot` | 28 tall, 12px, padding 0/12 | 32, 13px, 0/16 | **28, 12px, 0/12**; 44 under a thumb or ≤ 767px |
274
+ | Body / foot padding | 16 / 16 | 16/20 / 12/20 | **16 / 12/16** |
275
+ | Scrim | rgba(0,0,0,.5), no blur | .6, blur 2px | **.5, no blur** (`::backdrop` too) |
276
+
277
+ Popup only: `--ui-control-h` stays 32, and a button outside a popup is still 32 tall.
278
+
279
+ `.ui-modal__body` and `.ui-modal__foot` moved from `kit-plain.css` to `ui.css`. The admin loads `ui.css` and not
280
+ `kit-plain.css`, so before 0.7.3 its kit popups drew a body with no padding and two foot buttons touching
281
+ (AutopilotConfirmModal). The scrolling layout of a tall popup stays in `kit-plain.css`.
282
+
283
+ A large popup can keep its subtitle and its own actions in the kit head instead of drawing a second bar
284
+ (`kit-plain.css`):
285
+
286
+ ```html
287
+ <div class="ui-modal__head">
288
+ <div class="ui-modal__title">Mega menu<span class="ui-modal__subtitle">chrome/header</span></div>
289
+ <div class="ui-modal__actions"><button class="ui-btn">Copy</button><button class="ui-btn ui-btn--icon" aria-label="More">…</button></div>
290
+ <button type="button" class="ui-modal__close" aria-label="Close">…</button>
291
+ </div>
292
+ ```
293
+
294
+ `packages/ui/src/modal-density.test.ts` measures the table in Chromium at 1280 (mouse), 390 and under a finger, on
295
+ `gemboss-ui.css` and on the admin's sheets alone. `UI_MODAL_ROOT=<a 0.7.1 tree>` runs it against the old CSS, where
296
+ it is red.
297
+
251
298
  ## Resizable panels
252
299
 
253
300
  `ui-resizer` is the seam a person drags between two panels; `ui-resizing` on `<body>` is the drag in
@@ -390,7 +437,7 @@ import { Avatar, Dialog, Leaderboard, Podium, SearchDialog, ThemeToggle, themeSc
390
437
  - `Dialog`: the kit's native `<dialog class="ui-modal">` (see kit-plain.css) driven by `open`; a
391
438
  backdrop click closes it, focus returns to the opener.
392
439
  - `ThemeToggle` + `themeScript`: `data-theme="dark|light"` on <html>, remembered in localStorage
393
- (`gemboss-theme`), the OS's choice until then. `portal.css` redefines every `--gemboss-*` colour under
440
+ (`gemboss-theme`), the OS's choice until then. `theme-dark.css` (in `gemboss-ui.css`) redefines every `--gemboss-*` colour under
394
441
  `[data-theme="dark"]` (a test fails if a colour token is left out), so every kit class follows.
395
442
  Opt-in: a surface without it (the admin) stays light.
396
443
  - `Avatar` (24 · 32 · 40 · 56): picture or initials on one of eight tones picked from the name.
@@ -398,6 +445,16 @@ import { Avatar, Dialog, Leaderboard, Podium, SearchDialog, ThemeToggle, themeSc
398
445
  all when empty. `Podium`: the first three, second · first · third.
399
446
  - `initialsOf` moved to `initials.ts` (plain, not "use client") so server components can call it; it is
400
447
  still exported from the package and from account.tsx.
448
+ - **Without the whole kit (0.7.2):** a surface with its own skin (GemCommunity) takes only these pieces'
449
+ CSS: `@gemboss/ui/gemboss-tokens.css` (variables only, no rule) + `@gemboss/ui/portal.css`. `Avatar`,
450
+ `Leaderboard` and `Podium` read nothing but `--gemboss-*` tokens (a test holds that), so they look the
451
+ same as on GemFeedback without `gemboss-ui.css` restyling the rest of the page. `SearchDialog` and
452
+ `Dialog` still need `ui.css` (the modal). `portal.css` holds no rule on `:root`, `html` or `body`; the dark palette is its own file,
453
+ `theme-dark.css`, so a surface that uses `data-theme` for something else (GemCommunity's sign-in pages) is not
454
+ turned dark by importing the leaderboard.
455
+ - Rows take `current` (the viewer's own row: tinted, and read as "Minh, you" via `currentLabel`), `badge`
456
+ (after the name, e.g. a level) and `id` (a `#me` anchor). `title` may be left out on a page whose own
457
+ `<h1>` names the board; pass `label` then. Screen-reader text uses `.gb-sr-only`, styled in portal.css.
401
458
 
402
459
  ## Report pages (0.4.0)
403
460
 
@@ -418,6 +475,31 @@ surface computes every number and ratio; the kit only draws. Markup for each is
418
475
  - `ui-grid--2` / `--3`: card grid that drops to one column; `ui-section-head`: an `h2` + note between page head and
419
476
  card titles.
420
477
 
478
+ ## Long lists: pages or "load more" (0.7.3)
479
+
480
+ *Recommendation, not a rule: Claude, 04/10/2026, after Chris asked it to decide for GemFactory.*
481
+
482
+ | List | Pattern | Why |
483
+ |---|---|---|
484
+ | A work list: items with an #ID, filters, a review queue, links people send each other (GemFactory board) | **Pages**, `ui-pager` with numbers, position in the URL (`?p=3`) | Back returns to the same place, a link opens the same page, the total is visible. Infinite scroll loses all three. |
485
+ | One column of a Kanban (GemFactory), or a feed read top to bottom | **Load more**, `ui-more` | Nobody jumps to page 9 of one column or of a feed. |
486
+
487
+ `ui-pager`: `<a rel="prev">` · page links · `<span class="ui-pager__gap" aria-hidden="true">…</span>` · the current page as
488
+ `<a aria-current="page">` (filled) · `<a rel="next">` · `<span class="ui-pager__range">201–250 of 655</span>` (pushed right).
489
+ On the first or last page the edge is `<a role="link" aria-disabled="true">` with no `href`: a screen reader still reads a
490
+ link and says it is unavailable (a `<span aria-disabled>` has no role, so it reads as plain text). It is muted and does
491
+ not react to hover. Page links are at least 32x32, 44x44 under a finger. Arrow and word are two flex items, hence the 4px
492
+ gap. Under 576px only Previous · the current page · Next stay and the range wraps under them (eleven 44px targets
493
+ 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
494
+ show (first, last, current ± 1, a gap only where it hides two or more pages: GemFactory `pageWindow`).
495
+
496
+ `ui-more`: one centred row. Idle: a `ui-btn` ("Show 24 more"). Loading: `<span class="ui-spinner" aria-hidden="true">` +
497
+ `<span role="status">Loading…</span>`. Error: `<span role="alert">Couldn't load.</span>` + "Try again". End: a line. As an
498
+ IntersectionObserver sentinel it has height from its padding; zero the padding and it has none.
499
+
500
+ `.ui-prose` links are at `:where()` specificity, so a component's own link rule (`ui-pager`, `ui-seg`, `ui-tabs`) wins
501
+ inside a page-wide `.ui-prose` (GemFactory wraps every page in it; its page numbers were drawn as blue underlined text).
502
+
421
503
  ## The catalog: every token, class and component, with its markup
422
504
 
423
505
  `npm run catalog` builds `catalog/index.html` (also shipped in the npm tarball, and built by `prepack`):