@burdenoff/microfe-bigconsole 2026.915.1 → 2026.917.1

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 (26) hide show
  1. package/dist/bigconsole/assistant/assistantApi.js +58 -57
  2. package/dist/bigconsole/assistant/assistantApi.js.map +1 -1
  3. package/dist/bigconsole/assistant/assistantProduct.js +20 -0
  4. package/dist/bigconsole/assistant/assistantProduct.js.map +1 -0
  5. package/dist/bigconsole/assistant/assistantPrompt.js +34 -0
  6. package/dist/bigconsole/assistant/assistantPrompt.js.map +1 -0
  7. package/dist/bigconsole/assistant/conversationHistoryApi.js +33 -32
  8. package/dist/bigconsole/assistant/conversationHistoryApi.js.map +1 -1
  9. package/dist/bigconsole/assistant/index.js +10 -4
  10. package/dist/bigconsole/assistant/modes.js +29 -0
  11. package/dist/bigconsole/assistant/modes.js.map +1 -0
  12. package/dist/bigconsole/assistant/pageContext.js +32 -19
  13. package/dist/bigconsole/assistant/pageContext.js.map +1 -1
  14. package/dist/bigconsole/assistant/previewController.js +60 -0
  15. package/dist/bigconsole/assistant/previewController.js.map +1 -0
  16. package/dist/bigconsole/assistant/previewRunAction.js +9 -0
  17. package/dist/bigconsole/assistant/previewRunAction.js.map +1 -0
  18. package/dist/bigconsole/assistant/product.js +6 -0
  19. package/dist/bigconsole/assistant/product.js.map +1 -0
  20. package/dist/bigconsole/assistant/useWorkspaceWriteAccess.js +15 -0
  21. package/dist/bigconsole/assistant/useWorkspaceWriteAccess.js.map +1 -0
  22. package/dist/bigconsole/components/preview/AiPreviewContent.js +129 -67
  23. package/dist/bigconsole/components/preview/AiPreviewContent.js.map +1 -1
  24. package/dist/bigconsole/hooks/useWidgetOperations.js +1 -1
  25. package/dist/bigconsole/hooks/useWidgetOperations.js.map +1 -1
  26. package/package.json +5 -4
@@ -1 +1 @@
1
- {"version":3,"file":"conversationHistoryApi.js","names":[],"sources":["../../../src/bigconsole/assistant/conversationHistoryApi.ts"],"sourcesContent":["/**\n * Durable assistant history, stored in wspace-conversations.\n *\n * The agent's own session lives in a sandbox with a 10-minute TTL and no\n * persistent volume, so it cannot be the store of record for a transcript the\n * user expects to keep. Every completed turn is written here instead, tagged\n * with the product that owns it, so history is durable AND product-scoped —\n * a BigConsole chat can never appear in another product's panel.\n *\n * Raw operation strings rather than generated documents: these ops are only used\n * here, and `graphqlFetch` takes a query string, so there is nothing to gain\n * from putting them through codegen.\n */\n\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\nimport type { AssistantSandboxAuthContext } from './types';\n\n/** Every conversation written from here belongs to BigConsole. */\nexport const ASSISTANT_PRODUCT_ID = 'bigconsole';\n\nexport interface AssistantConversationSummary {\n id: string;\n title: string | null;\n agentSessionId: string | null;\n updatedAt: string;\n}\n\nexport interface AssistantHistoryTurnMessage {\n id: string;\n role: 'USER' | 'ASSISTANT';\n content: string;\n createdAt: string;\n}\n\nconst CONVERSATION_FIELDS = `\n id\n title\n agentSessionId\n updatedAt\n`;\n\nconst LIST_CONVERSATIONS = `\n query AssistantConversations($productId: String!, $first: Int) {\n assistantConversations(productId: $productId, first: $first) { ${CONVERSATION_FIELDS} }\n }\n`;\n\nconst CONVERSATION_MESSAGES = `\n query AssistantConversationMessages($conversationId: ID!, $first: Int) {\n assistantConversationMessages(conversationId: $conversationId, first: $first) {\n id\n role\n content\n createdAt\n }\n }\n`;\n\nconst SAVE_TURN = `\n mutation SaveAssistantTurn($input: SaveAssistantTurnInput!) {\n saveAssistantTurn(input: $input) { ${CONVERSATION_FIELDS} }\n }\n`;\n\nconst DELETE_CONVERSATION = `\n mutation DeleteAssistantConversation($conversationId: ID!) {\n deleteAssistantConversation(conversationId: $conversationId)\n }\n`;\n\nasync function historyFetch<TData>(params: {\n query: string;\n operationName: string;\n variables: Record<string, unknown>;\n workspaceId: string;\n authContext?: AssistantSandboxAuthContext;\n}): Promise<TData> {\n const result = await graphqlFetch<TData>({\n gateway: 'workspace',\n query: params.query,\n operationName: params.operationName,\n variables: params.variables,\n authToken: params.authContext?.accessToken ?? undefined,\n // Mint the workspace token rather than passing a possibly-stale string —\n // same reasoning as assistantApi: a stale token is nulled out and NOT\n // replaced unless the caller opts in here.\n workspaceToken: true,\n workspaceId: params.workspaceId,\n organizationId: params.authContext?.organizationId ?? true,\n suppressGlobalErrorEvent: true,\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors.map((error) => error.message).join(', '));\n }\n if (!result.data) {\n throw new Error(`${params.operationName} returned no data`);\n }\n\n return result.data;\n}\n\nexport async function listAssistantConversations(\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext,\n first = 25\n): Promise<AssistantConversationSummary[]> {\n const data = await historyFetch<{ assistantConversations: AssistantConversationSummary[] }>({\n query: LIST_CONVERSATIONS,\n operationName: 'AssistantConversations',\n variables: { productId: ASSISTANT_PRODUCT_ID, first },\n workspaceId,\n authContext,\n });\n return data.assistantConversations ?? [];\n}\n\nexport async function getAssistantConversationMessages(\n conversationId: string,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext,\n first = 200\n): Promise<AssistantHistoryTurnMessage[]> {\n const data = await historyFetch<{\n assistantConversationMessages: AssistantHistoryTurnMessage[];\n }>({\n query: CONVERSATION_MESSAGES,\n operationName: 'AssistantConversationMessages',\n variables: { conversationId, first },\n workspaceId,\n authContext,\n });\n return data.assistantConversationMessages ?? [];\n}\n\n/**\n * Persist one completed turn. Creates the conversation on the first turn and\n * returns it, so the caller can hold on to the id.\n */\nexport async function saveAssistantTurn(\n params: {\n conversationId: string | null;\n prompt: string;\n reply: string;\n agentSessionId: string | null;\n },\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<AssistantConversationSummary> {\n const data = await historyFetch<{ saveAssistantTurn: AssistantConversationSummary }>({\n query: SAVE_TURN,\n operationName: 'SaveAssistantTurn',\n variables: {\n input: {\n conversationId: params.conversationId,\n productId: ASSISTANT_PRODUCT_ID,\n prompt: params.prompt,\n reply: params.reply,\n agentSessionId: params.agentSessionId,\n },\n },\n workspaceId,\n authContext,\n });\n return data.saveAssistantTurn;\n}\n\nexport async function deleteAssistantConversation(\n conversationId: string,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<void> {\n await historyFetch<{ deleteAssistantConversation: boolean }>({\n query: DELETE_CONVERSATION,\n operationName: 'DeleteAssistantConversation',\n variables: { conversationId },\n workspaceId,\n authContext,\n });\n}\n"],"mappings":";;AAmBA,IAAa,IAAuB,cAgB9B,IAAsB,oDAOtB,IAAqB;;qEAE0C,EAAoB;;GAInF,IAAwB,yOAWxB,IAAY;;yCAEuB,EAAoB;;GAIvD,IAAsB;AAM5B,eAAe,EAAoB,GAMhB;CACjB,IAAM,IAAS,MAAM,EAAoB;EACvC,SAAS;EACT,OAAO,EAAO;EACd,eAAe,EAAO;EACtB,WAAW,EAAO;EAClB,WAAW,EAAO,aAAa,eAAe,KAAA;EAI9C,gBAAgB;EAChB,aAAa,EAAO;EACpB,gBAAgB,EAAO,aAAa,kBAAkB;EACtD,0BAA0B;EAC3B,CAAC;AAEF,KAAI,EAAO,QAAQ,OACjB,OAAU,MAAM,EAAO,OAAO,KAAK,MAAU,EAAM,QAAQ,CAAC,KAAK,KAAK,CAAC;AAEzE,KAAI,CAAC,EAAO,KACV,OAAU,MAAM,GAAG,EAAO,cAAc,mBAAmB;AAG7D,QAAO,EAAO;;AAGhB,eAAsB,EACpB,GACA,GACA,IAAQ,IACiC;AAQzC,SAPa,MAAM,EAAyE;EAC1F,OAAO;EACP,eAAe;EACf,WAAW;GAAE,WAAA;GAAiC;GAAO;EACrD;EACA;EACD,CAAC,EACU,0BAA0B,EAAE;;AAG1C,eAAsB,EACpB,GACA,GACA,GACA,IAAQ,KACgC;AAUxC,SATa,MAAM,EAEhB;EACD,OAAO;EACP,eAAe;EACf,WAAW;GAAE;GAAgB;GAAO;EACpC;EACA;EACD,CAAC,EACU,iCAAiC,EAAE;;AAOjD,eAAsB,EACpB,GAMA,GACA,GACuC;AAgBvC,SAfa,MAAM,EAAkE;EACnF,OAAO;EACP,eAAe;EACf,WAAW,EACT,OAAO;GACL,gBAAgB,EAAO;GACvB,WAAW;GACX,QAAQ,EAAO;GACf,OAAO,EAAO;GACd,gBAAgB,EAAO;GACxB,EACF;EACD;EACA;EACD,CAAC,EACU;;AAGd,eAAsB,EACpB,GACA,GACA,GACe;AACf,OAAM,EAAuD;EAC3D,OAAO;EACP,eAAe;EACf,WAAW,EAAE,mBAAgB;EAC7B;EACA;EACD,CAAC"}
1
+ {"version":3,"file":"conversationHistoryApi.js","names":[],"sources":["../../../src/bigconsole/assistant/conversationHistoryApi.ts"],"sourcesContent":["/**\n * Durable assistant history, stored in wspace-conversations.\n *\n * The agent's own session lives in a sandbox with a 10-minute TTL and no\n * persistent volume, so it cannot be the store of record for a transcript the\n * user expects to keep. Every completed turn is written here instead, tagged\n * with the product that owns it, so history is durable AND product-scoped —\n * a BigConsole chat can never appear in another product's panel.\n *\n * Raw operation strings rather than generated documents: these ops are only used\n * here, and `graphqlFetch` takes a query string, so there is nothing to gain\n * from putting them through codegen.\n */\n\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { BIGCONSOLE_PRODUCT } from './product';\nimport type { AssistantSandboxAuthContext } from './types';\n\n/**\n * Every conversation written from here belongs to BigConsole.\n *\n * ★ Same value as `assistantApi`'s `ASSISTANT_PRODUCT`, and now literally the\n * same source. These were two independent string literals under two different\n * names — history rows are tagged with this one while sandboxes are stamped with\n * that one, so a divergence would have hidden a product's whole transcript from\n * its own panel without failing anything.\n */\nexport const ASSISTANT_PRODUCT_ID = BIGCONSOLE_PRODUCT;\n\nexport interface AssistantConversationSummary {\n id: string;\n title: string | null;\n agentSessionId: string | null;\n updatedAt: string;\n}\n\nexport interface AssistantHistoryTurnMessage {\n id: string;\n role: 'USER' | 'ASSISTANT';\n content: string;\n createdAt: string;\n}\n\nconst CONVERSATION_FIELDS = `\n id\n title\n agentSessionId\n updatedAt\n`;\n\nconst LIST_CONVERSATIONS = `\n query AssistantConversations($productId: String!, $first: Int) {\n assistantConversations(productId: $productId, first: $first) { ${CONVERSATION_FIELDS} }\n }\n`;\n\nconst CONVERSATION_MESSAGES = `\n query AssistantConversationMessages($conversationId: ID!, $first: Int) {\n assistantConversationMessages(conversationId: $conversationId, first: $first) {\n id\n role\n content\n createdAt\n }\n }\n`;\n\nconst SAVE_TURN = `\n mutation SaveAssistantTurn($input: SaveAssistantTurnInput!) {\n saveAssistantTurn(input: $input) { ${CONVERSATION_FIELDS} }\n }\n`;\n\nconst DELETE_CONVERSATION = `\n mutation DeleteAssistantConversation($conversationId: ID!) {\n deleteAssistantConversation(conversationId: $conversationId)\n }\n`;\n\nasync function historyFetch<TData>(params: {\n query: string;\n operationName: string;\n variables: Record<string, unknown>;\n workspaceId: string;\n authContext?: AssistantSandboxAuthContext;\n}): Promise<TData> {\n const result = await graphqlFetch<TData>({\n gateway: 'workspace',\n query: params.query,\n operationName: params.operationName,\n variables: params.variables,\n authToken: params.authContext?.accessToken ?? undefined,\n // Mint the workspace token rather than passing a possibly-stale string —\n // same reasoning as assistantApi: a stale token is nulled out and NOT\n // replaced unless the caller opts in here.\n workspaceToken: true,\n workspaceId: params.workspaceId,\n organizationId: params.authContext?.organizationId ?? true,\n suppressGlobalErrorEvent: true,\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors.map((error) => error.message).join(', '));\n }\n if (!result.data) {\n throw new Error(`${params.operationName} returned no data`);\n }\n\n return result.data;\n}\n\nexport async function listAssistantConversations(\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext,\n first = 25\n): Promise<AssistantConversationSummary[]> {\n const data = await historyFetch<{ assistantConversations: AssistantConversationSummary[] }>({\n query: LIST_CONVERSATIONS,\n operationName: 'AssistantConversations',\n variables: { productId: ASSISTANT_PRODUCT_ID, first },\n workspaceId,\n authContext,\n });\n return data.assistantConversations ?? [];\n}\n\nexport async function getAssistantConversationMessages(\n conversationId: string,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext,\n first = 200\n): Promise<AssistantHistoryTurnMessage[]> {\n const data = await historyFetch<{\n assistantConversationMessages: AssistantHistoryTurnMessage[];\n }>({\n query: CONVERSATION_MESSAGES,\n operationName: 'AssistantConversationMessages',\n variables: { conversationId, first },\n workspaceId,\n authContext,\n });\n return data.assistantConversationMessages ?? [];\n}\n\n/**\n * Persist one completed turn. Creates the conversation on the first turn and\n * returns it, so the caller can hold on to the id.\n */\nexport async function saveAssistantTurn(\n params: {\n conversationId: string | null;\n prompt: string;\n reply: string;\n agentSessionId: string | null;\n },\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<AssistantConversationSummary> {\n const data = await historyFetch<{ saveAssistantTurn: AssistantConversationSummary }>({\n query: SAVE_TURN,\n operationName: 'SaveAssistantTurn',\n variables: {\n input: {\n conversationId: params.conversationId,\n productId: ASSISTANT_PRODUCT_ID,\n prompt: params.prompt,\n reply: params.reply,\n agentSessionId: params.agentSessionId,\n },\n },\n workspaceId,\n authContext,\n });\n return data.saveAssistantTurn;\n}\n\nexport async function deleteAssistantConversation(\n conversationId: string,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<void> {\n await historyFetch<{ deleteAssistantConversation: boolean }>({\n query: DELETE_CONVERSATION,\n operationName: 'DeleteAssistantConversation',\n variables: { conversationId },\n workspaceId,\n authContext,\n });\n}\n"],"mappings":";;;AA4BA,IAAa,IAAuB,GAgB9B,IAAsB,oDAOtB,IAAqB;;qEAE0C,EAAoB;;GAInF,IAAwB,yOAWxB,IAAY;;yCAEuB,EAAoB;;GAIvD,IAAsB;AAM5B,eAAe,EAAoB,GAMhB;CACjB,IAAM,IAAS,MAAM,EAAoB;EACvC,SAAS;EACT,OAAO,EAAO;EACd,eAAe,EAAO;EACtB,WAAW,EAAO;EAClB,WAAW,EAAO,aAAa,eAAe,KAAA;EAI9C,gBAAgB;EAChB,aAAa,EAAO;EACpB,gBAAgB,EAAO,aAAa,kBAAkB;EACtD,0BAA0B;EAC3B,CAAC;AAEF,KAAI,EAAO,QAAQ,OACjB,OAAU,MAAM,EAAO,OAAO,KAAK,MAAU,EAAM,QAAQ,CAAC,KAAK,KAAK,CAAC;AAEzE,KAAI,CAAC,EAAO,KACV,OAAU,MAAM,GAAG,EAAO,cAAc,mBAAmB;AAG7D,QAAO,EAAO;;AAGhB,eAAsB,EACpB,GACA,GACA,IAAQ,IACiC;AAQzC,SAPa,MAAM,EAAyE;EAC1F,OAAO;EACP,eAAe;EACf,WAAW;GAAE,WAAW;GAAsB;GAAO;EACrD;EACA;EACD,CAAC,EACU,0BAA0B,EAAE;;AAG1C,eAAsB,EACpB,GACA,GACA,GACA,IAAQ,KACgC;AAUxC,SATa,MAAM,EAEhB;EACD,OAAO;EACP,eAAe;EACf,WAAW;GAAE;GAAgB;GAAO;EACpC;EACA;EACD,CAAC,EACU,iCAAiC,EAAE;;AAOjD,eAAsB,EACpB,GAMA,GACA,GACuC;AAgBvC,SAfa,MAAM,EAAkE;EACnF,OAAO;EACP,eAAe;EACf,WAAW,EACT,OAAO;GACL,gBAAgB,EAAO;GACvB,WAAW;GACX,QAAQ,EAAO;GACf,OAAO,EAAO;GACd,gBAAgB,EAAO;GACxB,EACF;EACD;EACA;EACD,CAAC,EACU;;AAGd,eAAsB,EACpB,GACA,GACA,GACe;AACf,OAAM,EAAuD;EAC3D,OAAO;EACP,eAAe;EACf,WAAW,EAAE,mBAAgB;EAC7B;EACA;EACD,CAAC"}
@@ -1,4 +1,10 @@
1
- import { gatherPageContext as e, resolveBigConsoleConversationContext as t } from "./pageContext.js";
2
- import { useSandboxAssistantTransport as n } from "./createSandboxAssistantTransport.js";
3
- import { AiPreviewMiniPanel as r } from "../components/preview/AiPreviewMiniPanel.js";
4
- export { r as AiPreviewMiniPanel, e as gatherPageContext, t as resolveBigConsoleConversationContext, n as useSandboxAssistantTransport };
1
+ import { BIGCONSOLE_PRODUCT as e } from "./product.js";
2
+ import { describeCurrentPage as t, gatherPageContext as n, resolveBigConsoleConversationContext as r } from "./pageContext.js";
3
+ import { useSandboxAssistantTransport as i } from "./createSandboxAssistantTransport.js";
4
+ import { AiPreviewMiniPanel as a } from "../components/preview/AiPreviewMiniPanel.js";
5
+ import { API_CALLS_CONFIRM_PREAMBLE as o, USER_REQUEST_DELIMITER as s, buildPromptForMode as c } from "./assistantPrompt.js";
6
+ import { ASK as l, BIGCONSOLE_ASSISTANT_MODES as u, BUILD as d } from "./modes.js";
7
+ import { BIGCONSOLE_ASSISTANT_PRODUCT as f, STORAGE_FOLDERS as p } from "./assistantProduct.js";
8
+ import { PROBE_RESOURCES as m, useWorkspaceWriteAccess as h } from "./useWorkspaceWriteAccess.js";
9
+ import { useBigConsoleAssistantPreview as g } from "./previewController.js";
10
+ export { o as API_CALLS_CONFIRM_PREAMBLE, l as ASK, a as AiPreviewMiniPanel, u as BIGCONSOLE_ASSISTANT_MODES, f as BIGCONSOLE_ASSISTANT_PRODUCT, e as BIGCONSOLE_PRODUCT, d as BUILD, m as PROBE_RESOURCES, p as STORAGE_FOLDERS, s as USER_REQUEST_DELIMITER, c as buildPromptForMode, t as describeCurrentPage, n as gatherPageContext, r as resolveBigConsoleConversationContext, g as useBigConsoleAssistantPreview, i as useSandboxAssistantTransport, h as useWorkspaceWriteAccess };
@@ -0,0 +1,29 @@
1
+ //#region src/bigconsole/assistant/modes.ts
2
+ var e = {
3
+ id: "assistant",
4
+ label: "Ask",
5
+ description: "Ask about BigConsole and your workspace. It reads your dashboards, data sinks, parsers and widgets, explains what you are looking at and points you to the right page. Read-only.",
6
+ suggestions: [
7
+ "What can I do on this screen?",
8
+ "What is in this data sink?",
9
+ "Which widgets are on this dashboard?",
10
+ "Why is my parser returning no rows?"
11
+ ]
12
+ }, t = {
13
+ id: "api-calls",
14
+ label: "Build",
15
+ description: "Let the assistant build for you: create a data sink, a dashboard, a parser and the widgets on it. It shows you each change and waits for your approval before anything is saved.",
16
+ suggestions: [
17
+ "Build a school attendance dashboard from this CSV",
18
+ "Add a bar chart of monthly totals to this dashboard",
19
+ "Create a parser that splits the address column",
20
+ "Show me what this data sink contains"
21
+ ],
22
+ requiresAdmin: !0,
23
+ writeCapable: !0,
24
+ streamBudgetMs: 42e4
25
+ }, n = [e, t];
26
+ //#endregion
27
+ export { e as ASK, n as BIGCONSOLE_ASSISTANT_MODES, t as BUILD };
28
+
29
+ //# sourceMappingURL=modes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modes.js","names":[],"sources":["../../../src/bigconsole/assistant/modes.ts"],"sourcesContent":["/**\n * The modes BigConsole offers the shared assistant panel.\n *\n * ★★ Mode IDS are transport values, not labels. The id picks the sandbox image,\n * the env that image boots with, and the agent persona; `label` is the only part\n * a user sees. Renaming an id silently changes which agent answers.\n *\n * ★★★ WHY `assistant` (Ask) IS SAFE HERE, despite the note beside `MODE` in\n * `createSandboxAssistantTransport.ts`.\n *\n * That comment says BigConsole \"still boots the manually-tagged ACA image\n * `alpha-delegated-auth-v11`\", and that the combined `assistant` mode \"depends\n * on newer image contracts that are not yet guaranteed on this tag\". Both\n * halves are now stale:\n *\n * - ACA — Azure Container Apps — was DECOMMISSIONED and deleted on\n * 2026-08-15. The sandbox runs on the AWS/Cloudflare path now; there is no\n * ACA image line left to be behind.\n * - fe-libs' `getImageTagForMode` returns the SAME tag for `assistant` and\n * `api-calls` (`alpha-delegated-auth-v11` off-prod, `prod` in prod), and\n * `getImageForMode` maps `assistant` onto the api-calls IMAGE outright —\n * \"one image, mode chosen at runtime\". So Ask runs on exactly the image\n * BigConsole already boots today, with `AI_ASSISTANT_MODE` selecting the\n * persona.\n *\n * HealthyBowl, VibeControls and FluidGrids all ship this same `assistant` mode\n * in production, on that same image. Ask is not a new image contract for\n * BigConsole; it is the persona switch the image already supports.\n */\nimport type { AssistantModeConfig } from '@burdenoff/fe-libs/shared/assistant/conversation';\n\n/**\n * Read-only. The ONLY mode fe-libs ever forwards the user's tokens to, and the\n * only one whose sandbox boots with `AI_ASSISTANT_READONLY=1`.\n *\n * ★ This is the user-visible point of the migration. Today BigConsole's\n * assistant is Owner/Admin-only in its entirety, because the legacy\n * `AssistantWidget` has exactly one mode and that mode writes. A member who\n * previously saw NO assistant at all gets read-only Ask, while `Build` stays\n * hidden from them — fe-libs does that filtering itself\n * (`AssistantChatArea` keeps `!mode.requiresAdmin || canUseAdminModes`).\n */\nconst ASK = {\n id: 'assistant',\n label: 'Ask',\n description:\n 'Ask about BigConsole and your workspace. It reads your dashboards, data sinks, parsers and widgets, ' +\n 'explains what you are looking at and points you to the right page. Read-only.',\n suggestions: [\n 'What can I do on this screen?',\n 'What is in this data sink?',\n 'Which widgets are on this dashboard?',\n 'Why is my parser returning no rows?',\n ],\n // No flags: resolves to the chat defaults — no artifacts, 180s budget, not\n // continued in the background, provisionable.\n} as const satisfies AssistantModeConfig;\n\n/**\n * The write mode. `api-calls` is the only id whose sandbox boots with\n * `AI_ASSISTANT_CONFIRM_WRITES=1`, which is why the label may change and the id\n * may not.\n */\nconst BUILD = {\n id: 'api-calls',\n label: 'Build',\n description:\n 'Let the assistant build for you: create a data sink, a dashboard, a parser and the widgets on it. ' +\n 'It shows you each change and waits for your approval before anything is saved.',\n suggestions: [\n 'Build a school attendance dashboard from this CSV',\n 'Add a bar chart of monthly totals to this dashboard',\n 'Create a parser that splits the address column',\n 'Show me what this data sink contains',\n ],\n // `requiresAdmin` is what makes fe-libs issue the myRoles query at all.\n requiresAdmin: true,\n // `writeCapable` routes the first switch through the consent dialog and turns\n // on the composer's write-access warning.\n writeCapable: true,\n /**\n * 420s, matching the legacy transport's `STREAM_BUDGET_MS` exactly, so this\n * introduces no behavioural change.\n *\n * ★★ NOT cosmetic, and not a round number picked for comfort. The default is\n * 180s, which was SHORTER THAN THE WORK: a full build — data sink → dashboard\n * → parser → widget, each a separate gateway call preceded by a model\n * round-trip — measured 229s in prod. The agent finished, the dashboard\n * genuinely existed, and the user was told the assistant timed out. People\n * then retried and ended up with duplicate dashboards.\n *\n * Finite, positive and under `MAX_ASSISTANT_STREAM_BUDGET_MS`, so fe-libs\n * honours it (`resolveAssistantModeCapabilities`) instead of silently falling\n * back to 180_000 — which is why this seam needs fe-libs >= 2026.916.1.\n */\n streamBudgetMs: 420_000,\n} as const satisfies AssistantModeConfig;\n\n/**\n * The list handed to `AssistantProductProvider`, in display order.\n *\n * ★ Exported as ONE array and consumed by identity. The seam guard asserts\n * `toBe`, not deep equality, because any `.map()` or rebuild on the way to the\n * provider is exactly how `requiresAdmin` and `writeCapable` get dropped.\n */\nexport const BIGCONSOLE_ASSISTANT_MODES: readonly AssistantModeConfig[] = [ASK, BUILD];\n\nexport { ASK, BUILD };\n"],"mappings":";AA0CA,IAAM,IAAM;CACV,IAAI;CACJ,OAAO;CACP,aACE;CAEF,aAAa;EACX;EACA;EACA;EACA;EACD;CAGF,EAOK,IAAQ;CACZ,IAAI;CACJ,OAAO;CACP,aACE;CAEF,aAAa;EACX;EACA;EACA;EACA;EACD;CAED,eAAe;CAGf,cAAc;CAgBd,gBAAgB;CACjB,EASY,IAA6D,CAAC,GAAK,EAAM"}
@@ -1,24 +1,37 @@
1
+ import { BIGCONSOLE_PRODUCT as e } from "./product.js";
1
2
  //#region src/bigconsole/assistant/pageContext.ts
2
- function e() {
3
- let e = window.location.href, t = window.location.pathname.split("/").filter(Boolean).slice(0, 3).join("/") || "home", r = n(window.location.pathname, window.location.search);
3
+ function t() {
4
+ let t = window.location.href, n = window.location.pathname.split("/").filter(Boolean).slice(0, 3).join("/") || "home", r = o(window.location.pathname, window.location.search);
4
5
  return {
5
- currentUrl: e,
6
- currentPage: t,
6
+ currentUrl: t,
7
+ currentPage: n,
7
8
  entityType: r.entityType,
8
9
  entityId: r.entityId,
9
- platform: "bigconsole",
10
+ platform: e,
10
11
  version: "2026.430.500",
11
12
  viewportSize: `${window.innerWidth}x${window.innerHeight}`,
12
13
  userAgent: navigator.userAgent,
13
14
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
14
15
  };
15
16
  }
16
- function t(e, t) {
17
+ var n = "Home", r = {
18
+ home: n,
19
+ dashboards: "Dashboards",
20
+ datasinks: "Data sinks",
21
+ parsers: "Parsers",
22
+ pipelines: "Pipelines",
23
+ workflows: "Workflows"
24
+ };
25
+ function i() {
26
+ let { currentPage: e } = t(), i = e.split("/").filter(Boolean), a = (i[0] === "bigconsole" ? i[1] : i[0]) ?? "";
27
+ return !a || a === "home" ? n : r[a] ?? a.split("-").map((e) => e && e.charAt(0).toUpperCase() + e.slice(1)).join(" ");
28
+ }
29
+ function a(e, t) {
17
30
  return t.match(e);
18
31
  }
19
- function n(e, n) {
20
- let i = new URLSearchParams(n);
21
- for (let [n, i] of [
32
+ function o(e, t) {
33
+ let n = new URLSearchParams(t);
34
+ for (let [t, n] of [
22
35
  [/^\/bigconsole\/dashboards\/[^/]+\/parsers\/([^/]+)(?:\/edit)?$/, "BIGCONSOLE_PARSER"],
23
36
  [/^\/bigconsole\/dashboards\/([^/]+)(?:\/builder)?$/, "BIGCONSOLE_DASHBOARD"],
24
37
  [/^\/bigconsole\/datasinks\/([^/]+)(?:\/edit)?$/, "BIGCONSOLE_DATASINK"],
@@ -26,26 +39,26 @@ function n(e, n) {
26
39
  [/^\/bigconsole\/pipelines\/([^/]+)(?:\/edit)?$/, "BIGCONSOLE_PIPELINE"],
27
40
  [/^\/bigconsole\/workflows\/([^/]+)(?:\/edit)?$/, "BIGCONSOLE_WORKFLOW"]
28
41
  ]) {
29
- let a = t(n, e);
30
- if (a && a[1] && !r(a[1])) return {
31
- entityType: i,
32
- entityId: a[1],
33
- metadata: { routePattern: n.source }
42
+ let r = a(t, e);
43
+ if (r && r[1] && !s(r[1])) return {
44
+ entityType: n,
45
+ entityId: r[1],
46
+ metadata: { routePattern: t.source }
34
47
  };
35
48
  }
36
- let a = i.get("tab") ?? (() => {
49
+ let r = n.get("tab") ?? (() => {
37
50
  let t = e.split("/").filter(Boolean);
38
51
  return t.length > 2 ? t[t.length - 1] : void 0;
39
52
  })();
40
53
  return {
41
- activeTab: a ?? void 0,
42
- metadata: { ...a ? { activeTab: a } : {} }
54
+ activeTab: r ?? void 0,
55
+ metadata: { ...r ? { activeTab: r } : {} }
43
56
  };
44
57
  }
45
- function r(e) {
58
+ function s(e) {
46
59
  return e === "new" || e === "builder" || e === "list";
47
60
  }
48
61
  //#endregion
49
- export { e as gatherPageContext, n as resolveBigConsoleConversationContext };
62
+ export { i as describeCurrentPage, t as gatherPageContext, o as resolveBigConsoleConversationContext };
50
63
 
51
64
  //# sourceMappingURL=pageContext.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"pageContext.js","names":[],"sources":["../../../src/bigconsole/assistant/pageContext.ts"],"sourcesContent":["/**\n * Page context gathering for BigConsole — captures the user's current browser\n * context to be sent alongside every assistant prompt for better AI awareness.\n *\n * Mirrors microfe-vibecontrols' `utils/pageContext.ts`. The agent filters the\n * payload to an allow-list (`ai-assistant/src/context-loader.ts` →\n * `ALLOWED_CONTEXT_KEYS`), so the keys MUST be `currentUrl` (NOT `activeUrl`)\n * and the generic `entityType` / `entityId`, or the value is silently dropped.\n */\n\ninterface ConversationContextShape {\n entityType?: string;\n entityId?: string;\n activeTab?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface PageContext {\n // `currentUrl` (NOT `activeUrl`) — must match the agent's allowed-context-key\n // contract, otherwise the agent filters it out.\n currentUrl: string;\n currentPage: string;\n // The entity the user is currently looking at, resolved from the route. Lets\n // the assistant act on \"this / current / here\" without asking for an id.\n entityType?: string;\n entityId?: string;\n platform: string;\n version: string;\n viewportSize: string;\n userAgent: string;\n timestamp: string;\n}\n\nexport function gatherPageContext(): PageContext {\n const url = window.location.href;\n const pathSegments = window.location.pathname.split('/').filter(Boolean);\n const currentPage = pathSegments.slice(0, 3).join('/') || 'home';\n const entity = resolveBigConsoleConversationContext(window.location.pathname, window.location.search);\n\n return {\n currentUrl: url,\n currentPage,\n entityType: entity.entityType,\n entityId: entity.entityId,\n platform: 'bigconsole',\n version: import.meta.env.VITE_APP_VERSION ?? '2026.430.500',\n viewportSize: `${window.innerWidth}x${window.innerHeight}`,\n userAgent: navigator.userAgent,\n timestamp: new Date().toISOString(),\n };\n}\n\nfunction matchPath(pattern: RegExp, pathname: string): RegExpMatchArray | null {\n return pathname.match(pattern);\n}\n\n/**\n * Maps the current BigConsole route to `{ entityType, entityId }`. The BigConsole\n * MFE mounts under the `/bigconsole` base path in the host shell, so detail\n * routes are matched as `/bigconsole/<collection>/:id`.\n *\n * More specific routes (e.g. dashboard-scoped parsers, builder/edit sub-routes)\n * are matched before their parent so the most relevant entity wins.\n */\nexport function resolveBigConsoleConversationContext(pathname: string, search: string): ConversationContextShape {\n const params = new URLSearchParams(search);\n\n const directMatches: Array<[RegExp, string]> = [\n // Dashboard-scoped parser detail (more specific than the bare parser route).\n [/^\\/bigconsole\\/dashboards\\/[^/]+\\/parsers\\/([^/]+)(?:\\/edit)?$/, 'BIGCONSOLE_PARSER'],\n // Dashboard detail + builder.\n [/^\\/bigconsole\\/dashboards\\/([^/]+)(?:\\/builder)?$/, 'BIGCONSOLE_DASHBOARD'],\n // Data sinks.\n [/^\\/bigconsole\\/datasinks\\/([^/]+)(?:\\/edit)?$/, 'BIGCONSOLE_DATASINK'],\n // Parsers.\n [/^\\/bigconsole\\/parsers\\/([^/]+)(?:\\/edit)?$/, 'BIGCONSOLE_PARSER'],\n // Pipelines.\n [/^\\/bigconsole\\/pipelines\\/([^/]+)(?:\\/edit)?$/, 'BIGCONSOLE_PIPELINE'],\n // Workflows (BigConsole-native list/view; the SDK builder uses /workflow/...).\n [/^\\/bigconsole\\/workflows\\/([^/]+)(?:\\/edit)?$/, 'BIGCONSOLE_WORKFLOW'],\n ];\n\n for (const [pattern, entityType] of directMatches) {\n const match = matchPath(pattern, pathname);\n if (match && match[1] && !isReservedSegment(match[1])) {\n return {\n entityType,\n entityId: match[1],\n metadata: {\n routePattern: pattern.source,\n },\n };\n }\n }\n\n const activeTab =\n params.get('tab') ??\n (() => {\n const segments = pathname.split('/').filter(Boolean);\n return segments.length > 2 ? segments[segments.length - 1] : undefined;\n })();\n\n return {\n activeTab: activeTab ?? undefined,\n metadata: {\n ...(activeTab ? { activeTab } : {}),\n },\n };\n}\n\n/**\n * Static segments that share a route shape with `:id` detail routes\n * (e.g. `/bigconsole/datasinks/new`, `/bigconsole/parsers/new`). These are\n * pages, not entities, so they must not be reported as an `entityId`.\n */\nfunction isReservedSegment(segment: string): boolean {\n return segment === 'new' || segment === 'builder' || segment === 'list';\n}\n"],"mappings":";AAiCA,SAAgB,IAAiC;CAC/C,IAAM,IAAM,OAAO,SAAS,MAEtB,IADe,OAAO,SAAS,SAAS,MAAM,IAAI,CAAC,OAAO,QAAQ,CACvC,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI,IAAI,QACpD,IAAS,EAAqC,OAAO,SAAS,UAAU,OAAO,SAAS,OAAO;AAErG,QAAO;EACL,YAAY;EACZ;EACA,YAAY,EAAO;EACnB,UAAU,EAAO;EACjB,UAAU;EACV,SAA6C;EAC7C,cAAc,GAAG,OAAO,WAAW,GAAG,OAAO;EAC7C,WAAW,UAAU;EACrB,4BAAW,IAAI,MAAM,EAAC,aAAa;EACpC;;AAGH,SAAS,EAAU,GAAiB,GAA2C;AAC7E,QAAO,EAAS,MAAM,EAAQ;;AAWhC,SAAgB,EAAqC,GAAkB,GAA0C;CAC/G,IAAM,IAAS,IAAI,gBAAgB,EAAO;AAiB1C,MAAK,IAAM,CAAC,GAAS,MAf0B;EAE7C,CAAC,kEAAkE,oBAAoB;EAEvF,CAAC,qDAAqD,uBAAuB;EAE7E,CAAC,iDAAiD,sBAAsB;EAExE,CAAC,+CAA+C,oBAAoB;EAEpE,CAAC,iDAAiD,sBAAsB;EAExE,CAAC,iDAAiD,sBAAsB;EACzE,EAEkD;EACjD,IAAM,IAAQ,EAAU,GAAS,EAAS;AAC1C,MAAI,KAAS,EAAM,MAAM,CAAC,EAAkB,EAAM,GAAG,CACnD,QAAO;GACL;GACA,UAAU,EAAM;GAChB,UAAU,EACR,cAAc,EAAQ,QACvB;GACF;;CAIL,IAAM,IACJ,EAAO,IAAI,MAAM,WACV;EACL,IAAM,IAAW,EAAS,MAAM,IAAI,CAAC,OAAO,QAAQ;AACpD,SAAO,EAAS,SAAS,IAAI,EAAS,EAAS,SAAS,KAAK,KAAA;KAC3D;AAEN,QAAO;EACL,WAAW,KAAa,KAAA;EACxB,UAAU,EACR,GAAI,IAAY,EAAE,cAAW,GAAG,EAAE,EACnC;EACF;;AAQH,SAAS,EAAkB,GAA0B;AACnD,QAAO,MAAY,SAAS,MAAY,aAAa,MAAY"}
1
+ {"version":3,"file":"pageContext.js","names":[],"sources":["../../../src/bigconsole/assistant/pageContext.ts"],"sourcesContent":["/**\n * Page context gathering for BigConsole — captures the user's current browser\n * context to be sent alongside every assistant prompt for better AI awareness.\n *\n * Mirrors microfe-vibecontrols' `utils/pageContext.ts`. The agent filters the\n * payload to an allow-list (`ai-assistant/src/context-loader.ts` →\n * `ALLOWED_CONTEXT_KEYS`), so the keys MUST be `currentUrl` (NOT `activeUrl`)\n * and the generic `entityType` / `entityId`, or the value is silently dropped.\n */\n\nimport { BIGCONSOLE_PRODUCT } from './product';\n\ninterface ConversationContextShape {\n entityType?: string;\n entityId?: string;\n activeTab?: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * ★★ A `type`, NOT an `interface`, and the difference is load-bearing.\n *\n * fe-libs' product seam declares `gatherPageContext?: () => PageContext` against\n * ITS OWN `PageContext`, which carries an index signature\n * (`[key: string]: unknown`) so a product may add keys of its own. TypeScript\n * gives a type alias an IMPLICIT index signature and an interface NONE —\n * an interface stays open to declaration merging, so the compiler cannot know\n * its full set of keys and refuses the assignment.\n *\n * So this exact shape, written as an interface, is not assignable to fe-libs'\n * parameter: `assistantProduct.ts` fails with TS2322 while the runtime value is\n * perfectly correct. Changing this back to `interface` re-breaks the seam.\n */\nexport type PageContext = {\n // `currentUrl` (NOT `activeUrl`) — must match the agent's allowed-context-key\n // contract, otherwise the agent filters it out.\n currentUrl: string;\n currentPage: string;\n // The entity the user is currently looking at, resolved from the route. Lets\n // the assistant act on \"this / current / here\" without asking for an id.\n entityType?: string;\n entityId?: string;\n platform: string;\n version: string;\n viewportSize: string;\n userAgent: string;\n timestamp: string;\n};\n\nexport function gatherPageContext(): PageContext {\n const url = window.location.href;\n const pathSegments = window.location.pathname.split('/').filter(Boolean);\n const currentPage = pathSegments.slice(0, 3).join('/') || 'home';\n const entity = resolveBigConsoleConversationContext(window.location.pathname, window.location.search);\n\n return {\n currentUrl: url,\n currentPage,\n entityType: entity.entityType,\n entityId: entity.entityId,\n platform: BIGCONSOLE_PRODUCT,\n version: import.meta.env.VITE_APP_VERSION ?? '2026.430.500',\n viewportSize: `${window.innerWidth}x${window.innerHeight}`,\n userAgent: navigator.userAgent,\n timestamp: new Date().toISOString(),\n };\n}\n\n/** Shown to the user as \"where you are\", e.g. in the panel's empty state. */\nconst HOME_LABEL = 'Home';\n\n/**\n * Route segment → human label.\n *\n * ★ Only the collections BigConsole actually routes to. Anything missing falls\n * through to a title-cased segment, which is right far more often than a wrong\n * label would be — and a wrong answer here is worse than none, because the panel\n * states it as fact.\n */\nconst PAGE_LABELS: Readonly<Record<string, string>> = {\n home: HOME_LABEL,\n dashboards: 'Dashboards',\n datasinks: 'Data sinks',\n parsers: 'Parsers',\n pipelines: 'Pipelines',\n workflows: 'Workflows',\n};\n\n/**\n * A human label for where the user is right now, for the shared panel.\n *\n * ★★ The `/bigconsole` MOUNT SEGMENT IS SKIPPED. This MFE mounts under\n * `/bigconsole` in the host shell, so the first segment is the product name on\n * every single page — without this, every location would be described as\n * \"Bigconsole\", which is both useless and wrong.\n *\n * ★ Derived from `gatherPageContext().currentPage` rather than reading\n * `window.location` a second time, so the label and the agent's structured\n * context can never disagree about which page they describe.\n */\nexport function describeCurrentPage(): string {\n const { currentPage } = gatherPageContext();\n const segments = currentPage.split('/').filter(Boolean);\n const first = (segments[0] === BIGCONSOLE_PRODUCT ? segments[1] : segments[0]) ?? '';\n if (!first || first === 'home') return HOME_LABEL;\n return (\n PAGE_LABELS[first] ??\n first\n .split('-')\n .map((word) => (word ? word.charAt(0).toUpperCase() + word.slice(1) : word))\n .join(' ')\n );\n}\n\nfunction matchPath(pattern: RegExp, pathname: string): RegExpMatchArray | null {\n return pathname.match(pattern);\n}\n\n/**\n * Maps the current BigConsole route to `{ entityType, entityId }`. The BigConsole\n * MFE mounts under the `/bigconsole` base path in the host shell, so detail\n * routes are matched as `/bigconsole/<collection>/:id`.\n *\n * More specific routes (e.g. dashboard-scoped parsers, builder/edit sub-routes)\n * are matched before their parent so the most relevant entity wins.\n */\nexport function resolveBigConsoleConversationContext(pathname: string, search: string): ConversationContextShape {\n const params = new URLSearchParams(search);\n\n const directMatches: Array<[RegExp, string]> = [\n // Dashboard-scoped parser detail (more specific than the bare parser route).\n [/^\\/bigconsole\\/dashboards\\/[^/]+\\/parsers\\/([^/]+)(?:\\/edit)?$/, 'BIGCONSOLE_PARSER'],\n // Dashboard detail + builder.\n [/^\\/bigconsole\\/dashboards\\/([^/]+)(?:\\/builder)?$/, 'BIGCONSOLE_DASHBOARD'],\n // Data sinks.\n [/^\\/bigconsole\\/datasinks\\/([^/]+)(?:\\/edit)?$/, 'BIGCONSOLE_DATASINK'],\n // Parsers.\n [/^\\/bigconsole\\/parsers\\/([^/]+)(?:\\/edit)?$/, 'BIGCONSOLE_PARSER'],\n // Pipelines.\n [/^\\/bigconsole\\/pipelines\\/([^/]+)(?:\\/edit)?$/, 'BIGCONSOLE_PIPELINE'],\n // Workflows (BigConsole-native list/view; the SDK builder uses /workflow/...).\n [/^\\/bigconsole\\/workflows\\/([^/]+)(?:\\/edit)?$/, 'BIGCONSOLE_WORKFLOW'],\n ];\n\n for (const [pattern, entityType] of directMatches) {\n const match = matchPath(pattern, pathname);\n if (match && match[1] && !isReservedSegment(match[1])) {\n return {\n entityType,\n entityId: match[1],\n metadata: {\n routePattern: pattern.source,\n },\n };\n }\n }\n\n const activeTab =\n params.get('tab') ??\n (() => {\n const segments = pathname.split('/').filter(Boolean);\n return segments.length > 2 ? segments[segments.length - 1] : undefined;\n })();\n\n return {\n activeTab: activeTab ?? undefined,\n metadata: {\n ...(activeTab ? { activeTab } : {}),\n },\n };\n}\n\n/**\n * Static segments that share a route shape with `:id` detail routes\n * (e.g. `/bigconsole/datasinks/new`, `/bigconsole/parsers/new`). These are\n * pages, not entities, so they must not be reported as an `entityId`.\n */\nfunction isReservedSegment(segment: string): boolean {\n return segment === 'new' || segment === 'builder' || segment === 'list';\n}\n"],"mappings":";;AAiDA,SAAgB,IAAiC;CAC/C,IAAM,IAAM,OAAO,SAAS,MAEtB,IADe,OAAO,SAAS,SAAS,MAAM,IAAI,CAAC,OAAO,QAAQ,CACvC,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI,IAAI,QACpD,IAAS,EAAqC,OAAO,SAAS,UAAU,OAAO,SAAS,OAAO;AAErG,QAAO;EACL,YAAY;EACZ;EACA,YAAY,EAAO;EACnB,UAAU,EAAO;EACjB,UAAU;EACV,SAA6C;EAC7C,cAAc,GAAG,OAAO,WAAW,GAAG,OAAO;EAC7C,WAAW,UAAU;EACrB,4BAAW,IAAI,MAAM,EAAC,aAAa;EACpC;;AAIH,IAAM,IAAa,QAUb,IAAgD;CACpD,MAAM;CACN,YAAY;CACZ,WAAW;CACX,SAAS;CACT,WAAW;CACX,WAAW;CACZ;AAcD,SAAgB,IAA8B;CAC5C,IAAM,EAAE,mBAAgB,GAAmB,EACrC,IAAW,EAAY,MAAM,IAAI,CAAC,OAAO,QAAQ,EACjD,KAAS,EAAS,OAAA,eAA4B,EAAS,KAAK,EAAS,OAAO;AAElF,QADI,CAAC,KAAS,MAAU,SAAe,IAErC,EAAY,MACZ,EACG,MAAM,IAAI,CACV,KAAK,MAAU,KAAO,EAAK,OAAO,EAAE,CAAC,aAAa,GAAG,EAAK,MAAM,EAAE,CAAS,CAC3E,KAAK,IAAI;;AAIhB,SAAS,EAAU,GAAiB,GAA2C;AAC7E,QAAO,EAAS,MAAM,EAAQ;;AAWhC,SAAgB,EAAqC,GAAkB,GAA0C;CAC/G,IAAM,IAAS,IAAI,gBAAgB,EAAO;AAiB1C,MAAK,IAAM,CAAC,GAAS,MAf0B;EAE7C,CAAC,kEAAkE,oBAAoB;EAEvF,CAAC,qDAAqD,uBAAuB;EAE7E,CAAC,iDAAiD,sBAAsB;EAExE,CAAC,+CAA+C,oBAAoB;EAEpE,CAAC,iDAAiD,sBAAsB;EAExE,CAAC,iDAAiD,sBAAsB;EACzE,EAEkD;EACjD,IAAM,IAAQ,EAAU,GAAS,EAAS;AAC1C,MAAI,KAAS,EAAM,MAAM,CAAC,EAAkB,EAAM,GAAG,CACnD,QAAO;GACL;GACA,UAAU,EAAM;GAChB,UAAU,EACR,cAAc,EAAQ,QACvB;GACF;;CAIL,IAAM,IACJ,EAAO,IAAI,MAAM,WACV;EACL,IAAM,IAAW,EAAS,MAAM,IAAI,CAAC,OAAO,QAAQ;AACpD,SAAO,EAAS,SAAS,IAAI,EAAS,EAAS,SAAS,KAAK,KAAA;KAC3D;AAEN,QAAO;EACL,WAAW,KAAa,KAAA;EACxB,UAAU,EACR,GAAI,IAAY,EAAE,cAAW,GAAG,EAAE,EACnC;EACF;;AAQH,SAAS,EAAkB,GAA0B;AACnD,QAAO,MAAY,SAAS,MAAY,aAAa,MAAY"}
@@ -0,0 +1,60 @@
1
+ import { PREVIEW_STEPS as e, useAssistantRunStore as t } from "./assistantRunStore.js";
2
+ import { PreviewMeta as n, STEP_LABEL as r, formatAgo as i, previewLabels as a } from "../components/preview/previewShared.js";
3
+ import { previewRunAction as o } from "./previewRunAction.js";
4
+ import { useMemo as s } from "react";
5
+ import { useI18n as c } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
6
+ import { jsx as l } from "react/jsx-runtime";
7
+ import { AssistantEntityPreview as u, useAssistantStore as d } from "@burdenoff/fe-libs/shared/assistant";
8
+ //#region src/bigconsole/assistant/previewController.tsx
9
+ function f() {
10
+ let { t: o } = c(), s = t((e) => e.isRunning), d = t((e) => e.steps), f = t((e) => e.caption), p = t((e) => e.error), m = t((e) => e.updatedAt), h = e.map((e) => ({
11
+ id: e,
12
+ label: o(r[e].key, r[e].fallback),
13
+ status: d[e]
14
+ }));
15
+ return /* @__PURE__ */ l("div", {
16
+ "data-testid": "ai-preview-panel",
17
+ "data-variant": "assistant",
18
+ className: "min-w-0 p-3",
19
+ children: /* @__PURE__ */ l(u, {
20
+ title: o("bigconsole.widget.aiPreviewMini.title", "AI Preview"),
21
+ steps: h,
22
+ caption: s ? f || o("bigconsole.widget.aiPreviewMini.working", "Working…") : o("bigconsole.widget.aiPreviewMini.done", "Done"),
23
+ error: p,
24
+ meta: /* @__PURE__ */ l(n, {
25
+ state: s ? "running" : p ? "stopped" : "idle",
26
+ stateLabel: s ? o("bigconsole.widget.aiPreviewMini.live", "Live") : p ? o("bigconsole.widget.aiPreviewMini.stopped", "Stopped") : null,
27
+ updatedLabel: o("bigconsole.widget.aiPreviewMini.updated", "updated"),
28
+ updatedAt: m,
29
+ ago: (e) => i(e, o)
30
+ }),
31
+ labels: a(o)
32
+ })
33
+ });
34
+ }
35
+ var p = /* @__PURE__ */ new Set();
36
+ function m(e) {
37
+ let n = d.getState(), r = n.conversations.find((t) => t.id === e.run.conversationId)?.mode ?? n.mode;
38
+ if (o(e.kind, e.run.runId, r, p) === "ignore") return;
39
+ let i = t.getState();
40
+ if (e.kind === "start") {
41
+ p.add(e.run.runId), i.startRun("");
42
+ return;
43
+ }
44
+ if (e.kind === "snapshot") {
45
+ i.applyProgress(e.content);
46
+ return;
47
+ }
48
+ p.delete(e.run.runId), i.finishRun(e.outcome === "error" ? e.error ?? "The build stopped." : null);
49
+ }
50
+ function h() {
51
+ return s(() => ({
52
+ label: "Build preview",
53
+ render: () => /* @__PURE__ */ l(f, {}),
54
+ onEvent: m
55
+ }), []);
56
+ }
57
+ //#endregion
58
+ export { h as useBigConsoleAssistantPreview };
59
+
60
+ //# sourceMappingURL=previewController.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"previewController.js","names":[],"sources":["../../../src/bigconsole/assistant/previewController.tsx"],"sourcesContent":["/**\n * BigConsole's preview capability for the shared assistant panel (BC3).\n *\n * ★★★ THIS IS WHAT REPLACES THE TRANSPORT AS THE RUN STORE'S DRIVER.\n *\n * Today `createSandboxAssistantTransport` calls `startRun` / `applyProgress` /\n * `finishRun` itself, because it owns the turn. The shared panel consumes NO\n * transport at all — `AssistantChatArea` calls fe-libs' own `assistant/api`\n * directly — so once BC4 mounts the panel there is nothing left to drive the\n * store, and the live build preview would simply stop moving.\n *\n * fe-libs' preview capability is that missing wire: the bridge announces\n * `start` / `snapshot` / `end` for the run in flight, and this controller\n * forwards them to the same three store actions the transport used to call. The\n * store, both preview panels, and everything downstream are untouched.\n *\n * ★★ ABSENCE IS THE OFF-SWITCH. A product that supplies no controller gets no\n * toggle and no overlay; there is deliberately no second `enablePreview` flag to\n * disagree with the controller's presence. So this hook always returns a\n * controller — gating belongs at the mount site (BC4), not here.\n */\nimport { useMemo } from 'react';\nimport {\n AssistantEntityPreview,\n useAssistantStore,\n type AssistantPreviewController,\n type AssistantPreviewEvent,\n type AssistantPreviewStep,\n} from '@burdenoff/fe-libs/shared/assistant';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nimport { PreviewMeta, STEP_LABEL, formatAgo, previewLabels } from '../components/preview/previewShared';\nimport { PREVIEW_STEPS, useAssistantRunStore } from './assistantRunStore';\nimport { previewRunAction, type PreviewRunAction } from './previewRunAction';\n\n/**\n * The surface fe-libs renders inside its overlay.\n *\n * ★★ A COMPONENT, returned as JSX from `render()` — never hooks called inside\n * `render()` itself. fe-libs invokes `render()` from a single `PreviewSurface`\n * component, so hooks called there would belong to THAT fiber and any change in\n * their layout becomes \"rendered more hooks than during the previous render\".\n * Returning an element gives this surface its own fiber, which is both safer and\n * what lets it subscribe to the store independently.\n *\n * ★ Store-only by construction: no Apollo, no BigConsole context, no data\n * fetching — the same discipline that lets `AiPreviewMiniPanel` mount above the\n * router. The rich per-entity tabs stay in `AiPreviewPanel`, which lives inside\n * `BigConsoleRoot` where the data layer actually exists.\n *\n * ★ It renders the shared `AssistantEntityPreview` with the SAME mapping the\n * mini panel uses, and reuses `STEP_LABEL` / `previewLabels` / `PreviewMeta`\n * rather than restating them. Those helpers were extracted precisely because the\n * two existing panels kept drifting apart; a third copy here would reintroduce\n * the bug they were created to kill.\n */\nfunction BigConsoleAssistantPreviewSurface() {\n const { t } = useI18n();\n const isRunning = useAssistantRunStore((state) => state.isRunning);\n const steps = useAssistantRunStore((state) => state.steps);\n const caption = useAssistantRunStore((state) => state.caption);\n const error = useAssistantRunStore((state) => state.error);\n const updatedAt = useAssistantRunStore((state) => state.updatedAt);\n\n const railSteps: AssistantPreviewStep[] = PREVIEW_STEPS.map((key) => ({\n id: key,\n label: t(STEP_LABEL[key].key, STEP_LABEL[key].fallback),\n status: steps[key],\n }));\n\n return (\n <div data-testid=\"ai-preview-panel\" data-variant=\"assistant\" className=\"min-w-0 p-3\">\n <AssistantEntityPreview\n title={t('bigconsole.widget.aiPreviewMini.title', 'AI Preview')}\n steps={railSteps}\n // ★★ TWO branches, not three. The shared surface renders\n // `{error ? '' : caption}` — it BLANKS the caption whenever `error` is\n // set — so a failure branch here would compute a string that is then\n // discarded. The failure is named in `meta` instead, which\n // `PreviewHeader` renders without consulting `error`.\n caption={\n isRunning\n ? caption || t('bigconsole.widget.aiPreviewMini.working', 'Working…')\n : t('bigconsole.widget.aiPreviewMini.done', 'Done')\n }\n error={error}\n meta={\n <PreviewMeta\n state={isRunning ? 'running' : error ? 'stopped' : 'idle'}\n stateLabel={\n isRunning\n ? t('bigconsole.widget.aiPreviewMini.live', 'Live')\n : error\n ? t('bigconsole.widget.aiPreviewMini.stopped', 'Stopped')\n : null\n }\n updatedLabel={t('bigconsole.widget.aiPreviewMini.updated', 'updated')}\n updatedAt={updatedAt}\n ago={(seconds) => formatAgo(seconds, t)}\n />\n }\n labels={previewLabels(t)}\n />\n </div>\n );\n}\n\n/**\n * ★ Re-exported so consumers keep ONE import path, while the RULE itself lives\n * in a module that loads without a DOM. Importing THIS file pulls fe-libs'\n * assistant barrel, which touches `document` at load time — so the decision had\n * to move out before it could be tested. See `previewRunAction.ts`.\n *\n * ★ Local bindings, not `export … from`: a bare re-export creates no local name,\n * so `applyPreviewEvent` below would be calling something this module does not\n * actually have.\n */\nexport { previewRunAction };\nexport type { PreviewRunAction };\n\n/**\n * The runs this controller has adopted — Build runs only.\n *\n * ★ Module-level, like the handler itself: it must outlive the controller memo\n * and every re-render, and nothing renders it.\n */\nconst adoptedRuns = new Set<string>();\n\n/**\n * Forward one bridge event to the run store.\n *\n * ★ Module-level and imperative (`getState()`), NOT a hook: the bridge calls\n * this from an effect, and reading the store through a selector here would make\n * the controller depend on state it never renders.\n */\nfunction applyPreviewEvent(event: AssistantPreviewEvent): void {\n /**\n * ★ The mode of THIS RUN's conversation, never the panel's. The panel's `mode`\n * is whatever the user last selected, which need not be the mode the run in\n * flight was started in. Falls back to the panel mode only when the\n * conversation is gone.\n */\n const assistant = useAssistantStore.getState();\n const modeOfRun = assistant.conversations.find((c) => c.id === event.run.conversationId)?.mode ?? assistant.mode;\n\n if (previewRunAction(event.kind, event.run.runId, modeOfRun, adoptedRuns) === 'ignore') {\n return;\n }\n\n const store = useAssistantRunStore.getState();\n\n if (event.kind === 'start') {\n adoptedRuns.add(event.run.runId);\n /**\n * ★★ THE PROMPT IS NOT AVAILABLE, and that is a real gap rather than an\n * oversight. `AssistantPreviewRun` carries `runId`, `conversationId`,\n * `messageId`, `workspaceId` and `actorId` — no turn text — so the store's\n * `prompt` (which only feeds an informational line) is left empty. The\n * transport could pass it because it owned the turn; the bridge cannot,\n * because it observes one. Everything the preview actually draws comes from\n * `applyProgress`, so nothing visible depends on this.\n */\n store.startRun('');\n return;\n }\n\n if (event.kind === 'snapshot') {\n /**\n * ★ `content` is the PROSE with the tool-activity lines already removed by\n * fe-libs. That is what this store wants: it harvests entity links, raw\n * mutation ids and the newest `⏳ …` caption out of the agent's narration,\n * all of which live in the prose. The structured `event.steps` describe TOOL\n * calls, which are a different rail from BigConsole's four build steps.\n */\n store.applyProgress(event.content);\n return;\n }\n\n /**\n * ★★ `abandoned` is neither success nor failure — it is a run the bridge can\n * no longer follow (cancelled, superseded, or navigated away from mid-turn),\n * and the turn may well still be executing. Passing its text as an error would\n * paint a red \"Build stopped\" banner over a build that is very likely still\n * running; passing nothing at all would leave the spinner going forever. So it\n * ends the run WITHOUT an error, which stops the spinner and leaves every step\n * already marked `done` exactly as it was.\n */\n /**\n * ★ Released here, and only here. Run ids are `conversationId:messageId`, so\n * they are never reused and this is a leak rather than a correctness bug — but\n * an unbounded set that grows for the life of the tab is still a leak, and\n * `end` is the one event guaranteed to arrive for every adopted run\n * (`abandoned` included — that outcome exists precisely so a run the bridge\n * can no longer follow still terminates).\n */\n adoptedRuns.delete(event.run.runId);\n store.finishRun(event.outcome === 'error' ? (event.error ?? 'The build stopped.') : null);\n}\n\n/**\n * BigConsole's preview controller, stable across renders.\n *\n * ★★★ THE MEMO IS LOAD-BEARING, not tidiness. fe-libs' bridge deliberately does\n * NOT reset its run state when the controller identity changes — precisely so\n * that the natural `preview={{ render, onEvent }}` object literal does not\n * re-announce `start` on every poll tick. The cost of that choice is that\n * REPLACING a controller mid-run leaves the newcomer without a `start` for the\n * run already in flight. An empty dependency list is what guarantees one\n * controller for the life of the mount.\n *\n * ★ `label` is the overlay's toggle text. Plain English here, like FluidGrids':\n * the seam's user-facing strings are translated in BC5 along with the rest of\n * the bundle, and a half-translated toggle beside untranslated mode labels is\n * worse than a consistently English one.\n */\nexport function useBigConsoleAssistantPreview(): AssistantPreviewController {\n return useMemo<AssistantPreviewController>(\n () => ({\n label: 'Build preview',\n render: () => <BigConsoleAssistantPreviewSurface />,\n onEvent: applyPreviewEvent,\n }),\n []\n );\n}\n"],"mappings":";;;;;;;;AAwDA,SAAS,IAAoC;CAC3C,IAAM,EAAE,SAAM,GAAS,EACjB,IAAY,GAAsB,MAAU,EAAM,UAAU,EAC5D,IAAQ,GAAsB,MAAU,EAAM,MAAM,EACpD,IAAU,GAAsB,MAAU,EAAM,QAAQ,EACxD,IAAQ,GAAsB,MAAU,EAAM,MAAM,EACpD,IAAY,GAAsB,MAAU,EAAM,UAAU,EAE5D,IAAoC,EAAc,KAAK,OAAS;EACpE,IAAI;EACJ,OAAO,EAAE,EAAW,GAAK,KAAK,EAAW,GAAK,SAAS;EACvD,QAAQ,EAAM;EACf,EAAE;AAEH,QACE,kBAAC,OAAD;EAAK,eAAY;EAAmB,gBAAa;EAAY,WAAU;YACrE,kBAAC,GAAD;GACE,OAAO,EAAE,yCAAyC,aAAa;GAC/D,OAAO;GAMP,SACE,IACI,KAAW,EAAE,2CAA2C,WAAW,GACnE,EAAE,wCAAwC,OAAO;GAEhD;GACP,MACE,kBAAC,GAAD;IACE,OAAO,IAAY,YAAY,IAAQ,YAAY;IACnD,YACE,IACI,EAAE,wCAAwC,OAAO,GACjD,IACE,EAAE,2CAA2C,UAAU,GACvD;IAER,cAAc,EAAE,2CAA2C,UAAU;IAC1D;IACX,MAAM,MAAY,EAAU,GAAS,EAAE;IACvC,CAAA;GAEJ,QAAQ,EAAc,EAAE;GACxB,CAAA;EACE,CAAA;;AAuBV,IAAM,oBAAc,IAAI,KAAa;AASrC,SAAS,EAAkB,GAAoC;CAO7D,IAAM,IAAY,EAAkB,UAAU,EACxC,IAAY,EAAU,cAAc,MAAM,MAAM,EAAE,OAAO,EAAM,IAAI,eAAe,EAAE,QAAQ,EAAU;AAE5G,KAAI,EAAiB,EAAM,MAAM,EAAM,IAAI,OAAO,GAAW,EAAY,KAAK,SAC5E;CAGF,IAAM,IAAQ,EAAqB,UAAU;AAE7C,KAAI,EAAM,SAAS,SAAS;AAW1B,EAVA,EAAY,IAAI,EAAM,IAAI,MAAM,EAUhC,EAAM,SAAS,GAAG;AAClB;;AAGF,KAAI,EAAM,SAAS,YAAY;AAQ7B,IAAM,cAAc,EAAM,QAAQ;AAClC;;AAqBF,CADA,EAAY,OAAO,EAAM,IAAI,MAAM,EACnC,EAAM,UAAU,EAAM,YAAY,UAAW,EAAM,SAAS,uBAAwB,KAAK;;AAmB3F,SAAgB,IAA4D;AAC1E,QAAO,SACE;EACL,OAAO;EACP,cAAc,kBAAC,GAAD,EAAqC,CAAA;EACnD,SAAS;EACV,GACD,EAAE,CACH"}
@@ -0,0 +1,9 @@
1
+ import { BUILD as e } from "./modes.js";
2
+ //#region src/bigconsole/assistant/previewRunAction.ts
3
+ function t(t, n, r, i) {
4
+ return t === "start" ? r === e.id ? "start" : "ignore" : i.has(n) ? t === "snapshot" ? "progress" : "end" : "ignore";
5
+ }
6
+ //#endregion
7
+ export { t as previewRunAction };
8
+
9
+ //# sourceMappingURL=previewRunAction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"previewRunAction.js","names":[],"sources":["../../../src/bigconsole/assistant/previewRunAction.ts"],"sourcesContent":["/**\n * Which bridge events may drive BigConsole's BUILD preview.\n *\n * ★★★ A MODULE OF ITS OWN, AND THAT IS NOT ORGANISATION — IT IS WHAT MAKES THIS\n * TESTABLE AT ALL.\n *\n * This rule started life inside `previewController.tsx`, next to the React\n * surface, described as \"pure, and separate from the wiring\". It was not\n * separate: that file imports `useAssistantStore` from fe-libs' assistant\n * BARREL, and the barrel pulls modules that touch `document` at load time. So\n * importing it from a `bun test` (no DOM) threw `ReferenceError: document is not\n * defined` before a single assertion ran.\n *\n * Here the only runtime import is `./modes`, whose own fe-libs import is\n * type-only and therefore erased. That keeps this file loadable anywhere — which\n * is the whole point of splitting a decision out from its wiring.\n */\nimport type { AssistantPreviewEvent } from '@burdenoff/fe-libs/shared/assistant';\n\nimport { BUILD } from './modes';\n\n/** What, if anything, a bridge event should do to the run store. */\nexport type PreviewRunAction = 'start' | 'progress' | 'end' | 'ignore';\n\n/**\n * ★★★ ASK TURNS MUST NOT DRIVE THE BUILD PREVIEW (Codex review, PR #154).\n *\n * BC3 is what introduces a second mode. Before it, `api-calls` was the ONLY mode\n * BigConsole offered, so forwarding every event unconditionally was correct.\n * Now the controller is registered panel-wide while Ask is read-only, and an\n * unfiltered `start` would mark the build store running, open the preview and\n * light the DataSink step for an ordinary question. Worse, `applyProgress`\n * harvests entity links out of PROSE — so an Ask answer that merely mentions\n * `/bigconsole/dashboards/abc` would be recorded as an entity a build had just\n * created. Every member who only has Ask would see that, on every question.\n *\n * ★★ ADOPTION IS KEYED ON THE RUN, and that is the subtle half. Re-checking \"is\n * the mode Build right now?\" on every event looks equivalent and is not: a run\n * that STARTS in Build whose conversation is switched to Ask mid-run would have\n * its `end` filtered out, leaving the store spinning on a build that had already\n * finished. The mode is consulted ONCE, at `start`; after that the run is\n * followed to its end — the same way fe-libs' own bridge keys runs.\n */\nexport function previewRunAction(\n kind: AssistantPreviewEvent['kind'],\n runId: string,\n modeOfRun: string,\n adoptedRuns: ReadonlySet<string>\n): PreviewRunAction {\n if (kind === 'start') return modeOfRun === BUILD.id ? 'start' : 'ignore';\n if (!adoptedRuns.has(runId)) return 'ignore';\n return kind === 'snapshot' ? 'progress' : 'end';\n}\n"],"mappings":";;AA2CA,SAAgB,EACd,GACA,GACA,GACA,GACkB;AAGlB,QAFI,MAAS,UAAgB,MAAc,EAAM,KAAK,UAAU,WAC3D,EAAY,IAAI,EAAM,GACpB,MAAS,aAAa,aAAa,QADN"}
@@ -0,0 +1,6 @@
1
+ //#region src/bigconsole/assistant/product.ts
2
+ var e = "bigconsole";
3
+ //#endregion
4
+ export { e as BIGCONSOLE_PRODUCT };
5
+
6
+ //# sourceMappingURL=product.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product.js","names":[],"sources":["../../../src/bigconsole/assistant/product.ts"],"sourcesContent":["/**\n * BigConsole's product id, in one place.\n *\n * ★★ This single string answers three different questions, and they must never\n * be allowed to drift apart:\n *\n * 1. `AI_ASSISTANT_PRODUCT` — the env the sandbox is created with, which\n * selects the agent's product guide.\n * 2. `metadata.assistantProduct` — the reuse stamp (BOFF-7312). A sandbox\n * stamped with another product's id is never reused for this one. That is\n * not bookkeeping: adopting another product's container inherits its\n * persona and docs corpus, which is the production incident pinned in\n * fe-libs' `sandboxProductIsolation.test.ts` — a HealthyBowl user greeted\n * by \"I am the AI assistant for the Burdenoff platform\".\n * 3. The i18n namespace prefix: every shared-panel string resolves as\n * `bigconsole.assistant.<relative key>`.\n *\n * A second literal spelled the same way would satisfy all three today and\n * silently stop doing so the moment one of them is edited.\n * `assistantSeamWiring.test.ts` asserts that no other production file in this\n * folder contains the bare quoted literal.\n *\n * ★ Deliberately a module with NO other imports: the id is read by the seam, by\n * the i18n namespace and by the sandbox stamp, and none of them should have to\n * load anything else to get it.\n */\nexport const BIGCONSOLE_PRODUCT = 'bigconsole';\n"],"mappings":";AA0BA,IAAa,IAAqB"}
@@ -0,0 +1,15 @@
1
+ import { useWorkspaceWriteAccess as e } from "@burdenoff/fe-libs/shared/assistant/useWorkspaceWriteAccess";
2
+ //#region src/bigconsole/assistant/useWorkspaceWriteAccess.ts
3
+ var t = [
4
+ "bigconsole-datasink",
5
+ "bigconsole-dashboard",
6
+ "bigconsole-parser",
7
+ "bigconsole-widget"
8
+ ];
9
+ function n(n, r) {
10
+ return e(n, r, t);
11
+ }
12
+ //#endregion
13
+ export { t as PROBE_RESOURCES, n as useWorkspaceWriteAccess };
14
+
15
+ //# sourceMappingURL=useWorkspaceWriteAccess.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useWorkspaceWriteAccess.js","names":[],"sources":["../../../src/bigconsole/assistant/useWorkspaceWriteAccess.ts"],"sourcesContent":["/**\n * BigConsole's write-access probe.\n *\n * ★ The mechanism lives in `fe-libs/shared/assistant`; the RESOURCE TYPES stay\n * here, because which resources prove \"this user can write in this workspace\"\n * is the product's answer. The shared hook takes them with no default, so\n * another product cannot accidentally probe BigConsole's dashboards — or the\n * other way round.\n */\nimport { useWorkspaceWriteAccess as useShared } from '@burdenoff/fe-libs/shared/assistant/useWorkspaceWriteAccess';\nexport type { WorkspaceWriteAccess } from '@burdenoff/fe-libs/shared/assistant/useWorkspaceWriteAccess';\n\n/**\n * The resources a Build turn actually writes.\n *\n * ★★ READ OUT OF THE BACKEND, NOT INVENTED. These are the exact `resource:`\n * values on `@rbac` directives in `wspace-bigconsole-svc`, and they are\n * HYPHENATED — `bigconsole-datasink`, not `bigconsole_datasink`.\n *\n * ★★★ Do NOT copy HealthyBowl's `healthybowl_crop` underscore convention.\n * VibeControls did exactly that, probed `vibecontrols_vibe` and friends, matched\n * no resource type that exists, and so silently default-denied — its composer\n * warning said nothing about the permissions a write turn actually needs, and\n * nothing failed loudly enough to notice.\n *\n * ★ Scoped to the four the assistant ISSUES, not all 17 the service defines.\n * The agent's build path is `createDataSink` → `createBigConsoleDashboard` →\n * `createParser` → `createBigConsoleWidget`; probing `bigconsole-webhook` or\n * `bigconsole-rls-policy`, which the assistant never calls, would make\n * \"granted\" mean less than it does. All four carry `action: \"create\"` @rbac\n * entries, which is the only action the shared hook asks about.\n *\n * ★ Advisory, never a gate: it warns in the composer BEFORE a write turn is\n * spent rather than after, and self-corrects once workspace RBAC rows are\n * seeded. Gating on it would remove Build from the product entirely — at\n * workspace scope in prod the honest answer today is \"denied\" for everyone, org\n * owners included, because no roles are provisioned there yet.\n */\nexport const PROBE_RESOURCES = [\n 'bigconsole-datasink',\n 'bigconsole-dashboard',\n 'bigconsole-parser',\n 'bigconsole-widget',\n] as const;\n\n/**\n * ★ `PROBE_RESOURCES` is a module-level const, never an inline array. The shared\n * hook's effect deps are `[actorId, workspaceId]` only — `probeResources` is not\n * among them — so an array literal created during render would silently pin the\n * first render's list forever.\n */\nexport function useWorkspaceWriteAccess(actorId: string | null, workspaceId: string | null) {\n return useShared(actorId, workspaceId, PROBE_RESOURCES);\n}\n"],"mappings":";;AAsCA,IAAa,IAAkB;CAC7B;CACA;CACA;CACA;CACD;AAQD,SAAgB,EAAwB,GAAwB,GAA4B;AAC1F,QAAO,EAAU,GAAS,GAAa,EAAgB"}
@@ -1,115 +1,177 @@
1
- import e from "../datasink/DataSinkTableViewer.js";
2
- import t from "../../hooks/useWidgetOperations.js";
3
- import n from "../../hooks/useDataSinkOperations.js";
4
- import r from "../../hooks/useParserOperations.js";
5
- import i from "../widgets/WidgetWrapper.js";
6
- import a from "../DrilldownDashboardRenderer.js";
7
- import { useEffect as o, useRef as s, useState as c } from "react";
8
- import { Spinner as l } from "@burdenoff/fe-libs/ui";
9
- import { jsx as u, jsxs as d } from "react/jsx-runtime";
1
+ import { GetParserDocument as e, ListWidgetsByDashboardDocument as t } from "../../../generated/wspace-operations.js";
2
+ import n from "../datasink/DataSinkTableViewer.js";
3
+ import { normalizeWidget as r } from "../../hooks/useWidgetOperations.js";
4
+ import i from "../../hooks/useDataSinkOperations.js";
5
+ import a from "../widgets/WidgetWrapper.js";
6
+ import o from "../DrilldownDashboardRenderer.js";
7
+ import { useEffect as s, useRef as c, useState as l } from "react";
8
+ import { useApolloClient as u } from "@apollo/client/react";
9
+ import { Spinner as d } from "@burdenoff/fe-libs/ui";
10
+ import { jsx as f, jsxs as p } from "react/jsx-runtime";
10
11
  //#region src/bigconsole/components/preview/AiPreviewContent.tsx
11
- var f = 4e3, p = 6e4, m = "flex min-h-[6rem] min-w-0 items-center justify-center gap-2 rounded-card bg-bg-sunken px-3 py-6 text-center text-sm text-text-muted";
12
- function h({ label: e }) {
13
- return /* @__PURE__ */ d("div", {
14
- className: m,
15
- children: [/* @__PURE__ */ u(l, { className: "size-4 shrink-0" }), e]
12
+ var m = 4e3, h = 6e4, g = "flex min-h-[6rem] min-w-0 items-center justify-center gap-2 rounded-card bg-bg-sunken px-3 py-6 text-center text-sm text-text-muted";
13
+ function _({ label: e, testId: t }) {
14
+ return /* @__PURE__ */ p("div", {
15
+ className: g,
16
+ "data-testid": t,
17
+ children: [/* @__PURE__ */ f(d, { className: "size-4 shrink-0" }), e]
16
18
  });
17
19
  }
18
- function g({ text: e }) {
19
- return /* @__PURE__ */ u("p", {
20
- className: m,
20
+ function v({ text: e, testId: t }) {
21
+ return /* @__PURE__ */ f("p", {
22
+ className: g,
23
+ "data-testid": t,
21
24
  children: e
22
25
  });
23
26
  }
24
- function _(e, t, n) {
25
- let [r, i] = c(null), [a, l] = c(!0), u = s(e);
26
- return u.current = e, o(() => {
27
- if (!t) {
28
- l(!1);
29
- return;
30
- }
27
+ function y({ text: e, testId: t }) {
28
+ return /* @__PURE__ */ f("p", {
29
+ className: `${g} text-status-error-text`,
30
+ "data-testid": t,
31
+ children: e
32
+ });
33
+ }
34
+ function b(e, t, n) {
35
+ let [r, i] = l(null), [a, o] = l(!!t), [u, d] = l(null), [f, p] = l(t), g = c(e);
36
+ return g.current = e, t !== f && (p(t), i(null), d(null), o(!!t)), s(() => {
37
+ if (!t) return;
31
38
  let e = !1, r = Date.now(), a = async () => {
32
- let t = await u.current();
33
- e || (t != null && i(t), l(!1));
39
+ try {
40
+ let t = await g.current();
41
+ if (e) return;
42
+ t != null && i(t), d(null);
43
+ } catch (t) {
44
+ if (e) return;
45
+ d(t instanceof Error ? t : /* @__PURE__ */ Error("Failed to load preview content"));
46
+ } finally {
47
+ e || o(!1);
48
+ }
34
49
  };
35
50
  a();
36
- let o = window.setInterval(() => {
37
- !n || Date.now() - r > p || typeof document < "u" && document.visibilityState !== "visible" || a();
38
- }, f);
51
+ let s = window.setInterval(() => {
52
+ !n || Date.now() - r > h || typeof document < "u" && document.visibilityState !== "visible" || a();
53
+ }, m);
39
54
  return () => {
40
- e = !0, window.clearInterval(o);
55
+ e = !0, window.clearInterval(s);
41
56
  };
42
57
  }, [t, n]), {
43
58
  data: r,
44
- loading: a
59
+ loading: a,
60
+ error: u
45
61
  };
46
62
  }
47
- function v({ id: t, live: r }) {
48
- let { getDataSinkData: i } = n({ skipFetch: !0 }), { data: a, loading: o } = _(async () => {
49
- let e = await i({ id: t }, { limit: 50 });
50
- return e ? e.raw : null;
51
- }, t, r);
52
- return t ? o ? /* @__PURE__ */ u(h, { label: "Loading the data sink’s data…" }) : /* @__PURE__ */ u("div", {
63
+ function x({ id: e, live: t }) {
64
+ let { getDataSinkData: r } = i({ skipFetch: !0 }), { data: a, loading: o, error: s } = b(async () => {
65
+ let t = await r({ id: e }, { limit: 50 });
66
+ return t ? t.raw : null;
67
+ }, e, t);
68
+ return e ? s ? /* @__PURE__ */ f(y, {
69
+ text: "Could not load the data sink's rows.",
70
+ testId: "ai-preview-datasink-error"
71
+ }) : o ? /* @__PURE__ */ f(_, {
72
+ label: "Loading the data sink’s data…",
73
+ testId: "ai-preview-datasink-loading"
74
+ }) : /* @__PURE__ */ f("div", {
53
75
  "data-testid": "ai-preview-content-datasink",
54
76
  className: "min-w-0",
55
- children: /* @__PURE__ */ u(e, {
77
+ children: /* @__PURE__ */ f(n, {
56
78
  data: a,
57
79
  maxHeight: "300px"
58
80
  })
59
- }) : /* @__PURE__ */ u(g, { text: "Waiting for the data sink to be created…" });
81
+ }) : /* @__PURE__ */ f(v, {
82
+ text: "Waiting for the data sink to be created…",
83
+ testId: "ai-preview-datasink-waiting"
84
+ });
60
85
  }
61
- function y({ dashboardId: e }) {
62
- return e ? /* @__PURE__ */ u("div", {
86
+ function S({ dashboardId: e }) {
87
+ return e ? /* @__PURE__ */ f("div", {
63
88
  "data-testid": "ai-preview-content-dashboard",
64
89
  className: "min-w-0",
65
- children: /* @__PURE__ */ u(a, {
90
+ children: /* @__PURE__ */ f(o, {
66
91
  dashboardId: e,
67
92
  params: {}
68
93
  })
69
- }) : /* @__PURE__ */ u(g, { text: "Waiting for the dashboard to be created…" });
94
+ }) : /* @__PURE__ */ f(v, {
95
+ text: "Waiting for the dashboard to be created…",
96
+ testId: "ai-preview-dashboard-waiting"
97
+ });
70
98
  }
71
- function b({ parserId: t, live: n }) {
72
- let { executeParser: i } = r({ skipFetch: !0 }), { data: a, loading: o } = _(async () => {
73
- let e = await i(t ?? "");
74
- return e ? e.output : null;
75
- }, t, n);
76
- return t ? o ? /* @__PURE__ */ u(h, { label: "Running the parser…" }) : /* @__PURE__ */ u("div", {
99
+ function C({ parserId: t, live: r }) {
100
+ let a = u(), { getDataSinkData: o } = i({ skipFetch: !0 }), { data: s, loading: c, error: l } = b(async () => {
101
+ if (!t) return null;
102
+ let n = (await a.query({
103
+ query: e,
104
+ variables: { id: t },
105
+ fetchPolicy: "network-only"
106
+ })).data?.parser?.outputKey;
107
+ if (!n) return null;
108
+ let r = await o({ key: n }, { limit: 50 });
109
+ return r ? r.raw : null;
110
+ }, t, r);
111
+ return t ? l ? /* @__PURE__ */ f(y, {
112
+ text: "Could not read the parser's output.",
113
+ testId: "ai-preview-parser-error"
114
+ }) : c ? /* @__PURE__ */ f(_, {
115
+ label: "Loading the parser’s output…",
116
+ testId: "ai-preview-parser-loading"
117
+ }) : /* @__PURE__ */ f("div", {
77
118
  "data-testid": "ai-preview-content-parser",
78
119
  className: "min-w-0",
79
- children: /* @__PURE__ */ u(e, {
80
- data: a,
120
+ children: /* @__PURE__ */ f(n, {
121
+ data: s,
81
122
  maxHeight: "280px"
82
123
  })
83
- }) : /* @__PURE__ */ u(g, { text: "No parser in this build — the rows were already shaped." });
124
+ }) : /* @__PURE__ */ f(v, {
125
+ text: "No parser in this build — the rows were already shaped.",
126
+ testId: "ai-preview-parser-waiting"
127
+ });
84
128
  }
85
- function x(e) {
129
+ function w(e) {
86
130
  let t = e.toUpperCase();
87
131
  return t.includes("METRIC") || t.includes("KPI") || t.includes("CARD") || t.includes("STAT") ? "min(160px, 40vh)" : t.includes("TABLE") || t.includes("LIST") ? "min(280px, 50vh)" : "min(320px, 50vh)";
88
132
  }
89
- function S({ dashboardId: e, live: n }) {
90
- let { widgets: r, loading: a, refetch: c } = t(void 0, e, { skipFetch: !e }), l = s(c);
91
- return l.current = c, o(() => {
92
- if (!e) return;
93
- let t = Date.now(), r = window.setInterval(() => {
94
- !n || Date.now() - t > p || typeof document < "u" && document.visibilityState !== "visible" || l.current();
95
- }, f);
96
- return () => window.clearInterval(r);
97
- }, [e, n]), e ? a && r.length === 0 ? /* @__PURE__ */ u(h, { label: "Loading widgets…" }) : r.length === 0 ? /* @__PURE__ */ u(g, { text: "No widgets on this dashboard yet." }) : /* @__PURE__ */ u("div", {
133
+ function T(e, n) {
134
+ let i = u(), { data: a, loading: o, error: s } = b(async () => e ? ((await i.query({
135
+ query: t,
136
+ variables: { dashboardId: e },
137
+ fetchPolicy: "network-only"
138
+ })).data?.listWidgetsByDashboard || []).map(r) : null, e, n);
139
+ return {
140
+ widgets: a ?? [],
141
+ loading: o,
142
+ error: s
143
+ };
144
+ }
145
+ function E({ dashboardId: e, live: t }) {
146
+ let { widgets: n, loading: r, error: i } = T(e, t);
147
+ return e ? i ? /* @__PURE__ */ f(y, {
148
+ text: "Could not load this dashboard's widgets.",
149
+ testId: "ai-preview-widget-error"
150
+ }) : r && n.length === 0 ? /* @__PURE__ */ f(_, {
151
+ label: "Loading widgets…",
152
+ testId: "ai-preview-widget-loading"
153
+ }) : n.length === 0 ? /* @__PURE__ */ f(v, {
154
+ text: "No widgets on this dashboard yet.",
155
+ testId: "ai-preview-widget-empty"
156
+ }) : /* @__PURE__ */ f("div", {
98
157
  "data-testid": "ai-preview-content-widget",
99
158
  className: "flex min-w-0 flex-col gap-list-gap",
100
- children: r.map((e) => /* @__PURE__ */ u("div", {
159
+ children: n.map((e) => /* @__PURE__ */ f("div", {
101
160
  className: "min-w-0 overflow-hidden rounded-card border border-[var(--color-border-subtle)] bg-bg-canvas",
102
- style: { height: x(e.type) },
103
- children: /* @__PURE__ */ u(i, {
161
+ style: { height: w(e.type) },
162
+ children: /* @__PURE__ */ f(a, {
104
163
  widget: e,
105
164
  isLoading: !1,
106
165
  error: null,
107
166
  filterValues: {}
108
167
  })
109
168
  }, e.id))
110
- }) : /* @__PURE__ */ u(g, { text: "Waiting for the dashboard — widgets live on it." });
169
+ }) : /* @__PURE__ */ f(v, {
170
+ text: "Waiting for the dashboard — widgets live on it.",
171
+ testId: "ai-preview-widget-waiting"
172
+ });
111
173
  }
112
174
  //#endregion
113
- export { y as DashboardContent, v as DataSinkContent, b as ParserContent, S as WidgetsContent };
175
+ export { S as DashboardContent, x as DataSinkContent, C as ParserContent, E as WidgetsContent };
114
176
 
115
177
  //# sourceMappingURL=AiPreviewContent.js.map