@celestia-island/hikari 0.55.165 → 0.55.166
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 +1 -1
- package/src/errorReporting/HkErrorReportingOverlay.scss +6 -3
- package/src/overlay/overlayRoot.scss +13 -1
- package/src/overlay/overlayRoot.styles.test.ts +185 -0
- package/src/overlay/overlayRoot.ts +42 -16
- package/src/scale.scss +8 -1
- package/src/styles/theme/scale.scss +8 -1
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
// HkErrorReportingOverlay — fixed full-viewport host for the error landing.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
2
|
+
// Mounts on document.body (NOT the overlay root — the takeover must survive
|
|
3
|
+
// even a broken floating tree) and sits on --hk-z-error, ABOVE the fenced
|
|
4
|
+
// overlay root (--hk-z-overlay-root): an uncaught-error takeover must never
|
|
5
|
+
// be covered — not by app chrome, not by any floating layer. Only the
|
|
6
|
+
// window titlebar (--hk-z-titlebar) stays above it in the Tauri shell.
|
|
4
7
|
|
|
5
8
|
.hk-error-reporting-overlay {
|
|
6
9
|
position: fixed;
|
|
7
10
|
inset: 0;
|
|
8
|
-
z-index: var(--hk-z-
|
|
11
|
+
z-index: var(--hk-z-error);
|
|
9
12
|
}
|
|
@@ -9,9 +9,21 @@
|
|
|
9
9
|
*/
|
|
10
10
|
#hk-overlay-root {
|
|
11
11
|
/* Fences the root's z-index space from the global stacking order —
|
|
12
|
-
* the entire reason this container exists.
|
|
12
|
+
* the entire reason this container exists. The fence disciplines the
|
|
13
|
+
* root's INTERNALS only; the elevation below is what lifts the root
|
|
14
|
+
* itself above app chrome. */
|
|
13
15
|
isolation: isolate;
|
|
14
16
|
|
|
17
|
+
/* Document-space elevation — without it, any fixed/sticky chrome with
|
|
18
|
+
* a positive z-index (the admin status bar sits at
|
|
19
|
+
* --hk-z-toolbar: 50) painted over EVERY floating layer: the
|
|
20
|
+
* connection popover rendered behind the panel footer (2026-10-15
|
|
21
|
+
* defect report). The band lives in scale.scss between the old
|
|
22
|
+
* floating bands and --hk-z-error, preserving the pre-isolation
|
|
23
|
+
* order: content chrome < floating layers < error landing < titlebar.
|
|
24
|
+
* The fallback must match overlayRoot.ts's OVERLAY_ROOT_DOCUMENT_Z. */
|
|
25
|
+
z-index: var(--hk-z-overlay-root, 90000);
|
|
26
|
+
|
|
15
27
|
/* The root itself doesn't intercept pointer events; each teleported
|
|
16
28
|
* child restores its own pointer-events. */
|
|
17
29
|
pointer-events: none;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join, dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
import { compile } from "sass";
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
|
|
8
|
+
import { OVERLAY_ROOT_DOCUMENT_Z, ensureOverlayRoot } from "./overlayRoot";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Compiled-stylesheet + cross-file contract for the overlay root's
|
|
12
|
+
* document-space elevation. `isolation` fences the root's INTERNAL
|
|
13
|
+
* ordinals but does not lift the root above app chrome — without the
|
|
14
|
+
* elevation, any fixed/sticky bar with a positive z-index (the admin
|
|
15
|
+
* status bar's --hk-z-toolbar: 50) painted over every floating layer
|
|
16
|
+
* and the connection popover ended up behind the panel footer
|
|
17
|
+
* (2026-10-15 defect report). These assertions pin the elevation in
|
|
18
|
+
* all three places it must agree: scale.scss (the band), the compiled
|
|
19
|
+
* sheet, and the inline self-sufficient cssText.
|
|
20
|
+
*/
|
|
21
|
+
const overlayDir = resolve(dirname(fileURLToPath(import.meta.url)));
|
|
22
|
+
const vueSrc = resolve(overlayDir, "..");
|
|
23
|
+
|
|
24
|
+
function sourcesIn(dir: string): string[] {
|
|
25
|
+
const out: string[] = [];
|
|
26
|
+
for (const entry of readdirSync(dir)) {
|
|
27
|
+
if (entry === "node_modules" || entry === "__tests__") continue;
|
|
28
|
+
const path = join(dir, entry);
|
|
29
|
+
if (statSync(path).isDirectory()) out.push(...sourcesIn(path));
|
|
30
|
+
else if (/\.(scss|tsx)$/.test(entry)) out.push(path);
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe("overlay root document-space elevation contract", () => {
|
|
36
|
+
const css = compile(resolve(overlayDir, "overlayRoot.scss"), {
|
|
37
|
+
style: "expanded",
|
|
38
|
+
}).css;
|
|
39
|
+
const scale = readFileSync(resolve(vueSrc, "scale.scss"), "utf8");
|
|
40
|
+
const source = readFileSync(resolve(overlayDir, "overlayRoot.ts"), "utf8");
|
|
41
|
+
|
|
42
|
+
it("declares the elevation in the sheet, with the fence kept", () => {
|
|
43
|
+
expect(css).toContain("z-index: var(--hk-z-overlay-root, 90000);");
|
|
44
|
+
expect(css).toContain("isolation: isolate;");
|
|
45
|
+
expect(css).toContain("position: relative;");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("keeps scale.scss, the sheet fallback and the TS fallback on one value", () => {
|
|
49
|
+
const band = scale.match(/--hk-z-overlay-root:\s*(\d+)/);
|
|
50
|
+
// A missing match is a broken contract, not an empty pass.
|
|
51
|
+
expect(band).not.toBeNull();
|
|
52
|
+
expect(Number(band![1])).toBe(OVERLAY_ROOT_DOCUMENT_Z);
|
|
53
|
+
// The inline cssText interpolates the same constant.
|
|
54
|
+
expect(source).toContain(
|
|
55
|
+
"z-index:var(--hk-z-overlay-root,${OVERLAY_ROOT_DOCUMENT_Z})",
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("parks the band between the floating bands and error/titlebar", () => {
|
|
60
|
+
const value = (name: string) => {
|
|
61
|
+
const m = scale.match(new RegExp(`--hk-z-${name}:\\s*(\\d+)`));
|
|
62
|
+
expect(m, `--hk-z-${name} must stay defined in scale.scss`).not.toBeNull();
|
|
63
|
+
return Number(m![1]);
|
|
64
|
+
};
|
|
65
|
+
const band = value("overlay-root");
|
|
66
|
+
// Above every content-chrome band (the defect: footer at 50 won).
|
|
67
|
+
expect(band).toBeGreaterThan(value("tooltip"));
|
|
68
|
+
expect(band).toBeGreaterThan(value("modal"));
|
|
69
|
+
expect(band).toBeGreaterThan(value("toolbar"));
|
|
70
|
+
// Below the two surfaces that intentionally stay on top, exactly as
|
|
71
|
+
// in the pre-isolation global scale.
|
|
72
|
+
expect(band).toBeLessThan(value("error"));
|
|
73
|
+
expect(band).toBeLessThan(value("titlebar"));
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("inlines a self-sufficient pre-stylesheet elevation", () => {
|
|
77
|
+
const root = ensureOverlayRoot();
|
|
78
|
+
expect(root.id).toBe("hk-overlay-root");
|
|
79
|
+
expect(root.style.getPropertyValue("z-index")).toContain(
|
|
80
|
+
"var(--hk-z-overlay-root",
|
|
81
|
+
);
|
|
82
|
+
expect(root.style.getPropertyValue("position")).toBe("relative");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("lets no z-index consumer land between the root band and the error band", () => {
|
|
86
|
+
// The exact gap class the 2026-10-15 fix closed: a document-space
|
|
87
|
+
// surface that must own the top (hikari's error landing) sat at
|
|
88
|
+
// --hk-z-modal = 5000, and the elevated root silently jumped over
|
|
89
|
+
// it. Every --hk-z-* consumer must resolve STRICTLY below the root
|
|
90
|
+
// band (content chrome) or be one of the two intentional
|
|
91
|
+
// top-owners, which must stay ABOVE it — nothing ties the band
|
|
92
|
+
// (a tie would be DOM-order roulette) and nothing lands between
|
|
93
|
+
// the two bands. Consumers are matched anywhere INSIDE a
|
|
94
|
+
// z-index-bearing line, so calc()/nested-fallback wrappers
|
|
95
|
+
// (`var(--x, var(--hk-z-scrollbar))`) are still inventoried; their
|
|
96
|
+
// ARITHMETIC is not evaluated (a calc() offset off a top-owner
|
|
97
|
+
// token is textual guessing — same out-of-scope class as raw
|
|
98
|
+
// numbers, audited by hand at ≤ 51 in this package). Single-line
|
|
99
|
+
// declarations only.
|
|
100
|
+
const bandValues = new Map<string, number>();
|
|
101
|
+
for (const [, name, num] of scale.matchAll(/--hk-z-([a-z-]+):\s*(\d+)/g)) {
|
|
102
|
+
bandValues.set(name, Number(num));
|
|
103
|
+
}
|
|
104
|
+
// The ladder itself must be present — a broken parse must not
|
|
105
|
+
// masquerade as an empty pass.
|
|
106
|
+
expect(bandValues.size).toBeGreaterThanOrEqual(10);
|
|
107
|
+
expect(bandValues.get("overlay-root")).toBe(OVERLAY_ROOT_DOCUMENT_Z);
|
|
108
|
+
|
|
109
|
+
const sources = sourcesIn(vueSrc);
|
|
110
|
+
// Positive floor for the walk: hundreds of sheets/components live
|
|
111
|
+
// under src/ — fewer means the glob broke, not that the code did.
|
|
112
|
+
expect(sources.length).toBeGreaterThanOrEqual(200);
|
|
113
|
+
|
|
114
|
+
const found = new Map<string, Set<string>>(); // token name -> files
|
|
115
|
+
for (const file of sources) {
|
|
116
|
+
for (const line of readFileSync(file, "utf8").split("\n")) {
|
|
117
|
+
if (!/\bz-index\s*[:=]|\bzIndex\s*[:=]/.test(line)) continue;
|
|
118
|
+
for (const [, name] of line.matchAll(/var\(\s*--hk-z-([a-z-]+)/g)) {
|
|
119
|
+
if (!found.has(name)) found.set(name, new Set());
|
|
120
|
+
found.get(name)!.add(file);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Positive controls: the scan must see the known top-owners and the
|
|
125
|
+
// root itself, or the line patterns stopped matching.
|
|
126
|
+
for (const required of ["overlay-root", "error", "titlebar", "toolbar"]) {
|
|
127
|
+
expect(
|
|
128
|
+
found.has(required),
|
|
129
|
+
`scan lost the --hk-z-${required} consumer — patterns rotted`,
|
|
130
|
+
).toBe(true);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// File binding, not just name presence: the error takeover's
|
|
134
|
+
// "never covered" promise lives in ONE sheet — assert IT consumes
|
|
135
|
+
// the top-owner band, so the promise survives even if a second
|
|
136
|
+
// --hk-z-error consumer appears elsewhere.
|
|
137
|
+
const errorSheet = join(vueSrc, "errorReporting", "HkErrorReportingOverlay.scss");
|
|
138
|
+
expect(readFileSync(errorSheet, "utf8")).toMatch(
|
|
139
|
+
/z-index:\s*var\(\s*--hk-z-error[\s,)]/,
|
|
140
|
+
);
|
|
141
|
+
const titlebarSheet = join(vueSrc, "components", "HkTitleBar.scss");
|
|
142
|
+
expect(readFileSync(titlebarSheet, "utf8")).toMatch(
|
|
143
|
+
/z-index:\s*var\(\s*--hk-z-titlebar[\s,)]/,
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const topOwners = new Set(["error", "titlebar"]);
|
|
147
|
+
const rootSheet = resolve(overlayDir, "overlayRoot.scss");
|
|
148
|
+
for (const [name, files] of found) {
|
|
149
|
+
const value = bandValues.get(name);
|
|
150
|
+
expect(
|
|
151
|
+
value,
|
|
152
|
+
`--hk-z-${name} consumed (e.g. ${files.values().next().value}) but not defined in scale.scss`,
|
|
153
|
+
).not.toBeUndefined();
|
|
154
|
+
if (topOwners.has(name)) {
|
|
155
|
+
// A top-owner demoted to/below the root band would be painted
|
|
156
|
+
// over by the floating tree (the error landing sat at
|
|
157
|
+
// --hk-z-modal = 5000 when this guard was born).
|
|
158
|
+
expect(
|
|
159
|
+
value!,
|
|
160
|
+
`--hk-z-${name} (${value}) must stay above the overlay root band ` +
|
|
161
|
+
`(${OVERLAY_ROOT_DOCUMENT_Z}) — consumers: ${[...files].join(", ")}`,
|
|
162
|
+
).toBeGreaterThan(OVERLAY_ROOT_DOCUMENT_Z);
|
|
163
|
+
} else if (name === "overlay-root") {
|
|
164
|
+
// The band itself: ONLY the root's own sheet may sit at it —
|
|
165
|
+
// any other consumer at exactly 90000 races the root by DOM
|
|
166
|
+
// order.
|
|
167
|
+
for (const file of files) {
|
|
168
|
+
expect(
|
|
169
|
+
file,
|
|
170
|
+
`--hk-z-overlay-root consumed at ${file} — only ${rootSheet} may tie the band`,
|
|
171
|
+
).toBe(rootSheet);
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
expect(
|
|
175
|
+
value!,
|
|
176
|
+
`--hk-z-${name} (${value}) at ${[...files].join(", ")} must resolve ` +
|
|
177
|
+
`strictly below the overlay root band (${OVERLAY_ROOT_DOCUMENT_Z}) ` +
|
|
178
|
+
`or above the error band — ties and in-between values get painted ` +
|
|
179
|
+
`over by (or paint over) the floating tree`,
|
|
180
|
+
).toBeLessThan(OVERLAY_ROOT_DOCUMENT_Z);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
@@ -5,10 +5,21 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Inside this root, z-index values only compete with each other (the
|
|
7
7
|
* `isolation` property creates a new stacking context), so overlay types
|
|
8
|
-
* use simple
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* use simple ordinal bands instead of the old global thousand-scale. A
|
|
9
|
+
* tooltip at ordinal 300 inside the root always paints above a modal at
|
|
10
|
+
* ordinal 10 inside the root, regardless of what the global stacking
|
|
11
|
+
* space looks like outside.
|
|
12
|
+
*
|
|
13
|
+
* The fence only disciplines the root's INTERNALS, though — by itself
|
|
14
|
+
* `isolation` does not lift the root above app chrome. A fixed/sticky
|
|
15
|
+
* bar with a positive document-space z-index (the admin status bar sits
|
|
16
|
+
* at `--hk-z-toolbar: 50`) painted over EVERY floating layer, dropping
|
|
17
|
+
* the connection popover behind the panel footer (2026-10-15 defect
|
|
18
|
+
* report). The root therefore carries ONE document-space z-index of its
|
|
19
|
+
* own — `--hk-z-overlay-root` (fallback 90000) — parked between the old
|
|
20
|
+
* floating bands and `--hk-z-error`, so the pre-isolation global order
|
|
21
|
+
* is preserved: content chrome < floating layers < error landing <
|
|
22
|
+
* window titlebar.
|
|
12
23
|
*
|
|
13
24
|
* The root is created lazily on first use (`ensureOverlayRoot()`), so
|
|
14
25
|
* consumers don't need to include a bootstrap component — any Teleport
|
|
@@ -19,14 +30,14 @@
|
|
|
19
30
|
*
|
|
20
31
|
* | z-index | Layer | Examples |
|
|
21
32
|
* |---------|----------------|---------------------------------------|
|
|
22
|
-
* |
|
|
23
|
-
* |
|
|
24
|
-
* |
|
|
25
|
-
* |
|
|
26
|
-
* |
|
|
27
|
-
* |
|
|
28
|
-
* |
|
|
29
|
-
* |
|
|
33
|
+
* | 5 | modal backdrop | HkModal's fixed backdrop |
|
|
34
|
+
* | 10 | modal frame | HkModal body, dialogs |
|
|
35
|
+
* | 50 | blocking toast | HkBlockingToast |
|
|
36
|
+
* | 100 | toast | HkToast, persistent toasts |
|
|
37
|
+
* | 150 | loading veil | HkLoadingVeil |
|
|
38
|
+
* | 200 | anchored popup | HkSelectPanel popout, menus, popover |
|
|
39
|
+
* | 250 | breadcrumb | HkModalBreadcrumb strip |
|
|
40
|
+
* | 300 | tooltip | HkTooltip (always on top) |
|
|
30
41
|
*
|
|
31
42
|
* Components that need to layer within the SAME band (e.g. multiple
|
|
32
43
|
* stacked modals) use their own local stacking mechanisms (the modal
|
|
@@ -48,6 +59,15 @@ export const OVERLAY_Z = {
|
|
|
48
59
|
tooltip: 300,
|
|
49
60
|
} as const;
|
|
50
61
|
|
|
62
|
+
/**
|
|
63
|
+
* The root's document-space elevation fallback — must match the
|
|
64
|
+
* `--hk-z-overlay-root` value in `scale.scss` (kept honest by
|
|
65
|
+
* overlayRoot.styles.test.ts). Inlined because the root is created
|
|
66
|
+
* before any stylesheet loads; the var() indirection lets the sheet
|
|
67
|
+
* retune the band without touching this file.
|
|
68
|
+
*/
|
|
69
|
+
export const OVERLAY_ROOT_DOCUMENT_Z = 90000;
|
|
70
|
+
|
|
51
71
|
/**
|
|
52
72
|
* Idempotently create and append the overlay root to `<body>`.
|
|
53
73
|
* Called on module load — the first `import` of this module guarantees
|
|
@@ -61,10 +81,16 @@ export function ensureOverlayRoot(): HTMLElement {
|
|
|
61
81
|
root.id = "hk-overlay-root";
|
|
62
82
|
root.setAttribute("aria-hidden", "true");
|
|
63
83
|
// Inline styles — this element is created BEFORE any stylesheet
|
|
64
|
-
// loads, so it must be self-sufficient.
|
|
65
|
-
//
|
|
66
|
-
// the
|
|
67
|
-
|
|
84
|
+
// loads, so it must be self-sufficient. `isolation` fences the
|
|
85
|
+
// root's internal z-index space from the document's global stacking
|
|
86
|
+
// order; `position` + `z-index` then lift the fenced root above
|
|
87
|
+
// every content-chrome band (see the module doc) — `position` is
|
|
88
|
+
// repeated here so the elevation also applies pre-stylesheet. The
|
|
89
|
+
// var() name is the sheet's retuning path; the fallback constant
|
|
90
|
+
// must equal scale.scss's --hk-z-overlay-root.
|
|
91
|
+
root.style.cssText =
|
|
92
|
+
"isolation:isolate;pointer-events:none;position:relative;" +
|
|
93
|
+
`z-index:var(--hk-z-overlay-root,${OVERLAY_ROOT_DOCUMENT_Z});`;
|
|
68
94
|
document.body.appendChild(root);
|
|
69
95
|
}
|
|
70
96
|
return root;
|
package/src/scale.scss
CHANGED
|
@@ -69,7 +69,11 @@
|
|
|
69
69
|
* base content (0) to the window titlebar (100001); a higher band
|
|
70
70
|
* always paints above a lower one. Tooltips sit above toasts AND
|
|
71
71
|
* modals so their text stays readable and copyable while any overlay
|
|
72
|
-
* is showing (user direction 2026-10-15).
|
|
72
|
+
* is showing (user direction 2026-10-15). --hk-z-overlay-root is the
|
|
73
|
+
* ONE band consumed by the fenced floating-layer root itself rather
|
|
74
|
+
* than by a component: it lifts #hk-overlay-root above every
|
|
75
|
+
* content-chrome band while keeping the pre-isolation order (floating
|
|
76
|
+
* layers < error landing < window titlebar). */
|
|
73
77
|
--hk-z-base: 0; /* base content stacking context */
|
|
74
78
|
--hk-z-above: 1; /* local bump: checkbox marks, switch thumbs */
|
|
75
79
|
--hk-z-scrollbar: 10; /* scrollbar tracks and corner rulers */
|
|
@@ -85,6 +89,9 @@
|
|
|
85
89
|
--hk-z-modal: 5000; /* modal backdrop + frame */
|
|
86
90
|
--hk-z-tooltip: 6000; /* tooltip — above toasts and modals so its text
|
|
87
91
|
is always readable and copyable */
|
|
92
|
+
--hk-z-overlay-root: 90000; /* #hk-overlay-root — every floating layer's
|
|
93
|
+
document-space elevation (overlay/overlayRoot.ts);
|
|
94
|
+
above all content chrome, below error + titlebar */
|
|
88
95
|
--hk-z-error: 95000; /* error landing overlay (covers everything) */
|
|
89
96
|
--hk-z-titlebar: 100001; /* window titlebar (Tauri shell) */
|
|
90
97
|
|
|
@@ -71,7 +71,11 @@
|
|
|
71
71
|
* base content (0) to the window titlebar (100001); a higher band
|
|
72
72
|
* always paints above a lower one. Tooltips sit above toasts AND
|
|
73
73
|
* modals so their text stays readable and copyable while any overlay
|
|
74
|
-
* is showing (user direction 2026-10-15).
|
|
74
|
+
* is showing (user direction 2026-10-15). --hk-z-overlay-root is the
|
|
75
|
+
* ONE band consumed by the fenced floating-layer root itself rather
|
|
76
|
+
* than by a component: it lifts #hk-overlay-root above every
|
|
77
|
+
* content-chrome band while keeping the pre-isolation order (floating
|
|
78
|
+
* layers < error landing < window titlebar). */
|
|
75
79
|
--hk-z-base: 0; /* base content stacking context */
|
|
76
80
|
--hk-z-above: 1; /* local bump: checkbox marks, switch thumbs */
|
|
77
81
|
--hk-z-scrollbar: 10; /* scrollbar tracks and corner rulers */
|
|
@@ -87,6 +91,9 @@
|
|
|
87
91
|
--hk-z-modal: 5000; /* modal backdrop + frame */
|
|
88
92
|
--hk-z-tooltip: 6000; /* tooltip — above toasts and modals so its text
|
|
89
93
|
is always readable and copyable */
|
|
94
|
+
--hk-z-overlay-root: 90000; /* #hk-overlay-root — every floating layer's
|
|
95
|
+
document-space elevation (overlay/overlayRoot.ts);
|
|
96
|
+
above all content chrome, below error + titlebar */
|
|
90
97
|
--hk-z-error: 95000; /* error landing overlay (covers everything) */
|
|
91
98
|
--hk-z-titlebar: 100001; /* window titlebar (Tauri shell) */
|
|
92
99
|
|