@celestia-island/hikari 0.55.94 → 0.55.96
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
|
@@ -13,9 +13,11 @@
|
|
|
13
13
|
gap: 0.3rem;
|
|
14
14
|
max-width: 11rem;
|
|
15
15
|
/* Logical axes: the wide side reserves caret room at inline-end and
|
|
16
|
-
* flips together with the host input's RTL suffix/prefix side.
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
* flips together with the host input's RTL suffix/prefix side.
|
|
17
|
+
* Roomy padding: the pill's fill must not hug the label — the text
|
|
18
|
+
* keeps clear air on both sides (user direction, round N). */
|
|
19
|
+
padding-block: 4px;
|
|
20
|
+
padding-inline: 9px 11px;
|
|
19
21
|
border: none;
|
|
20
22
|
border-radius: var(--radius-sm, 6px);
|
|
21
23
|
background: transparent;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Source contract: the JSON tree's spacing rhythm is token-based, not
|
|
8
|
+
* ad-hoc. The 2026-09-30 normalization replaced the off-scale gaps
|
|
9
|
+
* (2px toggle margin, 0.35em colon, 0.75em type hint) with design
|
|
10
|
+
* units and added the toggle's first-line centering formula — this
|
|
11
|
+
* contract keeps the file from drifting back to magic values, which is
|
|
12
|
+
* exactly the "crooked collapsed/expanded alignment" regression class
|
|
13
|
+
* the normalization fixed.
|
|
14
|
+
*/
|
|
15
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const scss = readFileSync(join(here, "HkJsonTree.scss"), "utf-8");
|
|
17
|
+
|
|
18
|
+
describe("HkJsonTree spacing contract", () => {
|
|
19
|
+
it("keeps the toggle exactly one spacing unit from the row text", () => {
|
|
20
|
+
const block = scss.slice(scss.indexOf(".s-jt-toggle {"), scss.indexOf(".s-jt-toggle[data-parent]"));
|
|
21
|
+
expect(block).toContain("margin-inline-end: var(--space-4");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("centers the toggle box on the first text line by formula", () => {
|
|
25
|
+
const block = scss.slice(scss.indexOf(".s-jt-toggle {"), scss.indexOf(".s-jt-toggle[data-parent]"));
|
|
26
|
+
expect(block).toContain("align-self: flex-start");
|
|
27
|
+
expect(block).toContain("margin-block-start: max(0px, calc((1.55em - var(--jt-toggle, 14px)) / 2))");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("keeps every --jt-toggle fallback at the 14px the formula assumes", () => {
|
|
31
|
+
// Regex sweep, not a blacklist: ANY fallback other than 14px (today
|
|
32
|
+
// or added later) would de-center the box by the delta.
|
|
33
|
+
const fallbacks = [...scss.matchAll(/var\(--jt-toggle,\s*([^)]+)\)/g)].map((m) => m[1].trim());
|
|
34
|
+
expect(fallbacks.length, "fallback sites were scanned").toBeGreaterThan(2);
|
|
35
|
+
expect(fallbacks.every((f) => f === "14px"), `fallbacks = ${fallbacks}`).toBe(true);
|
|
36
|
+
// The canonical definition itself: the TSX drives depth steps and
|
|
37
|
+
// guide lines from JT_TOGGLE = 14 — a different definition desyncs
|
|
38
|
+
// CSS geometry from the TS geometry with zero other red.
|
|
39
|
+
expect(scss).toContain("--jt-toggle: 14px;");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("pins the depth step to exactly one toggle width", () => {
|
|
43
|
+
const tsx = readFileSync(join(here, "HkJsonTree.tsx"), "utf-8");
|
|
44
|
+
expect(tsx).toContain("const JT_INDENT = JT_TOGGLE;");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("reads collapsed summaries one tab in, at full row size", () => {
|
|
48
|
+
const tsx = readFileSync(join(here, "HkJsonTree.tsx"), "utf-8");
|
|
49
|
+
// Both collapsed shapes (object preview and long string) carry the
|
|
50
|
+
// one-tab slot before the summary — the collapsed text sits in the
|
|
51
|
+
// child column instead of hugging the chevron.
|
|
52
|
+
expect(tsx.match(/<span class="s-jt-indent" \/>/g)?.length).toBe(2);
|
|
53
|
+
const preview = scss.slice(scss.indexOf(".s-jt-preview {"));
|
|
54
|
+
// A smaller preview font would shrink a keyless row's line box below
|
|
55
|
+
// the toggle centering assumption and float the row high.
|
|
56
|
+
expect(preview).toContain("font-size: inherit");
|
|
57
|
+
expect(preview).not.toContain("0.5rem");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("keeps the collapsed summary indent slot exactly one tab wide", () => {
|
|
61
|
+
const indent = scss.slice(scss.indexOf(".s-jt-indent {"), scss.indexOf(".s-jt-preview {"));
|
|
62
|
+
// Deleting the whole rule (not just tweaking it) must also go red —
|
|
63
|
+
// this slot IS the second-round "collapsed text hugs the chevron" fix.
|
|
64
|
+
expect(indent, ".s-jt-indent rule present").not.toBe("");
|
|
65
|
+
expect(indent).toContain("width: var(--jt-toggle, 14px)");
|
|
66
|
+
expect(indent).toContain("flex-shrink: 0");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("keeps the key colon gap on one spacing unit", () => {
|
|
70
|
+
const block = scss.slice(scss.indexOf(".s-jt-colon {"), scss.indexOf(".s-jv-null"));
|
|
71
|
+
expect(block).toContain("margin-inline-end: var(--space-4");
|
|
72
|
+
expect(block).not.toContain("0.35em");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("keeps the type hint gap on two spacing units", () => {
|
|
76
|
+
const block = scss.slice(scss.indexOf(".s-jt-type {"), scss.indexOf(".s-jt-badge {"));
|
|
77
|
+
expect(block).toContain("margin-inline-start: var(--space-8");
|
|
78
|
+
expect(block).not.toContain("0.75em");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("leaves no off-scale magic gaps anywhere in the tree stylesheet", () => {
|
|
82
|
+
expect(scss).not.toMatch(/margin-inline-end: 0\.\d+em/);
|
|
83
|
+
expect(scss).not.toMatch(/margin-inline-start: 0\.\d+em/);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -69,14 +69,20 @@
|
|
|
69
69
|
display: inline-flex;
|
|
70
70
|
align-items: center;
|
|
71
71
|
justify-content: center;
|
|
72
|
-
width: var(--jt-toggle,
|
|
73
|
-
height: var(--jt-toggle,
|
|
72
|
+
width: var(--jt-toggle, 14px);
|
|
73
|
+
height: var(--jt-toggle, 14px);
|
|
74
74
|
flex-shrink: 0;
|
|
75
|
-
|
|
75
|
+
/* Toggle → text is ONE spacing unit everywhere (leaf, collapsed and
|
|
76
|
+
expanded parents alike) — the collapsed row and its children read
|
|
77
|
+
on the same rhythm instead of ad-hoc gaps. */
|
|
78
|
+
margin-inline-end: var(--space-4, 0.25rem);
|
|
76
79
|
/* The toggle box has no text baseline of its own — pin it to the line
|
|
77
|
-
top
|
|
78
|
-
|
|
80
|
+
top and center it on the FIRST text line by formula (line-height
|
|
81
|
+
1.55 × font-size vs the fixed box), so the chevron/leaf sits on the
|
|
82
|
+
text's optical center in collapsed and expanded rows alike instead
|
|
83
|
+
of floating above the baseline group. */
|
|
79
84
|
align-self: flex-start;
|
|
85
|
+
margin-block-start: max(0px, calc((1.55em - var(--jt-toggle, 14px)) / 2));
|
|
80
86
|
}
|
|
81
87
|
|
|
82
88
|
.s-jt-toggle[data-parent] {
|
|
@@ -117,7 +123,7 @@
|
|
|
117
123
|
|
|
118
124
|
.s-jt-colon {
|
|
119
125
|
color: rgb(var(--color-muted));
|
|
120
|
-
margin-inline-end: 0.
|
|
126
|
+
margin-inline-end: var(--space-4, 0.25rem);
|
|
121
127
|
}
|
|
122
128
|
|
|
123
129
|
.s-jv-null {
|
|
@@ -152,7 +158,7 @@
|
|
|
152
158
|
font-size: var(--text-3xs, 0.5rem);
|
|
153
159
|
font-style: italic;
|
|
154
160
|
font-weight: 400;
|
|
155
|
-
margin-inline-start: 0.
|
|
161
|
+
margin-inline-start: var(--space-8, 0.5rem);
|
|
156
162
|
}
|
|
157
163
|
|
|
158
164
|
.s-jt-badge {
|
|
@@ -172,9 +178,22 @@
|
|
|
172
178
|
font-weight: 400;
|
|
173
179
|
}
|
|
174
180
|
|
|
181
|
+
.s-jt-indent {
|
|
182
|
+
/* The collapsed summary reads one tab in — the same column the
|
|
183
|
+
children occupy when the node is expanded — instead of hugging the
|
|
184
|
+
chevron. Kept as a flex slot (not padding) so it can never shrink. */
|
|
185
|
+
flex-shrink: 0;
|
|
186
|
+
width: var(--jt-toggle, 14px);
|
|
187
|
+
}
|
|
188
|
+
|
|
175
189
|
.s-jt-preview {
|
|
176
190
|
color: rgb(var(--color-muted));
|
|
177
|
-
|
|
191
|
+
/* Full row size: a keyless collapsed root's ONLY text is the preview,
|
|
192
|
+
and a smaller font here would shrink the line box below the toggle's
|
|
193
|
+
centering assumption and float the whole row high (the 2026-09-30
|
|
194
|
+
second-round screenshot report). Quietness comes from the muted
|
|
195
|
+
color, not a smaller size. */
|
|
196
|
+
font-size: inherit;
|
|
178
197
|
font-weight: 400;
|
|
179
198
|
opacity: 0.7;
|
|
180
199
|
}
|
|
@@ -130,14 +130,24 @@ function initialExpandedSet(root: HJsonNode): Set<number> {
|
|
|
130
130
|
/**
|
|
131
131
|
* HkJsonTree — the interactive expandable JSON tree.
|
|
132
132
|
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* `
|
|
133
|
+
* A first-class family component (same tier as the rich-text surfaces):
|
|
134
|
+
* the chat tool blocks, the chest error landing's raw-details pane and
|
|
135
|
+
* any host pane render through THIS component — HkToolBlock consumes it
|
|
136
|
+
* directly, and the `s-jt-*` stylesheet single-sources the visual
|
|
137
|
+
* language, so the two can never drift. Feed it either pre-parsed
|
|
138
|
+
* `value` or a raw JSON `text`; non-container input renders nothing.
|
|
137
139
|
*
|
|
138
140
|
* The root keeps the legacy `s-tool-json-tree` class: hosts (including
|
|
139
141
|
* HkToolBlock's deferred scrollbar pass) target it, and the pane chrome
|
|
140
142
|
* (mono font, capped height, hidden native scrollbars) lives there.
|
|
143
|
+
*
|
|
144
|
+
* Spacing contract (pinned by HkJsonTree.contract.test.ts): the toggle
|
|
145
|
+
* box is 14px and sits ONE `--space-4` from the row text — identical for
|
|
146
|
+
* leaf dots, collapsed previews and expanded parents — and is centered
|
|
147
|
+
* on the first text line by formula, so collapsed and expanded states
|
|
148
|
+
* align with the same left rhythm; `key:` gaps are one unit, type hints
|
|
149
|
+
* two; the depth step is one toggle width (JT_INDENT = JT_TOGGLE,
|
|
150
|
+
* pinned alongside the stylesheet contract).
|
|
141
151
|
*/
|
|
142
152
|
export const HkJsonTree = defineComponent({
|
|
143
153
|
name: "HkJsonTree",
|
|
@@ -224,6 +234,7 @@ export const HkJsonTree = defineComponent({
|
|
|
224
234
|
</span>
|
|
225
235
|
{node.key !== null && <span class="s-jt-key">{node.key}</span>}
|
|
226
236
|
{node.key !== null && <span class="s-jt-colon">: </span>}
|
|
237
|
+
<span class="s-jt-indent" />
|
|
227
238
|
<span class="s-jt-preview">{node.preview}</span>
|
|
228
239
|
</div>
|
|
229
240
|
);
|
|
@@ -257,6 +268,7 @@ export const HkJsonTree = defineComponent({
|
|
|
257
268
|
</span>
|
|
258
269
|
{node.key !== null && <span class="s-jt-key">{node.key}</span>}
|
|
259
270
|
{node.key !== null && <span class="s-jt-colon">: </span>}
|
|
271
|
+
<span class="s-jt-indent" />
|
|
260
272
|
<span class="s-jv-str">"{truncateStr(node.stringValue, STR_PREVIEW_LEN)}"</span>
|
|
261
273
|
<span class="s-jt-type">string ({node.stringValue.length})</span>
|
|
262
274
|
</div>
|