@celestia-island/hikari 0.55.166 → 0.55.167

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.166",
3
+ "version": "0.55.167",
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",
@@ -80,6 +80,17 @@ export const HkErrorReportingOverlay = defineComponent({
80
80
  if (!err) return null;
81
81
  const options = getErrorReportingOptions();
82
82
 
83
+ // Host-owned card: everything inside the takeover is the host's —
84
+ // content, actions, dismissal. The host keeps the capture channels
85
+ // and the crash-surviving mount; only the card is swapped.
86
+ if (options.landing) {
87
+ return (
88
+ <div class="hk-error-reporting-overlay" role="alertdialog" aria-live="assertive" aria-modal="true">
89
+ {options.landing(err)}
90
+ </div>
91
+ );
92
+ }
93
+
83
94
  const description = options.describe
84
95
  ? options.describe(err)
85
96
  : err.message || t("hikari::errors.unexpectedDesc", "An unhandled error occurred");
@@ -207,4 +207,75 @@ describe("createErrorReporting", () => {
207
207
  clearGlobalError();
208
208
  expect(overlayHost()).toBeNull();
209
209
  });
210
+
211
+ it("renders the host landing card instead of the built-in one", async () => {
212
+ vi.spyOn(console, "error").mockImplementation(() => {});
213
+ installPlugin({
214
+ landing: (err) =>
215
+ h("div", { class: "host-card", "data-code": err.name }, [
216
+ h("p", { class: "host-card__msg" }, err.message),
217
+ h(
218
+ "button",
219
+ { class: "host-card__dismiss", onClick: () => clearGlobalError() },
220
+ "Dismiss",
221
+ ),
222
+ ]),
223
+ });
224
+ reportGlobalError(new TypeError("host boom"));
225
+ await nextTick();
226
+
227
+ const host = overlayHost();
228
+ expect(host).not.toBeNull();
229
+ // The built-in card is fully replaced by the host's.
230
+ expect(host!.querySelector(".hk-error-landing")).toBeNull();
231
+ expect(host!.querySelector(".host-card")).not.toBeNull();
232
+ expect(host!.querySelector(".host-card")!.getAttribute("data-code")).toBe("TypeError");
233
+ expect(host!.querySelector(".host-card__msg")!.textContent).toBe("host boom");
234
+ // The host owns the actions: the built-in Home/Retry pair is gone.
235
+ expect(host!.textContent).not.toContain("Back to home");
236
+ expect(host!.textContent).not.toContain("Retry");
237
+ // The takeover shell stays (fixed, alert-dialog semantics).
238
+ expect(host!.querySelector(".hk-error-reporting-overlay")!.getAttribute("role")).toBe(
239
+ "alertdialog",
240
+ );
241
+ // The host card rides INSIDE the takeover shell (same fixed z-band), not
242
+ // beside it: a sibling placement would float outside --hk-z-error. The
243
+ // quadrant of these assertions that pins "same shell" is the ancestor
244
+ // relationship — descendants of the shell, not just of the data node.
245
+ expect(host!.querySelector(".hk-error-reporting-overlay .host-card")).not.toBeNull();
246
+ // The host's own dismissal tears the takeover down.
247
+ (host!.querySelector(".host-card__dismiss") as HTMLButtonElement).click();
248
+ await nextTick();
249
+ expect(overlayHost()).toBeNull();
250
+ });
251
+
252
+ it("lets landing step over title/describe and the built-in actions", async () => {
253
+ vi.spyOn(console, "error").mockImplementation(() => {});
254
+ installPlugin({
255
+ title: "ignored headline",
256
+ describe: () => "ignored description",
257
+ homeHref: "/should-not-be-used",
258
+ onHome: vi.fn(),
259
+ onRetry: vi.fn(),
260
+ landing: (err) =>
261
+ h("div", { class: "host-card", "data-msg": err.message }, [
262
+ h("p", { class: "host-card__msg" }, err.message),
263
+ ]),
264
+ });
265
+ reportGlobalError(new TypeError("own content"));
266
+ await nextTick();
267
+
268
+ const host = overlayHost()!;
269
+ // The host's card won outright; the built-in card (and its translated
270
+ // headline / description) never mounted.
271
+ expect(host.querySelector(".hk-error-landing")).toBeNull();
272
+ expect(host.querySelector(".host-card__msg")!.textContent).toBe("own content");
273
+ expect(host.textContent).not.toContain("ignored headline");
274
+ expect(host.textContent).not.toContain("ignored description");
275
+ expect(host.textContent).not.toContain("Back to home");
276
+ expect(host.textContent).not.toContain("Retry");
277
+ // The host card still receives the raw accepted record, not a
278
+ // title/describe-shaped one — the overrides never leak in.
279
+ expect(host.querySelector(".host-card")!.getAttribute("data-msg")).toBe("own content");
280
+ });
210
281
  });
@@ -32,6 +32,11 @@ export { HkErrorReportingOverlay } from "./HkErrorReportingOverlay";
32
32
  * with Home / Retry actions. Because the overlay lives on its own tiny app
33
33
  * instance, it still shows when the host tree itself is what crashed.
34
34
  *
35
+ * Hosts whose error landing carries app-specific semantics pass
36
+ * `options.landing(err)`: the takeover then renders the host's card inside
37
+ * the same crash-surviving shell — capture channels and the takeover
38
+ * guarantee stay, the built-in card and its actions step aside.
39
+ *
35
40
  * Any pre-existing `app.config.errorHandler` is chained, not discarded.
36
41
  * All capture channels are optional; `shouldReport` filters, `onError` is
37
42
  * the telemetry hook.
@@ -1,4 +1,4 @@
1
- import { ref, type Ref } from "vue";
1
+ import { ref, type Ref, type VNodeChild } from "vue";
2
2
 
3
3
  import { reportHkRuntime, type HkRuntimeHandle } from "../runtime/registry";
4
4
 
@@ -17,11 +17,23 @@ export interface HkReportedError {
17
17
  }
18
18
 
19
19
  export interface HkErrorReportingOptions {
20
- /** Headline override; defaults to `hikari::errors.defaultTitle`. */
20
+ /** Headline override; defaults to `hikari::errors.defaultTitle`.
21
+ * Ignored when `landing` is set (the host owns the whole card). */
21
22
  title?: string;
22
23
  /** Description builder; defaults to the error's message, falling back to
23
- * `hikari::errors.unexpectedDesc` when the error carries none. */
24
+ * `hikari::errors.unexpectedDesc` when the error carries none.
25
+ * Ignored when `landing` is set. */
24
26
  describe?: (err: HkReportedError) => string;
27
+ /** Host-provided takeover card — the unification hatch for hosts whose
28
+ * error landing carries app-specific semantics (localized per-code
29
+ * headlines, raw wire payloads, a Dismiss action). When set, the overlay
30
+ * renders `landing(err)` INSTEAD of the built-in HkErrorLanding, inside
31
+ * the same crash-surviving host (dedicated body app, --hk-z-error): the
32
+ * host keeps every capture channel and the takeover guarantee and owns
33
+ * the card's content and actions outright — `title`, `describe`,
34
+ * `homeHref`, `onHome` and `onRetry` are all ignored. Resolve app-shaped
35
+ * payloads from `err.error` (e.g. an Error subclass carrying them). */
36
+ landing?: (err: HkReportedError) => VNodeChild;
25
37
  /** Target of the "back to home" action. `false` hides the button.
26
38
  * Ignored when `onHome` is set. Default `/`. */
27
39
  homeHref?: string | false;