@burdenoff/microfe-ggnomad 2026.1029.3 → 2026.1030.2
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/dist/generated/wspace-types.d.ts +102099 -35827
- package/dist/generated/wspace-types.d.ts.map +1 -1
- package/dist/ggnomad/components/AuditTrailDialog.d.ts +6 -0
- package/dist/ggnomad/components/AuditTrailDialog.d.ts.map +1 -1
- package/dist/ggnomad/components/AuditTrailDialog.js +47 -42
- package/dist/ggnomad/components/AuditTrailDialog.js.map +1 -1
- package/dist/ggnomad/components/EntityCard.d.ts +9 -1
- package/dist/ggnomad/components/EntityCard.d.ts.map +1 -1
- package/dist/ggnomad/components/EntityCard.js +14 -7
- package/dist/ggnomad/components/EntityCard.js.map +1 -1
- package/dist/ggnomad/graphql.d.ts +6 -0
- package/dist/ggnomad/graphql.d.ts.map +1 -1
- package/dist/ggnomad/graphql.js +39 -18
- package/dist/ggnomad/graphql.js.map +1 -1
- package/dist/ggnomad/hooks/useFocusedRecordParam.d.ts +23 -0
- package/dist/ggnomad/hooks/useFocusedRecordParam.d.ts.map +1 -0
- package/dist/ggnomad/hooks/useFocusedRecordParam.js +15 -0
- package/dist/ggnomad/hooks/useFocusedRecordParam.js.map +1 -0
- package/dist/ggnomad/pages/BookingsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/BookingsPage.js +149 -142
- package/dist/ggnomad/pages/BookingsPage.js.map +1 -1
- package/dist/ggnomad/pages/ProviderAnalyticsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/ProviderAnalyticsPage.js +135 -134
- package/dist/ggnomad/pages/ProviderAnalyticsPage.js.map +1 -1
- package/dist/services/assistantQuota.d.ts +69 -0
- package/dist/services/assistantQuota.d.ts.map +1 -0
- package/dist/services/assistantQuota.js +52 -0
- package/dist/services/assistantQuota.js.map +1 -0
- package/dist/services/createSandboxAssistantTransport.d.ts +5 -3
- package/dist/services/createSandboxAssistantTransport.d.ts.map +1 -1
- package/dist/services/createSandboxAssistantTransport.js +26 -6
- package/dist/services/createSandboxAssistantTransport.js.map +1 -1
- package/dist/utils/pageContext.d.ts +62 -8
- package/dist/utils/pageContext.d.ts.map +1 -1
- package/dist/utils/pageContext.js +114 -39
- package/dist/utils/pageContext.js.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { isQuotaExhaustedError as e } from "@burdenoff/fe-libs/shared/assistant";
|
|
2
|
+
//#region src/services/assistantQuota.ts
|
|
3
|
+
var t = "ggnomad", n = `${t}:quota-exhausted`, r = `${t}:ai-credits-updated`, i = 1e4, a = {
|
|
4
|
+
"ai-credits": {
|
|
5
|
+
key: "ggnomad.assistant.quota.aiCredits",
|
|
6
|
+
fallback: "Your workspace has used all of its GGNomad AI credits for this billing period, so the assistant can't answer right now. Upgrade your plan or add credits in Billing to keep using it."
|
|
7
|
+
},
|
|
8
|
+
sandbox: {
|
|
9
|
+
key: "ggnomad.assistant.quota.sandbox",
|
|
10
|
+
fallback: "Your workspace has reached its limit of assistant sandboxes on the current plan, so the assistant can't start right now. Upgrade your plan in Billing to keep using it."
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
function o(t) {
|
|
14
|
+
return c(t, "code").includes("QUOTA_NOT_PROVISIONED") || !e(t) ? null : s(t).some((e) => e.includes("sandbox")) ? "sandbox" : "ai-credits";
|
|
15
|
+
}
|
|
16
|
+
function s(e) {
|
|
17
|
+
return c(e, "quotaName");
|
|
18
|
+
}
|
|
19
|
+
function c(e, t, n = 0) {
|
|
20
|
+
if (n > 3 || typeof e != "object" || !e) return [];
|
|
21
|
+
let r = e, i = [], a = (e) => {
|
|
22
|
+
if (typeof e != "object" || !e) return;
|
|
23
|
+
let n = e[t];
|
|
24
|
+
typeof n == "string" && n.length > 0 && i.push(n);
|
|
25
|
+
}, o = (e) => {
|
|
26
|
+
if (Array.isArray(e)) for (let t of e) typeof t == "object" && t && a(t.extensions);
|
|
27
|
+
};
|
|
28
|
+
o(r.errors), o(r.graphQLErrors);
|
|
29
|
+
let s = r.result;
|
|
30
|
+
return typeof s == "object" && s && o(s.errors), a(r.extensions), [...i, ...c(r.cause, t, n + 1)];
|
|
31
|
+
}
|
|
32
|
+
var l = -Infinity;
|
|
33
|
+
function u(e, t = Date.now()) {
|
|
34
|
+
return t - l < 1e4 ? !1 : (l = t, typeof window < "u" && window.dispatchEvent(new CustomEvent(n, { detail: { kind: e } })), !0);
|
|
35
|
+
}
|
|
36
|
+
function d(e) {
|
|
37
|
+
u(o(e) ?? "ai-credits");
|
|
38
|
+
}
|
|
39
|
+
function f() {
|
|
40
|
+
typeof window > "u" || window.dispatchEvent(new CustomEvent(r));
|
|
41
|
+
}
|
|
42
|
+
function p(e, t) {
|
|
43
|
+
let n = o(e);
|
|
44
|
+
if (!n) return e;
|
|
45
|
+
u(n);
|
|
46
|
+
let { key: r, fallback: i } = a[n], s = t?.(r, i);
|
|
47
|
+
return Error(s && s !== r ? s : i, { cause: e });
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
export { r as AI_CREDITS_UPDATED_EVENT, i as ANNOUNCE_WINDOW_MS, t as GGNOMAD_ASSISTANT_PRODUCT, n as QUOTA_EXHAUSTED_EVENT, a as QUOTA_MESSAGES, u as announceAssistantQuotaWall, o as assistantQuotaWallOf, f as handleAssistantCreditsUpdated, d as handleAssistantQuotaExhausted, s as quotaNamesIn, p as toAssistantTurnError };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=assistantQuota.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assistantQuota.js","names":[],"sources":["../../src/services/assistantQuota.ts"],"sourcesContent":["/**\n * What the GGNomad assistant does when a quota refuses it.\n *\n * Two allowances gate the assistant, both provisioned per plan in\n * global-billing-svc: `ggnomad.ai.credits` (the per-token AI credit budget,\n * charged after each answer) and `ggnomad.sandbox.environments` (the sandbox\n * the assistant runs in). When either is used up the user must be told plainly\n * and shown the way to more, never left with a generic failure:\n *\n * - the shell's `QuotaUpgradeBridge` (ggnomad-app) listens for\n * `ggnomad:quota-exhausted` and opens ONE upgrade dialog with GGNomad's copy;\n * - the chat itself shows an explicit \"AI credits used up\" answer instead of\n * the gateway's raw refusal text.\n *\n * The transport can see the same wall twice in one send (the advisory credits\n * preflight and the turn itself), so the dialog is announced at most once per\n * {@link ANNOUNCE_WINDOW_MS}: one refusal, one dialog, no stacked prompts.\n */\n\nimport { isQuotaExhaustedError } from '@burdenoff/fe-libs/shared/assistant';\n\n/** The product id the shared assistant and the shell's bridge use for GGNomad. */\nexport const GGNOMAD_ASSISTANT_PRODUCT = 'ggnomad';\n\n/** Quota names in global-billing-svc that gate the GGNomad assistant. */\nexport const GGNOMAD_AI_CREDITS_QUOTA = 'ggnomad.ai.credits';\nexport const GGNOMAD_SANDBOX_QUOTA = 'ggnomad.sandbox.environments';\n\n/**\n * The shared assistant's product-namespaced window events (fe-libs\n * `quotaExhaustedEventName` / `aiCreditsUpdatedEventName`: the shared code owns\n * the mechanism, the product owns the `<product>:` namespace). The shell's\n * `QuotaUpgradeBridge` listens for the first; a credits meter for the second.\n */\nexport const QUOTA_EXHAUSTED_EVENT = `${GGNOMAD_ASSISTANT_PRODUCT}:quota-exhausted`;\nexport const AI_CREDITS_UPDATED_EVENT = `${GGNOMAD_ASSISTANT_PRODUCT}:ai-credits-updated`;\n\n/** Repeated refusals inside this window open no further dialog. */\nexport const ANNOUNCE_WINDOW_MS = 10_000;\n\nexport type AssistantQuotaWall = 'ai-credits' | 'sandbox';\n\n/** English fallbacks; the i18n keys are looked up first. */\nexport const QUOTA_MESSAGES: Record<AssistantQuotaWall, { key: string; fallback: string }> = {\n 'ai-credits': {\n key: 'ggnomad.assistant.quota.aiCredits',\n fallback:\n \"Your workspace has used all of its GGNomad AI credits for this billing period, so the assistant can't answer right now. Upgrade your plan or add credits in Billing to keep using it.\",\n },\n sandbox: {\n key: 'ggnomad.assistant.quota.sandbox',\n fallback:\n \"Your workspace has reached its limit of assistant sandboxes on the current plan, so the assistant can't start right now. Upgrade your plan in Billing to keep using it.\",\n },\n};\n\n/**\n * Which assistant wall an error is, or null when it is not an exhausted quota.\n *\n * A quota that was never provisioned (`QUOTA_NOT_PROVISIONED`) is not\n * exhaustion: buying more would not unblock it, so it is left to the generic\n * error path rather than shown as \"credits used up\".\n */\nexport function assistantQuotaWallOf(error: unknown): AssistantQuotaWall | null {\n if (extensionValuesIn(error, 'code').includes('QUOTA_NOT_PROVISIONED')) return null;\n if (!isQuotaExhaustedError(error)) return null;\n return quotaNamesIn(error).some((name) => name.includes('sandbox')) ? 'sandbox' : 'ai-credits';\n}\n\n/**\n * Every `extensions.quotaName` the gateway attached to a refusal, wherever the\n * raw-fetch transport put the GraphQL errors (`errors`, `result.errors`,\n * `graphQLErrors`, or the error's `cause`).\n */\nexport function quotaNamesIn(error: unknown): string[] {\n return extensionValuesIn(error, 'quotaName');\n}\n\nfunction extensionValuesIn(error: unknown, key: 'quotaName' | 'code', depth = 0): string[] {\n if (depth > 3 || error === null || typeof error !== 'object') return [];\n const host = error as Record<string, unknown>;\n const values: string[] = [];\n const read = (extensions: unknown) => {\n if (extensions === null || typeof extensions !== 'object') return;\n const value = (extensions as Record<string, unknown>)[key];\n if (typeof value === 'string' && value.length > 0) values.push(value);\n };\n const collect = (list: unknown) => {\n if (!Array.isArray(list)) return;\n for (const item of list) {\n if (item !== null && typeof item === 'object') {\n read((item as { extensions?: unknown }).extensions);\n }\n }\n };\n collect(host.errors);\n collect(host.graphQLErrors);\n const result = host.result;\n if (result !== null && typeof result === 'object') {\n collect((result as { errors?: unknown }).errors);\n }\n read(host.extensions);\n return [...values, ...extensionValuesIn(host.cause, key, depth + 1)];\n}\n\nlet lastAnnouncedAt = Number.NEGATIVE_INFINITY;\n\n/** Opens the shell's upgrade dialog for this wall, at most once per window. */\nexport function announceAssistantQuotaWall(\n wall: AssistantQuotaWall,\n now: number = Date.now()\n): boolean {\n if (now - lastAnnouncedAt < ANNOUNCE_WINDOW_MS) return false;\n lastAnnouncedAt = now;\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent(QUOTA_EXHAUSTED_EVENT, { detail: { kind: wall } }));\n }\n return true;\n}\n\n/** Test seam: forget the last announcement. */\nexport function resetAssistantQuotaAnnouncements(): void {\n lastAnnouncedAt = Number.NEGATIVE_INFINITY;\n}\n\n/** `onQuotaExhausted` for the shared transport (the advisory credits preflight). */\nexport function handleAssistantQuotaExhausted(error: unknown): void {\n announceAssistantQuotaWall(assistantQuotaWallOf(error) ?? 'ai-credits');\n}\n\n/** `onCreditsUpdated` for the shared transport: lets a credits meter refetch. */\nexport function handleAssistantCreditsUpdated(): void {\n if (typeof window === 'undefined') return;\n window.dispatchEvent(new CustomEvent(AI_CREDITS_UPDATED_EVENT));\n}\n\ntype Translate = (key: string, fallback: string) => string;\n\n/**\n * The error a failed turn rethrows. A quota wall becomes an explicit message\n * (and announces the dialog); anything else is returned unchanged.\n */\nexport function toAssistantTurnError(error: unknown, translate?: Translate): unknown {\n const wall = assistantQuotaWallOf(error);\n if (!wall) return error;\n announceAssistantQuotaWall(wall);\n const { key, fallback } = QUOTA_MESSAGES[wall];\n const translated = translate?.(key, fallback);\n const message = translated && translated !== key ? translated : fallback;\n return new Error(message, { cause: error });\n}\n"],"mappings":";;AAsBA,IAAa,IAA4B,WAY5B,IAAwB,GAAG,EAA0B,mBACrD,IAA2B,GAAG,EAA0B,sBAGxD,IAAqB,KAKrB,IAAgF;CAC3F,cAAc;EACZ,KAAK;EACL,UACE;CACJ;CACA,SAAS;EACP,KAAK;EACL,UACE;CACJ;AACF;AASA,SAAgB,EAAqB,GAA2C;CAG9E,OAFI,EAAkB,GAAO,MAAM,CAAC,CAAC,SAAS,uBAAuB,KACjE,CAAC,EAAsB,CAAK,IAAU,OACnC,EAAa,CAAK,CAAC,CAAC,MAAM,MAAS,EAAK,SAAS,SAAS,CAAC,IAAI,YAAY;AACpF;AAOA,SAAgB,EAAa,GAA0B;CACrD,OAAO,EAAkB,GAAO,WAAW;AAC7C;AAEA,SAAS,EAAkB,GAAgB,GAA2B,IAAQ,GAAa;CACzF,IAAI,IAAQ,KAAuB,OAAO,KAAU,aAAnC,GAA6C,OAAO,CAAC;CACtE,IAAM,IAAO,GACP,IAAmB,CAAC,GACpB,KAAQ,MAAwB;EACpC,IAA2B,OAAO,KAAe,aAA7C,GAAuD;EAC3D,IAAM,IAAS,EAAuC;EACtD,AAAI,OAAO,KAAU,YAAY,EAAM,SAAS,KAAG,EAAO,KAAK,CAAK;CACtE,GACM,KAAW,MAAkB;EAC5B,UAAM,QAAQ,CAAI,GACvB,KAAK,IAAM,KAAQ,GACjB,AAAqB,OAAO,KAAS,YAAjC,KACF,EAAM,EAAkC,UAAU;CAGxD;CAEA,AADA,EAAQ,EAAK,MAAM,GACnB,EAAQ,EAAK,aAAa;CAC1B,IAAM,IAAS,EAAK;CAKpB,OAJuB,OAAO,KAAW,YAArC,KACF,EAAS,EAAgC,MAAM,GAEjD,EAAK,EAAK,UAAU,GACb,CAAC,GAAG,GAAQ,GAAG,EAAkB,EAAK,OAAO,GAAK,IAAQ,CAAC,CAAC;AACrE;AAEA,IAAI,IAAkB;AAGtB,SAAgB,EACd,GACA,IAAc,KAAK,IAAI,GACd;CAMT,OALI,IAAM,IAAA,MAA6C,MACvD,IAAkB,GACd,OAAO,SAAW,OACpB,OAAO,cAAc,IAAI,YAAY,GAAuB,EAAE,QAAQ,EAAE,MAAM,EAAK,EAAE,CAAC,CAAC,GAElF;AACT;AAQA,SAAgB,EAA8B,GAAsB;CAClE,EAA2B,EAAqB,CAAK,KAAK,YAAY;AACxE;AAGA,SAAgB,IAAsC;CAChD,OAAO,SAAW,OACtB,OAAO,cAAc,IAAI,YAAY,CAAwB,CAAC;AAChE;AAQA,SAAgB,EAAqB,GAAgB,GAAgC;CACnF,IAAM,IAAO,EAAqB,CAAK;CACvC,IAAI,CAAC,GAAM,OAAO;CAClB,EAA2B,CAAI;CAC/B,IAAM,EAAE,QAAK,gBAAa,EAAe,IACnC,IAAa,IAAY,GAAK,CAAQ;CAE5C,OAAW,MADK,KAAc,MAAe,IAAM,IAAa,GACtC,EAAE,OAAO,EAAM,CAAC;AAC5C"}
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The sandbox lifecycle + poll/stream loop now lives in
|
|
5
5
|
* `@burdenoff/fe-libs/shared/assistant` (one implementation for all products);
|
|
6
|
-
* this is a thin wrapper
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* this is a thin wrapper that passes the product id, GGNomad's
|
|
7
|
+
* `gatherPageContext`, and its credit-UX callbacks. The exported name/shape are
|
|
8
|
+
* unchanged so app-shell imports don't move. The public return type is a
|
|
9
|
+
* LOCALLY-declared structural mirror — never re-export fe-libs types
|
|
10
|
+
* (vite-plugin-dts path rewriting).
|
|
9
11
|
*/
|
|
10
12
|
interface AssistantSendArgs {
|
|
11
13
|
prompt: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createSandboxAssistantTransport.d.ts","sourceRoot":"","sources":["../../src/services/createSandboxAssistantTransport.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"createSandboxAssistantTransport.d.ts","sourceRoot":"","sources":["../../src/services/createSandboxAssistantTransport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAaH,UAAU,iBAAiB;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnE,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC;IAC7C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,wBAAgB,4BAA4B,IAAI,kBAAkB,CAoCjE"}
|
|
@@ -1,14 +1,34 @@
|
|
|
1
1
|
import { gatherPageContext as e } from "../utils/pageContext.js";
|
|
2
|
-
import {
|
|
2
|
+
import { GGNOMAD_ASSISTANT_PRODUCT as t, handleAssistantCreditsUpdated as n, handleAssistantQuotaExhausted as r, toAssistantTurnError as i } from "./assistantQuota.js";
|
|
3
|
+
import { useEffect as a, useMemo as o, useRef as s } from "react";
|
|
4
|
+
import { useI18n as c } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
5
|
+
import { useSandboxAssistantTransport as l } from "@burdenoff/fe-libs/shared/assistant";
|
|
3
6
|
//#region src/services/createSandboxAssistantTransport.ts
|
|
4
|
-
function
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
function u() {
|
|
8
|
+
let { t: u } = c(), d = s(u);
|
|
9
|
+
a(() => {
|
|
10
|
+
d.current = u;
|
|
11
|
+
}, [u]);
|
|
12
|
+
let f = l({
|
|
13
|
+
product: t,
|
|
7
14
|
gatherPageContext: e,
|
|
8
|
-
enableCredits: !0
|
|
15
|
+
enableCredits: !0,
|
|
16
|
+
onQuotaExhausted: r,
|
|
17
|
+
onCreditsUpdated: n
|
|
9
18
|
});
|
|
19
|
+
return o(() => ({
|
|
20
|
+
sendPrompt: async (e) => {
|
|
21
|
+
try {
|
|
22
|
+
return await f.sendPrompt(e);
|
|
23
|
+
} catch (e) {
|
|
24
|
+
throw i(e, d.current);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
prewarm: f.prewarm,
|
|
28
|
+
suspend: f.suspend
|
|
29
|
+
}), [f]);
|
|
10
30
|
}
|
|
11
31
|
//#endregion
|
|
12
|
-
export {
|
|
32
|
+
export { u as useSandboxAssistantTransport };
|
|
13
33
|
|
|
14
34
|
//# sourceMappingURL=createSandboxAssistantTransport.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createSandboxAssistantTransport.js","names":[],"sources":["../../src/services/createSandboxAssistantTransport.ts"],"sourcesContent":["/**\n * ggnomad adapter for the shared fe-libs AssistantWidget transport.\n *\n * The sandbox lifecycle + poll/stream loop now lives in\n * `@burdenoff/fe-libs/shared/assistant` (one implementation for all products);\n * this is a thin wrapper. The exported name/shape are unchanged so app-shell
|
|
1
|
+
{"version":3,"file":"createSandboxAssistantTransport.js","names":[],"sources":["../../src/services/createSandboxAssistantTransport.ts"],"sourcesContent":["/**\n * ggnomad adapter for the shared fe-libs AssistantWidget transport.\n *\n * The sandbox lifecycle + poll/stream loop now lives in\n * `@burdenoff/fe-libs/shared/assistant` (one implementation for all products);\n * this is a thin wrapper that passes the product id, GGNomad's\n * `gatherPageContext`, and its credit-UX callbacks. The exported name/shape are\n * unchanged so app-shell imports don't move. The public return type is a\n * LOCALLY-declared structural mirror — never re-export fe-libs types\n * (vite-plugin-dts path rewriting).\n */\n\nimport { useEffect, useMemo, useRef } from 'react';\nimport { useSandboxAssistantTransport as useSharedTransport } from '@burdenoff/fe-libs/shared/assistant';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { gatherPageContext } from '@/utils/pageContext';\nimport {\n GGNOMAD_ASSISTANT_PRODUCT,\n handleAssistantCreditsUpdated,\n handleAssistantQuotaExhausted,\n toAssistantTurnError,\n} from './assistantQuota';\n\ninterface AssistantSendArgs {\n prompt: string;\n onProgress: (partialText: string) => void;\n signal: AbortSignal;\n}\n\nexport interface AssistantTransport {\n sendPrompt: (args: AssistantSendArgs) => Promise<{ text: string }>;\n prewarm?: (level?: 'probe' | 'full') => void;\n suspend?: () => void;\n}\n\nexport function useSandboxAssistantTransport(): AssistantTransport {\n const { t: translate } = useI18n();\n // The transport is built once per identity; read the current translator late.\n const translateRef = useRef(translate);\n useEffect(() => {\n translateRef.current = translate;\n }, [translate]);\n\n const shared = useSharedTransport({\n product: GGNOMAD_ASSISTANT_PRODUCT,\n gatherPageContext,\n enableCredits: true,\n // The advisory preflight saw an exhausted allowance: open the shell's\n // upgrade dialog (QuotaUpgradeBridge in ggnomad-app).\n onQuotaExhausted: handleAssistantQuotaExhausted,\n // A turn was charged: a credits meter listening on\n // `ggnomad:ai-credits-updated` refetches its balance.\n onCreditsUpdated: handleAssistantCreditsUpdated,\n });\n\n return useMemo<AssistantTransport>(\n () => ({\n sendPrompt: async (args) => {\n try {\n return await shared.sendPrompt(args);\n } catch (error) {\n // A refused turn says why in the chat (\"AI credits used up\") rather\n // than surfacing the gateway's raw refusal text.\n throw toAssistantTurnError(error, translateRef.current);\n }\n },\n prewarm: shared.prewarm,\n suspend: shared.suspend,\n }),\n [shared]\n );\n}\n"],"mappings":";;;;;;AAmCA,SAAgB,IAAmD;CACjE,IAAM,EAAE,GAAG,MAAc,EAAQ,GAE3B,IAAe,EAAO,CAAS;CACrC,QAAgB;EACd,EAAa,UAAU;CACzB,GAAG,CAAC,CAAS,CAAC;CAEd,IAAM,IAAS,EAAmB;EAChC,SAAS;EACT;EACA,eAAe;EAGf,kBAAkB;EAGlB,kBAAkB;CACpB,CAAC;CAED,OAAO,SACE;EACL,YAAY,OAAO,MAAS;GAC1B,IAAI;IACF,OAAO,MAAM,EAAO,WAAW,CAAI;GACrC,SAAS,GAAO;IAGd,MAAM,EAAqB,GAAO,EAAa,OAAO;GACxD;EACF;EACA,SAAS,EAAO;EAChB,SAAS,EAAO;CAClB,IACA,CAAC,CAAM,CACT;AACF"}
|
|
@@ -5,7 +5,20 @@
|
|
|
5
5
|
* floating assistant can answer "this page" / "current" questions and run
|
|
6
6
|
* operations scoped to the screen the user is looking at.
|
|
7
7
|
*/
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* What the assistant is told about the page.
|
|
10
|
+
*
|
|
11
|
+
* A `type` alias rather than an interface so it stays assignable to the shared
|
|
12
|
+
* transport's index-signature `PageContext`. Every key here is on the agent's
|
|
13
|
+
* `ALLOWED_CONTEXT_KEYS` list (ai-assistant `src/context-loader.ts`). Keys off
|
|
14
|
+
* that list are transmitted and then dropped, so the platform contract
|
|
15
|
+
* (HOW_TO_INTEGRATE_AI_ASSISTANT.md, Step 3) forbids sending `userAgent`,
|
|
16
|
+
* `timestamp`, search text or anything else that would not be used.
|
|
17
|
+
*
|
|
18
|
+
* The key is `currentUrl`, never `activeUrl`: the agent silently drops
|
|
19
|
+
* `activeUrl`.
|
|
20
|
+
*/
|
|
21
|
+
export type PageContext = {
|
|
9
22
|
currentUrl: string;
|
|
10
23
|
currentPage: string;
|
|
11
24
|
entityType?: string;
|
|
@@ -13,16 +26,57 @@ export interface PageContext {
|
|
|
13
26
|
platform: string;
|
|
14
27
|
version: string;
|
|
15
28
|
viewportSize: string;
|
|
16
|
-
|
|
17
|
-
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Every page-context entity type the assistant can receive from GGNomad.
|
|
32
|
+
*
|
|
33
|
+
* Each one is a record `wspace-ggnomad-svc` owns, with a single-record read the
|
|
34
|
+
* assistant's GGNomad guide names (ai-assistant
|
|
35
|
+
* `context/api-calls/products/ggnomad.md`, "Page context"). Keep the two in
|
|
36
|
+
* step: the guide's tests pin this exact list.
|
|
37
|
+
*/
|
|
38
|
+
export declare const GGNOMAD_ENTITY_TYPES: readonly ["GGNOMAD_PROPERTY", "GGNOMAD_TOUR", "GGNOMAD_ACTIVITY", "GGNOMAD_EVENT", "GGNOMAD_TRIP_PLAN", "GGNOMAD_BOOKING", "GGNOMAD_PROVIDER"];
|
|
39
|
+
export type GGNomadEntityType = (typeof GGNOMAD_ENTITY_TYPES)[number];
|
|
40
|
+
/** Where a page's entity id comes from: a route segment, or the `?id=` a list page writes. */
|
|
41
|
+
type EntitySource = 'path' | 'query';
|
|
42
|
+
interface GGNomadPage {
|
|
43
|
+
/** The route as `GGNomadRoutes` registers it, relative to the MFE's base path. */
|
|
44
|
+
route: string;
|
|
45
|
+
/** Present when the page can identify one record. */
|
|
46
|
+
entity?: {
|
|
47
|
+
type: GGNomadEntityType;
|
|
48
|
+
source: EntitySource;
|
|
49
|
+
};
|
|
18
50
|
}
|
|
19
|
-
|
|
20
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Every page `GGNomadRoutes` registers, in match order (literal routes before
|
|
53
|
+
* the parameterised routes they would otherwise shadow).
|
|
54
|
+
*
|
|
55
|
+
* Detail pages carry their record in the path. The bookings and
|
|
56
|
+
* provider-analytics pages focus a record in place and write it to `?id=`
|
|
57
|
+
* through `useFocusedRecordParam` (`src/ggnomad/hooks/useFocusedRecordParam.ts`).
|
|
58
|
+
* The public storefront (`store/:slug`) is addressed by slug, not by a record
|
|
59
|
+
* id, so it sends no entity.
|
|
60
|
+
*/
|
|
61
|
+
export declare const GGNOMAD_PAGES: readonly GGNomadPage[];
|
|
62
|
+
/** The shell mounts the MFE at `/ggnomad` (ggnomad-app `src/routes/AppRoutes.tsx`). */
|
|
63
|
+
export declare const GGNOMAD_BASE_PATH = "/ggnomad";
|
|
64
|
+
export interface GGNomadConversationContext {
|
|
65
|
+
/** The matched route, e.g. `ggnomad/hospitality/:id`; ids never appear here. */
|
|
66
|
+
currentPage: string;
|
|
67
|
+
entityType?: GGNomadEntityType;
|
|
21
68
|
entityId?: string;
|
|
22
|
-
activeTab?: string;
|
|
23
|
-
metadata?: Record<string, unknown>;
|
|
24
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Resolves the page and, where the page identifies one record, its entity type
|
|
72
|
+
* and id.
|
|
73
|
+
*
|
|
74
|
+
* Paths outside the MFE's base path (shell pages such as `/billing`) resolve to
|
|
75
|
+
* their first segment with no entity. The search string is read only for the
|
|
76
|
+
* `?id=` of a focused record; free-text search terms and other parameters are
|
|
77
|
+
* never forwarded (they can carry guest names or other personal data).
|
|
78
|
+
*/
|
|
79
|
+
export declare function resolveGGNomadConversationContext(pathname: string, search: string, basePath?: string): GGNomadConversationContext;
|
|
25
80
|
export declare function gatherPageContext(): PageContext;
|
|
26
|
-
export declare function resolveGGNomadConversationContext(pathname: string, search: string): ConversationContextShape;
|
|
27
81
|
export {};
|
|
28
82
|
//# sourceMappingURL=pageContext.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pageContext.d.ts","sourceRoot":"","sources":["../../src/utils/pageContext.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"pageContext.d.ts","sourceRoot":"","sources":["../../src/utils/pageContext.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,gJAQvB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,8FAA8F;AAC9F,KAAK,YAAY,GAAG,MAAM,GAAG,OAAO,CAAC;AAErC,UAAU,WAAW;IACnB,kFAAkF;IAClF,KAAK,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,iBAAiB,CAAC;QAAC,MAAM,EAAE,YAAY,CAAA;KAAE,CAAC;CAC5D;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,WAAW,EAyB/C,CAAC;AAEF,uFAAuF;AACvF,eAAO,MAAM,iBAAiB,aAAa,CAAC;AAE5C,MAAM,WAAW,0BAA0B;IACzC,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAgCD;;;;;;;;GAQG;AACH,wBAAgB,iCAAiC,CAC/C,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,QAAQ,GAAE,MAA0B,GACnC,0BAA0B,CAyB5B;AAED,wBAAgB,iBAAiB,IAAI,WAAW,CAe/C"}
|
|
@@ -1,50 +1,125 @@
|
|
|
1
1
|
//#region src/utils/pageContext.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
2
|
+
var e = [
|
|
3
|
+
{ route: "" },
|
|
4
|
+
{ route: "search" },
|
|
5
|
+
{ route: "travel" },
|
|
6
|
+
{ route: "hospitality" },
|
|
7
|
+
{
|
|
8
|
+
route: "hospitality/:id",
|
|
9
|
+
entity: {
|
|
10
|
+
type: "GGNOMAD_PROPERTY",
|
|
11
|
+
source: "path"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
{ route: "tourism" },
|
|
15
|
+
{
|
|
16
|
+
route: "tourism/tours/:id",
|
|
17
|
+
entity: {
|
|
18
|
+
type: "GGNOMAD_TOUR",
|
|
19
|
+
source: "path"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
route: "tourism/activities/:id",
|
|
24
|
+
entity: {
|
|
25
|
+
type: "GGNOMAD_ACTIVITY",
|
|
26
|
+
source: "path"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
{ route: "entertainment" },
|
|
30
|
+
{
|
|
31
|
+
route: "entertainment/:id",
|
|
32
|
+
entity: {
|
|
33
|
+
type: "GGNOMAD_EVENT",
|
|
34
|
+
source: "path"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
route: "bookings",
|
|
39
|
+
entity: {
|
|
40
|
+
type: "GGNOMAD_BOOKING",
|
|
41
|
+
source: "query"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{ route: "travel-policy" },
|
|
45
|
+
{ route: "operations" },
|
|
46
|
+
{ route: "favorites" },
|
|
47
|
+
{ route: "provider" },
|
|
48
|
+
{
|
|
49
|
+
route: "provider/analytics",
|
|
50
|
+
entity: {
|
|
51
|
+
type: "GGNOMAD_PROVIDER",
|
|
52
|
+
source: "query"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
route: "provider/:providerId/storefront",
|
|
57
|
+
entity: {
|
|
58
|
+
type: "GGNOMAD_PROVIDER",
|
|
59
|
+
source: "path"
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{ route: "store/:slug" },
|
|
63
|
+
{ route: "analytics" },
|
|
64
|
+
{ route: "trips" },
|
|
65
|
+
{
|
|
66
|
+
route: "trips/:id",
|
|
67
|
+
entity: {
|
|
68
|
+
type: "GGNOMAD_TRIP_PLAN",
|
|
69
|
+
source: "path"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
], t = "/ggnomad", n = /^[A-Za-z0-9_-]{1,128}$/;
|
|
73
|
+
function r(e) {
|
|
74
|
+
try {
|
|
75
|
+
return decodeURIComponent(e);
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
15
79
|
}
|
|
16
|
-
function
|
|
17
|
-
|
|
80
|
+
function i(e, t) {
|
|
81
|
+
let n = e === "" ? [] : e.split("/");
|
|
82
|
+
if (n.length !== t.length) return;
|
|
83
|
+
let i = null;
|
|
84
|
+
for (let e = 0; e < n.length; e += 1) {
|
|
85
|
+
let a = n[e], o = t[e];
|
|
86
|
+
if (a === void 0 || o === void 0) return;
|
|
87
|
+
if (a.startsWith(":")) i = r(o);
|
|
88
|
+
else if (a !== o) return;
|
|
89
|
+
}
|
|
90
|
+
return i;
|
|
18
91
|
}
|
|
19
|
-
function
|
|
20
|
-
let
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
let
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
92
|
+
function a(r, a, o = t) {
|
|
93
|
+
let s = o.replace(/\/+$/, ""), c = r.replace(/\/+$/, "") || "/", l = s.replace(/^\/+/, "");
|
|
94
|
+
if (c !== s && !c.startsWith(`${s}/`)) return { currentPage: c.split("/").filter(Boolean)[0] ?? "home" };
|
|
95
|
+
let u = c.slice(s.length).split("/").filter(Boolean);
|
|
96
|
+
for (let t of e) {
|
|
97
|
+
let e = i(t.route, u);
|
|
98
|
+
if (e === void 0) continue;
|
|
99
|
+
let r = t.route ? `${l}/${t.route}` : l;
|
|
100
|
+
if (!t.entity) return { currentPage: r };
|
|
101
|
+
let o = t.entity.source === "path" ? e : new URLSearchParams(a).get("id");
|
|
102
|
+
return !o || !n.test(o) ? { currentPage: r } : {
|
|
103
|
+
currentPage: r,
|
|
104
|
+
entityType: t.entity.type,
|
|
105
|
+
entityId: o
|
|
33
106
|
};
|
|
34
107
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
108
|
+
return { currentPage: l };
|
|
109
|
+
}
|
|
110
|
+
function o() {
|
|
111
|
+
let { origin: e, pathname: t, search: n } = window.location, r = a(t, n);
|
|
39
112
|
return {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
113
|
+
currentUrl: `${e}${t}`,
|
|
114
|
+
currentPage: r.currentPage,
|
|
115
|
+
entityType: r.entityType,
|
|
116
|
+
entityId: r.entityId,
|
|
117
|
+
platform: "ggnomad",
|
|
118
|
+
version: "2026.420.95",
|
|
119
|
+
viewportSize: `${window.innerWidth}x${window.innerHeight}`
|
|
45
120
|
};
|
|
46
121
|
}
|
|
47
122
|
//#endregion
|
|
48
|
-
export { e as gatherPageContext,
|
|
123
|
+
export { t as GGNOMAD_BASE_PATH, e as GGNOMAD_PAGES, o as gatherPageContext, a as resolveGGNomadConversationContext };
|
|
49
124
|
|
|
50
125
|
//# sourceMappingURL=pageContext.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pageContext.js","names":[],"sources":["../../src/utils/pageContext.ts"],"sourcesContent":["/**\n * Page context gathering for the GGNomad AI assistant.\n *\n * Captures the user's current browser context (URL, route, entity) so the\n * floating assistant can answer \"this page\" / \"current\" questions and run\n * operations scoped to the screen the user is looking at.\n */\n\
|
|
1
|
+
{"version":3,"file":"pageContext.js","names":[],"sources":["../../src/utils/pageContext.ts"],"sourcesContent":["/**\n * Page context gathering for the GGNomad AI assistant.\n *\n * Captures the user's current browser context (URL, route, entity) so the\n * floating assistant can answer \"this page\" / \"current\" questions and run\n * operations scoped to the screen the user is looking at.\n */\n\n/**\n * What the assistant is told about the page.\n *\n * A `type` alias rather than an interface so it stays assignable to the shared\n * transport's index-signature `PageContext`. Every key here is on the agent's\n * `ALLOWED_CONTEXT_KEYS` list (ai-assistant `src/context-loader.ts`). Keys off\n * that list are transmitted and then dropped, so the platform contract\n * (HOW_TO_INTEGRATE_AI_ASSISTANT.md, Step 3) forbids sending `userAgent`,\n * `timestamp`, search text or anything else that would not be used.\n *\n * The key is `currentUrl`, never `activeUrl`: the agent silently drops\n * `activeUrl`.\n */\nexport type PageContext = {\n currentUrl: string;\n currentPage: string;\n entityType?: string;\n entityId?: string;\n platform: string;\n version: string;\n viewportSize: string;\n};\n\n/**\n * Every page-context entity type the assistant can receive from GGNomad.\n *\n * Each one is a record `wspace-ggnomad-svc` owns, with a single-record read the\n * assistant's GGNomad guide names (ai-assistant\n * `context/api-calls/products/ggnomad.md`, \"Page context\"). Keep the two in\n * step: the guide's tests pin this exact list.\n */\nexport const GGNOMAD_ENTITY_TYPES = [\n 'GGNOMAD_PROPERTY',\n 'GGNOMAD_TOUR',\n 'GGNOMAD_ACTIVITY',\n 'GGNOMAD_EVENT',\n 'GGNOMAD_TRIP_PLAN',\n 'GGNOMAD_BOOKING',\n 'GGNOMAD_PROVIDER',\n] as const;\n\nexport type GGNomadEntityType = (typeof GGNOMAD_ENTITY_TYPES)[number];\n\n/** Where a page's entity id comes from: a route segment, or the `?id=` a list page writes. */\ntype EntitySource = 'path' | 'query';\n\ninterface GGNomadPage {\n /** The route as `GGNomadRoutes` registers it, relative to the MFE's base path. */\n route: string;\n /** Present when the page can identify one record. */\n entity?: { type: GGNomadEntityType; source: EntitySource };\n}\n\n/**\n * Every page `GGNomadRoutes` registers, in match order (literal routes before\n * the parameterised routes they would otherwise shadow).\n *\n * Detail pages carry their record in the path. The bookings and\n * provider-analytics pages focus a record in place and write it to `?id=`\n * through `useFocusedRecordParam` (`src/ggnomad/hooks/useFocusedRecordParam.ts`).\n * The public storefront (`store/:slug`) is addressed by slug, not by a record\n * id, so it sends no entity.\n */\nexport const GGNOMAD_PAGES: readonly GGNomadPage[] = [\n { route: '' },\n { route: 'search' },\n { route: 'travel' },\n { route: 'hospitality' },\n { route: 'hospitality/:id', entity: { type: 'GGNOMAD_PROPERTY', source: 'path' } },\n { route: 'tourism' },\n { route: 'tourism/tours/:id', entity: { type: 'GGNOMAD_TOUR', source: 'path' } },\n { route: 'tourism/activities/:id', entity: { type: 'GGNOMAD_ACTIVITY', source: 'path' } },\n { route: 'entertainment' },\n { route: 'entertainment/:id', entity: { type: 'GGNOMAD_EVENT', source: 'path' } },\n { route: 'bookings', entity: { type: 'GGNOMAD_BOOKING', source: 'query' } },\n { route: 'travel-policy' },\n { route: 'operations' },\n { route: 'favorites' },\n { route: 'provider' },\n { route: 'provider/analytics', entity: { type: 'GGNOMAD_PROVIDER', source: 'query' } },\n {\n route: 'provider/:providerId/storefront',\n entity: { type: 'GGNOMAD_PROVIDER', source: 'path' },\n },\n { route: 'store/:slug' },\n { route: 'analytics' },\n { route: 'trips' },\n { route: 'trips/:id', entity: { type: 'GGNOMAD_TRIP_PLAN', source: 'path' } },\n];\n\n/** The shell mounts the MFE at `/ggnomad` (ggnomad-app `src/routes/AppRoutes.tsx`). */\nexport const GGNOMAD_BASE_PATH = '/ggnomad';\n\nexport interface GGNomadConversationContext {\n /** The matched route, e.g. `ggnomad/hospitality/:id`; ids never appear here. */\n currentPage: string;\n entityType?: GGNomadEntityType;\n entityId?: string;\n}\n\n/** Record ids are opaque tokens; anything else in `?id=` is ignored rather than forwarded. */\nconst ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;\n\nfunction decodeSegment(segment: string): string | null {\n try {\n return decodeURIComponent(segment);\n } catch {\n return null;\n }\n}\n\nfunction matchRoute(route: string, segments: readonly string[]): string | null | undefined {\n const parts = route === '' ? [] : route.split('/');\n if (parts.length !== segments.length) return undefined;\n let captured: string | null = null;\n for (let index = 0; index < parts.length; index += 1) {\n const part = parts[index];\n const segment = segments[index];\n if (part === undefined || segment === undefined) return undefined;\n if (part.startsWith(':')) {\n // The last parameter is the record id (`provider/:providerId/storefront`\n // has only one).\n captured = decodeSegment(segment);\n } else if (part !== segment) {\n return undefined;\n }\n }\n return captured;\n}\n\n/**\n * Resolves the page and, where the page identifies one record, its entity type\n * and id.\n *\n * Paths outside the MFE's base path (shell pages such as `/billing`) resolve to\n * their first segment with no entity. The search string is read only for the\n * `?id=` of a focused record; free-text search terms and other parameters are\n * never forwarded (they can carry guest names or other personal data).\n */\nexport function resolveGGNomadConversationContext(\n pathname: string,\n search: string,\n basePath: string = GGNOMAD_BASE_PATH\n): GGNomadConversationContext {\n const base = basePath.replace(/\\/+$/, '');\n const normalized = pathname.replace(/\\/+$/, '') || '/';\n const baseName = base.replace(/^\\/+/, '');\n\n if (normalized !== base && !normalized.startsWith(`${base}/`)) {\n const first = normalized.split('/').filter(Boolean)[0];\n return { currentPage: first ?? 'home' };\n }\n\n const segments = normalized.slice(base.length).split('/').filter(Boolean);\n for (const page of GGNOMAD_PAGES) {\n const captured = matchRoute(page.route, segments);\n if (captured === undefined) continue;\n const currentPage = page.route ? `${baseName}/${page.route}` : baseName;\n if (!page.entity) return { currentPage };\n\n const entityId =\n page.entity.source === 'path' ? captured : new URLSearchParams(search).get('id');\n if (!entityId || !ID_PATTERN.test(entityId)) return { currentPage };\n return { currentPage, entityType: page.entity.type, entityId };\n }\n\n // Unknown GGNomad path: the router redirects it to the dashboard.\n return { currentPage: baseName };\n}\n\nexport function gatherPageContext(): PageContext {\n // Queries and fragments can contain guest details, search text or credentials.\n // Filter before transport: the sandbox's key allow-list runs after transmission.\n const { origin, pathname, search } = window.location;\n const context = resolveGGNomadConversationContext(pathname, search);\n\n return {\n currentUrl: `${origin}${pathname}`,\n currentPage: context.currentPage,\n entityType: context.entityType,\n entityId: context.entityId,\n platform: 'ggnomad',\n version: import.meta.env.VITE_APP_VERSION ?? '2026.420.95',\n viewportSize: `${window.innerWidth}x${window.innerHeight}`,\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/microfe-ggnomad",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.1030.2",
|
|
4
4
|
"description": "Burdenoff GGNomad Microfrontend",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"lint:sanity": "if [ \"${CI:-}\" = \"true\" ]; then bun run lint; else bun run lint -- --cache --cache-location .eslintcache; fi",
|
|
32
32
|
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
|
|
33
33
|
"type:check": "bash scripts/build-limits.sh tsc --noEmit",
|
|
34
|
-
"
|
|
34
|
+
"test": "bun test ./tests",
|
|
35
|
+
"sanity": "bash scripts/build-limits.sh --gate bash -c 'bun run codegen && bun run lint:sanity && bun run format && bun run type:check && bun run test && bun run build && bun run build-storybook'",
|
|
35
36
|
"schema:pull": "graphql-codegen --project schema-wspace && graphql-codegen --project schema-global",
|
|
36
37
|
"codegen": "graphql-codegen --project wspace && graphql-codegen --project global",
|
|
37
38
|
"codegen:all": "bun run schema:pull && bun run codegen",
|