@celestia-island/hikari 0.55.230 → 0.55.231

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.230",
3
+ "version": "0.55.231",
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",
@@ -1,5 +1,8 @@
1
1
  import { afterEach, describe, expect, it } from "vitest";
2
2
  import { createApp, defineComponent, h, nextTick, ref } from "vue";
3
+ import { readFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
3
6
 
4
7
  import { HkAvatarButton } from "./HkAvatarButton";
5
8
  import { HkAdminHeader } from "./HkAdminHeader";
@@ -107,6 +110,61 @@ describe("HkAvatarButton", () => {
107
110
  });
108
111
  });
109
112
 
113
+ describe("HkAvatarButton sheet contract", () => {
114
+ // happy-dom has no layout engine, so the geometry is pinned at the
115
+ // source level — the same approach the downstream chest tripwire
116
+ // (headerAvatarGeometry.test.ts) takes against the INSTALLED sheet.
117
+ const sheet = readFileSync(
118
+ join(dirname(fileURLToPath(import.meta.url)), "..", "styles", "admin-tokens.scss"),
119
+ "utf8",
120
+ );
121
+
122
+ /** Declarations of the rule whose whole prelude IS `selector`
123
+ * (a compound/descendant spelling is a different rule and does not
124
+ * match), comments stripped; null when no such rule exists — every
125
+ * assertion treats null as a failure, never as "no objection". */
126
+ function ruleDeclarations(selector: string): Array<[string, string]> | null {
127
+ const code = sheet
128
+ .replace(/\/\*[\s\S]*?\*\//g, "")
129
+ .replace(/(^|[^:])\/\/[^\n]*/gm, "$1");
130
+ for (let at = code.indexOf(selector); at >= 0; at = code.indexOf(selector, at + 1)) {
131
+ const open = code.indexOf("{", at + selector.length);
132
+ if (open < 0) continue;
133
+ if (code.slice(at + selector.length, open).trim() !== "") continue;
134
+ let boundary = at - 1;
135
+ while (boundary >= 0 && !"}{;".includes(code[boundary])) boundary -= 1;
136
+ if (code.slice(boundary + 1, at).replace(/\s+/g, " ").trim() !== "") continue;
137
+ const body = code.slice(open + 1, code.indexOf("}", open));
138
+ const out: Array<[string, string]> = [];
139
+ for (const part of body.split(";")) {
140
+ const m = /^\s*([-a-zA-Z]+)\s*:\s*([\s\S]+?)\s*$/.exec(part);
141
+ if (m) out.push([m[1], m[2].replace(/\s+/g, " ")]);
142
+ }
143
+ return out;
144
+ }
145
+ return null;
146
+ }
147
+
148
+ it("the trigger is a BLOCK — hosts may nest it in a plain wrapper div", () => {
149
+ // The chat face wraps HeaderLeft's trigger in a menu-anchor <div>:
150
+ // an inline-block button there joins a line box and the strut's
151
+ // descender space lands BELOW it, riding the avatar ~4px high inside
152
+ // a perfectly centred wrapper (2026-10-30 user report). Removing
153
+ // this declaration is the mutation this test must catch.
154
+ const rule = ruleDeclarations(".s-admin-header-avatar");
155
+ expect(rule, "admin-tokens must declare .s-admin-header-avatar").not.toBeNull();
156
+ // Positive control for the extractor itself: the geometry pair the
157
+ // downstream tripwire also reads. If these stop matching, the parser
158
+ // above is broken — fix it, not the assertion below.
159
+ expect(rule).toContainEqual(["width", "var(--space-28)"]);
160
+ expect(rule).toContainEqual(["height", "var(--space-28)"]);
161
+ expect(rule, "the trigger must not re-enter inline formatting").toContainEqual([
162
+ "display",
163
+ "block",
164
+ ]);
165
+ });
166
+ });
167
+
110
168
  describe("HkAdminHeader × HkAvatarButton / identity slot", () => {
111
169
  it("never renders aria-expanded when no expanded prop is given (drawer-mode contract)", () => {
112
170
  // R2 S3-2: menu engines read this pair; a drawer-mode trigger has no
@@ -574,6 +574,16 @@
574
574
  }
575
575
 
576
576
  .s-admin-header-avatar {
577
+ /* Block, never the UA's inline-block: hosts may nest the trigger in a
578
+ * plain block wrapper (the chat face's menu-anchor <div> around
579
+ * HeaderLeft's HkAvatarButton), and an inline-block button there joins
580
+ * a line box — its baseline (the bottom margin edge, because of
581
+ * `overflow: hidden`) sits on the strut's baseline, so the strut's
582
+ * descender space lands BELOW the avatar and the button rides ~4px
583
+ * high inside a perfectly centred wrapper (2026-10-30 user report:
584
+ * 前台头像略微偏上,后台正中). A direct flex/grid parent blockifies
585
+ * this away, so the console face is unaffected either way. */
586
+ display: block;
577
587
  position: relative;
578
588
  width: var(--space-28);
579
589
  height: var(--space-28);