@burdenoff/microfe-bigconsole 2026.929.2 → 2026.930.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bigconsole/assistant/assistantRunPersistence.js +111 -0
- package/dist/bigconsole/assistant/assistantRunPersistence.js.map +1 -0
- package/dist/bigconsole/assistant/assistantRunStore.js +87 -48
- package/dist/bigconsole/assistant/assistantRunStore.js.map +1 -1
- package/dist/bigconsole/assistant/previewController.js +19 -50
- package/dist/bigconsole/assistant/previewController.js.map +1 -1
- package/dist/bigconsole/assistant/useAssistantRunWorkspace.js +14 -0
- package/dist/bigconsole/assistant/useAssistantRunWorkspace.js.map +1 -0
- package/dist/bigconsole/components/DrilldownDashboardRenderer.js +74 -50
- package/dist/bigconsole/components/DrilldownDashboardRenderer.js.map +1 -1
- package/dist/bigconsole/components/dashboard/DrilldownBreadcrumb.js +9 -4
- package/dist/bigconsole/components/dashboard/DrilldownBreadcrumb.js.map +1 -1
- package/dist/bigconsole/components/embed/ReadOnlyDashboardRenderer.js +68 -40
- package/dist/bigconsole/components/embed/ReadOnlyDashboardRenderer.js.map +1 -1
- package/dist/bigconsole/components/embed/drilldown.js +26 -0
- package/dist/bigconsole/components/embed/drilldown.js.map +1 -0
- package/dist/bigconsole/components/embed/index.js +4 -3
- package/dist/bigconsole/components/embed/types.js.map +1 -1
- package/dist/bigconsole/components/preview/AiPreviewMiniPanel.js +32 -30
- package/dist/bigconsole/components/preview/AiPreviewMiniPanel.js.map +1 -1
- package/dist/bigconsole/components/preview/AiPreviewPanel.js +38 -36
- package/dist/bigconsole/components/preview/AiPreviewPanel.js.map +1 -1
- package/dist/bigconsole/components/preview/previewShared.js +1 -1
- package/dist/bigconsole/components/preview/previewShared.js.map +1 -1
- package/dist/bigconsole/components/widgets/DrilldownConfigPanel.js +307 -233
- package/dist/bigconsole/components/widgets/DrilldownConfigPanel.js.map +1 -1
- package/dist/bigconsole/components/widgets/PropertiesPanel.js +230 -220
- package/dist/bigconsole/components/widgets/PropertiesPanel.js.map +1 -1
- package/dist/bigconsole/components/widgets/WidgetWrapper.js +18 -19
- package/dist/bigconsole/components/widgets/WidgetWrapper.js.map +1 -1
- package/dist/bigconsole/context/BigConsoleContext.js +4 -1
- package/dist/bigconsole/context/BigConsoleContext.js.map +1 -1
- package/dist/bigconsole/hooks/useActivePage.js +25 -0
- package/dist/bigconsole/hooks/useActivePage.js.map +1 -0
- package/dist/bigconsole/hooks/useDrilldown.js +118 -71
- package/dist/bigconsole/hooks/useDrilldown.js.map +1 -1
- package/dist/bigconsole/hooks/useDrilldownOperations.js +32 -26
- package/dist/bigconsole/hooks/useDrilldownOperations.js.map +1 -1
- package/dist/bigconsole/hooks/useFilterUrlSync.js +1 -1
- package/dist/bigconsole/hooks/useFilterUrlSync.js.map +1 -1
- package/dist/bigconsole/pages/DashboardBuilderPage.js +190 -185
- package/dist/bigconsole/pages/DashboardBuilderPage.js.map +1 -1
- package/dist/bigconsole/pages/DashboardViewPage.js +224 -217
- package/dist/bigconsole/pages/DashboardViewPage.js.map +1 -1
- package/dist/generated/wspace-operations.js +132 -86
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/generated/wspace-types.js.map +1 -1
- package/package.json +3 -3
- package/dist/bigconsole/assistant/useAssistantPanelShowsPreview.js +0 -25
- package/dist/bigconsole/assistant/useAssistantPanelShowsPreview.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReadOnlyDashboardRenderer.js","names":[],"sources":["../../../../src/bigconsole/components/embed/ReadOnlyDashboardRenderer.tsx"],"sourcesContent":["/**\n * Read-only embedded dashboard renderer (BOFF-2986).\n *\n * Renders the sanitized render model's pages + widgets on a simple responsive\n * grid. It is STRICTLY view-only: no builder toolbar, edit toggle, sharing,\n * save, palette, inspector or comments — those authenticated affordances do not\n * exist in this component tree at all.\n *\n * Every embed carries the \"Powered by BigConsole\" footer. Burdenoff does no\n * white-labelling, so there is deliberately no model field, policy option or\n * URL parameter that hides it.\n */\n\nimport { useState } from 'react';\nimport type { EmbedCapabilities, EmbedPageModel, EmbedRenderModel } from './types';\nimport { EmbedWidgetRenderer } from './EmbedWidgetRenderer';\nimport { EmbedErrorBoundary } from './EmbedErrorBoundary';\n\nconst GRID_COLUMNS = 12;\n\ninterface Props {\n readonly model: EmbedRenderModel;\n}\n\nfunction PageGrid({ page, capabilities }: { page: EmbedPageModel; capabilities: EmbedCapabilities }) {\n if (page.widgets.length === 0) {\n return (\n <div className=\"flex h-40 items-center justify-center text-sm text-text-secondary\">This page has no widgets.</div>\n );\n }\n return (\n <div\n className=\"grid w-full gap-4 p-4\"\n style={{ gridTemplateColumns: `repeat(${GRID_COLUMNS}, minmax(0, 1fr))`, gridAutoRows: 'minmax(80px, auto)' }}\n >\n {page.widgets.map((widget) => (\n <div\n key={widget.id}\n className=\"overflow-hidden rounded-lg border border-border-default bg-bg-surface shadow-sm\"\n style={{\n // Clamp both ends: negative/oversized coordinates from malformed\n // model data must never produce an invalid CSS grid line.\n gridColumn: `${Math.min(Math.max(widget.layout.x, 0), GRID_COLUMNS - 1) + 1} / span ${Math.min(\n Math.max(widget.layout.w, 1),\n GRID_COLUMNS\n )}`,\n gridRow: `${Math.max(widget.layout.y, 0) + 1} / span ${Math.max(widget.layout.h, 1)}`,\n }}\n >\n <EmbedWidgetRenderer widget={widget} capabilities={capabilities} />\n </div>\n ))}\n </div>\n );\n}\n\n/** \"Powered by BigConsole\" attribution footer, rendered on every embed (never optional). */\nfunction BrandingFooter() {\n return (\n <div className=\"flex shrink-0 items-center justify-end border-t border-border-default bg-bg-surface px-4 py-2\">\n <a\n href=\"https://bigconsole.com\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-xs text-text-tertiary transition-colors hover:text-text-secondary\"\n >\n Powered by <span className=\"font-semibold text-text-secondary\">BigConsole</span>\n </a>\n </div>\n );\n}\n\nexport function ReadOnlyDashboardRenderer({ model }: Props) {\n const [activePageIndex, setActivePageIndex] = useState(0);\n const pages = model.pages;\n const presentation = model.presentation;\n // Only show a header when the owner enabled it AND there's a non-empty title.\n const headerTitle = presentation?.showTitle ? (presentation.title ?? '').trim() : '';\n const activePage = pages[Math.min(activePageIndex, pages.length - 1)];\n\n if (!activePage) {\n return (\n <div className=\"flex h-full flex-col bg-bg-canvas\">\n {headerTitle && (\n <header className=\"shrink-0 border-b border-border-default bg-bg-surface px-4 py-3\">\n <h1 className=\"truncate text-base font-semibold text-text-primary\">{headerTitle}</h1>\n </header>\n )}\n <div className=\"flex flex-1 items-center justify-center text-sm text-text-secondary\">\n This dashboard has no pages.\n </div>\n <BrandingFooter />\n </div>\n );\n }\n\n return (\n <EmbedErrorBoundary label=\"dashboard\">\n <div className=\"flex h-full w-full flex-col bg-bg-canvas\">\n {headerTitle && (\n <header className=\"shrink-0 border-b border-border-default bg-bg-surface px-4 py-3\">\n <h1 className=\"truncate text-base font-semibold text-text-primary\">{headerTitle}</h1>\n </header>\n )}\n {pages.length > 1 && (\n <div role=\"tablist\" className=\"flex gap-1 border-b border-border-default bg-bg-surface px-2\">\n {pages.map((page, i) => (\n <button\n key={page.id}\n role=\"tab\"\n aria-selected={i === activePageIndex}\n onClick={() => setActivePageIndex(i)}\n className={\n i === activePageIndex\n ? '-mb-px border-b-2 border-action-primary-bg px-3 py-2 text-sm font-medium text-text-primary'\n : 'border-b-2 border-transparent px-3 py-2 text-sm text-text-secondary hover:text-text-primary'\n }\n >\n {page.title ?? `Page ${i + 1}`}\n </button>\n ))}\n </div>\n )}\n <div className=\"min-h-0 flex-1 overflow-auto\">\n <PageGrid page={activePage} capabilities={model.capabilities} />\n </div>\n <BrandingFooter />\n </div>\n </EmbedErrorBoundary>\n );\n}\n"],"mappings":";;;;;AAkBA,IAAM,IAAe;AAMrB,SAAS,EAAS,EAAE,SAAM,mBAA2E;AAMnG,QALI,EAAK,QAAQ,WAAW,IAExB,kBAAC,OAAD;EAAK,WAAU;YAAoE;EAA+B,CAAA,GAIpH,kBAAC,OAAD;EACE,WAAU;EACV,OAAO;GAAE,qBAAqB,UAAU,EAAa;GAAoB,cAAc;GAAsB;YAE5G,EAAK,QAAQ,KAAK,MACjB,kBAAC,OAAD;GAEE,WAAU;GACV,OAAO;IAGL,YAAY,GAAG,KAAK,IAAI,KAAK,IAAI,EAAO,OAAO,GAAG,EAAE,EAAE,IAAe,EAAE,GAAG,EAAE,UAAU,KAAK,IACzF,KAAK,IAAI,EAAO,OAAO,GAAG,EAAE,EAC5B,EACD;IACD,SAAS,GAAG,KAAK,IAAI,EAAO,OAAO,GAAG,EAAE,GAAG,EAAE,UAAU,KAAK,IAAI,EAAO,OAAO,GAAG,EAAE;IACpF;aAED,kBAAC,GAAD;IAA6B;IAAsB;IAAgB,CAAA;GAC/D,EAbC,EAAO,GAaR,CACN;EACE,CAAA;;AAKV,SAAS,IAAiB;AACxB,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,KAAD;GACE,MAAK;GACL,QAAO;GACP,KAAI;GACJ,WAAU;aAJZ,CAKC,eACY,kBAAC,QAAD;IAAM,WAAU;cAAoC;IAAiB,CAAA,CAC9E;;EACA,CAAA;;AAIV,SAAgB,EAA0B,EAAE,YAAgB;CAC1D,IAAM,CAAC,GAAiB,KAAsB,EAAS,EAAE,EACnD,IAAQ,EAAM,OACd,IAAe,EAAM,cAErB,IAAc,GAAc,aAAa,EAAa,SAAS,IAAI,MAAM,GAAG,IAC5E,IAAa,EAAM,KAAK,IAAI,GAAiB,EAAM,SAAS,EAAE;AAkBpE,QAhBK,IAiBH,kBAAC,GAAD;EAAoB,OAAM;YACxB,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,KACC,kBAAC,UAAD;KAAQ,WAAU;eAChB,kBAAC,MAAD;MAAI,WAAU;gBAAsD;MAAiB,CAAA;KAC9E,CAAA;IAEV,EAAM,SAAS,KACd,kBAAC,OAAD;KAAK,MAAK;KAAU,WAAU;eAC3B,EAAM,KAAK,GAAM,MAChB,kBAAC,UAAD;MAEE,MAAK;MACL,iBAAe,MAAM;MACrB,eAAe,EAAmB,EAAE;MACpC,WACE,MAAM,IACF,+FACA;gBAGL,EAAK,SAAS,QAAQ,IAAI;MACpB,EAXF,EAAK,GAWH,CACT;KACE,CAAA;IAER,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MAAU,MAAM;MAAY,cAAc,EAAM;MAAgB,CAAA;KAC5D,CAAA;IACN,kBAAC,GAAD,EAAkB,CAAA;IACd;;EACa,CAAA,GA9CnB,kBAAC,OAAD;EAAK,WAAU;YAAf;GACG,KACC,kBAAC,UAAD;IAAQ,WAAU;cAChB,kBAAC,MAAD;KAAI,WAAU;eAAsD;KAAiB,CAAA;IAC9E,CAAA;GAEX,kBAAC,OAAD;IAAK,WAAU;cAAsE;IAE/E,CAAA;GACN,kBAAC,GAAD,EAAkB,CAAA;GACd"}
|
|
1
|
+
{"version":3,"file":"ReadOnlyDashboardRenderer.js","names":[],"sources":["../../../../src/bigconsole/components/embed/ReadOnlyDashboardRenderer.tsx"],"sourcesContent":["/**\n * Read-only embedded dashboard renderer (BOFF-2986).\n *\n * Renders the sanitized render model's pages + widgets on a simple responsive\n * grid. It is STRICTLY view-only: no builder toolbar, edit toggle, sharing,\n * save, palette, inspector or comments — those authenticated affordances do not\n * exist in this component tree at all.\n *\n * Every embed carries the \"Powered by BigConsole\" footer. Burdenoff does no\n * white-labelling, so there is deliberately no model field, policy option or\n * URL parameter that hides it.\n *\n * Drilldown (BOFF-7625): when the publication allows it and the host passes\n * `onDrilldown`, a widget with a drilldown hint gets a \"View details\" button.\n * Page tabs can be controlled (`activePageId` + `onPageChange`) so the host can\n * record each switch as a browser history entry.\n */\n\nimport { useMemo, useState } from 'react';\nimport type { EmbedCapabilities, EmbedDrilldownHint, EmbedPageModel, EmbedRenderModel } from './types';\nimport { EmbedWidgetRenderer } from './EmbedWidgetRenderer';\nimport { EmbedErrorBoundary } from './EmbedErrorBoundary';\nimport { readDrilldownHint } from './drilldown';\n\nconst GRID_COLUMNS = 12;\n\ninterface Props {\n readonly model: EmbedRenderModel;\n /** Controlled active page id. Omit (with onPageChange) to let the renderer own the tabs. */\n readonly activePageId?: string | null;\n /** Called when the viewer picks a page tab (controlled mode). */\n readonly onPageChange?: (pageId: string) => void;\n /** Follow a widget's drilldown. Only offered when the model's capabilities allow it. */\n readonly onDrilldown?: (hint: EmbedDrilldownHint) => void;\n}\n\ninterface PageGridProps {\n page: EmbedPageModel;\n capabilities: EmbedCapabilities;\n pageIds: ReadonlySet<string>;\n onDrilldown?: (hint: EmbedDrilldownHint) => void;\n}\n\nfunction DrilldownButton({ title, onClick }: { title?: string; onClick: () => void }) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label={title ? `View details for ${title}` : 'View details'}\n className=\"absolute right-2 top-2 z-10 rounded-button border border-border-default bg-bg-surface px-2 py-1 text-xs font-medium text-text-link shadow-sm transition-colors hover:bg-bg-sunken focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n data-testid=\"embed-drilldown\"\n >\n View details →\n </button>\n );\n}\n\nfunction PageGrid({ page, capabilities, pageIds, onDrilldown }: PageGridProps) {\n if (page.widgets.length === 0) {\n return (\n <div className=\"flex h-40 items-center justify-center text-sm text-text-secondary\">This page has no widgets.</div>\n );\n }\n return (\n <div\n className=\"grid w-full gap-4 p-4\"\n style={{ gridTemplateColumns: `repeat(${GRID_COLUMNS}, minmax(0, 1fr))`, gridAutoRows: 'minmax(80px, auto)' }}\n >\n {page.widgets.map((widget) => (\n <div\n key={widget.id}\n className=\"relative overflow-hidden rounded-lg border border-border-default bg-bg-surface shadow-sm\"\n style={{\n // Clamp both ends: negative/oversized coordinates from malformed\n // model data must never produce an invalid CSS grid line.\n gridColumn: `${Math.min(Math.max(widget.layout.x, 0), GRID_COLUMNS - 1) + 1} / span ${Math.min(\n Math.max(widget.layout.w, 1),\n GRID_COLUMNS\n )}`,\n gridRow: `${Math.max(widget.layout.y, 0) + 1} / span ${Math.max(widget.layout.h, 1)}`,\n }}\n >\n <EmbedWidgetCard widget={widget} capabilities={capabilities} pageIds={pageIds} onDrilldown={onDrilldown} />\n </div>\n ))}\n </div>\n );\n}\n\nfunction EmbedWidgetCard({\n widget,\n capabilities,\n pageIds,\n onDrilldown,\n}: {\n widget: EmbedPageModel['widgets'][number];\n capabilities: EmbedCapabilities;\n pageIds: ReadonlySet<string>;\n onDrilldown?: (hint: EmbedDrilldownHint) => void;\n}) {\n const hint = capabilities.drilldown && onDrilldown ? readDrilldownHint(widget, pageIds) : null;\n return (\n <>\n <EmbedWidgetRenderer widget={widget} capabilities={capabilities} />\n {hint && onDrilldown && <DrilldownButton title={widget.title} onClick={() => onDrilldown(hint)} />}\n </>\n );\n}\n\n/** \"Powered by BigConsole\" attribution footer, rendered on every embed (never optional). */\nfunction BrandingFooter() {\n return (\n <div className=\"flex shrink-0 items-center justify-end border-t border-border-default bg-bg-surface px-4 py-2\">\n <a\n href=\"https://bigconsole.com\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-xs text-text-tertiary transition-colors hover:text-text-secondary\"\n >\n Powered by <span className=\"font-semibold text-text-secondary\">BigConsole</span>\n </a>\n </div>\n );\n}\n\nexport function ReadOnlyDashboardRenderer({ model, activePageId, onPageChange, onDrilldown }: Props) {\n const [ownPageIndex, setOwnPageIndex] = useState(0);\n const pages = model.pages;\n const presentation = model.presentation;\n const pageIds = useMemo(() => new Set(pages.map((page) => page.id)), [pages]);\n // Only show a header when the owner enabled it AND there's a non-empty title.\n const headerTitle = presentation?.showTitle ? (presentation.title ?? '').trim() : '';\n // Controlled when the host tracks the page; an unknown id shows the first page.\n const controlled = onPageChange !== undefined;\n const controlledIndex = pages.findIndex((page) => page.id === activePageId);\n const activePageIndex = controlled ? Math.max(controlledIndex, 0) : ownPageIndex;\n const selectPage = (index: number): void => {\n const page = pages[index];\n if (!page) return;\n if (controlled) {\n onPageChange(page.id);\n } else {\n setOwnPageIndex(index);\n }\n };\n const activePage = pages[Math.min(activePageIndex, pages.length - 1)];\n\n if (!activePage) {\n return (\n <div className=\"flex h-full flex-col bg-bg-canvas\">\n {headerTitle && (\n <header className=\"shrink-0 border-b border-border-default bg-bg-surface px-4 py-3\">\n <h1 className=\"truncate text-base font-semibold text-text-primary\">{headerTitle}</h1>\n </header>\n )}\n <div className=\"flex flex-1 items-center justify-center text-sm text-text-secondary\">\n This dashboard has no pages.\n </div>\n <BrandingFooter />\n </div>\n );\n }\n\n return (\n <EmbedErrorBoundary label=\"dashboard\">\n <div className=\"flex h-full w-full flex-col bg-bg-canvas\">\n {headerTitle && (\n <header className=\"shrink-0 border-b border-border-default bg-bg-surface px-4 py-3\">\n <h1 className=\"truncate text-base font-semibold text-text-primary\">{headerTitle}</h1>\n </header>\n )}\n {pages.length > 1 && (\n <div role=\"tablist\" className=\"flex gap-1 border-b border-border-default bg-bg-surface px-2\">\n {pages.map((page, i) => (\n <button\n key={page.id}\n role=\"tab\"\n aria-selected={i === activePageIndex}\n onClick={() => selectPage(i)}\n className={\n i === activePageIndex\n ? '-mb-px border-b-2 border-action-primary-bg px-3 py-2 text-sm font-medium text-text-primary'\n : 'border-b-2 border-transparent px-3 py-2 text-sm text-text-secondary hover:text-text-primary'\n }\n >\n {page.title ?? `Page ${i + 1}`}\n </button>\n ))}\n </div>\n )}\n <div className=\"min-h-0 flex-1 overflow-auto\">\n <PageGrid page={activePage} capabilities={model.capabilities} pageIds={pageIds} onDrilldown={onDrilldown} />\n </div>\n <BrandingFooter />\n </div>\n </EmbedErrorBoundary>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/bigconsole/components/embed/drilldown.ts
|
|
2
|
+
var e = /^[A-Za-z0-9-]{1,64}$/;
|
|
3
|
+
function t(e) {
|
|
4
|
+
return !!e && typeof e == "object" && !Array.isArray(e);
|
|
5
|
+
}
|
|
6
|
+
function n(n, r) {
|
|
7
|
+
let i = n.drilldown;
|
|
8
|
+
if (!t(i)) return null;
|
|
9
|
+
if (i.kind === "page") return typeof i.pageId == "string" && r.has(i.pageId) ? {
|
|
10
|
+
kind: "page",
|
|
11
|
+
pageId: i.pageId
|
|
12
|
+
} : null;
|
|
13
|
+
if (i.kind === "dashboard" && typeof i.linkId == "string" && e.test(i.linkId)) {
|
|
14
|
+
let e = typeof i.pageId == "string" && i.pageId.length > 0 ? i.pageId : null;
|
|
15
|
+
return {
|
|
16
|
+
kind: "dashboard",
|
|
17
|
+
linkId: i.linkId,
|
|
18
|
+
pageId: e
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { n as readDrilldownHint };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=drilldown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drilldown.js","names":[],"sources":["../../../../src/bigconsole/components/embed/drilldown.ts"],"sourcesContent":["/**\n * Embed drilldown hints (BOFF-7625).\n *\n * The render model is untrusted input to the renderer, so a hint is re-checked\n * before it becomes clickable: a page hint must name a page of this model, and\n * a dashboard hint must carry a link id of the shape the linked render-model\n * route accepts. Anything else is ignored (the widget stays non-interactive).\n */\n\nimport type { EmbedDrilldownHint, EmbedWidgetModel } from './types';\n\n/** Same shape the gateway and BigConsole accept for one drilldown link id. */\nconst LINK_ID = /^[A-Za-z0-9-]{1,64}$/;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value);\n}\n\n/** The widget's drilldown hint if it is well-formed and followable, else null. */\nexport function readDrilldownHint(widget: EmbedWidgetModel, pageIds: ReadonlySet<string>): EmbedDrilldownHint | null {\n const hint: unknown = widget.drilldown;\n if (!isRecord(hint)) return null;\n if (hint.kind === 'page') {\n return typeof hint.pageId === 'string' && pageIds.has(hint.pageId) ? { kind: 'page', pageId: hint.pageId } : null;\n }\n if (hint.kind === 'dashboard' && typeof hint.linkId === 'string' && LINK_ID.test(hint.linkId)) {\n const pageId = typeof hint.pageId === 'string' && hint.pageId.length > 0 ? hint.pageId : null;\n return { kind: 'dashboard', linkId: hint.linkId, pageId };\n }\n return null;\n}\n"],"mappings":";AAYA,IAAM,IAAU;AAEhB,SAAS,EAAS,GAAkD;AAClE,QAAO,CAAC,CAAC,KAAS,OAAO,KAAU,YAAY,CAAC,MAAM,QAAQ,EAAM;;AAItE,SAAgB,EAAkB,GAA0B,GAAyD;CACnH,IAAM,IAAgB,EAAO;AAC7B,KAAI,CAAC,EAAS,EAAK,CAAE,QAAO;AAC5B,KAAI,EAAK,SAAS,OAChB,QAAO,OAAO,EAAK,UAAW,YAAY,EAAQ,IAAI,EAAK,OAAO,GAAG;EAAE,MAAM;EAAQ,QAAQ,EAAK;EAAQ,GAAG;AAE/G,KAAI,EAAK,SAAS,eAAe,OAAO,EAAK,UAAW,YAAY,EAAQ,KAAK,EAAK,OAAO,EAAE;EAC7F,IAAM,IAAS,OAAO,EAAK,UAAW,YAAY,EAAK,OAAO,SAAS,IAAI,EAAK,SAAS;AACzF,SAAO;GAAE,MAAM;GAAa,QAAQ,EAAK;GAAQ;GAAQ;;AAE3D,QAAO"}
|
|
@@ -2,6 +2,7 @@ import { EMBED_RENDER_MODEL_VERSION as e } from "./types.js";
|
|
|
2
2
|
import { EMBED_RENDERER_MANIFEST as t, EMBED_WIDGET_COVERAGE as n, isWidgetTypeEmbeddable as r } from "./renderer-manifest.js";
|
|
3
3
|
import { EmbedErrorBoundary as i } from "./EmbedErrorBoundary.js";
|
|
4
4
|
import { EmbedWidgetRenderer as a, getImplementedRendererTypes as o } from "./EmbedWidgetRenderer.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
5
|
+
import { readDrilldownHint as s } from "./drilldown.js";
|
|
6
|
+
import { ReadOnlyDashboardRenderer as c } from "./ReadOnlyDashboardRenderer.js";
|
|
7
|
+
import { validateEmbedRenderModel as l } from "./validate.js";
|
|
8
|
+
export { t as EMBED_RENDERER_MANIFEST, e as EMBED_RENDER_MODEL_VERSION, n as EMBED_WIDGET_COVERAGE, i as EmbedErrorBoundary, a as EmbedWidgetRenderer, c as ReadOnlyDashboardRenderer, o as getImplementedRendererTypes, r as isWidgetTypeEmbeddable, s as readDrilldownHint, l as validateEmbedRenderModel };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../../../src/bigconsole/components/embed/types.ts"],"sourcesContent":["/**\n * Stable, versioned embed render-model contract (BOFF-2986).\n *\n * This is the ONLY shape the read-only embed renderer consumes. It is a\n * sanitized DTO produced by the BigConsole publication builder — it deliberately\n * carries NO authenticated stores, Apollo clients, data-source configs,\n * credentials or internal ids. The renderer never fetches data itself; all\n * values are pre-projected into the model or fetched later through the\n * publication-scoped interaction controller using opaque aliases.\n *\n * Backend (publication builder) and frontend (renderer) MUST agree on\n * `EMBED_RENDER_MODEL_VERSION`. A mismatch fails closed at load time rather than\n * rendering a stale/incompatible view.\n */\n\nimport type { WidgetType } from '../../types';\n\n/** Bump on any breaking change to the DTO shapes below. */\nexport const EMBED_RENDER_MODEL_VERSION = 1 as const;\n\n/** Pure layout box (grid units); no store, no event emission. */\nexport interface EmbedWidgetLayout {\n readonly x: number;\n readonly y: number;\n readonly w: number;\n readonly h: number;\n}\n\n/**\n * A single widget as projected for embedding. `config` and `data` are already\n * sanitized and schema-narrowed per widget type by the publication builder; the\n * renderer validates them again against its per-type schema before rendering.\n */\nexport interface EmbedWidgetModel {\n /** Opaque, publication-scoped widget id (not the internal DB id). */\n readonly id: string;\n readonly type: WidgetType;\n readonly title?: string;\n readonly layout: EmbedWidgetLayout;\n /** Sanitized, type-narrowed presentation config. */\n readonly config: Readonly<Record<string, unknown>>;\n /** Sanitized, pre-projected data payload (or null for interaction-fetched). */\n readonly data: Readonly<Record<string, unknown>> | null;\n}\n\nexport interface EmbedPageModel {\n readonly id: string;\n readonly title?: string;\n readonly widgets: readonly EmbedWidgetModel[];\n}\n\n/**\n * Publication-approved runtime capabilities. Everything defaults OFF; the\n * renderer must never enable an interaction the publication did not grant.\n */\nexport interface EmbedCapabilities {\n readonly filters: boolean;\n readonly drilldown: boolean;\n readonly downloads: boolean;\n /** Side-effect actions (forms/webhooks/workflows) — out of scope for read-only MVP. */\n readonly actions: boolean;\n}\n\n/** Publish-time color mode chosen by the owner (BOFF-5735). */\nexport type EmbedThemeMode = 'light' | 'dark' | 'auto';\n\n/**\n * Owner-chosen, publish-time presentation options (BOFF-5735), attached to the\n * render model (resolved live from the policy). Optional: pre-5735 render models\n * carry no `presentation` block, so the renderer must default gracefully (light,\n * no header) when absent.\n *\n * Branding is NOT a presentation option: the \"Powered by BigConsole\" footer is\n * rendered on every embed, with or without this block. Burdenoff does no\n * white-labelling, so never add a field here that hides or replaces it.\n */\nexport interface EmbedPresentation {\n readonly theme: EmbedThemeMode;\n readonly showTitle: boolean;\n /** Dashboard name for the optional header, or null when hidden/empty. */\n readonly title: string | null;\n}\n\nexport interface EmbedRenderModel {\n readonly renderModelVersion: number;\n readonly clientContractVersion: number;\n readonly title?: string;\n readonly pages: readonly EmbedPageModel[];\n readonly capabilities: EmbedCapabilities;\n readonly presentation?: EmbedPresentation;\n /** ISO timestamp the immutable publication was frozen. */\n readonly definitionPublishedAt: string;\n}\n\n/** Result of validating an inbound render model before rendering. */\nexport type EmbedModelValidation =\n { readonly ok: true; readonly model: EmbedRenderModel } | { readonly ok: false; readonly reason: string };\n\n/**\n * Shape every embed widget renderer component receives. It gets the sanitized\n * model plus a controller for publication-scoped interactions — never raw hooks.\n */\nexport interface EmbedWidgetRenderProps {\n readonly widget: EmbedWidgetModel;\n readonly capabilities: EmbedCapabilities;\n}\n"],"mappings":";AAkBA,IAAa,IAA6B"}
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../../../src/bigconsole/components/embed/types.ts"],"sourcesContent":["/**\n * Stable, versioned embed render-model contract (BOFF-2986).\n *\n * This is the ONLY shape the read-only embed renderer consumes. It is a\n * sanitized DTO produced by the BigConsole publication builder — it deliberately\n * carries NO authenticated stores, Apollo clients, data-source configs,\n * credentials or internal ids. The renderer never fetches data itself; all\n * values are pre-projected into the model or fetched later through the\n * publication-scoped interaction controller using opaque aliases.\n *\n * Backend (publication builder) and frontend (renderer) MUST agree on\n * `EMBED_RENDER_MODEL_VERSION`. A mismatch fails closed at load time rather than\n * rendering a stale/incompatible view.\n */\n\nimport type { WidgetType } from '../../types';\n\n/** Bump on any breaking change to the DTO shapes below. */\nexport const EMBED_RENDER_MODEL_VERSION = 1 as const;\n\n/** Pure layout box (grid units); no store, no event emission. */\nexport interface EmbedWidgetLayout {\n readonly x: number;\n readonly y: number;\n readonly w: number;\n readonly h: number;\n}\n\n/**\n * A single widget as projected for embedding. `config` and `data` are already\n * sanitized and schema-narrowed per widget type by the publication builder; the\n * renderer validates them again against its per-type schema before rendering.\n */\n/**\n * Drilldown a widget offers in the embed (BOFF-7625). Only present when the\n * publication allows it:\n * - `page`: another page of this dashboard (switch tab).\n * - `dashboard`: another dashboard that is itself published for embedding,\n * fetched through the linked render-model route by the opaque `linkId`\n * (never the target dashboard's id), optionally opening `pageId`.\n */\nexport type EmbedDrilldownHint =\n | { readonly kind: 'page'; readonly pageId: string }\n | { readonly kind: 'dashboard'; readonly linkId: string; readonly pageId: string | null };\n\nexport interface EmbedWidgetModel {\n /** Opaque, publication-scoped widget id (not the internal DB id). */\n readonly id: string;\n readonly type: WidgetType;\n readonly title?: string;\n readonly layout: EmbedWidgetLayout;\n /** Sanitized, type-narrowed presentation config. */\n readonly config: Readonly<Record<string, unknown>>;\n /** Sanitized, pre-projected data payload (or null for interaction-fetched). */\n readonly data: Readonly<Record<string, unknown>> | null;\n /** Drilldown target, when the owner configured one the embed may follow (BOFF-7625). */\n readonly drilldown?: EmbedDrilldownHint;\n}\n\nexport interface EmbedPageModel {\n readonly id: string;\n readonly title?: string;\n readonly widgets: readonly EmbedWidgetModel[];\n}\n\n/**\n * Publication-approved runtime capabilities. Everything defaults OFF; the\n * renderer must never enable an interaction the publication did not grant.\n */\nexport interface EmbedCapabilities {\n readonly filters: boolean;\n readonly drilldown: boolean;\n readonly downloads: boolean;\n /** Side-effect actions (forms/webhooks/workflows) — out of scope for read-only MVP. */\n readonly actions: boolean;\n}\n\n/** Publish-time color mode chosen by the owner (BOFF-5735). */\nexport type EmbedThemeMode = 'light' | 'dark' | 'auto';\n\n/**\n * Owner-chosen, publish-time presentation options (BOFF-5735), attached to the\n * render model (resolved live from the policy). Optional: pre-5735 render models\n * carry no `presentation` block, so the renderer must default gracefully (light,\n * no header) when absent.\n *\n * Branding is NOT a presentation option: the \"Powered by BigConsole\" footer is\n * rendered on every embed, with or without this block. Burdenoff does no\n * white-labelling, so never add a field here that hides or replaces it.\n */\nexport interface EmbedPresentation {\n readonly theme: EmbedThemeMode;\n readonly showTitle: boolean;\n /** Dashboard name for the optional header, or null when hidden/empty. */\n readonly title: string | null;\n}\n\nexport interface EmbedRenderModel {\n readonly renderModelVersion: number;\n readonly clientContractVersion: number;\n readonly title?: string;\n readonly pages: readonly EmbedPageModel[];\n readonly capabilities: EmbedCapabilities;\n readonly presentation?: EmbedPresentation;\n /** ISO timestamp the immutable publication was frozen. */\n readonly definitionPublishedAt: string;\n}\n\n/** Result of validating an inbound render model before rendering. */\nexport type EmbedModelValidation =\n { readonly ok: true; readonly model: EmbedRenderModel } | { readonly ok: false; readonly reason: string };\n\n/**\n * Shape every embed widget renderer component receives. It gets the sanitized\n * model plus a controller for publication-scoped interactions — never raw hooks.\n */\nexport interface EmbedWidgetRenderProps {\n readonly widget: EmbedWidgetModel;\n readonly capabilities: EmbedCapabilities;\n}\n"],"mappings":";AAkBA,IAAa,IAA6B"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { PREVIEW_STEPS as e, useAssistantRunStore as t } from "../../assistant/assistantRunStore.js";
|
|
2
|
+
import { useAssistantRunWorkspace as n } from "../../assistant/useAssistantRunWorkspace.js";
|
|
3
3
|
import { Section as r } from "../common/Section.js";
|
|
4
4
|
import "../common/index.js";
|
|
5
5
|
import { DOCK_CLEARANCE as i, PreviewMeta as a, STEP_LABEL as o, formatAgo as s, previewLabels as c } from "./previewShared.js";
|
|
@@ -10,58 +10,60 @@ import { useI18n as y } from "@burdenoff/fe-libs/shared/providers/shell/I18nProv
|
|
|
10
10
|
import { AssistantEntityPreview as b } from "@burdenoff/fe-libs/shared/assistant";
|
|
11
11
|
//#region src/bigconsole/components/preview/AiPreviewMiniPanel.tsx
|
|
12
12
|
function x({ onNavigate: x }) {
|
|
13
|
-
let { t: S } = y()
|
|
13
|
+
let { t: S } = y();
|
|
14
|
+
n();
|
|
15
|
+
let C = t((e) => e.isRunning), w = t((e) => e.previewOpen), T = t((e) => e.hasRun), E = t((e) => e.richMounted), D = t((e) => e.steps), O = t((e) => e.entityIds), k = t((e) => e.caption), A = t((e) => e.error), j = t((e) => e.updatedAt), M = t((e) => e.openPreview), N = t((e) => e.closePreview), P = (e) => {
|
|
14
16
|
x ? x(e) : typeof window < "u" && window.location.assign(e);
|
|
15
17
|
};
|
|
16
|
-
if (!
|
|
17
|
-
if (!
|
|
18
|
+
if (!T || E) return null;
|
|
19
|
+
if (!w) return /* @__PURE__ */ v("div", {
|
|
18
20
|
className: "flex flex-wrap items-center justify-end gap-control-gap px-4 pt-3 md:px-6",
|
|
19
21
|
style: { marginRight: i },
|
|
20
22
|
children: [/* @__PURE__ */ _("span", {
|
|
21
23
|
className: "text-xs text-text-muted",
|
|
22
|
-
children:
|
|
24
|
+
children: C ? S("bigconsole.widget.aiPreview.buildRunning", "AI build running") : S("bigconsole.widget.aiPreview.buildFinished", "AI build finished")
|
|
23
25
|
}), /* @__PURE__ */ v(g, {
|
|
24
26
|
size: "sm",
|
|
25
27
|
variant: "outline",
|
|
26
|
-
onClick:
|
|
28
|
+
onClick: M,
|
|
27
29
|
"data-testid": "ai-preview-open",
|
|
28
30
|
children: [
|
|
29
31
|
/* @__PURE__ */ _(m, { className: "mr-1.5 size-4" }),
|
|
30
32
|
S("bigconsole.widget.aiPreviewMini.previewButton", "Preview"),
|
|
31
|
-
|
|
33
|
+
C ? /* @__PURE__ */ _(p, { className: "ml-1.5 size-3.5 animate-spin" }) : null
|
|
32
34
|
]
|
|
33
35
|
})]
|
|
34
36
|
});
|
|
35
|
-
let
|
|
36
|
-
|
|
37
|
+
let F = [];
|
|
38
|
+
O.datasink && F.push({
|
|
37
39
|
key: "datasink",
|
|
38
40
|
label: S("bigconsole.widget.aiPreviewMini.link.dataSink", "Data Sink"),
|
|
39
|
-
path: `/bigconsole/datasinks/${
|
|
41
|
+
path: `/bigconsole/datasinks/${O.datasink}`,
|
|
40
42
|
icon: /* @__PURE__ */ _(d, {
|
|
41
43
|
className: "size-4 shrink-0 text-text-muted",
|
|
42
44
|
"aria-hidden": "true"
|
|
43
45
|
})
|
|
44
|
-
}),
|
|
46
|
+
}), O.dashboard && F.push({
|
|
45
47
|
key: "dashboard",
|
|
46
48
|
label: S("bigconsole.widget.aiPreviewMini.link.dashboard", "Dashboard"),
|
|
47
|
-
path: `/bigconsole/dashboards/${
|
|
49
|
+
path: `/bigconsole/dashboards/${O.dashboard}`,
|
|
48
50
|
icon: /* @__PURE__ */ _(f, {
|
|
49
51
|
className: "size-4 shrink-0 text-text-muted",
|
|
50
52
|
"aria-hidden": "true"
|
|
51
53
|
})
|
|
52
|
-
}),
|
|
54
|
+
}), O.parser && F.push({
|
|
53
55
|
key: "parser",
|
|
54
56
|
label: S("bigconsole.widget.aiPreviewMini.link.parser", "Parser"),
|
|
55
|
-
path: `/bigconsole/parsers/${
|
|
57
|
+
path: `/bigconsole/parsers/${O.parser}`,
|
|
56
58
|
icon: /* @__PURE__ */ _(u, {
|
|
57
59
|
className: "size-4 shrink-0 text-text-muted",
|
|
58
60
|
"aria-hidden": "true"
|
|
59
61
|
})
|
|
60
62
|
});
|
|
61
|
-
let
|
|
63
|
+
let I = e.map((e) => ({
|
|
62
64
|
id: e,
|
|
63
65
|
label: S(o[e].key, o[e].fallback),
|
|
64
|
-
status:
|
|
66
|
+
status: D[e]
|
|
65
67
|
}));
|
|
66
68
|
return /* @__PURE__ */ v("div", {
|
|
67
69
|
"data-testid": "ai-preview-panel",
|
|
@@ -73,20 +75,20 @@ function x({ onNavigate: x }) {
|
|
|
73
75
|
children: S("bigconsole.widget.aiPreviewMini.title", "AI Preview")
|
|
74
76
|
}), /* @__PURE__ */ _(b, {
|
|
75
77
|
title: S("bigconsole.widget.aiPreviewMini.title", "AI Preview"),
|
|
76
|
-
steps:
|
|
77
|
-
caption:
|
|
78
|
-
error:
|
|
78
|
+
steps: I,
|
|
79
|
+
caption: C ? k || S("bigconsole.widget.aiPreviewMini.working", "Working…") : S("bigconsole.widget.aiPreviewMini.done", "Done"),
|
|
80
|
+
error: A,
|
|
79
81
|
meta: /* @__PURE__ */ _(a, {
|
|
80
|
-
state:
|
|
81
|
-
stateLabel:
|
|
82
|
+
state: C ? "running" : A ? "stopped" : "idle",
|
|
83
|
+
stateLabel: C ? S("bigconsole.widget.aiPreviewMini.live", "Live") : A ? S("bigconsole.widget.aiPreviewMini.stopped", "Stopped") : null,
|
|
82
84
|
updatedLabel: S("bigconsole.widget.aiPreviewMini.updated", "updated"),
|
|
83
|
-
updatedAt:
|
|
85
|
+
updatedAt: j,
|
|
84
86
|
ago: (e) => s(e, S)
|
|
85
87
|
}),
|
|
86
88
|
headerActions: /* @__PURE__ */ _(g, {
|
|
87
89
|
size: "sm",
|
|
88
90
|
variant: "ghost",
|
|
89
|
-
onClick:
|
|
91
|
+
onClick: N,
|
|
90
92
|
"aria-label": S("bigconsole.widget.aiPreviewMini.closeAriaLabel", "Close preview"),
|
|
91
93
|
"data-testid": "ai-preview-close",
|
|
92
94
|
children: /* @__PURE__ */ _(h, { className: "size-4" })
|
|
@@ -101,10 +103,10 @@ function x({ onNavigate: x }) {
|
|
|
101
103
|
title: S("bigconsole.widget.aiPreviewMini.builtSoFar", "Built so far"),
|
|
102
104
|
description: S("bigconsole.widget.aiPreviewMini.builtSoFarHint", "Open any piece to see the real thing."),
|
|
103
105
|
bodyClassName: "flex flex-col gap-list-gap",
|
|
104
|
-
children: [
|
|
106
|
+
children: [F.length === 0 ? /* @__PURE__ */ _("p", {
|
|
105
107
|
className: "text-sm text-text-muted",
|
|
106
|
-
children:
|
|
107
|
-
}) :
|
|
108
|
+
children: C ? S("bigconsole.widget.aiPreviewMini.building", "Building… links appear here as each piece is created.") : S("bigconsole.widget.aiPreviewMini.nothingBuilt", "Nothing built yet.")
|
|
109
|
+
}) : F.map((e) => /* @__PURE__ */ v("div", {
|
|
108
110
|
"data-testid": "ai-preview-row",
|
|
109
111
|
className: "flex min-w-0 items-center justify-between gap-control-gap rounded-card border border-[var(--color-border-subtle)] bg-bg-canvas px-3 py-2",
|
|
110
112
|
children: [/* @__PURE__ */ v("p", {
|
|
@@ -116,15 +118,15 @@ function x({ onNavigate: x }) {
|
|
|
116
118
|
}), /* @__PURE__ */ v(g, {
|
|
117
119
|
size: "sm",
|
|
118
120
|
variant: "ghost",
|
|
119
|
-
onClick: () =>
|
|
121
|
+
onClick: () => P(e.path),
|
|
120
122
|
children: [S("bigconsole.widget.aiPreviewMini.open", "Open"), /* @__PURE__ */ _(l, { className: "ml-1 size-3.5" })]
|
|
121
123
|
})]
|
|
122
|
-
}, e.key)),
|
|
124
|
+
}, e.key)), O.dashboard ? /* @__PURE__ */ v(g, {
|
|
123
125
|
size: "sm",
|
|
124
126
|
variant: "outline",
|
|
125
127
|
className: "h-auto w-full whitespace-normal py-1.5 text-center",
|
|
126
128
|
"data-testid": "ai-preview-open-in-bigconsole",
|
|
127
|
-
onClick: () =>
|
|
129
|
+
onClick: () => P(`/bigconsole/dashboards/${O.dashboard ?? ""}`),
|
|
128
130
|
children: [S("bigconsole.widget.aiPreviewMini.openInBigConsole", "Open in BigConsole for the live view"), /* @__PURE__ */ _(l, { className: "ml-1 size-3.5" })]
|
|
129
131
|
}) : null]
|
|
130
132
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiPreviewMiniPanel.js","names":[],"sources":["../../../../src/bigconsole/components/preview/AiPreviewMiniPanel.tsx"],"sourcesContent":["/**\n * AI Preview — the app-shell copy.\n *\n * The full <AiPreviewPanel> lives inside BigConsoleRoot and only exists on\n * /bigconsole pages, but the assistant widget is global: you can start a build\n * from anywhere. This lightweight panel is mounted by the app shell so the build\n * is visible on WHATEVER page you are on — workflows, store, workspaces, search.\n *\n * It is deliberately store-only: no Apollo, no BigConsole context, no data\n * fetching. That is what lets it mount above the router. It shows the same live\n * step rail and caption as the full panel, plus click-through links to each\n * entity as it lands. The rich per-entity tabs stay on the /bigconsole pages,\n * where the data layer actually exists.\n *\n * It renders nothing while the full panel is up (`richMounted`), so the two\n * never stack. Leave a /bigconsole page mid-run and the full panel unmounts,\n * `richMounted` flips false, and this one takes over without a flicker.\n *\n * Section emphasis (BOFF-5639): the chrome (header, progress) is recessed and\n * the \"Built so far\" list is the one raised surface — the same ladder as the\n * full panel, so the two copies read as one component. Spacing is identical to\n * the full panel too; they used to differ (`pt-1` vs `pt-3`), which shifted the\n * page under the user as they navigated.\n */\nimport type { ReactNode } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { AssistantEntityPreview, type AssistantPreviewStep } from '@burdenoff/fe-libs/shared/assistant';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useAssistantPanelShowsPreview } from '../../assistant/useAssistantPanelShowsPreview';\nimport { ArrowUpRight, Braces, Database, LayoutDashboard, Loader2, Sparkles, X } from 'lucide-react';\n\nimport { PREVIEW_STEPS, useAssistantRunStore } from '../../assistant/assistantRunStore';\nimport { Section } from '../common';\nimport { DOCK_CLEARANCE, PreviewMeta, STEP_LABEL, formatAgo, previewLabels } from './previewShared';\n\ninterface AiPreviewMiniPanelProps {\n /**\n * Route to an in-app path. The links are absolute (`/bigconsole/...`) because\n * this panel renders outside the BigConsole router. Falls back to a full\n * navigation when the host does not supply a handler.\n */\n onNavigate?: (path: string) => void;\n}\n\ninterface QuickLink {\n key: string;\n label: string;\n path: string;\n icon: ReactNode;\n}\n\nexport function AiPreviewMiniPanel({ onNavigate }: AiPreviewMiniPanelProps) {\n const { t } = useI18n();\n // ★★★ ONE SURFACE PER RUN. fe-libs renders this same controller inside its own\n // in-chat overlay; without this the page surface stayed up beside it and one\n // run was drawn twice, side by side (measured in prod, BOFF-7799).\n const panelShowsPreview = useAssistantPanelShowsPreview();\n const isRunning = useAssistantRunStore((state) => state.isRunning);\n const previewOpen = useAssistantRunStore((state) => state.previewOpen);\n const hasRun = useAssistantRunStore((state) => state.hasRun);\n const richMounted = useAssistantRunStore((state) => state.richMounted);\n const steps = useAssistantRunStore((state) => state.steps);\n const entityIds = useAssistantRunStore((state) => state.entityIds);\n const caption = useAssistantRunStore((state) => state.caption);\n const error = useAssistantRunStore((state) => state.error);\n const updatedAt = useAssistantRunStore((state) => state.updatedAt);\n const openPreview = useAssistantRunStore((state) => state.openPreview);\n const closePreview = useAssistantRunStore((state) => state.closePreview);\n\n const go = (path: string) => {\n if (onNavigate) onNavigate(path);\n else if (typeof window !== 'undefined') window.location.assign(path);\n };\n\n // Nothing has run, or the full in-context panel is already on screen.\n if (!hasRun || richMounted) return null;\n // The shared panel's overlay is already showing this run — stand down rather\n // than draw it a second time beside itself.\n if (panelShowsPreview) return null;\n\n if (!previewOpen) {\n return (\n <div\n className=\"flex flex-wrap items-center justify-end gap-control-gap px-4 pt-3 md:px-6\"\n style={{ marginRight: DOCK_CLEARANCE }}\n >\n <span className=\"text-xs text-text-muted\">\n {isRunning\n ? t('bigconsole.widget.aiPreview.buildRunning', 'AI build running')\n : t('bigconsole.widget.aiPreview.buildFinished', 'AI build finished')}\n </span>\n <Button size=\"sm\" variant=\"outline\" onClick={openPreview} data-testid=\"ai-preview-open\">\n <Sparkles className=\"mr-1.5 size-4\" />\n {t('bigconsole.widget.aiPreviewMini.previewButton', 'Preview')}\n {isRunning ? <Loader2 className=\"ml-1.5 size-3.5 animate-spin\" /> : null}\n </Button>\n </div>\n );\n }\n\n const links: QuickLink[] = [];\n if (entityIds.datasink)\n links.push({\n key: 'datasink',\n label: t('bigconsole.widget.aiPreviewMini.link.dataSink', 'Data Sink'),\n path: `/bigconsole/datasinks/${entityIds.datasink}`,\n icon: <Database className=\"size-4 shrink-0 text-text-muted\" aria-hidden=\"true\" />,\n });\n if (entityIds.dashboard)\n links.push({\n key: 'dashboard',\n label: t('bigconsole.widget.aiPreviewMini.link.dashboard', 'Dashboard'),\n path: `/bigconsole/dashboards/${entityIds.dashboard}`,\n icon: <LayoutDashboard className=\"size-4 shrink-0 text-text-muted\" aria-hidden=\"true\" />,\n });\n if (entityIds.parser)\n links.push({\n key: 'parser',\n label: t('bigconsole.widget.aiPreviewMini.link.parser', 'Parser'),\n path: `/bigconsole/parsers/${entityIds.parser}`,\n icon: <Braces className=\"size-4 shrink-0 text-text-muted\" aria-hidden=\"true\" />,\n });\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 // ★ The wrapper owns POSITIONING only — clearance for the assistant dock,\n // page gutters, and the testids this panel already had. Everything visual\n // belongs to the shared surface, which is what keeps this copy and the full\n // panel from drifting apart now that `previewShared` no longer holds a shell\n // recipe for them both.\n //\n // ★★ PADDING, not margin: `mx-4 md:mx-6` is the page gutter and it is\n // RESPONSIVE. See DOCK_CLEARANCE's docblock.\n <div\n data-testid=\"ai-preview-panel\"\n data-variant=\"mini\"\n style={{ paddingRight: DOCK_CLEARANCE }}\n className=\"mx-4 mb-3 mt-3 min-w-0 md:mx-6\"\n >\n {/* ★★ The panel's own heading, restored. The shared surface renders its\n title as a styled <span> inside a <section aria-label>, which labels\n the region but leaves NO heading — while it still renders an <h3> for\n the step rail. main had <h2>AI Preview</h2> above that <h3>, so the\n migration both removed the panel from screen-reader heading navigation\n and orphaned the h3 under whatever <h2> the page beneath happens to\n end with. Visually hidden because the surface already shows the title;\n this only restores the structure. */}\n <h2 className=\"sr-only\">{t('bigconsole.widget.aiPreviewMini.title', 'AI Preview')}</h2>\n <AssistantEntityPreview\n title={t('bigconsole.widget.aiPreviewMini.title', 'AI Preview')}\n steps={railSteps}\n // ★★ TWO branches, not three, and deliberately so: the shared surface\n // renders `{error ? '' : caption}` — it BLANKS the caption whenever\n // `error` is set, so a failure branch here would compute a string that\n // is then 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 headerActions={\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={closePreview}\n aria-label={t('bigconsole.widget.aiPreviewMini.closeAriaLabel', 'Close preview')}\n data-testid=\"ai-preview-close\"\n >\n <X className=\"size-4\" />\n </Button>\n }\n labels={previewLabels(t)}\n >\n {/* ★ No tabs here, by design: the rich per-entity views need Apollo and\n the BigConsole providers, which do not exist above the router. Links\n are what this copy can honestly offer. */}\n <div className=\"p-3\">\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title={t('bigconsole.widget.aiPreviewMini.builtSoFar', 'Built so far')}\n description={t('bigconsole.widget.aiPreviewMini.builtSoFarHint', 'Open any piece to see the real thing.')}\n bodyClassName=\"flex flex-col gap-list-gap\"\n >\n {links.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\n {isRunning\n ? t(\n 'bigconsole.widget.aiPreviewMini.building',\n 'Building… links appear here as each piece is created.'\n )\n : t('bigconsole.widget.aiPreviewMini.nothingBuilt', 'Nothing built yet.')}\n </p>\n ) : (\n links.map((link) => (\n <div\n key={link.key}\n data-testid=\"ai-preview-row\"\n className=\"flex min-w-0 items-center justify-between gap-control-gap rounded-card border border-[var(--color-border-subtle)] bg-bg-canvas px-3 py-2\"\n >\n <p className=\"flex min-w-0 items-center gap-icon-gap text-sm font-medium text-text-primary\">\n {link.icon}\n <span className=\"truncate\">\n {t('bigconsole.widget.aiPreviewMini.linkReady', '{{label}} ready', { label: link.label })}\n </span>\n </p>\n <Button size=\"sm\" variant=\"ghost\" onClick={() => go(link.path)}>\n {t('bigconsole.widget.aiPreviewMini.open', 'Open')}\n <ArrowUpRight className=\"ml-1 size-3.5\" />\n </Button>\n </div>\n ))\n )}\n {entityIds.dashboard ? (\n // fe-libs `Button` is `whitespace-nowrap` with a fixed `h-8` at\n // `size=\"sm\"`, so this long label overflowed the panel (which is\n // `overflow-hidden`) at 390px — and the hi/ta strings are longer\n // still. `h-auto whitespace-normal` lets it wrap to two lines\n // instead of being clipped.\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"h-auto w-full whitespace-normal py-1.5 text-center\"\n data-testid=\"ai-preview-open-in-bigconsole\"\n onClick={() => go(`/bigconsole/dashboards/${entityIds.dashboard ?? ''}`)}\n >\n {t('bigconsole.widget.aiPreviewMini.openInBigConsole', 'Open in BigConsole for the live view')}\n <ArrowUpRight className=\"ml-1 size-3.5\" />\n </Button>\n ) : null}\n </Section>\n </div>\n </AssistantEntityPreview>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AiPreviewMiniPanel.js","names":[],"sources":["../../../../src/bigconsole/components/preview/AiPreviewMiniPanel.tsx"],"sourcesContent":["/**\n * AI Preview — the app-shell copy.\n *\n * The full <AiPreviewPanel> lives inside BigConsoleRoot and only exists on\n * /bigconsole pages, but the assistant widget is global: you can start a build\n * from anywhere. This lightweight panel is mounted by the app shell so the build\n * is visible on WHATEVER page you are on — workflows, store, workspaces, search.\n *\n * It is deliberately store-only: no Apollo, no BigConsole context, no data\n * fetching. That is what lets it mount above the router. It shows the same live\n * step rail and caption as the full panel, plus click-through links to each\n * entity as it lands. The rich per-entity tabs stay on the /bigconsole pages,\n * where the data layer actually exists.\n *\n * It renders nothing while the full panel is up (`richMounted`), so the two\n * never stack. Leave a /bigconsole page mid-run and the full panel unmounts,\n * `richMounted` flips false, and this one takes over without a flicker.\n *\n * Section emphasis (BOFF-5639): the chrome (header, progress) is recessed and\n * the \"Built so far\" list is the one raised surface — the same ladder as the\n * full panel, so the two copies read as one component. Spacing is identical to\n * the full panel too; they used to differ (`pt-1` vs `pt-3`), which shifted the\n * page under the user as they navigated.\n */\nimport type { ReactNode } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { AssistantEntityPreview, type AssistantPreviewStep } from '@burdenoff/fe-libs/shared/assistant';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowUpRight, Braces, Database, LayoutDashboard, Loader2, Sparkles, X } from 'lucide-react';\n\nimport { PREVIEW_STEPS, useAssistantRunStore } from '../../assistant/assistantRunStore';\nimport { useAssistantRunWorkspace } from '../../assistant/useAssistantRunWorkspace';\nimport { Section } from '../common';\nimport { DOCK_CLEARANCE, PreviewMeta, STEP_LABEL, formatAgo, previewLabels } from './previewShared';\n\ninterface AiPreviewMiniPanelProps {\n /**\n * Route to an in-app path. The links are absolute (`/bigconsole/...`) because\n * this panel renders outside the BigConsole router. Falls back to a full\n * navigation when the host does not supply a handler.\n */\n onNavigate?: (path: string) => void;\n}\n\ninterface QuickLink {\n key: string;\n label: string;\n path: string;\n icon: ReactNode;\n}\n\nexport function AiPreviewMiniPanel({ onNavigate }: AiPreviewMiniPanelProps) {\n const { t } = useI18n();\n // ★★★ THE PAGE SURFACES ARE THE ONLY SURFACES (BOFF-7799). The assistant's\n // controller is headless, so nothing inside the panel competes with this one\n // and there is nothing to stand down for. Binding the workspace restores the\n // run after a reload in this tab and clears it on a workspace switch.\n useAssistantRunWorkspace();\n const isRunning = useAssistantRunStore((state) => state.isRunning);\n const previewOpen = useAssistantRunStore((state) => state.previewOpen);\n const hasRun = useAssistantRunStore((state) => state.hasRun);\n const richMounted = useAssistantRunStore((state) => state.richMounted);\n const steps = useAssistantRunStore((state) => state.steps);\n const entityIds = useAssistantRunStore((state) => state.entityIds);\n const caption = useAssistantRunStore((state) => state.caption);\n const error = useAssistantRunStore((state) => state.error);\n const updatedAt = useAssistantRunStore((state) => state.updatedAt);\n const openPreview = useAssistantRunStore((state) => state.openPreview);\n const closePreview = useAssistantRunStore((state) => state.closePreview);\n\n const go = (path: string) => {\n if (onNavigate) onNavigate(path);\n else if (typeof window !== 'undefined') window.location.assign(path);\n };\n\n // Nothing has run, or the full in-context panel is already on screen.\n if (!hasRun || richMounted) return null;\n\n if (!previewOpen) {\n return (\n <div\n className=\"flex flex-wrap items-center justify-end gap-control-gap px-4 pt-3 md:px-6\"\n style={{ marginRight: DOCK_CLEARANCE }}\n >\n <span className=\"text-xs text-text-muted\">\n {isRunning\n ? t('bigconsole.widget.aiPreview.buildRunning', 'AI build running')\n : t('bigconsole.widget.aiPreview.buildFinished', 'AI build finished')}\n </span>\n <Button size=\"sm\" variant=\"outline\" onClick={openPreview} data-testid=\"ai-preview-open\">\n <Sparkles className=\"mr-1.5 size-4\" />\n {t('bigconsole.widget.aiPreviewMini.previewButton', 'Preview')}\n {isRunning ? <Loader2 className=\"ml-1.5 size-3.5 animate-spin\" /> : null}\n </Button>\n </div>\n );\n }\n\n const links: QuickLink[] = [];\n if (entityIds.datasink)\n links.push({\n key: 'datasink',\n label: t('bigconsole.widget.aiPreviewMini.link.dataSink', 'Data Sink'),\n path: `/bigconsole/datasinks/${entityIds.datasink}`,\n icon: <Database className=\"size-4 shrink-0 text-text-muted\" aria-hidden=\"true\" />,\n });\n if (entityIds.dashboard)\n links.push({\n key: 'dashboard',\n label: t('bigconsole.widget.aiPreviewMini.link.dashboard', 'Dashboard'),\n path: `/bigconsole/dashboards/${entityIds.dashboard}`,\n icon: <LayoutDashboard className=\"size-4 shrink-0 text-text-muted\" aria-hidden=\"true\" />,\n });\n if (entityIds.parser)\n links.push({\n key: 'parser',\n label: t('bigconsole.widget.aiPreviewMini.link.parser', 'Parser'),\n path: `/bigconsole/parsers/${entityIds.parser}`,\n icon: <Braces className=\"size-4 shrink-0 text-text-muted\" aria-hidden=\"true\" />,\n });\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 // ★ The wrapper owns POSITIONING only — clearance for the assistant dock,\n // page gutters, and the testids this panel already had. Everything visual\n // belongs to the shared surface, which is what keeps this copy and the full\n // panel from drifting apart now that `previewShared` no longer holds a shell\n // recipe for them both.\n //\n // ★★ PADDING, not margin: `mx-4 md:mx-6` is the page gutter and it is\n // RESPONSIVE. See DOCK_CLEARANCE's docblock.\n <div\n data-testid=\"ai-preview-panel\"\n data-variant=\"mini\"\n style={{ paddingRight: DOCK_CLEARANCE }}\n className=\"mx-4 mb-3 mt-3 min-w-0 md:mx-6\"\n >\n {/* ★★ The panel's own heading, restored. The shared surface renders its\n title as a styled <span> inside a <section aria-label>, which labels\n the region but leaves NO heading — while it still renders an <h3> for\n the step rail. main had <h2>AI Preview</h2> above that <h3>, so the\n migration both removed the panel from screen-reader heading navigation\n and orphaned the h3 under whatever <h2> the page beneath happens to\n end with. Visually hidden because the surface already shows the title;\n this only restores the structure. */}\n <h2 className=\"sr-only\">{t('bigconsole.widget.aiPreviewMini.title', 'AI Preview')}</h2>\n <AssistantEntityPreview\n title={t('bigconsole.widget.aiPreviewMini.title', 'AI Preview')}\n steps={railSteps}\n // ★★ TWO branches, not three, and deliberately so: the shared surface\n // renders `{error ? '' : caption}` — it BLANKS the caption whenever\n // `error` is set, so a failure branch here would compute a string that\n // is then 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 headerActions={\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={closePreview}\n aria-label={t('bigconsole.widget.aiPreviewMini.closeAriaLabel', 'Close preview')}\n data-testid=\"ai-preview-close\"\n >\n <X className=\"size-4\" />\n </Button>\n }\n labels={previewLabels(t)}\n >\n {/* ★ No tabs here, by design: the rich per-entity views need Apollo and\n the BigConsole providers, which do not exist above the router. Links\n are what this copy can honestly offer. */}\n <div className=\"p-3\">\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title={t('bigconsole.widget.aiPreviewMini.builtSoFar', 'Built so far')}\n description={t('bigconsole.widget.aiPreviewMini.builtSoFarHint', 'Open any piece to see the real thing.')}\n bodyClassName=\"flex flex-col gap-list-gap\"\n >\n {links.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\n {isRunning\n ? t(\n 'bigconsole.widget.aiPreviewMini.building',\n 'Building… links appear here as each piece is created.'\n )\n : t('bigconsole.widget.aiPreviewMini.nothingBuilt', 'Nothing built yet.')}\n </p>\n ) : (\n links.map((link) => (\n <div\n key={link.key}\n data-testid=\"ai-preview-row\"\n className=\"flex min-w-0 items-center justify-between gap-control-gap rounded-card border border-[var(--color-border-subtle)] bg-bg-canvas px-3 py-2\"\n >\n <p className=\"flex min-w-0 items-center gap-icon-gap text-sm font-medium text-text-primary\">\n {link.icon}\n <span className=\"truncate\">\n {t('bigconsole.widget.aiPreviewMini.linkReady', '{{label}} ready', { label: link.label })}\n </span>\n </p>\n <Button size=\"sm\" variant=\"ghost\" onClick={() => go(link.path)}>\n {t('bigconsole.widget.aiPreviewMini.open', 'Open')}\n <ArrowUpRight className=\"ml-1 size-3.5\" />\n </Button>\n </div>\n ))\n )}\n {entityIds.dashboard ? (\n // fe-libs `Button` is `whitespace-nowrap` with a fixed `h-8` at\n // `size=\"sm\"`, so this long label overflowed the panel (which is\n // `overflow-hidden`) at 390px — and the hi/ta strings are longer\n // still. `h-auto whitespace-normal` lets it wrap to two lines\n // instead of being clipped.\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"h-auto w-full whitespace-normal py-1.5 text-center\"\n data-testid=\"ai-preview-open-in-bigconsole\"\n onClick={() => go(`/bigconsole/dashboards/${entityIds.dashboard ?? ''}`)}\n >\n {t('bigconsole.widget.aiPreviewMini.openInBigConsole', 'Open in BigConsole for the live view')}\n <ArrowUpRight className=\"ml-1 size-3.5\" />\n </Button>\n ) : null}\n </Section>\n </div>\n </AssistantEntityPreview>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useBigConsoleNavigate as e } from "../../context/BigConsoleContext.js";
|
|
2
2
|
import "../../context/index.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { PREVIEW_STEPS as t, useAssistantRunStore as n } from "../../assistant/assistantRunStore.js";
|
|
4
|
+
import { useAssistantRunWorkspace as r } from "../../assistant/useAssistantRunWorkspace.js";
|
|
5
5
|
import { Section as i } from "../common/Section.js";
|
|
6
6
|
import "../common/index.js";
|
|
7
7
|
import { DOCK_CLEARANCE as a, PreviewMeta as o, STEP_LABEL as s, formatAgo as c, previewLabels as l } from "./previewShared.js";
|
|
@@ -31,37 +31,39 @@ function D({ onOpen: e }) {
|
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
function O() {
|
|
34
|
-
let { t: _ } = w()
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
let { t: _ } = w();
|
|
35
|
+
r();
|
|
36
|
+
let O = e(), k = n((e) => e.isRunning), A = n((e) => e.previewOpen), j = n((e) => e.hasRun), M = n((e) => e.steps), N = n((e) => e.entityIds), P = n((e) => e.caption), F = n((e) => e.error), I = n((e) => e.updatedAt), L = n((e) => e.openPreview), R = n((e) => e.closePreview), z = n((e) => e.setRichMounted);
|
|
37
|
+
m(() => (z(!0), () => {
|
|
38
|
+
z(!1);
|
|
39
|
+
}), [z]);
|
|
40
|
+
let [B, V] = g(0), H = h(!1);
|
|
39
41
|
if (m(() => {
|
|
40
|
-
|
|
41
|
-
}, [
|
|
42
|
-
if (!
|
|
42
|
+
k && !H.current && V((e) => e + 1), H.current = k;
|
|
43
|
+
}, [k]), !j) return null;
|
|
44
|
+
if (!A) return /* @__PURE__ */ C("div", {
|
|
43
45
|
className: "flex flex-wrap items-center justify-end gap-control-gap px-4 pt-3 md:px-6",
|
|
44
46
|
style: { marginRight: a },
|
|
45
47
|
children: [/* @__PURE__ */ S("span", {
|
|
46
48
|
className: "text-xs text-text-muted",
|
|
47
|
-
children:
|
|
49
|
+
children: k ? _("bigconsole.widget.aiPreview.buildRunning", "AI build running") : _("bigconsole.widget.aiPreview.buildFinished", "AI build finished")
|
|
48
50
|
}), /* @__PURE__ */ C(x, {
|
|
49
51
|
size: "sm",
|
|
50
52
|
variant: "outline",
|
|
51
|
-
onClick:
|
|
53
|
+
onClick: L,
|
|
52
54
|
"data-testid": "ai-preview-open",
|
|
53
55
|
children: [
|
|
54
56
|
/* @__PURE__ */ S(y, { className: "mr-1.5 size-4" }),
|
|
55
57
|
_("bigconsole.widget.aiPreview.previewButton", "Preview"),
|
|
56
|
-
|
|
58
|
+
k ? /* @__PURE__ */ S(v, { className: "ml-1.5 size-3.5 animate-spin" }) : null
|
|
57
59
|
]
|
|
58
60
|
})]
|
|
59
61
|
});
|
|
60
|
-
let
|
|
62
|
+
let U = N.dashboard, W = t.map((e) => ({
|
|
61
63
|
id: e,
|
|
62
64
|
label: _(s[e].key, s[e].fallback),
|
|
63
|
-
status:
|
|
64
|
-
})),
|
|
65
|
+
status: M[e]
|
|
66
|
+
})), G = [
|
|
65
67
|
{
|
|
66
68
|
id: "datasink",
|
|
67
69
|
label: _("bigconsole.widget.aiPreview.tab.dataSink", "Data Sink"),
|
|
@@ -71,10 +73,10 @@ function O() {
|
|
|
71
73
|
headingLevel: 3,
|
|
72
74
|
title: _("bigconsole.widget.aiPreview.dataSinkRows", "Data sink rows"),
|
|
73
75
|
description: _("bigconsole.widget.aiPreview.dataSinkRowsHint", "The rows the assistant just landed, straight from the sink."),
|
|
74
|
-
actions:
|
|
76
|
+
actions: N.datasink ? /* @__PURE__ */ S(D, { onOpen: () => O(`/datasinks/${N.datasink}`) }) : void 0,
|
|
75
77
|
children: /* @__PURE__ */ S(d, {
|
|
76
|
-
id:
|
|
77
|
-
live:
|
|
78
|
+
id: N.datasink,
|
|
79
|
+
live: k
|
|
78
80
|
})
|
|
79
81
|
}) })
|
|
80
82
|
},
|
|
@@ -87,8 +89,8 @@ function O() {
|
|
|
87
89
|
headingLevel: 3,
|
|
88
90
|
title: _("bigconsole.widget.aiPreview.tab.dashboard", "Dashboard"),
|
|
89
91
|
description: _("bigconsole.widget.aiPreview.dashboardHint", "The live board, rendered exactly as the real page renders it."),
|
|
90
|
-
actions:
|
|
91
|
-
children: /* @__PURE__ */ S(u, { dashboardId:
|
|
92
|
+
actions: U ? /* @__PURE__ */ S(D, { onOpen: () => O(`/dashboards/${U}`) }) : void 0,
|
|
93
|
+
children: /* @__PURE__ */ S(u, { dashboardId: U })
|
|
92
94
|
}) })
|
|
93
95
|
},
|
|
94
96
|
{
|
|
@@ -100,10 +102,10 @@ function O() {
|
|
|
100
102
|
headingLevel: 3,
|
|
101
103
|
title: _("bigconsole.widget.aiPreview.parserOutput", "Parser output"),
|
|
102
104
|
description: _("bigconsole.widget.aiPreview.parserOutputHint", "The rows after the parser's transform, so you can check the shape."),
|
|
103
|
-
actions:
|
|
105
|
+
actions: N.parser ? /* @__PURE__ */ S(D, { onOpen: () => O(`/parsers/${N.parser}`) }) : void 0,
|
|
104
106
|
children: /* @__PURE__ */ S(f, {
|
|
105
|
-
parserId:
|
|
106
|
-
live:
|
|
107
|
+
parserId: N.parser,
|
|
108
|
+
live: k
|
|
107
109
|
})
|
|
108
110
|
}) })
|
|
109
111
|
},
|
|
@@ -116,10 +118,10 @@ function O() {
|
|
|
116
118
|
headingLevel: 3,
|
|
117
119
|
title: _("bigconsole.widget.aiPreview.widgets", "Widgets"),
|
|
118
120
|
description: _("bigconsole.widget.aiPreview.widgetsHint", "Every widget on the new dashboard, rendered live."),
|
|
119
|
-
actions:
|
|
121
|
+
actions: U ? /* @__PURE__ */ S(D, { onOpen: () => O(`/dashboards/${U}`) }) : void 0,
|
|
120
122
|
children: /* @__PURE__ */ S(p, {
|
|
121
|
-
dashboardId:
|
|
122
|
-
live:
|
|
123
|
+
dashboardId: U,
|
|
124
|
+
live: k
|
|
123
125
|
})
|
|
124
126
|
}) })
|
|
125
127
|
}
|
|
@@ -133,23 +135,23 @@ function O() {
|
|
|
133
135
|
children: _("bigconsole.widget.aiPreview.title", "AI Preview")
|
|
134
136
|
}), /* @__PURE__ */ S(T, {
|
|
135
137
|
title: _("bigconsole.widget.aiPreview.title", "AI Preview"),
|
|
136
|
-
runKey: String(
|
|
137
|
-
steps:
|
|
138
|
-
tabs:
|
|
138
|
+
runKey: String(B),
|
|
139
|
+
steps: W,
|
|
140
|
+
tabs: G,
|
|
139
141
|
follow: "produced",
|
|
140
|
-
caption:
|
|
141
|
-
error:
|
|
142
|
+
caption: k ? P || _("bigconsole.widget.aiPreview.working", "Working…") : _("bigconsole.widget.aiPreview.done", "Done"),
|
|
143
|
+
error: F,
|
|
142
144
|
meta: /* @__PURE__ */ S(o, {
|
|
143
|
-
state:
|
|
144
|
-
stateLabel:
|
|
145
|
+
state: k ? "running" : F ? "stopped" : "idle",
|
|
146
|
+
stateLabel: k ? _("bigconsole.widget.aiPreview.live", "Live") : F ? _("bigconsole.widget.aiPreview.stopped", "Stopped") : null,
|
|
145
147
|
updatedLabel: _("bigconsole.widget.aiPreview.updated", "updated"),
|
|
146
|
-
updatedAt:
|
|
148
|
+
updatedAt: I,
|
|
147
149
|
ago: (e) => c(e, _)
|
|
148
150
|
}),
|
|
149
151
|
headerActions: /* @__PURE__ */ S(x, {
|
|
150
152
|
size: "sm",
|
|
151
153
|
variant: "ghost",
|
|
152
|
-
onClick:
|
|
154
|
+
onClick: R,
|
|
153
155
|
"aria-label": _("bigconsole.widget.aiPreview.closeAriaLabel", "Close preview"),
|
|
154
156
|
"data-testid": "ai-preview-close",
|
|
155
157
|
children: /* @__PURE__ */ S(b, { className: "size-4" })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiPreviewPanel.js","names":[],"sources":["../../../../src/bigconsole/components/preview/AiPreviewPanel.tsx"],"sourcesContent":["/**\n * AI Preview — watch the assistant build, with the real content inline.\n *\n * Opens by itself the moment a prompt starts. A step rail (driven by the agent's\n * own narration) shows DataSink → Dashboard → Parser → Widget filling in, and\n * the four tabs show the ACTUAL content of each entity as it lands — the data\n * sink's rows, the live dashboard, the parser's transformed output, the rendered\n * widget charts — not a list of links. Each tab has an \"Open\" button that jumps\n * to the real page.\n *\n * The content renderers need Apollo + the BigConsole providers, which is why\n * this full panel lives inside BigConsoleRoot; the store-only app-shell mini\n * panel (AiPreviewMiniPanel) keeps the lighter link view for other pages.\n *\n * ★★★ THE SHELL, RAIL AND TABS ARE fe-libs' NOW (`AssistantEntityPreview`).\n * What stays here is what is actually BigConsole's: which entities exist, how to\n * render each one, and where the panel sits relative to the assistant sidebar.\n * The shared surface brought three things this file could not have on its own:\n *\n * - a viewer's tab choice is HONOURED. The local `useEffect` re-selected a tab\n * on every store tick, so clicking one held for about 1.5 seconds before the\n * next progress update took it back — you could not read a tab while the\n * build continued.\n * - the rail can show a FAILED step instead of drawing a failure as \"not\n * started\" (the store still cannot produce one — see `assistantRunStore` —\n * but the surface no longer loses it if it ever does).\n * - keyboard navigation across the tabs, which the Radix tablist here never\n * had overflow handling for below ~430px.\n */\nimport { useEffect, useRef, useState, type ReactNode } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport {\n AssistantEntityPreview,\n type AssistantPreviewStep,\n type AssistantPreviewTab,\n} from '@burdenoff/fe-libs/shared/assistant';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useAssistantPanelShowsPreview } from '../../assistant/useAssistantPanelShowsPreview';\nimport { ArrowUpRight, Loader2, Sparkles, X } from 'lucide-react';\n\nimport { PREVIEW_STEPS, useAssistantRunStore } from '../../assistant/assistantRunStore';\nimport { useBigConsoleNavigate } from '../../context';\nimport { Section } from '../common';\nimport { DOCK_CLEARANCE, PreviewMeta, STEP_LABEL, formatAgo, previewLabels } from './previewShared';\nimport { DashboardContent, DataSinkContent, ParserContent, WidgetsContent } from './AiPreviewContent';\n\n/**\n * The scroll box each tab's content sits in.\n *\n * ★★ The cap is NOT cosmetic and does not belong in fe-libs: the shared surface\n * is laid out by whatever contains it, and this panel is injected ABOVE a\n * routed page — so without a cap a long data-sink preview pushes the whole page\n * down. `min(440px, 60vh)` is the original value; the hard 440px alone ate most\n * of the fold on a short laptop screen.\n */\nfunction TabBody({ children }: { children: ReactNode }) {\n return (\n <div className=\"min-w-0 overflow-y-auto p-3\" style={{ maxHeight: 'min(440px, 60vh)' }}>\n {children}\n </div>\n );\n}\n\n/** Jump-to-the-real-page button, shared by all four tabs. */\nfunction OpenEntityButton({ onOpen }: { onOpen: () => void }) {\n const { t } = useI18n();\n return (\n <Button size=\"sm\" variant=\"outline\" onClick={onOpen} data-testid=\"ai-preview-open-entity\">\n {t('bigconsole.widget.aiPreview.open', 'Open')}\n <ArrowUpRight className=\"ml-1 size-3.5\" />\n </Button>\n );\n}\n\nexport function AiPreviewPanel() {\n const { t } = useI18n();\n // ★★★ ONE SURFACE PER RUN. fe-libs renders this same controller inside its own\n // in-chat overlay; without this the page surface stayed up beside it and one\n // run was drawn twice, side by side (measured in prod, BOFF-7799).\n const panelShowsPreview = useAssistantPanelShowsPreview();\n const navigate = useBigConsoleNavigate();\n\n const isRunning = useAssistantRunStore((state) => state.isRunning);\n const previewOpen = useAssistantRunStore((state) => state.previewOpen);\n const hasRun = useAssistantRunStore((state) => state.hasRun);\n const steps = useAssistantRunStore((state) => state.steps);\n const entityIds = useAssistantRunStore((state) => state.entityIds);\n const caption = useAssistantRunStore((state) => state.caption);\n const error = useAssistantRunStore((state) => state.error);\n const updatedAt = useAssistantRunStore((state) => state.updatedAt);\n const openPreview = useAssistantRunStore((state) => state.openPreview);\n const closePreview = useAssistantRunStore((state) => state.closePreview);\n const setRichMounted = useAssistantRunStore((state) => state.setRichMounted);\n\n useEffect(() => {\n setRichMounted(true);\n return () => {\n setRichMounted(false);\n };\n }, [setRichMounted]);\n\n /**\n * ★★★ WHAT COUNTS AS A \"RUN\" HERE, for the purpose of releasing a pinned tab.\n *\n * The store has no run id and deliberately never resets — a new prompt\n * CONTINUES the same build, preserving every step and entity id already made\n * (`assistantRunStore.startRun`). So the key is minted here, on the rising\n * edge of `isRunning`: each new turn is a new run, which releases a tab the\n * viewer pinned during the previous one. That is the right side to err on —\n * asking for something new and being shown the old tab is worse than losing a\n * pin you made a turn ago.\n */\n const [runKey, setRunKey] = useState(0);\n const wasRunningRef = useRef(false);\n useEffect(() => {\n if (isRunning && !wasRunningRef.current) setRunKey((n) => n + 1);\n wasRunningRef.current = isRunning;\n }, [isRunning]);\n\n if (!hasRun) return null;\n // The shared panel's overlay is already showing this run — stand down rather\n // than draw it a second time beside itself.\n if (panelShowsPreview) return null;\n\n if (!previewOpen) {\n return (\n <div\n className=\"flex flex-wrap items-center justify-end gap-control-gap px-4 pt-3 md:px-6\"\n style={{ marginRight: DOCK_CLEARANCE }}\n >\n {/* The lone button gave no clue what it belonged to. */}\n <span className=\"text-xs text-text-muted\">\n {isRunning\n ? t('bigconsole.widget.aiPreview.buildRunning', 'AI build running')\n : t('bigconsole.widget.aiPreview.buildFinished', 'AI build finished')}\n </span>\n <Button size=\"sm\" variant=\"outline\" onClick={openPreview} data-testid=\"ai-preview-open\">\n <Sparkles className=\"mr-1.5 size-4\" />\n {t('bigconsole.widget.aiPreview.previewButton', 'Preview')}\n {isRunning ? <Loader2 className=\"ml-1.5 size-3.5 animate-spin\" /> : null}\n </Button>\n </div>\n );\n }\n\n const dashboardId = entityIds.dashboard;\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 const tabs: AssistantPreviewTab[] = [\n {\n id: 'datasink',\n label: t('bigconsole.widget.aiPreview.tab.dataSink', 'Data Sink'),\n render: () => (\n <TabBody>\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title={t('bigconsole.widget.aiPreview.dataSinkRows', 'Data sink rows')}\n description={t(\n 'bigconsole.widget.aiPreview.dataSinkRowsHint',\n 'The rows the assistant just landed, straight from the sink.'\n )}\n actions={\n entityIds.datasink ? (\n <OpenEntityButton onOpen={() => navigate(`/datasinks/${entityIds.datasink}`)} />\n ) : undefined\n }\n >\n <DataSinkContent id={entityIds.datasink} live={isRunning} />\n </Section>\n </TabBody>\n ),\n },\n {\n id: 'dashboard',\n label: t('bigconsole.widget.aiPreview.tab.dashboard', 'Dashboard'),\n render: () => (\n <TabBody>\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title={t('bigconsole.widget.aiPreview.tab.dashboard', 'Dashboard')}\n description={t(\n 'bigconsole.widget.aiPreview.dashboardHint',\n 'The live board, rendered exactly as the real page renders it.'\n )}\n actions={\n dashboardId ? <OpenEntityButton onOpen={() => navigate(`/dashboards/${dashboardId}`)} /> : undefined\n }\n >\n <DashboardContent dashboardId={dashboardId} />\n </Section>\n </TabBody>\n ),\n },\n {\n id: 'parser',\n label: t('bigconsole.widget.aiPreview.tab.parser', 'Parser'),\n render: () => (\n <TabBody>\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title={t('bigconsole.widget.aiPreview.parserOutput', 'Parser output')}\n description={t(\n 'bigconsole.widget.aiPreview.parserOutputHint',\n \"The rows after the parser's transform, so you can check the shape.\"\n )}\n actions={\n entityIds.parser ? (\n <OpenEntityButton onOpen={() => navigate(`/parsers/${entityIds.parser}`)} />\n ) : undefined\n }\n >\n <ParserContent parserId={entityIds.parser} live={isRunning} />\n </Section>\n </TabBody>\n ),\n },\n {\n id: 'widget',\n label: t('bigconsole.widget.aiPreview.tab.widget', 'Widget'),\n render: () => (\n <TabBody>\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title={t('bigconsole.widget.aiPreview.widgets', 'Widgets')}\n description={t(\n 'bigconsole.widget.aiPreview.widgetsHint',\n 'Every widget on the new dashboard, rendered live.'\n )}\n actions={\n dashboardId ? <OpenEntityButton onOpen={() => navigate(`/dashboards/${dashboardId}`)} /> : undefined\n }\n >\n <WidgetsContent dashboardId={dashboardId} live={isRunning} />\n </Section>\n </TabBody>\n ),\n },\n ];\n\n return (\n // ★ The wrapper owns POSITIONING only — clearance for the assistant dock,\n // page gutters, and the testid BigConsole already had. Everything visual\n // belongs to the shared surface.\n //\n // ★★ PADDING, not margin: `mx-4 md:mx-6` is the page gutter and it is\n // RESPONSIVE. See DOCK_CLEARANCE's docblock.\n <div data-testid=\"ai-preview-panel\" style={{ paddingRight: DOCK_CLEARANCE }} className=\"mx-4 mt-3 min-w-0 md:mx-6\">\n {/* ★★ The panel's own heading, restored. The shared surface renders its\n title as a styled <span> inside a <section aria-label>, which labels\n the region but leaves NO heading — while it still renders an <h3> for\n the step rail. main had <h2>AI Preview</h2> above that <h3>, so the\n migration both removed the panel from screen-reader heading navigation\n and orphaned the h3 under whatever <h2> the page beneath happens to\n end with. Visually hidden because the surface already shows the title;\n this only restores the structure. */}\n <h2 className=\"sr-only\">{t('bigconsole.widget.aiPreview.title', 'AI Preview')}</h2>\n <AssistantEntityPreview\n title={t('bigconsole.widget.aiPreview.title', 'AI Preview')}\n runKey={String(runKey)}\n steps={railSteps}\n tabs={tabs}\n // ★ Explicit, though it is the default: this panel's own rule was\n // \"show the LATEST thing that actually landed — so the moment the data\n // sink is created you see its rows, not an empty 'waiting for the\n // dashboard' tab\". Stating it keeps that reasoning attached to the\n // consumer that discovered it, not only to the library that adopted it.\n follow=\"produced\"\n // ★★ TWO branches, not three, and deliberately so: the shared surface\n // renders `{error ? '' : caption}` — it BLANKS the caption whenever\n // `error` is set, so a failure branch here would compute a string that\n // is then discarded. The failure is named in `meta` instead, which\n // `PreviewHeader` renders without consulting `error`.\n caption={\n isRunning\n ? caption || t('bigconsole.widget.aiPreview.working', 'Working…')\n : t('bigconsole.widget.aiPreview.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.aiPreview.live', 'Live')\n : error\n ? t('bigconsole.widget.aiPreview.stopped', 'Stopped')\n : null\n }\n updatedLabel={t('bigconsole.widget.aiPreview.updated', 'updated')}\n updatedAt={updatedAt}\n ago={(seconds) => formatAgo(seconds, t)}\n />\n }\n headerActions={\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={closePreview}\n aria-label={t('bigconsole.widget.aiPreview.closeAriaLabel', 'Close preview')}\n data-testid=\"ai-preview-close\"\n >\n <X className=\"size-4\" />\n </Button>\n }\n labels={previewLabels(t)}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AiPreviewPanel.js","names":[],"sources":["../../../../src/bigconsole/components/preview/AiPreviewPanel.tsx"],"sourcesContent":["/**\n * AI Preview — watch the assistant build, with the real content inline.\n *\n * Opens by itself the moment a prompt starts. A step rail (driven by the agent's\n * own narration) shows DataSink → Dashboard → Parser → Widget filling in, and\n * the four tabs show the ACTUAL content of each entity as it lands — the data\n * sink's rows, the live dashboard, the parser's transformed output, the rendered\n * widget charts — not a list of links. Each tab has an \"Open\" button that jumps\n * to the real page.\n *\n * The content renderers need Apollo + the BigConsole providers, which is why\n * this full panel lives inside BigConsoleRoot; the store-only app-shell mini\n * panel (AiPreviewMiniPanel) keeps the lighter link view for other pages.\n *\n * ★★★ THE SHELL, RAIL AND TABS ARE fe-libs' NOW (`AssistantEntityPreview`).\n * What stays here is what is actually BigConsole's: which entities exist, how to\n * render each one, and where the panel sits relative to the assistant sidebar.\n * The shared surface brought three things this file could not have on its own:\n *\n * - a viewer's tab choice is HONOURED. The local `useEffect` re-selected a tab\n * on every store tick, so clicking one held for about 1.5 seconds before the\n * next progress update took it back — you could not read a tab while the\n * build continued.\n * - the rail can show a FAILED step instead of drawing a failure as \"not\n * started\" (the store still cannot produce one — see `assistantRunStore` —\n * but the surface no longer loses it if it ever does).\n * - keyboard navigation across the tabs, which the Radix tablist here never\n * had overflow handling for below ~430px.\n */\nimport { useEffect, useRef, useState, type ReactNode } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport {\n AssistantEntityPreview,\n type AssistantPreviewStep,\n type AssistantPreviewTab,\n} from '@burdenoff/fe-libs/shared/assistant';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowUpRight, Loader2, Sparkles, X } from 'lucide-react';\n\nimport { PREVIEW_STEPS, useAssistantRunStore } from '../../assistant/assistantRunStore';\nimport { useAssistantRunWorkspace } from '../../assistant/useAssistantRunWorkspace';\nimport { useBigConsoleNavigate } from '../../context';\nimport { Section } from '../common';\nimport { DOCK_CLEARANCE, PreviewMeta, STEP_LABEL, formatAgo, previewLabels } from './previewShared';\nimport { DashboardContent, DataSinkContent, ParserContent, WidgetsContent } from './AiPreviewContent';\n\n/**\n * The scroll box each tab's content sits in.\n *\n * ★★ The cap is NOT cosmetic and does not belong in fe-libs: the shared surface\n * is laid out by whatever contains it, and this panel is injected ABOVE a\n * routed page — so without a cap a long data-sink preview pushes the whole page\n * down. `min(440px, 60vh)` is the original value; the hard 440px alone ate most\n * of the fold on a short laptop screen.\n */\nfunction TabBody({ children }: { children: ReactNode }) {\n return (\n <div className=\"min-w-0 overflow-y-auto p-3\" style={{ maxHeight: 'min(440px, 60vh)' }}>\n {children}\n </div>\n );\n}\n\n/** Jump-to-the-real-page button, shared by all four tabs. */\nfunction OpenEntityButton({ onOpen }: { onOpen: () => void }) {\n const { t } = useI18n();\n return (\n <Button size=\"sm\" variant=\"outline\" onClick={onOpen} data-testid=\"ai-preview-open-entity\">\n {t('bigconsole.widget.aiPreview.open', 'Open')}\n <ArrowUpRight className=\"ml-1 size-3.5\" />\n </Button>\n );\n}\n\nexport function AiPreviewPanel() {\n const { t } = useI18n();\n // ★★★ THE PAGE SURFACES ARE THE ONLY SURFACES (BOFF-7799). The assistant's\n // controller is headless, so nothing inside the panel competes with this one\n // and there is nothing to stand down for. Binding the workspace restores the\n // run after a reload in this tab and clears it on a workspace switch.\n useAssistantRunWorkspace();\n const navigate = useBigConsoleNavigate();\n\n const isRunning = useAssistantRunStore((state) => state.isRunning);\n const previewOpen = useAssistantRunStore((state) => state.previewOpen);\n const hasRun = useAssistantRunStore((state) => state.hasRun);\n const steps = useAssistantRunStore((state) => state.steps);\n const entityIds = useAssistantRunStore((state) => state.entityIds);\n const caption = useAssistantRunStore((state) => state.caption);\n const error = useAssistantRunStore((state) => state.error);\n const updatedAt = useAssistantRunStore((state) => state.updatedAt);\n const openPreview = useAssistantRunStore((state) => state.openPreview);\n const closePreview = useAssistantRunStore((state) => state.closePreview);\n const setRichMounted = useAssistantRunStore((state) => state.setRichMounted);\n\n useEffect(() => {\n setRichMounted(true);\n return () => {\n setRichMounted(false);\n };\n }, [setRichMounted]);\n\n /**\n * ★★★ WHAT COUNTS AS A \"RUN\" HERE, for the purpose of releasing a pinned tab.\n *\n * The store has no run id and deliberately never resets — a new prompt\n * CONTINUES the same build, preserving every step and entity id already made\n * (`assistantRunStore.startRun`). So the key is minted here, on the rising\n * edge of `isRunning`: each new turn is a new run, which releases a tab the\n * viewer pinned during the previous one. That is the right side to err on —\n * asking for something new and being shown the old tab is worse than losing a\n * pin you made a turn ago.\n */\n const [runKey, setRunKey] = useState(0);\n const wasRunningRef = useRef(false);\n useEffect(() => {\n if (isRunning && !wasRunningRef.current) setRunKey((n) => n + 1);\n wasRunningRef.current = isRunning;\n }, [isRunning]);\n\n if (!hasRun) return null;\n\n if (!previewOpen) {\n return (\n <div\n className=\"flex flex-wrap items-center justify-end gap-control-gap px-4 pt-3 md:px-6\"\n style={{ marginRight: DOCK_CLEARANCE }}\n >\n {/* The lone button gave no clue what it belonged to. */}\n <span className=\"text-xs text-text-muted\">\n {isRunning\n ? t('bigconsole.widget.aiPreview.buildRunning', 'AI build running')\n : t('bigconsole.widget.aiPreview.buildFinished', 'AI build finished')}\n </span>\n <Button size=\"sm\" variant=\"outline\" onClick={openPreview} data-testid=\"ai-preview-open\">\n <Sparkles className=\"mr-1.5 size-4\" />\n {t('bigconsole.widget.aiPreview.previewButton', 'Preview')}\n {isRunning ? <Loader2 className=\"ml-1.5 size-3.5 animate-spin\" /> : null}\n </Button>\n </div>\n );\n }\n\n const dashboardId = entityIds.dashboard;\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 const tabs: AssistantPreviewTab[] = [\n {\n id: 'datasink',\n label: t('bigconsole.widget.aiPreview.tab.dataSink', 'Data Sink'),\n render: () => (\n <TabBody>\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title={t('bigconsole.widget.aiPreview.dataSinkRows', 'Data sink rows')}\n description={t(\n 'bigconsole.widget.aiPreview.dataSinkRowsHint',\n 'The rows the assistant just landed, straight from the sink.'\n )}\n actions={\n entityIds.datasink ? (\n <OpenEntityButton onOpen={() => navigate(`/datasinks/${entityIds.datasink}`)} />\n ) : undefined\n }\n >\n <DataSinkContent id={entityIds.datasink} live={isRunning} />\n </Section>\n </TabBody>\n ),\n },\n {\n id: 'dashboard',\n label: t('bigconsole.widget.aiPreview.tab.dashboard', 'Dashboard'),\n render: () => (\n <TabBody>\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title={t('bigconsole.widget.aiPreview.tab.dashboard', 'Dashboard')}\n description={t(\n 'bigconsole.widget.aiPreview.dashboardHint',\n 'The live board, rendered exactly as the real page renders it.'\n )}\n actions={\n dashboardId ? <OpenEntityButton onOpen={() => navigate(`/dashboards/${dashboardId}`)} /> : undefined\n }\n >\n <DashboardContent dashboardId={dashboardId} />\n </Section>\n </TabBody>\n ),\n },\n {\n id: 'parser',\n label: t('bigconsole.widget.aiPreview.tab.parser', 'Parser'),\n render: () => (\n <TabBody>\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title={t('bigconsole.widget.aiPreview.parserOutput', 'Parser output')}\n description={t(\n 'bigconsole.widget.aiPreview.parserOutputHint',\n \"The rows after the parser's transform, so you can check the shape.\"\n )}\n actions={\n entityIds.parser ? (\n <OpenEntityButton onOpen={() => navigate(`/parsers/${entityIds.parser}`)} />\n ) : undefined\n }\n >\n <ParserContent parserId={entityIds.parser} live={isRunning} />\n </Section>\n </TabBody>\n ),\n },\n {\n id: 'widget',\n label: t('bigconsole.widget.aiPreview.tab.widget', 'Widget'),\n render: () => (\n <TabBody>\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title={t('bigconsole.widget.aiPreview.widgets', 'Widgets')}\n description={t(\n 'bigconsole.widget.aiPreview.widgetsHint',\n 'Every widget on the new dashboard, rendered live.'\n )}\n actions={\n dashboardId ? <OpenEntityButton onOpen={() => navigate(`/dashboards/${dashboardId}`)} /> : undefined\n }\n >\n <WidgetsContent dashboardId={dashboardId} live={isRunning} />\n </Section>\n </TabBody>\n ),\n },\n ];\n\n return (\n // ★ The wrapper owns POSITIONING only — clearance for the assistant dock,\n // page gutters, and the testid BigConsole already had. Everything visual\n // belongs to the shared surface.\n //\n // ★★ PADDING, not margin: `mx-4 md:mx-6` is the page gutter and it is\n // RESPONSIVE. See DOCK_CLEARANCE's docblock.\n <div data-testid=\"ai-preview-panel\" style={{ paddingRight: DOCK_CLEARANCE }} className=\"mx-4 mt-3 min-w-0 md:mx-6\">\n {/* ★★ The panel's own heading, restored. The shared surface renders its\n title as a styled <span> inside a <section aria-label>, which labels\n the region but leaves NO heading — while it still renders an <h3> for\n the step rail. main had <h2>AI Preview</h2> above that <h3>, so the\n migration both removed the panel from screen-reader heading navigation\n and orphaned the h3 under whatever <h2> the page beneath happens to\n end with. Visually hidden because the surface already shows the title;\n this only restores the structure. */}\n <h2 className=\"sr-only\">{t('bigconsole.widget.aiPreview.title', 'AI Preview')}</h2>\n <AssistantEntityPreview\n title={t('bigconsole.widget.aiPreview.title', 'AI Preview')}\n runKey={String(runKey)}\n steps={railSteps}\n tabs={tabs}\n // ★ Explicit, though it is the default: this panel's own rule was\n // \"show the LATEST thing that actually landed — so the moment the data\n // sink is created you see its rows, not an empty 'waiting for the\n // dashboard' tab\". Stating it keeps that reasoning attached to the\n // consumer that discovered it, not only to the library that adopted it.\n follow=\"produced\"\n // ★★ TWO branches, not three, and deliberately so: the shared surface\n // renders `{error ? '' : caption}` — it BLANKS the caption whenever\n // `error` is set, so a failure branch here would compute a string that\n // is then discarded. The failure is named in `meta` instead, which\n // `PreviewHeader` renders without consulting `error`.\n caption={\n isRunning\n ? caption || t('bigconsole.widget.aiPreview.working', 'Working…')\n : t('bigconsole.widget.aiPreview.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.aiPreview.live', 'Live')\n : error\n ? t('bigconsole.widget.aiPreview.stopped', 'Stopped')\n : null\n }\n updatedLabel={t('bigconsole.widget.aiPreview.updated', 'updated')}\n updatedAt={updatedAt}\n ago={(seconds) => formatAgo(seconds, t)}\n />\n }\n headerActions={\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={closePreview}\n aria-label={t('bigconsole.widget.aiPreview.closeAriaLabel', 'Close preview')}\n data-testid=\"ai-preview-close\"\n >\n <X className=\"size-4\" />\n </Button>\n }\n labels={previewLabels(t)}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AAuDA,SAAS,EAAQ,EAAE,eAAqC;AACtD,QACE,kBAAC,OAAD;EAAK,WAAU;EAA8B,OAAO,EAAE,WAAW,oBAAoB;EAClF;EACG,CAAA;;AAKV,SAAS,EAAiB,EAAE,aAAkC;CAC5D,IAAM,EAAE,MAAM,GAAS;AACvB,QACE,kBAAC,GAAD;EAAQ,MAAK;EAAK,SAAQ;EAAU,SAAS;EAAQ,eAAY;YAAjE,CACG,EAAE,oCAAoC,OAAO,EAC9C,kBAAC,GAAD,EAAc,WAAU,iBAAkB,CAAA,CACnC;;;AAIb,SAAgB,IAAiB;CAC/B,IAAM,EAAE,SAAM,GAAS;AAKvB,IAA0B;CAC1B,IAAM,IAAW,GAAuB,EAElC,IAAY,GAAsB,MAAU,EAAM,UAAU,EAC5D,IAAc,GAAsB,MAAU,EAAM,YAAY,EAChE,IAAS,GAAsB,MAAU,EAAM,OAAO,EACtD,IAAQ,GAAsB,MAAU,EAAM,MAAM,EACpD,IAAY,GAAsB,MAAU,EAAM,UAAU,EAC5D,IAAU,GAAsB,MAAU,EAAM,QAAQ,EACxD,IAAQ,GAAsB,MAAU,EAAM,MAAM,EACpD,IAAY,GAAsB,MAAU,EAAM,UAAU,EAC5D,IAAc,GAAsB,MAAU,EAAM,YAAY,EAChE,IAAe,GAAsB,MAAU,EAAM,aAAa,EAClE,IAAiB,GAAsB,MAAU,EAAM,eAAe;AAE5E,UACE,EAAe,GAAK,QACP;AACX,IAAe,GAAM;KAEtB,CAAC,EAAe,CAAC;CAapB,IAAM,CAAC,GAAQ,KAAa,EAAS,EAAE,EACjC,IAAgB,EAAO,GAAM;AAMnC,KALA,QAAgB;AAEd,EADI,KAAa,CAAC,EAAc,WAAS,GAAW,MAAM,IAAI,EAAE,EAChE,EAAc,UAAU;IACvB,CAAC,EAAU,CAAC,EAEX,CAAC,EAAQ,QAAO;AAEpB,KAAI,CAAC,EACH,QACE,kBAAC,OAAD;EACE,WAAU;EACV,OAAO,EAAE,aAAa,GAAgB;YAFxC,CAKE,kBAAC,QAAD;GAAM,WAAU;aACb,IACG,EAAE,4CAA4C,mBAAmB,GACjE,EAAE,6CAA6C,oBAAoB;GAClE,CAAA,EACP,kBAAC,GAAD;GAAQ,MAAK;GAAK,SAAQ;GAAU,SAAS;GAAa,eAAY;aAAtE;IACE,kBAAC,GAAD,EAAU,WAAU,iBAAkB,CAAA;IACrC,EAAE,6CAA6C,UAAU;IACzD,IAAY,kBAAC,GAAD,EAAS,WAAU,gCAAiC,CAAA,GAAG;IAC7D;KACL;;CAIV,IAAM,IAAc,EAAU,WAExB,IAAoC,EAAc,KAAK,OAAS;EACpE,IAAI;EACJ,OAAO,EAAE,EAAW,GAAK,KAAK,EAAW,GAAK,SAAS;EACvD,QAAQ,EAAM;EACf,EAAE,EAEG,IAA8B;EAClC;GACE,IAAI;GACJ,OAAO,EAAE,4CAA4C,YAAY;GACjE,cACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;IACE,UAAS;IACT,SAAQ;IACR,cAAc;IACd,OAAO,EAAE,4CAA4C,iBAAiB;IACtE,aAAa,EACX,gDACA,8DACD;IACD,SACE,EAAU,WACR,kBAAC,GAAD,EAAkB,cAAc,EAAS,cAAc,EAAU,WAAW,EAAI,CAAA,GAC9E,KAAA;cAGN,kBAAC,GAAD;KAAiB,IAAI,EAAU;KAAU,MAAM;KAAa,CAAA;IACpD,CAAA,EACF,CAAA;GAEb;EACD;GACE,IAAI;GACJ,OAAO,EAAE,6CAA6C,YAAY;GAClE,cACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;IACE,UAAS;IACT,SAAQ;IACR,cAAc;IACd,OAAO,EAAE,6CAA6C,YAAY;IAClE,aAAa,EACX,6CACA,gEACD;IACD,SACE,IAAc,kBAAC,GAAD,EAAkB,cAAc,EAAS,eAAe,IAAc,EAAI,CAAA,GAAG,KAAA;cAG7F,kBAAC,GAAD,EAA+B,gBAAe,CAAA;IACtC,CAAA,EACF,CAAA;GAEb;EACD;GACE,IAAI;GACJ,OAAO,EAAE,0CAA0C,SAAS;GAC5D,cACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;IACE,UAAS;IACT,SAAQ;IACR,cAAc;IACd,OAAO,EAAE,4CAA4C,gBAAgB;IACrE,aAAa,EACX,gDACA,qEACD;IACD,SACE,EAAU,SACR,kBAAC,GAAD,EAAkB,cAAc,EAAS,YAAY,EAAU,SAAS,EAAI,CAAA,GAC1E,KAAA;cAGN,kBAAC,GAAD;KAAe,UAAU,EAAU;KAAQ,MAAM;KAAa,CAAA;IACtD,CAAA,EACF,CAAA;GAEb;EACD;GACE,IAAI;GACJ,OAAO,EAAE,0CAA0C,SAAS;GAC5D,cACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;IACE,UAAS;IACT,SAAQ;IACR,cAAc;IACd,OAAO,EAAE,uCAAuC,UAAU;IAC1D,aAAa,EACX,2CACA,oDACD;IACD,SACE,IAAc,kBAAC,GAAD,EAAkB,cAAc,EAAS,eAAe,IAAc,EAAI,CAAA,GAAG,KAAA;cAG7F,kBAAC,GAAD;KAA6B;KAAa,MAAM;KAAa,CAAA;IACrD,CAAA,EACF,CAAA;GAEb;EACF;AAED,QAOE,kBAAC,OAAD;EAAK,eAAY;EAAmB,OAAO,EAAE,cAAc,GAAgB;EAAE,WAAU;YAAvF,CASE,kBAAC,MAAD;GAAI,WAAU;aAAW,EAAE,qCAAqC,aAAa;GAAM,CAAA,EACnF,kBAAC,GAAD;GACE,OAAO,EAAE,qCAAqC,aAAa;GAC3D,QAAQ,OAAO,EAAO;GACtB,OAAO;GACD;GAMN,QAAO;GAMP,SACE,IACI,KAAW,EAAE,uCAAuC,WAAW,GAC/D,EAAE,oCAAoC,OAAO;GAE5C;GACP,MACE,kBAAC,GAAD;IACE,OAAO,IAAY,YAAY,IAAQ,YAAY;IACnD,YACE,IACI,EAAE,oCAAoC,OAAO,GAC7C,IACE,EAAE,uCAAuC,UAAU,GACnD;IAER,cAAc,EAAE,uCAAuC,UAAU;IACtD;IACX,MAAM,MAAY,EAAU,GAAS,EAAE;IACvC,CAAA;GAEJ,eACE,kBAAC,GAAD;IACE,MAAK;IACL,SAAQ;IACR,SAAS;IACT,cAAY,EAAE,8CAA8C,gBAAgB;IAC5E,eAAY;cAEZ,kBAAC,GAAD,EAAG,WAAU,UAAW,CAAA;IACjB,CAAA;GAEX,QAAQ,EAAc,EAAE;GACxB,CAAA,CACE"}
|
|
@@ -4,7 +4,7 @@ import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
|
4
4
|
function i(e, t) {
|
|
5
5
|
return e < 5 ? t("bigconsole.widget.aiPreview.agoJustNow", "just now") : e < 60 ? t("bigconsole.widget.aiPreview.agoSeconds", "{{n}}s ago", { n: e }) : e < 3600 ? t("bigconsole.widget.aiPreview.agoMinutes", "{{n}}m ago", { n: Math.floor(e / 60) }) : t("bigconsole.widget.aiPreview.agoHours", "{{n}}h ago", { n: Math.floor(e / 3600) });
|
|
6
6
|
}
|
|
7
|
-
var a = "var(--assistant-dock-width, 0px)", o = {
|
|
7
|
+
var a = "var(--assistant-panel-inset, var(--assistant-dock-width, 0px))", o = {
|
|
8
8
|
datasink: {
|
|
9
9
|
key: "bigconsole.widget.aiPreview.tab.dataSink",
|
|
10
10
|
fallback: "Data Sink"
|