@celestia-island/hikari 0.40.7 → 0.40.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@celestia-island/hikari",
3
- "version": "0.40.7",
3
+ "version": "0.40.9",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Hikari Vue 3 component library — production-grade UI components based on shittim-chest design system",
@@ -36,6 +36,15 @@
36
36
  color: rgb(var(--color-text));
37
37
  }
38
38
 
39
+ /* Mobile sheet mode: the docked sheet spans the viewport, so the fixed
40
+ * 18rem pop would hug the sheet's left edge with dead space to the right.
41
+ * Fill the sheet instead — the panel chrome keeps its own symmetric
42
+ * horizontal gutters (the glass content padding), so the bar and legend
43
+ * get equal left/right insets. Desktop stays anchored at 18rem. */
44
+ .hk-popover-panel.hk-is-sheet .hk-ctx-pop {
45
+ width: 100%;
46
+ }
47
+
39
48
  .hk-ctx-pop-model {
40
49
  display: flex;
41
50
  }
@@ -173,6 +173,10 @@
173
173
  white-space: nowrap;
174
174
  overflow: hidden;
175
175
  text-overflow: ellipsis;
176
+ // Window chrome, not content — titles never participate in text
177
+ // selection (2026-09-04 user report alongside the sheet chrome pass).
178
+ user-select: none;
179
+ -webkit-user-select: none;
176
180
  }
177
181
 
178
182
  .hk-modal-close {
@@ -339,9 +343,10 @@
339
343
  // modal rendered ~60vh of blank body under three rows). The top inset
340
344
  // now only bounds the sheet via the max-height cap below.
341
345
  top: auto;
342
- // --hk-sheet-bottom-gap keeps the docked sheet from touching the very
343
- // bottom edge (host-tunable; chest narrows the default visual gap).
344
- bottom: var(--hk-sheet-bottom-gap, 0.375rem);
346
+ // --hk-sheet-bottom-gap: hosts may lift the docked sheet off the very
347
+ // bottom edge; the family default sits flush on it (2026-09-04 user
348
+ // report: even the tightened 0.375rem default still read as a seam).
349
+ bottom: var(--hk-sheet-bottom-gap, 0px);
345
350
  left: 0;
346
351
  right: 0;
347
352
  transform: none;
@@ -356,10 +361,10 @@
356
361
  // Plain-vh fallback first: dvh-less engines (older Android WebView /
357
362
  // Tauri) would drop the whole dvh calc and leave the sheet uncapped.
358
363
  max-height: calc(
359
- 100vh - var(--hk-sheet-top-inset, 4.25rem) - var(--hk-sheet-bottom-gap, 0.375rem)
364
+ 100vh - var(--hk-sheet-top-inset, 4.25rem) - var(--hk-sheet-bottom-gap, 0px)
360
365
  );
361
366
  max-height: calc(
362
- 100dvh - var(--hk-sheet-top-inset, 4.25rem) - var(--hk-sheet-bottom-gap, 0.375rem)
367
+ 100dvh - var(--hk-sheet-top-inset, 4.25rem) - var(--hk-sheet-bottom-gap, 0px)
363
368
  );
364
369
  }
365
370
 
@@ -21,13 +21,14 @@ const here = dirname(fileURLToPath(import.meta.url));
21
21
  const src = readFileSync(join(here, "HkModal.scss"), "utf-8");
22
22
 
23
23
  describe("HkModal mobile sheet spacing contract", () => {
24
- it("sizes the docked sheet from --hk-sheet-bottom-gap (not hard 0)", () => {
24
+ it("sizes the docked sheet from --hk-sheet-bottom-gap (host-tunable hook)", () => {
25
25
  const block = src.slice(src.indexOf("@media (max-width: 767px)"));
26
26
  expect(block).toContain("bottom: var(--hk-sheet-bottom-gap");
27
- // Default keeps a small visible gap; hosts may narrow or zero it.
28
- // (2026-08-29 user request: the docked sheet sat too far off the
29
- // bottom — default tightened from 0.5rem to 0.375rem.)
30
- expect(block).toMatch(/--hk-sheet-bottom-gap,\s*0\.375rem/);
27
+ // Family default sits flush on the bottom edge; hosts may re-add a
28
+ // gap via the same custom property. (History: 2026-08-29 tightened
29
+ // 0.5rem 0.375rem; 2026-09-04 user report — the remaining sliver
30
+ // still read as a visible seam, so the default settled on 0.)
31
+ expect(block).toMatch(/--hk-sheet-bottom-gap,\s*0px/);
31
32
  });
32
33
 
33
34
  it("keeps the footer gap host-tunable and stacks the safe-area inset", () => {
@@ -0,0 +1,51 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { getModelMeta, registerModelCatalog } from "./HkModelCatalog";
4
+
5
+ /**
6
+ * The simulated-world roster (integration world_service `agent_models`):
7
+ * every model id a chest card can carry must resolve a context window, or
8
+ * its context ring degrades to the empty "–" state — the "only the sample
9
+ * card shows usage" regression. The builtin catalog is the fallback that
10
+ * keeps unknown-to-nothing lineups legible.
11
+ */
12
+ const WORLD_LINEUP = [
13
+ "glm-5.3-flash",
14
+ "claude-opus-5",
15
+ "deepseek-v4-pro",
16
+ "deepseek-v4-flash",
17
+ "gemini-3.1-flash-preview",
18
+ "gpt-5.5-pro",
19
+ "gemini-3.1-pro-preview",
20
+ "gpt-5.4",
21
+ "gpt-5.4-nano",
22
+ "claude-haiku-4-5",
23
+ "gemini-3-flash",
24
+ "qwen3.7:14b",
25
+ "qwen3.8:27b",
26
+ ];
27
+
28
+ describe("HkModelCatalog builtin specs", () => {
29
+ it("resolves every world-lineup model id to a positive context window", () => {
30
+ for (const model of WORLD_LINEUP) {
31
+ const meta = getModelMeta(model);
32
+ expect(meta, `${model} resolves`).toBeDefined();
33
+ expect(meta?.contextWindow ?? 0, `${model} window`).toBeGreaterThan(0);
34
+ }
35
+ });
36
+
37
+ it("keeps ollama-style colon tags whole (only # splits)", () => {
38
+ expect(getModelMeta("qwen3.7:14b")?.contextWindow).toBe(262_144);
39
+ expect(getModelMeta("qwen3.8:27b")?.contextWindow).toBe(262_144);
40
+ });
41
+
42
+ it("still strips a #tag before lookup", () => {
43
+ expect(getModelMeta("deepseek-v4-pro#2")?.contextWindow).toBe(128_000);
44
+ expect(getModelMeta("claude-opus-5#7")?.contextWindow).toBe(200_000);
45
+ });
46
+
47
+ it("lets the registered catalog win over the builtin entries", () => {
48
+ registerModelCatalog({ "claude-opus-5": { contextWindow: 999 } });
49
+ expect(getModelMeta("claude-opus-5")?.contextWindow).toBe(999);
50
+ });
51
+ });
@@ -64,6 +64,59 @@ const BUILTIN_CATALOG: HModelCatalog = {
64
64
  pricing: { in: 2, out: 8 },
65
65
  vision: true, reasoning: true, tools: true,
66
66
  },
67
+ "glm-5.3-flash": {
68
+ contextWindow: 128_000, maxOutput: 32_000,
69
+ pricing: { in: 0.5, out: 2 },
70
+ vision: false, reasoning: true, tools: true,
71
+ },
72
+ "gpt-5.5-pro": {
73
+ contextWindow: 400_000, maxOutput: 100_000,
74
+ pricing: { in: 10, out: 50, cached: 1 },
75
+ vision: true, reasoning: true, tools: true,
76
+ },
77
+ "gpt-5.4": {
78
+ contextWindow: 400_000, maxOutput: 64_000,
79
+ pricing: { in: 2.5, out: 15, cached: 0.25 },
80
+ vision: true, reasoning: true, tools: true,
81
+ },
82
+ "gpt-5.4-nano": {
83
+ contextWindow: 400_000, maxOutput: 16_000,
84
+ pricing: { in: 0.2, out: 1.2, cached: 0.05 },
85
+ vision: false, reasoning: false, tools: true,
86
+ },
87
+ "claude-opus-5": {
88
+ contextWindow: 200_000, maxOutput: 64_000,
89
+ pricing: { in: 5, out: 25, cached: 0.5 },
90
+ vision: true, reasoning: true, tools: true,
91
+ },
92
+ "claude-haiku-4-5": {
93
+ contextWindow: 200_000, maxOutput: 32_000,
94
+ pricing: { in: 0.8, out: 4, cached: 0.08 },
95
+ vision: false, reasoning: false, tools: true,
96
+ },
97
+ "gemini-3.1-pro-preview": {
98
+ contextWindow: 1_000_000, maxOutput: 64_000,
99
+ pricing: { in: 5, out: 30 },
100
+ vision: true, reasoning: true, tools: true,
101
+ },
102
+ "gemini-3.1-flash-preview": {
103
+ contextWindow: 1_000_000, maxOutput: 64_000,
104
+ pricing: { in: 1.5, out: 10 },
105
+ vision: true, reasoning: true, tools: true,
106
+ },
107
+ "gemini-3-flash": {
108
+ contextWindow: 1_000_000, maxOutput: 32_000,
109
+ pricing: { in: 1.5, out: 9 },
110
+ vision: true, reasoning: false, tools: true,
111
+ },
112
+ "qwen3.7:14b": {
113
+ contextWindow: 262_144, maxOutput: 32_768,
114
+ vision: false, reasoning: true, tools: true,
115
+ },
116
+ "qwen3.8:27b": {
117
+ contextWindow: 262_144, maxOutput: 32_768,
118
+ vision: false, reasoning: true, tools: true,
119
+ },
67
120
  };
68
121
 
69
122
  /**
@@ -100,7 +100,7 @@
100
100
  72vh,
101
101
  calc(
102
102
  100vh - var(--hk-sheet-top-inset, 4.25rem) -
103
- var(--hk-sheet-bottom-gap, 0.375rem)
103
+ var(--hk-sheet-bottom-gap, 0px)
104
104
  )
105
105
  );
106
106
  display: flex;
@@ -137,6 +137,10 @@
137
137
  cursor: pointer;
138
138
  display: flex;
139
139
  justify-content: center;
140
+ // Window chrome, not content — a long-press must never start a text
141
+ // selection over the drag handle.
142
+ user-select: none;
143
+ -webkit-user-select: none;
140
144
 
141
145
  &::before {
142
146
  content: "";
@@ -147,17 +151,81 @@
147
151
  }
148
152
  }
149
153
 
150
- .hk-popover-sheet-title {
154
+ /* Sheet heading row — title on the left, explicit close on the right,
155
+ * one vertically-centered flex line (2026-09-04 user report: the bare
156
+ * title band read too small, hung right of the content it named, and
157
+ * long-press selected it; every other window in the app carries an ✕).
158
+ * The title aligns to the CONTENT edge: the leading inset defaults to
159
+ * --space-12, matching the padded content blocks popover sheets host
160
+ * (e.g. HkContextRing's 0.75rem body). Hosts tune it via
161
+ * --hk-sheet-title-inset; the trailing inset hugs the close button. */
162
+ .hk-popover-sheet-header {
151
163
  flex-shrink: 0;
152
- // Same heading grammar as the select sheet title: small, semibold,
153
- // slightly letterspaced, inset to the content edge. Hosts tune the
154
- // leading inset via --hk-sheet-title-inset.
155
- padding: 0 var(--space-16, 1rem) var(--space-8, 0.5rem)
156
- var(--hk-sheet-title-inset, calc(var(--space-16, 1rem) * 2));
157
- font-size: var(--text-xs, 0.75rem);
164
+ display: flex;
165
+ align-items: center;
166
+ gap: var(--space-8, 0.5rem);
167
+ padding: 0 var(--space-8, 0.5rem) var(--space-8, 0.5rem)
168
+ var(--hk-sheet-title-inset, var(--space-12, 0.75rem));
169
+ }
170
+
171
+ .hk-popover-sheet-title {
172
+ flex: 1;
173
+ min-width: 0;
174
+ overflow: hidden;
175
+ white-space: nowrap;
176
+ text-overflow: ellipsis;
177
+ // Same heading grammar as the select sheet title: semibold, slightly
178
+ // letterspaced — one step up from the old 0.75rem so the sheet names
179
+ // itself at body-text size, and window chrome never participates in
180
+ // text selection.
181
+ font-size: var(--text-base, 0.875rem);
158
182
  font-weight: 600;
159
183
  letter-spacing: 0.02em;
160
184
  color: rgb(var(--color-muted, 140 140 140));
185
+ user-select: none;
186
+ -webkit-user-select: none;
187
+ }
188
+
189
+ .hk-popover-sheet-close {
190
+ flex-shrink: 0;
191
+ // Pushes to the right edge with or without a title beside it.
192
+ margin-left: auto;
193
+ width: 2rem;
194
+ height: 2rem;
195
+ display: inline-flex;
196
+ align-items: center;
197
+ justify-content: center;
198
+ padding: 0;
199
+ background: transparent;
200
+ border: none;
201
+ border-radius: var(--hi-radius-md, 0.5rem);
202
+ color: var(--hk-modal-close-color, var(--hi-color-text-secondary, #666));
203
+ cursor: pointer;
204
+ user-select: none;
205
+ -webkit-user-select: none;
206
+ transition:
207
+ background 0.15s ease,
208
+ color 0.15s ease;
209
+
210
+ &:hover {
211
+ background: var(
212
+ --hk-modal-close-hover-bg,
213
+ var(--hi-secondary-bg, rgba(0, 0, 0, 0.06))
214
+ );
215
+ color: var(
216
+ --hk-modal-close-hover-color,
217
+ var(--hi-color-text-primary, #333)
218
+ );
219
+ }
220
+
221
+ &:active {
222
+ transform: scale(0.92);
223
+ }
224
+
225
+ &:focus-visible {
226
+ outline: 2px solid var(--hi-color-primary, #ffc0cb);
227
+ outline-offset: 2px;
228
+ }
161
229
  }
162
230
 
163
231
  .hk-popover-sheet-enter-active {
@@ -17,11 +17,13 @@ const tsx = readFileSync(join(here, "HkPopover.tsx"), "utf-8");
17
17
  const scss = readFileSync(join(here, "HkPopover.scss"), "utf-8");
18
18
 
19
19
  describe("HkPopover mobile sheet docking contract", () => {
20
- it("docks the sheet above the shared bottom gap (inline sheet style)", () => {
20
+ it("docks the sheet at the shared bottom-gap hook (inline sheet style)", () => {
21
21
  const block = tsx.slice(tsx.indexOf("panelStyle"));
22
22
  expect(block).toContain("var(--hk-sheet-bottom-gap");
23
- // Same default as the HkModal sheet contract.
24
- expect(block).toMatch(/--hk-sheet-bottom-gap,\s*0\.375rem/);
23
+ // Family default sits flush on the bottom edge (2026-09-04 user
24
+ // report: even the tightened 0.375rem lift read as a visible seam);
25
+ // hosts may re-add a gap via the same custom property.
26
+ expect(block).toMatch(/--hk-sheet-bottom-gap,\s*0px/);
25
27
  });
26
28
 
27
29
  it("keeps sheet content clear of the home bar via the safe-area inset", () => {
@@ -15,6 +15,7 @@ import {
15
15
 
16
16
  import { usePopupManager, type PopupHandle } from "../runtime/usePopupManager";
17
17
  import { useBreakpoint } from "../runtime/useBreakpoint";
18
+ import { useI18n } from "../i18n/context";
18
19
  import { useSurfaceTransition } from "../composables/useSurfaceTransition";
19
20
  import { attachOverlayScrollbars, type OverlayScrollbarHandle } from "../composables/useOverlayScrollbar";
20
21
  import { onceFrame } from "../runtime/animationBus";
@@ -76,6 +77,7 @@ export default defineComponent({
76
77
  setup(props, { emit, slots }) {
77
78
  const attrs = useAttrs();
78
79
  const manager = usePopupManager();
80
+ const { t } = useI18n();
79
81
  const handle = ref<PopupHandle | null>(null);
80
82
  const panelRef = ref<HTMLElement>();
81
83
  const resolvedPlacement = ref<PopupPlacement>(props.placement);
@@ -489,15 +491,17 @@ export default defineComponent({
489
491
 
490
492
  const panelStyle = computed(() => {
491
493
  if (sheetMode.value) {
492
- // Bottom sheet: pinned above the bottom edge with the SAME
493
- // host-tunable gap as the HkModal mobile docking
494
- // (--hk-sheet-bottom-gap) so every bottom-up window belongs to
495
- // one sheet family; full width, inside the top inset reserved
496
- // for the secondary-window breadcrumb strip.
494
+ // Bottom sheet: sits flush on the viewport bottom edge (the
495
+ // host-tunable --hk-sheet-bottom-gap lift is still honored so the
496
+ // sheet belongs to the HkModal/HkSelect sheet family — the family
497
+ // default is 0 since the 2026-09-04 report: even a 0.375rem lift
498
+ // read as a visible seam under the docked sheet); full width,
499
+ // inside the top inset reserved for the secondary-window
500
+ // breadcrumb strip.
497
501
  return {
498
502
  left: "0",
499
503
  right: "0",
500
- bottom: "var(--hk-sheet-bottom-gap, 0.375rem)",
504
+ bottom: "var(--hk-sheet-bottom-gap, 0px)",
501
505
  top: "auto" as const,
502
506
  position: "fixed" as const,
503
507
  pointerEvents: "auto" as const,
@@ -568,11 +572,36 @@ export default defineComponent({
568
572
  {sheetMode.value && (
569
573
  <div class="hk-popover-sheet-grabber" aria-hidden="true" onClick={() => close()} />
570
574
  )}
571
- {sheetMode.value && props.title && (
572
- // Sheet heading bandopt-in via the `title` prop so
573
- // anchored-desktop consumers are untouched; keeps the
574
- // docked sheet naming itself like every other window.
575
- <div class="hk-popover-sheet-title">{props.title}</div>
575
+ {sheetMode.value && (
576
+ // Sheet heading rowthe title and the close button on
577
+ // one vertically-centered line (2026-09-04 user report:
578
+ // the bare title band read too small and hung right of
579
+ // the content it named, and every other window in the
580
+ // app carries an explicit ✕). Same header grammar as the
581
+ // HkModal header: title left, close on the right edge.
582
+ <div class="hk-popover-sheet-header">
583
+ {props.title && (
584
+ <div class="hk-popover-sheet-title">{props.title}</div>
585
+ )}
586
+ <button
587
+ class="hk-popover-sheet-close"
588
+ aria-label={t("hikari::modal.close", "Close")}
589
+ onClick={close}
590
+ >
591
+ <svg
592
+ viewBox="0 0 24 24"
593
+ fill="none"
594
+ stroke="currentColor"
595
+ stroke-width="2"
596
+ stroke-linecap="round"
597
+ width="16"
598
+ height="16"
599
+ >
600
+ <line x1="18" y1="6" x2="6" y2="18" />
601
+ <line x1="6" y1="6" x2="18" y2="18" />
602
+ </svg>
603
+ </button>
604
+ </div>
576
605
  )}
577
606
  {slots.default?.()}
578
607
  </div>
@@ -127,4 +127,50 @@ describe("HkPopover sheetOnMobile", () => {
127
127
  expect(document.body.querySelector(".hk-popover-panel")!.classList.contains("hk-is-sheet")).toBe(true);
128
128
  expect(document.body.querySelector(".hk-popover-scrim")).toBeNull();
129
129
  });
130
+
131
+ // 2026-09-04 user report: the sheet heading was a bare small band —
132
+ // the sheet now names itself with a title + explicit close button on
133
+ // one vertically-centered line, like every other window in the app.
134
+ it("renders a title + close heading row when titled, close alone when not", async () => {
135
+ setViewport(375);
136
+ mountPopover({ sheetOnMobile: true, title: "Context usage" });
137
+ await nextTick();
138
+
139
+ const panel = document.body.querySelector<HTMLElement>(".hk-popover-panel")!;
140
+ const header = panel.querySelector<HTMLElement>(".hk-popover-sheet-header")!;
141
+ expect(header).toBeTruthy();
142
+ expect(header.querySelector(".hk-popover-sheet-title")!.textContent).toBe("Context usage");
143
+ const close = header.querySelector<HTMLButtonElement>(".hk-popover-sheet-close")!;
144
+ expect(close).toBeTruthy();
145
+ expect(close.getAttribute("aria-label")).toBeTruthy();
146
+ });
147
+
148
+ it("closes the sheet from the heading close button", async () => {
149
+ setViewport(375);
150
+ const { open } = mountPopover({ sheetOnMobile: true, title: "Context usage" });
151
+ await nextTick();
152
+ const close = document.body.querySelector<HTMLButtonElement>(".hk-popover-sheet-close")!;
153
+ close.click();
154
+ await nextTick();
155
+ expect(open.value).toBe(false);
156
+ });
157
+
158
+ it("renders the close button even without a title, right-aligned", async () => {
159
+ setViewport(375);
160
+ mountPopover({ sheetOnMobile: true });
161
+ await nextTick();
162
+ const header = document.body.querySelector<HTMLElement>(".hk-popover-sheet-header")!;
163
+ expect(header.querySelector(".hk-popover-sheet-title")).toBeNull();
164
+ expect(header.querySelector(".hk-popover-sheet-close")).toBeTruthy();
165
+ });
166
+
167
+ it("keeps the heading row out of anchored desktop mode", async () => {
168
+ setViewport(1200);
169
+ mountPopover({ sheetOnMobile: true, title: "Context usage" });
170
+ await nextTick();
171
+ const panel = document.body.querySelector<HTMLElement>(".hk-popover-panel")!;
172
+ expect(panel.querySelector(".hk-popover-sheet-header")).toBeNull();
173
+ expect(panel.querySelector(".hk-popover-sheet-title")).toBeNull();
174
+ expect(panel.querySelector(".hk-popover-sheet-close")).toBeNull();
175
+ });
130
176
  });
@@ -244,11 +244,12 @@
244
244
  position: fixed;
245
245
  left: 0;
246
246
  right: 0;
247
- /* Float off the bottom edge with the SAME host-tunable gap as the
248
- * HkModal mobile docking (--hk-sheet-bottom-gap) every bottom-up
249
- * window reads as one sheet family; was glued to bottom: 0, which is
250
- * exactly why dropdown sheets read as "not the standard modal". */
251
- bottom: var(--hk-sheet-bottom-gap, 0.375rem);
247
+ /* Sit flush on the bottom edge; the host-tunable --hk-sheet-bottom-gap
248
+ * lift (shared with the HkModal mobile docking) stays available so
249
+ * every bottom-up window reads as one sheet family. Was glued to
250
+ * bottom: 0, then floated at the 0.375rem family default both wrong
251
+ * for someone; the 2026-09-04 report settles the family on flush. */
252
+ bottom: var(--hk-sheet-bottom-gap, 0px);
252
253
  display: flex;
253
254
  flex-direction: column;
254
255
  /* The panel height hugs its content (up to the cap below); the sheet
@@ -266,7 +267,7 @@
266
267
  72vh,
267
268
  calc(
268
269
  100vh - var(--hk-sheet-top-inset, 4.25rem) -
269
- var(--hk-sheet-bottom-gap, 0.375rem)
270
+ var(--hk-sheet-bottom-gap, 0px)
270
271
  )
271
272
  );
272
273
  background: rgb(var(--color-surface));
@@ -285,6 +286,10 @@
285
286
  cursor: pointer;
286
287
  display: flex;
287
288
  justify-content: center;
289
+ /* Window chrome — never a text-selection start (matches the popover
290
+ * sheet grabber). */
291
+ user-select: none;
292
+ -webkit-user-select: none;
288
293
 
289
294
  &::before {
290
295
  content: "";
@@ -302,13 +307,17 @@
302
307
  * roughly another --space-16 of leading padding inside, so the old
303
308
  * single --space-16 left the title hanging left of every row it
304
309
  * named instead of reading as the list's heading. Hosts tune it via
305
- * --hk-sheet-title-inset; the trailing inset stays --space-16. */
310
+ * --hk-sheet-title-inset; the trailing inset stays --space-16.
311
+ * Size/weight match the popover sheet heading (--text-base, window
312
+ * chrome never text-selectable — 2026-09-04 sheet chrome pass). */
306
313
  padding: 0 var(--space-16) var(--space-8)
307
314
  var(--hk-sheet-title-inset, calc(var(--space-16) * 2));
308
- font-size: var(--text-xs);
315
+ font-size: var(--text-base);
309
316
  font-weight: 600;
310
317
  letter-spacing: 0.02em;
311
318
  color: rgb(var(--color-muted));
319
+ user-select: none;
320
+ -webkit-user-select: none;
312
321
  }
313
322
 
314
323
  /* Track host for the sheet list's overlay scrollbar — owns the flex
@@ -16,11 +16,13 @@ const here = dirname(fileURLToPath(import.meta.url));
16
16
  const src = readFileSync(join(here, "HkSelect.scss"), "utf-8");
17
17
 
18
18
  describe("HkSelectPanel mobile sheet docking contract", () => {
19
- it("floats the sheet on --hk-sheet-bottom-gap (not hard 0)", () => {
19
+ it("docks the sheet on the --hk-sheet-bottom-gap hook (flush family default)", () => {
20
20
  const block = src.match(/\.hk-select-sheet-panel\s*{[^}]*}/)?.[0] ?? "";
21
21
  expect(block).toContain("bottom: var(--hk-sheet-bottom-gap");
22
- // Same default as the HkModal sheet contract.
23
- expect(block).toMatch(/--hk-sheet-bottom-gap,\s*0\.375rem/);
22
+ // Family default sits flush on the bottom edge (2026-09-04 user
23
+ // report: the 0.375rem family default read as a seam); hosts may
24
+ // re-add a gap via the same custom property.
25
+ expect(block).toMatch(/--hk-sheet-bottom-gap,\s*0px/);
24
26
  });
25
27
 
26
28
  it("stacks the safe-area inset on the sheet list bottom padding", () => {