@celestia-island/hikari 0.55.180 → 0.55.182
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 +2 -1
- package/src/components/HkDraggableList.scss +17 -0
- package/src/components/HkDraggableList.styles.test.ts +19 -0
- package/src/components/HkDraggableList.test.tsx +48 -0
- package/src/utils/connectivity.ts +6 -3
- package/src/utils/healthProbe.ts +5 -2
- package/src/utils/powNonce.ts +6 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@celestia-island/hikari",
|
|
3
|
-
"version": "0.55.
|
|
3
|
+
"version": "0.55.182",
|
|
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",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"vue": "^*"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
+
"@celestia-island/tairitsu-transport": "^0.1",
|
|
61
62
|
"@xterm/addon-fit": "^0.*",
|
|
62
63
|
"@xterm/addon-image": "^0.10.0-beta.301",
|
|
63
64
|
"@xterm/xterm": "^6.1.0-beta.304",
|
|
@@ -89,6 +89,23 @@
|
|
|
89
89
|
display: flex;
|
|
90
90
|
align-items: center;
|
|
91
91
|
gap: var(--space-8, 0.5rem);
|
|
92
|
+
|
|
93
|
+
/* A vertical list's row content owns the lane's width: when the host
|
|
94
|
+
renders ONE root element per row (the card shapes — wowsp's install
|
|
95
|
+
and account cards), stretch it across the lane so every row fills
|
|
96
|
+
the list instead of hugging its own text (wowsp report 2026-10-18:
|
|
97
|
+
the settings card lists shrank to content width after adopting this
|
|
98
|
+
component — the retired SortableList rows were plain blocks that
|
|
99
|
+
filled). min-width: 0 keeps ellipsis chains inside the card working.
|
|
100
|
+
Fragment rows — several sibling elements sharing the lane (chest's
|
|
101
|
+
tab/subview manager rows) — keep host-owned layout: the selector
|
|
102
|
+
deliberately does not match them. The drag ghost reuses the row
|
|
103
|
+
markup, so a single-root ghost fills its frame exactly like its
|
|
104
|
+
live row. */
|
|
105
|
+
> :only-child {
|
|
106
|
+
flex: 1;
|
|
107
|
+
min-width: 0;
|
|
108
|
+
}
|
|
92
109
|
}
|
|
93
110
|
|
|
94
111
|
.hk-draggable-list-ghost {
|
|
@@ -62,3 +62,22 @@ describe("HkDraggableList handle-lane sheet", () => {
|
|
|
62
62
|
expect(body).toContain("display: none");
|
|
63
63
|
});
|
|
64
64
|
});
|
|
65
|
+
|
|
66
|
+
describe("HkDraggableList content-lane fill sheet", () => {
|
|
67
|
+
const css = listCss();
|
|
68
|
+
|
|
69
|
+
it("stretches a single-root row across the lane", () => {
|
|
70
|
+
const body = ruleBody(css, ".hk-draggable-list-content > :only-child");
|
|
71
|
+
expect(body, "the :only-child fill rule must exist").not.toBeNull();
|
|
72
|
+
expect(body).toContain("flex: 1");
|
|
73
|
+
expect(body).toContain("min-width: 0");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// The fill must stay keyed on :only-child: fragment rows (several
|
|
77
|
+
// siblings sharing the lane) are host-owned layout and must not match.
|
|
78
|
+
it("never stretches fragment rows — the rule keys on :only-child alone", () => {
|
|
79
|
+
const fillRules = css.match(/\.hk-draggable-list-content\s*>\s*[^{]+\{/g) ?? [];
|
|
80
|
+
expect(fillRules.length).toBe(1);
|
|
81
|
+
expect(fillRules[0]).toContain(":only-child");
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -223,6 +223,54 @@ describe("HkDraggableList list semantics", () => {
|
|
|
223
223
|
});
|
|
224
224
|
});
|
|
225
225
|
|
|
226
|
+
describe("HkDraggableList content-lane shape", () => {
|
|
227
|
+
/** The lane hands the slot content through verbatim, and the sheet
|
|
228
|
+
* stretches a lane's ONLY element child across the row (the
|
|
229
|
+
* `:only-child` fill — sheet twin:
|
|
230
|
+
* HkDraggableList.styles.test.ts). That selector's semantics hinge on
|
|
231
|
+
* this shape: a single-root slot must land as exactly one element
|
|
232
|
+
* child, and a fragment slot as several siblings (which the fill
|
|
233
|
+
* deliberately does not match). */
|
|
234
|
+
|
|
235
|
+
function mountWithSlot(slot: () => unknown): HTMLElement {
|
|
236
|
+
const container = document.createElement("div");
|
|
237
|
+
document.body.appendChild(container);
|
|
238
|
+
const app = createApp({
|
|
239
|
+
render: () =>
|
|
240
|
+
h(
|
|
241
|
+
HDraggableList,
|
|
242
|
+
{ items: [{ key: "a" }] },
|
|
243
|
+
{ item: () => slot() },
|
|
244
|
+
),
|
|
245
|
+
});
|
|
246
|
+
app.mount(container);
|
|
247
|
+
mounts.push({ app, container });
|
|
248
|
+
return container.querySelector(".hk-draggable-list") as HTMLElement;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function laneOf(root: HTMLElement): HTMLElement {
|
|
252
|
+
return root.querySelector(".hk-draggable-list-content") as HTMLElement;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
it("lands a single-root slot as the lane's only element child", () => {
|
|
256
|
+
const root = mountWithSlot(() => h("div", { class: "row-card" }, "body"));
|
|
257
|
+
const children = Array.from(laneOf(root).children);
|
|
258
|
+
expect(children).toHaveLength(1);
|
|
259
|
+
expect(children[0]!.classList.contains("row-card")).toBe(true);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("lands a fragment slot as several lane siblings the fill ignores", () => {
|
|
263
|
+
const root = mountWithSlot(() => [
|
|
264
|
+
h("span", { class: "row-lead" }, "lead"),
|
|
265
|
+
h("span", { class: "row-tail" }, "tail"),
|
|
266
|
+
]);
|
|
267
|
+
const children = Array.from(laneOf(root).children);
|
|
268
|
+
expect(children).toHaveLength(2);
|
|
269
|
+
expect(children[0]!.classList.contains("row-lead")).toBe(true);
|
|
270
|
+
expect(children[1]!.classList.contains("row-tail")).toBe(true);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
226
274
|
describe("HkDraggableList press ergonomics (clickable rows)", () => {
|
|
227
275
|
/** Two guards make a clickable row safe inside the list (ported from
|
|
228
276
|
* wowsp's SortableList twin, which ran them in production):
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* fails the origin is unreachable, otherwise the host responded but the
|
|
7
7
|
* CORS policy rejected the credentialed request.
|
|
8
8
|
*/
|
|
9
|
+
import { bindHostFetch } from "@celestia-island/tairitsu-transport/contract";
|
|
10
|
+
|
|
9
11
|
export type OriginProbe =
|
|
10
12
|
| { ok: true }
|
|
11
13
|
| { ok: false; reason: "unreachable" | "cors-blocked" };
|
|
@@ -16,8 +18,9 @@ export async function probeOrigin(
|
|
|
16
18
|
): Promise<OriginProbe> {
|
|
17
19
|
// Bare `?? fetch` detaches Window's fetch receiver — the default path
|
|
18
20
|
// throws "Illegal invocation" in a browser (2026-10-17 chest live
|
|
19
|
-
// report family).
|
|
20
|
-
|
|
21
|
+
// report family). The shared bindHostFetch() from tairitsu-transport is
|
|
22
|
+
// the one sanctioned acquisition, re-resolved per call.
|
|
23
|
+
const fetchFn = opts?.fetchFn ?? bindHostFetch();
|
|
21
24
|
try {
|
|
22
25
|
// A successful credentialed fetch means reachable + CORS-whitelisted,
|
|
23
26
|
// regardless of the HTTP status code (4xx/5xx still prove the origin).
|
|
@@ -45,7 +48,7 @@ export async function probeOriginWithBody(
|
|
|
45
48
|
opts?: { fetchFn?: typeof fetch },
|
|
46
49
|
): Promise<{ ok: boolean; body?: string }> {
|
|
47
50
|
// Same receiver rule as probeOrigin: never capture Window's fetch bare.
|
|
48
|
-
const fetchFn = opts?.fetchFn ??
|
|
51
|
+
const fetchFn = opts?.fetchFn ?? bindHostFetch();
|
|
49
52
|
try {
|
|
50
53
|
const resp = await fetchFn(url, { credentials: "include", mode: "cors" });
|
|
51
54
|
return { ok: true, body: await resp.text().catch(() => undefined) };
|
package/src/utils/healthProbe.ts
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* Pure function, no app config — callers bring the URL.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { bindHostFetch } from "@celestia-island/tairitsu-transport/contract";
|
|
13
|
+
|
|
12
14
|
/** Minimal subset shared by all `/api/health` consumers. */
|
|
13
15
|
export interface HealthProbeBody {
|
|
14
16
|
status: string;
|
|
@@ -43,8 +45,9 @@ export async function probeHealthEndpoint(
|
|
|
43
45
|
// Bare `?? fetch` captures Window's fetch detached — every default
|
|
44
46
|
// call throws "Illegal invocation" in a browser (2026-10-17 chest live
|
|
45
47
|
// report; the probe then silently reported every panel unreachable).
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
+
// The shared bindHostFetch() from tairitsu-transport re-attaches the
|
|
49
|
+
// receiver and re-resolves per call.
|
|
50
|
+
const fetchFn = opts.fetchFn ?? bindHostFetch();
|
|
48
51
|
const started = performance.now();
|
|
49
52
|
try {
|
|
50
53
|
const resp = await fetchFn(url, {
|
package/src/utils/powNonce.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bindHostFetch } from "@celestia-island/tairitsu-transport/contract";
|
|
1
2
|
import { solvePow, type PowChallenge } from "./pow";
|
|
2
3
|
|
|
3
4
|
export type ChallengeDescriptor =
|
|
@@ -31,9 +32,10 @@ export async function fetchChallenge(
|
|
|
31
32
|
baseUrl: string,
|
|
32
33
|
// Bare `= fetch` captures Window's fetch detached — the default throws
|
|
33
34
|
// "Illegal invocation" in a browser (2026-10-17 chest live report
|
|
34
|
-
// family).
|
|
35
|
-
// call so vi.stubGlobal
|
|
36
|
-
|
|
35
|
+
// family). The shared bindHostFetch() from tairitsu-transport
|
|
36
|
+
// re-attaches the receiver, re-resolved per call so vi.stubGlobal
|
|
37
|
+
// stubs stay visible.
|
|
38
|
+
fetchFn: typeof fetch = bindHostFetch(),
|
|
37
39
|
): Promise<ChallengeDescriptor | undefined> {
|
|
38
40
|
let resp: Response;
|
|
39
41
|
try {
|
|
@@ -64,7 +66,7 @@ export async function negotiateNonce(
|
|
|
64
66
|
): Promise<string | undefined> {
|
|
65
67
|
try {
|
|
66
68
|
// Same receiver rule as fetchChallenge's default: never bare `?? fetch`.
|
|
67
|
-
const fetchFn = opts?.fetchFn ??
|
|
69
|
+
const fetchFn = opts?.fetchFn ?? bindHostFetch();
|
|
68
70
|
let body: Record<string, unknown> | undefined;
|
|
69
71
|
if (opts?.captchaToken) {
|
|
70
72
|
body = { type: "captcha", token: opts.captchaToken };
|