@celestia-island/hikari 0.55.284 → 0.55.285

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.284",
3
+ "version": "0.55.285",
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",
@@ -0,0 +1,178 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import { createApp, h, nextTick, ref } from "vue";
3
+
4
+ import HkMarkdownRenderer from "./HkMarkdownRenderer";
5
+ import { applyInlineChipsToBody, inlineChipDisplay, parseInlineChips } from "./inlineChips";
6
+
7
+ /**
8
+ * Inline chip pass contract tests (the opt-in `inlineChips` mode):
9
+ * - the parser splits `{{ token }}` / `@ref` forms and preserves the
10
+ * accessible text (tokens canonicalize their spacing, refs stay raw,
11
+ * plain runs round-trip byte-exactly)
12
+ * - the boundary guards hold: emails, doubled `@`, code spans, code
13
+ * fences and link text never chip
14
+ * - the component applies the pass only when the prop is on
15
+ *
16
+ * House style: raw createApp mounts torn down after each case; the
17
+ * async marked/DOMPurify settle uses a bounded poll.
18
+ */
19
+
20
+ const mounts: { app: ReturnType<typeof createApp>; container: HTMLElement }[] = [];
21
+
22
+ function mount(props: Record<string, unknown>) {
23
+ const container = document.createElement("div");
24
+ document.body.appendChild(container);
25
+ const app = createApp({ render: () => h(HkMarkdownRenderer, props as never) });
26
+ app.mount(container);
27
+ mounts.push({ app, container });
28
+ return container;
29
+ }
30
+
31
+ async function settleFor(root: HTMLElement, selector: string, tries = 100): Promise<void> {
32
+ for (let i = 0; i < tries; i += 1) {
33
+ if (root.querySelector(selector)) return;
34
+ await new Promise((r) => setTimeout(r, 5));
35
+ await nextTick();
36
+ }
37
+ }
38
+
39
+ afterEach(() => {
40
+ for (const { app, container } of mounts) {
41
+ app.unmount();
42
+ container.remove();
43
+ }
44
+ mounts.length = 0;
45
+ });
46
+
47
+ describe("parseInlineChips", () => {
48
+ it("splits tokens, refs and plain runs in source order", () => {
49
+ expect(parseInlineChips("Badge: {{container_badge}} then @system/permission-mode")).toEqual([
50
+ { kind: "text", text: "Badge: " },
51
+ { kind: "token", token: "container_badge" },
52
+ { kind: "text", text: " then " },
53
+ { kind: "ref", raw: "@system/permission-mode" },
54
+ ]);
55
+ });
56
+
57
+ it("tokenizes with the shared grammar rules (bare ids only)", () => {
58
+ // Richer engine grammars stay literal — same rule as HkTemplateText.
59
+ expect(parseInlineChips("{{ a.b }} {{ x | filter }} {{ ok }}")).toEqual([
60
+ { kind: "text", text: "{{ a.b }} {{ x | filter }} " },
61
+ { kind: "token", token: "ok" },
62
+ ]);
63
+ });
64
+
65
+ it("guards the @ boundary: emails and doubled @ stay text", () => {
66
+ expect(parseInlineChips("mail foo@bar.com now")).toEqual([
67
+ { kind: "text", text: "mail foo@bar.com now" },
68
+ ]);
69
+ expect(parseInlineChips("user@@host")).toEqual([{ kind: "text", text: "user@@host" }]);
70
+ expect(parseInlineChips("word@notachip")).toEqual([{ kind: "text", text: "word@notachip" }]);
71
+ });
72
+
73
+ it("refs stop at punctuation and match bare handles", () => {
74
+ expect(parseInlineChips("see @system/x, then @demiurge.")).toEqual([
75
+ { kind: "text", text: "see " },
76
+ { kind: "ref", raw: "@system/x" },
77
+ { kind: "text", text: ", then " },
78
+ { kind: "ref", raw: "@demiurge" },
79
+ { kind: "text", text: "." },
80
+ ]);
81
+ });
82
+
83
+ it("chip display canonicalizes tokens and keeps refs raw", () => {
84
+ expect(inlineChipDisplay({ kind: "token", token: "current_datetime" })).toBe("{{ current_datetime }}");
85
+ expect(inlineChipDisplay({ kind: "ref", raw: "@system/related-todos" })).toBe("@system/related-todos");
86
+ });
87
+ });
88
+
89
+ describe("applyInlineChipsToBody", () => {
90
+ it("chips prose but never enters code, pre or links", () => {
91
+ const root = document.createElement("div");
92
+ root.innerHTML =
93
+ "<p>Badge {{container_badge}} ref @system/permission-mode mail a@b.com</p>" +
94
+ "<pre><code>{{ keep_literal }}</code></pre>" +
95
+ "<p><a href=\"https://x/@at/in-url\">link @text</a></p>";
96
+ const chips = applyInlineChipsToBody(root);
97
+ const chipEls = [...root.querySelectorAll("span.hk-tpl-chip")];
98
+ // Two prose chips only: the token and the ref (email, code, link excluded).
99
+ expect(chips).toBe(2);
100
+ expect(chipEls.map((el) => el.textContent)).toEqual(["{{ container_badge }}", "@system/permission-mode"]);
101
+ expect(chipEls[0]!.hasAttribute("data-ref")).toBe(false);
102
+ expect(chipEls[1]!.hasAttribute("data-ref")).toBe(true);
103
+ // Literal surfaces untouched.
104
+ expect(root.querySelector("code")!.textContent).toBe("{{ keep_literal }}");
105
+ expect(root.querySelector("a")!.textContent).toBe("link @text");
106
+ // Idempotent: a second pass finds nothing new to chip.
107
+ expect(applyInlineChipsToBody(root)).toBe(0);
108
+ });
109
+ });
110
+
111
+ describe("HkMarkdownRenderer inlineChips", () => {
112
+ it("chips tokens and refs when the prop is on", async () => {
113
+ const container = mount({
114
+ content: "Badge: {{container_badge}}\n\nRead @system/related-todos first.",
115
+ inlineChips: true,
116
+ });
117
+ await settleFor(container, ".hk-tpl-chip");
118
+ const chips = [...container.querySelectorAll("span.hk-tpl-chip")];
119
+ expect(chips.map((el) => el.textContent)).toEqual(["{{ container_badge }}", "@system/related-todos"]);
120
+ });
121
+
122
+ it("renders no chips by default (existing consumers unchanged)", async () => {
123
+ const container = mount({ content: "Badge: {{container_badge}} ref @system/related-todos" });
124
+ await settleFor(container, ".hk-markdown-body p");
125
+ await new Promise((r) => setTimeout(r, 10));
126
+ await nextTick();
127
+ expect(container.querySelectorAll(".hk-tpl-chip").length).toBe(0);
128
+ expect(container.textContent).toContain("{{container_badge}}");
129
+ expect(container.textContent).toContain("@system/related-todos");
130
+ });
131
+
132
+ it("guards the grammar boundaries the two implementations must share", () => {
133
+ // Multi-level refs, `@` at either end, a leading `_`/`-` segment.
134
+ expect(parseInlineChips("@a/b/c")).toEqual([{ kind: "ref", raw: "@a/b/c" }]);
135
+ expect(parseInlineChips("@sole")).toEqual([{ kind: "ref", raw: "@sole" }]);
136
+ expect(parseInlineChips("mail @")).toEqual([{ kind: "text", text: "mail @" }]);
137
+ expect(parseInlineChips("@_private @-x @--flag")).toEqual([
138
+ { kind: "text", text: "@_private @-x @--flag" },
139
+ ]);
140
+ // Never chips inside a bare URL (npm-scoped packages are common in
141
+ // prompt prose) — the Rust mirror guards this too.
142
+ expect(parseInlineChips("see https://www.npmjs.com/package/@celestia-island/hikari")).toEqual([
143
+ { kind: "text", text: "see https://www.npmjs.com/package/@celestia-island/hikari" },
144
+ ]);
145
+ // Carriage returns inside the braces are still the same token.
146
+ expect(parseInlineChips("{{\rname\r}}")).toEqual([{ kind: "token", token: "name" }]);
147
+ });
148
+
149
+ it("un-chips when the prop flips back off (same mount)", async () => {
150
+ // The chip pass mutates the DOM in place, so a naive watcher leaves the
151
+ // chips behind when the prop goes back to false — this drives the real
152
+ // toggle on ONE mounted instance.
153
+ const flag = ref(true);
154
+ const content = "Badge {{x}} and @a/b";
155
+ const container = document.createElement("div");
156
+ document.body.appendChild(container);
157
+ const app = createApp({ render: () => h(HkMarkdownRenderer, { content, inlineChips: flag.value }) });
158
+ app.mount(container);
159
+ mounts.push({ app, container });
160
+
161
+ await settleFor(container, ".hk-tpl-chip");
162
+ expect(container.querySelectorAll(".hk-tpl-chip").length).toBe(2);
163
+
164
+ flag.value = false;
165
+ await nextTick();
166
+ await new Promise((r) => setTimeout(r, 10));
167
+ await nextTick();
168
+ expect(container.querySelectorAll(".hk-tpl-chip").length).toBe(0);
169
+ expect(container.textContent).toContain("{{x}}");
170
+ expect(container.textContent).toContain("@a/b");
171
+
172
+ // ...and back on, still one mount.
173
+ flag.value = true;
174
+ await nextTick();
175
+ await settleFor(container, ".hk-tpl-chip");
176
+ expect(container.querySelectorAll(".hk-tpl-chip").length).toBe(2);
177
+ });
178
+ });
@@ -207,3 +207,41 @@
207
207
  @include backdrop-filter(blur(4px));
208
208
  z-index: 10;
209
209
  }
210
+
211
+ /* ── Inline chips (opt-in `inlineChips` prop) ────────────────────────
212
+ Fill-template tokens (`{{ name }}`) and asset references (`@level/name`,
213
+ bare `@name`) chipped inside rendered prose. The look is the family's
214
+ `.hk-tpl-chip` (HkTemplateField.scss is canonical); duplicated here
215
+ SCOPED to the markdown body so a markdown-only consumer does not pull
216
+ the template editor's sheet. Refs take the muted variant: a reference
217
+ points elsewhere, it is not a fillable value, so it must not read as
218
+ a template token. */
219
+ .hk-markdown-body .hk-tpl-chip {
220
+ display: inline-flex;
221
+ align-items: center;
222
+ padding: 0 var(--space-6, 6px);
223
+ border-radius: var(--radius-sm, 4px);
224
+ border: 1px solid color-mix(in srgb, var(--hi-color-primary) 32%, transparent);
225
+ background: color-mix(in srgb, var(--hi-color-primary) 12%, transparent);
226
+ color: color-mix(in srgb, var(--hi-color-primary) 70%, rgb(0 0 0));
227
+ font-family: var(--font-mono, monospace);
228
+ font-size: 0.8em;
229
+ line-height: 1.5;
230
+ white-space: nowrap;
231
+ /* NO user-select here: rendered prose IS content (see the sheet's own
232
+ * `user-select(text)` policy at the top and styles/selection.scss's
233
+ * "markdown body" content container). The editor chip in
234
+ * HkTemplateField.scss disables selection for caret/click handling;
235
+ * copying a PROMPT BODY must keep every `{{token}}` / `@ref` in the
236
+ * clipboard, so this scoped copy must not carry that rule. */
237
+
238
+ [data-theme="dark"] & {
239
+ color: color-mix(in srgb, var(--hi-color-primary) 60%, rgb(255 255 255));
240
+ }
241
+
242
+ &[data-ref] {
243
+ border-color: color-mix(in srgb, var(--hi-color-muted, #6c6c6c) 40%, transparent);
244
+ background: color-mix(in srgb, var(--hi-color-muted, #6c6c6c) 10%, transparent);
245
+ color: var(--hi-color-muted, #6c6c6c);
246
+ }
247
+ }
@@ -1,6 +1,7 @@
1
1
  import { computed, defineComponent, nextTick, onBeforeUnmount, ref, watch } from "vue";
2
2
  import type { Renderer, Tokens } from "marked";
3
3
  import HSpinner from "./HkSpinner";
4
+ import { applyInlineChipsToBody } from "./inlineChips";
4
5
  import { attachOverlayScrollbars, type OverlayScrollbarHandle } from "../composables/useOverlayScrollbar";
5
6
  import { asIndexable } from "../utils/typing";
6
7
  import "./HkMarkdownRenderer.scss";
@@ -118,10 +119,26 @@ export default defineComponent({
118
119
  content: { type: String, required: true },
119
120
  loading: { type: Boolean, default: false },
120
121
  plain: { type: Boolean, default: false },
122
+ /**
123
+ * Opt-in inline chip pass: fill-template tokens (`{{ name }}`) and
124
+ * asset references (`@level/name`, bare `@name`) inside the
125
+ * rendered prose chip up as `.hk-tpl-chip` spans (refs carry
126
+ * `data-ref`, the muted variant). Applied AFTER sanitize, as pure
127
+ * DOM surgery with `textContent` only — the pass cannot introduce
128
+ * markup, and `code`/`pre`/`a` subtrees are skipped so literal
129
+ * code and link text stay verbatim. Off by default: every existing
130
+ * consumer renders exactly as before.
131
+ */
132
+ inlineChips: { type: Boolean, default: false },
121
133
  },
122
134
  setup(props) {
123
135
  const renderedHtml = ref("");
124
136
  const parseError = ref(false);
137
+ // Monotonic parse counter: `renderedHtml` does not change when the
138
+ // SAME body is re-parsed for a prop flip, so a watch on it would never
139
+ // fire — and the chip pass mutates the DOM in place, leaving chips
140
+ // behind after `inlineChips` goes back to false.
141
+ const parseSeq = ref(0);
125
142
 
126
143
  async function parse() {
127
144
  if (!props.content) {
@@ -138,11 +155,13 @@ export default defineComponent({
138
155
  } catch {
139
156
  parseError.value = true;
140
157
  renderedHtml.value = renderPlain(props.content);
158
+ } finally {
159
+ parseSeq.value += 1;
141
160
  }
142
161
  }
143
162
 
144
163
  watch(
145
- () => [props.content, props.plain],
164
+ () => [props.content, props.plain, props.inlineChips],
146
165
  () => { void parse(); },
147
166
  { immediate: true },
148
167
  );
@@ -168,8 +187,20 @@ export default defineComponent({
168
187
  }
169
188
  }
170
189
 
171
- watch(renderedHtml, () => {
172
- void nextTick(syncOverlayScrollbars);
190
+ watch(parseSeq, () => {
191
+ void nextTick(() => {
192
+ const body = bodyRef.value;
193
+ if (!body) return;
194
+ // Re-seat the DOM on the pristine rendered HTML first: the chip
195
+ // pass mutates it in place, so a prop flip must start from the
196
+ // un-chipped markup (assigning the string Vue already patched is
197
+ // idempotent; when the body did not change it is the only write).
198
+ body.innerHTML = renderedHtml.value;
199
+ // The chip pass runs BEFORE the overlay scan: it splits text
200
+ // nodes, so the scan must reconcile against the final DOM.
201
+ if (props.inlineChips) applyInlineChipsToBody(body);
202
+ syncOverlayScrollbars();
203
+ });
173
204
  });
174
205
 
175
206
  onBeforeUnmount(() => {
@@ -0,0 +1,170 @@
1
+ /**
2
+ * inlineChips — the read-only chip pass for markdown-embedded prompt
3
+ * text (`HkMarkdownRenderer`'s opt-in `inlineChips` mode).
4
+ *
5
+ * Prompt assets (entelecheia's five prompt levels) embed two reference
6
+ * forms in their prose:
7
+ * - fill-template tokens `{{ name }}` — the shared fill-template
8
+ * grammar (`templateGrammar.ts` parses them; only bare
9
+ * `[A-Za-z0-9_]+` identifiers are tokens, everything richer stays
10
+ * literal);
11
+ * - asset references `@level/name` (`@system/permission-mode`) and
12
+ * bare `@name` handles — the engine's cross-asset mention form.
13
+ *
14
+ * This module splits a plain-text run into literal text and chip
15
+ * segments; the caller renders chips as `.hk-tpl-chip` spans (the
16
+ * family's chip look). Chips preserve the REFERENCE, not the exact
17
+ * bytes: tokens render the canonical `{{ name }}` spelling (so `{{name}}`
18
+ * displays as `{{ name }}` — the same rule HkTemplateText applies, and
19
+ * `parseTemplate`/`serializeTemplate` remain byte-exact for the value
20
+ * itself) while refs render their raw matched text.
21
+ *
22
+ * Boundary guards (an `@` must NOT chip):
23
+ * - preceded by a word character (`foo@bar.com` — emails stay text);
24
+ * - preceded by another `@` (`user@@host` stays text);
25
+ * refs never absorb trailing punctuation (`.`/`'`/`,` are not in the
26
+ * charset) and never match inside `code`/`pre`/links — the renderer's
27
+ * DOM walk skips those subtrees.
28
+ */
29
+
30
+ import { parseTemplate, formatTokenDisplay } from "./templateGrammar";
31
+
32
+ export interface HkInlineChipTextSegment {
33
+ kind: "text";
34
+ text: string;
35
+ }
36
+
37
+ export interface HkInlineChipTokenSegment {
38
+ kind: "token";
39
+ /** The bare identifier (`container_badge`). */
40
+ token: string;
41
+ }
42
+
43
+ export interface HkInlineChipRefSegment {
44
+ kind: "ref";
45
+ /** The exact raw matched text (`@system/permission-mode`). */
46
+ raw: string;
47
+ }
48
+
49
+ export type HkInlineChipSegment =
50
+ | HkInlineChipTextSegment
51
+ | HkInlineChipTokenSegment
52
+ | HkInlineChipRefSegment;
53
+
54
+ /** `@ref` grammar: `@` (not preceded by a word char or another `@`),
55
+ * one path segment, then `/segment` repeats. Punctuation ends the
56
+ * match naturally (dot and quote are outside the charset). */
57
+ const REF_RE = /(?<![A-Za-z0-9_@])@[A-Za-z0-9][A-Za-z0-9_-]*(?:\/[A-Za-z0-9][A-Za-z0-9_-]*)*/g;
58
+
59
+ /** Split literal text runs into text + `@ref` segments (tokens are
60
+ * already extracted by the template pass before this runs). */
61
+ function splitRefs(text: string, push: (seg: HkInlineChipSegment) => void): void {
62
+ let cursor = 0;
63
+ REF_RE.lastIndex = 0;
64
+ for (let m = REF_RE.exec(text); m; m = REF_RE.exec(text)) {
65
+ if (m.index > cursor) {
66
+ push({ kind: "text", text: text.slice(cursor, m.index) });
67
+ }
68
+ // URL guard: an `@` inside a bare URL is part of the URL. marked
69
+ // autolinks most of these (and `<a>` is a chip skip surface), but the
70
+ // rule must not depend on the engine's autolink heuristic — npm-scoped
71
+ // package URLs are common in prompt prose.
72
+ let tokenStart = m.index;
73
+ while (tokenStart > 0 && !/\s/.test(text[tokenStart - 1]!)) tokenStart -= 1;
74
+ if (text.slice(tokenStart, m.index).includes("://")) {
75
+ push({ kind: "text", text: m[0] });
76
+ cursor = m.index + m[0].length;
77
+ continue;
78
+ }
79
+ push({ kind: "ref", raw: m[0] });
80
+ cursor = m.index + m[0].length;
81
+ }
82
+ if (cursor < text.length) {
83
+ push({ kind: "text", text: text.slice(cursor) });
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Split a plain-text run into literal text, `{{ token }}` and `@ref`
89
+ * segments, in source order. The empty string parses to an empty list;
90
+ * a run with no chip forms round-trips as one text segment.
91
+ */
92
+ export function parseInlineChips(text: string): HkInlineChipSegment[] {
93
+ const out: HkInlineChipSegment[] = [];
94
+ const push = (seg: HkInlineChipSegment) => {
95
+ // Merge adjacent text runs so the caller can cheaply detect the
96
+ // "nothing to chip" case (one text segment).
97
+ const last = out[out.length - 1];
98
+ if (seg.kind === "text" && last?.kind === "text") {
99
+ last.text += seg.text;
100
+ return;
101
+ }
102
+ out.push(seg);
103
+ };
104
+ for (const seg of parseTemplate(text)) {
105
+ if (seg.kind === "token") {
106
+ push({ kind: "token", token: seg.token });
107
+ } else {
108
+ splitRefs(seg.text, push);
109
+ }
110
+ }
111
+ return out;
112
+ }
113
+
114
+ /** The canonical chip text for a segment (tokens spell `{{ name }}`,
115
+ * refs keep their raw form — both copy back to the source). */
116
+ export function inlineChipDisplay(seg: HkInlineChipTokenSegment | HkInlineChipRefSegment): string {
117
+ return seg.kind === "token" ? formatTokenDisplay(seg.token) : seg.raw;
118
+ }
119
+
120
+ /** Subtrees the chip pass must never enter: literal code (the token is
121
+ * documentation there, not a live form), links (the `@` may be part of
122
+ * a URL or handle path the anchor already contextualizes) and chips
123
+ * themselves (idempotency — a second pass must not re-chip). */
124
+ const CHIP_SKIP_SELECTOR = "code, pre, a, script, style, textarea, .hk-tpl-chip";
125
+
126
+ /**
127
+ * Walk `root`'s text nodes and wrap chip forms in `.hk-tpl-chip`
128
+ * spans. Pure DOM surgery on already-sanitized markup: the only nodes
129
+ * created are spans + text, built with `textContent` (no HTML
130
+ * parsing), so the pass cannot reintroduce markup. Tokens get the
131
+ * canonical `{{ name }}` spelling; refs carry `data-ref` (the muted
132
+ * chip variant) and their exact raw text.
133
+ *
134
+ * Returns the number of chips created (0 when the mode is off or the
135
+ * body carries no chip forms) — a cheap assertion hook for tests.
136
+ */
137
+ export function applyInlineChipsToBody(root: HTMLElement | null | undefined): number {
138
+ if (!root) return 0;
139
+ const walker = document.createTreeWalker(root, 4 /* NodeFilter.SHOW_TEXT */);
140
+ const targets: Text[] = [];
141
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) {
142
+ const value = node.nodeValue ?? "";
143
+ // Cheap prefilter: chips only ever introduce `{` or `@`.
144
+ if (!/[{@]/.test(value)) continue;
145
+ const parent = (node as Text).parentElement;
146
+ if (parent?.closest?.(CHIP_SKIP_SELECTOR)) continue;
147
+ targets.push(node as Text);
148
+ }
149
+ let chips = 0;
150
+ for (const node of targets) {
151
+ const segments = parseInlineChips(node.nodeValue ?? "");
152
+ if (segments.length === 0 || segments.every((seg) => seg.kind === "text")) continue;
153
+ const frag = document.createDocumentFragment();
154
+ for (const seg of segments) {
155
+ if (seg.kind === "text") {
156
+ frag.append(seg.text);
157
+ continue;
158
+ }
159
+ chips += 1;
160
+ const chip = document.createElement("span");
161
+ chip.className = "hk-tpl-chip";
162
+ chip.setAttribute("data-static", "");
163
+ chip.textContent = inlineChipDisplay(seg);
164
+ if (seg.kind === "ref") chip.setAttribute("data-ref", "");
165
+ frag.append(chip);
166
+ }
167
+ node.replaceWith(frag);
168
+ }
169
+ return chips;
170
+ }
package/src/index.ts CHANGED
@@ -913,6 +913,17 @@ export {
913
913
  type HkTemplateSegment,
914
914
  type HkTemplateTokenDef,
915
915
  } from "./components/templateGrammar";
916
+ // The chip grammar lives beside the fill-template grammar it reuses; both
917
+ // are exported so hosts can reason about (and test) reference forms
918
+ // without mounting a renderer.
919
+ export {
920
+ parseInlineChips,
921
+ inlineChipDisplay,
922
+ applyInlineChipsToBody,
923
+ type HkInlineChipSegment,
924
+ type HkInlineChipTokenSegment,
925
+ type HkInlineChipRefSegment,
926
+ } from "./components/inlineChips";
916
927
  export { HkErrorLanding as HkErrorLanding } from "./components/HkErrorLanding";
917
928
  export { HkErrorReportingOverlay as HkErrorReportingOverlay } from "./errorReporting";
918
929
  export { HkAdminShell as HkAdminShell } from "./components/HkAdminShell";