@celestia-island/hikari 0.55.223 → 0.55.224

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.55.223",
3
+ "version": "0.55.224",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Hikari UI library, Vue 3 runtime — one design system for Vue 3 and Rust/WASM applications",
@@ -93,6 +93,11 @@
93
93
  }
94
94
 
95
95
  .hk-alert-text {
96
+ /* Alert body = the same copy-this-error-detail content class as the
97
+ * toast message and message-box text (L2 selection policy re-enables,
98
+ * 2026-10-29); the title above stays chrome. */
99
+ -webkit-user-select: text;
100
+ user-select: text;
96
101
  margin: 0;
97
102
  line-height: 1.5;
98
103
  color: color-mix(in srgb, var(--hi-color-text-secondary, #666666) 85%, transparent);
@@ -58,10 +58,17 @@
58
58
  }
59
59
 
60
60
  .hk-blocking-toast-title {
61
+ /* Content, not chrome — copy-worthy detail under the L2 selection
62
+ * policy (2026-10-29): the card shell denies, these two text blocks
63
+ * select again. */
64
+ -webkit-user-select: text;
65
+ user-select: text;
61
66
  font-weight: 700;
62
67
  }
63
68
 
64
69
  .hk-blocking-toast-message {
70
+ -webkit-user-select: text;
71
+ user-select: text;
65
72
  margin: 0;
66
73
  display: -webkit-box;
67
74
  -webkit-line-clamp: 5;
@@ -15,6 +15,10 @@
15
15
  }
16
16
 
17
17
  .hk-confirm-dialog-message {
18
+ /* Dialog message = copy-worthy content, same class as the message-box
19
+ * text (L2 selection policy re-enables, 2026-10-29). */
20
+ -webkit-user-select: text;
21
+ user-select: text;
18
22
  margin: 0;
19
23
  color: color-mix(in srgb, var(--hi-color-text-secondary) 85%, transparent);
20
24
  font-size: var(--text-sm, 0.8125rem);
@@ -67,6 +67,10 @@
67
67
  }
68
68
 
69
69
  .hk-diff-viewer__code {
70
+ /* The diff body is content (review text users copy into replies);
71
+ * the line-number gutter above and the sign column stay denied. */
72
+ -webkit-user-select: text;
73
+ user-select: text;
70
74
  padding-inline-end: 1ch;
71
75
  min-width: 0;
72
76
  }
@@ -45,6 +45,11 @@
45
45
  }
46
46
 
47
47
  .s-jt-row {
48
+ /* Tree rows are content — values users copy out of a payload — while
49
+ * the pane chrome above stays denied (L2 selection policy, 2026-10-29;
50
+ * the copy-tree affordance remains the bulk path). */
51
+ -webkit-user-select: text;
52
+ user-select: text;
48
53
  /* Baseline alignment keeps the small type hints (boolean/number/string)
49
54
  on the value's text baseline instead of floating above it. */
50
55
  display: flex;
@@ -117,6 +117,12 @@
117
117
  }
118
118
 
119
119
  .s-log-entry {
120
+ /* Log lines are the canonical copy-me content (diagnostics users
121
+ * select per-fragment, not just via the toolbar copy-all) — re-enable
122
+ * under the L2 selection policy (2026-10-29); the toolbar chrome
123
+ * around them stays denied. */
124
+ -webkit-user-select: text;
125
+ user-select: text;
120
126
  padding: var(--space-2, 0.125rem) var(--space-4, 0.25rem);
121
127
  font-family: var(--font-mono, vars.$hikari-font-family-mono);
122
128
  font-size: var(--text-xs, 0.75rem);
@@ -2,6 +2,11 @@
2
2
  @use "./hikari-vars" as vars;
3
3
 
4
4
  .hk-markdown {
5
+ /* Rendered prose is content by definition — the whole subtree selects
6
+ * again under the L2 selection policy (2026-10-29); its code blocks
7
+ * are covered twice over (whitelist tier 3). */
8
+ -webkit-user-select: text;
9
+ user-select: text;
5
10
  font-size: var(--text-base);
6
11
  line-height: 1.7;
7
12
  color: var(--hi-color-text-primary, #1e1e1e);
@@ -25,6 +25,11 @@
25
25
  }
26
26
 
27
27
  .hk-message-box-text {
28
+ /* Dialog message = content the user may need to copy (error detail),
29
+ * not chrome — whitelisted under the L2 selection policy (2026-10-29).
30
+ * The prompt's field below is covered by the editable-controls tier. */
31
+ -webkit-user-select: text;
32
+ user-select: text;
28
33
  margin: 0;
29
34
  color: rgb(var(--color-text));
30
35
  font-size: var(--text-base, 15px);
@@ -420,6 +420,17 @@
420
420
  }
421
421
  }
422
422
 
423
+ /* Opt-out consumers get plain selectable cells back — explicit because
424
+ * the L2 selection policy (2026-10-29) denies at the root now: absence
425
+ * of the copy-cells deny rule no longer means selectable. Same shape as
426
+ * the copy layer, so "wholesale" is a per-cell mirror of the default. */
427
+ .hk-table:not([data-copy-cells]) {
428
+ .hk-table-cell {
429
+ -webkit-user-select: text;
430
+ user-select: text;
431
+ }
432
+ }
433
+
423
434
  /* The standard trailing row-actions column (HkRowActions trigger):
424
435
  fixed narrow track, centered, never wrapping. */
425
436
  .hk-table-manage-cell {
@@ -55,6 +55,12 @@
55
55
  }
56
56
 
57
57
  .hk-toast-message {
58
+ /* The message text is content — error detail a user copies out of a
59
+ * failing app — not chrome. Re-enable inside the denied toast shell
60
+ * (L2 selection policy, 2026-10-29): the shell keeps its none, the
61
+ * message subtree selects again. */
62
+ -webkit-user-select: text;
63
+ user-select: text;
58
64
  flex: 1;
59
65
  min-width: 0;
60
66
  display: -webkit-box;
@@ -720,10 +720,11 @@
720
720
  // stays enabled even when the window controls degraded.
721
721
  button.disabled = false;
722
722
  button.removeAttribute("aria-disabled");
723
- // Right-hand cluster, immediately LEFT of close: closing keeps the frame
724
- // corner (the familiar target), so a user reaching for it on a hung window
725
- // cannot hit reload by mistake.
726
- actions.insertBefore(button, closeBtn);
723
+ // Reload LEADS the cluster (2026-10-29 user direction): the affordance
724
+ // sits ahead of the window controls — [reload][minimize][maximize][close]
725
+ // — so the three caption buttons (and the close frame corner) keep their
726
+ // familiar trailing positions.
727
+ actions.insertBefore(button, minimizeBtn);
727
728
  reloadBtn = button;
728
729
  return button;
729
730
  }
@@ -396,19 +396,20 @@ describe("boot-chrome.js handoff and watchdog", () => {
396
396
  expect(control("close").disabled).toBe(false);
397
397
  });
398
398
 
399
- it("puts the reload affordance at the right edge, next to close (S3-5)", () => {
399
+ it("puts the reload affordance ahead of the window controls (2026-10-29 user direction)", () => {
400
400
  useWindowApi(fakeWindowApi());
401
401
  setGlobal("__TAURI_INTERNALS__", { invoke: vi.fn() });
402
402
  runBootChrome({ hangMs: 1000 });
403
403
 
404
404
  vi.advanceTimersByTime(1000);
405
405
 
406
- // Right-hand cluster, immediately left of close: closing keeps the frame
407
- // corner, so a mis-aimed click on a hung window cannot reload it.
406
+ // Reload LEADS the cluster — [reload][min][max][close] — so the three
407
+ // caption buttons (and the close frame corner) keep their familiar
408
+ // trailing positions instead of the reload splitting max from close.
408
409
  expect(controls().map((button) => button.getAttribute("data-act"))).toEqual([
410
+ "reload",
409
411
  "minimize",
410
412
  "maximize",
411
- "reload",
412
413
  "close",
413
414
  ]);
414
415
  });
@@ -6,6 +6,9 @@
6
6
  // 1. theme/channels.scss — the static seed: --color-* channel palette,
7
7
  // --hi-color-* derivations, --c-*/--shadow-*/--border-* aliases and
8
8
  // the icon-button bridge (vendored from src/tokens.scss).
9
+ // 1b. selection.scss — the selection policy: deny-by-default at the
10
+ // root (chrome text never takes a drag selection), whitelisted back
11
+ // for editable controls and the .hk-selectable content hook.
9
12
  // 2. theme/scale.scss — the L2 scale tokens: spacing, type, radius,
10
13
  // blur, opacity, durations, easings, z bands, fonts (vendored from
11
14
  // src/scale.scss).
@@ -28,6 +31,7 @@
28
31
  // version of this file re-exported packages/theme + packages/components
29
32
  // through paths that escape the npm package and did not compile.
30
33
  @use "./reset.scss";
34
+ @use "./selection.scss";
31
35
  @use "./theme/channels.scss";
32
36
  @use "./theme/scale.scss";
33
37
  @use "../overlay/overlayRoot.scss";
@@ -0,0 +1,59 @@
1
+ /* ── Selection policy (L2) — deny by default, whitelist back ───────────
2
+ * 2026-10-29 user direction: UI chrome text (workspace menus, buttons,
3
+ * tabs, labels, rails, caption bars) must not take a drag selection.
4
+ * The old model was a blacklist — every component sheet opted out
5
+ * piecemeal, and any surface a sheet forgot shipped selectable until a
6
+ * screenshot complained. This sheet flips the default: the root denies,
7
+ * and only whitelisted subtrees select again.
8
+ *
9
+ * How one rule covers everything: `user-select` is not inherited, but
10
+ * its initial value `auto` RESOLVES against the parent's used value.
11
+ * A single `none` on `html` therefore determines every descendant
12
+ * (shadow trees included), and a whitelisted subtree declaring a
13
+ * concrete value re-enables its own descendants the same way — no
14
+ * `*` sweep, and component/host rules keep winning by specificity or
15
+ * order wherever they speak.
16
+ *
17
+ * The whitelist:
18
+ * 1. Editable controls — input, textarea, select, contenteditable:
19
+ * the user authored this text; selecting it is the point.
20
+ * 2. `.hk-selectable` — the opt-in utility for hosts and components:
21
+ * put it on any subtree whose text is CONTENT (chat bodies, logs,
22
+ * error details, IDs, prose, standalone code blocks) rather than
23
+ * chrome. This is the contract that replaces the old per-component
24
+ * blacklist.
25
+ *
26
+ * Deliberately NOT whitelisted: bare `pre` / `code` / `kbd` / `samp`.
27
+ * A concrete value on an element BEATS the ancestor's none-propagation,
28
+ * so an element-type whitelist would punch through chrome that embeds
29
+ * those elements — HkButton's HkKbd shortcut chip (denied `.hk-btn` on
30
+ * the button itself), or a `<code>` slotted into a click-to-copy table
31
+ * cell (the 2026-10-18 copy-cells contract). Content containers own
32
+ * their re-enables instead: hikari's prose/code components (markdown
33
+ * body, diff code, json-tree rows, log entries) self-declare
34
+ * `user-select: text` in their sheets, so vendored copies carry their
35
+ * own contract, and hosts mark standalone code/log surfaces with
36
+ * `.hk-selectable`.
37
+ *
38
+ * Order: SECOND in the canonical composition (index.scss), right after
39
+ * the reset — the same "only lowers defaults; component sheets and host
40
+ * styles override by specificity/order" discipline applies. A host that
41
+ * truly wants select-everything back sets `user-select: text` on its own
42
+ * root; marking content with `.hk-selectable` is the supported route.
43
+ */
44
+
45
+ html {
46
+ -webkit-user-select: none;
47
+ user-select: none;
48
+ }
49
+
50
+ input,
51
+ textarea,
52
+ select,
53
+ /* `i` flag: HTML enumerated attribute values are ASCII case-insensitive,
54
+ * so contenteditable="FALSE" (valid, means false) must be excluded too. */
55
+ [contenteditable]:not([contenteditable='false' i]),
56
+ .hk-selectable {
57
+ -webkit-user-select: text;
58
+ user-select: text;
59
+ }
@@ -0,0 +1,134 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ import * as sass from "sass";
6
+ import { describe, expect, it } from "vitest";
7
+
8
+ /**
9
+ * Selection policy guard (2026-10-29 user direction).
10
+ *
11
+ * `selection.scss` flips the selection model from a blacklist (component
12
+ * sheets opting out piecemeal; anything a sheet forgot shipped selectable)
13
+ * to a whitelist: the root denies, editable controls / the `.hk-selectable`
14
+ * content hook / pre-code conventions select again, and hikari's own
15
+ * content-bearing components self-declare their re-enables in their sheets
16
+ * (toast message, markdown body, diff code, json-tree rows, table opt-out).
17
+ *
18
+ * The contract under test, per half:
19
+ * - the canonical composition loads the sheet SECOND (right after the
20
+ * reset, before any theme/component sheet can override);
21
+ * - the compiled policy carries the root deny and every whitelist tier
22
+ * (assertions run on the COMPILED sheet — compressed drops both
23
+ * comment styles, so raw-text matching cannot pass on a commented-out
24
+ * rule);
25
+ * - the component re-enables really exist in the compiled component
26
+ * sheets, with the chrome halves still denied.
27
+ */
28
+
29
+ const stylesDir = resolve(dirname(fileURLToPath(import.meta.url)));
30
+ const srcDir = resolve(stylesDir, "..");
31
+
32
+ function read(path: string): string {
33
+ return readFileSync(resolve(stylesDir, path), "utf8").replace(/\r\n/g, "\n");
34
+ }
35
+
36
+ function compile(path: string): string {
37
+ return sass.compile(resolve(stylesDir, path), {
38
+ loadPaths: [srcDir, join(srcDir, "components"), join(srcDir, "styles"), join(srcDir, "styles", "theme")],
39
+ style: "compressed",
40
+ silenceDeprecations: ["import", "global-builtin", "legacy-js-api"],
41
+ }).css;
42
+ }
43
+
44
+ describe("selection policy sheet", () => {
45
+ it("loads second in the canonical composition (right after the reset)", () => {
46
+ const uses = read("index.scss")
47
+ .split("\n")
48
+ .filter((line) => line.startsWith('@use "./'))
49
+ .map((line) => line.match(/^@use "([^"]+)"/)?.[1]);
50
+ expect(uses[0]).toBe("./reset.scss");
51
+ expect(uses[1]).toBe("./selection.scss");
52
+ });
53
+
54
+ it("denies selection at the root", () => {
55
+ const css = compile("selection.scss");
56
+ expect(css).toMatch(/html\s*\{[^}]*?-webkit-user-select:\s*none/);
57
+ expect(css).toMatch(/html\s*\{[^}]*?user-select:\s*none/);
58
+ });
59
+
60
+ it("whitelists exactly the editable controls and the content hook", () => {
61
+ const css = compile("selection.scss");
62
+ // Exact, comma-anchored selector list — substring matching would pass
63
+ // vacuously ("select" occurs inside every user-select declaration).
64
+ // Compressed output drops unnecessary attribute-quote characters.
65
+ const group = css.match(
66
+ /((?:input|textarea|select|\[[^\]]+\]|\.hk-selectable)[^{}]*)\{-webkit-user-select:\s*text;user-select:\s*text\}/,
67
+ );
68
+ expect(group).toBeTruthy();
69
+ expect(group![1].split(",").map((s) => s.trim())).toEqual([
70
+ "input",
71
+ "textarea",
72
+ "select",
73
+ "[contenteditable]:not([contenteditable=false i])",
74
+ ".hk-selectable",
75
+ ]);
76
+ // Element-type content conventions (pre/code/kbd/samp) stay OUT: a
77
+ // concrete value beats ancestor chrome denies, so a bare kbd/code
78
+ // whitelist punched through HkButton's denied shortcut chip and
79
+ // click-to-copy table cells (2026-10-29 review, S1).
80
+ expect(css).not.toMatch(/(^|[,\s])(pre|code|kbd|samp)([,\s]*\{|,)/);
81
+ });
82
+
83
+ it("re-enables the toast family message text (content, not chrome)", () => {
84
+ const css = compile("../components/HkToast.scss");
85
+ expect(css).toMatch(/\.hk-toast-message[^{]*\{[^}]*?-webkit-user-select:\s*text/);
86
+ // The shell stays denied — only the message subtree selects.
87
+ expect(css).toMatch(/\.hk-toast-item\s*\{[^}]*?user-select:\s*none/);
88
+ const cssBlocking = compile("../components/HkBlockingToast.scss");
89
+ expect(cssBlocking).toMatch(/\.hk-blocking-toast-title[^{]*\{[^}]*?user-select:\s*text/);
90
+ expect(cssBlocking).toMatch(/\.hk-blocking-toast-message[^{]*\{[^}]*?user-select:\s*text/);
91
+ const cssBox = compile("../components/HkMessageBox.scss");
92
+ expect(cssBox).toMatch(/\.hk-message-box-text[^{]*\{[^}]*?user-select:\s*text/);
93
+ });
94
+
95
+ it("re-enables the markdown body and the diff code, keeping the diff gutter denied", () => {
96
+ const css = compile("../components/HkMarkdownRenderer.scss");
97
+ expect(css).toMatch(/\.hk-markdown\s*\{[^}]*?user-select:\s*text/);
98
+ const cssDiff = compile("../components/HkDiffViewer.scss");
99
+ expect(cssDiff).toMatch(/\.hk-diff-viewer__code[^{]*\{[^}]*?user-select:\s*text/);
100
+ expect(cssDiff).toMatch(/\.hk-diff-viewer__no[^{]*\{[^}]*?user-select:\s*none/);
101
+ expect(cssDiff).toMatch(/\.hk-diff-viewer__sign[^{]*\{[^}]*?user-select:\s*none/);
102
+ });
103
+
104
+ it("re-enables json-tree rows inside the denied pane chrome", () => {
105
+ const css = compile("../components/HkJsonTree.scss");
106
+ expect(css).toMatch(/\.s-jt-row[^{]*\{[^}]*?user-select:\s*text/);
107
+ expect(css).toMatch(/\.s-tool-json-tree[^{]*\{[^}]*?user-select:\s*none/);
108
+ });
109
+
110
+ it("re-enables the alert body and the confirm-dialog message", () => {
111
+ // Same copy-this-error-detail content class as the toast/message-box
112
+ // surfaces (2026-10-29 review, S2: the parallel surfaces were missed
113
+ // in the first cut).
114
+ const cssAlert = compile("../components/HkAlert.scss");
115
+ expect(cssAlert).toMatch(/\.hk-alert-text[^{]*\{[^}]*?user-select:\s*text/);
116
+ const cssConfirm = compile("../components/HkConfirmDialog.scss");
117
+ expect(cssConfirm).toMatch(
118
+ /\.hk-confirm-dialog-message[^{]*\{[^}]*?user-select:\s*text/,
119
+ );
120
+ const cssLog = compile("../components/HkLogWindow.scss");
121
+ expect(cssLog).toMatch(/\.s-log-entry[^{]*\{[^}]*?user-select:\s*text/);
122
+ });
123
+
124
+ it("keeps the table contract two-sided under the deny-by-default root", () => {
125
+ // 2026-10-18 contract: copy-cells (default) denies cells with the
126
+ // click-to-copy cursor; the opt-out now needs an explicit mirror —
127
+ // absence of a deny no longer means selectable once the root denies.
128
+ const css = compile("../components/HkTable.scss");
129
+ expect(css).toMatch(/\.hk-table\[data-copy-cells\] \.hk-table-cell\{[^}]*?user-select:\s*none/);
130
+ expect(css).toMatch(
131
+ /\.hk-table:not\(\[data-copy-cells\]\) \.hk-table-cell\{[^}]*?user-select:\s*text/,
132
+ );
133
+ });
134
+ });