@gemboss/ui 0.46.0 → 0.48.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,96 @@
2
2
 
3
3
  Versions up to 0.9.0 are described in the README, each in the section that introduced it.
4
4
 
5
+ ## Unreleased
6
+
7
+ ## 0.48.0 (21/10/2026)
8
+
9
+ **LanguageSwitcher: what its first three surfaces had to work around** (21/10/2026; GemCommunity #333, GemNews #2,
10
+ GemFeedback #82).
11
+
12
+ Added
13
+ - `gembossUI.languageSwitcher()` (plain JS) returns `update({ value, labels, suggested, languages })` and
14
+ `setLabels(labels)`: the switcher changes where it is, no new element. The button's name and label, the menu's name and
15
+ the suggested hint change in place; the rows are drawn again only when `languages` or `suggested` change, and the row
16
+ that had focus has it again. An open menu stays open (placed again), focus stays where it was. `labels` given replace
17
+ those words only; `suggested: null` takes the row away. GemNews's `paintLang()` rebuilt the switcher (destroy + new,
18
+ focus carried by hand) every time its language changed. `set()`, `value()`, `open()`, `close()`, `destroy()` unchanged.
19
+ - `TopbarNav` `foot`: the app's own section at the foot of the FOLDED tab list, under a line (`.gb-topnav-foot`), and the
20
+ class `gb-topnav-unfolded` for its copy in the topbar's end, shown only while the tabs are unfolded. A portal topbar
21
+ (brand, tab menu, search, theme, Sign in) has no room for a 55px language button at 320–390: GemFeedback hid it and
22
+ printed links at the foot of the page. Now `foot={<LanguageSwitcher variant="inline" end={false} …/>}` plus
23
+ `<LanguageSwitcher className="gb-topnav-unfolded" …/>` in the end slot: one language control in the bar at every width.
24
+ - `TopbarNav` `foldAt={760}`: the tabs fold over the whole phone topbar (≤760px) instead of under 600px, for a bar whose end
25
+ holds more (GemFeedback's three tabs ran under the switcher at 601–687). Default 600, unchanged.
26
+ - `topnav-fold.css` (`@gemboss/ui/topnav-fold.css`), a new entry `TopbarNav` imports after `portal.css`: the rules for `foot`,
27
+ `foldAt`, `gb-topnav-unfolded` and the ellipsis below. `portal.css` is in the frozen `gemboss-ui.css`, which had 24 bytes
28
+ left under its ceiling. 0.3 KB gzip minified (ceiling 789 in `weight-budget.json`).
29
+
30
+ Changed
31
+ - The LanguageSwitcher's rows, hint and separator are one class (0,1,0) instead of zero specificity (`:where`), with the
32
+ core's values: an app's `button { font: inherit }` (0,0,1) beat the 13px rows (GemCommunity 14 / 16px, GemNews 16px; both
33
+ restated 13px). An app restyles them on purpose with one class of its own. The surface and the box stay `:where`.
34
+ - Every `--gemboss-*` token `language-switcher.css` reads has its light value as a fallback: a page with neither the core nor
35
+ `gemboss-tokens.css` draws the switcher right (light).
36
+ - `TopbarNav`'s folded button: the open tab's name sits in `span.gb-topnav-fold-label` and gives way with an ellipsis before
37
+ it runs under the topbar's end ("Changelog" at 320 overlapped the search button by 40px, with or without a switcher).
38
+
39
+ Fixed
40
+ - The switcher's tick touched the language's name on a page with app-shell.css and no core (GemCommunity's /welcome, for the
41
+ brand): app-shell.css's `.ui-menu__item` (0,1,0) reads `--ui-space-2` / `--ui-radius-sm`, unset there, and beat the row's
42
+ gap. The row's gap and corners are at two classes (`.gb-lang__item.ui-menu__item`, 8px / 6px). app-shell.css itself is
43
+ unchanged: the frozen gemboss-ui.css has 5 bytes left under its ceiling, and two fallbacks there cost 3.
44
+ - README: "the switcher fits the phone bar at 320–390" is AppShell's bar; a portal Topbar + TopbarNav puts it in the tab
45
+ menu (`foot`).
46
+
47
+ ## 0.47.0 (21/10/2026)
48
+
49
+ **The kit's menu rows at the admin's density** (Chris 21/10/2026, of a menu drawn 44px tall: "nó đang to hơn bình thường";
50
+ 0.43.0 did this for the editor's menus).
51
+
52
+ Changed
53
+ - `.ui-menu__item` (`Menu` / `MenuItem`, kit-plain menus, the assistant header's ⋯) and `.gemboss-account-menu-item`
54
+ (`AccountMenu`, the admin's account corner included) under a mouse: a Polaris ActionList item, 32px tall with 4px padding
55
+ above and below (were 44px with 12px, for every pointer). A kit menu's rows sit 6px in from a list padded 6
56
+ (`.ui-menu__list`, `.gb-dock-menu-list`: were 4), as an ActionList item in its section; the account menu's rows keep 16px at
57
+ the sides, under its head. Under a finger or on a screen 767px or narrower: 44px with 12px 16px, as before. The row states
58
+ `box-sizing: border-box` itself. `language-switcher.css` repeats the core's row for a page without the core (gemboss.ai), so it
59
+ follows: list padded 6, rows 32 / 4px 6px under a mouse, 44 / 12px 16px under a finger. The density gate measures both (`menuItem`, `menuSurface` in polaris-reference.json), red
60
+ on 0.46's rows. Measured on the admin (local): the account menu's four rows 176px → 128px, the assistant's ⋯ rows 44 → 32.
61
+
62
+ **A choice made by looking, and a logo sheet** (GemBoss Brand › Logo, 21/10/2026: the seller picks one of three AI logos from
63
+ GemMedia, sees each the way it will be used, and confirms one).
64
+
65
+ Added
66
+ - `QuestionCard`: a choice's `media` (a picture across the top of the choice, before its dot and words), `layout="tiles"`
67
+ (the choices side by side, as many as fit at 200px: three on a card, one on a phone) and a choice's `disabled` (shown greyed,
68
+ its radio off, never the confirmed value: the confirm stays off while a disabled choice is the value). Rows stay the default.
69
+ `question-card.css` +3 rules (`.ui-qcard__media`, `.ui-qcard--tiles …`), within its ceiling.
70
+ - `LogoSheet` (`packages/ui/src/logo-sheet.tsx`, `@gemboss/ui/logo-sheet.css`, its own entry): the mark on a checkerboard, the
71
+ name lockup on a light and on a dark header, the favicon at its real 32px beside the name. Spans and images only, so it sits
72
+ inside a choice. 0.5 KB gzip minified (ceiling 1,057 in `weight-budget.json`).
73
+ - `logo-sheet.test.tsx`, and `question-card.test.tsx` for tiles and a disabled choice.
74
+
75
+ **The thread rail for a page with no React** (GemFactory's full-screen Factory Assistant, 21/10/2026: the plain frame had no
76
+ way back to an earlier conversation).
77
+
78
+ Added
79
+ - `session-rail-plain.js`, `gembossUI.sessionRail(opts)` → `{ el, update, focusSearch, destroy }`: `SessionRail` in ES5, the
80
+ same markup and classes, the same search (marks folded), recency groups, inline rename, ⋯ menu and two-step delete; the
81
+ helpers as `gembossUI.assistantSessions` (`relativeTime`, `normalizeForSearch`, `groupSessions`). With no
82
+ `onToggleCollapsed` it keeps its own collapse, in localStorage under `storageKey`. Its own entry: 4.5 KB gzip minified
83
+ (ceiling 5,115 in `weight-budget.json`).
84
+ - `assistantFrame({ rail })`: the admin's `AssistantWorkspaceLayout` around the body (the rail in full screen only,
85
+ `is-dock` in every other shape, the body never moved) and `frame.setSwitching(on)`. Without `rail` the frame's markup is
86
+ as before. `assistant-plain.js` stays inside its ceiling.
87
+ - `gembossUI.fullChromeRoom(chatEl)` (`assistant-plain.js`): `chat.tsx`'s `useFullChromeRoom` for a page with no React. In full
88
+ screen the frame's floated buttons sat over the first message of a chat column (GemFactory `/assistant` with the rail, 1440);
89
+ how far they reach into the `.gb-chat` is written as `--gb-chat-chrome-w` / `-h` + `data-chrome`, which assistant-chat.css
90
+ already pads by. Cleared outside full screen and when they do not overlap.
91
+ - `assistant-plain-browser.test.ts`: the helpers against `sessions.tsx`; in Chromium the plain rail against `SessionRail`
92
+ over 20 steps at 1440 and 900 (the tree, the focus, the menu's place and every callback after each), the framed layout
93
+ against `AssistantHost` + `AssistantWorkspaceLayout` over 10 shapes, and two negative controls.
94
+
5
95
  ## 0.46.0 (21/10/2026)
6
96
 
7
97
  **`.gemboss-page-section-content` is gone** (gemboss one panel P2b, 21/10/2026).
package/README.md CHANGED
@@ -59,6 +59,7 @@ its CSS out of the build with it (`sideEffects`). gzip of the minified file, as
59
59
  | 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
60
  | assistant | `@gemboss/ui/assistant.css` | `packages/ui/src/assistant.css` | the dock and the launcher | 2.2 KB | `AssistantHost`, the launcher import it |
61
61
  | portal | `@gemboss/ui/portal.css` | `packages/ui/src/portal.css` | `TopbarNav`, `SearchDialog`, `Avatar`, `Leaderboard`, `Podium`, the `ThemeToggle` button | 2.1 KB | those components import it |
62
+ | topnav-fold | `@gemboss/ui/topnav-fold.css` | `packages/ui/src/topnav-fold.css` | `TopbarNav`'s folded menu after the frozen bundle (unreleased): `foot`, `foldAt={760}`, `gb-topnav-unfolded`, the open tab's ellipsis | 0.3 KB | `TopbarNav` imports it (after portal.css) |
62
63
  | site | `@gemboss/ui/site.css` | `packages/ui/src/site.css` | the public site layer (0.40.0): the bar, the lockup at the site size, links, buttons, the footer | 1.0 KB | `SiteHeader` / `SiteBrand` import it; a plain-HTML page links it |
63
64
  | theme-dark | `@gemboss/ui/theme-dark.css` | `packages/ui/src/theme-dark.css` | the dark palette | 0.6 KB | `ThemeToggle` and `themeScript` (`theme-script.ts`, without portal.css) import it |
64
65
  | collections | `@gemboss/ui/collections.css` | `packages/ui/src/collections.css` | tables, lists, grids, boards | 4.6 KB | `IndexTable`, `ResourceList`, `Board`… import it |
@@ -80,6 +81,7 @@ its CSS out of the build with it (`sideEffects`). gzip of the minified file, as
80
81
  | range-bar | `@gemboss/ui/range-bar.css` | `packages/ui/src/range-bar.css` | `RangeBar`: a value against a market's spread on one axis (0.43.0) | 0.6 KB | `RangeBar` imports it |
81
82
  | 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 |
82
83
  | 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
+ | 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 |
83
85
  | 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) |
84
86
 
85
87
  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).
@@ -333,7 +335,27 @@ The page reloads on every click, so the frame keeps the shape on screen for the
333
335
  `<prefix>.presentation`, `keepAcrossPages: false` to turn it off): open on one page, open on the next. A run does not
334
336
  survive the reload, and leaving the full-screen page leaves the status bar, as `leave-full` does. The full-screen page
335
337
  is yours (`fullHref`); the frame enters full there by itself. `launchers` are hidden while the frame is up and open it.
336
- Not in the plain frame yet: `secondTab` and page facts.
338
+ Not in the plain frame yet: `secondTab`, page facts and the popup's open-chat tabs (`OpenChatTabs`).
339
+
340
+ **The thread rail, with no React (0.47.0): `session-rail-plain.js`.** `SessionRail` for the same pages: the same
341
+ markup and classes (shell.css draws both), the search box (case and Vietnamese marks folded), the recency groups, inline
342
+ rename, the ⋯ menu and its two-step delete. The sessions, and what opening, renaming, pinning or deleting one does, stay
343
+ the page's. Hand it to the frame as `rail` and the frame builds the admin's `AssistantWorkspaceLayout` around the body:
344
+ the rail in full screen only, `is-dock` in the popup and the panel, the body never moved. `frame.setSwitching(true)` dims
345
+ the page while the next thread loads; `gembossUI.fullChromeRoom(chatEl)` keeps the first message out from under the
346
+ full-screen buttons (as `useFullChromeRoom` does for `AssistantChat`). The rail is its own file and its own ceiling (4.5 KB gzip), so a page with no full-screen
347
+ assistant does not pay for it. `assistant-plain-browser.test.ts` holds it to `SessionRail` over 20 steps (typing, Escape,
348
+ the menu, rename, pin, delete, collapse, an empty list) and the framed layout to `AssistantHost` + `AssistantWorkspaceLayout`.
349
+
350
+ ```html
351
+ <script src="kit-plain.js"></script><script src="session-rail-plain.js"></script><script src="assistant-plain.js"></script>
352
+ <script>
353
+ const rail = gembossUI.sessionRail({ sessions, activeId, onNewChat, onOpen: openThread, onRename: rename,
354
+ onDelete: remove, titleMaxLength: 120, storageKey: "gemfactory.assistant.railCollapsed" });
355
+ const frame = gembossUI.assistantFrame({ title: "Factory Assistant", body: chatElement, rail, fullHref: "/assistant" });
356
+ rail.update({ sessions: fresh, activeId: id }); // after a turn, a rename, a delete
357
+ </script>
358
+ ```
337
359
 
338
360
  ## The engine picker (0.25.0): `EnginePicker`, `BridgeNotice`
339
361
 
@@ -1442,8 +1464,8 @@ import { FLEET_LANGUAGES, LanguageSwitcher, matchLanguage } from "@gemboss/ui";
1442
1464
  | The menu | the kit's, not a new one: `details.ui-menu` (it opens with no JavaScript), its list `ui-menu__list gemboss-popover-surface` floated in the top layer by `./menu-layer` as `Menu`'s is, its rows `ui-menu__item`, so it looks like the account menu. `role="menu"` named `trigger`; each row a `menuitemradio` with `aria-checked`, `data-lang`, a check column (`.gb-lang__check`, shown on the checked row only) and the name in its own `lang` (`span.gb-lang__name lang="zh"`), so a screen reader says "中文" in Chinese. The suggested row carries `ui-menu__hint`, then `hr.ui-menu__sep` |
1443
1465
  | Keys | an opening (a click, Enter / Space, ↓ / ↑ on the button) puts focus on the checked row; ↑ ↓ wrap, Home / End; Escape closes it, focus back on the button; Tab closes it and focus goes on from the button, forward or back; a click outside closes it; a row closes it, focus on the button, and calls `onChange` when it is not the current one. Rows are out of the Tab order (`tabindex="-1"`), as a menu's are |
1444
1466
  | Helpers | `matchLanguage(languages, wanted)`: the first of `navigator.languages`, an Accept-Language header or one tag that the list has, exactly or by its base language ("zh-CN" → "zh"), else undefined; pure, so a server reads the header with it. `languageShort(language)` |
1445
- | CSS | `language-switcher.css`, its own entry, 1.1 KB gzip, imported by the component. The menu's base rules (the core's `ui-menu` ones) are repeated there at zero specificity (`:where`) with the core's values: where the core is loaded it wins and nothing changes; a page that links only the tokens and this file (gemboss.ai, whose pages the core would reset) draws the same menu. Tokens only, so `data-theme="dark"` follows |
1446
- | The topbar | at 320 / 360 / 390 the kit's phone bar still fits with the switcher between ThemeToggle and the account chip (☰, brand, search, toggle, switcher, chip; no horizontal scroll) |
1467
+ | CSS | `language-switcher.css`, its own entry, 1.1 KB gzip, imported by the component. The menu's base rules (the core's `ui-menu` ones) are repeated there with the core's values, so a page that links only this file (gemboss.ai, whose pages the core would reset; GemNews's extension) draws the same menu. The surface and the box at zero specificity (`:where`); the rows, the hint and the separator at ONE class (0,1,0, since 22/10/2026), the core's own weight: an app's `button { font: inherit }` (0,0,1) cannot make them 16px, and one class of the app's (`.gb-lang__item { … }` after it, `.my-bar .gb-lang__item`) restyles them on purpose (the gap and corners take two: `.gb-lang__item.ui-menu__item`, so app-shell.css's unset tokens on a page without the core cannot zero the gap). Tokens, so `data-theme="dark"` follows, each with its light value as the fallback: no core, no `gemboss-tokens.css`, still right (light) |
1468
+ | The topbar | AppShell's phone bar (☰, brand, search, toggle, switcher, account chip) fits it at 320 / 360 / 390, between ThemeToggle and the chip, no horizontal scroll. A portal `Topbar` with `TopbarNav` does NOT have that room (GemFeedback: brand, tab menu, search, theme, Sign in, no room for a 55px button at 320–390, and at 601–687 the tabs ran under it): there the switcher goes in the folded tab menu, `TopbarNav foot`, and its end-slot copy wears `gb-topnav-unfolded` (README "Public portal pieces" › TopbarNav) |
1447
1469
 
1448
1470
  ### Plain HTML: `gembossUI.languageSwitcher()` (21/10/2026)
1449
1471
 
@@ -1474,7 +1496,8 @@ the page has it; it holds no closing script tag, so a server can inline it.
1474
1496
  | | |
1475
1497
  | --- | --- |
1476
1498
  | Options | the component's props, same names and defaults (`value`, `languages`, `onChange`, `suggested`, `labels`, `variant`, `end`, `className`), plus `mount`: an element the switcher REPLACES (a placeholder the server wrote) |
1477
- | Returns | `{ el, value(), set(code), open(), close(), destroy() }`. A chosen row updates the switcher itself before `onChange` (the component waits for its `value` prop); `set` is for a change from elsewhere; `destroy` removes it and its two document listeners |
1499
+ | Returns | `{ el, value(), set(code), update(props), setLabels(labels), open(), close(), destroy() }`. A chosen row updates the switcher itself before `onChange` (the component waits for its `value` prop); `set` is for a change from elsewhere; `destroy` removes it and its two document listeners |
1500
+ | `update` / `setLabels` (22/10/2026) | the page's language changed, or its list: `sw.setLabels({ trigger: T("menu.lang"), suggested: T("menu.lang_suggested") })`, `sw.update({ value, labels, suggested, languages })` (any of them). In place, as the component re-renders: the button's name and label, the menu's name, the hint; the rows drawn again only when `languages` or `suggested` change, the row that had focus focused again. An open menu stays open, placed again; the same `el`. `labels` given replace those words only; `suggested: null` takes the row away. No more destroy + new + carrying the focus by hand (GemNews's `paintLang`) |
1478
1501
  | `gembossUI` | `languageSwitcher`, `FLEET_LANGUAGES`, `LANGUAGE_SWITCHER_LABELS`, `matchLanguage`, `languageShort`. With `kit-plain.js` the open list floats in the top layer (`gembossUI.floatMenu`) and `initMenus` closes it like any kit menu; without it the list hangs under the button (`position: absolute`), as every kit menu does where there is no popover API |
1479
1502
  | A Chrome extension | a popup or options page loads both files by `src` (no inline script: an extension's CSP forbids it) and calls it from its own script; `onChange` writes `chrome.storage` |
1480
1503
 
@@ -1484,10 +1507,18 @@ two DOMs node for node, attribute for attribute, with focus, open state and the
1484
1507
  script: closed, opened by a click, ↓ ↓ Home End ↑, Escape, ↓ on the button, Home + Enter, the current row, a click outside,
1485
1508
  Tab; with the defaults and with Vietnamese labels, a suggested row, the inline variant, left-aligned, links. NEGATIVE
1486
1509
  CONTROL: one `lang` changed on the plain page and the comparison sees it. Then: the short label per language, the list
1487
- under the button's right edge, one tick; 36 / 44px and 44px rows; the phone bar at 390 / 360 / 320; contrast in both
1510
+ under the button's right edge, one tick; 36 / 44px and rows of 44 under a finger; the phone bar at 390 / 360 / 320; contrast in both
1488
1511
  themes (label ≥ 4.5:1, tick ≥ 3:1); the standalone page's menu equal to the core's (negative control: without the file,
1489
- the browser's popover box). `LANGUAGE_SWITCHER_SHOTS=<dir>` writes the screenshots (closed / open, both variants, both
1490
- themes, vi / en / zh). `language-switcher.test.tsx` holds the markup, the labels, the helpers and the plain file's defaults.
1512
+ the browser's popover box). 22/10/2026: `update` / `setLabels` with the menu open and a row focused (the plain DOM equal to
1513
+ the component's re-render, focus and open state kept); an app's `button { font: inherit; font-size: 16px; padding: 0 }`,
1514
+ `a {}` and `hr {}` before or after the kit, and a page with only this file (+ app-shell.css): 13px rows at the core's
1515
+ padding (4px 6px under a mouse, 12px 16px under a finger), the tick 8px from the name (negative controls: the rows back at
1516
+ `:where` → 16px; without the two-class gap rule, app-shell.css after it → 0px gap);
1517
+ GemFeedback's portal bar with the switcher in `TopbarNav foot`, `foldAt={760}`: one language control at 320–1024, nothing
1518
+ overlapping or cut (negative control: at 320 without the foot the tab menu is squeezed out).
1519
+ `LANGUAGE_SWITCHER_SHOTS=<dir>` writes the screenshots (closed / open, both variants, both themes, vi / en / zh).
1520
+ `language-switcher.test.tsx` holds the markup, the labels, the helpers, the plain file's defaults and the CSS contract (the
1521
+ rows at one class, every token with its fallback).
1491
1522
 
1492
1523
  ## Repeater rows (16/10/2026): `RepeaterList`, `useKeyedRows`
1493
1524
 
@@ -1675,7 +1706,25 @@ import { Avatar, Dialog, Leaderboard, Podium, SearchDialog, ThemeToggle, themeSc
1675
1706
  - `TopbarNav`: section tabs in the topbar after the brand, the active one underlined on the topbar's
1676
1707
  edge. A topbar holding tabs drops the centre search column (`:has(.gb-topnav)`), so the tabs get the
1677
1708
  room. 36px under a mouse, 44px under a finger. Under 600px the tabs fold into one button naming the
1678
- open tab, with the tabs in a list under it (a row that scrolls sideways hid the third tab at 390).
1709
+ open tab, with the tabs in a list under it (a row that scrolls sideways hid the third tab at 390). The open tab's name
1710
+ gives way with an ellipsis before it runs under the topbar's end (22/10/2026: "Changelog" at 320).
1711
+ - `foot` (22/10/2026): the app's own section at the foot of the folded list, under a line; shown only folded. What the
1712
+ topbar's end has no room for once the tabs fold, the language first. Its copy in the end slot wears
1713
+ `gb-topnav-unfolded`, hidden wherever the tabs fold, so the bar has one at every width, one at a time:
1714
+
1715
+ ```tsx
1716
+ const Lang = (p: Partial<LanguageSwitcherProps>) => <LanguageSwitcher value={lang} languages={withHrefs} {...p} />;
1717
+ <div className="gemboss-topbar-start"><BrandLockup name="GemFeedback" href="/" />
1718
+ <TopbarNav as={Link} items={tabs} foldAt={760} foot={<Lang variant="inline" end={false} />} /></div>
1719
+ <div className="gemboss-topbar-center" />
1720
+ <div className="gemboss-topbar-end"><SearchDialog … /><ThemeToggle /><Lang className="gb-topnav-unfolded" /><SignInButton … /></div>
1721
+ ```
1722
+ - `foldAt={760}`: fold over the whole phone topbar (≤760px) instead of under 600px, for a bar whose end holds more
1723
+ (GemFeedback signed out: search, theme, language, Sign in; its three tabs ran under the switcher at 601–687). Measured
1724
+ in Chromium: brand + folded tabs + search + theme + Sign in fit from 320 with the language in the foot, and from 761 the
1725
+ tabs + all four controls.
1726
+ - These three and the ellipsis are `topnav-fold.css`, an entry of their own that `TopbarNav` imports after `portal.css`
1727
+ (which is in the frozen `gemboss-ui.css`). A page that renders TopbarNav's markup without the component links it.
1679
1728
  - `SearchDialog`: the topbar search button opening a 640px popup (Featurebase's, the admin's ⌘K).
1680
1729
  `initial` before typing, `search(q)` while typing (150 ms debounce, latest answer wins); ↑ ↓ Enter
1681
1730
  Esc; ⌘K / Ctrl+K and "/" open it. Results are links; without JavaScript the button goes to
@@ -2456,6 +2505,8 @@ entry, each presentational and server-renderable (no state, no effect):
2456
2505
  | `RangeBar` | a value against a market's spread on one axis: `band` (where half of it sits), `mid`, `you`, `ticks`. `size="sm"` is the shape for a metric tile; `size="lg"` adds the labels. The surface passes the words and the numbers (`labels`, `format`): the bar does no money maths. `rangeBarGeometry` is the pure part, for a plain-HTML page. |
2457
2506
  | `ProgressRing` | "3 of 5" as a ring with the count inside. `progressRingGeometry` is the pure arc. A `max` that is not a positive number is an empty ring, never NaN. |
2458
2507
  | `SpotArt` | six pictures in the brand's gem drawing style (`gem-radar`, `orbit`, `people`, `page`, `plug`, `price`) for the pauses and empty states a text-only screen lacks. Painted by tokens, decorative by default, an image only when given a `title`. |
2508
+ | `QuestionCard` choices made by looking (unreleased) | a choice's `media` is a picture across the top of it (spans and images: the choice is a `<label>`), `layout="tiles"` puts the choices side by side (three on a card, one column on a phone), and a choice's `disabled` shows it greyed with its radio off (an option that failed its checks: seen, not choosable); the confirm stays off while a disabled choice is the value. GemBoss Brand › Logo is the first surface: three AI logos, one confirmed. |
2509
+ | `LogoSheet` (unreleased) | one logo the way it has to be judged before it becomes a store's: the mark on a checkerboard (a white box around it shows), the name lockup on a light header and on a dark one (`lockup`, `lockupDark`), and the favicon at 32 × 32 CSS px beside the name, as a tab shows it. Every picture is a file the surface already has; nothing is drawn or scaled. Spans only, so it can be a `QuestionCard` choice's `media`. |
2459
2510
  | `question-card.css`, `briefing-card.css`, `range-bar.css`, `progress-ring.css`, `spot-art.css` | one entry each, loaded by its component; a plain-HTML surface links `@gemboss/ui/<name>.css` and writes the classes listed at the top of each file |
2460
2511
 
2461
2512
  Three lighter steps of iris came with them, because a wash, a band and an art stroke need more than `--gemboss-accent-soft`:
@@ -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.46.0 · Editor</h1>
11
+ <h1>@gemboss/ui 0.48.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 &quot;@gemboss/ui/editor.css&quot;;
14
14
  import { EditorFrame, EditorTopbar, EditorBack, EditorChatToggle, EditorPaneTabs, EditorSaveState, DeviceToggle } from &quot;@gemboss/ui/editor&quot;;