@hexclave/next 1.0.21 → 1.0.23

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.
Files changed (80) hide show
  1. package/README.md +187 -7
  2. package/dist/components/elements/sidebar-layout.js +1 -1
  3. package/dist/components-page/oauth-callback.js +14 -19
  4. package/dist/components-page/oauth-callback.js.map +1 -1
  5. package/dist/components-page/oauth-callback.test.d.ts +1 -0
  6. package/dist/components-page/oauth-callback.test.js +90 -0
  7. package/dist/components-page/oauth-callback.test.js.map +1 -0
  8. package/dist/dev-tool/index.d.ts +6 -5
  9. package/dist/dev-tool/index.d.ts.map +1 -1
  10. package/dist/dev-tool/index.js +16 -6
  11. package/dist/dev-tool/index.js.map +1 -1
  12. package/dist/esm/components/elements/sidebar-layout.js +1 -1
  13. package/dist/esm/components-page/oauth-callback.js +14 -19
  14. package/dist/esm/components-page/oauth-callback.js.map +1 -1
  15. package/dist/esm/components-page/oauth-callback.test.d.ts +1 -0
  16. package/dist/esm/components-page/oauth-callback.test.js +89 -0
  17. package/dist/esm/components-page/oauth-callback.test.js.map +1 -0
  18. package/dist/esm/dev-tool/index.d.ts +6 -5
  19. package/dist/esm/dev-tool/index.d.ts.map +1 -1
  20. package/dist/esm/dev-tool/index.js +16 -6
  21. package/dist/esm/dev-tool/index.js.map +1 -1
  22. package/dist/esm/generated/quetzal-translations.d.ts +2 -2
  23. package/dist/esm/lib/auth.d.ts.map +1 -1
  24. package/dist/esm/lib/auth.js +32 -11
  25. package/dist/esm/lib/auth.js.map +1 -1
  26. package/dist/esm/lib/auth.test.js +25 -10
  27. package/dist/esm/lib/auth.test.js.map +1 -1
  28. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +3 -2
  29. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
  30. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.js +1 -0
  31. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.js.map +1 -1
  32. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js +54 -0
  33. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js.map +1 -1
  34. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +5 -2
  35. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
  36. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js +29 -5
  37. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  38. package/dist/esm/lib/hexclave-app/apps/implementations/common.js +1 -1
  39. package/dist/esm/lib/hexclave-app/apps/implementations/server-app-impl.d.ts +1 -1
  40. package/dist/esm/lib/hexclave-app/apps/interfaces/admin-app.d.ts +1 -0
  41. package/dist/esm/lib/hexclave-app/apps/interfaces/admin-app.d.ts.map +1 -1
  42. package/dist/esm/lib/hexclave-app/apps/interfaces/admin-app.js.map +1 -1
  43. package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.d.ts +5 -3
  44. package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.d.ts.map +1 -1
  45. package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.js.map +1 -1
  46. package/dist/generated/quetzal-translations.d.ts +2 -2
  47. package/dist/lib/auth.d.ts.map +1 -1
  48. package/dist/lib/auth.js +31 -10
  49. package/dist/lib/auth.js.map +1 -1
  50. package/dist/lib/auth.test.js +23 -8
  51. package/dist/lib/auth.test.js.map +1 -1
  52. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +3 -2
  53. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
  54. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.js +1 -0
  55. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.js.map +1 -1
  56. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js +54 -0
  57. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js.map +1 -1
  58. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +5 -2
  59. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
  60. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js +28 -4
  61. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  62. package/dist/lib/hexclave-app/apps/implementations/common.js +1 -1
  63. package/dist/lib/hexclave-app/apps/implementations/server-app-impl.d.ts +1 -1
  64. package/dist/lib/hexclave-app/apps/interfaces/admin-app.d.ts +1 -0
  65. package/dist/lib/hexclave-app/apps/interfaces/admin-app.d.ts.map +1 -1
  66. package/dist/lib/hexclave-app/apps/interfaces/admin-app.js.map +1 -1
  67. package/dist/lib/hexclave-app/apps/interfaces/client-app.d.ts +5 -3
  68. package/dist/lib/hexclave-app/apps/interfaces/client-app.d.ts.map +1 -1
  69. package/dist/lib/hexclave-app/apps/interfaces/client-app.js.map +1 -1
  70. package/package.json +4 -4
  71. package/src/components-page/oauth-callback.test.tsx +109 -0
  72. package/src/components-page/oauth-callback.tsx +14 -19
  73. package/src/dev-tool/index.ts +27 -6
  74. package/src/lib/auth.test.ts +32 -10
  75. package/src/lib/auth.ts +41 -7
  76. package/src/lib/hexclave-app/apps/implementations/admin-app-impl.ts +2 -1
  77. package/src/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.ts +66 -0
  78. package/src/lib/hexclave-app/apps/implementations/client-app-impl.ts +40 -4
  79. package/src/lib/hexclave-app/apps/interfaces/admin-app.ts +1 -0
  80. package/src/lib/hexclave-app/apps/interfaces/client-app.ts +5 -3
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-callback.js","names":[],"sources":["../../../src/components-page/oauth-callback.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n\nimport { KnownError } from \"@hexclave/shared\";\nimport { captureError } from \"@hexclave/shared/dist/utils/errors\";\nimport { runAsynchronously } from \"@hexclave/shared/dist/utils/promises\";\nimport { Spinner, cn } from \"@hexclave/ui\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { useStackApp } from \"..\";\nimport { MaybeFullPage } from \"../components/elements/maybe-full-page\";\nimport { StyledLink } from \"../components/link\";\nimport { hexclaveAppInternalsSymbol } from \"../lib/hexclave-app\";\nimport { useTranslation } from \"../lib/translations\";\n\nexport function OAuthCallback({ fullPage }: { fullPage?: boolean }) {\n const { t } = useTranslation();\n const app = useStackApp();\n const called = useRef(false);\n const [showRedirectLink, setShowRedirectLink] = useState(false);\n const [redirectUrl, setRedirectUrl] = useState<string | null>(null);\n\n useEffect(() => runAsynchronously(async () => {\n if (called.current) return;\n called.current = true;\n const redirectToError = async (url: URL) => {\n const urlString = url.toString();\n if (app[hexclaveAppInternalsSymbol].getRedirectMethod() === \"none\") {\n setRedirectUrl(urlString);\n return;\n }\n await app[hexclaveAppInternalsSymbol].redirectToUrl(urlString, { replace: true });\n };\n try {\n const hasRedirected = await app.callOAuthCallback();\n if (!hasRedirected) {\n await app.redirectToSignIn({ noRedirectBack: true });\n }\n } catch (e) {\n if (KnownError.isKnownError(e)) {\n const errorUrl = new URL(app.urls.error, window.location.href);\n errorUrl.searchParams.set(\"errorCode\", e.errorCode);\n errorUrl.searchParams.set(\"message\", e.message);\n errorUrl.searchParams.set(\"details\", JSON.stringify(e.details ?? {}));\n await redirectToError(errorUrl);\n return;\n }\n captureError(\"<OAuthCallback />\", e);\n await redirectToError(new URL(app.urls.error, window.location.href));\n }\n }), [app]);\n\n useEffect(() => {\n setTimeout(() => setShowRedirectLink(true), 3000);\n }, []);\n\n return (\n <MaybeFullPage\n fullPage={fullPage ?? false}\n containerClassName=\"flex items-center justify-center\"\n >\n <div\n className={cn(\n \"text-center justify-center items-center stack-scope flex flex-col gap-4 max-w-[380px]\",\n fullPage ? \"p-4\" : \"p-0\"\n )}\n >\n <div className=\"flex flex-col justify-center items-center gap-4\">\n <Spinner size={20} />\n </div>\n {showRedirectLink || redirectUrl != null ? <p>{t('If you are not redirected automatically, ')}<StyledLink\n className=\"whitespace-nowrap\"\n href={redirectUrl ?? \"#\"}\n onClick={(e) => {\n if (redirectUrl != null) return;\n e.preventDefault();\n runAsynchronously(app.redirectToHome());\n }}\n >{t(\"click here\")}</StyledLink></p> : null}\n </div>\n </MaybeFullPage>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AAkBA,SAAgB,cAAc,EAAE,YAAoC;CAClE,MAAM,EAAE,MAAM,gBAAgB;CAC9B,MAAM,MAAM,aAAa;CACzB,MAAM,SAAS,OAAO,MAAM;CAC5B,MAAM,CAAC,kBAAkB,uBAAuB,SAAS,MAAM;CAC/D,MAAM,CAAC,aAAa,kBAAkB,SAAwB,KAAK;AAEnE,iBAAgB,kBAAkB,YAAY;AAC5C,MAAI,OAAO,QAAS;AACpB,SAAO,UAAU;EACjB,MAAM,kBAAkB,OAAO,QAAa;GAC1C,MAAM,YAAY,IAAI,UAAU;AAChC,OAAI,IAAI,4BAA4B,mBAAmB,KAAK,QAAQ;AAClE,mBAAe,UAAU;AACzB;;AAEF,SAAM,IAAI,4BAA4B,cAAc,WAAW,EAAE,SAAS,MAAM,CAAC;;AAEnF,MAAI;AAEF,OAAI,CADkB,MAAM,IAAI,mBAAmB,CAEjD,OAAM,IAAI,iBAAiB,EAAE,gBAAgB,MAAM,CAAC;WAE/C,GAAG;AACV,OAAI,WAAW,aAAa,EAAE,EAAE;IAC9B,MAAM,WAAW,IAAI,IAAI,IAAI,KAAK,OAAO,OAAO,SAAS,KAAK;AAC9D,aAAS,aAAa,IAAI,aAAa,EAAE,UAAU;AACnD,aAAS,aAAa,IAAI,WAAW,EAAE,QAAQ;AAC/C,aAAS,aAAa,IAAI,WAAW,KAAK,UAAU,EAAE,WAAW,EAAE,CAAC,CAAC;AACrE,UAAM,gBAAgB,SAAS;AAC/B;;AAEF,gBAAa,qBAAqB,EAAE;AACpC,SAAM,gBAAgB,IAAI,IAAI,IAAI,KAAK,OAAO,OAAO,SAAS,KAAK,CAAC;;GAEtE,EAAE,CAAC,IAAI,CAAC;AAEV,iBAAgB;AACd,mBAAiB,oBAAoB,KAAK,EAAE,IAAK;IAChD,EAAE,CAAC;AAEN,QACE,oBAAC;EACC,UAAU,YAAY;EACtB,oBAAmB;YAEnB,qBAAC;GACC,WAAW,GACT,yFACA,WAAW,QAAQ,MACpB;cAED,oBAAC;IAAI,WAAU;cACb,oBAAC,WAAQ,MAAM,KAAM;KACjB,EACL,oBAAoB,eAAe,OAAO,qBAAC,kBAAG,EAAE,4CAA4C,EAAC,oBAAC;IAC7F,WAAU;IACV,MAAM,eAAe;IACrB,UAAU,MAAM;AACd,SAAI,eAAe,KAAM;AACzB,OAAE,gBAAgB;AAClB,uBAAkB,IAAI,gBAAgB,CAAC;;cAEzC,EAAE,aAAa;KAAc,IAAI,GAAG;IAClC;GACQ"}
1
+ {"version":3,"file":"oauth-callback.js","names":[],"sources":["../../../src/components-page/oauth-callback.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n\nimport { KnownError } from \"@hexclave/shared\";\nimport { captureError } from \"@hexclave/shared/dist/utils/errors\";\nimport { runAsynchronously } from \"@hexclave/shared/dist/utils/promises\";\nimport { Spinner, cn } from \"@hexclave/ui\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { useStackApp } from \"..\";\nimport { MaybeFullPage } from \"../components/elements/maybe-full-page\";\nimport { StyledLink } from \"../components/link\";\nimport { useTranslation } from \"../lib/translations\";\nimport { ErrorPage } from \"./error-page\";\n\nexport function OAuthCallback({ fullPage }: { fullPage?: boolean }) {\n const { t } = useTranslation();\n const app = useStackApp();\n const called = useRef(false);\n const [showRedirectLink, setShowRedirectLink] = useState(false);\n const [errorSearchParams, setErrorSearchParams] = useState<Record<string, string> | null>(null);\n\n useEffect(() => runAsynchronously(async () => {\n if (called.current) return;\n called.current = true;\n try {\n const hasRedirected = await app.callOAuthCallback();\n if (!hasRedirected) {\n await app.redirectToSignIn({ noRedirectBack: true });\n }\n } catch (e) {\n if (KnownError.isKnownError(e)) {\n setErrorSearchParams({\n errorCode: e.errorCode,\n message: e.message,\n details: JSON.stringify(e.details ?? {}),\n });\n return;\n }\n captureError(\"<OAuthCallback />\", e);\n setErrorSearchParams({});\n }\n }), [app]);\n\n useEffect(() => {\n setTimeout(() => setShowRedirectLink(true), 3000);\n }, []);\n\n if (errorSearchParams != null) {\n return <ErrorPage searchParams={errorSearchParams} fullPage={fullPage} />;\n }\n\n return (\n <MaybeFullPage\n fullPage={fullPage ?? false}\n containerClassName=\"flex items-center justify-center\"\n >\n <div\n className={cn(\n \"text-center justify-center items-center stack-scope flex flex-col gap-4 max-w-[380px]\",\n fullPage ? \"p-4\" : \"p-0\"\n )}\n >\n <div className=\"flex flex-col justify-center items-center gap-4\">\n <Spinner size={20} />\n </div>\n {showRedirectLink ? <p>{t('If you are not redirected automatically, ')}<StyledLink\n className=\"whitespace-nowrap\"\n href=\"#\"\n onClick={(e) => {\n e.preventDefault();\n runAsynchronously(app.redirectToHome());\n }}\n >{t(\"click here\")}</StyledLink></p> : null}\n </div>\n </MaybeFullPage>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AAkBA,SAAgB,cAAc,EAAE,YAAoC;CAClE,MAAM,EAAE,MAAM,gBAAgB;CAC9B,MAAM,MAAM,aAAa;CACzB,MAAM,SAAS,OAAO,MAAM;CAC5B,MAAM,CAAC,kBAAkB,uBAAuB,SAAS,MAAM;CAC/D,MAAM,CAAC,mBAAmB,wBAAwB,SAAwC,KAAK;AAE/F,iBAAgB,kBAAkB,YAAY;AAC5C,MAAI,OAAO,QAAS;AACpB,SAAO,UAAU;AACjB,MAAI;AAEF,OAAI,CADkB,MAAM,IAAI,mBAAmB,CAEjD,OAAM,IAAI,iBAAiB,EAAE,gBAAgB,MAAM,CAAC;WAE/C,GAAG;AACV,OAAI,WAAW,aAAa,EAAE,EAAE;AAC9B,yBAAqB;KACnB,WAAW,EAAE;KACb,SAAS,EAAE;KACX,SAAS,KAAK,UAAU,EAAE,WAAW,EAAE,CAAC;KACzC,CAAC;AACF;;AAEF,gBAAa,qBAAqB,EAAE;AACpC,wBAAqB,EAAE,CAAC;;GAE1B,EAAE,CAAC,IAAI,CAAC;AAEV,iBAAgB;AACd,mBAAiB,oBAAoB,KAAK,EAAE,IAAK;IAChD,EAAE,CAAC;AAEN,KAAI,qBAAqB,KACvB,QAAO,oBAAC;EAAU,cAAc;EAA6B;GAAY;AAG3E,QACE,oBAAC;EACC,UAAU,YAAY;EACtB,oBAAmB;YAEnB,qBAAC;GACC,WAAW,GACT,yFACA,WAAW,QAAQ,MACpB;cAED,oBAAC;IAAI,WAAU;cACb,oBAAC,WAAQ,MAAM,KAAM;KACjB,EACL,mBAAmB,qBAAC,kBAAG,EAAE,4CAA4C,EAAC,oBAAC;IACtE,WAAU;IACV,MAAK;IACL,UAAU,MAAM;AACd,OAAE,gBAAgB;AAClB,uBAAkB,IAAI,gBAAgB,CAAC;;cAEzC,EAAE,aAAa;KAAc,IAAI,GAAG;IAClC;GACQ"}
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,89 @@
1
+ import React, { act } from "react";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { KnownErrors } from "@hexclave/shared";
4
+ import { createRoot } from "react-dom/client";
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
+ import { OAuthCallback } from "./oauth-callback.js";
7
+ import { TranslationProviderClient } from "../providers/translation-provider-client.js";
8
+
9
+ //#region src/components-page/oauth-callback.test.tsx
10
+ // @vitest-environment jsdom
11
+ const appMockState = vi.hoisted(() => ({ app: null }));
12
+ vi.mock("..", () => ({ useStackApp: () => {
13
+ if (appMockState.app == null) throw new Error("Expected test app to be set before rendering.");
14
+ return appMockState.app;
15
+ } }));
16
+ vi.mock("@hexclave/ui", async () => {
17
+ await import("react");
18
+ return {
19
+ Button: (props) => /* @__PURE__ */ jsx("button", {
20
+ type: "button",
21
+ onClick: props.onClick,
22
+ children: props.children
23
+ }),
24
+ Spinner: () => /* @__PURE__ */ jsx("div", { "data-testid": "spinner" }),
25
+ Typography: (props) => /* @__PURE__ */ jsx("div", { children: props.children }),
26
+ cn: (...classes) => classes.filter(Boolean).join(" ")
27
+ };
28
+ });
29
+ const previousActEnvironment = Reflect.get(globalThis, "IS_REACT_ACT_ENVIRONMENT");
30
+ function createAppTestDouble(options) {
31
+ return {
32
+ callOAuthCallback: options.callOAuthCallback,
33
+ redirectToSignIn: vi.fn(async () => {}),
34
+ redirectToHome: vi.fn(async () => {})
35
+ };
36
+ }
37
+ let root = null;
38
+ let container = null;
39
+ async function renderWithApp(app) {
40
+ appMockState.app = app;
41
+ container = document.createElement("div");
42
+ document.body.append(container);
43
+ root = createRoot(container);
44
+ await act(async () => {
45
+ root?.render(/* @__PURE__ */ jsx(TranslationProviderClient, {
46
+ quetzalKeys: /* @__PURE__ */ new Map(),
47
+ quetzalLocale: /* @__PURE__ */ new Map(),
48
+ children: /* @__PURE__ */ jsx(OAuthCallback, {})
49
+ }));
50
+ });
51
+ }
52
+ async function flushEffects() {
53
+ await act(async () => {
54
+ await new Promise((resolve) => setTimeout(resolve, 0));
55
+ });
56
+ }
57
+ describe("OAuthCallback", () => {
58
+ beforeEach(() => {
59
+ Reflect.set(globalThis, "IS_REACT_ACT_ENVIRONMENT", true);
60
+ });
61
+ afterEach(() => {
62
+ act(() => {
63
+ root?.unmount();
64
+ });
65
+ container?.remove();
66
+ root = null;
67
+ container = null;
68
+ appMockState.app = null;
69
+ vi.restoreAllMocks();
70
+ Reflect.set(globalThis, "IS_REACT_ACT_ENVIRONMENT", previousActEnvironment);
71
+ });
72
+ it("renders backend-encoded OAuth callback errors on the callback page", async () => {
73
+ const errorMessage = "Your sign up was rejected by an administrator's sign-up rule.";
74
+ const callOAuthCallback = vi.fn(async () => {
75
+ throw new KnownErrors.SignUpRejected(errorMessage);
76
+ });
77
+ const app = createAppTestDouble({ callOAuthCallback });
78
+ await renderWithApp(app);
79
+ await flushEffects();
80
+ expect(callOAuthCallback).toHaveBeenCalledOnce();
81
+ expect(container?.textContent).toContain("SIGN_UP_REJECTED");
82
+ expect(container?.textContent).toContain(errorMessage);
83
+ expect(app.redirectToSignIn).not.toHaveBeenCalled();
84
+ });
85
+ });
86
+
87
+ //#endregion
88
+ export { };
89
+ //# sourceMappingURL=oauth-callback.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-callback.test.js","names":[],"sources":["../../../src/components-page/oauth-callback.test.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n// @vitest-environment jsdom\n\nimport { KnownErrors } from \"@hexclave/shared\";\nimport React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport type { StackClientApp } from \"../lib/hexclave-app/apps/interfaces/client-app\";\nimport { TranslationProviderClient } from \"../providers/translation-provider-client\";\nimport { OAuthCallback } from \"./oauth-callback\";\n\nconst appMockState = vi.hoisted(() => ({ app: null as unknown }));\n\nvi.mock(\"..\", () => ({\n useStackApp: () => {\n if (appMockState.app == null) {\n throw new Error(\"Expected test app to be set before rendering.\");\n }\n return appMockState.app;\n },\n}));\n\nvi.mock(\"@hexclave/ui\", async () => {\n const React = await import(\"react\");\n return {\n Button: (props: { children: React.ReactNode, onClick?: () => void }) => (\n <button type=\"button\" onClick={props.onClick}>{props.children}</button>\n ),\n Spinner: () => <div data-testid=\"spinner\" />,\n Typography: (props: { children: React.ReactNode }) => <div>{props.children}</div>,\n cn: (...classes: (string | false | null | undefined)[]) => classes.filter(Boolean).join(\" \"),\n };\n});\n\nconst previousActEnvironment = Reflect.get(globalThis, \"IS_REACT_ACT_ENVIRONMENT\");\n\nfunction createAppTestDouble(options: {\n callOAuthCallback: () => Promise<boolean>,\n}) {\n const app = {\n callOAuthCallback: options.callOAuthCallback,\n redirectToSignIn: vi.fn(async () => {}),\n redirectToHome: vi.fn(async () => {}),\n };\n\n // This test double intentionally implements only the StackClientApp surface\n // that OAuthCallback and the rendered error card touch.\n return app as unknown as StackClientApp<true>;\n}\n\nlet root: Root | null = null;\nlet container: HTMLDivElement | null = null;\n\nasync function renderWithApp(app: StackClientApp<true>) {\n appMockState.app = app;\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n await act(async () => {\n root?.render(\n <TranslationProviderClient quetzalKeys={new Map()} quetzalLocale={new Map()}>\n <OAuthCallback />\n </TranslationProviderClient>\n );\n });\n}\n\nasync function flushEffects() {\n await act(async () => {\n await new Promise((resolve) => setTimeout(resolve, 0));\n });\n}\n\ndescribe(\"OAuthCallback\", () => {\n beforeEach(() => {\n Reflect.set(globalThis, \"IS_REACT_ACT_ENVIRONMENT\", true);\n });\n\n afterEach(() => {\n act(() => {\n root?.unmount();\n });\n container?.remove();\n root = null;\n container = null;\n appMockState.app = null;\n vi.restoreAllMocks();\n Reflect.set(globalThis, \"IS_REACT_ACT_ENVIRONMENT\", previousActEnvironment);\n });\n\n it(\"renders backend-encoded OAuth callback errors on the callback page\", async () => {\n const errorMessage = \"Your sign up was rejected by an administrator's sign-up rule.\";\n const callOAuthCallback = vi.fn(async () => {\n throw new KnownErrors.SignUpRejected(errorMessage);\n });\n const app = createAppTestDouble({ callOAuthCallback });\n\n await renderWithApp(app);\n await flushEffects();\n\n expect(callOAuthCallback).toHaveBeenCalledOnce();\n expect(container?.textContent).toContain(\"SIGN_UP_REJECTED\");\n expect(container?.textContent).toContain(errorMessage);\n expect(app.redirectToSignIn).not.toHaveBeenCalled();\n });\n});\n"],"mappings":";;;;;;;;;;AAcA,MAAM,eAAe,GAAG,eAAe,EAAE,KAAK,MAAiB,EAAE;AAEjE,GAAG,KAAK,aAAa,EACnB,mBAAmB;AACjB,KAAI,aAAa,OAAO,KACtB,OAAM,IAAI,MAAM,gDAAgD;AAElE,QAAO,aAAa;GAEvB,EAAE;AAEH,GAAG,KAAK,gBAAgB,YAAY;AACpB,OAAM,OAAO;AAC3B,QAAO;EACL,SAAS,UACP,oBAAC;GAAO,MAAK;GAAS,SAAS,MAAM;aAAU,MAAM;IAAkB;EAEzE,eAAe,oBAAC,SAAI,eAAY,YAAY;EAC5C,aAAa,UAAyC,oBAAC,mBAAK,MAAM,WAAe;EACjF,KAAK,GAAG,YAAmD,QAAQ,OAAO,QAAQ,CAAC,KAAK,IAAI;EAC7F;EACD;AAEF,MAAM,yBAAyB,QAAQ,IAAI,YAAY,2BAA2B;AAElF,SAAS,oBAAoB,SAE1B;AASD,QARY;EACV,mBAAmB,QAAQ;EAC3B,kBAAkB,GAAG,GAAG,YAAY,GAAG;EACvC,gBAAgB,GAAG,GAAG,YAAY,GAAG;EACtC;;AAOH,IAAI,OAAoB;AACxB,IAAI,YAAmC;AAEvC,eAAe,cAAc,KAA2B;AACtD,cAAa,MAAM;AACnB,aAAY,SAAS,cAAc,MAAM;AACzC,UAAS,KAAK,OAAO,UAAU;AAC/B,QAAO,WAAW,UAAU;AAC5B,OAAM,IAAI,YAAY;AACpB,QAAM,OACJ,oBAAC;GAA0B,6BAAa,IAAI,KAAK;GAAE,+BAAe,IAAI,KAAK;aACzE,oBAAC,kBAAgB;IACS,CAC7B;GACD;;AAGJ,eAAe,eAAe;AAC5B,OAAM,IAAI,YAAY;AACpB,QAAM,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;GACtD;;AAGJ,SAAS,uBAAuB;AAC9B,kBAAiB;AACf,UAAQ,IAAI,YAAY,4BAA4B,KAAK;GACzD;AAEF,iBAAgB;AACd,YAAU;AACR,SAAM,SAAS;IACf;AACF,aAAW,QAAQ;AACnB,SAAO;AACP,cAAY;AACZ,eAAa,MAAM;AACnB,KAAG,iBAAiB;AACpB,UAAQ,IAAI,YAAY,4BAA4B,uBAAuB;GAC3E;AAEF,IAAG,sEAAsE,YAAY;EACnF,MAAM,eAAe;EACrB,MAAM,oBAAoB,GAAG,GAAG,YAAY;AAC1C,SAAM,IAAI,YAAY,eAAe,aAAa;IAClD;EACF,MAAM,MAAM,oBAAoB,EAAE,mBAAmB,CAAC;AAEtD,QAAM,cAAc,IAAI;AACxB,QAAM,cAAc;AAEpB,SAAO,kBAAkB,CAAC,sBAAsB;AAChD,SAAO,WAAW,YAAY,CAAC,UAAU,mBAAmB;AAC5D,SAAO,WAAW,YAAY,CAAC,UAAU,aAAa;AACtD,SAAO,IAAI,iBAAiB,CAAC,IAAI,kBAAkB;GACnD;EACF"}
@@ -4,16 +4,17 @@ import { StackClientApp } from "../lib/hexclave-app";
4
4
  /**
5
5
  * Mounts the Hexclave dev tool on the page.
6
6
  *
7
- * - Only renders on localhost (or when overridden via console)
8
- * - Lazily loads the dev tool UI via dynamic import
9
- * - Returns a cleanup function to unmount
7
+ * Visibility is determined by the `devTool` option:
8
+ * - `true`: always show
9
+ * - `false`: never show (caller gates this — mountDevTool won't be called)
10
+ * - `"auto"` / `undefined`: show based on NODE_ENV or localhost/file: heuristics
10
11
  *
11
12
  * Console commands (also work in production):
12
13
  * HexclaveDevTool.enable() — force-show the dev tool
13
14
  * HexclaveDevTool.disable() — force-hide the dev tool
14
- * HexclaveDevTool.reset() — revert to default (localhost-only)
15
+ * HexclaveDevTool.reset() — revert to auto behavior
15
16
  */
16
- declare function mountDevTool(app: StackClientApp<true>): () => void;
17
+ declare function mountDevTool(app: StackClientApp<true>, devToolOption?: boolean | "auto"): () => void;
17
18
  //#endregion
18
19
  export { mountDevTool };
19
20
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/dev-tool/index.ts"],"mappings":";;;;;AAkFA;;;;;;;;;;iBAAgB,YAAA,CAAa,GAAA,EAAK,cAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/dev-tool/index.ts"],"mappings":";;;;;AAsGA;;;;;;;;;;;iBAAgB,YAAA,CAAa,GAAA,EAAK,cAAA,QAAsB,aAAA"}
@@ -2,6 +2,7 @@ import { runAsynchronously } from "@hexclave/shared/dist/utils/promises";
2
2
  import { canMountIntoDom } from "../in-page-ui/dom.js";
3
3
  import { captureError } from "@hexclave/shared/dist/utils/errors";
4
4
  import { isLocalhost } from "@hexclave/shared/dist/utils/urls";
5
+ import { envVars } from "../generated/env.js";
5
6
 
6
7
  //#region src/dev-tool/index.ts
7
8
  const OVERRIDE_KEY = "__hexclave-dev-tool-override";
@@ -13,10 +14,17 @@ function getOverride() {
13
14
  } catch {}
14
15
  return null;
15
16
  }
17
+ let activeDevToolOption = void 0;
16
18
  function shouldShow() {
17
19
  const override = getOverride();
18
20
  if (override !== null) return override;
19
21
  if (!canMountIntoDom()) return false;
22
+ if (activeDevToolOption === true) return true;
23
+ const nodeEnv = envVars.NODE_ENV;
24
+ if (nodeEnv !== void 0) return nodeEnv === "development";
25
+ try {
26
+ if (new URL(window.location.href).protocol === "file:") return true;
27
+ } catch {}
20
28
  return isLocalhost(window.location.href);
21
29
  }
22
30
  let activeCleanup = null;
@@ -53,17 +61,19 @@ function tryMount() {
53
61
  /**
54
62
  * Mounts the Hexclave dev tool on the page.
55
63
  *
56
- * - Only renders on localhost (or when overridden via console)
57
- * - Lazily loads the dev tool UI via dynamic import
58
- * - Returns a cleanup function to unmount
64
+ * Visibility is determined by the `devTool` option:
65
+ * - `true`: always show
66
+ * - `false`: never show (caller gates this — mountDevTool won't be called)
67
+ * - `"auto"` / `undefined`: show based on NODE_ENV or localhost/file: heuristics
59
68
  *
60
69
  * Console commands (also work in production):
61
70
  * HexclaveDevTool.enable() — force-show the dev tool
62
71
  * HexclaveDevTool.disable() — force-hide the dev tool
63
- * HexclaveDevTool.reset() — revert to default (localhost-only)
72
+ * HexclaveDevTool.reset() — revert to auto behavior
64
73
  */
65
- function mountDevTool(app) {
74
+ function mountDevTool(app, devToolOption) {
66
75
  activeApp = app;
76
+ activeDevToolOption = devToolOption === false ? void 0 : devToolOption ?? void 0;
67
77
  tryMount();
68
78
  const myCleanup = activeCleanup;
69
79
  return () => {
@@ -101,7 +111,7 @@ if (typeof window !== "undefined") window.HexclaveDevTool = window.HexclaveDevTo
101
111
  activeCleanup();
102
112
  activeCleanup = null;
103
113
  }
104
- console.log("[Stack DevTool] Reset to default (visible on localhost only).");
114
+ console.log("[Stack DevTool] Reset to default (auto-detect based on NODE_ENV or localhost/file: origin).");
105
115
  }
106
116
  };
107
117
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/dev-tool/index.ts"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n\nimport type { StackClientApp } from \"../lib/hexclave-app\";\nimport { captureError } from \"@hexclave/shared/dist/utils/errors\";\nimport { runAsynchronously } from \"@hexclave/shared/dist/utils/promises\";\nimport { isLocalhost } from \"@hexclave/shared/dist/utils/urls\";\nimport { canMountIntoDom } from \"../in-page-ui/dom\";\nimport type { createDevTool as CreateDevToolFn } from \"./dev-tool-core\";\n\n// Hexclave rebrand: UI-only local pref — straight rename (one-time reset is harmless)\nconst OVERRIDE_KEY = '__hexclave-dev-tool-override';\n\nfunction getOverride(): boolean | null {\n try {\n const val = localStorage.getItem(OVERRIDE_KEY);\n if (val === 'true') return true;\n if (val === 'false') return false;\n } catch {}\n return null;\n}\n\nfunction shouldShow(): boolean {\n const override = getOverride();\n if (override !== null) return override;\n if (!canMountIntoDom()) return false;\n return isLocalhost(window.location.href);\n}\n\nlet activeCleanup: (() => void) | null = null;\nlet activeApp: StackClientApp<true> | null = null;\nlet mountGeneration = 0;\n\nlet createDevToolPromise: Promise<typeof CreateDevToolFn> | null = null;\nfunction loadCreateDevTool(): Promise<typeof CreateDevToolFn> {\n if (!createDevToolPromise) {\n createDevToolPromise = import(\"./dev-tool-core\").then(m => m.createDevTool).catch((err) => {\n createDevToolPromise = null;\n throw err;\n });\n }\n return createDevToolPromise;\n}\n\nfunction tryMount() {\n if (activeCleanup) {\n activeCleanup();\n activeCleanup = null;\n }\n\n if (!shouldShow() || !activeApp || !canMountIntoDom()) return;\n\n const generation = ++mountGeneration;\n const app = activeApp;\n\n runAsynchronously(async () => {\n const createDevTool = await loadCreateDevTool();\n if (generation !== mountGeneration) return;\n if (!shouldShow() || activeApp !== app || !canMountIntoDom()) return;\n activeCleanup = createDevTool(app);\n }, {\n noErrorLogging: true,\n onError: (error) => {\n captureError(\"dev-tool-mount\", error);\n },\n });\n}\n\n/**\n * Mounts the Hexclave dev tool on the page.\n *\n * - Only renders on localhost (or when overridden via console)\n * - Lazily loads the dev tool UI via dynamic import\n * - Returns a cleanup function to unmount\n *\n * Console commands (also work in production):\n * HexclaveDevTool.enable() — force-show the dev tool\n * HexclaveDevTool.disable() — force-hide the dev tool\n * HexclaveDevTool.reset() — revert to default (localhost-only)\n */\nexport function mountDevTool(app: StackClientApp<true>): () => void {\n activeApp = app;\n tryMount();\n\n // Capture the cleanup created by THIS specific mount call so that React\n // StrictMode's double-invoke doesn't let the first effect's cleanup tear\n // down the second mount (which would cause the tool to disappear silently).\n const myCleanup = activeCleanup;\n\n return () => {\n activeApp = null;\n if (activeCleanup === myCleanup && myCleanup != null) {\n activeCleanup = null;\n myCleanup();\n }\n };\n}\n\n// Expose console commands: HexclaveDevTool.enable() / .disable() / .reset()\nif (typeof window !== 'undefined') {\n // Hexclave rebrand: expose under both the legacy and new global names.\n (window as any).HexclaveDevTool = (window as any).HexclaveDevTool = {\n enable() {\n try {\n localStorage.setItem(OVERRIDE_KEY, 'true');\n } catch {}\n tryMount();\n console.log('[Stack DevTool] Enabled. Refresh if the panel does not appear.');\n },\n disable() {\n try {\n localStorage.setItem(OVERRIDE_KEY, 'false');\n } catch {}\n if (activeCleanup) {\n activeCleanup();\n activeCleanup = null;\n }\n console.log('[Stack DevTool] Disabled.');\n },\n reset() {\n try {\n localStorage.removeItem(OVERRIDE_KEY);\n } catch {}\n if (shouldShow()) {\n tryMount();\n } else if (activeCleanup) {\n activeCleanup();\n activeCleanup = null;\n }\n console.log('[Stack DevTool] Reset to default (visible on localhost only).');\n },\n };\n}\n\n"],"mappings":";;;;;;AAaA,MAAM,eAAe;AAErB,SAAS,cAA8B;AACrC,KAAI;EACF,MAAM,MAAM,aAAa,QAAQ,aAAa;AAC9C,MAAI,QAAQ,OAAQ,QAAO;AAC3B,MAAI,QAAQ,QAAS,QAAO;SACtB;AACR,QAAO;;AAGT,SAAS,aAAsB;CAC7B,MAAM,WAAW,aAAa;AAC9B,KAAI,aAAa,KAAM,QAAO;AAC9B,KAAI,CAAC,iBAAiB,CAAE,QAAO;AAC/B,QAAO,YAAY,OAAO,SAAS,KAAK;;AAG1C,IAAI,gBAAqC;AACzC,IAAI,YAAyC;AAC7C,IAAI,kBAAkB;AAEtB,IAAI,uBAA+D;AACnE,SAAS,oBAAqD;AAC5D,KAAI,CAAC,qBACH,wBAAuB,OAAO,sBAAmB,MAAK,MAAK,EAAE,cAAc,CAAC,OAAO,QAAQ;AACzF,yBAAuB;AACvB,QAAM;GACN;AAEJ,QAAO;;AAGT,SAAS,WAAW;AAClB,KAAI,eAAe;AACjB,iBAAe;AACf,kBAAgB;;AAGlB,KAAI,CAAC,YAAY,IAAI,CAAC,aAAa,CAAC,iBAAiB,CAAE;CAEvD,MAAM,aAAa,EAAE;CACrB,MAAM,MAAM;AAEZ,mBAAkB,YAAY;EAC5B,MAAM,gBAAgB,MAAM,mBAAmB;AAC/C,MAAI,eAAe,gBAAiB;AACpC,MAAI,CAAC,YAAY,IAAI,cAAc,OAAO,CAAC,iBAAiB,CAAE;AAC9D,kBAAgB,cAAc,IAAI;IACjC;EACD,gBAAgB;EAChB,UAAU,UAAU;AAClB,gBAAa,kBAAkB,MAAM;;EAExC,CAAC;;;;;;;;;;;;;;AAeJ,SAAgB,aAAa,KAAuC;AAClE,aAAY;AACZ,WAAU;CAKV,MAAM,YAAY;AAElB,cAAa;AACX,cAAY;AACZ,MAAI,kBAAkB,aAAa,aAAa,MAAM;AACpD,mBAAgB;AAChB,cAAW;;;;AAMjB,IAAI,OAAO,WAAW,YAEpB,CAAC,OAAe,kBAAkB,AAAC,OAAe,kBAAkB;CAClE,SAAS;AACP,MAAI;AACF,gBAAa,QAAQ,cAAc,OAAO;UACpC;AACR,YAAU;AACV,UAAQ,IAAI,iEAAiE;;CAE/E,UAAU;AACR,MAAI;AACF,gBAAa,QAAQ,cAAc,QAAQ;UACrC;AACR,MAAI,eAAe;AACjB,kBAAe;AACf,mBAAgB;;AAElB,UAAQ,IAAI,4BAA4B;;CAE1C,QAAQ;AACN,MAAI;AACF,gBAAa,WAAW,aAAa;UAC/B;AACR,MAAI,YAAY,CACd,WAAU;WACD,eAAe;AACxB,kBAAe;AACf,mBAAgB;;AAElB,UAAQ,IAAI,gEAAgE;;CAE/E"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/dev-tool/index.ts"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n\nimport type { StackClientApp } from \"../lib/hexclave-app\";\nimport { captureError } from \"@hexclave/shared/dist/utils/errors\";\nimport { runAsynchronously } from \"@hexclave/shared/dist/utils/promises\";\nimport { isLocalhost } from \"@hexclave/shared/dist/utils/urls\";\nimport { canMountIntoDom } from \"../in-page-ui/dom\";\nimport { envVars } from \"../generated/env\";\nimport type { createDevTool as CreateDevToolFn } from \"./dev-tool-core\";\n\n// Hexclave rebrand: UI-only local pref — straight rename (one-time reset is harmless)\nconst OVERRIDE_KEY = '__hexclave-dev-tool-override';\n\nfunction getOverride(): boolean | null {\n try {\n const val = localStorage.getItem(OVERRIDE_KEY);\n if (val === 'true') return true;\n if (val === 'false') return false;\n } catch {}\n return null;\n}\n\nlet activeDevToolOption: true | \"auto\" | undefined = undefined;\n\nfunction shouldShow(): boolean {\n const override = getOverride();\n if (override !== null) return override;\n if (!canMountIntoDom()) return false;\n\n // If devTool was explicitly set to true, always show\n if (activeDevToolOption === true) return true;\n\n // \"auto\" behavior (the default):\n const nodeEnv = envVars.NODE_ENV;\n if (nodeEnv !== undefined) {\n // NODE_ENV is available (bundler/process env exists) — only show in development\n return nodeEnv === \"development\";\n }\n\n // NODE_ENV not found (no process.env/import.meta) — show on localhost or file: protocol\n try {\n const url = new URL(window.location.href);\n if (url.protocol === \"file:\") return true;\n } catch {}\n return isLocalhost(window.location.href);\n}\n\nlet activeCleanup: (() => void) | null = null;\nlet activeApp: StackClientApp<true> | null = null;\nlet mountGeneration = 0;\n\nlet createDevToolPromise: Promise<typeof CreateDevToolFn> | null = null;\nfunction loadCreateDevTool(): Promise<typeof CreateDevToolFn> {\n if (!createDevToolPromise) {\n createDevToolPromise = import(\"./dev-tool-core\").then(m => m.createDevTool).catch((err) => {\n createDevToolPromise = null;\n throw err;\n });\n }\n return createDevToolPromise;\n}\n\nfunction tryMount() {\n if (activeCleanup) {\n activeCleanup();\n activeCleanup = null;\n }\n\n if (!shouldShow() || !activeApp || !canMountIntoDom()) return;\n\n const generation = ++mountGeneration;\n const app = activeApp;\n\n runAsynchronously(async () => {\n const createDevTool = await loadCreateDevTool();\n if (generation !== mountGeneration) return;\n if (!shouldShow() || activeApp !== app || !canMountIntoDom()) return;\n activeCleanup = createDevTool(app);\n }, {\n noErrorLogging: true,\n onError: (error) => {\n captureError(\"dev-tool-mount\", error);\n },\n });\n}\n\n/**\n * Mounts the Hexclave dev tool on the page.\n *\n * Visibility is determined by the `devTool` option:\n * - `true`: always show\n * - `false`: never show (caller gates this mountDevTool won't be called)\n * - `\"auto\"` / `undefined`: show based on NODE_ENV or localhost/file: heuristics\n *\n * Console commands (also work in production):\n * HexclaveDevTool.enable() — force-show the dev tool\n * HexclaveDevTool.disable() — force-hide the dev tool\n * HexclaveDevTool.reset() — revert to auto behavior\n */\nexport function mountDevTool(app: StackClientApp<true>, devToolOption?: boolean | \"auto\"): () => void {\n activeApp = app;\n activeDevToolOption = devToolOption === false ? undefined : devToolOption ?? undefined;\n tryMount();\n\n // Capture the cleanup created by THIS specific mount call so that React\n // StrictMode's double-invoke doesn't let the first effect's cleanup tear\n // down the second mount (which would cause the tool to disappear silently).\n const myCleanup = activeCleanup;\n\n return () => {\n activeApp = null;\n if (activeCleanup === myCleanup && myCleanup != null) {\n activeCleanup = null;\n myCleanup();\n }\n };\n}\n\n// Expose console commands: HexclaveDevTool.enable() / .disable() / .reset()\nif (typeof window !== 'undefined') {\n // Hexclave rebrand: expose under both the legacy and new global names.\n (window as any).HexclaveDevTool = (window as any).HexclaveDevTool = {\n enable() {\n try {\n localStorage.setItem(OVERRIDE_KEY, 'true');\n } catch {}\n tryMount();\n console.log('[Stack DevTool] Enabled. Refresh if the panel does not appear.');\n },\n disable() {\n try {\n localStorage.setItem(OVERRIDE_KEY, 'false');\n } catch {}\n if (activeCleanup) {\n activeCleanup();\n activeCleanup = null;\n }\n console.log('[Stack DevTool] Disabled.');\n },\n reset() {\n try {\n localStorage.removeItem(OVERRIDE_KEY);\n } catch {}\n if (shouldShow()) {\n tryMount();\n } else if (activeCleanup) {\n activeCleanup();\n activeCleanup = null;\n }\n console.log('[Stack DevTool] Reset to default (auto-detect based on NODE_ENV or localhost/file: origin).');\n },\n };\n}\n\n"],"mappings":";;;;;;;AAcA,MAAM,eAAe;AAErB,SAAS,cAA8B;AACrC,KAAI;EACF,MAAM,MAAM,aAAa,QAAQ,aAAa;AAC9C,MAAI,QAAQ,OAAQ,QAAO;AAC3B,MAAI,QAAQ,QAAS,QAAO;SACtB;AACR,QAAO;;AAGT,IAAI,sBAAiD;AAErD,SAAS,aAAsB;CAC7B,MAAM,WAAW,aAAa;AAC9B,KAAI,aAAa,KAAM,QAAO;AAC9B,KAAI,CAAC,iBAAiB,CAAE,QAAO;AAG/B,KAAI,wBAAwB,KAAM,QAAO;CAGzC,MAAM,UAAU,QAAQ;AACxB,KAAI,YAAY,OAEd,QAAO,YAAY;AAIrB,KAAI;AAEF,MADY,IAAI,IAAI,OAAO,SAAS,KAAK,CACjC,aAAa,QAAS,QAAO;SAC/B;AACR,QAAO,YAAY,OAAO,SAAS,KAAK;;AAG1C,IAAI,gBAAqC;AACzC,IAAI,YAAyC;AAC7C,IAAI,kBAAkB;AAEtB,IAAI,uBAA+D;AACnE,SAAS,oBAAqD;AAC5D,KAAI,CAAC,qBACH,wBAAuB,OAAO,sBAAmB,MAAK,MAAK,EAAE,cAAc,CAAC,OAAO,QAAQ;AACzF,yBAAuB;AACvB,QAAM;GACN;AAEJ,QAAO;;AAGT,SAAS,WAAW;AAClB,KAAI,eAAe;AACjB,iBAAe;AACf,kBAAgB;;AAGlB,KAAI,CAAC,YAAY,IAAI,CAAC,aAAa,CAAC,iBAAiB,CAAE;CAEvD,MAAM,aAAa,EAAE;CACrB,MAAM,MAAM;AAEZ,mBAAkB,YAAY;EAC5B,MAAM,gBAAgB,MAAM,mBAAmB;AAC/C,MAAI,eAAe,gBAAiB;AACpC,MAAI,CAAC,YAAY,IAAI,cAAc,OAAO,CAAC,iBAAiB,CAAE;AAC9D,kBAAgB,cAAc,IAAI;IACjC;EACD,gBAAgB;EAChB,UAAU,UAAU;AAClB,gBAAa,kBAAkB,MAAM;;EAExC,CAAC;;;;;;;;;;;;;;;AAgBJ,SAAgB,aAAa,KAA2B,eAA8C;AACpG,aAAY;AACZ,uBAAsB,kBAAkB,QAAQ,SAAY,iBAAiB;AAC7E,WAAU;CAKV,MAAM,YAAY;AAElB,cAAa;AACX,cAAY;AACZ,MAAI,kBAAkB,aAAa,aAAa,MAAM;AACpD,mBAAgB;AAChB,cAAW;;;;AAMjB,IAAI,OAAO,WAAW,YAEpB,CAAC,OAAe,kBAAkB,AAAC,OAAe,kBAAkB;CAClE,SAAS;AACP,MAAI;AACF,gBAAa,QAAQ,cAAc,OAAO;UACpC;AACR,YAAU;AACV,UAAQ,IAAI,iEAAiE;;CAE/E,UAAU;AACR,MAAI;AACF,gBAAa,QAAQ,cAAc,QAAQ;UACrC;AACR,MAAI,eAAe;AACjB,kBAAe;AACf,mBAAgB;;AAElB,UAAQ,IAAI,4BAA4B;;CAE1C,QAAQ;AACN,MAAI;AACF,gBAAa,WAAW,aAAa;UAC/B;AACR,MAAI,YAAY,CACd,WAAU;WACD,eAAe;AACxB,kBAAe;AACf,mBAAgB;;AAElB,UAAQ,IAAI,8FAA8F;;CAE7G"}