@burdenoff/microfe-bigconsole 2026.922.2 → 2026.924.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.
@@ -19,7 +19,6 @@ var e = {
19
19
  "Create a parser that splits the address column",
20
20
  "Show me what this data sink contains"
21
21
  ],
22
- requiresAdmin: !0,
23
22
  writeCapable: !0,
24
23
  streamBudgetMs: 42e4,
25
24
  accumulatesNarration: !0,
@@ -1 +1 @@
1
- {"version":3,"file":"modes.js","names":[],"sources":["../../../src/bigconsole/assistant/modes.ts"],"sourcesContent":["/**\n * The modes BigConsole offers the shared assistant panel.\n *\n * ★★ Mode IDS are transport values, not labels. The id picks the sandbox image,\n * the env that image boots with, and the agent persona; `label` is the only part\n * a user sees. Renaming an id silently changes which agent answers.\n *\n * ★★★ WHY `assistant` (Ask) IS SAFE HERE, despite the note beside `MODE` in\n * `createSandboxAssistantTransport.ts`.\n *\n * That comment says BigConsole \"still boots the manually-tagged ACA image\n * `alpha-delegated-auth-v11`\", and that the combined `assistant` mode \"depends\n * on newer image contracts that are not yet guaranteed on this tag\". Both\n * halves are now stale:\n *\n * - ACA — Azure Container Apps — was DECOMMISSIONED and deleted on\n * 2026-08-15. The sandbox runs on the AWS/Cloudflare path now; there is no\n * ACA image line left to be behind.\n * - fe-libs' `getImageTagForMode` returns the SAME tag for `assistant` and\n * `api-calls` (`alpha-delegated-auth-v11` off-prod, `prod` in prod), and\n * `getImageForMode` maps `assistant` onto the api-calls IMAGE outright —\n * \"one image, mode chosen at runtime\". So Ask runs on exactly the image\n * BigConsole already boots today, with `AI_ASSISTANT_MODE` selecting the\n * persona.\n *\n * HealthyBowl, VibeControls and FluidGrids all ship this same `assistant` mode\n * in production, on that same image. Ask is not a new image contract for\n * BigConsole; it is the persona switch the image already supports.\n */\nimport type { AssistantModeConfig } from '@burdenoff/fe-libs/shared/assistant/conversation';\n\n/**\n * Read-only. The ONLY mode fe-libs ever forwards the user's tokens to, and the\n * only one whose sandbox boots with `AI_ASSISTANT_READONLY=1`.\n *\n * ★ This is the user-visible point of the migration. Today BigConsole's\n * assistant is Owner/Admin-only in its entirety, because the legacy\n * `AssistantWidget` has exactly one mode and that mode writes. A member who\n * previously saw NO assistant at all gets read-only Ask, while `Build` stays\n * hidden from them — fe-libs does that filtering itself\n * (`AssistantChatArea` keeps `!mode.requiresAdmin || canUseAdminModes`).\n */\nconst ASK = {\n id: 'assistant',\n label: 'Ask',\n description:\n 'Ask about BigConsole and your workspace. It reads your dashboards, data sinks, parsers and widgets, ' +\n 'explains what you are looking at and points you to the right page. Read-only.',\n suggestions: [\n 'What can I do on this screen?',\n 'What is in this data sink?',\n 'Which widgets are on this dashboard?',\n 'Why is my parser returning no rows?',\n ],\n // No flags: resolves to the chat defaults — no artifacts, 180s budget, not\n // continued in the background, provisionable.\n} as const satisfies AssistantModeConfig;\n\n/**\n * The write mode. `api-calls` is the only id whose sandbox boots with\n * `AI_ASSISTANT_CONFIRM_WRITES=1`, which is why the label may change and the id\n * may not.\n */\nconst BUILD = {\n id: 'api-calls',\n label: 'Build',\n description:\n 'Let the assistant build for you: create a data sink, a dashboard, a parser and the widgets on it. ' +\n 'It shows you each change and waits for your approval before anything is saved.',\n suggestions: [\n 'Build a school attendance dashboard from this CSV',\n 'Add a bar chart of monthly totals to this dashboard',\n 'Create a parser that splits the address column',\n 'Show me what this data sink contains',\n ],\n // `requiresAdmin` is what makes fe-libs issue the myRoles query at all.\n requiresAdmin: true,\n // `writeCapable` routes the first switch through the consent dialog and turns\n // on the composer's write-access warning.\n writeCapable: true,\n /**\n * 420s, matching the legacy transport's `STREAM_BUDGET_MS` exactly, so this\n * introduces no behavioural change.\n *\n * ★★ NOT cosmetic, and not a round number picked for comfort. The default is\n * 180s, which was SHORTER THAN THE WORK: a full build — data sink → dashboard\n * → parser → widget, each a separate gateway call preceded by a model\n * round-trip — measured 229s in prod. The agent finished, the dashboard\n * genuinely existed, and the user was told the assistant timed out. People\n * then retried and ended up with duplicate dashboards.\n *\n * Finite, positive and under `MAX_ASSISTANT_STREAM_BUDGET_MS`, so fe-libs\n * honours it (`resolveAssistantModeCapabilities`) instead of silently falling\n * back to 180_000 — which is why this seam needs fe-libs >= 2026.916.1.\n */\n streamBudgetMs: 420_000,\n /**\n * Report the WHOLE run's narration, joined oldest first, instead of only the\n * newest sandbox message (BC1, BOFF-7334).\n *\n * ★★ Legacy PARITY, not a new behaviour. `createSandboxAssistantTransport`'s\n * `buildProgress` already joins every assistant message of the run — its own\n * comment reads \"ACCUMULATE the run, don't just show its last line\" — and it\n * does so for a reason that has not changed: the agent opens a NEW message per\n * step and prints a link the moment a create lands (\"✅ Data sink created —\n * [Open …](/…)\"). Rendering only the newest message threw all of that away on\n * the following poll; the user saw a lone \"Thinking…\" and none of the links\n * they had been promised. Without this flag the shared panel would reintroduce\n * the bug BigConsole already fixed.\n *\n * ★★ It changes what is STORED, not only what is displayed: the completed turn\n * persists the joined account as its answer, so Copy, Export and read-aloud\n * carry it too. That is correct HERE — the build log IS the answer, and it is\n * where the links to the new data sink and dashboard live — and wrong for Ask,\n * which is why Ask does not set it.\n *\n * Honoured by `resolveAssistantModeCapabilities`, which defaults it to `false`\n * for every mode including the built-ins, so this seam needs fe-libs\n * >= 2026.917.2 (fe-libs #233).\n */\n accumulatesNarration: true,\n /**\n * 15,155 characters of ONE attached file's text, against fe-libs' 8,000\n * default (BOFF-7334; fe-libs #242, so this seam needs >= 2026.920.2).\n *\n * ★★★ DERIVED FROM THE BYTE ENVELOPE, BECAUSE THAT IS THE ONE THAT BINDS:\n *\n * floor((MAX_INLINE_TURN_BODY_BYTES - PROMPT_ENVELOPE_RESERVE_BYTES)\n * / MAX_UTF8_BYTES_PER_JS_CHAR / MAX_ATTACHMENTS)\n * = floor(227,328 / 3 / 5) = 15,155\n *\n * and 5 x 15,155 x 3 = 227,325 <= 227,328, so this is the LARGEST value that\n * derivation allows — three bytes of slack. Two properties follow, and\n * `tests/assistantSeam.test.ts` guards both rather than the literal:\n * 1. the attachment TEXT can never exhaust the CHARACTER total\n * (5 x 15,155 = 75,775 <= MAX_CONFIGURABLE_INLINE_CHARS), and\n * 2. its RAW UTF-8 BYTES can never exhaust the envelope `capTextToBody`\n * cuts to (5 x 15,155 x 3 = 227,325 <= 227,328), whatever script the\n * files are written in.\n *\n * ★★★ (2) BOUNDS THE PAYLOAD, NOT THE RENDERED TURN. An earlier revision of\n * this docblock claimed the stronger thing — \"it can never exhaust the BYTE\n * envelope, whatever script the files are written in\" — and that is FALSE.\n * `capTextToBody` weighs `JSON.stringify({prompt, parts})` over the RENDERED\n * block, and that block indents every line by four spaces, writes every\n * newline as a two-byte escape, and adds an `[n] file — mime, size` header, a\n * `Contents (...)` line and a pair of fences per file plus one\n * `--- Attachments (N) ---` frame per turn. Where the lines are short and\n * multibyte that is over 4 bytes per attachment character, not 3, so the\n * envelope CAN still bind and fe-libs then cuts the turn total until it\n * fits. Measured through `buildPromptWithAttachments` on the installed\n * 2026.920.4, over the fixture the test carries — five 120 K-character\n * all-CJK files of 1-3 characters a line — characters of each file that\n * reached the agent:\n *\n * 15,155/200,000 15,151 15,151 15,151 11,179 3\n * 8k/20k (today) 7,996 7,996 4,006 2 omitted\n *\n * 56,635 characters served for 227,326 of the 227,328 bytes, i.e. 4.01 bytes\n * a character where the derivation budgets 3, and the fifth file arrives as\n * a THREE-CHARACTER sliver announced as a sample.\n * That is NOT a reason to lower this number, and the alternative is worse:\n * on that same turn it still serves at least as much of every file as the\n * shared default it raises (the second row IS the comparison); on the\n * ordinary Build turn — three CJK fact tables and two Devanagari lookups —\n * it hands BOTH lookups over WHOLE where the default cuts them to 77 and 3\n * characters; and no per-file value can repair a slice fe-libs spends IN\n * ATTACHMENT ORDER out of a total it has already cut. That last part is fe-libs' to fix — BOFF-7532 — and the\n * residual is pinned by the test named \"the byte envelope STILL binds\".\n *\n * ★★★ WHY 3 IS A BOUND, NOT A GUESS. fe-libs budgets in JS string LENGTH and\n * enforces in UTF-8 BYTES. Every UTF-16 code unit encodes to at most 3 bytes:\n * <= U+007F is 1, <= U+07FF is 2, the rest of the BMP is 3, and an astral\n * character is 4 bytes across TWO units — 2 bytes a unit. So\n * `utf8Bytes(s) <= 3 * s.length` holds for every string, with equality from\n * U+0800 (CJK, Devanagari, Thai). The test proves that over the whole BMP\n * instead of asserting it.\n *\n * ★★★ THIS SEAM'S FIRST TWO ANSWERS WERE BOTH WRONG, and the second was wrong\n * in a way ASCII tests could not see. 100,000 let two files eat the turn.\n * 40,000 — `MAX_CONFIGURABLE_INLINE_CHARS / MAX_ATTACHMENTS` — was defended\n * here as \"the largest per-file limit that cannot starve a later attachment\";\n * that claim was FALSE for any non-ASCII file, because the character total is\n * not what binds. `capTextToBody` lowers the turn total until the prompt fits\n * 227,328 BYTES, and `applyInlineBudget` then still spends\n * `min(remainingTotal, perFileChars)` IN ATTACHMENT ORDER. Measured against\n * the installed 2026.920.4 dist with the fixtures the tests now carry — a\n * 2,000-row all-CJK fact CSV (186 K characters, 506 KB, 2.78 bytes a\n * character) and a 40-row Devanagari dimension CSV — rows of each file that\n * reached the agent:\n *\n * 8k/20k (today) 40k/200k 15,155/200,000\n * 2 big + 1 small 85, 85, 40w 429, 429, 0 162, 162, 40w\n * 3 big + 2 small 85, 85, 43, 1, 0 429, 427, 0, 0, 0 162 x3, 40w, 40w\n * 5 big 85, 85, 43, 1, — 429, 427, 0, 0, — 162 x5\n *\n * (\"w\" is a table that arrived WHOLE; \"—\" is the honest `contents omitted`\n * note; every other zero is a header fragment announced as a truncation.) At\n * 40,000 the dimension tables arrive as SLIVERS ANNOUNCED AS SAMPLES —\n * \"Contents (first 50 characters of 5.7 KB; truncated)\" — which is the\n * failure this mode's docblocks keep naming: the agent believes it sampled\n * the dimension, joins against values it never saw, and the dashboard it then\n * builds looks right. Worse, on the ordinary two-fact-tables-and-a-lookup\n * turn 40,000 was BEATEN BY THE SHARED DEFAULT it replaces, which hands that\n * lookup table over WHOLE.\n *\n * ★★ WHAT THE DERIVATION COSTS, in the ASCII case where 40,000 was safe —\n * same measurement, a 2,200-row / 144 KB fact CSV and a 40-row dimension CSV:\n *\n * 8k/20k (today) 40k/200k 15,155/200,000\n * 1 big 118 596 225\n * 3 big + 2 small 118, 118, 59, 0, 0 596 x3, 40w, 40w 225 x3, 40w, 40w\n *\n * 225 rows of a fact table instead of 596. Still 1.9x fe-libs' default, and\n * still more than the legacy widget ever sent: `attachmentExtract.ts` cuts at\n * `MAX_ROWS` = 200 rows *or* `MAX_INLINE_CHARS_PER_FILE` = 100,000, whichever\n * comes first, and for a row-oriented CSV the row cap almost always wins — so\n * its 100,000 was a ceiling that never bound. Build stays a strict\n * improvement on both paths it replaces, in every script, which matching a\n * ceiling was never worth breaking.\n *\n * ★ What this does NOT fix, because it cannot be fixed from a mode config:\n * the user's own message shares that same 227,328-byte envelope, and opting\n * above fe-libs' defaults is what turns `capTextToBody` on at all. See\n * `maxInlineCharsTotal` below for the measured window. The real fix is a\n * per-attachment floor or a smallest-first spending pass IN FE-LIBS —\n * BOFF-7532. Raise this number only once that lands.\n */\n maxInlineCharsPerFile: 15_155,\n /**\n * 200,000 characters of attached text across the whole turn — fe-libs'\n * `MAX_CONFIGURABLE_INLINE_CHARS`, i.e. \"as much as a 256 KB body can carry\".\n *\n * ★★★ SET EXPLICITLY BECAUSE LEAVING IT OUT IS NOT NEUTRAL. fe-libs raises\n * the turn total to match the per-file limit only when that is LARGER, so\n * omitting this resolves `{perFileChars: 15_155, totalChars: 20_000}` — the\n * 8,000/20,000 default's total, one and a third files' worth of budget for a\n * five-file turn, spent GREEDILY IN ATTACHMENT ORDER. At 200,000 the\n * character total never binds before the per-file limit does\n * (5 x 15,155 = 75,775), which is the point: the per-file limit is the only\n * thing rationing the turn, and it is derived from the envelope.\n *\n * ★★ RAISING EITHER FIELD ABOVE fe-libs' DEFAULTS ARMS A SECOND, TIGHTER\n * CEILING, and that is a measured cost rather than a theoretical one.\n * `buildInlineBudget` runs `capTextToBody` only when\n * `perFileChars > 8,000 || totalChars > 20,000`, and `capTextToBody` fits the\n * prompt into `MAX_INLINE_TURN_BODY_BYTES - PROMPT_ENVELOPE_RESERVE_BYTES` =\n * 227,328 bytes. The un-opted path has no such pass — its only ceiling is\n * `buildBoundedAttachmentTurn`'s `MAX_TURN_BODY_BYTES` = 262,144, roughly\n * 35 KB more room — so for a very large USER MESSAGE this mode delivers less\n * than it would have unconfigured. Measured, 3 big + 2 small ASCII CSVs, rows\n * that reached the agent:\n *\n * user message chars this mode fe-libs defaults\n * <= 189,000 225, 225, 225, 40, 40 118, 118, 59, 0, 0\n * 190,000 225, 225, 53, 1, 0 118, 118, 59, 0, 0\n * 210,000 222, 0, 0, 0, 0 118, 118, 59, 0, 0\n * >= 226,000 nothing at all 118, 118, 59, 0, 0\n *\n * The threshold is 190,000 characters TYPED OR PASTED INTO THE COMPOSER, so\n * the window is narrow — but it is reachable (a pasted CSV under \"build me a\n * dashboard from this\"), it is pinned by a test, and it is one more reason\n * the per-file limit is not raised: at 40,000 the same crossover sat at\n * 137,000 characters, and by 150,000 it had already lost three of the five\n * files this value still serves whole. BOFF-7532 asks fe-libs to measure\n * `capTextToBody` against the envelope the send path actually enforces, and\n * to spend the remainder fairly instead of in attachment order.\n *\n * ★★ AND IT IS NOT \"cut, never rejected\", which an earlier draft of this\n * docblock claimed. `buildBoundedAttachmentTurn` cuts the attachment TEXT,\n * then drops the attachment block entirely with an explicit disclosure (\"N\n * attachment(s) omitted from this turn to fit the request-size limit\"), and\n * if even that text-only turn still exceeds `MAX_TURN_BODY_BYTES` it ends in\n * `throw new Error(REQUEST_TOO_LARGE_REFUSAL)`. The path is narrow — the\n * user's own message has to overflow a 256 KB body on its own — but it is\n * real, and it is the one case where nothing is sent at all.\n *\n * ★ Read at PREPARE time as well as at send time: fe-libs keeps\n * `largestInlineCharsPerFile(modes)` characters of a file when it is attached,\n * so a CSV attached while in Ask is not already cut to 8,000 by the time the\n * user switches to Build. The send then cuts to the SENDING mode's limits.\n *\n * ★★★ NONE OF THIS RUNS UNTIL THE APP MOVES ITS OWN fe-libs PIN. fe-libs is\n * `external` in this package's Vite build, so the HOST APP's copy is what\n * executes here, and `bigconsole-app` pins it in `dependencies` AND in\n * `overrides` — an `overrides` entry is not satisfied by this package's\n * `peerDependencies`. At the app's `2026.917.2`, `resolveAssistantInlineLimits`\n * does not exist at all, so both fields above are read by nobody, with no\n * error and no warning, and the panel keeps the fixed 8,000/20,000 path.\n * `accumulatesNarration` DOES exist there, so half of BOFF-7334 would ship\n * and half would not. Bump the app's fe-libs to >= 2026.920.2 in both places\n * in the same PR as the microfe bump — BOFF-7533.\n */\n maxInlineCharsTotal: 200_000,\n} as const satisfies AssistantModeConfig;\n\n/**\n * The list handed to `AssistantProductProvider`, in display order.\n *\n * ★ Exported as ONE array and consumed by identity. The seam guard asserts\n * `toBe`, not deep equality, because any `.map()` or rebuild on the way to the\n * provider is exactly how `requiresAdmin` and `writeCapable` get dropped.\n */\nexport const BIGCONSOLE_ASSISTANT_MODES: readonly AssistantModeConfig[] = [ASK, BUILD];\n\nexport { ASK, BUILD };\n"],"mappings":";AA0CA,IAAM,IAAM;CACV,IAAI;CACJ,OAAO;CACP,aACE;CAEF,aAAa;EACX;EACA;EACA;EACA;EACD;CAGF,EAOK,IAAQ;CACZ,IAAI;CACJ,OAAO;CACP,aACE;CAEF,aAAa;EACX;EACA;EACA;EACA;EACD;CAED,eAAe;CAGf,cAAc;CAgBd,gBAAgB;CAyBhB,sBAAsB;CA4GtB,uBAAuB;CAiEvB,qBAAqB;CACtB,EASY,IAA6D,CAAC,GAAK,EAAM"}
1
+ {"version":3,"file":"modes.js","names":[],"sources":["../../../src/bigconsole/assistant/modes.ts"],"sourcesContent":["/**\n * The modes BigConsole offers the shared assistant panel.\n *\n * ★★ Mode IDS are transport values, not labels. The id picks the sandbox image,\n * the env that image boots with, and the agent persona; `label` is the only part\n * a user sees. Renaming an id silently changes which agent answers.\n *\n * ★★★ WHY `assistant` (Ask) IS SAFE HERE, despite the note beside `MODE` in\n * `createSandboxAssistantTransport.ts`.\n *\n * That comment says BigConsole \"still boots the manually-tagged ACA image\n * `alpha-delegated-auth-v11`\", and that the combined `assistant` mode \"depends\n * on newer image contracts that are not yet guaranteed on this tag\". Both\n * halves are now stale:\n *\n * - ACA — Azure Container Apps — was DECOMMISSIONED and deleted on\n * 2026-08-15. The sandbox runs on the AWS/Cloudflare path now; there is no\n * ACA image line left to be behind.\n * - fe-libs' `getImageTagForMode` returns the SAME tag for `assistant` and\n * `api-calls` (`alpha-delegated-auth-v11` off-prod, `prod` in prod), and\n * `getImageForMode` maps `assistant` onto the api-calls IMAGE outright —\n * \"one image, mode chosen at runtime\". So Ask runs on exactly the image\n * BigConsole already boots today, with `AI_ASSISTANT_MODE` selecting the\n * persona.\n *\n * HealthyBowl, VibeControls and FluidGrids all ship this same `assistant` mode\n * in production, on that same image. Ask is not a new image contract for\n * BigConsole; it is the persona switch the image already supports.\n */\nimport type { AssistantModeConfig } from '@burdenoff/fe-libs/shared/assistant/conversation';\n\n/**\n * Read-only. The ONLY mode fe-libs ever forwards the user's tokens to, and the\n * only one whose sandbox boots with `AI_ASSISTANT_READONLY=1`.\n *\n * ★ This is the user-visible point of the migration. Today BigConsole's\n * assistant is Owner/Admin-only in its entirety, because the legacy\n * `AssistantWidget` has exactly one mode and that mode writes. A member who\n * previously saw NO assistant at all gets read-only Ask, while `Build` stays\n * hidden from them — fe-libs does that filtering itself\n * (`AssistantChatArea` keeps `!mode.requiresAdmin || canUseAdminModes`).\n */\nconst ASK = {\n id: 'assistant',\n label: 'Ask',\n description:\n 'Ask about BigConsole and your workspace. It reads your dashboards, data sinks, parsers and widgets, ' +\n 'explains what you are looking at and points you to the right page. Read-only.',\n suggestions: [\n 'What can I do on this screen?',\n 'What is in this data sink?',\n 'Which widgets are on this dashboard?',\n 'Why is my parser returning no rows?',\n ],\n // No flags: resolves to the chat defaults — no artifacts, 180s budget, not\n // continued in the background, provisionable.\n} as const satisfies AssistantModeConfig;\n\n/**\n * The write mode. `api-calls` is the only id whose sandbox boots with\n * `AI_ASSISTANT_CONFIRM_WRITES=1`, which is why the label may change and the id\n * may not.\n */\nconst BUILD = {\n id: 'api-calls',\n label: 'Build',\n description:\n 'Let the assistant build for you: create a data sink, a dashboard, a parser and the widgets on it. ' +\n 'It shows you each change and waits for your approval before anything is saved.',\n suggestions: [\n 'Build a school attendance dashboard from this CSV',\n 'Add a bar chart of monthly totals to this dashboard',\n 'Create a parser that splits the address column',\n 'Show me what this data sink contains',\n ],\n // ★★★ NO `requiresAdmin` (BOFF-7800). Declaring it is what makes fe-libs issue\n // the myRoles query at all and hide this mode from anyone who is not an org\n // Owner/Admin — the wrong question at the wrong scope, since product writes\n // are governed by the WORKSPACE role and the server already grants\n // `sandbox:create` to Editor and above. RBAC alone decides what this mode may\n // DO; nothing decides who may see it.\n // `writeCapable` routes the first switch through the consent dialog and turns\n // on the composer's write-access warning.\n writeCapable: true,\n /**\n * 420s, matching the legacy transport's `STREAM_BUDGET_MS` exactly, so this\n * introduces no behavioural change.\n *\n * ★★ NOT cosmetic, and not a round number picked for comfort. The default is\n * 180s, which was SHORTER THAN THE WORK: a full build — data sink → dashboard\n * → parser → widget, each a separate gateway call preceded by a model\n * round-trip — measured 229s in prod. The agent finished, the dashboard\n * genuinely existed, and the user was told the assistant timed out. People\n * then retried and ended up with duplicate dashboards.\n *\n * Finite, positive and under `MAX_ASSISTANT_STREAM_BUDGET_MS`, so fe-libs\n * honours it (`resolveAssistantModeCapabilities`) instead of silently falling\n * back to 180_000 — which is why this seam needs fe-libs >= 2026.916.1.\n */\n streamBudgetMs: 420_000,\n /**\n * Report the WHOLE run's narration, joined oldest first, instead of only the\n * newest sandbox message (BC1, BOFF-7334).\n *\n * ★★ Legacy PARITY, not a new behaviour. `createSandboxAssistantTransport`'s\n * `buildProgress` already joins every assistant message of the run — its own\n * comment reads \"ACCUMULATE the run, don't just show its last line\" — and it\n * does so for a reason that has not changed: the agent opens a NEW message per\n * step and prints a link the moment a create lands (\"✅ Data sink created —\n * [Open …](/…)\"). Rendering only the newest message threw all of that away on\n * the following poll; the user saw a lone \"Thinking…\" and none of the links\n * they had been promised. Without this flag the shared panel would reintroduce\n * the bug BigConsole already fixed.\n *\n * ★★ It changes what is STORED, not only what is displayed: the completed turn\n * persists the joined account as its answer, so Copy, Export and read-aloud\n * carry it too. That is correct HERE — the build log IS the answer, and it is\n * where the links to the new data sink and dashboard live — and wrong for Ask,\n * which is why Ask does not set it.\n *\n * Honoured by `resolveAssistantModeCapabilities`, which defaults it to `false`\n * for every mode including the built-ins, so this seam needs fe-libs\n * >= 2026.917.2 (fe-libs #233).\n */\n accumulatesNarration: true,\n /**\n * 15,155 characters of ONE attached file's text, against fe-libs' 8,000\n * default (BOFF-7334; fe-libs #242, so this seam needs >= 2026.920.2).\n *\n * ★★★ DERIVED FROM THE BYTE ENVELOPE, BECAUSE THAT IS THE ONE THAT BINDS:\n *\n * floor((MAX_INLINE_TURN_BODY_BYTES - PROMPT_ENVELOPE_RESERVE_BYTES)\n * / MAX_UTF8_BYTES_PER_JS_CHAR / MAX_ATTACHMENTS)\n * = floor(227,328 / 3 / 5) = 15,155\n *\n * and 5 x 15,155 x 3 = 227,325 <= 227,328, so this is the LARGEST value that\n * derivation allows — three bytes of slack. Two properties follow, and\n * `tests/assistantSeam.test.ts` guards both rather than the literal:\n * 1. the attachment TEXT can never exhaust the CHARACTER total\n * (5 x 15,155 = 75,775 <= MAX_CONFIGURABLE_INLINE_CHARS), and\n * 2. its RAW UTF-8 BYTES can never exhaust the envelope `capTextToBody`\n * cuts to (5 x 15,155 x 3 = 227,325 <= 227,328), whatever script the\n * files are written in.\n *\n * ★★★ (2) BOUNDS THE PAYLOAD, NOT THE RENDERED TURN. An earlier revision of\n * this docblock claimed the stronger thing — \"it can never exhaust the BYTE\n * envelope, whatever script the files are written in\" — and that is FALSE.\n * `capTextToBody` weighs `JSON.stringify({prompt, parts})` over the RENDERED\n * block, and that block indents every line by four spaces, writes every\n * newline as a two-byte escape, and adds an `[n] file — mime, size` header, a\n * `Contents (...)` line and a pair of fences per file plus one\n * `--- Attachments (N) ---` frame per turn. Where the lines are short and\n * multibyte that is over 4 bytes per attachment character, not 3, so the\n * envelope CAN still bind and fe-libs then cuts the turn total until it\n * fits. Measured through `buildPromptWithAttachments` on the installed\n * 2026.920.4, over the fixture the test carries — five 120 K-character\n * all-CJK files of 1-3 characters a line — characters of each file that\n * reached the agent:\n *\n * 15,155/200,000 15,151 15,151 15,151 11,179 3\n * 8k/20k (today) 7,996 7,996 4,006 2 omitted\n *\n * 56,635 characters served for 227,326 of the 227,328 bytes, i.e. 4.01 bytes\n * a character where the derivation budgets 3, and the fifth file arrives as\n * a THREE-CHARACTER sliver announced as a sample.\n * That is NOT a reason to lower this number, and the alternative is worse:\n * on that same turn it still serves at least as much of every file as the\n * shared default it raises (the second row IS the comparison); on the\n * ordinary Build turn — three CJK fact tables and two Devanagari lookups —\n * it hands BOTH lookups over WHOLE where the default cuts them to 77 and 3\n * characters; and no per-file value can repair a slice fe-libs spends IN\n * ATTACHMENT ORDER out of a total it has already cut. That last part is fe-libs' to fix — BOFF-7532 — and the\n * residual is pinned by the test named \"the byte envelope STILL binds\".\n *\n * ★★★ WHY 3 IS A BOUND, NOT A GUESS. fe-libs budgets in JS string LENGTH and\n * enforces in UTF-8 BYTES. Every UTF-16 code unit encodes to at most 3 bytes:\n * <= U+007F is 1, <= U+07FF is 2, the rest of the BMP is 3, and an astral\n * character is 4 bytes across TWO units — 2 bytes a unit. So\n * `utf8Bytes(s) <= 3 * s.length` holds for every string, with equality from\n * U+0800 (CJK, Devanagari, Thai). The test proves that over the whole BMP\n * instead of asserting it.\n *\n * ★★★ THIS SEAM'S FIRST TWO ANSWERS WERE BOTH WRONG, and the second was wrong\n * in a way ASCII tests could not see. 100,000 let two files eat the turn.\n * 40,000 — `MAX_CONFIGURABLE_INLINE_CHARS / MAX_ATTACHMENTS` — was defended\n * here as \"the largest per-file limit that cannot starve a later attachment\";\n * that claim was FALSE for any non-ASCII file, because the character total is\n * not what binds. `capTextToBody` lowers the turn total until the prompt fits\n * 227,328 BYTES, and `applyInlineBudget` then still spends\n * `min(remainingTotal, perFileChars)` IN ATTACHMENT ORDER. Measured against\n * the installed 2026.920.4 dist with the fixtures the tests now carry — a\n * 2,000-row all-CJK fact CSV (186 K characters, 506 KB, 2.78 bytes a\n * character) and a 40-row Devanagari dimension CSV — rows of each file that\n * reached the agent:\n *\n * 8k/20k (today) 40k/200k 15,155/200,000\n * 2 big + 1 small 85, 85, 40w 429, 429, 0 162, 162, 40w\n * 3 big + 2 small 85, 85, 43, 1, 0 429, 427, 0, 0, 0 162 x3, 40w, 40w\n * 5 big 85, 85, 43, 1, — 429, 427, 0, 0, — 162 x5\n *\n * (\"w\" is a table that arrived WHOLE; \"—\" is the honest `contents omitted`\n * note; every other zero is a header fragment announced as a truncation.) At\n * 40,000 the dimension tables arrive as SLIVERS ANNOUNCED AS SAMPLES —\n * \"Contents (first 50 characters of 5.7 KB; truncated)\" — which is the\n * failure this mode's docblocks keep naming: the agent believes it sampled\n * the dimension, joins against values it never saw, and the dashboard it then\n * builds looks right. Worse, on the ordinary two-fact-tables-and-a-lookup\n * turn 40,000 was BEATEN BY THE SHARED DEFAULT it replaces, which hands that\n * lookup table over WHOLE.\n *\n * ★★ WHAT THE DERIVATION COSTS, in the ASCII case where 40,000 was safe —\n * same measurement, a 2,200-row / 144 KB fact CSV and a 40-row dimension CSV:\n *\n * 8k/20k (today) 40k/200k 15,155/200,000\n * 1 big 118 596 225\n * 3 big + 2 small 118, 118, 59, 0, 0 596 x3, 40w, 40w 225 x3, 40w, 40w\n *\n * 225 rows of a fact table instead of 596. Still 1.9x fe-libs' default, and\n * still more than the legacy widget ever sent: `attachmentExtract.ts` cuts at\n * `MAX_ROWS` = 200 rows *or* `MAX_INLINE_CHARS_PER_FILE` = 100,000, whichever\n * comes first, and for a row-oriented CSV the row cap almost always wins — so\n * its 100,000 was a ceiling that never bound. Build stays a strict\n * improvement on both paths it replaces, in every script, which matching a\n * ceiling was never worth breaking.\n *\n * ★ What this does NOT fix, because it cannot be fixed from a mode config:\n * the user's own message shares that same 227,328-byte envelope, and opting\n * above fe-libs' defaults is what turns `capTextToBody` on at all. See\n * `maxInlineCharsTotal` below for the measured window. The real fix is a\n * per-attachment floor or a smallest-first spending pass IN FE-LIBS —\n * BOFF-7532. Raise this number only once that lands.\n */\n maxInlineCharsPerFile: 15_155,\n /**\n * 200,000 characters of attached text across the whole turn — fe-libs'\n * `MAX_CONFIGURABLE_INLINE_CHARS`, i.e. \"as much as a 256 KB body can carry\".\n *\n * ★★★ SET EXPLICITLY BECAUSE LEAVING IT OUT IS NOT NEUTRAL. fe-libs raises\n * the turn total to match the per-file limit only when that is LARGER, so\n * omitting this resolves `{perFileChars: 15_155, totalChars: 20_000}` — the\n * 8,000/20,000 default's total, one and a third files' worth of budget for a\n * five-file turn, spent GREEDILY IN ATTACHMENT ORDER. At 200,000 the\n * character total never binds before the per-file limit does\n * (5 x 15,155 = 75,775), which is the point: the per-file limit is the only\n * thing rationing the turn, and it is derived from the envelope.\n *\n * ★★ RAISING EITHER FIELD ABOVE fe-libs' DEFAULTS ARMS A SECOND, TIGHTER\n * CEILING, and that is a measured cost rather than a theoretical one.\n * `buildInlineBudget` runs `capTextToBody` only when\n * `perFileChars > 8,000 || totalChars > 20,000`, and `capTextToBody` fits the\n * prompt into `MAX_INLINE_TURN_BODY_BYTES - PROMPT_ENVELOPE_RESERVE_BYTES` =\n * 227,328 bytes. The un-opted path has no such pass — its only ceiling is\n * `buildBoundedAttachmentTurn`'s `MAX_TURN_BODY_BYTES` = 262,144, roughly\n * 35 KB more room — so for a very large USER MESSAGE this mode delivers less\n * than it would have unconfigured. Measured, 3 big + 2 small ASCII CSVs, rows\n * that reached the agent:\n *\n * user message chars this mode fe-libs defaults\n * <= 189,000 225, 225, 225, 40, 40 118, 118, 59, 0, 0\n * 190,000 225, 225, 53, 1, 0 118, 118, 59, 0, 0\n * 210,000 222, 0, 0, 0, 0 118, 118, 59, 0, 0\n * >= 226,000 nothing at all 118, 118, 59, 0, 0\n *\n * The threshold is 190,000 characters TYPED OR PASTED INTO THE COMPOSER, so\n * the window is narrow — but it is reachable (a pasted CSV under \"build me a\n * dashboard from this\"), it is pinned by a test, and it is one more reason\n * the per-file limit is not raised: at 40,000 the same crossover sat at\n * 137,000 characters, and by 150,000 it had already lost three of the five\n * files this value still serves whole. BOFF-7532 asks fe-libs to measure\n * `capTextToBody` against the envelope the send path actually enforces, and\n * to spend the remainder fairly instead of in attachment order.\n *\n * ★★ AND IT IS NOT \"cut, never rejected\", which an earlier draft of this\n * docblock claimed. `buildBoundedAttachmentTurn` cuts the attachment TEXT,\n * then drops the attachment block entirely with an explicit disclosure (\"N\n * attachment(s) omitted from this turn to fit the request-size limit\"), and\n * if even that text-only turn still exceeds `MAX_TURN_BODY_BYTES` it ends in\n * `throw new Error(REQUEST_TOO_LARGE_REFUSAL)`. The path is narrow — the\n * user's own message has to overflow a 256 KB body on its own — but it is\n * real, and it is the one case where nothing is sent at all.\n *\n * ★ Read at PREPARE time as well as at send time: fe-libs keeps\n * `largestInlineCharsPerFile(modes)` characters of a file when it is attached,\n * so a CSV attached while in Ask is not already cut to 8,000 by the time the\n * user switches to Build. The send then cuts to the SENDING mode's limits.\n *\n * ★★★ NONE OF THIS RUNS UNTIL THE APP MOVES ITS OWN fe-libs PIN. fe-libs is\n * `external` in this package's Vite build, so the HOST APP's copy is what\n * executes here, and `bigconsole-app` pins it in `dependencies` AND in\n * `overrides` — an `overrides` entry is not satisfied by this package's\n * `peerDependencies`. At the app's `2026.917.2`, `resolveAssistantInlineLimits`\n * does not exist at all, so both fields above are read by nobody, with no\n * error and no warning, and the panel keeps the fixed 8,000/20,000 path.\n * `accumulatesNarration` DOES exist there, so half of BOFF-7334 would ship\n * and half would not. Bump the app's fe-libs to >= 2026.920.2 in both places\n * in the same PR as the microfe bump — BOFF-7533.\n */\n maxInlineCharsTotal: 200_000,\n} as const satisfies AssistantModeConfig;\n\n/**\n * The list handed to `AssistantProductProvider`, in display order.\n *\n * ★ Exported as ONE array and consumed by identity. The seam guard asserts\n * `toBe`, not deep equality, because any `.map()` or rebuild on the way to the\n * provider is exactly how `requiresAdmin` and `writeCapable` get dropped.\n */\nexport const BIGCONSOLE_ASSISTANT_MODES: readonly AssistantModeConfig[] = [ASK, BUILD];\n\nexport { ASK, BUILD };\n"],"mappings":";AA0CA,IAAM,IAAM;CACV,IAAI;CACJ,OAAO;CACP,aACE;CAEF,aAAa;EACX;EACA;EACA;EACA;EACD;CAGF,EAOK,IAAQ;CACZ,IAAI;CACJ,OAAO;CACP,aACE;CAEF,aAAa;EACX;EACA;EACA;EACA;EACD;CASD,cAAc;CAgBd,gBAAgB;CAyBhB,sBAAsB;CA4GtB,uBAAuB;CAiEvB,qBAAqB;CACtB,EASY,IAA6D,CAAC,GAAK,EAAM"}
@@ -0,0 +1,25 @@
1
+ import { useEffect as e, useState as t } from "react";
2
+ //#region src/bigconsole/assistant/useAssistantPanelShowsPreview.ts
3
+ var n = "[data-assistant-panel]", r = "data-assistant-overlay", i = "preview";
4
+ function a(e = document) {
5
+ return e.querySelector(n)?.getAttribute(r) === i;
6
+ }
7
+ function o() {
8
+ let [n, i] = t(() => typeof document > "u" ? !1 : a());
9
+ return e(() => {
10
+ if (typeof document > "u") return;
11
+ let e = () => i(a());
12
+ e();
13
+ let t = new MutationObserver(e);
14
+ return t.observe(document.body, {
15
+ subtree: !0,
16
+ childList: !0,
17
+ attributes: !0,
18
+ attributeFilter: [r]
19
+ }), () => t.disconnect();
20
+ }, []), n;
21
+ }
22
+ //#endregion
23
+ export { o as useAssistantPanelShowsPreview };
24
+
25
+ //# sourceMappingURL=useAssistantPanelShowsPreview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAssistantPanelShowsPreview.js","names":[],"sources":["../../../src/bigconsole/assistant/useAssistantPanelShowsPreview.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\n\n/**\n * Is the shared assistant panel currently showing the preview ITSELF?\n *\n * ★★★ WHY THIS EXISTS. The preview has three surfaces — the rich page panel,\n * the mini page panel, and fe-libs' in-chat overlay — and only one may show per\n * run. BigConsole already arbitrates its own two (`richMounted`), but nothing\n * arbitrated against the third: fe-libs renders the SAME controller via\n * `controller.render()` inside its overlay, driven by its own local state, so\n * opening the overlay left the page surface up as well.\n *\n * Measured on app.bigconsole.com 2026-09-24, one Build run, one click of the\n * panel's \"Build preview\" toggle:\n * before -> page surface [283,145,1109,270], no overlay\n * after -> page surface STILL [283,145,1109,270]\n * plus the same preview again at [897,166,518,160] inside the\n * overlay, inside the assistant panel\n * Two live surfaces for one run, side by side, showing the same build.\n *\n * ★★ IT READS THE ATTRIBUTE fe-libs ALREADY PUBLISHES, rather than adding a\n * contract. `AIAssistantPanel` writes `data-assistant-overlay=\"preview\"` on its\n * root for exactly this kind of cross-package coordination — the same mechanism\n * `assistantPanelOwnsEscape` reads to decide who owns the Escape key. Verified\n * live before building on it: the attribute is absent before the toggle and\n * `\"preview\"` after.\n *\n * ★ A MutationObserver, not a poll or a render-time read. The attribute changes\n * from inside fe-libs' own state, on a node this package does not own and does\n * not re-render with, so there is no React signal to hang it on.\n */\nconst PANEL_SELECTOR = '[data-assistant-panel]';\nconst OVERLAY_ATTR = 'data-assistant-overlay';\nconst PREVIEW = 'preview';\n\n/** Reads the current answer from the DOM. Safe to call before any panel mounts. */\nexport function panelShowsPreviewNow(doc: Pick<Document, 'querySelector'> = document): boolean {\n return doc.querySelector(PANEL_SELECTOR)?.getAttribute(OVERLAY_ATTR) === PREVIEW;\n}\n\nexport function useAssistantPanelShowsPreview(): boolean {\n const [showing, setShowing] = useState(() => (typeof document === 'undefined' ? false : panelShowsPreviewNow()));\n\n useEffect(() => {\n if (typeof document === 'undefined') return;\n const read = () => setShowing(panelShowsPreviewNow());\n read();\n // ★ `subtree` is required: the panel mounts and unmounts below <body>, so an\n // observer bound to attributes alone would never see a panel that appears\n // after this hook runs — which is the common case, since the page surface\n // exists before the user opens the assistant.\n const observer = new MutationObserver(read);\n observer.observe(document.body, {\n subtree: true,\n childList: true,\n attributes: true,\n attributeFilter: [OVERLAY_ATTR],\n });\n return () => observer.disconnect();\n }, []);\n\n return showing;\n}\n"],"mappings":";;AA+BA,IAAM,IAAiB,0BACjB,IAAe,0BACf,IAAU;AAGhB,SAAgB,EAAqB,IAAuC,UAAmB;AAC7F,QAAO,EAAI,cAAc,EAAe,EAAE,aAAa,EAAa,KAAK;;AAG3E,SAAgB,IAAyC;CACvD,IAAM,CAAC,GAAS,KAAc,QAAgB,OAAO,WAAa,MAAc,KAAQ,GAAsB,CAAE;AAoBhH,QAlBA,QAAgB;AACd,MAAI,OAAO,WAAa,IAAa;EACrC,IAAM,UAAa,EAAW,GAAsB,CAAC;AACrD,KAAM;EAKN,IAAM,IAAW,IAAI,iBAAiB,EAAK;AAO3C,SANA,EAAS,QAAQ,SAAS,MAAM;GAC9B,SAAS;GACT,WAAW;GACX,YAAY;GACZ,iBAAiB,CAAC,EAAa;GAChC,CAAC,QACW,EAAS,YAAY;IACjC,EAAE,CAAC,EAEC"}
@@ -1,130 +1,131 @@
1
1
  import { Section as e } from "../common/Section.js";
2
2
  import "../common/index.js";
3
- import { PREVIEW_STEPS as t, useAssistantRunStore as n } from "../../assistant/assistantRunStore.js";
4
- import { DOCK_CLEARANCE as r, PreviewMeta as i, STEP_LABEL as a, formatAgo as o, previewLabels as s } from "./previewShared.js";
5
- import { ArrowUpRight as c, Braces as l, Database as u, LayoutDashboard as d, Loader2 as f, Sparkles as p, X as m } from "lucide-react";
6
- import { useI18n as h } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
7
- import { Button as g } from "@burdenoff/fe-libs/ui";
8
- import { jsx as _, jsxs as v } from "react/jsx-runtime";
9
- import { AssistantEntityPreview as y } from "@burdenoff/fe-libs/shared/assistant";
3
+ import { useAssistantPanelShowsPreview as t } from "../../assistant/useAssistantPanelShowsPreview.js";
4
+ import { PREVIEW_STEPS as n, useAssistantRunStore as r } from "../../assistant/assistantRunStore.js";
5
+ import { DOCK_CLEARANCE as i, PreviewMeta as a, STEP_LABEL as o, formatAgo as s, previewLabels as c } from "./previewShared.js";
6
+ import { ArrowUpRight as l, Braces as u, Database as d, LayoutDashboard as f, Loader2 as p, Sparkles as m, X as h } from "lucide-react";
7
+ import { useI18n as g } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
8
+ import { Button as _ } from "@burdenoff/fe-libs/ui";
9
+ import { jsx as v, jsxs as y } from "react/jsx-runtime";
10
+ import { AssistantEntityPreview as b } from "@burdenoff/fe-libs/shared/assistant";
10
11
  //#region src/bigconsole/components/preview/AiPreviewMiniPanel.tsx
11
- function b({ onNavigate: b }) {
12
- let { t: x } = h(), S = n((e) => e.isRunning), C = n((e) => e.previewOpen), w = n((e) => e.hasRun), T = n((e) => e.richMounted), E = n((e) => e.steps), D = n((e) => e.entityIds), O = n((e) => e.caption), k = n((e) => e.error), A = n((e) => e.updatedAt), j = n((e) => e.openPreview), M = n((e) => e.closePreview), N = (e) => {
13
- b ? b(e) : typeof window < "u" && window.location.assign(e);
12
+ function x({ onNavigate: x }) {
13
+ let { t: S } = g(), C = t(), w = r((e) => e.isRunning), T = r((e) => e.previewOpen), E = r((e) => e.hasRun), D = r((e) => e.richMounted), O = r((e) => e.steps), k = r((e) => e.entityIds), A = r((e) => e.caption), j = r((e) => e.error), M = r((e) => e.updatedAt), N = r((e) => e.openPreview), P = r((e) => e.closePreview), F = (e) => {
14
+ x ? x(e) : typeof window < "u" && window.location.assign(e);
14
15
  };
15
- if (!w || T) return null;
16
- if (!C) return /* @__PURE__ */ v("div", {
16
+ if (!E || D || C) return null;
17
+ if (!T) return /* @__PURE__ */ y("div", {
17
18
  className: "flex flex-wrap items-center justify-end gap-control-gap px-4 pt-3 md:px-6",
18
- style: { marginRight: r },
19
- children: [/* @__PURE__ */ _("span", {
19
+ style: { marginRight: i },
20
+ children: [/* @__PURE__ */ v("span", {
20
21
  className: "text-xs text-text-muted",
21
- children: S ? x("bigconsole.widget.aiPreview.buildRunning", "AI build running") : x("bigconsole.widget.aiPreview.buildFinished", "AI build finished")
22
- }), /* @__PURE__ */ v(g, {
22
+ children: w ? S("bigconsole.widget.aiPreview.buildRunning", "AI build running") : S("bigconsole.widget.aiPreview.buildFinished", "AI build finished")
23
+ }), /* @__PURE__ */ y(_, {
23
24
  size: "sm",
24
25
  variant: "outline",
25
- onClick: j,
26
+ onClick: N,
26
27
  "data-testid": "ai-preview-open",
27
28
  children: [
28
- /* @__PURE__ */ _(p, { className: "mr-1.5 size-4" }),
29
- x("bigconsole.widget.aiPreviewMini.previewButton", "Preview"),
30
- S ? /* @__PURE__ */ _(f, { className: "ml-1.5 size-3.5 animate-spin" }) : null
29
+ /* @__PURE__ */ v(m, { className: "mr-1.5 size-4" }),
30
+ S("bigconsole.widget.aiPreviewMini.previewButton", "Preview"),
31
+ w ? /* @__PURE__ */ v(p, { className: "ml-1.5 size-3.5 animate-spin" }) : null
31
32
  ]
32
33
  })]
33
34
  });
34
- let P = [];
35
- D.datasink && P.push({
35
+ let I = [];
36
+ k.datasink && I.push({
36
37
  key: "datasink",
37
- label: x("bigconsole.widget.aiPreviewMini.link.dataSink", "Data Sink"),
38
- path: `/bigconsole/datasinks/${D.datasink}`,
39
- icon: /* @__PURE__ */ _(u, {
38
+ label: S("bigconsole.widget.aiPreviewMini.link.dataSink", "Data Sink"),
39
+ path: `/bigconsole/datasinks/${k.datasink}`,
40
+ icon: /* @__PURE__ */ v(d, {
40
41
  className: "size-4 shrink-0 text-text-muted",
41
42
  "aria-hidden": "true"
42
43
  })
43
- }), D.dashboard && P.push({
44
+ }), k.dashboard && I.push({
44
45
  key: "dashboard",
45
- label: x("bigconsole.widget.aiPreviewMini.link.dashboard", "Dashboard"),
46
- path: `/bigconsole/dashboards/${D.dashboard}`,
47
- icon: /* @__PURE__ */ _(d, {
46
+ label: S("bigconsole.widget.aiPreviewMini.link.dashboard", "Dashboard"),
47
+ path: `/bigconsole/dashboards/${k.dashboard}`,
48
+ icon: /* @__PURE__ */ v(f, {
48
49
  className: "size-4 shrink-0 text-text-muted",
49
50
  "aria-hidden": "true"
50
51
  })
51
- }), D.parser && P.push({
52
+ }), k.parser && I.push({
52
53
  key: "parser",
53
- label: x("bigconsole.widget.aiPreviewMini.link.parser", "Parser"),
54
- path: `/bigconsole/parsers/${D.parser}`,
55
- icon: /* @__PURE__ */ _(l, {
54
+ label: S("bigconsole.widget.aiPreviewMini.link.parser", "Parser"),
55
+ path: `/bigconsole/parsers/${k.parser}`,
56
+ icon: /* @__PURE__ */ v(u, {
56
57
  className: "size-4 shrink-0 text-text-muted",
57
58
  "aria-hidden": "true"
58
59
  })
59
60
  });
60
- let F = t.map((e) => ({
61
+ let L = n.map((e) => ({
61
62
  id: e,
62
- label: x(a[e].key, a[e].fallback),
63
- status: E[e]
63
+ label: S(o[e].key, o[e].fallback),
64
+ status: O[e]
64
65
  }));
65
- return /* @__PURE__ */ v("div", {
66
+ return /* @__PURE__ */ y("div", {
66
67
  "data-testid": "ai-preview-panel",
67
68
  "data-variant": "mini",
68
- style: { paddingRight: r },
69
+ style: { paddingRight: i },
69
70
  className: "mx-4 mb-3 mt-3 min-w-0 md:mx-6",
70
- children: [/* @__PURE__ */ _("h2", {
71
+ children: [/* @__PURE__ */ v("h2", {
71
72
  className: "sr-only",
72
- children: x("bigconsole.widget.aiPreviewMini.title", "AI Preview")
73
- }), /* @__PURE__ */ _(y, {
74
- title: x("bigconsole.widget.aiPreviewMini.title", "AI Preview"),
75
- steps: F,
76
- caption: S ? O || x("bigconsole.widget.aiPreviewMini.working", "Working…") : x("bigconsole.widget.aiPreviewMini.done", "Done"),
77
- error: k,
78
- meta: /* @__PURE__ */ _(i, {
79
- state: S ? "running" : k ? "stopped" : "idle",
80
- stateLabel: S ? x("bigconsole.widget.aiPreviewMini.live", "Live") : k ? x("bigconsole.widget.aiPreviewMini.stopped", "Stopped") : null,
81
- updatedLabel: x("bigconsole.widget.aiPreviewMini.updated", "updated"),
82
- updatedAt: A,
83
- ago: (e) => o(e, x)
73
+ children: S("bigconsole.widget.aiPreviewMini.title", "AI Preview")
74
+ }), /* @__PURE__ */ v(b, {
75
+ title: S("bigconsole.widget.aiPreviewMini.title", "AI Preview"),
76
+ steps: L,
77
+ caption: w ? A || S("bigconsole.widget.aiPreviewMini.working", "Working…") : S("bigconsole.widget.aiPreviewMini.done", "Done"),
78
+ error: j,
79
+ meta: /* @__PURE__ */ v(a, {
80
+ state: w ? "running" : j ? "stopped" : "idle",
81
+ stateLabel: w ? S("bigconsole.widget.aiPreviewMini.live", "Live") : j ? S("bigconsole.widget.aiPreviewMini.stopped", "Stopped") : null,
82
+ updatedLabel: S("bigconsole.widget.aiPreviewMini.updated", "updated"),
83
+ updatedAt: M,
84
+ ago: (e) => s(e, S)
84
85
  }),
85
- headerActions: /* @__PURE__ */ _(g, {
86
+ headerActions: /* @__PURE__ */ v(_, {
86
87
  size: "sm",
87
88
  variant: "ghost",
88
- onClick: M,
89
- "aria-label": x("bigconsole.widget.aiPreviewMini.closeAriaLabel", "Close preview"),
89
+ onClick: P,
90
+ "aria-label": S("bigconsole.widget.aiPreviewMini.closeAriaLabel", "Close preview"),
90
91
  "data-testid": "ai-preview-close",
91
- children: /* @__PURE__ */ _(m, { className: "size-4" })
92
+ children: /* @__PURE__ */ v(h, { className: "size-4" })
92
93
  }),
93
- labels: s(x),
94
- children: /* @__PURE__ */ _("div", {
94
+ labels: c(S),
95
+ children: /* @__PURE__ */ v("div", {
95
96
  className: "p-3",
96
- children: /* @__PURE__ */ v(e, {
97
+ children: /* @__PURE__ */ y(e, {
97
98
  emphasis: "standard",
98
99
  padding: "compact",
99
100
  headingLevel: 3,
100
- title: x("bigconsole.widget.aiPreviewMini.builtSoFar", "Built so far"),
101
- description: x("bigconsole.widget.aiPreviewMini.builtSoFarHint", "Open any piece to see the real thing."),
101
+ title: S("bigconsole.widget.aiPreviewMini.builtSoFar", "Built so far"),
102
+ description: S("bigconsole.widget.aiPreviewMini.builtSoFarHint", "Open any piece to see the real thing."),
102
103
  bodyClassName: "flex flex-col gap-list-gap",
103
- children: [P.length === 0 ? /* @__PURE__ */ _("p", {
104
+ children: [I.length === 0 ? /* @__PURE__ */ v("p", {
104
105
  className: "text-sm text-text-muted",
105
- children: S ? x("bigconsole.widget.aiPreviewMini.building", "Building… links appear here as each piece is created.") : x("bigconsole.widget.aiPreviewMini.nothingBuilt", "Nothing built yet.")
106
- }) : P.map((e) => /* @__PURE__ */ v("div", {
106
+ children: w ? S("bigconsole.widget.aiPreviewMini.building", "Building… links appear here as each piece is created.") : S("bigconsole.widget.aiPreviewMini.nothingBuilt", "Nothing built yet.")
107
+ }) : I.map((e) => /* @__PURE__ */ y("div", {
107
108
  "data-testid": "ai-preview-row",
108
109
  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",
109
- children: [/* @__PURE__ */ v("p", {
110
+ children: [/* @__PURE__ */ y("p", {
110
111
  className: "flex min-w-0 items-center gap-icon-gap text-sm font-medium text-text-primary",
111
- children: [e.icon, /* @__PURE__ */ _("span", {
112
+ children: [e.icon, /* @__PURE__ */ v("span", {
112
113
  className: "truncate",
113
- children: x("bigconsole.widget.aiPreviewMini.linkReady", "{{label}} ready", { label: e.label })
114
+ children: S("bigconsole.widget.aiPreviewMini.linkReady", "{{label}} ready", { label: e.label })
114
115
  })]
115
- }), /* @__PURE__ */ v(g, {
116
+ }), /* @__PURE__ */ y(_, {
116
117
  size: "sm",
117
118
  variant: "ghost",
118
- onClick: () => N(e.path),
119
- children: [x("bigconsole.widget.aiPreviewMini.open", "Open"), /* @__PURE__ */ _(c, { className: "ml-1 size-3.5" })]
119
+ onClick: () => F(e.path),
120
+ children: [S("bigconsole.widget.aiPreviewMini.open", "Open"), /* @__PURE__ */ v(l, { className: "ml-1 size-3.5" })]
120
121
  })]
121
- }, e.key)), D.dashboard ? /* @__PURE__ */ v(g, {
122
+ }, e.key)), k.dashboard ? /* @__PURE__ */ y(_, {
122
123
  size: "sm",
123
124
  variant: "outline",
124
125
  className: "h-auto w-full whitespace-normal py-1.5 text-center",
125
126
  "data-testid": "ai-preview-open-in-bigconsole",
126
- onClick: () => N(`/bigconsole/dashboards/${D.dashboard ?? ""}`),
127
- children: [x("bigconsole.widget.aiPreviewMini.openInBigConsole", "Open in BigConsole for the live view"), /* @__PURE__ */ _(c, { className: "ml-1 size-3.5" })]
127
+ onClick: () => F(`/bigconsole/dashboards/${k.dashboard ?? ""}`),
128
+ children: [S("bigconsole.widget.aiPreviewMini.openInBigConsole", "Open in BigConsole for the live view"), /* @__PURE__ */ v(l, { className: "ml-1 size-3.5" })]
128
129
  }) : null]
129
130
  })
130
131
  })
@@ -132,6 +133,6 @@ function b({ onNavigate: b }) {
132
133
  });
133
134
  }
134
135
  //#endregion
135
- export { b as AiPreviewMiniPanel };
136
+ export { x as AiPreviewMiniPanel };
136
137
 
137
138
  //# sourceMappingURL=AiPreviewMiniPanel.js.map
@@ -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 { 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 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
+ {"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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"}
@@ -2,162 +2,163 @@ import { Section as e } from "../common/Section.js";
2
2
  import "../common/index.js";
3
3
  import { useBigConsoleNavigate as t } from "../../context/BigConsoleContext.js";
4
4
  import "../../context/index.js";
5
- import { PREVIEW_STEPS as n, useAssistantRunStore as r } from "../../assistant/assistantRunStore.js";
6
- import { DOCK_CLEARANCE as i, PreviewMeta as a, STEP_LABEL as o, formatAgo as s, previewLabels as c } from "./previewShared.js";
7
- import { DashboardContent as l, DataSinkContent as u, ParserContent as d, WidgetsContent as f } from "./AiPreviewContent.js";
8
- import { useEffect as p, useRef as m, useState as h } from "react";
9
- import { ArrowUpRight as g, Loader2 as _, Sparkles as v, X as y } from "lucide-react";
10
- import { useI18n as b } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
11
- import { Button as x } from "@burdenoff/fe-libs/ui";
12
- import { jsx as S, jsxs as C } from "react/jsx-runtime";
13
- import { AssistantEntityPreview as w } from "@burdenoff/fe-libs/shared/assistant";
5
+ import { useAssistantPanelShowsPreview as n } from "../../assistant/useAssistantPanelShowsPreview.js";
6
+ import { PREVIEW_STEPS as r, useAssistantRunStore as i } from "../../assistant/assistantRunStore.js";
7
+ import { DOCK_CLEARANCE as a, PreviewMeta as o, STEP_LABEL as s, formatAgo as c, previewLabels as l } from "./previewShared.js";
8
+ import { DashboardContent as u, DataSinkContent as d, ParserContent as f, WidgetsContent as p } from "./AiPreviewContent.js";
9
+ import { useEffect as m, useRef as h, useState as g } from "react";
10
+ import { ArrowUpRight as _, Loader2 as v, Sparkles as y, X as b } from "lucide-react";
11
+ import { useI18n as x } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
12
+ import { Button as S } from "@burdenoff/fe-libs/ui";
13
+ import { jsx as C, jsxs as w } from "react/jsx-runtime";
14
+ import { AssistantEntityPreview as T } from "@burdenoff/fe-libs/shared/assistant";
14
15
  //#region src/bigconsole/components/preview/AiPreviewPanel.tsx
15
- function T({ children: e }) {
16
- return /* @__PURE__ */ S("div", {
16
+ function E({ children: e }) {
17
+ return /* @__PURE__ */ C("div", {
17
18
  className: "min-w-0 overflow-y-auto p-3",
18
19
  style: { maxHeight: "min(440px, 60vh)" },
19
20
  children: e
20
21
  });
21
22
  }
22
- function E({ onOpen: e }) {
23
- let { t } = b();
24
- return /* @__PURE__ */ C(x, {
23
+ function D({ onOpen: e }) {
24
+ let { t } = x();
25
+ return /* @__PURE__ */ w(S, {
25
26
  size: "sm",
26
27
  variant: "outline",
27
28
  onClick: e,
28
29
  "data-testid": "ai-preview-open-entity",
29
- children: [t("bigconsole.widget.aiPreview.open", "Open"), /* @__PURE__ */ S(g, { className: "ml-1 size-3.5" })]
30
+ children: [t("bigconsole.widget.aiPreview.open", "Open"), /* @__PURE__ */ C(_, { className: "ml-1 size-3.5" })]
30
31
  });
31
32
  }
32
- function D() {
33
- let { t: g } = b(), D = t(), O = r((e) => e.isRunning), k = r((e) => e.previewOpen), A = r((e) => e.hasRun), j = r((e) => e.steps), M = r((e) => e.entityIds), N = r((e) => e.caption), P = r((e) => e.error), F = r((e) => e.updatedAt), I = r((e) => e.openPreview), L = r((e) => e.closePreview), R = r((e) => e.setRichMounted);
34
- p(() => (R(!0), () => {
35
- R(!1);
36
- }), [R]);
37
- let [z, B] = h(0), V = m(!1);
38
- if (p(() => {
39
- O && !V.current && B((e) => e + 1), V.current = O;
40
- }, [O]), !A) return null;
41
- if (!k) return /* @__PURE__ */ C("div", {
33
+ function O() {
34
+ let { t: _ } = x(), O = n(), k = t(), A = i((e) => e.isRunning), j = i((e) => e.previewOpen), M = i((e) => e.hasRun), N = i((e) => e.steps), P = i((e) => e.entityIds), F = i((e) => e.caption), I = i((e) => e.error), L = i((e) => e.updatedAt), R = i((e) => e.openPreview), z = i((e) => e.closePreview), B = i((e) => e.setRichMounted);
35
+ m(() => (B(!0), () => {
36
+ B(!1);
37
+ }), [B]);
38
+ let [V, H] = g(0), U = h(!1);
39
+ if (m(() => {
40
+ A && !U.current && H((e) => e + 1), U.current = A;
41
+ }, [A]), !M || O) return null;
42
+ if (!j) return /* @__PURE__ */ w("div", {
42
43
  className: "flex flex-wrap items-center justify-end gap-control-gap px-4 pt-3 md:px-6",
43
- style: { marginRight: i },
44
- children: [/* @__PURE__ */ S("span", {
44
+ style: { marginRight: a },
45
+ children: [/* @__PURE__ */ C("span", {
45
46
  className: "text-xs text-text-muted",
46
- children: O ? g("bigconsole.widget.aiPreview.buildRunning", "AI build running") : g("bigconsole.widget.aiPreview.buildFinished", "AI build finished")
47
- }), /* @__PURE__ */ C(x, {
47
+ children: A ? _("bigconsole.widget.aiPreview.buildRunning", "AI build running") : _("bigconsole.widget.aiPreview.buildFinished", "AI build finished")
48
+ }), /* @__PURE__ */ w(S, {
48
49
  size: "sm",
49
50
  variant: "outline",
50
- onClick: I,
51
+ onClick: R,
51
52
  "data-testid": "ai-preview-open",
52
53
  children: [
53
- /* @__PURE__ */ S(v, { className: "mr-1.5 size-4" }),
54
- g("bigconsole.widget.aiPreview.previewButton", "Preview"),
55
- O ? /* @__PURE__ */ S(_, { className: "ml-1.5 size-3.5 animate-spin" }) : null
54
+ /* @__PURE__ */ C(y, { className: "mr-1.5 size-4" }),
55
+ _("bigconsole.widget.aiPreview.previewButton", "Preview"),
56
+ A ? /* @__PURE__ */ C(v, { className: "ml-1.5 size-3.5 animate-spin" }) : null
56
57
  ]
57
58
  })]
58
59
  });
59
- let H = M.dashboard, U = n.map((e) => ({
60
+ let W = P.dashboard, G = r.map((e) => ({
60
61
  id: e,
61
- label: g(o[e].key, o[e].fallback),
62
- status: j[e]
63
- })), W = [
62
+ label: _(s[e].key, s[e].fallback),
63
+ status: N[e]
64
+ })), K = [
64
65
  {
65
66
  id: "datasink",
66
- label: g("bigconsole.widget.aiPreview.tab.dataSink", "Data Sink"),
67
- render: () => /* @__PURE__ */ S(T, { children: /* @__PURE__ */ S(e, {
67
+ label: _("bigconsole.widget.aiPreview.tab.dataSink", "Data Sink"),
68
+ render: () => /* @__PURE__ */ C(E, { children: /* @__PURE__ */ C(e, {
68
69
  emphasis: "standard",
69
70
  padding: "compact",
70
71
  headingLevel: 3,
71
- title: g("bigconsole.widget.aiPreview.dataSinkRows", "Data sink rows"),
72
- description: g("bigconsole.widget.aiPreview.dataSinkRowsHint", "The rows the assistant just landed, straight from the sink."),
73
- actions: M.datasink ? /* @__PURE__ */ S(E, { onOpen: () => D(`/datasinks/${M.datasink}`) }) : void 0,
74
- children: /* @__PURE__ */ S(u, {
75
- id: M.datasink,
76
- live: O
72
+ title: _("bigconsole.widget.aiPreview.dataSinkRows", "Data sink rows"),
73
+ description: _("bigconsole.widget.aiPreview.dataSinkRowsHint", "The rows the assistant just landed, straight from the sink."),
74
+ actions: P.datasink ? /* @__PURE__ */ C(D, { onOpen: () => k(`/datasinks/${P.datasink}`) }) : void 0,
75
+ children: /* @__PURE__ */ C(d, {
76
+ id: P.datasink,
77
+ live: A
77
78
  })
78
79
  }) })
79
80
  },
80
81
  {
81
82
  id: "dashboard",
82
- label: g("bigconsole.widget.aiPreview.tab.dashboard", "Dashboard"),
83
- render: () => /* @__PURE__ */ S(T, { children: /* @__PURE__ */ S(e, {
83
+ label: _("bigconsole.widget.aiPreview.tab.dashboard", "Dashboard"),
84
+ render: () => /* @__PURE__ */ C(E, { children: /* @__PURE__ */ C(e, {
84
85
  emphasis: "standard",
85
86
  padding: "compact",
86
87
  headingLevel: 3,
87
- title: g("bigconsole.widget.aiPreview.tab.dashboard", "Dashboard"),
88
- description: g("bigconsole.widget.aiPreview.dashboardHint", "The live board, rendered exactly as the real page renders it."),
89
- actions: H ? /* @__PURE__ */ S(E, { onOpen: () => D(`/dashboards/${H}`) }) : void 0,
90
- children: /* @__PURE__ */ S(l, { dashboardId: H })
88
+ title: _("bigconsole.widget.aiPreview.tab.dashboard", "Dashboard"),
89
+ description: _("bigconsole.widget.aiPreview.dashboardHint", "The live board, rendered exactly as the real page renders it."),
90
+ actions: W ? /* @__PURE__ */ C(D, { onOpen: () => k(`/dashboards/${W}`) }) : void 0,
91
+ children: /* @__PURE__ */ C(u, { dashboardId: W })
91
92
  }) })
92
93
  },
93
94
  {
94
95
  id: "parser",
95
- label: g("bigconsole.widget.aiPreview.tab.parser", "Parser"),
96
- render: () => /* @__PURE__ */ S(T, { children: /* @__PURE__ */ S(e, {
96
+ label: _("bigconsole.widget.aiPreview.tab.parser", "Parser"),
97
+ render: () => /* @__PURE__ */ C(E, { children: /* @__PURE__ */ C(e, {
97
98
  emphasis: "standard",
98
99
  padding: "compact",
99
100
  headingLevel: 3,
100
- title: g("bigconsole.widget.aiPreview.parserOutput", "Parser output"),
101
- description: g("bigconsole.widget.aiPreview.parserOutputHint", "The rows after the parser's transform, so you can check the shape."),
102
- actions: M.parser ? /* @__PURE__ */ S(E, { onOpen: () => D(`/parsers/${M.parser}`) }) : void 0,
103
- children: /* @__PURE__ */ S(d, {
104
- parserId: M.parser,
105
- live: O
101
+ title: _("bigconsole.widget.aiPreview.parserOutput", "Parser output"),
102
+ description: _("bigconsole.widget.aiPreview.parserOutputHint", "The rows after the parser's transform, so you can check the shape."),
103
+ actions: P.parser ? /* @__PURE__ */ C(D, { onOpen: () => k(`/parsers/${P.parser}`) }) : void 0,
104
+ children: /* @__PURE__ */ C(f, {
105
+ parserId: P.parser,
106
+ live: A
106
107
  })
107
108
  }) })
108
109
  },
109
110
  {
110
111
  id: "widget",
111
- label: g("bigconsole.widget.aiPreview.tab.widget", "Widget"),
112
- render: () => /* @__PURE__ */ S(T, { children: /* @__PURE__ */ S(e, {
112
+ label: _("bigconsole.widget.aiPreview.tab.widget", "Widget"),
113
+ render: () => /* @__PURE__ */ C(E, { children: /* @__PURE__ */ C(e, {
113
114
  emphasis: "standard",
114
115
  padding: "compact",
115
116
  headingLevel: 3,
116
- title: g("bigconsole.widget.aiPreview.widgets", "Widgets"),
117
- description: g("bigconsole.widget.aiPreview.widgetsHint", "Every widget on the new dashboard, rendered live."),
118
- actions: H ? /* @__PURE__ */ S(E, { onOpen: () => D(`/dashboards/${H}`) }) : void 0,
119
- children: /* @__PURE__ */ S(f, {
120
- dashboardId: H,
121
- live: O
117
+ title: _("bigconsole.widget.aiPreview.widgets", "Widgets"),
118
+ description: _("bigconsole.widget.aiPreview.widgetsHint", "Every widget on the new dashboard, rendered live."),
119
+ actions: W ? /* @__PURE__ */ C(D, { onOpen: () => k(`/dashboards/${W}`) }) : void 0,
120
+ children: /* @__PURE__ */ C(p, {
121
+ dashboardId: W,
122
+ live: A
122
123
  })
123
124
  }) })
124
125
  }
125
126
  ];
126
- return /* @__PURE__ */ C("div", {
127
+ return /* @__PURE__ */ w("div", {
127
128
  "data-testid": "ai-preview-panel",
128
- style: { paddingRight: i },
129
+ style: { paddingRight: a },
129
130
  className: "mx-4 mt-3 min-w-0 md:mx-6",
130
- children: [/* @__PURE__ */ S("h2", {
131
+ children: [/* @__PURE__ */ C("h2", {
131
132
  className: "sr-only",
132
- children: g("bigconsole.widget.aiPreview.title", "AI Preview")
133
- }), /* @__PURE__ */ S(w, {
134
- title: g("bigconsole.widget.aiPreview.title", "AI Preview"),
135
- runKey: String(z),
136
- steps: U,
137
- tabs: W,
133
+ children: _("bigconsole.widget.aiPreview.title", "AI Preview")
134
+ }), /* @__PURE__ */ C(T, {
135
+ title: _("bigconsole.widget.aiPreview.title", "AI Preview"),
136
+ runKey: String(V),
137
+ steps: G,
138
+ tabs: K,
138
139
  follow: "produced",
139
- caption: O ? N || g("bigconsole.widget.aiPreview.working", "Working…") : g("bigconsole.widget.aiPreview.done", "Done"),
140
- error: P,
141
- meta: /* @__PURE__ */ S(a, {
142
- state: O ? "running" : P ? "stopped" : "idle",
143
- stateLabel: O ? g("bigconsole.widget.aiPreview.live", "Live") : P ? g("bigconsole.widget.aiPreview.stopped", "Stopped") : null,
144
- updatedLabel: g("bigconsole.widget.aiPreview.updated", "updated"),
145
- updatedAt: F,
146
- ago: (e) => s(e, g)
140
+ caption: A ? F || _("bigconsole.widget.aiPreview.working", "Working…") : _("bigconsole.widget.aiPreview.done", "Done"),
141
+ error: I,
142
+ meta: /* @__PURE__ */ C(o, {
143
+ state: A ? "running" : I ? "stopped" : "idle",
144
+ stateLabel: A ? _("bigconsole.widget.aiPreview.live", "Live") : I ? _("bigconsole.widget.aiPreview.stopped", "Stopped") : null,
145
+ updatedLabel: _("bigconsole.widget.aiPreview.updated", "updated"),
146
+ updatedAt: L,
147
+ ago: (e) => c(e, _)
147
148
  }),
148
- headerActions: /* @__PURE__ */ S(x, {
149
+ headerActions: /* @__PURE__ */ C(S, {
149
150
  size: "sm",
150
151
  variant: "ghost",
151
- onClick: L,
152
- "aria-label": g("bigconsole.widget.aiPreview.closeAriaLabel", "Close preview"),
152
+ onClick: z,
153
+ "aria-label": _("bigconsole.widget.aiPreview.closeAriaLabel", "Close preview"),
153
154
  "data-testid": "ai-preview-close",
154
- children: /* @__PURE__ */ S(y, { className: "size-4" })
155
+ children: /* @__PURE__ */ C(b, { className: "size-4" })
155
156
  }),
156
- labels: c(g)
157
+ labels: l(_)
157
158
  })]
158
159
  });
159
160
  }
160
161
  //#endregion
161
- export { D as AiPreviewPanel };
162
+ export { O as AiPreviewPanel };
162
163
 
163
164
  //# sourceMappingURL=AiPreviewPanel.js.map
@@ -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 { 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 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":"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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 { 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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-bigconsole",
3
- "version": "2026.922.2",
3
+ "version": "2026.924.2",
4
4
  "description": "BigConsole - AI-powered analytics and dashboard platform",
5
5
  "type": "module",
6
6
  "files": [