@gemboss/ui 0.39.0 → 0.41.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 +285 -0
- package/README.md +638 -3
- package/catalog/editor.html +15 -13
- package/catalog/index.html +5235 -1145
- package/gemboss-ui.css +3 -3
- package/package.json +8 -2
- package/parts/components.css +2 -2
- package/parts/core.css +1 -1
- package/parts/shell.css +2 -2
- package/src/assistant/chat-markdown.ts +7 -4
- package/src/assistant.css +1 -1
- package/src/brand-gem.tsx +19 -0
- package/src/brand.tsx +4 -7
- package/src/chat-plain.js +2 -2
- package/src/editor/controls.tsx +50 -14
- package/src/editor/index.ts +3 -3
- package/src/editor/panel.tsx +10 -4
- package/src/editor/repeater.tsx +113 -27
- package/src/editor/swatch.ts +20 -0
- package/src/editor-plain.js +1 -1
- package/src/editor-repeater.css +1 -1
- package/src/editor.css +1 -1
- package/src/gemboss-field.css +1 -1
- package/src/icons.tsx +11 -0
- package/src/index.ts +12 -1
- package/src/reader-dialog.css +1 -0
- package/src/reader-dialog.tsx +26 -0
- package/src/service-switcher.css +1 -1
- package/src/service-switcher.tsx +108 -31
- package/src/shell.css +1 -1
- package/src/site.css +1 -0
- package/src/site.tsx +50 -0
- package/src/team-chat-data.ts +79 -0
- package/src/team-chat-plain.js +1 -0
- package/src/team-chat.css +1 -0
- package/src/team-chat.tsx +825 -0
- package/src/ui.css +1 -1
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
|
+
| 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 |
|
|
62
63
|
| 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 |
|
|
63
64
|
| collections | `@gemboss/ui/collections.css` | `packages/ui/src/collections.css` | tables, lists, grids, boards | 4.6 KB | `IndexTable`, `ResourceList`, `Board`… import it |
|
|
64
65
|
| editor | `@gemboss/ui/editor.css` | `packages/ui/src/editor.css` | the editors | 7.9 KB | the `@gemboss/ui/editor` components import it |
|
|
@@ -70,7 +71,9 @@ its CSS out of the build with it (`sideEffects`). gzip of the minified file, as
|
|
|
70
71
|
| assistant-chat | `@gemboss/ui/assistant-chat.css` | `packages/ui/src/assistant-chat.css` | the chat pieces the package draws (0.29.0): column, starters that wrap, meta, errors, notes, paperclip | 0.9 KB | `AssistantChat` and its pieces import it |
|
|
71
72
|
| minichart | `@gemboss/ui/minichart.css` | `packages/ui/src/minichart.css` | `Sparkline`, `ShareBar` (0.30.0) | 0.5 KB | those components import it |
|
|
72
73
|
| setup-guide | `@gemboss/ui/setup-guide.css` | `packages/ui/src/setup-guide.css` | `SetupGuide` (0.30.0) | 0.9 KB | `SetupGuide` imports it |
|
|
73
|
-
|
|
|
74
|
+
| reader-dialog | `@gemboss/ui/reader-dialog.css` | `packages/ui/src/reader-dialog.css` | `ReaderDialog`: the popup sized for reading a document (0.41.0) | 0.4 KB | `ReaderDialog` imports it |
|
|
75
|
+
| service-switcher | `@gemboss/ui/service-switcher.css` | `packages/ui/src/service-switcher.css` | `ServiceSwitcher` and `Switcher`: the button after the brand and its panel (0.36.0; any list, illustrated tiles 0.40.0) | 2.2 KB | `ServiceSwitcher` imports it, and `BrandLockup` through it (README "ServiceSwitcher") |
|
|
76
|
+
| team-chat | `@gemboss/ui/team-chat.css` | `packages/ui/src/team-chat.css` | the team chat layer (20/10/2026): rail, messages, composer, thread and people panels, the screen's layout | 3.6 KB | `TeamChat` and its pieces import it (with `Avatar`'s portal.css and `Reactions`' reactions.css); a plain-HTML page links it (README "The team chat layer") |
|
|
74
77
|
| 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) |
|
|
75
78
|
|
|
76
79
|
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).
|
|
@@ -639,7 +642,7 @@ import "@gemboss/ui/editor.css"; // only on the screens that show an editor
|
|
|
639
642
|
| Preview | `.gemboss-pv` › `.gemboss-pv-stage` › `.gemboss-pv-frame` (`.desktop` / `.tablet` / `.mobile`, `.gemboss-pv-fit` for scale-to-fit) › `iframe.gemboss-pv-iframe`; `.gemboss-pv-spin`; empty state `.gemboss-pv-empty` (`-art`, `-title`, `-sub`, `-cta`). Device toggle `.gemboss-pv-devices › button.gemboss-pv-device` |
|
|
640
643
|
| Left column | tab row `div.gemboss-ec-tabs[role=tablist] › button[role=tab][aria-selected]` (add `.gemboss-ec-tab` for the 44px pointer floors); Configure pane `.gemboss-ec-dock-body` |
|
|
641
644
|
| Inspector | `.gemboss-ei` (`--docked` inside the Configure pane) › `.gemboss-ei-head` (`-title`, `-x`, `-savedot`), `.gemboss-ei-tabs › .gemboss-ei-tab`, `.gemboss-ei-body`, `.gemboss-ei-foot`. Groups `.gemboss-ei-group › .gemboss-ei-grouphead` (collapsible) and `.gemboss-ei-gh` / `.gemboss-ei-grp`. Field `.gemboss-ei-fld › label + control + .gemboss-ei-hint` |
|
|
642
|
-
| Controls | text / select `.gemboss-field.gemboss-ei-inp` (+ `.gemboss-ei-
|
|
645
|
+
| Controls | text / select `.gemboss-field.gemboss-ei-inp` (+ `.gemboss-ei-mono`), a number with its unit inside `UnitField` (`label.gemboss-field.gemboss-field--group` › `.gemboss-field__bare` + `.gemboss-field__suffix`, 0.40.0; `.gemboss-ei-suffix` is gone), segmented `.gemboss-ei-seg`, colour `.gemboss-ei-color` / `.gemboss-ei-swatches › .gemboss-ei-sw` / `.gemboss-ei-swatch`, range `.gemboss-ei-range` + `.gemboss-ei-rangeval`, spacing box `.gemboss-ei-edgebox › .gemboss-ei-edge`, image `.gemboss-ei-img` (`-thumb`, `-ctl`, `-actions`, `-grid`, `-cell`), repeater `.gemboss-ei-rep-item` (`-head`, `-acts`, `-add`), blocks `.gemboss-ei-block` (`-head`, `-x`), add `.gemboss-ei-addrow` (its buttons are the kit's `.ui-btn` via `ChipButton`, 0.40.0; `.gemboss-ei-addchip` is gone), element tree `.gemboss-ei-tree › .gemboss-ei-tnode` (`-tname`, `-tacts`, `-tadd`), notes `.gemboss-ei-note`, `.gemboss-ei-oos` (`-h`), `.gemboss-ei-empty`, drag `.gemboss-ei-drag` / `.gemboss-ei-dragmask` |
|
|
643
646
|
| Layers | `.gemboss-layers` › `.gemboss-layer-head` + `.gemboss-layers-list` › `.gemboss-layer` (`.is-sel`) › `.gemboss-layer-main` (`.gemboss-layer-type`) + `.gemboss-layer-acts` (`.gemboss-layer-del`) |
|
|
644
647
|
| Settings drawer | `.gemboss-settings` › `.gemboss-picker-head` + `.gemboss-settings-body` › `.gemboss-settings-field`, `-grouphead`, `-gen`, `-note`, `-error`, `-handle`; `.gemboss-settings-foot` (`-save`, `-cancel`); backdrop `.gemboss-picker-backdrop`, empty `.gemboss-picker-empty` |
|
|
645
648
|
| Page / section editor bar | `.gemboss-pageedit` (`-top`, `-title`, `-kind`, `-status`, `-standing`, `-scope`, `-modes`, `-notice`, `-chip`, `-pv`; the left cluster's gap is `--gemboss-tb-gap`, 12px, for a child that hands it back; above 1000px the bar's tracks are `auto fit-content(100%) auto`, so its free space sits beside the centre), `.gemboss-sec-type`, section insert `.gemboss-sec-insert-*`, draft preview `.gemboss-page-draft-stage` / `-status`, `.gemboss-page-section-content`, global styles bundle `.gemboss-bundle-look` |
|
|
@@ -766,7 +769,7 @@ import { ColorControl, EdgeBox, Field, ImageControl, InspectorGroup, PanelAction
|
|
|
766
769
|
|---|---|---|
|
|
767
770
|
| `PanelTabs` | `variant="column"`: `.gemboss-ec-tabs` › `.gemboss-ec-tab` (Configure · Chat, `icon` before the label). `variant="inspector"`: `.gemboss-ei-tabs` › `.gemboss-ei-tab(.on)` | `role=tablist` / `tab`, `aria-selected`, `data-value` (the tab's id, for plain HTML), `aria-controls` from a tab's `controls`. `roving` (default on): only the selected tab is a Tab stop, ←/→ move the selection and the focus, wrapping. The admin's Configure · Chat row passes `roving={false}` (both tabs Tab stops, no arrows: how it always behaved). |
|
|
768
771
|
| `PanelHeader`, `PanelAction` | `header.gemboss-ei-head` › `.gemboss-ei-title` (+ `small` subtitle) · `.gemboss-ei-act` buttons · `.gemboss-ei-x` ✕ | ✕ only with `onClose` (a docked panel has none); `onDragStart` adds `.gemboss-ei-drag` and takes the mouse-down of a floating panel. |
|
|
769
|
-
| `InspectorGroup` | `.gemboss-ei-grp` › `button.gemboss-ei-gh` (
|
|
772
|
+
| `InspectorGroup` | `.gemboss-ei-grp` › `button.gemboss-ei-gh` (13px / 650, the chevron at its end, `note` as `.gemboss-ei-gh-note`) + the body | `aria-expanded`, `data-open`; closed, CSS hides everything after the header (the body stays mounted; an inline `display` on the body's first element would win over that, so give it a bare wrapper). Controlled (`open` + `onOpenChange`, which receives the NEXT state) or not (`defaultOpen`). `attrs`: `data-*` on the group itself, so a page that finds groups by an attribute needs no wrapper (a wrapper drops the divider between groups). `collapsible={false}`: the schema form's static group, `section.gemboss-ei-group` › `h4.gemboss-ei-grouphead` + `hint`. |
|
|
770
773
|
| `Field` | `.gemboss-ei-fld` › `label` (13px / 500; `htmlFor`; `labelValue` as `.gemboss-ei-rangeval` at its right edge) · control · `hint` | No label when `label` is undefined / null. `className` (the admin's `togrow`), `style`. |
|
|
771
774
|
| `Seg` | `.gemboss-ei-seg[role=group]` › `button[aria-pressed][data-value]` (`.on`) | A click hands back the option's value; `label` names the group; `style` on the group. |
|
|
772
775
|
| `ColorControl` | `.gemboss-ei-color` › `input[type=color].gemboss-ei-swatch` + text box | The swatch commits on every change; the text box keeps a draft and commits on blur (every blur) or Enter. Anything but a 6-digit hex disables the swatch, with a title saying why (`swatchState`). |
|
|
@@ -1175,6 +1178,40 @@ The body scrolls under a head that stays, and scrolling past its end does not sc
|
|
|
1175
1178
|
contain`). `packages/ui/src/sheet-browser.test.tsx` measures it in Chromium; its negative control runs the same probe
|
|
1176
1179
|
without `sheet.css` and finds the centred popup. Catalog section "Ngăn trượt cạnh phải".
|
|
1177
1180
|
|
|
1181
|
+
## ReaderDialog (0.41.0): a document read over its list
|
|
1182
|
+
|
|
1183
|
+
Two surfaces drew the same popup with their own CSS the same week, because `Dialog`'s widths (460, `wide` 620) are a
|
|
1184
|
+
form's: GemMedia's admin Docs (gemmedia #76) and partner-radar's Docs (gemlabs #1122) open a long document over the list
|
|
1185
|
+
of documents, nearly the whole window, the whole screen on a phone. Chris 20/10/2026: put it in the kit. `ReaderDialog`
|
|
1186
|
+
is that popup:
|
|
1187
|
+
|
|
1188
|
+
```tsx
|
|
1189
|
+
import { ReaderDialog } from "@gemboss/ui";
|
|
1190
|
+
|
|
1191
|
+
<ReaderDialog open onClose={close} title={doc.title} subtitle={`v${doc.version} · updated ${doc.updated}`}
|
|
1192
|
+
actions={<><CopyLink slug={doc.slug} /><a className="ui-btn ui-btn--sm" href={doc.src} target="_blank">Open in a new tab</a></>}>
|
|
1193
|
+
<iframe className="ui-reader__frame" src={doc.src} title={doc.title} />
|
|
1194
|
+
</ReaderDialog>
|
|
1195
|
+
```
|
|
1196
|
+
|
|
1197
|
+
The body is the document's. Give it one child that fills it: `iframe.ui-reader__frame` for a self-contained HTML
|
|
1198
|
+
document, or a scroller of your own with `ui-reader__scroll` (padded 24/32/48, 16/16/40 on a phone; it scrolls, keeps
|
|
1199
|
+
its overscroll, and takes `tabIndex={-1}` and focus so the arrow keys and Space read the document). Keys pressed inside
|
|
1200
|
+
an iframe do not reach the `<dialog>`: a surface that wants Escape there forwards it (`frame.contentWindow` keydown →
|
|
1201
|
+
`dialog.close()`, same-origin). A route that draws it (Next's intercepting route) leaves `open` true and goes back in
|
|
1202
|
+
`onClose`, as with `Sheet`.
|
|
1203
|
+
|
|
1204
|
+
| | |
|
|
1205
|
+
| --- | --- |
|
|
1206
|
+
| Markup | `<dialog class="ui-modal ui-reader">` with the popup's own `ui-modal__head` (title, `__subtitle`, `__actions`, ✕), `__body`, `__foot` |
|
|
1207
|
+
| Behaviour | It is `Dialog` with `ui-reader` on it (one render tree, one hook, `useModalDialog`): Escape, ✕ and a press that started on the backdrop close it, `onClose` hears each once, the page behind is inert and holds still (`html:has(dialog.ui-modal:modal)`), focus goes back to the opener. On open focus goes to the first control, as `Dialog`'s; move it to your document |
|
|
1208
|
+
| Size | `min(1440px, 94vw)` × `min(92dvh, 1200px)`, centred, from 768px up; the whole screen below that, where the actions take their own line under the title and ✕ stays beside it |
|
|
1209
|
+
| CSS | `reader-dialog.css`, its own entry, loaded by the component. Every selector is `dialog.ui-modal.ui-reader…` ((0,2,1)). It lays itself out (no padding, border-box, a column the document fills), so it works on a surface that loads `ui.css` alone. A plain-HTML surface links `@gemboss/ui/reader-dialog.css` beside `gemboss-ui.css` and opens the markup with `showModal()` |
|
|
1210
|
+
|
|
1211
|
+
`packages/ui/src/reader-dialog-browser.test.tsx` measures it in Chromium (size at four windows and three phones, the
|
|
1212
|
+
iframe flush under the head, the scroller scrolling under a head that stays, the phone's head, the page lock, Escape);
|
|
1213
|
+
its negative control runs without `reader-dialog.css` and finds the 460px popup. Catalog section "Hộp đọc tài liệu".
|
|
1214
|
+
|
|
1178
1215
|
## SettingsDialog (0.35.0): settings as one popup
|
|
1179
1216
|
|
|
1180
1217
|
The admin's Settings (apps/admin `SettingsDialog`): one popup over the app, the sections in a rail on the left with a
|
|
@@ -1228,6 +1265,32 @@ import { AppShell, BrandLockup, ServiceSwitcher } from "@gemboss/ui";
|
|
|
1228
1265
|
**When.** On the topbar of an INTERNAL service, beside its logo. Not on a public surface (GemFeedback's board,
|
|
1229
1266
|
GemCommunity, a storefront): the panel names internal tools and links to their admin pages.
|
|
1230
1267
|
|
|
1268
|
+
**The same panel for any list: `Switcher` (0.40.0).** `ServiceSwitcher` is `Switcher` with the Gem services. A public
|
|
1269
|
+
surface that has its own list to switch between (GemCommunity's groups: Chris 20/10/2026, "kèm hình ảnh minh hoạ, tên và
|
|
1270
|
+
description … như các dịch vụ gemboss") uses `Switcher` with its own items, never the services:
|
|
1271
|
+
|
|
1272
|
+
```tsx
|
|
1273
|
+
import { Switcher } from "@gemboss/ui";
|
|
1274
|
+
|
|
1275
|
+
<Switcher
|
|
1276
|
+
items={[
|
|
1277
|
+
{ id: "gemcommunity", name: "GemCommunity", description: "The GemBoss users' group.", href: "/c/gemcommunity", group: "mine", media: { glyph: "◈" }, tint: "iris" },
|
|
1278
|
+
{ id: "sellers-cn", name: "China Brands Go Global", description: "Chinese brands going global.", href: "/c/sellers-cn/signup", group: "discover", media: { src: "/covers/cn.jpg" }, badge: "Apply" },
|
|
1279
|
+
]}
|
|
1280
|
+
groups={[{ id: "mine", title: "Your groups" }, { id: "discover", title: "Discover" }]}
|
|
1281
|
+
current="gemcommunity"
|
|
1282
|
+
labels={{ trigger: "Switch group", title: "Communities", search: "Find a group" }}
|
|
1283
|
+
foot={<button type="button" className="ui-btn">Create a community</button>}
|
|
1284
|
+
/>
|
|
1285
|
+
```
|
|
1286
|
+
|
|
1287
|
+
| | |
|
|
1288
|
+
| --- | --- |
|
|
1289
|
+
| Items | `id`, `name`, `href` (same tab), `description` (two lines at most), `group` (a `groups` id; unknown or none: a last group without a heading), `tint` (`SERVICE_TINTS`), `badge` (a chip after the name: "Apply"). The picture: `media: { src }` (an image) or `media: { glyph }` (an emoji or two letters, drawn 44px on a panel of the tint) makes the tile a card; without `media`, the service kind: a 40px mark (`icon` or `glyph`). |
|
|
1290
|
+
| A card | `a.gb-svc__tile.gb-svc__tile--media` > `span.gb-svc__media` (16:9 across the tile, an `<img alt="" loading="lazy">` covering it, or `span.gb-svc__media-glyph`), the name (+ `span.gb-svc__badge`), the line. A group with a card lays its grid out as `gb-svc__grid--media`: 208px columns, two a row on a phone. |
|
|
1291
|
+
| Props | `items`, `groups`, `current`, `labels` (as ServiceSwitcher's), `search`, `allHref` (default none) or `foot` (the app's own foot, actions; wins), `triggerIcon` (default `apps`), `className`. Everything else (the dialog, where it opens, search, keys, the phone sheet) is ServiceSwitcher's, because it is the same code. |
|
|
1292
|
+
|
|
1293
|
+
|
|
1231
1294
|
| | |
|
|
1232
1295
|
| --- | --- |
|
|
1233
1296
|
| Props | `current` (a `ServiceId`, required): the service this page belongs to. `services` (default `GEM_SERVICES`; another list keeps the groups' order). `labels`: `trigger` ("Switch service", the button's name and tooltip), `title` ("Gem services"), `close`, `search` ("Find a service"), `noMatch` ("No service matches “{query}”."), `all`. `allHref`: the foot link, default `SERVICES_HEALTH_URL` (GemFactory's `/services`, every service and its health); `null` draws no foot. `search` (default true). `className` (the button). `BrandLockup switcher={…}` takes the same, less `className`. Every prop is plain data, so a server component can pass it (BrandLockup is not a client module; ServiceSwitcher is) |
|
|
@@ -1540,6 +1603,69 @@ import { Avatar, Dialog, Leaderboard, Podium, SearchDialog, ThemeToggle, themeSc
|
|
|
1540
1603
|
(after the name, e.g. a level) and `id` (a `#me` anchor). `title` may be left out on a page whose own
|
|
1541
1604
|
`<h1>` names the board; pass `label` then. Screen-reader text uses `.gb-sr-only`, styled in portal.css.
|
|
1542
1605
|
|
|
1606
|
+
## The public site layer (0.40.0): `SiteHeader`, `SiteBrand`, links, buttons, footer
|
|
1607
|
+
|
|
1608
|
+
**Which layer.** A PUBLIC page (gemboss.ai, the docs, news, a product's home page before sign-in) uses this layer. An APP
|
|
1609
|
+
surface (the admin, a portal behind sign-in, GemFeedback's board) keeps `Topbar` + `BrandLockup`. Chris 20/10/2026: "có gemboss
|
|
1610
|
+
ui thì có gemboss website ui"; measured the same day, the public pages had drifted on the same detail: the logo name 18px on
|
|
1611
|
+
gemboss.ai and the docs, 16px on news, 13.3px on the community home; the bar 65 / 57 / 69 / 56px.
|
|
1612
|
+
|
|
1613
|
+
**Two lockup sizes, one gem** (Chris 20/10/2026): site 18px (`SiteBrand`, `.gb-brand--site`), app 13.3px (`BrandLockup`). The gem
|
|
1614
|
+
is the same 26px-tall drawing (`brand-gem.tsx`). The site name stays on a phone; the app one hides at ≤460px, where an app's bar
|
|
1615
|
+
is full of controls. `SiteBrand` does not go through `BrandLockup`: brand.tsx brings the ServiceSwitcher, whose panel names
|
|
1616
|
+
internal tools and their admin URLs, and a public page must not carry it.
|
|
1617
|
+
|
|
1618
|
+
The values are gemboss.ai's own (`website/assets/styles.css`): the bar 64px with the content in a 1132px column, nav links
|
|
1619
|
+
14px/500, the primary button ink (dark: it inverts with `--gemboss-primary`) 40px and 36px in the bar, footer headings 13px/600
|
|
1620
|
+
and links 14px. Every colour is a `--gemboss-*` token, so `theme-dark.css` applies unchanged.
|
|
1621
|
+
|
|
1622
|
+
```tsx
|
|
1623
|
+
import { SiteBrand, SiteHeader } from "@gemboss/ui"; // brings @gemboss/ui/site.css
|
|
1624
|
+
|
|
1625
|
+
<SiteHeader
|
|
1626
|
+
brand={<SiteBrand name="GemBoss" href="/" />}
|
|
1627
|
+
nav={<><a className="gb-site-link" href="/product" aria-current="page">Product</a><a className="gb-site-link" href="/pricing">Pricing</a></>}
|
|
1628
|
+
actions={<><a className="gb-site-link" href="/login">Sign in</a><a className="gb-site-btn gb-site-btn--primary" href="/start">Start free</a></>} />
|
|
1629
|
+
```
|
|
1630
|
+
|
|
1631
|
+
**Plain HTML** needs only the tokens and `site.css`, NOT the core (which resets the page): site.css repeats the lockup's base
|
|
1632
|
+
rules under `gb-brand--site`, so gemboss.ai and the docs can adopt it beside their own stylesheet (`site-browser.test.tsx`
|
|
1633
|
+
"standalone" holds it). Link `gemboss-tokens.css` (or map the `--gemboss-*` tokens the file reads onto your own palette, which
|
|
1634
|
+
also carries your dark mode), then `site.css`, and write the same markup (the catalog's "Trang công khai" section shows it
|
|
1635
|
+
rendered; copy the gem's `<svg>` from there):
|
|
1636
|
+
|
|
1637
|
+
```html
|
|
1638
|
+
<header class="gb-site-header"><div class="gb-site-header__in">
|
|
1639
|
+
<a class="gb-brand gb-brand--site" href="/" aria-label="GemBoss home"><svg class="gb-brand-gem" …></svg><span class="gb-brand-name">GemBoss</span></a>
|
|
1640
|
+
<nav class="gb-site-nav" aria-label="Main"><a class="gb-site-link" href="/pricing">Pricing</a></nav>
|
|
1641
|
+
<div class="gb-site-actions"><a class="gb-site-link" href="/login">Sign in</a><a class="gb-site-btn gb-site-btn--primary" href="/start">Start free</a></div>
|
|
1642
|
+
</div></header>
|
|
1643
|
+
|
|
1644
|
+
<footer class="gb-site-footer"><div class="gb-site-wrap">
|
|
1645
|
+
<div class="gb-site-footer__grid">
|
|
1646
|
+
<div><!-- brand + a line --></div>
|
|
1647
|
+
<div class="gb-site-footer__col"><h4>Product</h4><a href="/funnels">Adaptive funnels</a></div>
|
|
1648
|
+
</div>
|
|
1649
|
+
<div class="gb-site-footer__bottom"><span>© 2026 GemBoss</span><span><a href="/terms">Terms</a></span></div>
|
|
1650
|
+
</div></footer>
|
|
1651
|
+
```
|
|
1652
|
+
|
|
1653
|
+
| Class | What |
|
|
1654
|
+
|---|---|
|
|
1655
|
+
| `gb-site-header`, `gb-site-header__in` | the sticky bar (64px) and its 1132px row; ≤900px the nav folds away, ≤380px the bar's text actions too |
|
|
1656
|
+
| `gb-brand gb-brand--site` | the lockup at the site size (`SiteBrand` writes it) |
|
|
1657
|
+
| `gb-site-nav`, `gb-site-link` | the bar's links, 14px/500; `aria-current="page"` is 600 |
|
|
1658
|
+
| `gb-site-actions` | the right side of the bar |
|
|
1659
|
+
| `gb-site-btn` + `--primary` / `--secondary` | the site's buttons: 40px, 36px in the bar, 44px under a finger or at ≤768px |
|
|
1660
|
+
| `gb-site-footer`, `__grid`, `__col`, `__bottom`, `gb-site-wrap` | the footer: a brand column + link columns, then one row of small print |
|
|
1661
|
+
|
|
1662
|
+
What it holds, measured in Chromium (`site-browser.test.tsx`): the bar 64px; the site name 18px and the app name 13.3px side by
|
|
1663
|
+
side; at 390px the site name shown, every control 44px, the nav folded; no sideways scroll at 1280, 390 or 320 (at 320 it was 15px
|
|
1664
|
+
too wide with "Sign in" in the bar, hence the ≤380px rule).
|
|
1665
|
+
|
|
1666
|
+
Not in this layer yet: a menu button for the folded nav (each site keeps its own), and the page sections (hero, features, FAQ,
|
|
1667
|
+
pricing, CTA band), which are the next step.
|
|
1668
|
+
|
|
1543
1669
|
## Reactions (0.21.0): one emoji set and one chip for the fleet
|
|
1544
1670
|
|
|
1545
1671
|
Chris 06/10/2026: "reaction các thứ comment nên chuẩn hoá một kiểu, bên gemcare cũng có, nên đưa vào gemboss ui". GemCare's
|
|
@@ -1558,6 +1684,514 @@ message with reactions does not grow by a full line. A plain-HTML surface writes
|
|
|
1558
1684
|
`reactions.css`, and the catalog's "Reactions" section).
|
|
1559
1685
|
|
|
1560
1686
|
|
|
1687
|
+
## The team chat layer (20/10/2026): `TeamChat`, the rail, messages, composer, thread and people
|
|
1688
|
+
|
|
1689
|
+
Chris 20/10/2026: bring GemCare's Slack-like channel chat to GemCommunity, and make the two inherit the same UI so the next
|
|
1690
|
+
upgrade happens in one place. GemCare built the chat (its `public/assets/inbox.{html,css,js}`, origin/main 20/10/2026); its
|
|
1691
|
+
look and behaviour moved here: the channel rail with unread and @mention badges, day separators, the "New messages" line,
|
|
1692
|
+
a person's consecutive messages grouped into one turn, the reply quote, "(edited)", the read receipt, reactions, the thread
|
|
1693
|
+
preview under a message, the hover action bar, the thread panel on the right, the round "jump to the latest". New for the
|
|
1694
|
+
community: the PEOPLE panel, and a layout that gives a small screen its space back.
|
|
1695
|
+
|
|
1696
|
+
**What it needs.** `team-chat.css`, its own entry (the React components import it); `Avatar` from `portal.css` and
|
|
1697
|
+
`Reactions` from `reactions.css` (reused, not redrawn: the components import them too); the kit's `ui-btn` and `ui-check`
|
|
1698
|
+
from the core's `components.css`; `theme-dark.css` for the dark palette. Every colour in `team-chat.css` is a `--gemboss-*`
|
|
1699
|
+
token, so `data-theme="dark"` turns it dark unchanged. It holds no rule on `:root`, `html` or `body`.
|
|
1700
|
+
|
|
1701
|
+
| Component | Draws | Takes |
|
|
1702
|
+
|---|---|---|
|
|
1703
|
+
| `TeamChat` | the screen: `div.gb-tc[data-view][data-panel]` = rail · `section.gb-tc-main` · scrim · panel | `rail`, `children`, `panel`, `panelKind` (`"thread"` / `"people"`), `panelOpen`, `onClosePanel`, `view` (`"list"` / `"conversation"`) |
|
|
1704
|
+
| `ChannelRail`, `ChannelSection`, `ChannelItem` | `nav.gb-tc-rail`: head (title + actions), groups ("Channels", "Direct messages"), rows with a kind glyph (# · lock · person), `@n` and unread badges | `title`, `actions`; `label`; `name`, `kind`, `unread`, `mentions`, `active` (`aria-current`), `online` (a DM), `href` + `linkAs` or `onOpen` |
|
|
1705
|
+
| `ChatHeader` | `header.gb-tc-head`: back (phone only), glyph + name, topic, actions, the members toggle | `name`, `kind`, `topic`, `onBack`, `members: { count, open, onToggle, controls }`, `actions` |
|
|
1706
|
+
| `MessageList`, `DaySeparator`, `NewMessagesLine`, `JumpToLatest` | `div.gb-tc-body` > `div.gb-tc-log[role=log][aria-live=polite]` > `ol.gb-tc-list`; the sticky day pill; the "New messages" line; the round button with a count | `label`, `before` ("Load earlier"), `after` ("Linh is typing…"), `empty`, `jump`, `logRef`, `onScroll`; `label`, `dateTime`; `count`, `visible`, `onJump` |
|
|
1707
|
+
| `Message`, `ReadReceipt` | `li.gb-tc-msg`: avatar or (in a turn) the time on hover, quote, name + time, body, "(edited)", receipt, files, link cards, `Reactions`, thread preview, the action bar | `author`, `avatarSrc` / `avatar`, `time`, `dateTime`, `body`, `grouped`, `edited`, `mentionsMe`, `highlight`, `sending`, `failed`, `status`, `badges`, `replyTo`, `attachments`, `extra`, `reactions`, `onReact`, `thread`, `receipt`, `onReply`, `onOpenThread`, `moreActions`, `editor`, `messageId`; `read`, `of`, `label` |
|
|
1708
|
+
| `Composer` | `form.gb-tc-composer`: "Replying to…", files, mention picker, one box (paperclip · field · send), "Also send to…", error | `onSend`, `value` / `defaultValue` + `onValueChange`, `placeholder`, `label`, `replyingTo` + `onCancelReply`, `files`, `picker`, `onAttach`, `also`, `error`, `busy`, `disabled`, `canSendEmpty`, `inputRef`, `onKeyDown`, `onPaste` |
|
|
1709
|
+
| `ThreadPanel` | `aside.gb-tc-thread`: head + close, notice (`gb-tc-thread__note`, `--error` in the danger colour), the root message, "N replies", the replies flat, a composer | `onClose`, `root`, `replyCount`, `children`, `composer`, `notice`, `noticeTone` (`"error"`), `title`, `subtitle` |
|
|
1710
|
+
| `PeoplePanel`, `PeopleSection`, `PersonRow` | `aside.gb-tc-people[data-collapsed]`: head + collapse button, sections "Online — 3", rows with avatar + presence dot, name, a line under it, a role chip | `collapsed`, `onToggleCollapse`, `actions`, `title`; `label`, `count`; `name`, `src` / `avatar`, `subtitle`, `role`, `presence` (`online` / `away` / `offline`), `href` + `linkAs` or `onSelect` |
|
|
1711
|
+
|
|
1712
|
+
Props only: no fetching, no store, no router. The app owns the data and the actions; the components keep only the screen's
|
|
1713
|
+
own state (the reaction picker a message opened, the composer's text when the app does not hold it, focus). Every word they
|
|
1714
|
+
write themselves is in `TEAM_CHAT_LABELS`; pass `labels` (any subset) to translate. `teamChatRows(messages, { meId,
|
|
1715
|
+
isUnread })` (plain TS, `team-chat-data.ts`) says where the day separators and the "New messages" line go and which
|
|
1716
|
+
messages are `grouped`: same author, same day, under 5 minutes apart, not a reply, and not the first new one (GemCare's
|
|
1717
|
+
rules). `chatDayWord` gives "Today" / "Yesterday"; older days the app formats in its own locale.
|
|
1718
|
+
|
|
1719
|
+
**The layout.** `.gb-tc` is a CSS grid and a size container: it decides by its OWN width (in an app shell the chat is the
|
|
1720
|
+
window minus the shell's rail), with container queries. `TEAM_CHAT_DRAWER_MAX` (1100) and `TEAM_CHAT_PHONE_MAX` (760) are the
|
|
1721
|
+
same numbers for JS.
|
|
1722
|
+
|
|
1723
|
+
| Width of `.gb-tc` | Right panel | Rail / conversation |
|
|
1724
|
+
|---|---|---|
|
|
1725
|
+
| > 1100px | a third column: a thread 380px (`--gb-tc-thread-w`), the people panel 260px (`--gb-tc-people-w`), 56px collapsed | rail 240px (`--gb-tc-rail-w`) + conversation |
|
|
1726
|
+
| ≤ 1100px | a drawer over the conversation from the right (thread 380px, people 300px), with a scrim; a collapsed people panel is a CLOSED drawer | the conversation keeps its whole width |
|
|
1727
|
+
| ≤ 760px | a sheet over the whole chat | `data-view="list"`: the rail alone; `"conversation"`: the conversation alone, with a back button in its head |
|
|
1728
|
+
|
|
1729
|
+
While the panel is a drawer, `TeamChat` moves focus into it on open, makes the rail and the conversation `inert` (Tab stays
|
|
1730
|
+
in the drawer), closes it on Escape or a press on the scrim (`onClosePanel`), and gives focus back to what opened it.
|
|
1731
|
+
Escape closes a thread at any width. The message list is a container too: where IT is narrower than 560px (the thread
|
|
1732
|
+
panel, a docked window) the action bar drops its quick reactions and the rows their side padding.
|
|
1733
|
+
|
|
1734
|
+
**Under a finger** (`pointer: coarse`) and at phone width every control is 44px: rail rows, people rows, the quote, the
|
|
1735
|
+
thread preview, the action bar's buttons, the composer's buttons (the jump button 48px); under a mouse the rail rows and
|
|
1736
|
+
the action bar are 32px, as the admin's density. The composer's field is 16px there (iOS zooms the page under it). With
|
|
1737
|
+
no hover (`hover: none`) the action bar is ONE TAP away: a tap focuses the message row (`tabindex="-1"`) and the bar shows.
|
|
1738
|
+
GemCare's bar was always on under a finger, a row of 44px buttons under every message; this gives that height back to
|
|
1739
|
+
the conversation.
|
|
1740
|
+
|
|
1741
|
+
### Plain HTML: the markup contract
|
|
1742
|
+
|
|
1743
|
+
A surface with no React (GemCare) writes exactly this, links `team-chat.css` beside the core, `portal.css` and
|
|
1744
|
+
`reactions.css`, and does the behaviour itself (below). `team-chat-contract.test.tsx` renders every component with the data
|
|
1745
|
+
of the block named after it and holds the two to the same tree (tags, classes, attributes, text; `<svg>…</svg>` is an
|
|
1746
|
+
icon from the set, `Icon` in React, drawn as in the catalog), so this markup and the components cannot drift. Ids here
|
|
1747
|
+
(`tc-…`) are the app's own; keep the pairs (`aria-labelledby` → `id`, `for` → `id`, `aria-controls` → `id`) pointing at
|
|
1748
|
+
each other.
|
|
1749
|
+
|
|
1750
|
+
The screen. `data-panel` names the panel in the right slot (absent: none); the scrim is drawn only while the panel is
|
|
1751
|
+
OPEN (a thread is open; the people panel is not collapsed).
|
|
1752
|
+
|
|
1753
|
+
<!-- team-chat: screen -->
|
|
1754
|
+
```html
|
|
1755
|
+
<div class="gb-tc" data-view="conversation" data-panel="thread">
|
|
1756
|
+
<nav class="gb-tc-rail" aria-label="Channels">…</nav>
|
|
1757
|
+
<section class="gb-tc-main" aria-label="Conversation">…</section>
|
|
1758
|
+
<div class="gb-tc-scrim" aria-hidden="true">
|
|
1759
|
+
</div>
|
|
1760
|
+
<aside class="gb-tc-thread" aria-label="Thread">…</aside>
|
|
1761
|
+
</div>
|
|
1762
|
+
```
|
|
1763
|
+
|
|
1764
|
+
The rail. The active row has `aria-current` (`"page"` on a link, `"true"` on a button); a row with unread messages adds
|
|
1765
|
+
`gb-tc-channel--unread`; its `aria-label` says the counts (a badge cannot be read out). A DM row may carry a presence dot.
|
|
1766
|
+
|
|
1767
|
+
<!-- team-chat: rail -->
|
|
1768
|
+
```html
|
|
1769
|
+
<nav class="gb-tc-rail" aria-label="Channels">
|
|
1770
|
+
<div class="gb-tc-rail__head">
|
|
1771
|
+
<h2 class="gb-tc-rail__title">Acme team</h2>
|
|
1772
|
+
<div class="gb-tc-rail__actions">
|
|
1773
|
+
<button type="button" class="ui-btn ui-btn--ghost ui-btn--icon" aria-label="New message">
|
|
1774
|
+
<svg>…</svg>
|
|
1775
|
+
</button>
|
|
1776
|
+
</div>
|
|
1777
|
+
</div>
|
|
1778
|
+
<div class="gb-tc-rail__scroll">
|
|
1779
|
+
<div class="gb-tc-rail__section" role="group" aria-labelledby="tc-channels-h">
|
|
1780
|
+
<h3 class="gb-tc-rail__heading" id="tc-channels-h">Channels</h3>
|
|
1781
|
+
<ul class="gb-tc-rail__list">
|
|
1782
|
+
<li>
|
|
1783
|
+
<button type="button" class="gb-tc-channel" aria-current="true" aria-label="general">
|
|
1784
|
+
<span class="gb-tc-channel__glyph" aria-hidden="true">
|
|
1785
|
+
<svg>…</svg>
|
|
1786
|
+
</span>
|
|
1787
|
+
<span class="gb-tc-channel__name">general</span>
|
|
1788
|
+
</button>
|
|
1789
|
+
</li>
|
|
1790
|
+
<li>
|
|
1791
|
+
<button type="button" class="gb-tc-channel gb-tc-channel--unread" aria-label="launch, 2 mentioning you, 5 unread">
|
|
1792
|
+
<span class="gb-tc-channel__glyph" aria-hidden="true">
|
|
1793
|
+
<svg>…</svg>
|
|
1794
|
+
</span>
|
|
1795
|
+
<span class="gb-tc-channel__name">launch</span>
|
|
1796
|
+
<span class="gb-tc-badge gb-tc-badge--mention" aria-hidden="true">@2</span>
|
|
1797
|
+
<span class="gb-tc-badge" aria-hidden="true">5</span>
|
|
1798
|
+
</button>
|
|
1799
|
+
</li>
|
|
1800
|
+
<li>
|
|
1801
|
+
<button type="button" class="gb-tc-channel" aria-label="payroll, private channel">
|
|
1802
|
+
<span class="gb-tc-channel__glyph" aria-hidden="true">
|
|
1803
|
+
<svg>…</svg>
|
|
1804
|
+
</span>
|
|
1805
|
+
<span class="gb-tc-channel__name">payroll</span>
|
|
1806
|
+
</button>
|
|
1807
|
+
</li>
|
|
1808
|
+
</ul>
|
|
1809
|
+
</div>
|
|
1810
|
+
<div class="gb-tc-rail__section" role="group" aria-labelledby="tc-dms-h">
|
|
1811
|
+
<h3 class="gb-tc-rail__heading" id="tc-dms-h">Direct messages</h3>
|
|
1812
|
+
<ul class="gb-tc-rail__list">
|
|
1813
|
+
<li>
|
|
1814
|
+
<a class="gb-tc-channel gb-tc-channel--unread" href="/chat/dm/linh" aria-label="Linh Tran, direct message, 1 unread">
|
|
1815
|
+
<span class="gb-tc-channel__glyph" aria-hidden="true">
|
|
1816
|
+
<svg>…</svg>
|
|
1817
|
+
<span class="gb-tc-presence gb-tc-presence--online">
|
|
1818
|
+
</span>
|
|
1819
|
+
</span>
|
|
1820
|
+
<span class="gb-tc-channel__name">Linh Tran</span>
|
|
1821
|
+
<span class="gb-tc-badge" aria-hidden="true">1</span>
|
|
1822
|
+
</a>
|
|
1823
|
+
</li>
|
|
1824
|
+
</ul>
|
|
1825
|
+
</div>
|
|
1826
|
+
</div>
|
|
1827
|
+
</nav>
|
|
1828
|
+
```
|
|
1829
|
+
|
|
1830
|
+
The conversation's head. The back button shows only at phone width; the members toggle is `aria-expanded` and points at
|
|
1831
|
+
the people panel.
|
|
1832
|
+
|
|
1833
|
+
<!-- team-chat: header -->
|
|
1834
|
+
```html
|
|
1835
|
+
<header class="gb-tc-head">
|
|
1836
|
+
<button type="button" class="ui-btn ui-btn--ghost ui-btn--icon gb-tc-head__back" aria-label="Back to channels">
|
|
1837
|
+
<svg>…</svg>
|
|
1838
|
+
</button>
|
|
1839
|
+
<div class="gb-tc-head__text">
|
|
1840
|
+
<h2 class="gb-tc-head__title">
|
|
1841
|
+
<span class="gb-tc-head__glyph" aria-hidden="true">
|
|
1842
|
+
<svg>…</svg>
|
|
1843
|
+
</span>
|
|
1844
|
+
<span class="gb-tc-head__name">general</span>
|
|
1845
|
+
</h2>
|
|
1846
|
+
<p class="gb-tc-head__sub">Everyone on the team is here.</p>
|
|
1847
|
+
</div>
|
|
1848
|
+
<div class="gb-tc-head__actions">
|
|
1849
|
+
<button type="button" class="ui-btn ui-btn--ghost gb-tc-head__members" aria-expanded="false" aria-controls="tc-people" aria-label="Members, 12">
|
|
1850
|
+
<svg>…</svg>
|
|
1851
|
+
<span class="gb-tc-head__count">12</span>
|
|
1852
|
+
</button>
|
|
1853
|
+
</div>
|
|
1854
|
+
</header>
|
|
1855
|
+
```
|
|
1856
|
+
|
|
1857
|
+
The list: a day, a message that starts a turn, the "New messages" line, and a turn of two (the second `gb-tc-msg--grouped`:
|
|
1858
|
+
the time in the avatar's column, shown on hover, and the author for screen readers). The jump button is `hidden` while the
|
|
1859
|
+
reader is at the bottom.
|
|
1860
|
+
|
|
1861
|
+
<!-- team-chat: list -->
|
|
1862
|
+
```html
|
|
1863
|
+
<div class="gb-tc-body">
|
|
1864
|
+
<div class="gb-tc-log" role="log" aria-live="polite" aria-label="Messages in #general" tabindex="0">
|
|
1865
|
+
<ol class="gb-tc-list">
|
|
1866
|
+
<li class="gb-tc-day">
|
|
1867
|
+
<time class="gb-tc-day__label" datetime="2026-10-20">Today</time>
|
|
1868
|
+
</li>
|
|
1869
|
+
<li class="gb-tc-msg" tabindex="-1">
|
|
1870
|
+
<span class="gb-avatar gb-avatar--32 gb-tc-msg__av" data-tone="4" aria-hidden="true">LT</span>
|
|
1871
|
+
<div class="gb-tc-msg__main">
|
|
1872
|
+
<div class="gb-tc-msg__who">
|
|
1873
|
+
<span class="gb-tc-msg__name">Linh Tran</span>
|
|
1874
|
+
<time class="gb-tc-msg__time" datetime="2026-10-20T09:41:00+07:00">09:41</time>
|
|
1875
|
+
</div>
|
|
1876
|
+
<div class="gb-tc-msg__body">Morning! The launch checklist is in the doc.</div>
|
|
1877
|
+
</div>
|
|
1878
|
+
</li>
|
|
1879
|
+
<li class="gb-tc-new">
|
|
1880
|
+
<span class="gb-tc-new__label" role="separator" aria-label="New messages">New messages</span>
|
|
1881
|
+
</li>
|
|
1882
|
+
<li class="gb-tc-msg" tabindex="-1">
|
|
1883
|
+
<span class="gb-avatar gb-avatar--32 gb-tc-msg__av" data-tone="0" aria-hidden="true">QL</span>
|
|
1884
|
+
<div class="gb-tc-msg__main">
|
|
1885
|
+
<div class="gb-tc-msg__who">
|
|
1886
|
+
<span class="gb-tc-msg__name">Quan Le</span>
|
|
1887
|
+
<time class="gb-tc-msg__time" datetime="2026-10-20T09:44:00+07:00">09:44</time>
|
|
1888
|
+
</div>
|
|
1889
|
+
<div class="gb-tc-msg__body">On it.</div>
|
|
1890
|
+
</div>
|
|
1891
|
+
</li>
|
|
1892
|
+
<li class="gb-tc-msg gb-tc-msg--grouped" tabindex="-1">
|
|
1893
|
+
<time class="gb-tc-msg__hover-time" datetime="2026-10-20T09:45:00+07:00">09:45</time>
|
|
1894
|
+
<div class="gb-tc-msg__main">
|
|
1895
|
+
<span class="gb-tc-sr">Quan Le, 09:45: </span>
|
|
1896
|
+
<div class="gb-tc-msg__body">Pushed the fix for the cart drawer.</div>
|
|
1897
|
+
</div>
|
|
1898
|
+
</li>
|
|
1899
|
+
</ol>
|
|
1900
|
+
</div>
|
|
1901
|
+
<button type="button" class="gb-tc-jump" aria-label="Jump to the latest message, 3 new">
|
|
1902
|
+
<svg>…</svg>
|
|
1903
|
+
<span class="gb-tc-jump__n" aria-hidden="true">3</span>
|
|
1904
|
+
</button>
|
|
1905
|
+
</div>
|
|
1906
|
+
```
|
|
1907
|
+
|
|
1908
|
+
A message with everything: the quote it answers, "(edited)", a read receipt (the viewer's own messages; a ring, a slice
|
|
1909
|
+
per share, filled and ticked when everyone has read it), reactions (the package's `Reactions`; none, no row), the thread
|
|
1910
|
+
preview, the action bar. Modifiers on the `li`: `gb-tc-msg--mention` (calls the viewer: an amber bar on the left),
|
|
1911
|
+
`--highlight` (jumped to), `--sending` (faded, no bar, `p.gb-tc-msg__status` "Sending…"), `--failed` (the status in the danger
|
|
1912
|
+
colour), `--active` (the reaction picker is open: the bar stays), `--editing` (the app's edit box in `div.gb-tc-msg__editor`
|
|
1913
|
+
in place of the body, no bar). Slots: `div.gb-tc-msg__files` (pictures, files), `div.gb-tc-msg__extra` (link cards), the
|
|
1914
|
+
app's own buttons at the end of `div.gb-tc-acts`. "Add reaction" opens the package's picker (`ui-reaction-picker`) in the
|
|
1915
|
+
flow under the body; the quick pair (👍 ✅) is `gb-tc-act--quick`.
|
|
1916
|
+
|
|
1917
|
+
<!-- team-chat: message -->
|
|
1918
|
+
```html
|
|
1919
|
+
<li class="gb-tc-msg" data-msg-id="42" tabindex="-1">
|
|
1920
|
+
<span class="gb-avatar gb-avatar--32 gb-tc-msg__av" data-tone="7" aria-hidden="true">MP</span>
|
|
1921
|
+
<div class="gb-tc-msg__main">
|
|
1922
|
+
<button type="button" class="gb-tc-quote">
|
|
1923
|
+
<span class="gb-tc-quote__who">Linh Tran</span>
|
|
1924
|
+
<span class="gb-tc-quote__text">Can someone own the hero copy?</span>
|
|
1925
|
+
</button>
|
|
1926
|
+
<div class="gb-tc-msg__who">
|
|
1927
|
+
<span class="gb-tc-msg__name">Mai Pham</span>
|
|
1928
|
+
<time class="gb-tc-msg__time" datetime="2026-10-20T10:02:00+07:00">10:02</time>
|
|
1929
|
+
</div>
|
|
1930
|
+
<div class="gb-tc-msg__body">Mine, shipping it at noon.<span class="gb-tc-msg__edited">(edited)</span>
|
|
1931
|
+
<span class="gb-tc-receipt gb-tc-receipt--all" role="img" aria-label="Read by everyone (3)" title="Read by everyone (3)">
|
|
1932
|
+
<svg>…</svg>
|
|
1933
|
+
</span>
|
|
1934
|
+
</div>
|
|
1935
|
+
<div class="ui-reactions">
|
|
1936
|
+
<div class="ui-reactions__row">
|
|
1937
|
+
<button type="button" class="ui-reaction" aria-pressed="false" title="Linh, Quan" aria-label="👍, Linh, Quan. Add yours">
|
|
1938
|
+
<span class="ui-reaction__emoji">👍</span>
|
|
1939
|
+
<span class="ui-reaction__names">Linh, Quan</span>
|
|
1940
|
+
</button>
|
|
1941
|
+
<button type="button" class="ui-reaction-add" title="Add reaction" aria-label="Add a reaction to the message from Mai Pham" aria-expanded="false">
|
|
1942
|
+
<svg>…</svg>
|
|
1943
|
+
</button>
|
|
1944
|
+
</div>
|
|
1945
|
+
</div>
|
|
1946
|
+
<button type="button" class="gb-tc-threadprev" aria-label="Open the thread, 3 replies, Last reply 2 hours ago">
|
|
1947
|
+
<span class="gb-tc-threadprev__faces" aria-hidden="true">
|
|
1948
|
+
<span class="gb-avatar gb-avatar--24" data-tone="4" aria-hidden="true">LT</span>
|
|
1949
|
+
<span class="gb-avatar gb-avatar--24" data-tone="0" aria-hidden="true">QL</span>
|
|
1950
|
+
</span>
|
|
1951
|
+
<span class="gb-tc-threadprev__count">3 replies</span>
|
|
1952
|
+
<span class="gb-tc-threadprev__last">Last reply 2 hours ago</span>
|
|
1953
|
+
</button>
|
|
1954
|
+
</div>
|
|
1955
|
+
<div class="gb-tc-acts" role="toolbar" aria-label="Actions for the message from Mai Pham">
|
|
1956
|
+
<button type="button" class="gb-tc-act gb-tc-act--quick" aria-pressed="false" aria-label="Add 👍 on the message from Mai Pham">👍</button>
|
|
1957
|
+
<button type="button" class="gb-tc-act gb-tc-act--quick" aria-pressed="false" aria-label="Add ✅ on the message from Mai Pham">✅</button>
|
|
1958
|
+
<button type="button" class="gb-tc-act" aria-expanded="false" aria-label="More reactions for the message from Mai Pham" title="Add reaction">
|
|
1959
|
+
<svg>…</svg>
|
|
1960
|
+
</button>
|
|
1961
|
+
<button type="button" class="gb-tc-act" aria-label="Reply to the message from Mai Pham" title="Reply">
|
|
1962
|
+
<svg>…</svg>
|
|
1963
|
+
</button>
|
|
1964
|
+
<button type="button" class="gb-tc-act" aria-label="Reply in thread to the message from Mai Pham" title="Reply in thread">
|
|
1965
|
+
<svg>…</svg>
|
|
1966
|
+
</button>
|
|
1967
|
+
</div>
|
|
1968
|
+
</li>
|
|
1969
|
+
```
|
|
1970
|
+
|
|
1971
|
+
The composer. "Replying to…" sits right above the box; `div.gb-tc-composer__files` and `div.gb-tc-composer__picker` hold
|
|
1972
|
+
the app's files and @mention list; Send is `disabled` while there is nothing to send.
|
|
1973
|
+
|
|
1974
|
+
<!-- team-chat: composer -->
|
|
1975
|
+
```html
|
|
1976
|
+
<form class="gb-tc-composer">
|
|
1977
|
+
<div class="gb-tc-replying">
|
|
1978
|
+
<span class="gb-tc-replying__text">Replying to Linh Tran: Can someone own the hero copy?</span>
|
|
1979
|
+
<button type="button" class="ui-btn ui-btn--ghost ui-btn--icon" aria-label="Stop replying">
|
|
1980
|
+
<svg>…</svg>
|
|
1981
|
+
</button>
|
|
1982
|
+
</div>
|
|
1983
|
+
<div class="gb-tc-composer__files">…</div>
|
|
1984
|
+
<div class="gb-tc-composer__picker">…</div>
|
|
1985
|
+
<div class="gb-tc-composer__box">
|
|
1986
|
+
<button type="button" class="ui-btn ui-btn--ghost ui-btn--icon gb-tc-composer__attach" aria-label="Attach a file">
|
|
1987
|
+
<svg>…</svg>
|
|
1988
|
+
</button>
|
|
1989
|
+
<label class="gb-tc-sr" for="tc-input">Message</label>
|
|
1990
|
+
<textarea id="tc-input" class="gb-tc-composer__input" rows="1" maxlength="4000" placeholder="Message #general">
|
|
1991
|
+
</textarea>
|
|
1992
|
+
<button type="submit" class="ui-btn ui-btn--primary ui-btn--icon gb-tc-composer__send" aria-label="Send" disabled="">
|
|
1993
|
+
<svg>…</svg>
|
|
1994
|
+
</button>
|
|
1995
|
+
</div>
|
|
1996
|
+
</form>
|
|
1997
|
+
```
|
|
1998
|
+
|
|
1999
|
+
The thread panel: the root, the "N replies" rule, the replies flat, a composer with "Also send to the channel".
|
|
2000
|
+
|
|
2001
|
+
<!-- team-chat: thread -->
|
|
2002
|
+
```html
|
|
2003
|
+
<aside id="tc-thread" class="gb-tc-thread" aria-labelledby="tc-thread-title" tabindex="-1">
|
|
2004
|
+
<header class="gb-tc-panel-head">
|
|
2005
|
+
<div class="gb-tc-panel-head__text">
|
|
2006
|
+
<h2 class="gb-tc-panel-head__title" id="tc-thread-title">Thread</h2>
|
|
2007
|
+
<p class="gb-tc-panel-head__sub">#general</p>
|
|
2008
|
+
</div>
|
|
2009
|
+
<button type="button" class="ui-btn ui-btn--ghost ui-btn--icon gb-tc-panel-head__close" aria-label="Close thread">
|
|
2010
|
+
<svg>…</svg>
|
|
2011
|
+
</button>
|
|
2012
|
+
</header>
|
|
2013
|
+
<div class="gb-tc-body">
|
|
2014
|
+
<div class="gb-tc-log" role="log" aria-live="polite" aria-labelledby="tc-thread-title" tabindex="0">
|
|
2015
|
+
<ol class="gb-tc-list">
|
|
2016
|
+
<li class="gb-tc-msg" tabindex="-1">
|
|
2017
|
+
<span class="gb-avatar gb-avatar--32 gb-tc-msg__av" data-tone="4" aria-hidden="true">LT</span>
|
|
2018
|
+
<div class="gb-tc-msg__main">
|
|
2019
|
+
<div class="gb-tc-msg__who">
|
|
2020
|
+
<span class="gb-tc-msg__name">Linh Tran</span>
|
|
2021
|
+
<time class="gb-tc-msg__time" datetime="2026-10-20T09:41:00+07:00">09:41</time>
|
|
2022
|
+
</div>
|
|
2023
|
+
<div class="gb-tc-msg__body">Can someone own the hero copy?</div>
|
|
2024
|
+
</div>
|
|
2025
|
+
</li>
|
|
2026
|
+
<li class="gb-tc-thread__count">
|
|
2027
|
+
<span>1 reply</span>
|
|
2028
|
+
</li>
|
|
2029
|
+
<li class="gb-tc-msg" tabindex="-1">
|
|
2030
|
+
<span class="gb-avatar gb-avatar--32 gb-tc-msg__av" data-tone="7" aria-hidden="true">MP</span>
|
|
2031
|
+
<div class="gb-tc-msg__main">
|
|
2032
|
+
<div class="gb-tc-msg__who">
|
|
2033
|
+
<span class="gb-tc-msg__name">Mai Pham</span>
|
|
2034
|
+
<time class="gb-tc-msg__time" datetime="2026-10-20T10:02:00+07:00">10:02</time>
|
|
2035
|
+
</div>
|
|
2036
|
+
<div class="gb-tc-msg__body">Mine, shipping it at noon.</div>
|
|
2037
|
+
</div>
|
|
2038
|
+
</li>
|
|
2039
|
+
</ol>
|
|
2040
|
+
</div>
|
|
2041
|
+
</div>
|
|
2042
|
+
<form class="gb-tc-composer">
|
|
2043
|
+
<div class="gb-tc-composer__box">
|
|
2044
|
+
<label class="gb-tc-sr" for="tc-thread-input">Reply to thread</label>
|
|
2045
|
+
<textarea id="tc-thread-input" class="gb-tc-composer__input" rows="1" maxlength="4000" placeholder="Reply…">
|
|
2046
|
+
</textarea>
|
|
2047
|
+
<button type="submit" class="ui-btn ui-btn--primary ui-btn--icon gb-tc-composer__send" aria-label="Send" disabled="">
|
|
2048
|
+
<svg>…</svg>
|
|
2049
|
+
</button>
|
|
2050
|
+
</div>
|
|
2051
|
+
<label class="ui-check gb-tc-composer__also">
|
|
2052
|
+
<input type="checkbox"/>
|
|
2053
|
+
<span>Also send to #general</span>
|
|
2054
|
+
</label>
|
|
2055
|
+
</form>
|
|
2056
|
+
</aside>
|
|
2057
|
+
```
|
|
2058
|
+
|
|
2059
|
+
The people panel. Collapsed: `data-collapsed` on the `aside`, the toggle `aria-expanded="false"` and labelled "Show
|
|
2060
|
+
members", no `gb-tc-panel-head__actions`; the names stay in the accessibility tree (clipped, not removed). A row is a link,
|
|
2061
|
+
a button, or a `div` when it does nothing; `gb-tc-person--offline` fades it. On its own (outside `.gb-tc`, e.g. an app
|
|
2062
|
+
shell's right column) it fills its column's height.
|
|
2063
|
+
|
|
2064
|
+
<!-- team-chat: people -->
|
|
2065
|
+
```html
|
|
2066
|
+
<aside id="tc-people" class="gb-tc-people" aria-labelledby="tc-people-title" tabindex="-1">
|
|
2067
|
+
<header class="gb-tc-panel-head">
|
|
2068
|
+
<div class="gb-tc-panel-head__text">
|
|
2069
|
+
<h2 class="gb-tc-panel-head__title" id="tc-people-title">Members</h2>
|
|
2070
|
+
</div>
|
|
2071
|
+
<button type="button" class="ui-btn ui-btn--ghost ui-btn--icon gb-tc-people__toggle" aria-expanded="true" aria-controls="tc-people-body" aria-label="Hide members" title="Hide members">
|
|
2072
|
+
<svg>…</svg>
|
|
2073
|
+
</button>
|
|
2074
|
+
</header>
|
|
2075
|
+
<div class="gb-tc-people__body" id="tc-people-body">
|
|
2076
|
+
<div class="gb-tc-people__section" role="group" aria-labelledby="tc-online-h">
|
|
2077
|
+
<h3 class="gb-tc-people__heading" id="tc-online-h">Online — 2</h3>
|
|
2078
|
+
<ul class="gb-tc-people__list">
|
|
2079
|
+
<li>
|
|
2080
|
+
<button type="button" class="gb-tc-person" title="Linh Tran">
|
|
2081
|
+
<span class="gb-tc-person__av">
|
|
2082
|
+
<span class="gb-avatar gb-avatar--32" data-tone="4" aria-hidden="true">LT</span>
|
|
2083
|
+
<span class="gb-tc-presence gb-tc-presence--online">
|
|
2084
|
+
<span class="gb-tc-sr">Online</span>
|
|
2085
|
+
</span>
|
|
2086
|
+
</span>
|
|
2087
|
+
<span class="gb-tc-person__text">
|
|
2088
|
+
<span class="gb-tc-person__name">Linh Tran</span>
|
|
2089
|
+
<span class="gb-tc-person__sub">Support lead</span>
|
|
2090
|
+
</span>
|
|
2091
|
+
<span class="gb-tc-person__role">Admin</span>
|
|
2092
|
+
</button>
|
|
2093
|
+
</li>
|
|
2094
|
+
<li>
|
|
2095
|
+
<a class="gb-tc-person" href="/u/quan" title="Quan Le">
|
|
2096
|
+
<span class="gb-tc-person__av">
|
|
2097
|
+
<span class="gb-avatar gb-avatar--32" data-tone="0" aria-hidden="true">QL</span>
|
|
2098
|
+
<span class="gb-tc-presence gb-tc-presence--away">
|
|
2099
|
+
<span class="gb-tc-sr">Away</span>
|
|
2100
|
+
</span>
|
|
2101
|
+
</span>
|
|
2102
|
+
<span class="gb-tc-person__text">
|
|
2103
|
+
<span class="gb-tc-person__name">Quan Le</span>
|
|
2104
|
+
<span class="gb-tc-person__sub">In a meeting</span>
|
|
2105
|
+
</span>
|
|
2106
|
+
</a>
|
|
2107
|
+
</li>
|
|
2108
|
+
</ul>
|
|
2109
|
+
</div>
|
|
2110
|
+
<div class="gb-tc-people__section" role="group" aria-labelledby="tc-members-h">
|
|
2111
|
+
<h3 class="gb-tc-people__heading" id="tc-members-h">Members — 1</h3>
|
|
2112
|
+
<ul class="gb-tc-people__list">
|
|
2113
|
+
<li>
|
|
2114
|
+
<div class="gb-tc-person gb-tc-person--offline" title="Bao Ngo">
|
|
2115
|
+
<span class="gb-tc-person__av">
|
|
2116
|
+
<span class="gb-avatar gb-avatar--32" data-tone="6" aria-hidden="true">BN</span>
|
|
2117
|
+
<span class="gb-tc-presence gb-tc-presence--offline">
|
|
2118
|
+
<span class="gb-tc-sr">Offline</span>
|
|
2119
|
+
</span>
|
|
2120
|
+
</span>
|
|
2121
|
+
<span class="gb-tc-person__text">
|
|
2122
|
+
<span class="gb-tc-person__name">Bao Ngo</span>
|
|
2123
|
+
</span>
|
|
2124
|
+
</div>
|
|
2125
|
+
</li>
|
|
2126
|
+
</ul>
|
|
2127
|
+
</div>
|
|
2128
|
+
</div>
|
|
2129
|
+
</aside>
|
|
2130
|
+
```
|
|
2131
|
+
|
|
2132
|
+
**What a plain-HTML surface does itself** (the React components do it): Enter sends and Shift+Enter is a new line, but not
|
|
2133
|
+
during an IME composition (`isComposing`, keyCode 229); the field grows with its text (`field-sizing: content`; where a
|
|
2134
|
+
browser lacks it, set `height` from `scrollHeight` through CSSOM, which a `style-src 'self'` CSP allows); Escape or the scrim
|
|
2135
|
+
closes a drawer, focus moves into it and back, the rail and the conversation are `inert` meanwhile; the reaction picker's
|
|
2136
|
+
open state (`gb-tc-msg--active`, `aria-expanded` on its button); arrow keys along `div.gb-tc-acts[role=toolbar]`; when to
|
|
2137
|
+
show `.gb-tc-jump` and its count.
|
|
2138
|
+
|
|
2139
|
+
### Plain HTML: `gembossUI.teamChat` (20/10/2026)
|
|
2140
|
+
|
|
2141
|
+
So a plain page does not write the markup above by hand. `team-chat-plain.js` builds the rows from data and RETURNS
|
|
2142
|
+
elements whose markup is the component's for the same props: tags, classes, every attribute, the text, the icons' SVG.
|
|
2143
|
+
`team-chat-plain-browser.test.ts` draws every piece in every state both ways in Chromium (the contract's data,
|
|
2144
|
+
`team-chat.test.fixtures.tsx`, and the states beyond it) and holds them to one tree, so the markup is written once, in
|
|
2145
|
+
`team-chat.tsx`, and a plain page follows a change to it with a version bump. A classic script, no imports, inlinable:
|
|
2146
|
+
after `kit-plain.js` when the page has it (it adds `gembossUI.teamChat`; loaded first it says so in the console).
|
|
2147
|
+
|
|
2148
|
+
| Builder | Draws what this draws |
|
|
2149
|
+
|---|---|
|
|
2150
|
+
| `message(props)` | `Message`, every prop by the same name; plus `picking` / `reactionsPicking` (a picker drawn open) and `onPickingChange(which, open)` (`"bar"` or `"reactions"`) for a page that redraws its whole list |
|
|
2151
|
+
| `daySeparator(label, dateTime)`, `newMessagesLine(labels)` | `DaySeparator`, `NewMessagesLine` |
|
|
2152
|
+
| `jumpToLatest({ count, visible, onJump, labels })`, `updateJump(button, { count, visible, labels })` | `JumpToLatest`; `updateJump` redraws the same button (a scroll handler keeps its focus) |
|
|
2153
|
+
| `readReceipt({ read, of, label })` | `ReadReceipt` (`null` when `of` ≤ 0) |
|
|
2154
|
+
| `channelSection({ label, id, items })`, `channelItem(props)` | `ChannelSection`, `ChannelItem` (`href`: a link, else a button); `items` are rows or their props |
|
|
2155
|
+
| `peopleSection({ label, count, id, items })`, `personRow(props)` | `PeopleSection`, `PersonRow` (a link, a button with `onSelect`, else a `div`) |
|
|
2156
|
+
| `reactions(props)`, `reactionPicker(props)` | `Reactions` (plus `open` / `onOpenChange`), `ReactionPicker` |
|
|
2157
|
+
| `avatar({ name, src, size, className })`, `icon(name, size)` | `Avatar` (portal.tsx's initials and tone), `Icon` (the icons the builders draw, and `more`) |
|
|
2158
|
+
| `rows(messages, opts)`, `dayKey(at)`, `dayWord(at, now)` | `teamChatRows`, `chatDayKey`, `chatDayWord` |
|
|
2159
|
+
| `LABELS` | `TEAM_CHAT_LABELS`; `labels` (any subset) translates, functions included |
|
|
2160
|
+
|
|
2161
|
+
```html
|
|
2162
|
+
<link rel="stylesheet" href="/ui/portal.css"><link rel="stylesheet" href="/ui/reactions.css"><link rel="stylesheet" href="/ui/team-chat.css">
|
|
2163
|
+
<script src="/ui/kit-plain.js"></script><script src="/ui/team-chat-plain.js"></script>
|
|
2164
|
+
```
|
|
2165
|
+
|
|
2166
|
+
```js
|
|
2167
|
+
const tc = gembossUI.teamChat;
|
|
2168
|
+
list.replaceChildren(...tc.rows(messages, { meId: me.id, isUnread: (m) => m.id > readUpTo }).map((r) =>
|
|
2169
|
+
r.kind === "day" ? tc.daySeparator(dayLabel(r.date), isoDay(r.date))
|
|
2170
|
+
: r.kind === "new" ? tc.newMessagesLine()
|
|
2171
|
+
: tc.message({ author: r.message.authorName, time: hhmm(r.message.at), dateTime: r.message.at, body: r.message.text,
|
|
2172
|
+
grouped: r.grouped, messageId: r.message.id, reactions: r.message.reactions, edited: Boolean(r.message.editedAt),
|
|
2173
|
+
receipt: tc.readReceipt({ read: 2, of: 3, label: "Read by Linh, Quan. Not yet: Bo" }),
|
|
2174
|
+
onReact: (emoji) => react(r.message.id, emoji), onReply: () => startReply(r.message), onOpenThread: (e) => openThread(r.message.id, e.currentTarget),
|
|
2175
|
+
moreActions: myMoreButton(r.message), picking: view.picking === r.message.id,
|
|
2176
|
+
onPickingChange: (which, open) => { if (which === "bar") view.picking = open ? r.message.id : null; } })));
|
|
2177
|
+
```
|
|
2178
|
+
|
|
2179
|
+
Strings are TEXT (a body `"<img onerror=…>"` shows those characters); a slot that takes a node (`avatar`, `badges`,
|
|
2180
|
+
`attachments`, `extra`, `receipt`, `moreActions`, `editor`, a rich `time` or `body`) takes a DOM node as given. Callbacks are
|
|
2181
|
+
click listeners and get what React's `onClick` gets (the event; `onReact` the emoji). The behaviour is the components': the
|
|
2182
|
+
action bar is a toolbar (arrows, Home / End), "More reactions" opens the package's picker under the body and the reaction
|
|
2183
|
+
row's "Add reaction" one under the row (search, this browser's "Frequently used" under the React picker's own storage key,
|
|
2184
|
+
arrows, Enter picks the first match, Escape closes and gives focus back), a chip's click remembers its emoji. A row drawn
|
|
2185
|
+
with `picking` takes no focus: after a redraw the page puts focus back, as it does for anything it redraws.
|
|
2186
|
+
|
|
2187
|
+
Not built here: the screen's shell (`TeamChat`, `ChannelRail`, `ChatHeader`, `MessageList`, `Composer`, `ThreadPanel`,
|
|
2188
|
+
`PeoplePanel`) is markup a page writes once (the blocks above), with the behaviour listed just above.
|
|
2189
|
+
|
|
2190
|
+
The script carries two kinds of copy, both held by `team-chat-plain-data.test.ts`: the icons' drawing (icons.tsx) and the
|
|
2191
|
+
emoji set (reactions-data.ts) as a generated block (`UPDATE_PLAIN_TEAM_CHAT=1` on that test writes it), and the labels, the
|
|
2192
|
+
avatar's initials and tone, the reaction helpers and `teamChatRows` in ES5, called with the TypeScript's inputs and held to
|
|
2193
|
+
its answers.
|
|
2194
|
+
|
|
1561
2195
|
## Action card (0.28.0): an assistant proposes, a person applies
|
|
1562
2196
|
|
|
1563
2197
|
Chris 08/10/2026: the Feedback Assistant lists likely duplicates and merges them, and carries out status, owner, label
|
|
@@ -1862,6 +2496,7 @@ something the base cannot express, add a token or a prop here, and every surface
|
|
|
1862
2496
|
| `packages/ui/src/editor-plain.js` | 0.14.0: `gembossUI.initEditorFrame()`, the frame's behaviour for plain HTML; 0.17.0: `gembossUI.initEditorPanel()`, the settings panel's. Load after `kit-plain.js`. |
|
|
1863
2497
|
| `packages/ui/src/editor-layers-plain.js` | 0.22.0: `gembossUI.initEditorLayers()`, the layers', the outline's and the drawers' behaviour for plain HTML. Load after `kit-plain.js`. |
|
|
1864
2498
|
| `packages/ui/src/service-switcher-plain.js` | 0.36.0: `gembossUI.initServiceSwitcher()`, the ServiceSwitcher drawn and driven for plain HTML (`@gemboss/ui/service-switcher-plain.js`). After `kit-plain.js` when the page has it; works alone. See "ServiceSwitcher". |
|
|
2499
|
+
| `packages/ui/src/team-chat-plain.js` | 20/10/2026: `gembossUI.teamChat`, builders that return the team chat's rows (messages, day separators, "New messages", jump, receipt, rail rows, people rows, reactions and the picker) with the components' markup, for plain HTML (`@gemboss/ui/team-chat-plain.js`). After `kit-plain.js` when the page has it; works alone. See "The team chat layer". |
|
|
1865
2500
|
| `catalog/editor.html` | 0.14.0: the frame rendered from the components and running (`packages/ui/scripts/build-catalog-editor.mjs`), linked from `catalog/index.html`. |
|
|
1866
2501
|
| `packages/ui/src/editor.css` | 0.9.0: the editors (frame, top bar, preview, Configure dock, inspector and controls, Layers, settings drawer, page picker, menus). A separate entry, NOT inside `gemboss-ui.css`; the admin imports it from the editor components, never from a layout. See "Editor CSS" above. |
|
|
1867
2502
|
| `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. |
|