@burdenoff/microfe-vibecontrols 2026.601.2 → 2026.601.3
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/components/agent-manager/dialogs/NewSessionDialog.js +16 -15
- package/dist/components/agent-manager/dialogs/NewSessionDialog.js.map +1 -1
- package/dist/components/docs/DocsTabPanel.js +7 -4
- package/dist/components/docs/DocsTabPanel.js.map +1 -1
- package/dist/pages/VibesPage.js +3 -3
- package/dist/pages/VibesPage.js.map +1 -1
- package/dist/pages/ai/AgentManagerPage.js +138 -127
- package/dist/pages/ai/AgentManagerPage.js.map +1 -1
- package/dist/providers/VibeControlsProvider.js +43 -35
- package/dist/providers/VibeControlsProvider.js.map +1 -1
- package/package.json +1 -1
|
@@ -5,20 +5,20 @@ import r from "./AgentEventsProvider.js";
|
|
|
5
5
|
import { QUOTA_EXHAUSTED_EVENT as i, inferQuotaKindFromOperation as a } from "../utils/quotaUtils.js";
|
|
6
6
|
import { QuotaExhaustedProvider as o } from "./QuotaExhaustedProvider.js";
|
|
7
7
|
import s from "../hooks/useInitialDataLoader.js";
|
|
8
|
-
import { createContext as c, use as l, useCallback as u, useEffect as d, useMemo as f, useRef as p, useState as
|
|
9
|
-
import { MemoryRouter as
|
|
10
|
-
import { ApolloClient as
|
|
11
|
-
import { onError as
|
|
12
|
-
import { setContext as
|
|
13
|
-
import { ApolloProvider as
|
|
14
|
-
import { QueryClient as
|
|
15
|
-
import { getStoredAuthToken as
|
|
8
|
+
import { createContext as c, use as l, useCallback as u, useEffect as d, useMemo as f, useRef as p, useState as m } from "react";
|
|
9
|
+
import { MemoryRouter as h, useNavigate as g } from "react-router-dom";
|
|
10
|
+
import { ApolloClient as ee, from as _ } from "@apollo/client";
|
|
11
|
+
import { onError as v } from "@apollo/client/link/error";
|
|
12
|
+
import { setContext as y } from "@apollo/client/link/context";
|
|
13
|
+
import { ApolloProvider as b } from "@apollo/client/react";
|
|
14
|
+
import { MutationCache as x, QueryClient as te, QueryClientProvider as S } from "@tanstack/react-query";
|
|
15
|
+
import { getStoredAuthToken as ne, getStoredWorkspaceToken as C, isWorkspaceTokenValidForContext as w } from "@burdenoff/fe-libs/shared/graphql";
|
|
16
16
|
import { getStoredWorkspaceId as T } from "@burdenoff/fe-libs/shared/graphql/fetch";
|
|
17
|
-
import { PermissionProvider as
|
|
18
|
-
import { isAuthNetworkError as
|
|
17
|
+
import { PermissionProvider as re } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
18
|
+
import { isAuthNetworkError as ie } from "@burdenoff/fe-libs/shared/utils";
|
|
19
19
|
import { Fragment as E, jsx as D } from "react/jsx-runtime";
|
|
20
20
|
//#region src/providers/VibeControlsProvider.tsx
|
|
21
|
-
function
|
|
21
|
+
function ae() {
|
|
22
22
|
try {
|
|
23
23
|
let e = sessionStorage.getItem("burdenoff-active-context-project");
|
|
24
24
|
if (e) return e;
|
|
@@ -30,7 +30,7 @@ function ie() {
|
|
|
30
30
|
return null;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
function
|
|
33
|
+
function oe(e = []) {
|
|
34
34
|
return e.some((e) => {
|
|
35
35
|
let t = typeof e.extensions?.code == "string" ? e.extensions.code : "", n = typeof e.extensions?.errorCode == "string" ? e.extensions.errorCode : "", r = (e.message ?? "").toLowerCase();
|
|
36
36
|
return t === "UNAUTHENTICATED" || n === "TOKEN_EXPIRED" || r.includes("token_expired") || r.includes("token expired") || r.includes("session expired") || r.includes("unauthorized") || r.includes("unauthenticated") || r.includes("authentication required") || r.includes("workspace context missing");
|
|
@@ -47,7 +47,7 @@ var k = ({ workspaceId: e, children: t }) => (s({ workspaceId: e }), /* @__PURE_
|
|
|
47
47
|
if (!e) throw Error("useVibeControls must be used within VibeControlsProvider");
|
|
48
48
|
return e;
|
|
49
49
|
}, M = () => {
|
|
50
|
-
let { navigate: e, basePath: t } = j(), n =
|
|
50
|
+
let { navigate: e, basePath: t } = j(), n = g();
|
|
51
51
|
return u((r) => {
|
|
52
52
|
let i = t && t !== "/" ? `${t}${r}` : r;
|
|
53
53
|
e ? e(i) : n(i);
|
|
@@ -56,18 +56,26 @@ var k = ({ workspaceId: e, children: t }) => (s({ workspaceId: e }), /* @__PURE_
|
|
|
56
56
|
t,
|
|
57
57
|
n
|
|
58
58
|
]);
|
|
59
|
-
}, N = ({ children: s, basePath: c, navigate: l, currentUser: u, workspaceId:
|
|
60
|
-
let [U] =
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
}, N = ({ children: s, basePath: c, navigate: l, currentUser: u, workspaceId: g, organizationId: E, projectId: j, tenantId: M, apiGatewayUrl: N, authToken: P, workspaceToken: F, onAgentSelect: I, onSessionStart: L, onVibeActivate: R, defaultView: z, registerNavItems: B, registerFooterItems: V, onAuthError: H }) => {
|
|
60
|
+
let [U] = m(() => {
|
|
61
|
+
let e = new te({
|
|
62
|
+
mutationCache: new x({ onSuccess: () => {
|
|
63
|
+
e.invalidateQueries();
|
|
64
|
+
} }),
|
|
65
|
+
defaultOptions: { queries: {
|
|
66
|
+
staleTime: 300 * 1e3,
|
|
67
|
+
retry: 2
|
|
68
|
+
} }
|
|
69
|
+
});
|
|
70
|
+
return e;
|
|
71
|
+
}), W = p(P), G = p(F), K = p(g), q = p(E), J = p(M), Y = p(j), X = p(H);
|
|
64
72
|
d(() => {
|
|
65
73
|
W.current = P;
|
|
66
74
|
}, [P]), d(() => {
|
|
67
75
|
G.current = F;
|
|
68
76
|
}, [F]), d(() => {
|
|
69
|
-
K.current =
|
|
70
|
-
}, [
|
|
77
|
+
K.current = g;
|
|
78
|
+
}, [g]), d(() => {
|
|
71
79
|
q.current = E;
|
|
72
80
|
}, [E]), d(() => {
|
|
73
81
|
J.current = M;
|
|
@@ -76,11 +84,11 @@ var k = ({ workspaceId: e, children: t }) => (s({ workspaceId: e }), /* @__PURE_
|
|
|
76
84
|
}, [j]), d(() => {
|
|
77
85
|
X.current = H;
|
|
78
86
|
}, [H]);
|
|
79
|
-
let
|
|
80
|
-
link:
|
|
81
|
-
|
|
87
|
+
let se = f(() => new ee({
|
|
88
|
+
link: _([
|
|
89
|
+
v(({ error: e, operation: n }) => {
|
|
82
90
|
if (t.is(e)) {
|
|
83
|
-
if (
|
|
91
|
+
if (oe(e.errors)) {
|
|
84
92
|
console.warn("[VibeControls] Auth error detected — notifying host shell"), X.current?.();
|
|
85
93
|
return;
|
|
86
94
|
}
|
|
@@ -95,10 +103,10 @@ var k = ({ workspaceId: e, children: t }) => (s({ workspaceId: e }), /* @__PURE_
|
|
|
95
103
|
return;
|
|
96
104
|
}
|
|
97
105
|
}
|
|
98
|
-
e instanceof Error &&
|
|
106
|
+
e instanceof Error && ie(e) && (console.warn("[VibeControls] Auth network error — notifying host shell"), X.current?.());
|
|
99
107
|
}),
|
|
100
|
-
|
|
101
|
-
let n = W.current ??
|
|
108
|
+
y((e, { headers: t }) => {
|
|
109
|
+
let n = W.current ?? ne(), r = K.current || T(), i = Y.current || ae(), a = G.current ?? C(), o = w(a, {
|
|
102
110
|
workspaceId: r,
|
|
103
111
|
organizationId: q.current,
|
|
104
112
|
tenantId: J.current,
|
|
@@ -131,7 +139,7 @@ var k = ({ workspaceId: e, children: t }) => (s({ workspaceId: e }), /* @__PURE_
|
|
|
131
139
|
basePath: c,
|
|
132
140
|
navigate: l,
|
|
133
141
|
currentUser: u,
|
|
134
|
-
workspaceId:
|
|
142
|
+
workspaceId: g,
|
|
135
143
|
organizationId: E,
|
|
136
144
|
projectId: j,
|
|
137
145
|
tenantId: M,
|
|
@@ -146,9 +154,9 @@ var k = ({ workspaceId: e, children: t }) => (s({ workspaceId: e }), /* @__PURE_
|
|
|
146
154
|
registerFooterItems: V,
|
|
147
155
|
onAuthError: H
|
|
148
156
|
}, Q = /* @__PURE__ */ D(o, { children: typeof window < "u" && /^\/vibecontrols\/(share|deck)\//.test(window.location.pathname) ? s : /* @__PURE__ */ D(k, {
|
|
149
|
-
workspaceId:
|
|
150
|
-
children: /* @__PURE__ */ D(
|
|
151
|
-
scopeId:
|
|
157
|
+
workspaceId: g,
|
|
158
|
+
children: /* @__PURE__ */ D(re, {
|
|
159
|
+
scopeId: g ?? null,
|
|
152
160
|
gateway: "workspace",
|
|
153
161
|
children: /* @__PURE__ */ D(r, {
|
|
154
162
|
toastPosition: "bottom-right",
|
|
@@ -157,20 +165,20 @@ var k = ({ workspaceId: e, children: t }) => (s({ workspaceId: e }), /* @__PURE_
|
|
|
157
165
|
children: s
|
|
158
166
|
})
|
|
159
167
|
})
|
|
160
|
-
}) }), $ = /* @__PURE__ */ D(
|
|
168
|
+
}) }), $ = /* @__PURE__ */ D(S, {
|
|
161
169
|
client: U,
|
|
162
170
|
children: c ? /* @__PURE__ */ D(A.Provider, {
|
|
163
171
|
value: Z,
|
|
164
172
|
children: Q
|
|
165
|
-
}) : /* @__PURE__ */ D(
|
|
166
|
-
client:
|
|
173
|
+
}) : /* @__PURE__ */ D(b, {
|
|
174
|
+
client: se,
|
|
167
175
|
children: /* @__PURE__ */ D(A.Provider, {
|
|
168
176
|
value: Z,
|
|
169
177
|
children: Q
|
|
170
178
|
})
|
|
171
179
|
})
|
|
172
180
|
});
|
|
173
|
-
return c ? $ : /* @__PURE__ */ D(
|
|
181
|
+
return c ? $ : /* @__PURE__ */ D(h, { children: $ });
|
|
174
182
|
};
|
|
175
183
|
//#endregion
|
|
176
184
|
export { N as VibeControlsProvider, j as useVibeControls, M as useVibeNavigate };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VibeControlsProvider.js","names":[],"sources":["../../src/providers/VibeControlsProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from 'react';\nimport { createContext, use, useMemo, useCallback, useRef, useEffect, useState } from 'react';\nimport { MemoryRouter, useNavigate as useRouterNavigate } from 'react-router-dom';\nimport { ApolloClient, from } from '@apollo/client';\nimport { buildVibeControlsCache } from '@/lib/apolloCache';\nimport { BatchHttpLink } from '@apollo/client/link/batch-http';\nimport { onError } from '@apollo/client/link/error';\nimport { CombinedGraphQLErrors } from '@apollo/client/errors';\nimport { setContext } from '@apollo/client/link/context';\nimport { ApolloProvider } from '@apollo/client/react';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport {\n getStoredAuthToken,\n getStoredWorkspaceToken,\n isWorkspaceTokenValidForContext,\n} from '@burdenoff/fe-libs/shared/graphql';\nimport { getStoredWorkspaceId } from '@burdenoff/fe-libs/shared/graphql/fetch';\nimport { PermissionProvider } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { isAuthNetworkError } from '@burdenoff/fe-libs/shared/utils';\nimport type { VibeControlsRootProps } from '../types';\nimport { AgentEventsProvider } from './AgentEventsProvider';\nimport { QuotaExhaustedProvider } from './QuotaExhaustedProvider';\nimport { useInitialDataLoader } from '../hooks/useInitialDataLoader';\nimport {\n QUOTA_EXHAUSTED_EVENT,\n inferQuotaKindFromOperation,\n type QuotaExhaustedEventDetail,\n} from '../utils/quotaUtils';\n\ntype GraphQLErrorLike = { message?: string; extensions?: Record<string, unknown> };\n\nfunction getStoredProjectId(): string | null {\n try {\n const fromSession = sessionStorage.getItem('burdenoff-active-context-project');\n if (fromSession) return fromSession;\n\n const activeProfileId = sessionStorage.getItem('bf-active-profile');\n const profileContext = activeProfileId\n ? localStorage.getItem(`bf-p-${activeProfileId}-context`)\n : null;\n if (!profileContext) return null;\n\n const parsed = JSON.parse(profileContext) as { projectId?: unknown };\n return typeof parsed.projectId === 'string' && parsed.projectId.length > 0\n ? parsed.projectId\n : null;\n } catch {\n return null;\n }\n}\n\nfunction hasSessionAuthErrors(errors: ReadonlyArray<GraphQLErrorLike> = []): boolean {\n return errors.some((error) => {\n const code = typeof error.extensions?.code === 'string' ? error.extensions.code : '';\n const errorCode =\n typeof error.extensions?.errorCode === 'string' ? error.extensions.errorCode : '';\n const message = (error.message ?? '').toLowerCase();\n\n return (\n code === 'UNAUTHENTICATED' ||\n errorCode === 'TOKEN_EXPIRED' ||\n message.includes('token_expired') ||\n message.includes('token expired') ||\n message.includes('session expired') ||\n message.includes('unauthorized') ||\n message.includes('unauthenticated') ||\n message.includes('authentication required') ||\n message.includes('workspace context missing')\n );\n });\n}\n\nfunction findQuotaExhaustedError(\n errors: ReadonlyArray<GraphQLErrorLike> = []\n): GraphQLErrorLike | undefined {\n return errors.find((error) => {\n const code = typeof error.extensions?.code === 'string' ? error.extensions.code : '';\n const errorCode =\n typeof error.extensions?.errorCode === 'string' ? error.extensions.errorCode : '';\n // Match both the constant form and the human-readable phrase, case\n // insensitively — mirrors what `isQuotaExhaustedError` accepts at the\n // per-page level so the link doesn't miss any quota error the rest\n // of the code base already recognises.\n const message = (error.message ?? '').toLowerCase();\n return (\n code === 'QUOTA_EXHAUSTED' ||\n errorCode === 'QUOTA_EXHAUSTED' ||\n message.includes('quota_exhausted') ||\n message.includes('quota exhausted')\n );\n });\n}\n\n/**\n * Runs the initial data query inside the ApolloProvider tree so the result\n * lands in the MFE's own Apollo cache (not the shell's).\n */\nconst InitialDataPrefetcher: FC<PropsWithChildren<{ workspaceId?: string | null }>> = ({\n workspaceId,\n children,\n}) => {\n useInitialDataLoader({ workspaceId });\n return <>{children}</>;\n};\n\n/**\n * Context value for VibeControls microfrontend\n */\ntype VibeControlsContextValue = VibeControlsRootProps;\n\nconst VibeControlsContext = createContext<VibeControlsContextValue | null>(null);\n\n/**\n * Hook to access VibeControls context\n */\nexport const useVibeControls = () => {\n const context = use(VibeControlsContext);\n if (!context) {\n throw new Error('useVibeControls must be used within VibeControlsProvider');\n }\n return context;\n};\n\n/**\n * Hook for navigation within the VibeControls microfrontend.\n * Uses the shell's navigate function if available, falling back to\n * React Router's useNavigate. Automatically prepends basePath.\n *\n * Usage:\n * const navigate = useVibeNavigate();\n * navigate('/agents/123'); // navigates to /vibecontrols/agents/123\n */\nexport const useVibeNavigate = () => {\n const { navigate: shellNavigate, basePath } = useVibeControls();\n const routerNavigate = useRouterNavigate();\n\n return useCallback(\n (path: string) => {\n const fullPath = basePath && basePath !== '/' ? `${basePath}${path}` : path;\n if (shellNavigate) {\n shellNavigate(fullPath);\n } else {\n routerNavigate(fullPath);\n }\n },\n [shellNavigate, basePath, routerNavigate]\n );\n};\n\n/**\n * Provider component for VibeControls microfrontend\n * Sets up routing, Apollo Client with auth error detection, and context.\n *\n * The Apollo Client is configured with:\n * - An onError link that detects UNAUTHENTICATED / FORBIDDEN responses and\n * invokes the `onAuthError` prop so the host shell can trigger logout.\n * - A dynamic auth link that reads the token per-request (not baked in at\n * client creation time), so token refreshes take effect immediately.\n */\nexport const VibeControlsProvider: FC<PropsWithChildren<VibeControlsRootProps>> = ({\n children,\n basePath,\n navigate,\n currentUser,\n workspaceId,\n organizationId,\n projectId,\n tenantId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n onAgentSelect,\n onSessionStart,\n onVibeActivate,\n defaultView,\n registerNavItems,\n registerFooterItems,\n onAuthError,\n}) => {\n // Per-instance QueryClient so multiple MFE mounts don't share cache\n const [queryClient] = useState(\n () =>\n new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 5 * 60 * 1000, // 5 minutes\n retry: 2,\n },\n },\n })\n );\n\n // Store tokens and callbacks in refs so the Apollo Client (and its cache)\n // survives token refreshes. The setContext auth link reads from refs\n // per-request, so refreshed tokens take effect immediately without\n // destroying the InMemoryCache.\n const authTokenRef = useRef(authToken);\n const workspaceTokenRef = useRef(workspaceToken);\n const workspaceIdRef = useRef(workspaceId);\n const organizationIdRef = useRef(organizationId);\n const tenantIdRef = useRef(tenantId);\n const projectIdRef = useRef(projectId);\n const onAuthErrorRef = useRef(onAuthError);\n useEffect(() => {\n authTokenRef.current = authToken;\n }, [authToken]);\n useEffect(() => {\n workspaceTokenRef.current = workspaceToken;\n }, [workspaceToken]);\n useEffect(() => {\n workspaceIdRef.current = workspaceId;\n }, [workspaceId]);\n useEffect(() => {\n organizationIdRef.current = organizationId;\n }, [organizationId]);\n useEffect(() => {\n tenantIdRef.current = tenantId;\n }, [tenantId]);\n useEffect(() => {\n projectIdRef.current = projectId;\n }, [projectId]);\n useEffect(() => {\n onAuthErrorRef.current = onAuthError;\n }, [onAuthError]);\n\n // Build Apollo Client with error link + dynamic auth link.\n // Only recreated when apiGatewayUrl changes (different endpoint = different client).\n //\n // SCOPE NOTE (codex #128 review):\n // This client — and the `errorLink` below — is ONLY used when the microfe\n // mounts in standalone mode (no `basePath`, e.g. `bun run dev` against\n // this package directly). When integrated into an app shell (the\n // production path) `basePath` is set, the local ApolloProvider is\n // skipped further down, and mutations go through the shell's\n // `MultiGatewayProvider` Apollo client. In that integrated mode the\n // global error-dispatch path here does NOT fire — quota-handling\n // relies on the per-page `isQuotaExhaustedError(result.error)`\n // checks (e.g. TargetsPage.handleCreate) which still render the\n // local QuotaExhaustedDialog. Tracked as a follow-up: lift this\n // link into fe-libs `MultiGatewayProvider` so the global event\n // dispatches in shell mode too.\n const apolloClient = useMemo(() => {\n // Error link (Apollo v4 pattern): detect authentication failures + quota.\n //\n // Quota: we use `errorPolicy: 'all'` for mutations, so GraphQL errors land\n // on `result.error` instead of rejecting the mutation promise. Most\n // callsites only have a `catch` block, so quota errors silently disappear\n // (BOFF-2625). Dispatching a CustomEvent here lets a single provider\n // (QuotaExhaustedProvider) render the dialog for every mutation without\n // touching individual handlers. Per-page handlers that already react to\n // `isQuotaExhaustedError(result.error)` still work — both paths just open\n // the same modal state.\n const errorLink = onError(({ error, operation }) => {\n if (CombinedGraphQLErrors.is(error)) {\n // Auth first: a response can carry BOTH an UNAUTHENTICATED and a\n // QUOTA_EXHAUSTED error (token expired mid-request that also\n // triggered a quota check). If we returned after dispatching the\n // quota event, the host shell would never get the auth signal and\n // the session would stay broken behind the modal. The quota\n // dialog is moot for an expired session anyway.\n if (hasSessionAuthErrors(error.errors)) {\n console.warn('[VibeControls] Auth error detected — notifying host shell');\n onAuthErrorRef.current?.();\n return;\n }\n\n const quotaError = findQuotaExhaustedError(error.errors);\n if (quotaError) {\n const detail: QuotaExhaustedEventDetail = {\n kind: inferQuotaKindFromOperation(operation?.operationName),\n message: quotaError.message,\n operationName: operation?.operationName ?? null,\n };\n window.dispatchEvent(\n new CustomEvent<QuotaExhaustedEventDetail>(QUOTA_EXHAUSTED_EVENT, { detail })\n );\n return;\n }\n }\n\n if (error instanceof Error && isAuthNetworkError(error)) {\n console.warn('[VibeControls] Auth network error — notifying host shell');\n onAuthErrorRef.current?.();\n }\n });\n\n // Auth link: dynamically attach dual-token auth and workspace context per-request.\n // Reads from refs so token refreshes don't recreate the client.\n const authLink = setContext((_, { headers }) => {\n const resolvedAuthToken = authTokenRef.current ?? getStoredAuthToken();\n // Fall back to storage when workspaceId prop is empty (e.g. during initial render\n // before ActiveContextProvider has propagated the value through workspaceBaseProps)\n const resolvedWorkspaceId = workspaceIdRef.current || getStoredWorkspaceId();\n const resolvedProjectId = projectIdRef.current || getStoredProjectId();\n const rawWorkspaceToken = workspaceTokenRef.current ?? getStoredWorkspaceToken();\n const resolvedWorkspaceToken = isWorkspaceTokenValidForContext(rawWorkspaceToken, {\n workspaceId: resolvedWorkspaceId,\n organizationId: organizationIdRef.current,\n tenantId: tenantIdRef.current,\n projectId: resolvedProjectId,\n })\n ? rawWorkspaceToken\n : null;\n\n return {\n headers: {\n ...headers,\n ...(resolvedAuthToken ? { Authorization: `Bearer ${resolvedAuthToken}` } : {}),\n ...(resolvedWorkspaceToken\n ? { 'X-Workspace-Authorization': `Bearer ${resolvedWorkspaceToken}` }\n : {}),\n ...(resolvedWorkspaceId ? { 'x-workspace-id': resolvedWorkspaceId } : {}),\n ...(resolvedProjectId ? { 'x-project-id': resolvedProjectId } : {}),\n },\n };\n });\n\n const httpLink = new BatchHttpLink({\n uri: apiGatewayUrl ? `${apiGatewayUrl}/workspaces/graphql` : '/workspaces/graphql',\n batchMax: 10,\n batchInterval: 10, // ms — tight window so latency isn't hurt\n });\n\n return new ApolloClient({\n link: from([errorLink, authLink, httpLink]),\n cache: buildVibeControlsCache(),\n defaultOptions: {\n watchQuery: { fetchPolicy: 'cache-first', errorPolicy: 'all' },\n query: { errorPolicy: 'all' },\n mutate: { errorPolicy: 'all' },\n },\n });\n }, [apiGatewayUrl]);\n\n const contextValue: VibeControlsContextValue = {\n basePath,\n navigate,\n currentUser,\n workspaceId,\n organizationId,\n projectId,\n tenantId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n onAgentSelect,\n onSessionStart,\n onVibeActivate,\n defaultView,\n registerNavItems,\n registerFooterItems,\n onAuthError,\n };\n\n const isPublicLinkRoute =\n typeof window !== 'undefined' &&\n /^\\/vibecontrols\\/(share|deck)\\//.test(window.location.pathname);\n\n // Core providers that wrap the children\n // PermissionProvider requires AuthProvider which is provided by the shell.\n // It fetches permissions from wspace-rbac-svc via the workspace gateway.\n // AgentEventsProvider manages WebSocket connections to agents for real-time notifications.\n //\n // QuotaExhaustedProvider wraps both public and authenticated trees because\n // public deck routes (`/vibecontrols/deck/:token`) still run mutations like\n // `ExecuteSharedVibeDeckButton` that can hit command-execution quota\n // (#128 review codex P2). The provider is cheap and route-agnostic — it\n // only listens for a window event.\n const authedTree = (\n <InitialDataPrefetcher workspaceId={workspaceId}>\n <PermissionProvider scopeId={workspaceId ?? null} gateway=\"workspace\">\n <AgentEventsProvider\n toastPosition=\"bottom-right\"\n autoDismissDelay={5000}\n maxVisibleToasts={3}\n >\n {children}\n </AgentEventsProvider>\n </PermissionProvider>\n </InitialDataPrefetcher>\n );\n\n const routedChildren = (\n <QuotaExhaustedProvider>{isPublicLinkRoute ? children : authedTree}</QuotaExhaustedProvider>\n );\n\n const coreProviders = (\n <QueryClientProvider client={queryClient}>\n {basePath ? (\n <VibeControlsContext.Provider value={contextValue}>\n {routedChildren}\n </VibeControlsContext.Provider>\n ) : (\n <ApolloProvider client={apolloClient}>\n <VibeControlsContext.Provider value={contextValue}>\n {routedChildren}\n </VibeControlsContext.Provider>\n </ApolloProvider>\n )}\n </QueryClientProvider>\n );\n\n // When integrated into a shell (basePath provided), the shell already has a router\n // so we don't create another one. When standalone (no basePath), use MemoryRouter.\n if (basePath) {\n // Integrated mode: shell already has BrowserRouter, don't create nested router\n return coreProviders;\n }\n\n // Standalone mode: create MemoryRouter for independent operation\n return <MemoryRouter>{coreProviders}</MemoryRouter>;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VibeControlsProvider.js","names":[],"sources":["../../src/providers/VibeControlsProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from 'react';\nimport { createContext, use, useMemo, useCallback, useRef, useEffect, useState } from 'react';\nimport { MemoryRouter, useNavigate as useRouterNavigate } from 'react-router-dom';\nimport { ApolloClient, from } from '@apollo/client';\nimport { buildVibeControlsCache } from '@/lib/apolloCache';\nimport { BatchHttpLink } from '@apollo/client/link/batch-http';\nimport { onError } from '@apollo/client/link/error';\nimport { CombinedGraphQLErrors } from '@apollo/client/errors';\nimport { setContext } from '@apollo/client/link/context';\nimport { ApolloProvider } from '@apollo/client/react';\nimport { MutationCache, QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport {\n getStoredAuthToken,\n getStoredWorkspaceToken,\n isWorkspaceTokenValidForContext,\n} from '@burdenoff/fe-libs/shared/graphql';\nimport { getStoredWorkspaceId } from '@burdenoff/fe-libs/shared/graphql/fetch';\nimport { PermissionProvider } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { isAuthNetworkError } from '@burdenoff/fe-libs/shared/utils';\nimport type { VibeControlsRootProps } from '../types';\nimport { AgentEventsProvider } from './AgentEventsProvider';\nimport { QuotaExhaustedProvider } from './QuotaExhaustedProvider';\nimport { useInitialDataLoader } from '../hooks/useInitialDataLoader';\nimport {\n QUOTA_EXHAUSTED_EVENT,\n inferQuotaKindFromOperation,\n type QuotaExhaustedEventDetail,\n} from '../utils/quotaUtils';\n\ntype GraphQLErrorLike = { message?: string; extensions?: Record<string, unknown> };\n\nfunction getStoredProjectId(): string | null {\n try {\n const fromSession = sessionStorage.getItem('burdenoff-active-context-project');\n if (fromSession) return fromSession;\n\n const activeProfileId = sessionStorage.getItem('bf-active-profile');\n const profileContext = activeProfileId\n ? localStorage.getItem(`bf-p-${activeProfileId}-context`)\n : null;\n if (!profileContext) return null;\n\n const parsed = JSON.parse(profileContext) as { projectId?: unknown };\n return typeof parsed.projectId === 'string' && parsed.projectId.length > 0\n ? parsed.projectId\n : null;\n } catch {\n return null;\n }\n}\n\nfunction hasSessionAuthErrors(errors: ReadonlyArray<GraphQLErrorLike> = []): boolean {\n return errors.some((error) => {\n const code = typeof error.extensions?.code === 'string' ? error.extensions.code : '';\n const errorCode =\n typeof error.extensions?.errorCode === 'string' ? error.extensions.errorCode : '';\n const message = (error.message ?? '').toLowerCase();\n\n return (\n code === 'UNAUTHENTICATED' ||\n errorCode === 'TOKEN_EXPIRED' ||\n message.includes('token_expired') ||\n message.includes('token expired') ||\n message.includes('session expired') ||\n message.includes('unauthorized') ||\n message.includes('unauthenticated') ||\n message.includes('authentication required') ||\n message.includes('workspace context missing')\n );\n });\n}\n\nfunction findQuotaExhaustedError(\n errors: ReadonlyArray<GraphQLErrorLike> = []\n): GraphQLErrorLike | undefined {\n return errors.find((error) => {\n const code = typeof error.extensions?.code === 'string' ? error.extensions.code : '';\n const errorCode =\n typeof error.extensions?.errorCode === 'string' ? error.extensions.errorCode : '';\n // Match both the constant form and the human-readable phrase, case\n // insensitively — mirrors what `isQuotaExhaustedError` accepts at the\n // per-page level so the link doesn't miss any quota error the rest\n // of the code base already recognises.\n const message = (error.message ?? '').toLowerCase();\n return (\n code === 'QUOTA_EXHAUSTED' ||\n errorCode === 'QUOTA_EXHAUSTED' ||\n message.includes('quota_exhausted') ||\n message.includes('quota exhausted')\n );\n });\n}\n\n/**\n * Runs the initial data query inside the ApolloProvider tree so the result\n * lands in the MFE's own Apollo cache (not the shell's).\n */\nconst InitialDataPrefetcher: FC<PropsWithChildren<{ workspaceId?: string | null }>> = ({\n workspaceId,\n children,\n}) => {\n useInitialDataLoader({ workspaceId });\n return <>{children}</>;\n};\n\n/**\n * Context value for VibeControls microfrontend\n */\ntype VibeControlsContextValue = VibeControlsRootProps;\n\nconst VibeControlsContext = createContext<VibeControlsContextValue | null>(null);\n\n/**\n * Hook to access VibeControls context\n */\nexport const useVibeControls = () => {\n const context = use(VibeControlsContext);\n if (!context) {\n throw new Error('useVibeControls must be used within VibeControlsProvider');\n }\n return context;\n};\n\n/**\n * Hook for navigation within the VibeControls microfrontend.\n * Uses the shell's navigate function if available, falling back to\n * React Router's useNavigate. Automatically prepends basePath.\n *\n * Usage:\n * const navigate = useVibeNavigate();\n * navigate('/agents/123'); // navigates to /vibecontrols/agents/123\n */\nexport const useVibeNavigate = () => {\n const { navigate: shellNavigate, basePath } = useVibeControls();\n const routerNavigate = useRouterNavigate();\n\n return useCallback(\n (path: string) => {\n const fullPath = basePath && basePath !== '/' ? `${basePath}${path}` : path;\n if (shellNavigate) {\n shellNavigate(fullPath);\n } else {\n routerNavigate(fullPath);\n }\n },\n [shellNavigate, basePath, routerNavigate]\n );\n};\n\n/**\n * Provider component for VibeControls microfrontend\n * Sets up routing, Apollo Client with auth error detection, and context.\n *\n * The Apollo Client is configured with:\n * - An onError link that detects UNAUTHENTICATED / FORBIDDEN responses and\n * invokes the `onAuthError` prop so the host shell can trigger logout.\n * - A dynamic auth link that reads the token per-request (not baked in at\n * client creation time), so token refreshes take effect immediately.\n */\nexport const VibeControlsProvider: FC<PropsWithChildren<VibeControlsRootProps>> = ({\n children,\n basePath,\n navigate,\n currentUser,\n workspaceId,\n organizationId,\n projectId,\n tenantId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n onAgentSelect,\n onSessionStart,\n onVibeActivate,\n defaultView,\n registerNavItems,\n registerFooterItems,\n onAuthError,\n}) => {\n // Per-instance QueryClient so multiple MFE mounts don't share cache\n const [queryClient] = useState(\n () => {\n const client = new QueryClient({\n mutationCache: new MutationCache({\n onSuccess: () => {\n void client.invalidateQueries();\n },\n }),\n defaultOptions: {\n queries: {\n staleTime: 5 * 60 * 1000, // 5 minutes\n retry: 2,\n },\n },\n });\n return client;\n }\n );\n\n // Store tokens and callbacks in refs so the Apollo Client (and its cache)\n // survives token refreshes. The setContext auth link reads from refs\n // per-request, so refreshed tokens take effect immediately without\n // destroying the InMemoryCache.\n const authTokenRef = useRef(authToken);\n const workspaceTokenRef = useRef(workspaceToken);\n const workspaceIdRef = useRef(workspaceId);\n const organizationIdRef = useRef(organizationId);\n const tenantIdRef = useRef(tenantId);\n const projectIdRef = useRef(projectId);\n const onAuthErrorRef = useRef(onAuthError);\n useEffect(() => {\n authTokenRef.current = authToken;\n }, [authToken]);\n useEffect(() => {\n workspaceTokenRef.current = workspaceToken;\n }, [workspaceToken]);\n useEffect(() => {\n workspaceIdRef.current = workspaceId;\n }, [workspaceId]);\n useEffect(() => {\n organizationIdRef.current = organizationId;\n }, [organizationId]);\n useEffect(() => {\n tenantIdRef.current = tenantId;\n }, [tenantId]);\n useEffect(() => {\n projectIdRef.current = projectId;\n }, [projectId]);\n useEffect(() => {\n onAuthErrorRef.current = onAuthError;\n }, [onAuthError]);\n\n // Build Apollo Client with error link + dynamic auth link.\n // Only recreated when apiGatewayUrl changes (different endpoint = different client).\n //\n // SCOPE NOTE (codex #128 review):\n // This client — and the `errorLink` below — is ONLY used when the microfe\n // mounts in standalone mode (no `basePath`, e.g. `bun run dev` against\n // this package directly). When integrated into an app shell (the\n // production path) `basePath` is set, the local ApolloProvider is\n // skipped further down, and mutations go through the shell's\n // `MultiGatewayProvider` Apollo client. In that integrated mode the\n // global error-dispatch path here does NOT fire — quota-handling\n // relies on the per-page `isQuotaExhaustedError(result.error)`\n // checks (e.g. TargetsPage.handleCreate) which still render the\n // local QuotaExhaustedDialog. Tracked as a follow-up: lift this\n // link into fe-libs `MultiGatewayProvider` so the global event\n // dispatches in shell mode too.\n const apolloClient = useMemo(() => {\n // Error link (Apollo v4 pattern): detect authentication failures + quota.\n //\n // Quota: we use `errorPolicy: 'all'` for mutations, so GraphQL errors land\n // on `result.error` instead of rejecting the mutation promise. Most\n // callsites only have a `catch` block, so quota errors silently disappear\n // (BOFF-2625). Dispatching a CustomEvent here lets a single provider\n // (QuotaExhaustedProvider) render the dialog for every mutation without\n // touching individual handlers. Per-page handlers that already react to\n // `isQuotaExhaustedError(result.error)` still work — both paths just open\n // the same modal state.\n const errorLink = onError(({ error, operation }) => {\n if (CombinedGraphQLErrors.is(error)) {\n // Auth first: a response can carry BOTH an UNAUTHENTICATED and a\n // QUOTA_EXHAUSTED error (token expired mid-request that also\n // triggered a quota check). If we returned after dispatching the\n // quota event, the host shell would never get the auth signal and\n // the session would stay broken behind the modal. The quota\n // dialog is moot for an expired session anyway.\n if (hasSessionAuthErrors(error.errors)) {\n console.warn('[VibeControls] Auth error detected — notifying host shell');\n onAuthErrorRef.current?.();\n return;\n }\n\n const quotaError = findQuotaExhaustedError(error.errors);\n if (quotaError) {\n const detail: QuotaExhaustedEventDetail = {\n kind: inferQuotaKindFromOperation(operation?.operationName),\n message: quotaError.message,\n operationName: operation?.operationName ?? null,\n };\n window.dispatchEvent(\n new CustomEvent<QuotaExhaustedEventDetail>(QUOTA_EXHAUSTED_EVENT, { detail })\n );\n return;\n }\n }\n\n if (error instanceof Error && isAuthNetworkError(error)) {\n console.warn('[VibeControls] Auth network error — notifying host shell');\n onAuthErrorRef.current?.();\n }\n });\n\n // Auth link: dynamically attach dual-token auth and workspace context per-request.\n // Reads from refs so token refreshes don't recreate the client.\n const authLink = setContext((_, { headers }) => {\n const resolvedAuthToken = authTokenRef.current ?? getStoredAuthToken();\n // Fall back to storage when workspaceId prop is empty (e.g. during initial render\n // before ActiveContextProvider has propagated the value through workspaceBaseProps)\n const resolvedWorkspaceId = workspaceIdRef.current || getStoredWorkspaceId();\n const resolvedProjectId = projectIdRef.current || getStoredProjectId();\n const rawWorkspaceToken = workspaceTokenRef.current ?? getStoredWorkspaceToken();\n const resolvedWorkspaceToken = isWorkspaceTokenValidForContext(rawWorkspaceToken, {\n workspaceId: resolvedWorkspaceId,\n organizationId: organizationIdRef.current,\n tenantId: tenantIdRef.current,\n projectId: resolvedProjectId,\n })\n ? rawWorkspaceToken\n : null;\n\n return {\n headers: {\n ...headers,\n ...(resolvedAuthToken ? { Authorization: `Bearer ${resolvedAuthToken}` } : {}),\n ...(resolvedWorkspaceToken\n ? { 'X-Workspace-Authorization': `Bearer ${resolvedWorkspaceToken}` }\n : {}),\n ...(resolvedWorkspaceId ? { 'x-workspace-id': resolvedWorkspaceId } : {}),\n ...(resolvedProjectId ? { 'x-project-id': resolvedProjectId } : {}),\n },\n };\n });\n\n const httpLink = new BatchHttpLink({\n uri: apiGatewayUrl ? `${apiGatewayUrl}/workspaces/graphql` : '/workspaces/graphql',\n batchMax: 10,\n batchInterval: 10, // ms — tight window so latency isn't hurt\n });\n\n return new ApolloClient({\n link: from([errorLink, authLink, httpLink]),\n cache: buildVibeControlsCache(),\n defaultOptions: {\n watchQuery: { fetchPolicy: 'cache-first', errorPolicy: 'all' },\n query: { errorPolicy: 'all' },\n mutate: { errorPolicy: 'all' },\n },\n });\n }, [apiGatewayUrl]);\n\n const contextValue: VibeControlsContextValue = {\n basePath,\n navigate,\n currentUser,\n workspaceId,\n organizationId,\n projectId,\n tenantId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n onAgentSelect,\n onSessionStart,\n onVibeActivate,\n defaultView,\n registerNavItems,\n registerFooterItems,\n onAuthError,\n };\n\n const isPublicLinkRoute =\n typeof window !== 'undefined' &&\n /^\\/vibecontrols\\/(share|deck)\\//.test(window.location.pathname);\n\n // Core providers that wrap the children\n // PermissionProvider requires AuthProvider which is provided by the shell.\n // It fetches permissions from wspace-rbac-svc via the workspace gateway.\n // AgentEventsProvider manages WebSocket connections to agents for real-time notifications.\n //\n // QuotaExhaustedProvider wraps both public and authenticated trees because\n // public deck routes (`/vibecontrols/deck/:token`) still run mutations like\n // `ExecuteSharedVibeDeckButton` that can hit command-execution quota\n // (#128 review codex P2). The provider is cheap and route-agnostic — it\n // only listens for a window event.\n const authedTree = (\n <InitialDataPrefetcher workspaceId={workspaceId}>\n <PermissionProvider scopeId={workspaceId ?? null} gateway=\"workspace\">\n <AgentEventsProvider\n toastPosition=\"bottom-right\"\n autoDismissDelay={5000}\n maxVisibleToasts={3}\n >\n {children}\n </AgentEventsProvider>\n </PermissionProvider>\n </InitialDataPrefetcher>\n );\n\n const routedChildren = (\n <QuotaExhaustedProvider>{isPublicLinkRoute ? children : authedTree}</QuotaExhaustedProvider>\n );\n\n const coreProviders = (\n <QueryClientProvider client={queryClient}>\n {basePath ? (\n <VibeControlsContext.Provider value={contextValue}>\n {routedChildren}\n </VibeControlsContext.Provider>\n ) : (\n <ApolloProvider client={apolloClient}>\n <VibeControlsContext.Provider value={contextValue}>\n {routedChildren}\n </VibeControlsContext.Provider>\n </ApolloProvider>\n )}\n </QueryClientProvider>\n );\n\n // When integrated into a shell (basePath provided), the shell already has a router\n // so we don't create another one. When standalone (no basePath), use MemoryRouter.\n if (basePath) {\n // Integrated mode: shell already has BrowserRouter, don't create nested router\n return coreProviders;\n }\n\n // Standalone mode: create MemoryRouter for independent operation\n return <MemoryRouter>{coreProviders}</MemoryRouter>;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA+BA,SAAS,KAAoC;AAC3C,KAAI;EACF,IAAM,IAAc,eAAe,QAAQ,mCAAmC;AAC9E,MAAI,EAAa,QAAO;EAExB,IAAM,IAAkB,eAAe,QAAQ,oBAAoB,EAC7D,IAAiB,IACnB,aAAa,QAAQ,QAAQ,EAAgB,UAAU,GACvD;AACJ,MAAI,CAAC,EAAgB,QAAO;EAE5B,IAAM,IAAS,KAAK,MAAM,EAAe;AACzC,SAAO,OAAO,EAAO,aAAc,YAAY,EAAO,UAAU,SAAS,IACrE,EAAO,YACP;SACE;AACN,SAAO;;;AAIX,SAAS,GAAqB,IAA0C,EAAE,EAAW;AACnF,QAAO,EAAO,MAAM,MAAU;EAC5B,IAAM,IAAO,OAAO,EAAM,YAAY,QAAS,WAAW,EAAM,WAAW,OAAO,IAC5E,IACJ,OAAO,EAAM,YAAY,aAAc,WAAW,EAAM,WAAW,YAAY,IAC3E,KAAW,EAAM,WAAW,IAAI,aAAa;AAEnD,SACE,MAAS,qBACT,MAAc,mBACd,EAAQ,SAAS,gBAAgB,IACjC,EAAQ,SAAS,gBAAgB,IACjC,EAAQ,SAAS,kBAAkB,IACnC,EAAQ,SAAS,eAAe,IAChC,EAAQ,SAAS,kBAAkB,IACnC,EAAQ,SAAS,0BAA0B,IAC3C,EAAQ,SAAS,4BAA4B;GAE/C;;AAGJ,SAAS,EACP,IAA0C,EAAE,EACd;AAC9B,QAAO,EAAO,MAAM,MAAU;EAC5B,IAAM,IAAO,OAAO,EAAM,YAAY,QAAS,WAAW,EAAM,WAAW,OAAO,IAC5E,IACJ,OAAO,EAAM,YAAY,aAAc,WAAW,EAAM,WAAW,YAAY,IAK3E,KAAW,EAAM,WAAW,IAAI,aAAa;AACnD,SACE,MAAS,qBACT,MAAc,qBACd,EAAQ,SAAS,kBAAkB,IACnC,EAAQ,SAAS,kBAAkB;GAErC;;AAOJ,IAAM,KAAiF,EACrF,gBACA,mBAEA,EAAqB,EAAE,gBAAa,CAAC,EAC9B,kBAAA,GAAA,EAAG,aAAY,CAAA,GAQlB,IAAsB,EAA+C,KAAK,EAKnE,UAAwB;CACnC,IAAM,IAAU,EAAI,EAAoB;AACxC,KAAI,CAAC,EACH,OAAU,MAAM,2DAA2D;AAE7E,QAAO;GAYI,UAAwB;CACnC,IAAM,EAAE,UAAU,GAAe,gBAAa,GAAiB,EACzD,IAAiB,GAAmB;AAE1C,QAAO,GACJ,MAAiB;EAChB,IAAM,IAAW,KAAY,MAAa,MAAM,GAAG,IAAW,MAAS;AACvE,EAAI,IACF,EAAc,EAAS,GAEvB,EAAe,EAAS;IAG5B;EAAC;EAAe;EAAU;EAAe,CAC1C;GAaU,KAAsE,EACjF,aACA,aACA,aACA,gBACA,gBACA,mBACA,cACA,aACA,kBACA,cACA,mBACA,kBACA,mBACA,mBACA,gBACA,qBACA,wBACA,qBACI;CAEJ,IAAM,CAAC,KAAe,QACd;EACJ,IAAM,IAAS,IAAI,GAAY;GAC7B,eAAe,IAAI,EAAc,EAC/B,iBAAiB;AACV,MAAO,mBAAmB;MAElC,CAAC;GACF,gBAAgB,EACd,SAAS;IACP,WAAW,MAAS;IACpB,OAAO;IACR,EACF;GACF,CAAC;AACF,SAAO;GAEV,EAMK,IAAe,EAAO,EAAU,EAChC,IAAoB,EAAO,EAAe,EAC1C,IAAiB,EAAO,EAAY,EACpC,IAAoB,EAAO,EAAe,EAC1C,IAAc,EAAO,EAAS,EAC9B,IAAe,EAAO,EAAU,EAChC,IAAiB,EAAO,EAAY;AAmB1C,CAlBA,QAAgB;AACd,IAAa,UAAU;IACtB,CAAC,EAAU,CAAC,EACf,QAAgB;AACd,IAAkB,UAAU;IAC3B,CAAC,EAAe,CAAC,EACpB,QAAgB;AACd,IAAe,UAAU;IACxB,CAAC,EAAY,CAAC,EACjB,QAAgB;AACd,IAAkB,UAAU;IAC3B,CAAC,EAAe,CAAC,EACpB,QAAgB;AACd,IAAY,UAAU;IACrB,CAAC,EAAS,CAAC,EACd,QAAgB;AACd,IAAa,UAAU;IACtB,CAAC,EAAU,CAAC,EACf,QAAgB;AACd,IAAe,UAAU;IACxB,CAAC,EAAY,CAAC;CAkBjB,IAAM,KAAe,QAkFZ,IAAI,GAAa;EACtB,MAAM,EAAK;GAxEK,GAAS,EAAE,UAAO,mBAAgB;AAClD,QAAI,EAAsB,GAAG,EAAM,EAAE;AAOnC,SAAI,GAAqB,EAAM,OAAO,EAAE;AAEtC,MADA,QAAQ,KAAK,4DAA4D,EACzE,EAAe,WAAW;AAC1B;;KAGF,IAAM,IAAa,EAAwB,EAAM,OAAO;AACxD,SAAI,GAAY;MACd,IAAM,IAAoC;OACxC,MAAM,EAA4B,GAAW,cAAc;OAC3D,SAAS,EAAW;OACpB,eAAe,GAAW,iBAAiB;OAC5C;AACD,aAAO,cACL,IAAI,YAAuC,GAAuB,EAAE,WAAQ,CAAC,CAC9E;AACD;;;AAIJ,IAAI,aAAiB,SAAS,GAAmB,EAAM,KACrD,QAAQ,KAAK,2DAA2D,EACxE,EAAe,WAAW;KAE5B;GAIe,GAAY,GAAG,EAAE,iBAAc;IAC9C,IAAM,IAAoB,EAAa,WAAW,IAAoB,EAGhE,IAAsB,EAAe,WAAW,GAAsB,EACtE,IAAoB,EAAa,WAAW,IAAoB,EAChE,IAAoB,EAAkB,WAAW,GAAyB,EAC1E,IAAyB,EAAgC,GAAmB;KAChF,aAAa;KACb,gBAAgB,EAAkB;KAClC,UAAU,EAAY;KACtB,WAAW;KACZ,CAAC,GACE,IACA;AAEJ,WAAO,EACL,SAAS;KACP,GAAG;KACH,GAAI,IAAoB,EAAE,eAAe,UAAU,KAAqB,GAAG,EAAE;KAC7E,GAAI,IACA,EAAE,6BAA6B,UAAU,KAA0B,GACnE,EAAE;KACN,GAAI,IAAsB,EAAE,kBAAkB,GAAqB,GAAG,EAAE;KACxE,GAAI,IAAoB,EAAE,gBAAgB,GAAmB,GAAG,EAAE;KACnE,EACF;KACD;GAEe,IAAI,EAAc;IACjC,KAAK,IAAgB,GAAG,EAAc,uBAAuB;IAC7D,UAAU;IACV,eAAe;IAChB,CAAC;GAG0C,CAAC;EAC3C,OAAO,GAAwB;EAC/B,gBAAgB;GACd,YAAY;IAAE,aAAa;IAAe,aAAa;IAAO;GAC9D,OAAO,EAAE,aAAa,OAAO;GAC7B,QAAQ,EAAE,aAAa,OAAO;GAC/B;EACF,CAAC,EACD,CAAC,EAAc,CAAC,EAEb,IAAyC;EAC7C;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,EA8BK,IACJ,kBAAC,GAAD,EAAA,UA5BA,OAAO,SAAW,OAClB,kCAAkC,KAAK,OAAO,SAAS,SAAS,GA2BnB,IAd7C,kBAAC,GAAD;EAAoC;YAClC,kBAAC,IAAD;GAAoB,SAAS,KAAe;GAAM,SAAQ;aACxD,kBAAC,GAAD;IACE,eAAc;IACd,kBAAkB;IAClB,kBAAkB;IAEjB;IACmB,CAAA;GACH,CAAA;EACC,CAAA,EAIoE,CAAA,EAGxF,IACJ,kBAAC,GAAD;EAAqB,QAAQ;YAC1B,IACC,kBAAC,EAAoB,UAArB;GAA8B,OAAO;aAClC;GAC4B,CAAA,GAE/B,kBAAC,GAAD;GAAgB,QAAQ;aACtB,kBAAC,EAAoB,UAArB;IAA8B,OAAO;cAClC;IAC4B,CAAA;GAChB,CAAA;EAEC,CAAA;AAWxB,QANI,IAEK,IAIF,kBAAC,GAAD,EAAA,UAAe,GAA6B,CAAA"}
|