@celestia-island/hikari 0.55.32 → 0.55.33

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.32",
3
+ "version": "0.55.33",
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",
@@ -24,6 +24,13 @@ import { describe, expect, it } from "vitest";
24
24
  * belong to the HOST-app namespace (registered at runtime via
25
25
  * mergeMessages, or passed in as props.t) and are pinned by their own
26
26
  * suites (timeKeys.test.ts & co.) — out of scope here by construction.
27
+ *
28
+ * Interpolated keys cannot be pinned by literal, so their CONSTRUCTION
29
+ * SITES are scanned wherever the backtick template appears — adjacency to
30
+ * t() is NOT required (F1: HkStatusBar.tsx:233 builds the key into a
31
+ * variable first) — and each discovered family is whitelisted and, when
32
+ * its enum is closed inside hikari, pinned key-by-key (see
33
+ * CONSTRUCTED_FAMILY_KEYS).
27
34
  */
28
35
  const modules = import.meta.glob<{ default: Record<string, unknown> }>(
29
36
  "./locales/*/*.json",
@@ -75,18 +82,23 @@ function walkSources(dir: string, acc: string[] = []): string[] {
75
82
  * `t(` and the key; the character class covers ", ', and ` so a template
76
83
  * literal WITHOUT interpolation is still a pinned literal. */
77
84
  const STATIC_USE_RE = /\bt\(\s*(["'`])hikari::[A-Za-z0-9_.]+\1/g;
78
- /** Dynamic families: a template key with `${…}` interpolation can never be
79
- * resolved statically — they are enumerated in DYNAMIC_KEY_FAMILIES. */
80
- const DYNAMIC_USE_RE = /\bt\(\s*`hikari::[A-Za-z0-9_.]*\$\{/g;
85
+ /** Constructed key families: a template key with `${…}` interpolation can
86
+ * never be resolved statically — they are enumerated in
87
+ * DYNAMIC_KEY_FAMILIES. The regex matches the template construction
88
+ * ANYWHERE in source, NOT only adjacent to `t(`: F1 (verify-hk603,
89
+ * 2026-09-19) — HkStatusBar.tsx builds `hikari::statusBar.tier.${tier}`
90
+ * into a variable and only calls t(tierLabelKey) 135 lines later, so the
91
+ * old adjacency-only regex never saw the family at all. */
92
+ const CONSTRUCTED_KEY_RE = /`hikari::[A-Za-z0-9_.]*\$\{/g;
81
93
 
82
94
  interface Site {
83
95
  key: string;
84
96
  where: string;
85
97
  }
86
98
 
87
- function scanSources(): { statics: Site[]; dynamics: string[] } {
99
+ function scanSources(): { statics: Site[]; constructed: Site[] } {
88
100
  const statics: Site[] = [];
89
- const dynamics: string[] = [];
101
+ const constructed: Site[] = [];
90
102
  for (const path of walkSources(SRC_ROOT)) {
91
103
  const text = readFileSync(path, "utf8");
92
104
  const rel = path.slice(SRC_ROOT.length + 1);
@@ -94,13 +106,14 @@ function scanSources(): { statics: Site[]; dynamics: string[] } {
94
106
  const key = match[0].slice(match[0].indexOf("hikari::"), -1);
95
107
  statics.push({ key, where: `${rel}:${text.slice(0, match.index).split("\n").length}` });
96
108
  }
97
- for (const match of text.matchAll(DYNAMIC_USE_RE)) {
109
+ for (const match of text.matchAll(CONSTRUCTED_KEY_RE)) {
98
110
  // The literal portion before "${" — e.g. "hikari::context." — which
99
111
  // is the family prefix the whitelist is keyed by.
100
- dynamics.push(match[0].slice(match[0].indexOf("hikari::"), -2));
112
+ const family = match[0].slice(1, -2);
113
+ constructed.push({ key: family, where: `${rel}:${text.slice(0, match.index).split("\n").length}` });
101
114
  }
102
115
  }
103
- return { statics, dynamics };
116
+ return { statics, constructed };
104
117
  }
105
118
 
106
119
  /** Interpolated key families that intentionally escape the static scan,
@@ -112,13 +125,25 @@ const DYNAMIC_KEY_FAMILIES: Record<string, string> = {
112
125
  "hikari::context.": "HkContextRing section keys — closed enum, pinned by name in contextKeys.test.ts.",
113
126
  "hikari::statusBar.backend.": "HkConnectionStatus — enumerated connection-state suffixes.",
114
127
  "hikari::statusBar.region.": "HkStatusBar region names — OPTIONAL per-locale overrides over Intl.DisplayNames (see LOCALE_SPECIFIC_NESTED in flatKeyParity.test.ts).",
128
+ "hikari::statusBar.tier.": "HkStatusBar transport tiers — constructed into a variable at a distance from its t() call (F1); the four concrete ids are pinned by name in CONSTRUCTED_FAMILY_KEYS below.",
115
129
  "hikari::theme.groups.": "Registry type: group/slot ids are registered by downstream apps; t() resolves via a resolveLocalizedText fallback, never a hikari bundle.",
116
130
  "hikari::theme.tokens.": "HkColorSchemeEditor token enum — closed set defined in platform.json locales.",
117
131
  };
118
132
 
133
+ /** Concrete suffixes each constructed family resolves to, for families whose
134
+ * enum is closed INSIDE hikari (the whitelist reason records which). Each
135
+ * listed key is asserted to exist, non-empty, in every locale — that is
136
+ * the tooth the adjacency regex lacked: deleting the whole family from
137
+ * every bundle used to leave every audit pin green (F1 blind spot).
138
+ * Families resolved by downstream apps (theme.groups.) or optional
139
+ * per-locale overrides (statusBar.region.) deliberately stay out. */
140
+ const CONSTRUCTED_FAMILY_KEYS: Record<string, string[]> = {
141
+ "hikari::statusBar.tier.": ["local", "poll", "sse", "ws"],
142
+ };
143
+
119
144
  describe("t() usage surface vs locale bundles", () => {
120
145
  const universes = new Map(EXPECTED_LOCALES.map((l) => [l, buildLocaleUniverse(l)]));
121
- const { statics, dynamics } = scanSources();
146
+ const { statics, constructed } = scanSources();
122
147
 
123
148
  it("covers all 11 locales in the merged bundle universe", () => {
124
149
  const found = new Set(
@@ -144,6 +169,11 @@ describe("t() usage surface vs locale bundles", () => {
144
169
  expect(firstSiteOf("hikari::filePicker.currentPath")).toMatch(/HkFileBrowserDialog\.tsx/);
145
170
  // Split-across-lines call form (t( and the key on different lines):
146
171
  expect(firstSiteOf("hikari::phoneInput.searchCountries")).toMatch(/HkPhoneInput\.tsx/);
172
+ // Constructed-key scan positive (F1): the tier template is built into a
173
+ // variable 135 lines away from its t() call — the scan must still see it.
174
+ expect(
175
+ constructed.find((s) => s.key === "hikari::statusBar.tier.")?.where ?? "NOT FOUND",
176
+ ).toMatch(/HkStatusBar\.tsx/);
147
177
  });
148
178
 
149
179
  it("resolves every statically referenced hikari:: key in every locale, non-empty", () => {
@@ -165,13 +195,36 @@ describe("t() usage surface vs locale bundles", () => {
165
195
  expect(violations, "every referenced key must resolve in all 11 locales").toEqual([]);
166
196
  });
167
197
 
168
- it("enumerates exactly the whitelisted dynamic key families, both directions", () => {
169
- const found = [...new Set(dynamics)].sort();
170
- expect(found, "families found in source but missing from the whitelist").toEqual(
198
+ it("enumerates exactly the whitelisted constructed key families, both directions", () => {
199
+ const found = [...new Set(constructed.map((s) => s.key))].sort();
200
+ expect(found, "families constructed in source but missing from the whitelist").toEqual(
171
201
  Object.keys(DYNAMIC_KEY_FAMILIES).sort(),
172
202
  );
173
203
  for (const family of Object.keys(DYNAMIC_KEY_FAMILIES)) {
174
- expect(found, `${family} is whitelisted but no longer used — prune it`).toContain(family);
204
+ expect(found, `${family} is whitelisted but no longer constructed — prune it`).toContain(family);
205
+ }
206
+ });
207
+
208
+ it("resolves every named concrete key of the constructed families in every locale", () => {
209
+ const violations: string[] = [];
210
+ for (const [family, suffixes] of Object.entries(CONSTRUCTED_FAMILY_KEYS)) {
211
+ expect(
212
+ DYNAMIC_KEY_FAMILIES[family],
213
+ `${family} carries named keys but has no whitelist entry`,
214
+ ).toBeDefined();
215
+ for (const suffix of suffixes) {
216
+ const key = family + suffix;
217
+ for (const locale of EXPECTED_LOCALES) {
218
+ const value = universes.get(locale)!.get(key);
219
+ if (typeof value !== "string" || value.length === 0) {
220
+ violations.push(`${key} is missing or empty in ${locale}`);
221
+ }
222
+ }
223
+ }
175
224
  }
225
+ expect(
226
+ violations,
227
+ "constructed families' concrete keys must resolve in all 11 locales",
228
+ ).toEqual([]);
176
229
  });
177
230
  });