@gemboss/ui 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -301,6 +301,8 @@ const open = dock.presentation !== "hidden";
301
301
  - `placement="topbar"`: a 44×44 icon button, left of the account chip, ≤760px only. The label stays the accessible name.
302
302
  - No provider and no state: pass `hidden` while your assistant is open.
303
303
  - The shortcut is a hint only: the surface owns the key binding.
304
+ - The phone topbar (≤760px) gives in this order when it is short: the search (to a 96px floor), then the brand name
305
+ (ellipsis). The actions never shrink, so the account chip stays on screen (0.6.1; `topbar.test.tsx`).
304
306
  - 0.6.0, not a patch: an existing `<AssistantLauncher>` call changes meaning (no longer fixed; the default copy is the
305
307
  rail one, hidden on phones). A surface on `^0.5.x` must move to `^0.6.0` in the same PR that places the two copies.
306
308
 
@@ -27,7 +27,7 @@ body{margin:0;background:var(--gemboss-app-bg,#f1f1f1);color:var(--gemboss-text)
27
27
  @media (max-width:760px){.cat{grid-template-columns:1fr;padding:16px}.cat-nav{position:static;flex-direction:row;flex-wrap:wrap}}
28
28
  </style></head><body>
29
29
  <div class="cat"><nav class="cat-nav" aria-label="Mục"><a href="#tokens">Token</a><a href="#buttons">Nút</a><a href="#fields">Ô nhập</a><a href="#choice">Tab, nhóm nút, thẻ</a><a href="#surfaces">Thẻ, số liệu, bảng, trạng thái trống</a><a href="#report">Báo cáo</a><a href="#resizer">Thanh kéo giữa hai cột</a><a href="#modal">Hộp thoại, toast, menu (JS thuần)</a><a href="#shell">Khung app</a><a href="#auth">Đăng nhập</a><a href="#chat">Thân chat</a><a href="#assistant">Trợ lý: cột phiên</a><a href="#icons">Icon</a></nav>
30
- <main><header class="cat-head"><h1>@gemboss/ui 0.6.0</h1><p>Ngôn ngữ thiết kế chung của các công cụ GemBoss. Markup dưới mỗi ví dụ là đúng thứ package render ra; bề mặt không dùng React (HTML tĩnh, Python) chép markup này và nạp <code>gemboss-ui.css</code>. Dựng từ gemboss <code>47291357064c</code>.</p></header>
30
+ <main><header class="cat-head"><h1>@gemboss/ui 0.6.2</h1><p>Ngôn ngữ thiết kế chung của các công cụ GemBoss. Markup dưới mỗi ví dụ là đúng thứ package render ra; bề mặt không dùng React (HTML tĩnh, Python) chép markup này và nạp <code>gemboss-ui.css</code>. Dựng từ gemboss <code>4397bdb795d6</code>.</p></header>
31
31
  <section id="tokens"><h2>Token</h2><p>Đổi giao diện một bề mặt bằng cách đặt lại token, không viết đè class. Tên và giá trị đọc thẳng từ gemboss-ui.css.</p><div class="cat-swatches"><div class="cat-swatch"><i style="background:var(--gemboss-primary)"></i><code>--gemboss-primary</code><span>#1b1c1e</span></div><div class="cat-swatch"><i style="background:var(--gemboss-primary-dark)"></i><code>--gemboss-primary-dark</code><span>#000000</span></div><div class="cat-swatch"><i style="background:var(--gemboss-primary-soft)"></i><code>--gemboss-primary-soft</code><span>#f2f3f5</span></div><div class="cat-swatch"><i style="background:var(--gemboss-accent)"></i><code>--gemboss-accent</code><span>#5661f5</span></div><div class="cat-swatch"><i style="background:var(--gemboss-accent-strong)"></i><code>--gemboss-accent-strong</code><span>#3a44de</span></div><div class="cat-swatch"><i style="background:var(--gemboss-accent-soft)"></i><code>--gemboss-accent-soft</code><span>#eef0fe</span></div><div class="cat-swatch"><i style="background:var(--gemboss-text-strong)"></i><code>--gemboss-text-strong</code><span>#1b1c1e</span></div><div class="cat-swatch"><i style="background:var(--gemboss-text)"></i><code>--gemboss-text</code><span>#1b1c1e</span></div><div class="cat-swatch"><i style="background:var(--gemboss-text-muted)"></i><code>--gemboss-text-muted</code><span>#686b71</span></div><div class="cat-swatch"><i style="background:var(--gemboss-surface)"></i><code>--gemboss-surface</code><span>#ffffff</span></div><div class="cat-swatch"><i style="background:var(--gemboss-canvas)"></i><code>--gemboss-canvas</code><span>#ffffff</span></div><div class="cat-swatch"><i style="background:var(--gemboss-preview-canvas)"></i><code>--gemboss-preview-canvas</code><span>#eceef1</span></div><div class="cat-swatch"><i style="background:var(--gemboss-rail-bg)"></i><code>--gemboss-rail-bg</code><span>#ffffff</span></div><div class="cat-swatch"><i style="background:var(--gemboss-panel-bg)"></i><code>--gemboss-panel-bg</code><span>#ffffff</span></div><div class="cat-swatch"><i style="background:var(--gemboss-divider)"></i><code>--gemboss-divider</code><span>#e5e7ea</span></div><div class="cat-swatch"><i style="background:var(--gemboss-divider-soft)"></i><code>--gemboss-divider-soft</code><span>#edeff2</span></div><div class="cat-swatch"><i style="background:var(--gemboss-hover)"></i><code>--gemboss-hover</code><span>#eceef2</span></div><div class="cat-swatch"><i style="background:var(--gemboss-active-bg)"></i><code>--gemboss-active-bg</code><span>#f0f1f4</span></div><div class="cat-swatch"><i style="background:var(--gemboss-active-fg)"></i><code>--gemboss-active-fg</code><span>#1b1c1e</span></div><div class="cat-swatch"><i style="background:var(--gemboss-danger)"></i><code>--gemboss-danger</code><span>#d72c0d</span></div><div class="cat-swatch"><i style="background:var(--gemboss-danger-strong)"></i><code>--gemboss-danger-strong</code><span>#b42318</span></div><div class="cat-swatch"><i style="background:var(--gemboss-danger-soft)"></i><code>--gemboss-danger-soft</code><span>#fff0e9</span></div><div class="cat-swatch"><i style="background:var(--gemboss-danger-border)"></i><code>--gemboss-danger-border</code><span>#f5c9bc</span></div><div class="cat-swatch"><i style="background:var(--gemboss-success)"></i><code>--gemboss-success</code><span>#007f5f</span></div><div class="cat-swatch"><i style="background:var(--gemboss-success-strong)"></i><code>--gemboss-success-strong</code><span>#0a6a4c</span></div><div class="cat-swatch"><i style="background:var(--gemboss-success-soft)"></i><code>--gemboss-success-soft</code><span>#e3f1df</span></div><div class="cat-swatch"><i style="background:var(--gemboss-success-border)"></i><code>--gemboss-success-border</code><span>#b7dfcd</span></div><div class="cat-swatch"><i style="background:var(--gemboss-warning)"></i><code>--gemboss-warning</code><span>#9a6700</span></div><div class="cat-swatch"><i style="background:var(--gemboss-warning-soft)"></i><code>--gemboss-warning-soft</code><span>#fff8e0</span></div><div class="cat-swatch"><i style="background:var(--gemboss-warning-border)"></i><code>--gemboss-warning-border</code><span>#f5d569</span></div><div class="cat-swatch"><i style="background:var(--gemboss-dark-bg)"></i><code>--gemboss-dark-bg</code><span>#0a0a0c</span></div><div class="cat-swatch"><i style="background:var(--gemboss-dark-rail)"></i><code>--gemboss-dark-rail</code><span>#0e0e11</span></div><div class="cat-swatch"><i style="background:var(--gemboss-dark-surface)"></i><code>--gemboss-dark-surface</code><span>#16161a</span></div><div class="cat-swatch"><i style="background:var(--gemboss-dark-surface-2)"></i><code>--gemboss-dark-surface-2</code><span>#1e1e24</span></div><div class="cat-swatch"><i style="background:var(--gemboss-dark-divider)"></i><code>--gemboss-dark-divider</code><span>#2a2a31</span></div><div class="cat-swatch"><i style="background:var(--gemboss-dark-text)"></i><code>--gemboss-dark-text</code><span>#ededf0</span></div><div class="cat-swatch"><i style="background:var(--gemboss-dark-muted)"></i><code>--gemboss-dark-muted</code><span>#9a9aa4</span></div><div class="cat-swatch"><i style="background:var(--gemboss-field-bg)"></i><code>--gemboss-field-bg</code><span>#fdfdfd</span></div><div class="cat-swatch"><i style="background:var(--gemboss-field-bg-hover)"></i><code>--gemboss-field-bg-hover</code><span>#fafafa</span></div><div class="cat-swatch"><i style="background:var(--gemboss-field-ink)"></i><code>--gemboss-field-ink</code><span>#303030</span></div><div class="cat-swatch"><i style="background:var(--gemboss-field-placeholder)"></i><code>--gemboss-field-placeholder</code><span>#616161</span></div><div class="cat-swatch"><i style="background:var(--gemboss-field-line)"></i><code>--gemboss-field-line</code><span>#8a8a8a</span></div><div class="cat-swatch"><i style="background:var(--gemboss-field-line-hover)"></i><code>--gemboss-field-line-hover</code><span>#616161</span></div><div class="cat-swatch"><i style="background:var(--gemboss-field-line-active)"></i><code>--gemboss-field-line-active</code><span>#1a1a1a</span></div><div class="cat-swatch"><i style="background:var(--gemboss-chip-ink)"></i><code>--gemboss-chip-ink</code><span>#303633</span></div><div class="cat-swatch"><i style="background:var(--gemboss-chip-line)"></i><code>--gemboss-chip-line</code><span>#cfd6ce</span></div><div class="cat-swatch"><i style="background:var(--gemboss-chip-line-hover)"></i><code>--gemboss-chip-line-hover</code><span>#aab4ad</span></div><div class="cat-swatch"><i style="background:var(--gemboss-chip-bg-hover)"></i><code>--gemboss-chip-bg-hover</code><span>#f6f7f5</span></div><div class="cat-swatch"><i style="background:var(--gemboss-on-primary)"></i><code>--gemboss-on-primary</code><span>#ffffff</span></div><div class="cat-swatch"><i style="background:var(--gemboss-app-bg)"></i><code>--gemboss-app-bg</code><span>#ffffff</span></div></div><table class="ui-table cat-scale"><thead><tr><th>Token</th><th>Giá trị</th><th>Mẫu</th></tr></thead><tbody><tr><td><code>--gemboss-fs-micro</code></td><td>11px</td><td style="font-size:var(--gemboss-fs-micro)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-fs-caption</code></td><td>12px</td><td style="font-size:var(--gemboss-fs-caption)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-fs-body</code></td><td>14px</td><td style="font-size:var(--gemboss-fs-body)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-fs-body-lg</code></td><td>16px</td><td style="font-size:var(--gemboss-fs-body-lg)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-fs-h3</code></td><td>16px</td><td style="font-size:var(--gemboss-fs-h3)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-fs-h2</code></td><td>20px</td><td style="font-size:var(--gemboss-fs-h2)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-fs-h1</code></td><td>24px</td><td style="font-size:var(--gemboss-fs-h1)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-fs-display</code></td><td>30px</td><td style="font-size:var(--gemboss-fs-display)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-radius-xs</code></td><td>4px</td><td><i class="cat-radius" style="border-radius:var(--gemboss-radius-xs)"></i></td></tr><tr><td><code>--gemboss-radius-sm</code></td><td>6px</td><td><i class="cat-radius" style="border-radius:var(--gemboss-radius-sm)"></i></td></tr><tr><td><code>--gemboss-radius-md</code></td><td>8px</td><td><i class="cat-radius" style="border-radius:var(--gemboss-radius-md)"></i></td></tr><tr><td><code>--gemboss-radius-lg</code></td><td>12px</td><td><i class="cat-radius" style="border-radius:var(--gemboss-radius-lg)"></i></td></tr><tr><td><code>--gemboss-radius-xl</code></td><td>16px</td><td><i class="cat-radius" style="border-radius:var(--gemboss-radius-xl)"></i></td></tr><tr><td><code>--gemboss-radius-2xl</code></td><td>20px</td><td><i class="cat-radius" style="border-radius:var(--gemboss-radius-2xl)"></i></td></tr><tr><td><code>--gemboss-radius-full</code></td><td>999px</td><td><i class="cat-radius" style="border-radius:var(--gemboss-radius-full)"></i></td></tr><tr><td><code>--gemboss-shadow-resting</code></td><td class="cat-small">0 1px 2px rgba(31, 30, 29, 0.04), 0 2px 6px rgba(31, 30, 29, 0.06)</td><td><i class="cat-radius" style="box-shadow:var(--gemboss-shadow-resting);border:0"></i></td></tr><tr><td><code>--gemboss-shadow-raised</code></td><td class="cat-small">0 4px 16px rgba(31, 30, 29, 0.10)</td><td><i class="cat-radius" style="box-shadow:var(--gemboss-shadow-raised);border:0"></i></td></tr><tr><td><code>--gemboss-shadow-overlay</code></td><td class="cat-small">0 16px 48px rgba(31, 30, 29, 0.18)</td><td><i class="cat-radius" style="box-shadow:var(--gemboss-shadow-overlay);border:0"></i></td></tr></tbody></table></section>
32
32
  <section id="buttons"><h2>Nút</h2><p>Một lớp <code>ui-btn</code>, ba biến thể, tông đỏ, một cỡ nhỏ. Cao 32px dưới chuột (bằng ô nhập và ô chọn), 44px dưới ngón tay và ở khổ điện thoại, cỡ nhỏ cũng vậy. Dùng được trên <code>&lt;button&gt;</code> lẫn <code>&lt;a&gt;</code> (căn giữa sẵn).</p><figure class="cat-demo"><figcaption>Mặc định · chính · ghost · nhỏ · tắt</figcaption><div class="cat-live"><div class="cat-row"><button type="button" class="ui-btn">Lưu nháp</button><button type="button" class="ui-btn ui-btn--primary">Xuất bản</button><button type="button" class="ui-btn ui-btn--ghost">Huỷ</button><button type="button" class="ui-btn ui-btn--sm">Nhỏ</button><button type="button" class="ui-btn ui-btn--primary" disabled>Đang lưu…</button><a class="ui-btn" href="#buttons">Link dạng nút</a></div></div><details class="cat-code"><summary>Markup</summary><pre><code>&lt;div class=&quot;cat-row&quot;&gt;
33
33
  &lt;button type=&quot;button&quot; class=&quot;ui-btn&quot;&gt;Lưu nháp&lt;/button&gt;
package/gemboss-ui.css CHANGED
@@ -1,8 +1,8 @@
1
1
  /*
2
2
  * GemBoss admin design system — GENERATED — do not edit; regenerate from gemboss:
3
3
  * node apps/admin/scripts/export-ui-css.mjs --out <path> (npm run export:ui-css)
4
- * gemboss commit: 47291357064c6877d0ebe7d118b6a1b52b1d9d22
5
- * sources sha256: 22617cdf59a7330f1285221022e5341d94e5b5e74cf917ce6f863c85733505e1
4
+ * gemboss commit: 4397bdb795d6d122d2384e14f9538dceceb82e9e
5
+ * sources sha256: bea19a3043c91b979b8550aa0844ef1f9b09ec145cf1a037be3a9e66ce7f516b
6
6
  * contents: 1 allow-listed rule from apps/admin/src/app/globals.css + packages/ui/src/gemboss-tokens.css
7
7
  * (verbatim) + packages/ui/src/ui.css (verbatim) + packages/ui/src/gemboss-field.css (verbatim)
8
8
  * + 181 rules from packages/ui/src/shell.css (the shared shell)
@@ -432,7 +432,7 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
432
432
  /* ── Thông báo: `<div class="ui-notice ui-notice--warning">…</div>`. Lỗi tải, cảnh báo cấu hình, việc cần người
433
433
  làm. Mỗi tông mang màu chữ riêng (nền tông luôn sáng, kể cả trong admin tối). Không có tông = ghi chú trung tính.
434
434
  role="status"/"alert" CHỈ khi thông báo xuất hiện sau khi trang đã tải (vùng live); có sẵn lúc tải thì không role. */
435
- .ui-notice { padding: 12px 16px; border-radius: var(--ui-radius-md); border: 1px solid var(--ui-border); background: var(--ui-surface-2); color: var(--ui-text); font-size: var(--ui-fs-body); }
435
+ .ui-notice { padding: 12px 16px; border-radius: var(--ui-radius-md); border: 1px solid var(--ui-border); background: var(--ui-surface-2); color: var(--ui-text); font-size: var(--ui-fs-body); overflow-wrap: anywhere; } /* a URL or key in a notice wraps instead of being cut by its card (gemwatcher Pulse, 0.6.2) */
436
436
  .ui-notice--warning { background: var(--gemboss-warning-soft); border-color: var(--gemboss-warning-border); color: var(--gemboss-warning); }
437
437
  .ui-notice--danger { background: var(--gemboss-danger-soft); border-color: var(--gemboss-danger-border); color: var(--gemboss-danger-strong); }
438
438
  .ui-notice--success { background: var(--gemboss-success-soft); border-color: var(--gemboss-success-border); color: var(--gemboss-success-strong); }
@@ -2032,14 +2032,22 @@ body.gemboss-assistant-unread .gnav-item.gnav-assistant::after {
2032
2032
  .gemboss-topbar-center { display: flex; justify-content: center; min-width: 0; }
2033
2033
  .gemboss-topbar-end { display: flex; align-items: center; justify-content: flex-end; gap: 8px; min-width: 0; }
2034
2034
  @media (max-width: 760px) {
2035
- /* The topbar is one flex row here: the middle slot takes what is left. */
2036
- .gemboss-topbar-center { flex: 1 1 auto; justify-content: flex-start; }
2035
+ /* The topbar is one flex row here: the middle slot takes what is left. Basis 0, not auto: with `auto` the search's own
2036
+ * width (up to 520px) was its claim, so the other slots had to give way to it (0.6.0 pinned them instead, see below). With
2037
+ * 0 it only takes what the others leave, down to a 96px floor: an input with its icon still reads as search. */
2038
+ .gemboss-topbar-center { flex: 1 1 0; min-width: 96px; justify-content: flex-start; }
2039
+ .gemboss-topbar-center:empty { min-width: 0; }
2037
2040
  /* No actions: the empty slot would still take one flex `gap` (8px) off the search at 320px. */
2038
2041
  .gemboss-topbar-end:empty { display: none; }
2039
- /* Only the search gives. With `min-width: 0` on all three slots, flex shrank them all: the actions slot gave way and its
2040
- * buttons (justify flex-end) spilled LEFT over the search (two 44px targets, assistant + account, overlapped
2041
- * partner-radar's search by 15px at 320 and 360), and the start slot squeezed the gem mark to 16px. */
2042
- .gemboss-topbar-start, .gemboss-topbar-end { flex: 0 0 auto; }
2042
+ /* The actions never give: with `min-width: 0` the slot shrank and its buttons (justify flex-end) spilled LEFT over the
2043
+ * search (assistant + account, 15px over partner-radar's search at 320 and 360, 0.6.0).
2044
+ * The start slot gives LAST, after the search reaches its floor: the brand name ellipsizes (.gb-brand-name) instead of
2045
+ * the account chip leaving the screen. 0.6.0 pinned it (`flex: 0 0 auto`) and a 30-character brand at 461px pushed the
2046
+ * chip 44px past the right edge (review #3690). The menu button and the mark keep their 44px. */
2047
+ .gemboss-topbar-end { flex: 0 0 auto; }
2048
+ .gemboss-topbar-start { flex: 0 1 auto; }
2049
+ .gemboss-topbar-start > .gemboss-mobile-menu-btn { flex: 0 0 auto; }
2050
+ .gemboss-topbar-start > .gb-brand { min-width: 44px; }
2043
2051
  }
2044
2052
 
2045
2053
  .gnav-heading {
@@ -3384,7 +3392,9 @@ button.ui-card__media, a.ui-card__media { cursor: pointer; }
3384
3392
  /* Delta: `<span class="ui-delta ui-delta--good"><span aria-hidden="true">↑</span> 12%</span>`. Tốt / xấu là của BỀ MẶT
3385
3393
  quyết (lượt đọc tăng là tốt, tỉ lệ thoát tăng là xấu), mũi tên là hướng. Không tông = trung tính (không đổi, hoặc
3386
3394
  "chưa có kỳ trước"). Luôn kèm chữ cho trình đọc màn hình nếu mũi tên là thứ duy nhất nói hướng. */
3387
- .ui-delta { display: inline-flex; align-items: baseline; gap: 2px; font-size: var(--gemboss-fs-caption); font-weight: 600; color: var(--ui-text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
3395
+ /* Wraps (0.6.2): a delta that carries its comparison ("▼ 50% vs previous 30 days (20m)") ran 73px out of a 145px metric at
3396
+ 390 on gemcare. Short ones ("↑ 18%") still sit on one line; long ones break between words, never inside a number. */
3397
+ .ui-delta { display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: 2px; font-size: var(--gemboss-fs-caption); font-weight: 600; color: var(--ui-text-muted); font-variant-numeric: tabular-nums; }
3388
3398
  .ui-delta--good { color: var(--ui-success-fg); }
3389
3399
  .ui-delta--bad { color: var(--ui-danger-fg); }
3390
3400
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gemboss/ui",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "GemBoss design system for every surface in the fleet: the admin shell (topbar + rail + drawer) as React components, and the admin stylesheet exported as one CSS file.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
package/src/app-shell.css CHANGED
@@ -31,14 +31,22 @@
31
31
  .gemboss-topbar-center { display: flex; justify-content: center; min-width: 0; }
32
32
  .gemboss-topbar-end { display: flex; align-items: center; justify-content: flex-end; gap: 8px; min-width: 0; }
33
33
  @media (max-width: 760px) {
34
- /* The topbar is one flex row here: the middle slot takes what is left. */
35
- .gemboss-topbar-center { flex: 1 1 auto; justify-content: flex-start; }
34
+ /* The topbar is one flex row here: the middle slot takes what is left. Basis 0, not auto: with `auto` the search's own
35
+ * width (up to 520px) was its claim, so the other slots had to give way to it (0.6.0 pinned them instead, see below). With
36
+ * 0 it only takes what the others leave, down to a 96px floor: an input with its icon still reads as search. */
37
+ .gemboss-topbar-center { flex: 1 1 0; min-width: 96px; justify-content: flex-start; }
38
+ .gemboss-topbar-center:empty { min-width: 0; }
36
39
  /* No actions: the empty slot would still take one flex `gap` (8px) off the search at 320px. */
37
40
  .gemboss-topbar-end:empty { display: none; }
38
- /* Only the search gives. With `min-width: 0` on all three slots, flex shrank them all: the actions slot gave way and its
39
- * buttons (justify flex-end) spilled LEFT over the search (two 44px targets, assistant + account, overlapped
40
- * partner-radar's search by 15px at 320 and 360), and the start slot squeezed the gem mark to 16px. */
41
- .gemboss-topbar-start, .gemboss-topbar-end { flex: 0 0 auto; }
41
+ /* The actions never give: with `min-width: 0` the slot shrank and its buttons (justify flex-end) spilled LEFT over the
42
+ * search (assistant + account, 15px over partner-radar's search at 320 and 360, 0.6.0).
43
+ * The start slot gives LAST, after the search reaches its floor: the brand name ellipsizes (.gb-brand-name) instead of
44
+ * the account chip leaving the screen. 0.6.0 pinned it (`flex: 0 0 auto`) and a 30-character brand at 461px pushed the
45
+ * chip 44px past the right edge (review #3690). The menu button and the mark keep their 44px. */
46
+ .gemboss-topbar-end { flex: 0 0 auto; }
47
+ .gemboss-topbar-start { flex: 0 1 auto; }
48
+ .gemboss-topbar-start > .gemboss-mobile-menu-btn { flex: 0 0 auto; }
49
+ .gemboss-topbar-start > .gb-brand { min-width: 44px; }
42
50
  }
43
51
 
44
52
  .gnav-heading {
@@ -131,6 +131,28 @@ export function clampDockHeight(height: number, viewport: number): number {
131
131
  return Math.min(Math.max(Math.round(height), DOCK_MIN_HEIGHT), max);
132
132
  }
133
133
 
134
+ /**
135
+ * How far below the topbar a popup that has just OPENED starts (03/10/2026).
136
+ *
137
+ * The 12px ceiling above is where a popup the seller DRAGS can reach. It is not where one should open.
138
+ * Chris, 03/10: "default height cao hơn sát vào topbar". Measured on his browser (window 620px tall):
139
+ * gemwatcher had a dragged 599 saved, every reopen clamped it to 552, and the popup opened 12px under
140
+ * the topbar; a flat 480 default also leaves only 84px on a 620 window and ~0 on a 560 one.
141
+ */
142
+ export const DOCK_OPEN_GAP = 120;
143
+
144
+ /** The tallest a popup OPENS at in this window: a quarter-ish band of page stays visible above it. */
145
+ export function dockOpenCeiling(viewport: number): number {
146
+ return Math.max(DOCK_MIN_HEIGHT, Math.round(viewport) - DOCK_TOPBAR_HEIGHT - DOCK_OPEN_GAP);
147
+ }
148
+
149
+ /** The height a popup opens at: what the seller asked for (or the default), never closer to the topbar
150
+ * than DOCK_OPEN_GAP. Dragging in-session can still go up to the 12px ceiling of `clampDockHeight`. */
151
+ export function openingDockHeight(requested: number | null, viewport: number): number {
152
+ const want = requested !== null && Number.isFinite(requested) && requested > 0 ? requested : DOCK_DEFAULT_HEIGHT;
153
+ return clampDockHeight(Math.min(want, dockOpenCeiling(viewport)), viewport);
154
+ }
155
+
134
156
  export const initialAssistantDockState: AssistantDockState = {
135
157
  presentation: "hidden",
136
158
  mounted: false,
@@ -18,6 +18,7 @@ import {
18
18
  initialAssistantDockState,
19
19
  DOCK_DEFAULT_HEIGHT,
20
20
  DOCK_DEFAULT_OFFSET_RIGHT,
21
+ openingDockHeight,
21
22
  SIDE_DEFAULT_WIDTH,
22
23
  type AssistantDockState,
23
24
  } from "./dock";
@@ -115,8 +116,10 @@ export function AssistantProvider({ options, children }: { options?: AssistantOp
115
116
  // Restore the seller's popup size + pin after mount (localStorage is unavailable during SSR).
116
117
  useEffect(() => {
117
118
  try {
118
- const raw = Number(window.localStorage.getItem(storageKey(opts.current.storagePrefix, "dockHeight")));
119
- const height = Number.isFinite(raw) && raw > 0 ? raw : DOCK_DEFAULT_HEIGHT;
119
+ const stored = window.localStorage.getItem(storageKey(opts.current.storagePrefix, "dockHeight"));
120
+ // Opens no closer to the topbar than DOCK_OPEN_GAP, even when a past drag saved the ceiling
121
+ // (dock.ts, 03/10/2026). The stored number is left as it was: a drag rewrites it.
122
+ const height = openingDockHeight(stored === null || stored === "" ? null : Number(stored), window.innerHeight);
120
123
  requestedHeight.current = height;
121
124
  dispatch({ type: "resize", height, viewport: window.innerHeight });
122
125
  // `Number(null)` is 0, not NaN — so reading a key that was never written used to slide the
package/src/kit-plain.css CHANGED
@@ -127,7 +127,9 @@ button.ui-card__media, a.ui-card__media { cursor: pointer; }
127
127
  /* Delta: `<span class="ui-delta ui-delta--good"><span aria-hidden="true">↑</span> 12%</span>`. Tốt / xấu là của BỀ MẶT
128
128
  quyết (lượt đọc tăng là tốt, tỉ lệ thoát tăng là xấu), mũi tên là hướng. Không tông = trung tính (không đổi, hoặc
129
129
  "chưa có kỳ trước"). Luôn kèm chữ cho trình đọc màn hình nếu mũi tên là thứ duy nhất nói hướng. */
130
- .ui-delta { display: inline-flex; align-items: baseline; gap: 2px; font-size: var(--gemboss-fs-caption); font-weight: 600; color: var(--ui-text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
130
+ /* Wraps (0.6.2): a delta that carries its comparison ("▼ 50% vs previous 30 days (20m)") ran 73px out of a 145px metric at
131
+ 390 on gemcare. Short ones ("↑ 18%") still sit on one line; long ones break between words, never inside a number. */
132
+ .ui-delta { display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: 2px; font-size: var(--gemboss-fs-caption); font-weight: 600; color: var(--ui-text-muted); font-variant-numeric: tabular-nums; }
131
133
  .ui-delta--good { color: var(--ui-success-fg); }
132
134
  .ui-delta--bad { color: var(--ui-danger-fg); }
133
135
 
package/src/kit-plain.js CHANGED
@@ -149,7 +149,13 @@
149
149
  if (fresh) vp.dataset.readyAt = String(Date.now() + 50);
150
150
  var wait = Math.max(0, Number(vp.dataset.readyAt || 0) - Date.now());
151
151
  var put = function () { vp.append(t); };
152
- if (wait) setTimeout(put, wait); else put();
152
+ // ONE timer drains the waiting toasts in the order they were sent. A setTimeout per toast did not: the second one
153
+ // computed a slightly shorter wait and could land first (CI, 03/10/2026: "down" before "<b>hi</b>"). Anything sent
154
+ // after the region is ready still goes straight in, behind the queue.
155
+ if (wait || vp._gbQueue) {
156
+ (vp._gbQueue = vp._gbQueue || []).push(put);
157
+ if (!vp._gbFlush) vp._gbFlush = setTimeout(function () { var q = vp._gbQueue; vp._gbQueue = null; vp._gbFlush = null; q.forEach(function (f) { f(); }); }, wait);
158
+ } else put();
153
159
  if (!sticky) setTimeout(function () { t.remove(); }, wait + (opts.duration || (opts.error ? 6000 : 3500)));
154
160
  return t;
155
161
  }
package/src/ui.css CHANGED
@@ -249,7 +249,7 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
249
249
  /* ── Thông báo: `<div class="ui-notice ui-notice--warning">…</div>`. Lỗi tải, cảnh báo cấu hình, việc cần người
250
250
  làm. Mỗi tông mang màu chữ riêng (nền tông luôn sáng, kể cả trong admin tối). Không có tông = ghi chú trung tính.
251
251
  role="status"/"alert" CHỈ khi thông báo xuất hiện sau khi trang đã tải (vùng live); có sẵn lúc tải thì không role. */
252
- .ui-notice { padding: 12px 16px; border-radius: var(--ui-radius-md); border: 1px solid var(--ui-border); background: var(--ui-surface-2); color: var(--ui-text); font-size: var(--ui-fs-body); }
252
+ .ui-notice { padding: 12px 16px; border-radius: var(--ui-radius-md); border: 1px solid var(--ui-border); background: var(--ui-surface-2); color: var(--ui-text); font-size: var(--ui-fs-body); overflow-wrap: anywhere; } /* a URL or key in a notice wraps instead of being cut by its card (gemwatcher Pulse, 0.6.2) */
253
253
  .ui-notice--warning { background: var(--gemboss-warning-soft); border-color: var(--gemboss-warning-border); color: var(--gemboss-warning); }
254
254
  .ui-notice--danger { background: var(--gemboss-danger-soft); border-color: var(--gemboss-danger-border); color: var(--gemboss-danger-strong); }
255
255
  .ui-notice--success { background: var(--gemboss-success-soft); border-color: var(--gemboss-success-border); color: var(--gemboss-success-strong); }