@gemboss/ui 0.7.2 → 0.8.1
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 +127 -6
- package/catalog/index.html +234 -6
- package/gemboss-ui.css +82 -27
- package/package.json +1 -1
- package/src/app-shell.css +26 -0
- package/src/assistant/dock.ts +19 -18
- package/src/assistant/host.tsx +40 -5
- package/src/assistant/provider.tsx +43 -19
- package/src/index.ts +3 -0
- package/src/kit-plain.css +31 -12
- package/src/page.tsx +35 -0
- package/src/ui.css +23 -13
package/README.md
CHANGED
|
@@ -40,6 +40,55 @@ Surfaces with no React (gemcare, gempress) use the CSS alone and write the same
|
|
|
40
40
|
`nav.icon-nav > .gnav-scroll > .gnav-group > a.gnav-item > span.gnav-ico + span.gnav-label`.
|
|
41
41
|
`packages/ui/src/shell.tsx` is the reference.
|
|
42
42
|
|
|
43
|
+
## Page width (0.8.1)
|
|
44
|
+
|
|
45
|
+
Chris, 04/10/2026: the rail on the left, and on the right a content column with the same max width on every
|
|
46
|
+
service, not edge to edge. The tiers are the admin's (`gemboss-shell.css`, `.gemboss-w-*` around a Polaris Page),
|
|
47
|
+
same numbers; `page-width.test.tsx` fails if the admin and the package disagree.
|
|
48
|
+
|
|
49
|
+
| Tier | Class | Max width | For | Admin |
|
|
50
|
+
|---|---|---|---|---|
|
|
51
|
+
| `default` | `ui-page` | `--ui-content-max` (900px) | lists, tables, card grids, dashboards, detail pages | bare Page, `.gemboss-w-gallery`, `.gemboss-w-dashboard` |
|
|
52
|
+
| `feed` | `ui-page ui-page--feed` | 720px | read top to bottom: activity, logs, timelines, a thread | `.gemboss-w-feed` |
|
|
53
|
+
| `form` | `ui-page ui-page--form` | 662px | one column of fields: settings, a create form | `.gemboss-w-form` (Polaris narrowWidth) |
|
|
54
|
+
| `canvas` | `ui-page ui-page--canvas` | 1440px | content measured to be wider than a column of text: a horizontal path, a wide comparison table | `.gemboss-w-canvas` (funnel path, 864px needed) |
|
|
55
|
+
| `full` | `ui-page ui-page--full` | none | a board or an editor that IS the screen. Write the reason next to it. | the editor (fixed, its own frame) |
|
|
56
|
+
|
|
57
|
+
React: one `PageContainer` directly inside `AppShell` caps every screen. Pass the CURRENT route's tier; a page
|
|
58
|
+
cannot widen a cap its parent set, so a screen that needs `canvas` or `full` gets it here, not from a second
|
|
59
|
+
wrapper of its own.
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
<AppShell rail={rail}>
|
|
63
|
+
<PageContainer width={route === "settings" ? "form" : route === "board" ? "full" : "default"}>{page}</PageContainer>
|
|
64
|
+
</AppShell>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`as="section"` changes the tag. `pageWidthClass("form")` returns the class string for a server template.
|
|
68
|
+
`PageContainer` lives in its own plain module (`packages/ui/src/page.tsx`), not in `shell.tsx`, and `AppShell` has no width
|
|
69
|
+
prop: the admin imports the shell pieces from `shell.tsx`, a client module its bundler keeps whole, and the
|
|
70
|
+
admin's first-load shell budget had no room for even this much JS (240.0 / 240 KB, 04/10/2026).
|
|
71
|
+
|
|
72
|
+
Plain HTML (GemFactory, gemcare, gempress): one wrapper as the first child of main.
|
|
73
|
+
|
|
74
|
+
```html
|
|
75
|
+
<main class="gemboss-platform-main">
|
|
76
|
+
<div class="ui-page ui-page--form">…</div>
|
|
77
|
+
</main>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Delete the surface's own cap when it moves onto a tier (`maxWidth: 1160` on each gemwatcher screen, a
|
|
81
|
+
`max-width` on a `.container`): two caps on one column means the narrower one wins and nobody can tell which.
|
|
82
|
+
|
|
83
|
+
**A cap only exists where it is SMALLER than the room it sits in.** Right of the 240px rail, inside main's 24px
|
|
84
|
+
padding, the room is `viewport − 288`: 992 at 1280, 1152 at 1440, 1632 at 1920. So 900 bites at all three, and
|
|
85
|
+
1440 (`canvas`) bites only from about 1730px; below that the column is just the room. Before you quote a width,
|
|
86
|
+
check `cap < viewport − rail − padding` at the width people actually use (the admin shipped two caps, 1040 then
|
|
87
|
+
998, that were wider than the room at 1277 and did nothing there, 10/09/2026). The column is never wider than
|
|
88
|
+
the room, so no tier makes a page scroll sideways; at ≤760px there is no rail and the column is `viewport − 32`.
|
|
89
|
+
|
|
90
|
+
The page must reset `body { margin: 0 }` itself: `gemboss-ui.css` does not (the admin gets it from Polaris).
|
|
91
|
+
|
|
43
92
|
## The sign-in screen
|
|
44
93
|
|
|
45
94
|
```tsx
|
|
@@ -85,17 +134,27 @@ A live run is never thrown off screen (`assistant/dock.ts` holds that rule). The
|
|
|
85
134
|
its top edge and slides along the bottom, by pointer or keyboard, and remembers both under
|
|
86
135
|
`storagePrefix`.
|
|
87
136
|
|
|
88
|
-
The side panel is
|
|
89
|
-
|
|
137
|
+
The side panel is the DEFAULT shape since 0.8.0 (Chris, 04/10/2026): a browser that never chose opens the assistant
|
|
138
|
+
there, and `allowSide` defaults to true — a surface whose layout cannot make room passes `allowSide={false}` and gets
|
|
139
|
+
the popup. The popup header has "Pin to the side", and the panel carries "Float as a popup" back; the choice is kept
|
|
140
|
+
under `<prefix>.shape` (the older `floatShape` key recorded the old default for everyone, so it is not read). The rail
|
|
141
|
+
link reopens in that shape unless the person last worked full screen. A popup opens as tall as the window allows,
|
|
142
|
+
12px under the topbar (it was min(480, viewport − 176)); a height the person dragged to is kept, and dragging it back
|
|
143
|
+
to the top returns to "as tall as the window allows".
|
|
144
|
+
|
|
145
|
+
**Upgrading to 0.8.0:** a host that never passed `allowSide` now offers the panel. Its page must make room — render
|
|
146
|
+
inside `AppShell` (`.gemboss-platform-main` takes the margin) or pad by `var(--gb-assistant-side-w)` while
|
|
147
|
+
`<prefix>-side` is on `<body>` — or pass `allowSide={false}`. A full-screen "shrink" handler should call `reopen()`
|
|
148
|
+
(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
149
|
the page moves over for it instead of being covered: the provider puts `<prefix>-side` on `<body>` and
|
|
91
150
|
the width in `--gb-assistant-side-w` (`SIDE_WIDTH_VAR`), and `.gemboss-platform-main` takes that as
|
|
92
151
|
`margin-right`. A surface with its own layout reads the same variable. Its left edge is a
|
|
93
152
|
`.ui-resizer` (drag, or ←/→, Shift for 64px, Home/End), clamped to 380–720px and never leaving the page
|
|
94
153
|
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
|
|
96
|
-
it takes the screen. The status bar, a busy ×, leaving full screen mid-run and full
|
|
154
|
+
+ 380 + 480). Between 761 and 1099 the panel covers the content area and is not offered — the host
|
|
155
|
+
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
156
|
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.
|
|
157
|
+
worked in (`floatShape`, remembered across reloads under `<prefix>.shape`), so a pinned panel never pops back as a popup.
|
|
99
158
|
The host registers `allowSide` with the provider, and every way into the panel checks it, including
|
|
100
159
|
a "side" remembered in storage: a host without it gets the popup.
|
|
101
160
|
|
|
@@ -201,6 +260,7 @@ surfaces on the package, the same gaps were filled six ways. What 0.5.0 settles,
|
|
|
201
260
|
|---|---|---|
|
|
202
261
|
| Control height: `gemboss-field`, `ui-select`, `ui-btn`, `ui-seg` | **32px** (Polaris TextField / Select), `--ui-control-h` | **44px**, `ui-btn--sm` included |
|
|
203
262
|
| `ui-btn--sm` | 28px | 44px |
|
|
263
|
+
| `ui-btn` inside a popup's head or foot (0.7.3) | 28px, 12px | 44px |
|
|
204
264
|
| Focus ring, every control | 2px iris `--ui-focus` (= `--gemboss-accent`, the admin's `--p-color-border-focus`) | same |
|
|
205
265
|
| Body text (`html`, `body`, at zero specificity) | 13px / 20px, weight 450, `--gemboss-text`, antialiased (Polaris bodyMd) | same |
|
|
206
266
|
| Kit text (tabs, table, notice, pager, empty, page-head lead) | `--ui-fs-body`, 13px | 16px in fields (iOS zoom) |
|
|
@@ -248,6 +308,42 @@ If your surface still sets any of these itself (a `body { font-size }`, a `min-h
|
|
|
248
308
|
cascade over the package's zero-specificity base. `packages/ui/src/kit-look.test.ts` measures the table above in
|
|
249
309
|
Chromium on the built CSS.
|
|
250
310
|
|
|
311
|
+
## Popup density (0.7.3)
|
|
312
|
+
|
|
313
|
+
The kit popup (`.ui-overlay > .ui-modal`, `<dialog class="ui-modal">`, `openModal`, `Dialog`) is drawn at Polaris
|
|
314
|
+
Modal's density, the popup the admin opens. Chris, 04/10/2026, on GemFactory's detail popup: the admin's buttons are
|
|
315
|
+
smaller and save room (GemFeedback #1099). Measured at 1280 with a mouse:
|
|
316
|
+
|
|
317
|
+
| | Polaris Modal (admin) | 0.7.1 / 0.7.3 | 0.7.3 |
|
|
318
|
+
|---|---|---|---|
|
|
319
|
+
| × | 28×28 | 44×44 at every pointer | **28×28**; 44×44 under a thumb or ≤ 767px wide (head stays 53) |
|
|
320
|
+
| Head | 53px, padding 12/16 | 61px, padding 16/20 | **53px**, padding 12/16, `align-items: center` |
|
|
321
|
+
| Title | 14px / 650 | 15px / 620 | **14px / 650** |
|
|
322
|
+
| `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 |
|
|
323
|
+
| Body / foot padding | 16 / 16 | 16/20 / 12/20 | **16 / 12/16** |
|
|
324
|
+
| Scrim | rgba(0,0,0,.5), no blur | .6, blur 2px | **.5, no blur** (`::backdrop` too) |
|
|
325
|
+
|
|
326
|
+
Popup only: `--ui-control-h` stays 32, and a button outside a popup is still 32 tall.
|
|
327
|
+
|
|
328
|
+
`.ui-modal__body` and `.ui-modal__foot` moved from `kit-plain.css` to `ui.css`. The admin loads `ui.css` and not
|
|
329
|
+
`kit-plain.css`, so before 0.7.3 its kit popups drew a body with no padding and two foot buttons touching
|
|
330
|
+
(AutopilotConfirmModal). The scrolling layout of a tall popup stays in `kit-plain.css`.
|
|
331
|
+
|
|
332
|
+
A large popup can keep its subtitle and its own actions in the kit head instead of drawing a second bar
|
|
333
|
+
(`kit-plain.css`):
|
|
334
|
+
|
|
335
|
+
```html
|
|
336
|
+
<div class="ui-modal__head">
|
|
337
|
+
<div class="ui-modal__title">Mega menu<span class="ui-modal__subtitle">chrome/header</span></div>
|
|
338
|
+
<div class="ui-modal__actions"><button class="ui-btn">Copy</button><button class="ui-btn ui-btn--icon" aria-label="More">…</button></div>
|
|
339
|
+
<button type="button" class="ui-modal__close" aria-label="Close">…</button>
|
|
340
|
+
</div>
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
`packages/ui/src/modal-density.test.ts` measures the table in Chromium at 1280 (mouse), 390 and under a finger, on
|
|
344
|
+
`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
|
|
345
|
+
it is red.
|
|
346
|
+
|
|
251
347
|
## Resizable panels
|
|
252
348
|
|
|
253
349
|
`ui-resizer` is the seam a person drags between two panels; `ui-resizing` on `<body>` is the drag in
|
|
@@ -428,6 +524,31 @@ surface computes every number and ratio; the kit only draws. Markup for each is
|
|
|
428
524
|
- `ui-grid--2` / `--3`: card grid that drops to one column; `ui-section-head`: an `h2` + note between page head and
|
|
429
525
|
card titles.
|
|
430
526
|
|
|
527
|
+
## Long lists: pages or "load more" (0.7.3)
|
|
528
|
+
|
|
529
|
+
*Recommendation, not a rule: Claude, 04/10/2026, after Chris asked it to decide for GemFactory.*
|
|
530
|
+
|
|
531
|
+
| List | Pattern | Why |
|
|
532
|
+
|---|---|---|
|
|
533
|
+
| 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. |
|
|
534
|
+
| 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. |
|
|
535
|
+
|
|
536
|
+
`ui-pager`: `<a rel="prev">` · page links · `<span class="ui-pager__gap" aria-hidden="true">…</span>` · the current page as
|
|
537
|
+
`<a aria-current="page">` (filled) · `<a rel="next">` · `<span class="ui-pager__range">201–250 of 655</span>` (pushed right).
|
|
538
|
+
On the first or last page the edge is `<a role="link" aria-disabled="true">` with no `href`: a screen reader still reads a
|
|
539
|
+
link and says it is unavailable (a `<span aria-disabled>` has no role, so it reads as plain text). It is muted and does
|
|
540
|
+
not react to hover. Page links are at least 32x32, 44x44 under a finger. Arrow and word are two flex items, hence the 4px
|
|
541
|
+
gap. Under 576px only Previous · the current page · Next stay and the range wraps under them (eleven 44px targets
|
|
542
|
+
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
|
|
543
|
+
show (first, last, current ± 1, a gap only where it hides two or more pages: GemFactory `pageWindow`).
|
|
544
|
+
|
|
545
|
+
`ui-more`: one centred row. Idle: a `ui-btn` ("Show 24 more"). Loading: `<span class="ui-spinner" aria-hidden="true">` +
|
|
546
|
+
`<span role="status">Loading…</span>`. Error: `<span role="alert">Couldn't load.</span>` + "Try again". End: a line. As an
|
|
547
|
+
IntersectionObserver sentinel it has height from its padding; zero the padding and it has none.
|
|
548
|
+
|
|
549
|
+
`.ui-prose` links are at `:where()` specificity, so a component's own link rule (`ui-pager`, `ui-seg`, `ui-tabs`) wins
|
|
550
|
+
inside a page-wide `.ui-prose` (GemFactory wraps every page in it; its page numbers were drawn as blue underlined text).
|
|
551
|
+
|
|
431
552
|
## The catalog: every token, class and component, with its markup
|
|
432
553
|
|
|
433
554
|
`npm run catalog` builds `catalog/index.html` (also shipped in the npm tarball, and built by `prepack`):
|
|
@@ -475,7 +596,7 @@ something the base cannot express, add a token or a prop here, and every surface
|
|
|
475
596
|
| `packages/ui/src/shell.css` | the SHARED SHELL (rail, topbar, main, mobile drawer, the assistant's thread rail, popover surface, account chip). 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). |
|
|
476
597
|
| `packages/ui/src/kit-plain.css` | primitives for surfaces that render plain HTML, not React (0.2.1): menu, key/value list, icon button; 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. |
|
|
477
598
|
| `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, 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. |
|
|
478
|
-
| `packages/ui/src/chat.css`, `chat-composer.css`, `assistant.css`, `auth.css`, `app-shell.css` | the chat body, the composer, the assistant frame, the sign-in screen, the topbar slots + base text. All inside `gemboss-ui.css`; the admin @imports `chat.css` and `chat-composer.css` itself. |
|
|
599
|
+
| `packages/ui/src/chat.css`, `chat-composer.css`, `assistant.css`, `auth.css`, `app-shell.css` | the chat body, the composer, the assistant frame, the sign-in screen, the topbar slots + base text + the page width tiers (`ui-page`, 0.8.1). All inside `gemboss-ui.css`; the admin @imports `chat.css` and `chat-composer.css` itself. |
|
|
479
600
|
| `packages/ui/src/icon.svg` | 0.5.0: the GemBoss app icon, for `<link rel="icon" type="image/svg+xml">`. Must stay byte-identical to `apps/admin/src/app/icon.svg` (`kit-look.test.ts` checks). |
|
|
480
601
|
| `packages/ui/src/**/*.tsx` | the components, as TypeScript source. Next: add `@gemboss/ui` to `transpilePackages`. Vite compiles it as is. |
|
|
481
602
|
|