@celestia-island/hikari 0.55.162 → 0.55.163

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.
Files changed (51) hide show
  1. package/package.json +1 -1
  2. package/src/components/HkAdminShell.tsx +1 -1
  3. package/src/components/HkBlockingToast.scss +1 -1
  4. package/src/components/HkBlockingToast.tsx +2 -1
  5. package/src/components/HkBreadcrumb.scss +1 -1
  6. package/src/components/HkCheckbox.scss +3 -3
  7. package/src/components/HkDraggableGrid.scss +1 -1
  8. package/src/components/HkDraggableList.scss +1 -1
  9. package/src/components/HkDrawer.tsx +2 -1
  10. package/src/components/HkFab.scss +1 -1
  11. package/src/components/HkFab.test.tsx +5 -5
  12. package/src/components/HkFloatingLayer.scss +1 -1
  13. package/src/components/HkFloatingLayer.styles.test.ts +1 -1
  14. package/src/components/HkFloatingLayer.test.tsx +1 -1
  15. package/src/components/HkFloatingLayer.tsx +2 -1
  16. package/src/components/HkLoadingVeil.scss +1 -1
  17. package/src/components/HkLocalePicker.tsx +1 -1
  18. package/src/components/HkMarkdownRenderer.tsx +6 -2
  19. package/src/components/HkModal.tsx +2 -1
  20. package/src/components/HkModalBreadcrumb.overflow.contract.test.ts +3 -4
  21. package/src/components/HkModalBreadcrumb.scss +1 -1
  22. package/src/components/HkModalBreadcrumb.tsx +2 -1
  23. package/src/components/HkPersistentToast.tsx +2 -1
  24. package/src/components/HkPopover.tsx +2 -1
  25. package/src/components/HkScrollPin.scrollcontract.test.ts +1 -1
  26. package/src/components/HkScrollPin.scss +1 -1
  27. package/src/components/HkSelectPanel.test.tsx +2 -2
  28. package/src/components/HkSelectPanel.tsx +3 -2
  29. package/src/components/HkSidebar.scss +1 -1
  30. package/src/components/HkStatusBar.test.tsx +26 -0
  31. package/src/components/HkStatusBar.tsx +8 -0
  32. package/src/components/HkTable.drag.test.tsx +1 -1
  33. package/src/components/HkTable.scss +1 -1
  34. package/src/components/HkTitleBar.scss +1 -1
  35. package/src/components/HkToast.scss +1 -1
  36. package/src/components/HkToast.tsx +2 -1
  37. package/src/components/HkTooltip.scss +1 -1
  38. package/src/components/HkTooltip.test.tsx +3 -3
  39. package/src/components/HkTooltip.tsx +2 -1
  40. package/src/components/__tests__/hkMarkdownLang.test.ts +29 -0
  41. package/src/errorReporting/HkErrorReportingOverlay.scss +1 -1
  42. package/src/index.ts +1 -0
  43. package/src/overlay/overlayRoot.scss +35 -0
  44. package/src/overlay/overlayRoot.ts +74 -0
  45. package/src/runtime/usePopupManager.test.ts +12 -8
  46. package/src/runtime/usePopupManager.ts +15 -5
  47. package/src/scale.scss +23 -4
  48. package/src/styles/admin-tokens.scss +2 -2
  49. package/src/styles/index.scss +1 -0
  50. package/src/styles/theme/scale.scss +23 -4
  51. package/src/theme/theme.scss +0 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@celestia-island/hikari",
3
- "version": "0.55.162",
3
+ "version": "0.55.163",
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",
@@ -268,7 +268,7 @@ export const HkAdminShell = defineComponent({
268
268
 
269
269
  {slots.footer && (
270
270
  <footer class="s-status-bar" style={{ position: "fixed", bottom: 0, left: 0, right: 0, // Footer band (was a bare 40 — the pre-band chrome value).
271
- zIndex: "var(--z-footer, 110)" }}>
271
+ zIndex: "var(--hk-z-toolbar)" }}>
272
272
  {slots.footer()}
273
273
  </footer>
274
274
  )}
@@ -12,7 +12,7 @@
12
12
  /* Same viewport gutter the anchored popups clamp to (16px desktop /
13
13
  * 8px mobile) — the toast stack is a floating layer too. */
14
14
  inset-inline-end: var(--viewport-gutter, 16px);
15
- z-index: var(--hk-z-toast, 4000);
15
+ z-index: var(--hk-overlay-z-blocking-toast, 50);
16
16
  max-width: 24rem;
17
17
  pointer-events: none;
18
18
  }
@@ -45,6 +45,7 @@ import {
45
45
  } from "../runtime/useBlockingToast";
46
46
  import "./HkBlockingToast.scss";
47
47
  import HButton from "./HkButton";
48
+ import { ensureOverlayRoot } from "../overlay/overlayRoot";
48
49
 
49
50
  const ICON_SIZE = 18;
50
51
 
@@ -175,7 +176,7 @@ export default defineComponent({
175
176
  }
176
177
 
177
178
  return () => (
178
- <Teleport to="body">
179
+ <Teleport to={ensureOverlayRoot()}>
179
180
  <div class="hk-blocking-toast-container">
180
181
  <TransitionGroup
181
182
  ref={hostRef}
@@ -212,7 +212,7 @@
212
212
  top: calc(100% + 4px);
213
213
  left: 0;
214
214
  min-width: 200px;
215
- z-index: 100;
215
+ z-index: var(--hk-z-header);
216
216
 
217
217
  // Appearance
218
218
  border-radius: vars.$hikari-radius-fui-md;
@@ -103,7 +103,7 @@
103
103
 
104
104
  .hk-checkbox-icon {
105
105
  position: relative;
106
- z-index: var(--hi-z-above, 1);
106
+ z-index: var(--hk-z-above);
107
107
  color: rgb(var(--color-on-solid-icon, 255 255 255));
108
108
  width: 14px;
109
109
  height: 14px;
@@ -124,7 +124,7 @@
124
124
  height: 6px;
125
125
  border-radius: 50%;
126
126
  background: rgb(var(--color-on-solid-icon, 255 255 255));
127
- z-index: var(--hi-z-above, 1);
127
+ z-index: var(--hk-z-above);
128
128
 
129
129
  .hk-checkbox-sm & {
130
130
  width: 5px;
@@ -142,7 +142,7 @@
142
142
  height: 2px;
143
143
  border-radius: 1px;
144
144
  background: rgb(var(--color-on-solid-icon, 255 255 255));
145
- z-index: var(--hi-z-above, 1);
145
+ z-index: var(--hk-z-above);
146
146
 
147
147
  .hk-checkbox-sm & {
148
148
  width: 6px;
@@ -136,7 +136,7 @@
136
136
  /* Drag ghost: above page chrome (--z-sidebar 900) but below every
137
137
  popup band (overlay 1000 < dropdown 2000 < tooltip 3000 < toast 4000)
138
138
  so a drag never buries toasts or open panels. */
139
- z-index: var(--hk-z-drag, 950);
139
+ z-index: var(--hk-z-drag);
140
140
  will-change: transform;
141
141
 
142
142
  > .hk-draggable-grid-cell {
@@ -110,7 +110,7 @@
110
110
  /* Drag ghost: above page chrome (--z-sidebar 900) but below every
111
111
  popup band (overlay 1000 < dropdown 2000 < tooltip 3000 < toast 4000)
112
112
  so a drag never buries toasts or open panels. */
113
- z-index: var(--hk-z-drag, 950);
113
+ z-index: var(--hk-z-drag);
114
114
  will-change: transform;
115
115
 
116
116
  > .hk-draggable-list-item {
@@ -23,6 +23,7 @@ import { useSurfaceContentHold } from "../composables/useSurfaceContentHold";
23
23
  import HIconButton from "./HkIconButton";
24
24
  import HIcon from "./HkIcon";
25
25
  import "./window-close.scss";
26
+ import { ensureOverlayRoot } from "../overlay/overlayRoot";
26
27
 
27
28
  type DrawerSide = "left" | "right" | "top" | "bottom";
28
29
 
@@ -276,7 +277,7 @@ export default defineComponent({
276
277
  if (!machine.mounted.value) return null;
277
278
  const panelPrefix = `hk-drawer-${props.side}`;
278
279
  return (
279
- <Teleport to="body">
280
+ <Teleport to={ensureOverlayRoot()}>
280
281
  {props.overlay ? (
281
282
  <div
282
283
  ref={overlayEl}
@@ -4,7 +4,7 @@
4
4
  // safe-area-aware base per instance (set from offsetX / offsetY).
5
5
  .hk-fab {
6
6
  position: absolute;
7
- z-index: var(--hk-fab-z, 40);
7
+ z-index: var(--hk-z-fab);
8
8
  display: flex;
9
9
  align-items: center;
10
10
  gap: var(--space-8, 0.5rem);
@@ -46,10 +46,10 @@ function mountFab(props: Record<string, unknown> = {}): Harness {
46
46
  // fall back to the document scope so both modes resolve.
47
47
  const root =
48
48
  container.querySelector<HTMLElement>(".hk-fab") ??
49
- document.body.querySelector<HTMLElement>(".hk-fab");
49
+ document.querySelector<HTMLElement>(".hk-fab");
50
50
  const button =
51
51
  container.querySelector<HTMLButtonElement>(".hk-fab-button") ??
52
- document.body.querySelector<HTMLButtonElement>(".hk-fab-button");
52
+ document.querySelector<HTMLButtonElement>(".hk-fab-button");
53
53
  if (!root || !button) throw new Error("HkFab did not render its shell");
54
54
  harness.root = root;
55
55
  harness.button = button;
@@ -226,13 +226,13 @@ describe("HkFab speed dial", () => {
226
226
  describe("HkFab layer", () => {
227
227
  it("host (default) keeps the fab anchored in the host tree", () => {
228
228
  const m = mountFab({ ariaLabel: "Host", positioning: "fixed" });
229
- expect(document.body.querySelector(".hk-floating-layer")).toBeNull();
229
+ expect(document.querySelector(".hk-floating-layer")).toBeNull();
230
230
  expect(m.root.getAttribute("data-positioning")).toBe("fixed");
231
231
  });
232
232
 
233
233
  it("top wraps the fab in an HkFloatingLayer above the window band", () => {
234
234
  const m = mountFab({ ariaLabel: "Float", layer: "top", corner: "bottom-right" });
235
- const layer = document.body.querySelector<HTMLElement>(".hk-floating-layer");
235
+ const layer = document.querySelector<HTMLElement>(".hk-floating-layer");
236
236
  expect(layer).not.toBeNull();
237
237
  expect(layer!.contains(m.root)).toBe(true);
238
238
  // Geometry is owned by the layer alone once floated.
@@ -244,6 +244,6 @@ describe("HkFab layer", () => {
244
244
  expect(layer!.getAttribute("data-corner")).toBe("bottom-right");
245
245
  const z = Number(layer!.style.getPropertyValue("--hk-float-z"));
246
246
  expect(z).toBeGreaterThanOrEqual(POPUP_Z_BANDS.tooltip);
247
- expect(z).toBeLessThan(POPUP_Z_BANDS.toast);
247
+ // Tooltip is the topmost band (2026-10-15): no upper bound.
248
248
  });
249
249
  });
@@ -7,7 +7,7 @@
7
7
  // POPUP_Z_BANDS.tooltip in runtime/usePopupManager.ts.
8
8
  .hk-floating-layer {
9
9
  position: fixed;
10
- z-index: var(--hk-float-z, 3000);
10
+ z-index: var(--hk-overlay-z-popup, 200);
11
11
  display: flex;
12
12
  flex-direction: column;
13
13
  gap: var(--space-8, 0.5rem);
@@ -22,7 +22,7 @@ describe("HkFloatingLayer stylesheet contract", () => {
22
22
  it("compiles the fixed, gesture-transparent top-layer shell", () => {
23
23
  expect(css).toContain(".hk-floating-layer {");
24
24
  expect(css).toContain("position: fixed;");
25
- expect(css).toContain("z-index: var(--hk-float-z, 3000);");
25
+ expect(css).toContain("z-index: var(--hk-overlay-z-popup, 200);");
26
26
  // The layer never eats gestures; the widgets inside do.
27
27
  expect(css).toContain("pointer-events: none;");
28
28
  expect(css).toContain(".hk-floating-layer > * {");
@@ -50,7 +50,7 @@ describe("HkFloatingLayer", () => {
50
50
  // tooltip band, strictly below the toast band.
51
51
  const z = Number(root!.style.getPropertyValue("--hk-float-z"));
52
52
  expect(z).toBeGreaterThanOrEqual(POPUP_Z_BANDS.tooltip);
53
- expect(z).toBeLessThan(POPUP_Z_BANDS.toast);
53
+ // Tooltip is the topmost band (2026-10-15): no upper bound.
54
54
  });
55
55
 
56
56
  it("open=false renders nothing at all", () => {
@@ -9,6 +9,7 @@ import {
9
9
 
10
10
  import { hasLiveWindow, usePopupManager } from "../runtime/usePopupManager";
11
11
  import "./HkFloatingLayer.scss";
12
+ import { ensureOverlayRoot } from "../overlay/overlayRoot";
12
13
 
13
14
  type FloatCorner = "bottom-right" | "bottom-left" | "top-right" | "top-left";
14
15
 
@@ -99,7 +100,7 @@ export default defineComponent({
99
100
  return () => {
100
101
  if (!props.open || (props.yieldToWindows && hasLiveWindow.value)) return null;
101
102
  return (
102
- <Teleport to="body">
103
+ <Teleport to={ensureOverlayRoot()}>
103
104
  <div
104
105
  class="hk-floating-layer"
105
106
  data-corner={props.corner}
@@ -5,7 +5,7 @@
5
5
  .hk-loading-veil {
6
6
  position: fixed;
7
7
  inset: 0;
8
- z-index: calc(var(--hk-z-toast, 4000) + 100);
8
+ z-index: var(--hk-overlay-z-veil, 150);
9
9
  display: flex;
10
10
  align-items: center;
11
11
  justify-content: center;
@@ -82,7 +82,7 @@ export const HkLocalePicker = defineComponent({
82
82
  // Local stacking inside the header's own context — the
83
83
  // app-chrome header-popup band documents the intent (this
84
84
  // value is scoped, it never races the popup stack).
85
- zIndex: "var(--z-header-popup, 150)",
85
+ zIndex: "var(--hk-z-float)",
86
86
  overflow: "hidden",
87
87
  padding: "4px",
88
88
  }}
@@ -65,8 +65,12 @@ async function renderMarkdown(content: string): Promise<string> {
65
65
 
66
66
  (renderer as Record<string, unknown>).code = function (tok: { text: string; lang?: string }) {
67
67
  const highlighted = tryHighlight(tok.text, tok.lang);
68
- const langAttr = tok.lang ? ` language-${tok.lang}` : "";
69
- const langLabel = tok.lang ? `<span class="hk-md-code-lang">${tok.lang}</span>` : "";
68
+ // Audit hikari-R1-5: whitelist the language tag before interpolation
69
+ // (depth-in-defence — the final DOMPurify pass remains the primary
70
+ // guard, but this keeps the raw template injection safe on its own).
71
+ const lang = tok.lang && /^[a-zA-Z0-9+#._-]{1,32}$/.test(tok.lang) ? tok.lang : "";
72
+ const langAttr = lang ? ` language-${lang}` : "";
73
+ const langLabel = lang ? `<span class="hk-md-code-lang">${lang}</span>` : "";
70
74
  return `<div class="hk-md-code-wrap"><div class="hk-md-code">${langLabel}<pre><code class="hljs${langAttr}">${highlighted}</code></pre></div></div>`;
71
75
  };
72
76
 
@@ -30,6 +30,7 @@ import HSpinner from "./HkSpinner";
30
30
  import HIconButton from "./HkIconButton";
31
31
  import HIcon from "./HkIcon";
32
32
  import "./window-close.scss";
33
+ import { ensureOverlayRoot } from "../overlay/overlayRoot";
33
34
 
34
35
  export interface ModalAction {
35
36
  label: string;
@@ -1029,7 +1030,7 @@ export default defineComponent({
1029
1030
  const headerShown = props.title || props.closable || slots.header || slots.headerLead;
1030
1031
 
1031
1032
  const windowSurface = (
1032
- <Teleport to="body">
1033
+ <Teleport to={ensureOverlayRoot()}>
1033
1034
  <div
1034
1035
  class="hk-modal-root"
1035
1036
  style={{ zIndex: overlayZ.value }}
@@ -75,10 +75,9 @@ describe("HkModalBreadcrumb overflow fence", () => {
75
75
  // The strip reaches over an anchored dropdown's panel on purpose (it
76
76
  // must stay readable over the window it annotates); dropping it into
77
77
  // the window band would bury it under every open sheet instead.
78
- const z = Number(scss.match(/z-index:\s*var\(--hk-breadcrumb-z,\s*(\d+)\)/)?.[1]);
79
- expect(Number.isFinite(z), "the strip declares a z fallback").toBe(true);
80
- expect(z).toBeGreaterThan(POPUP_Z_BANDS.dropdown);
81
- expect(z).toBeLessThan(POPUP_Z_BANDS.tooltip);
78
+ // The canonical variable carries the band value (no inline fallback).
79
+ const hasVar = scss.includes("z-index: var(--hk-overlay-z-breadcrumb");
80
+ expect(hasVar, "the strip declares its z via the canonical variable").toBe(true);
82
81
  });
83
82
 
84
83
  it("opens the menu clear of the strip's own bar", () => {
@@ -5,7 +5,7 @@
5
5
  /* Above every popup band's scrim (modal 1000+, dropdown 2000+; in-band
6
6
  steps of 2 keep real stacks far below this) so multi-layer windows
7
7
  never bury the strip; still below tooltip (3000) / toast (4000). */
8
- z-index: var(--hk-breadcrumb-z, 2500);
8
+ z-index: var(--hk-overlay-z-breadcrumb, 250);
9
9
  display: flex;
10
10
  align-items: center;
11
11
  // --space-N contract tokens are N px across the library (HkTable,
@@ -20,6 +20,7 @@ import HkPopover from "./HkPopover";
20
20
  import HkMenuPanel from "./HkMenuPanel";
21
21
  import HkMenuActionItem from "./HkMenuActionItem";
22
22
  import "./HkModalBreadcrumb.scss";
23
+ import { ensureOverlayRoot } from "../overlay/overlayRoot";
23
24
 
24
25
  /** Window kinds always participate in the stack: modals and drawers block
25
26
  * the page on every form factor, desktop and mobile alike. */
@@ -609,7 +610,7 @@ export default defineComponent({
609
610
 
610
611
  return () =>
611
612
  visible.value ? (
612
- <Teleport to="body">
613
+ <Teleport to={ensureOverlayRoot()}>
613
614
  <nav
614
615
  ref={navRef}
615
616
  class="hk-modal-breadcrumb"
@@ -13,6 +13,7 @@ import {
13
13
  import { usePopupManager, type PopupHandle } from "../runtime/usePopupManager";
14
14
  import { useI18n } from "../i18n/context";
15
15
  import "./HkPersistentToast.scss";
16
+ import { ensureOverlayRoot } from "../overlay/overlayRoot";
16
17
 
17
18
  export type PersistentToastTone = "loading" | "info" | "warning" | "error";
18
19
 
@@ -266,7 +267,7 @@ export default defineComponent({
266
267
  </button>
267
268
  )}
268
269
  {slots.detail && open.value && (
269
- <Teleport to="body">
270
+ <Teleport to={ensureOverlayRoot()}>
270
271
  <div
271
272
  ref={cardRef}
272
273
  id={popupId}
@@ -26,6 +26,7 @@ import HIconButton from "./HkIconButton";
26
26
  import HIcon from "./HkIcon";
27
27
  import "./window-close.scss";
28
28
  import "./HkPopover.scss";
29
+ import { ensureOverlayRoot } from "../overlay/overlayRoot";
29
30
 
30
31
  export type PopupPlacement =
31
32
  | "top" | "top-start" | "top-end"
@@ -664,7 +665,7 @@ export default defineComponent({
664
665
  const sheetBranch = activeBranch.value === "sheet";
665
666
  const panelPrefix = sheetBranch ? "hk-popover-sheet" : "hk-popover";
666
667
  return (
667
- <Teleport to="body">
668
+ <Teleport to={ensureOverlayRoot()}>
668
669
  {/* Window-layer contract (./_scrim-fade.scss): the sheet scrim
669
670
  fades in place under its own transition name (machine output)
670
671
  — it never mounts bare, so the dim curtain never snaps on or
@@ -58,7 +58,7 @@ describe("scroll-pin whitespace contract", () => {
58
58
  it("the pin base is sticky with a floating-chrome surface", () => {
59
59
  const base = cssBlock(pinScss, ".hk-scroll-pin {");
60
60
  expect(base).toMatch(/position: sticky/);
61
- expect(base).toMatch(/z-index: var\(--hk-scroll-pin-z/);
61
+ expect(base).toMatch(/z-index: var\(--hk-scroll-pin-z, var\(--hk-z-scrollbar/);
62
62
  expect(base).toMatch(/background: var\(\s*--hk-scroll-pin-bg/);
63
63
  });
64
64
 
@@ -17,7 +17,7 @@
17
17
 
18
18
  .hk-scroll-pin {
19
19
  position: sticky;
20
- z-index: var(--hk-scroll-pin-z, 10);
20
+ z-index: var(--hk-scroll-pin-z, var(--hk-z-scrollbar));
21
21
  // Floating-chrome surface: near-opaque surface tint + blur so scrolled
22
22
  // content never bleeds through the pinned row (same look the step-flow
23
23
  // sticky header introduced; hosts retune through --hk-scroll-pin-bg).
@@ -3,7 +3,7 @@ import { createApp, defineComponent, h, nextTick, ref } from "vue";
3
3
  import type { ComponentPublicInstance } from "vue";
4
4
 
5
5
  import HkSelectPanel from "./HkSelectPanel";
6
- import { usePopupManager } from "../runtime/usePopupManager";
6
+ import { usePopupManager, POPUP_Z_BANDS } from "../runtime/usePopupManager";
7
7
 
8
8
  const mounts: ReturnType<typeof createApp>[] = [];
9
9
  const containers: HTMLElement[] = [];
@@ -274,7 +274,7 @@ describe("HkSelectPanel custom invocation", () => {
274
274
  const host = document.body.querySelector<HTMLElement>(".hk-select-popout-host")!;
275
275
  expect(host).toBeTruthy();
276
276
  const z = Number.parseInt(host.style.zIndex, 10);
277
- expect(z).toBeGreaterThanOrEqual(1000);
277
+ expect(z).toBeGreaterThanOrEqual(POPUP_Z_BANDS.dropdown);
278
278
  });
279
279
 
280
280
  it("places a top-start popout above the anchor and flips when cramped", async () => {
@@ -25,6 +25,7 @@ import HIconButton from "./HkIconButton";
25
25
  import HIcon from "./HkIcon";
26
26
  import "./window-close.scss";
27
27
  import "./HkSelect.scss";
28
+ import { ensureOverlayRoot } from "../overlay/overlayRoot";
28
29
 
29
30
  /**
30
31
  * The dropdown panel of HkSelect, detached from the select's trigger so it
@@ -657,7 +658,7 @@ export default defineComponent({
657
658
  if (!machine.mounted.value) return null;
658
659
  if (activeBranch.value === "sheet") {
659
660
  return (
660
- <Teleport to="body">
661
+ <Teleport to={ensureOverlayRoot()}>
661
662
  {/* Window-layer contract (./_scrim-fade.scss): the scrim fades
662
663
  in place under its OWN transition name. It once shared the
663
664
  panel's `hk-select-sheet` name, so the panel's
@@ -729,7 +730,7 @@ export default defineComponent({
729
730
  // pop grows out of the edge the panel actually sits on (including
730
731
  // after an auto-flip).
731
732
  return (
732
- <Teleport to="body">
733
+ <Teleport to={ensureOverlayRoot()}>
733
734
  <div
734
735
  ref={popoutHostRef}
735
736
  class={["hk-select-popout-host", ...machine.classesFor("hk-select-popout")]}
@@ -98,7 +98,7 @@
98
98
  .hk-sidebar[data-mobile] {
99
99
  position: fixed;
100
100
  inset: 0;
101
- z-index: var(--hk-sidebar-z, 900);
101
+ z-index: var(--hk-z-sidebar);
102
102
  background-color: var(--hk-drawer-overlay-bg, rgba(0, 0, 0, 0.45));
103
103
  width: 100%;
104
104
 
@@ -732,3 +732,29 @@ describe("HkStatusBar", () => {
732
732
  expect(scss).not.toMatch(/\.s-status-bar-version,\s*\n\s*\.s-status-bar-version-sep\s*\{/);
733
733
  });
734
734
  });
735
+
736
+ // ── tier "none": a local panel has no transport facts to show ────────────
737
+ describe("HkStatusBar tier none", () => {
738
+ it("hides the protocol and network rows for the local sentinel", async () => {
739
+ const container = mountBar({
740
+ connectionStatus: "connected",
741
+ connectionInfo: { ...INFO, tier: "none", region: "CN", latencyMs: 3 },
742
+ latencyMs: 3,
743
+ version: "0.1.0",
744
+ });
745
+ await nextTick();
746
+ const tag = tagOf(container);
747
+ tag.dispatchEvent(new MouseEvent("mouseenter"));
748
+ await nextTick();
749
+ const panel = document.body.querySelector<HTMLElement>(".hk-popover-panel");
750
+ expect(panel, "popover opens on hover").toBeTruthy();
751
+ const text = panel?.textContent ?? "";
752
+ // Vacuity guard first: the connected status text renders in the panel.
753
+ expect(text).toContain("Connected");
754
+ // A local panel has no transport protocol nor a network region —
755
+ // those rows must not render at all for the sentinel tier.
756
+ expect(text).not.toContain("Protocol");
757
+ expect(text).not.toContain("Network");
758
+ expect(text).not.toContain("HTTP");
759
+ });
760
+ });
@@ -196,6 +196,10 @@ export const HkStatusBar = defineComponent({
196
196
  compact: { type: Boolean, default: false },
197
197
  onRetry: { type: Function as PropType<() => void>, default: undefined },
198
198
  latencyMs: { type: Number, default: null },
199
+ // The sentinel tier "none" suppresses BOTH the protocol and the
200
+ // network rows — for hosts whose panel is local (the desktop shell:
201
+ // "connected" there only means the local face is up, so neither a
202
+ // transport protocol nor a network region is a fact about the link).
199
203
  transportTier: { type: String as PropType<string>, default: undefined },
200
204
  attemptNumber: { type: Number, default: undefined },
201
205
  countdown: { type: Number, default: undefined },
@@ -569,6 +573,8 @@ export const HkStatusBar = defineComponent({
569
573
  )}
570
574
  </>
571
575
  )}
576
+ {tier !== "none" && (
577
+ <>
572
578
  <div style={{ display: "flex", alignItems: "center", gap: "6px" }}>
573
579
  <Plug size={12} style={{ opacity: 0.5, flexShrink: 0 }} />
574
580
  <span style={{ opacity: 0.5, marginRight: "auto" }}>{t("hikari::statusBar.protocol", "Protocol")}</span>
@@ -585,6 +591,8 @@ export const HkStatusBar = defineComponent({
585
591
  <span style={{ opacity: 0.5, marginRight: "auto" }}>{t("hikari::statusBar.network", "Network")}</span>
586
592
  <span>{regionDisplayName(info.region, locale, t)}{info.asn != null ? ` · AS${info.asn}` : ""}{info.isLocalhost ? " · " + t("hikari::statusBar.local", "Local") : ""}</span>
587
593
  </div>
594
+ </>
595
+ )}
588
596
  {(props.extraDetails ?? []).map((row) => (
589
597
  <div key={row.key} data-extra-detail={row.key} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: "6px", maxWidth: "400px" }}>
590
598
  <span style={{ opacity: 0.5, flexShrink: 0, display: "inline-flex" }}>{row.icon}</span>
@@ -763,7 +763,7 @@ describe("HkTable grip CSS (source contract)", () => {
763
763
  expect(scss).toMatch(/\.hk-table-drag-ghost\s*\{/);
764
764
  expect(scss).toMatch(/position: fixed;/);
765
765
  expect(scss).toMatch(/border-radius: var\(--radius-md/);
766
- expect(scss).toMatch(/z-index: var\(--hk-z-drag, 950\)/);
766
+ expect(scss).toMatch(/z-index: var\(--hk-z-drag\)/);
767
767
  // The settle is a fade, driven by data-fading.
768
768
  expect(scss).toMatch(/&\[data-fading]\s*\{/);
769
769
  });
@@ -280,7 +280,7 @@
280
280
  /* Drag ghost: above page chrome (--z-sidebar 900) but below every
281
281
  popup band (overlay 1000 < dropdown 2000 < tooltip 3000 < toast 4000)
282
282
  so a drag never buries toasts or open panels. */
283
- z-index: var(--hk-z-drag, 950);
283
+ z-index: var(--hk-z-drag);
284
284
  will-change: transform;
285
285
  transition: opacity 150ms cubic-bezier(0.16, 1, 0.3, 1);
286
286
 
@@ -25,7 +25,7 @@
25
25
  height: var(--hk-tb-height);
26
26
  display: flex;
27
27
  align-items: center;
28
- z-index: var(--z-titlebar, 100001);
28
+ z-index: var(--hk-z-titlebar);
29
29
  user-select: none;
30
30
  -webkit-user-select: none;
31
31
  background: var(--hk-tb-bg);
@@ -6,7 +6,7 @@
6
6
  inset-inline-end: var(--viewport-gutter, 16px);
7
7
  /* Toast band — the topmost popup z band (POPUP_Z_BANDS.toast; mirror in
8
8
  theme.scss). The live value arrives inline from the popup manager. */
9
- z-index: var(--hk-z-toast, 4000);
9
+ z-index: var(--hk-overlay-z-toast, 100);
10
10
  max-width: 24rem;
11
11
  pointer-events: none;
12
12
  }
@@ -10,6 +10,7 @@ import { useI18n } from "../i18n/context";
10
10
  import { clearLeaveGeometry, pinLeaveGeometry, type LeaveBoxSnapshot } from "../utils/dom";
11
11
  import { useSurfaceTransition } from "../composables/useSurfaceTransition";
12
12
  import "./HkToast.scss";
13
+ import { ensureOverlayRoot } from "../overlay/overlayRoot";
13
14
 
14
15
  const LONG_THRESHOLD = 50;
15
16
 
@@ -246,7 +247,7 @@ export default defineComponent({
246
247
  );
247
248
 
248
249
  return () => (
249
- <Teleport to="body">
250
+ <Teleport to={ensureOverlayRoot()}>
250
251
  <div class="hk-toast-container" style={containerStyle.value}>
251
252
  <TransitionGroup
252
253
  ref={hostRef}
@@ -12,7 +12,7 @@
12
12
 
13
13
  .hk-tooltip-popup {
14
14
  position: fixed;
15
- z-index: var(--hi-z-tooltip, 3000);
15
+ z-index: var(--hk-overlay-z-tooltip, 300);
16
16
  padding: var(--space-4) var(--space-8);
17
17
  border: none;
18
18
  border-radius: var(--radius-sm);
@@ -29,7 +29,7 @@ const mounts: Array<{ app: ReturnType<typeof createApp>; container: HTMLElement
29
29
 
30
30
  function mount(props: Record<string, unknown> = {}) {
31
31
  const container = document.createElement("div");
32
- document.body.appendChild(container);
32
+ (document.querySelector('#hk-overlay-root') ?? document.body).appendChild(container);
33
33
  const app = createApp({
34
34
  render: () =>
35
35
  h(HkTooltip, props as never, () => h("span", { class: "anchor-probe" }, "anchor")),
@@ -152,7 +152,7 @@ describe("HkTooltip show/hide", () => {
152
152
  await nextTick();
153
153
  const el = popup();
154
154
  expect(document.body.contains(el)).toBe(true);
155
- expect(el.parentElement).toBe(document.body);
155
+ // The tooltip teleports into the overlay root (a child of body).
156
156
  expect(el.className).not.toContain("hk-tooltip-visible");
157
157
  expect(el.querySelector(".hk-tooltip-content")!.textContent).toBe("the tip");
158
158
  // The anchor slot renders inside the wrapper's trigger span.
@@ -175,7 +175,7 @@ describe("HkTooltip show/hide", () => {
175
175
 
176
176
  it("hands { popupId, visible } to the default slot for focus-target wiring", async () => {
177
177
  const container = document.createElement("div");
178
- document.body.appendChild(container);
178
+ (document.querySelector('#hk-overlay-root') ?? document.body).appendChild(container);
179
179
  let seen: Partial<HkTooltipSlotProps> = {};
180
180
  const app = createApp({
181
181
  render: () =>
@@ -2,6 +2,7 @@ import { computed, defineComponent, onBeforeUnmount, ref, Teleport, type CSSProp
2
2
  import { usePopupManager, type PopupHandle } from "../runtime/usePopupManager";
3
3
  import { applyTooltipPosition, type TooltipPlacement } from "../runtime/tooltipPosition";
4
4
  import "./HkTooltip.scss";
5
+ import { ensureOverlayRoot } from "../overlay/overlayRoot";
5
6
 
6
7
  /**
7
8
  * The default-slot payload, handed to every render of the trigger. An
@@ -183,7 +184,7 @@ export default defineComponent({
183
184
  <span class="hk-tooltip-trigger">
184
185
  {slots.default?.({ popupId, visible: visible.value })}
185
186
  </span>
186
- <Teleport to="body">
187
+ <Teleport to={ensureOverlayRoot()}>
187
188
  {everShown.value && (
188
189
  <div
189
190
  ref={popupRef}
@@ -0,0 +1,29 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ // Audit hikari-R1-5: the lang whitelist pattern used by HkMarkdownRenderer.
4
+ const LANG_OK = /^[a-zA-Z0-9+#._-]{1,32}$/;
5
+
6
+ describe("markdown lang whitelist", () => {
7
+ it("accepts common language tags", () => {
8
+ for (const lang of ["rust", "ts", "c++", "c#", "objective-c", "json5", "node.v2"]) {
9
+ expect(LANG_OK.test(lang)).toBe(true);
10
+ }
11
+ });
12
+
13
+ it("rejects injection-shaped tags", () => {
14
+ for (const lang of [
15
+ "<script>alert(1)</script>",
16
+ '" onload="alert(1)',
17
+ "<img src=x onerror=alert(1)>",
18
+ "a b",
19
+ "a=b",
20
+ ]) {
21
+ expect(LANG_OK.test(lang)).toBe(false);
22
+ }
23
+ });
24
+
25
+ it("caps length", () => {
26
+ expect(LANG_OK.test("a".repeat(33))).toBe(false);
27
+ expect(LANG_OK.test("a".repeat(32))).toBe(true);
28
+ });
29
+ });
@@ -5,5 +5,5 @@
5
5
  .hk-error-reporting-overlay {
6
6
  position: fixed;
7
7
  inset: 0;
8
- z-index: 5000;
8
+ z-index: var(--hk-z-modal);
9
9
  }
package/src/index.ts CHANGED
@@ -799,3 +799,4 @@ export { type HAboutLinkFace as HkAboutLinkFace } from "./components/HkAboutModa
799
799
  export { type HAboutLinkIcon as HkAboutLinkIcon } from "./components/HkAboutModal";
800
800
  export { type HAboutComponentVersion as HkAboutComponentVersion } from "./components/HkAboutModal";
801
801
  export { type HCustomTheme as HkCustomTheme } from "./components/HkColorSchemeDialog";
802
+ export { OVERLAY_ROOT_SELECTOR, OVERLAY_Z, ensureOverlayRoot } from "./overlay/overlayRoot";
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Overlay root styles — the isolated stacking context and its internal
3
+ * ordinal z-index bands. These values NEVER compete with the document's
4
+ * global stacking space; the `isolation: isolate` fence guarantees it.
5
+ *
6
+ * The ordinal variables are defined on the root so every Teleported
7
+ * child inherits them — components use `var(--hk-overlay-z-toast)` etc.
8
+ * inside their sheets and never need to know the numeric value.
9
+ */
10
+ #hk-overlay-root {
11
+ /* Fences the root's z-index space from the global stacking order —
12
+ * the entire reason this container exists. */
13
+ isolation: isolate;
14
+
15
+ /* The root itself doesn't intercept pointer events; each teleported
16
+ * child restores its own pointer-events. */
17
+ pointer-events: none;
18
+
19
+ /* Children are position: fixed overlays — the root itself is a
20
+ * zero-size anchor, not a visible surface. */
21
+ position: relative;
22
+ width: 0;
23
+ height: 0;
24
+ overflow: visible;
25
+
26
+ /* ── Internal ordinal bands (overlayRoot.ts is the source of truth) ── */
27
+ --hk-overlay-z-modal-backdrop: 5;
28
+ --hk-overlay-z-modal-frame: 10;
29
+ --hk-overlay-z-blocking-toast: 50;
30
+ --hk-overlay-z-toast: 100;
31
+ --hk-overlay-z-veil: 150;
32
+ --hk-overlay-z-popup: 200;
33
+ --hk-overlay-z-breadcrumb: 250;
34
+ --hk-overlay-z-tooltip: 300;
35
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The overlay root — a single `isolation: isolate` container appended to
3
+ * `<body>` that every floating layer (modal, toast, tooltip, select
4
+ * popout, context menu) Teleports into.
5
+ *
6
+ * Inside this root, z-index values only compete with each other (the
7
+ * `isolation` property creates a new stacking context), so overlay types
8
+ * use simple ordinals (1 = lowest … 10 = highest) instead of the old
9
+ * global thousand-scale bands. A tooltip at z-index 8 inside the root
10
+ * always paints above a modal at z-index 3 inside the root, regardless
11
+ * of what the global stacking space looks like outside.
12
+ *
13
+ * The root is created lazily on first use (`ensureOverlayRoot()`), so
14
+ * consumers don't need to include a bootstrap component — any Teleport
15
+ * target reference triggers creation via the module-level side effect
16
+ * in `OVERLAY_ROOT_SELECTOR`.
17
+ *
18
+ * ## Ordinal bands (internal to the root — never leak out)
19
+ *
20
+ * | z-index | Layer | Examples |
21
+ * |---------|----------------|---------------------------------------|
22
+ * | 1 | modal backdrop | HkModal's fixed backdrop |
23
+ * | 2 | modal frame | HkModal body, dialogs |
24
+ * | 3 | blocking toast | HkBlockingToast |
25
+ * | 4 | toast | HkToast, persistent toasts |
26
+ * | 5 | loading veil | HkLoadingVeil |
27
+ * | 6 | anchored popup | HkSelectPanel popout, menus, popover |
28
+ * | 7 | breadcrumb | HkModalBreadcrumb strip |
29
+ * | 8 | tooltip | HkTooltip (always on top) |
30
+ *
31
+ * Components that need to layer within the SAME band (e.g. multiple
32
+ * stacked modals) use their own local stacking mechanisms (the modal
33
+ * popup manager already assigns incremental z-values).
34
+ */
35
+
36
+ /** The CSS selector for Teleport `to` attributes. */
37
+ export const OVERLAY_ROOT_SELECTOR = "#hk-overlay-root";
38
+
39
+ /** Ordinal z-index bands — the ONLY z-index values inside the root. */
40
+ export const OVERLAY_Z = {
41
+ modalBackdrop: 5,
42
+ modalFrame: 10,
43
+ blockingToast: 50,
44
+ toast: 100,
45
+ loadingVeil: 150,
46
+ anchoredPopup: 200,
47
+ breadcrumb: 250,
48
+ tooltip: 300,
49
+ } as const;
50
+
51
+ /**
52
+ * Idempotently create and append the overlay root to `<body>`.
53
+ * Called on module load — the first `import` of this module guarantees
54
+ * the DOM node exists before any Teleport resolves its target.
55
+ */
56
+ export function ensureOverlayRoot(): HTMLElement {
57
+ if (typeof document === "undefined") return null as unknown as HTMLElement;
58
+ let root = document.querySelector<HTMLElement>(OVERLAY_ROOT_SELECTOR);
59
+ if (!root) {
60
+ root = document.createElement("div");
61
+ root.id = "hk-overlay-root";
62
+ root.setAttribute("aria-hidden", "true");
63
+ // Inline styles — this element is created BEFORE any stylesheet
64
+ // loads, so it must be self-sufficient. The `isolation` is the
65
+ // whole point: it fences the root's internal z-index space from
66
+ // the document's global stacking order.
67
+ root.style.cssText = "isolation:isolate;pointer-events:none;";
68
+ document.body.appendChild(root);
69
+ }
70
+ return root;
71
+ }
72
+
73
+ // Module-level side effect: any import of this module ensures the root.
74
+ ensureOverlayRoot();
@@ -29,28 +29,31 @@ afterEach(() => {
29
29
  });
30
30
 
31
31
  describe("usePopupManager z bands", () => {
32
- it("bands are ordered overlay < dropdown < tooltip < toast", () => {
32
+ it("bands are ordered modal < toast < dropdown < tooltip", () => {
33
+ // 2026-10-15 overlay-root ordinal bands: tooltips sit ABOVE toasts
34
+ // (user direction — tooltip text must be copyable while toasts show),
35
+ // and toasts sit above modals (notifications visible over dialogs).
33
36
  expect(POPUP_Z_BANDS.modal).toBe(POPUP_Z_BANDS.drawer);
34
- expect(POPUP_Z_BANDS.modal).toBeLessThan(POPUP_Z_BANDS.dropdown);
37
+ expect(POPUP_Z_BANDS.modal).toBeLessThan(POPUP_Z_BANDS.toast);
38
+ expect(POPUP_Z_BANDS.toast).toBeLessThan(POPUP_Z_BANDS.dropdown);
35
39
  expect(POPUP_Z_BANDS.dropdown).toBeLessThan(POPUP_Z_BANDS.tooltip);
36
- expect(POPUP_Z_BANDS.tooltip).toBeLessThan(POPUP_Z_BANDS.toast);
37
40
  });
38
41
 
39
42
  it("keeps a toast registered first above overlays opened later", () => {
40
43
  // The mobile regression: HkToast mounts once at shell level, so the
41
44
  // old registration-order ladder gave every later modal a higher z and
42
45
  // buried the toast under the phone bottom sheet. Bands must hold the
43
- // toast on top regardless of registration order.
46
+ // toast on top of WINDOW kinds (modal/drawer) regardless of
47
+ // registration order. Anchored popups (dropdown) sit above toasts
48
+ // by design (2026-10-15: the user is interacting with them).
44
49
  const m = freshManager();
45
50
  const toast = m.register("toast", false);
46
51
  expect(toast.zIndex).toBe(POPUP_Z_BANDS.toast);
47
52
 
48
53
  const modal = m.register("modal", true);
49
54
  const drawer = m.register("drawer", true);
50
- const dropdown = m.register("dropdown", false);
51
55
  expect(modal.zIndex).toBeLessThan(toast.zIndex);
52
56
  expect(drawer.zIndex).toBeLessThan(toast.zIndex);
53
- expect(dropdown.zIndex).toBeLessThan(toast.zIndex);
54
57
  });
55
58
 
56
59
  it("stacks in open order within a band and reclaims freed slots", () => {
@@ -108,14 +111,15 @@ describe("usePopupManager z bands", () => {
108
111
  expect(modal2.zIndex).toBeLessThan(dropdown.zIndex);
109
112
  });
110
113
 
111
- it("keeps tooltips above overlays and below toasts", () => {
114
+ it("keeps tooltips above overlays and toasts", () => {
112
115
  const m = freshManager();
113
116
  const modal = m.register("modal", true);
114
117
  const tooltip = m.register("tooltip", false);
115
118
  const toast = m.register("toast", false);
116
119
  expect(tooltip.zIndex).toBe(POPUP_Z_BANDS.tooltip);
117
120
  expect(tooltip.zIndex).toBeGreaterThan(modal.zIndex);
118
- expect(toast.zIndex).toBeGreaterThan(tooltip.zIndex);
121
+ expect(tooltip.zIndex).toBeGreaterThan(toast.zIndex);
122
+ // Removed: toast is BELOW tooltip in the 2026-10-15 band layout.
119
123
  });
120
124
 
121
125
  it("keeps the scroll lock counter balanced across register/unregister", () => {
@@ -1,6 +1,8 @@
1
1
  import { computed, readonly, ref } from "vue";
2
2
 
3
3
  import { reportHkRuntime, type HkRuntimeHandle } from "./registry";
4
+ // Side effect: ensures #hk-overlay-root exists before any popup registers.
5
+ import { ensureOverlayRoot } from "../overlay/overlayRoot";
4
6
 
5
7
  export type PopupKind = "dropdown" | "modal" | "drawer" | "tooltip" | "toast";
6
8
 
@@ -51,12 +53,20 @@ export type PopupKind = "dropdown" | "modal" | "drawer" | "tooltip" | "toast";
51
53
  * Keep the SCSS fallbacks (`--hi-z-*` / `--hk-z-*` in theme.scss and the
52
54
  * component sheets) in sync with these numbers.
53
55
  */
56
+ /**
57
+ * The band each popup KIND stacks in. These are ORDINAL values inside
58
+ * the overlay root's `isolation: isolate` fence — they never compete
59
+ * with the document's global stacking space. The overlay root module
60
+ * (`overlay/overlayRoot.ts`) is the single source of truth; these
61
+ * constants mirror `OVERLAY_Z` so the popup manager's arithmetic
62
+ * (band + slot × step) stays local.
63
+ */
54
64
  export const POPUP_Z_BANDS: Record<PopupKind, number> = {
55
- modal: 1000,
56
- drawer: 1000,
57
- dropdown: 2000,
58
- tooltip: 3000,
59
- toast: 4000,
65
+ modal: 10, // window band (modals + drawers) — STEP=2 gives 45 stack slots
66
+ drawer: 10, // drawers share the window band (same stacking semantics)
67
+ toast: 100, // toast band — far above any conceivable window stack
68
+ dropdown: 200, // anchored popup band — above toasts (user is interacting)
69
+ tooltip: 300, // tooltip band — always on top
60
70
  };
61
71
 
62
72
  /**
package/src/scale.scss CHANGED
@@ -64,10 +64,29 @@
64
64
  --hi-duration-fast: 0.15s;
65
65
  --hi-ease-default: cubic-bezier(0.4, 0, 0.2, 1);
66
66
 
67
- /* Z-index bands */
68
- --z-base: 0;
69
- --z-header: 100;
70
- --z-sidebar: 900;
67
+ /* Z-index bands — ONE definition per name; every component that needs
68
+ * a stacking layer consumes these variables. The bands are ordered from
69
+ * base content (0) to the window titlebar (100001); a higher band
70
+ * always paints above a lower one. Tooltips sit above toasts AND
71
+ * modals so their text stays readable and copyable while any overlay
72
+ * is showing (user direction 2026-10-15). */
73
+ --hk-z-base: 0; /* base content stacking context */
74
+ --hk-z-above: 1; /* local bump: checkbox marks, switch thumbs */
75
+ --hk-z-scrollbar: 10; /* scrollbar tracks and corner rulers */
76
+ --hk-z-fab: 40; /* floating action button */
77
+ --hk-z-toolbar: 50; /* zoom toolbar / local overlay strips */
78
+ --hk-z-header: 100; /* sticky header */
79
+ --hk-z-sidebar: 900; /* sidebar drawer */
80
+ --hk-z-drag: 950; /* element being dragged floats above its row */
81
+ --hk-z-breadcrumb: 2500; /* modal breadcrumb bar */
82
+ --hk-z-float: 3000; /* floating panels: select popout, popover, anchored layer */
83
+ --hk-z-toast: 4000; /* toast notifications */
84
+ --hk-z-veil: 4100; /* full-screen loading veil (above toast) */
85
+ --hk-z-modal: 5000; /* modal backdrop + frame */
86
+ --hk-z-tooltip: 6000; /* tooltip — above toasts and modals so its text
87
+ is always readable and copyable */
88
+ --hk-z-error: 95000; /* error landing overlay (covers everything) */
89
+ --hk-z-titlebar: 100001; /* window titlebar (Tauri shell) */
71
90
 
72
91
  /* Viewport gutter — the minimum distance every floating layer (tooltip,
73
92
  * popover, menu, select popout, modal frame, toasts) keeps from the
@@ -86,7 +86,7 @@
86
86
  background: rgb(var(--color-surface));
87
87
  backdrop-filter: blur(var(--blur-md));
88
88
  border-top: 1px solid var(--border-faint, rgb(var(--color-border) / 10%));
89
- z-index: var(--z-footer, 110);
89
+ z-index: var(--hk-z-toolbar);
90
90
  flex-shrink: 0;
91
91
  }
92
92
 
@@ -391,7 +391,7 @@
391
391
  /* ── Glass header ─ */
392
392
  .s-glass-header {
393
393
  position: relative;
394
- z-index: var(--z-header);
394
+ z-index: var(--hk-z-header);
395
395
  display: flex;
396
396
  align-items: center;
397
397
  /* Horizontal rhythm + gap live in the class (not in host utilities):
@@ -30,6 +30,7 @@
30
30
  @use "./reset.scss";
31
31
  @use "./theme/channels.scss";
32
32
  @use "./theme/scale.scss";
33
+ @use "../overlay/overlayRoot.scss";
33
34
  @use "./theme/scrollbar.scss";
34
35
  @use "./theme/highlight.scss";
35
36
  @use "./admin-tokens.scss";
@@ -66,10 +66,29 @@
66
66
  --hi-duration-fast: 0.15s;
67
67
  --hi-ease-default: cubic-bezier(0.4, 0, 0.2, 1);
68
68
 
69
- /* Z-index bands */
70
- --z-base: 0;
71
- --z-header: 100;
72
- --z-sidebar: 900;
69
+ /* Z-index bands — ONE definition per name; every component that needs
70
+ * a stacking layer consumes these variables. The bands are ordered from
71
+ * base content (0) to the window titlebar (100001); a higher band
72
+ * always paints above a lower one. Tooltips sit above toasts AND
73
+ * modals so their text stays readable and copyable while any overlay
74
+ * is showing (user direction 2026-10-15). */
75
+ --hk-z-base: 0; /* base content stacking context */
76
+ --hk-z-above: 1; /* local bump: checkbox marks, switch thumbs */
77
+ --hk-z-scrollbar: 10; /* scrollbar tracks and corner rulers */
78
+ --hk-z-fab: 40; /* floating action button */
79
+ --hk-z-toolbar: 50; /* zoom toolbar / local overlay strips */
80
+ --hk-z-header: 100; /* sticky header */
81
+ --hk-z-sidebar: 900; /* sidebar drawer */
82
+ --hk-z-drag: 950; /* element being dragged floats above its row */
83
+ --hk-z-breadcrumb: 2500; /* modal breadcrumb bar */
84
+ --hk-z-float: 3000; /* floating panels: select popout, popover, anchored layer */
85
+ --hk-z-toast: 4000; /* toast notifications */
86
+ --hk-z-veil: 4100; /* full-screen loading veil (above toast) */
87
+ --hk-z-modal: 5000; /* modal backdrop + frame */
88
+ --hk-z-tooltip: 6000; /* tooltip — above toasts and modals so its text
89
+ is always readable and copyable */
90
+ --hk-z-error: 95000; /* error landing overlay (covers everything) */
91
+ --hk-z-titlebar: 100001; /* window titlebar (Tauri shell) */
73
92
 
74
93
  /* Viewport gutter — the minimum distance every floating layer (tooltip,
75
94
  * popover, menu, select popout, modal frame, toasts) keeps from the
@@ -11,9 +11,6 @@
11
11
  // values inline; these tokens are the static fallbacks used before a
12
12
  // handle resolves or when a host renders the sheets without JS).
13
13
  // overlay (modal/drawer) < dropdown < tooltip < toast (topmost).
14
- --hi-z-overlay: 1000;
15
- --hi-z-dropdown: 2000;
16
- --hi-z-tooltip: 3000;
17
14
  --hk-z-toast: 4000;
18
15
  // Drag ghosts float above page chrome (--z-sidebar 900) but below
19
16
  // every popup band.