@burdenoff/microfe-bigconsole 2026.929.2 → 2026.930.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bigconsole/assistant/assistantRunPersistence.js +111 -0
- package/dist/bigconsole/assistant/assistantRunPersistence.js.map +1 -0
- package/dist/bigconsole/assistant/assistantRunStore.js +87 -48
- package/dist/bigconsole/assistant/assistantRunStore.js.map +1 -1
- package/dist/bigconsole/assistant/previewController.js +19 -50
- package/dist/bigconsole/assistant/previewController.js.map +1 -1
- package/dist/bigconsole/assistant/useAssistantRunWorkspace.js +14 -0
- package/dist/bigconsole/assistant/useAssistantRunWorkspace.js.map +1 -0
- package/dist/bigconsole/components/DrilldownDashboardRenderer.js +74 -50
- package/dist/bigconsole/components/DrilldownDashboardRenderer.js.map +1 -1
- package/dist/bigconsole/components/dashboard/DrilldownBreadcrumb.js +9 -4
- package/dist/bigconsole/components/dashboard/DrilldownBreadcrumb.js.map +1 -1
- package/dist/bigconsole/components/embed/ReadOnlyDashboardRenderer.js +68 -40
- package/dist/bigconsole/components/embed/ReadOnlyDashboardRenderer.js.map +1 -1
- package/dist/bigconsole/components/embed/drilldown.js +26 -0
- package/dist/bigconsole/components/embed/drilldown.js.map +1 -0
- package/dist/bigconsole/components/embed/index.js +4 -3
- package/dist/bigconsole/components/embed/types.js.map +1 -1
- package/dist/bigconsole/components/preview/AiPreviewMiniPanel.js +32 -30
- package/dist/bigconsole/components/preview/AiPreviewMiniPanel.js.map +1 -1
- package/dist/bigconsole/components/preview/AiPreviewPanel.js +38 -36
- package/dist/bigconsole/components/preview/AiPreviewPanel.js.map +1 -1
- package/dist/bigconsole/components/preview/previewShared.js +1 -1
- package/dist/bigconsole/components/preview/previewShared.js.map +1 -1
- package/dist/bigconsole/components/widgets/DrilldownConfigPanel.js +307 -233
- package/dist/bigconsole/components/widgets/DrilldownConfigPanel.js.map +1 -1
- package/dist/bigconsole/components/widgets/PropertiesPanel.js +230 -220
- package/dist/bigconsole/components/widgets/PropertiesPanel.js.map +1 -1
- package/dist/bigconsole/components/widgets/WidgetWrapper.js +18 -19
- package/dist/bigconsole/components/widgets/WidgetWrapper.js.map +1 -1
- package/dist/bigconsole/context/BigConsoleContext.js +4 -1
- package/dist/bigconsole/context/BigConsoleContext.js.map +1 -1
- package/dist/bigconsole/hooks/useActivePage.js +25 -0
- package/dist/bigconsole/hooks/useActivePage.js.map +1 -0
- package/dist/bigconsole/hooks/useDrilldown.js +118 -71
- package/dist/bigconsole/hooks/useDrilldown.js.map +1 -1
- package/dist/bigconsole/hooks/useDrilldownOperations.js +32 -26
- package/dist/bigconsole/hooks/useDrilldownOperations.js.map +1 -1
- package/dist/bigconsole/hooks/useFilterUrlSync.js +1 -1
- package/dist/bigconsole/hooks/useFilterUrlSync.js.map +1 -1
- package/dist/bigconsole/pages/DashboardBuilderPage.js +190 -185
- package/dist/bigconsole/pages/DashboardBuilderPage.js.map +1 -1
- package/dist/bigconsole/pages/DashboardViewPage.js +224 -217
- package/dist/bigconsole/pages/DashboardViewPage.js.map +1 -1
- package/dist/generated/wspace-operations.js +132 -86
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/generated/wspace-types.js.map +1 -1
- package/package.json +3 -3
- package/dist/bigconsole/assistant/useAssistantPanelShowsPreview.js +0 -25
- package/dist/bigconsole/assistant/useAssistantPanelShowsPreview.js.map +0 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
//#region src/bigconsole/assistant/assistantRunPersistence.ts
|
|
2
|
+
var e = "bigconsole.assistant.previewRun.v1:", t = [
|
|
3
|
+
"datasink",
|
|
4
|
+
"dashboard",
|
|
5
|
+
"parser",
|
|
6
|
+
"widget"
|
|
7
|
+
], n = [
|
|
8
|
+
"datasink",
|
|
9
|
+
"dashboard",
|
|
10
|
+
"parser"
|
|
11
|
+
], r = /^[A-Za-z0-9_-]{1,128}$/;
|
|
12
|
+
function i(t) {
|
|
13
|
+
return `${e}${t}`;
|
|
14
|
+
}
|
|
15
|
+
function a() {
|
|
16
|
+
try {
|
|
17
|
+
return globalThis.sessionStorage === void 0 ? null : globalThis.sessionStorage;
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function o(e) {
|
|
23
|
+
return typeof e == "object" && !!e && !Array.isArray(e);
|
|
24
|
+
}
|
|
25
|
+
function s(e) {
|
|
26
|
+
return e === "pending" || e === "running" || e === "done" || e === "skipped";
|
|
27
|
+
}
|
|
28
|
+
function c(e) {
|
|
29
|
+
let t;
|
|
30
|
+
try {
|
|
31
|
+
t = JSON.parse(e);
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
if (!o(t) || t.v !== 1 || t.hasRun !== !0) return null;
|
|
36
|
+
let { previewOpen: i, actorId: a, updatedAt: c, steps: l, entityIds: u } = t;
|
|
37
|
+
if (typeof i != "boolean" || a !== null && typeof a != "string" || c !== null && (typeof c != "number" || !Number.isFinite(c)) || !o(l)) return null;
|
|
38
|
+
let { datasink: d, dashboard: f, parser: p, widget: m } = l;
|
|
39
|
+
if (!s(d) || !s(f) || !s(p) || !s(m) || !o(u)) return null;
|
|
40
|
+
let h = {};
|
|
41
|
+
for (let e of n) {
|
|
42
|
+
let t = u[e];
|
|
43
|
+
if (t !== void 0) {
|
|
44
|
+
if (typeof t != "string" || !r.test(t)) return null;
|
|
45
|
+
h[e] = t;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
v: 1,
|
|
50
|
+
actorId: a,
|
|
51
|
+
hasRun: !0,
|
|
52
|
+
previewOpen: i,
|
|
53
|
+
steps: {
|
|
54
|
+
datasink: d,
|
|
55
|
+
dashboard: f,
|
|
56
|
+
parser: p,
|
|
57
|
+
widget: m
|
|
58
|
+
},
|
|
59
|
+
entityIds: h,
|
|
60
|
+
updatedAt: c
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function l(e, n) {
|
|
64
|
+
let r = a();
|
|
65
|
+
if (!r) return null;
|
|
66
|
+
let o;
|
|
67
|
+
try {
|
|
68
|
+
o = r.getItem(i(e));
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
if (o === null) return null;
|
|
73
|
+
let s = c(o);
|
|
74
|
+
if (!s) return d(e), null;
|
|
75
|
+
if (s.actorId !== n) return null;
|
|
76
|
+
let l = { ...s.steps };
|
|
77
|
+
for (let e of t) l[e] === "running" && (l[e] = "pending");
|
|
78
|
+
return {
|
|
79
|
+
hasRun: !0,
|
|
80
|
+
previewOpen: s.previewOpen,
|
|
81
|
+
steps: l,
|
|
82
|
+
entityIds: s.entityIds,
|
|
83
|
+
updatedAt: s.updatedAt
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function u(e, t, n) {
|
|
87
|
+
let r = a();
|
|
88
|
+
if (!r) return;
|
|
89
|
+
let o = {
|
|
90
|
+
v: 1,
|
|
91
|
+
actorId: t,
|
|
92
|
+
hasRun: !0,
|
|
93
|
+
previewOpen: n.previewOpen,
|
|
94
|
+
steps: n.steps,
|
|
95
|
+
entityIds: n.entityIds,
|
|
96
|
+
updatedAt: n.updatedAt
|
|
97
|
+
};
|
|
98
|
+
try {
|
|
99
|
+
r.setItem(i(e), JSON.stringify(o));
|
|
100
|
+
} catch {}
|
|
101
|
+
}
|
|
102
|
+
function d(e) {
|
|
103
|
+
let t = a();
|
|
104
|
+
if (t) try {
|
|
105
|
+
t.removeItem(i(e));
|
|
106
|
+
} catch {}
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
109
|
+
export { l as readPersistedRun, d as removePersistedRun, u as writePersistedRun };
|
|
110
|
+
|
|
111
|
+
//# sourceMappingURL=assistantRunPersistence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assistantRunPersistence.js","names":[],"sources":["../../../src/bigconsole/assistant/assistantRunPersistence.ts"],"sourcesContent":["/**\n * The build preview's run state, kept for the life of the TAB (BOFF-7799, A3).\n *\n * ★★★ WHY. BigConsole restores the conversation from the server\n * (`SERVER_HISTORY`), but the preview's run store lived only in memory. A\n * reload kept the chat and lost the page preview under the breadcrumb, so the\n * build a user had just watched was gone while the conversation that made it\n * was still on screen. This keeps the part the page preview draws, and nothing\n * else, in `sessionStorage`.\n *\n * ★★ WHAT IS KEPT, AND WHAT IS NOT. Kept: `hasRun`, whether the preview is\n * open, the four step statuses, the entity ids and the last-update time. None\n * of that is sensitive: the ids name workspace resources the server still\n * authorises on every fetch. NOT kept: the prompt (the user's own words), the\n * caption (the agent's narration) and the error text. `isRunning` is not kept\n * either: nothing can follow a turn across a reload, so a restored `running`\n * step drops back to `pending`, exactly as `finishRun` does.\n *\n * ★★ ONE ENTRY PER WORKSPACE, and it names its actor. The key carries the\n * workspace, so a reload in another workspace never reads this one. The record\n * carries the actor it was written for, because fe-libs found that the\n * workspace alone is not an identity: two accounts can share a workspace and a\n * tab (profile switching), and the second must not be shown the first one's\n * build (see fe-libs `AssistantPreviewRun.actorId`).\n *\n * ★ EVERY STORAGE ACCESS IS CONTAINED. `sessionStorage` can be missing (tests,\n * SSR), can throw on access (blocked site data, sandboxed frames) and can throw\n * on write (quota). Any of those means \"nothing kept\", never an exception in\n * the page surface or in the store action that triggered the write.\n */\nimport type { PreviewStepKey, PreviewStepStatus } from './assistantRunStore';\n\n/** `sessionStorage` key prefix; the workspace id follows it. */\nexport const RUN_STORAGE_PREFIX = 'bigconsole.assistant.previewRun.v1:';\n\n/** The slice of the run store that survives a reload. */\nexport interface PersistedRun {\n hasRun: true;\n previewOpen: boolean;\n steps: Record<PreviewStepKey, PreviewStepStatus>;\n entityIds: { datasink?: string; dashboard?: string; parser?: string };\n updatedAt: number | null;\n}\n\n/** What is actually written: the slice, its format version and its owner. */\ninterface StoredRun extends PersistedRun {\n v: 1;\n actorId: string | null;\n}\n\nconst STEP_KEYS: readonly PreviewStepKey[] = ['datasink', 'dashboard', 'parser', 'widget'];\nconst ENTITY_KEYS = ['datasink', 'dashboard', 'parser'] as const;\n\n/**\n * ★★ The id charset the store itself harvests (`ENTITY_LINK`, `JSON_ID_STEPS`).\n * Restored ids are interpolated into in-app paths (`/bigconsole/datasinks/<id>`),\n * so a value edited in devtools must not be able to smuggle `../` or a query\n * string into a navigation. Anything else is treated as a corrupt record.\n */\nconst ENTITY_ID = /^[A-Za-z0-9_-]{1,128}$/;\n\nexport function runStorageKey(workspaceId: string): string {\n return `${RUN_STORAGE_PREFIX}${workspaceId}`;\n}\n\n/** The tab's `sessionStorage`, or null wherever it cannot be used. */\nfunction sessionStore(): Storage | null {\n try {\n // ★ Reading the property is itself the access that throws when site data\n // is blocked, so the `typeof` guard sits inside the try, not before it.\n return typeof globalThis.sessionStorage === 'undefined' ? null : globalThis.sessionStorage;\n } catch {\n return null;\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isStepStatus(value: unknown): value is PreviewStepStatus {\n return value === 'pending' || value === 'running' || value === 'done' || value === 'skipped';\n}\n\n/** Parses and validates one stored record. Any deviation reads as absent. */\nfunction parseStoredRun(raw: string): StoredRun | null {\n let value: unknown;\n try {\n value = JSON.parse(raw);\n } catch {\n return null;\n }\n if (!isRecord(value) || value.v !== 1 || value.hasRun !== true) return null;\n\n const { previewOpen, actorId, updatedAt, steps, entityIds } = value;\n if (typeof previewOpen !== 'boolean') return null;\n if (actorId !== null && typeof actorId !== 'string') return null;\n if (updatedAt !== null && (typeof updatedAt !== 'number' || !Number.isFinite(updatedAt))) return null;\n\n if (!isRecord(steps)) return null;\n const { datasink, dashboard, parser, widget } = steps;\n if (!isStepStatus(datasink) || !isStepStatus(dashboard) || !isStepStatus(parser) || !isStepStatus(widget)) {\n return null;\n }\n\n if (!isRecord(entityIds)) return null;\n const ids: PersistedRun['entityIds'] = {};\n for (const key of ENTITY_KEYS) {\n const id = entityIds[key];\n if (id === undefined) continue;\n if (typeof id !== 'string' || !ENTITY_ID.test(id)) return null;\n ids[key] = id;\n }\n\n return {\n v: 1,\n actorId,\n hasRun: true,\n previewOpen,\n steps: { datasink, dashboard, parser, widget },\n entityIds: ids,\n updatedAt,\n };\n}\n\n/**\n * The run kept for this workspace and actor, or null.\n *\n * ★ A record written for another actor is left in place, not deleted: it is\n * that actor's, and they may switch back. A corrupt record is removed.\n */\nexport function readPersistedRun(workspaceId: string, actorId: string | null): PersistedRun | null {\n const storage = sessionStore();\n if (!storage) return null;\n let raw: string | null;\n try {\n raw = storage.getItem(runStorageKey(workspaceId));\n } catch {\n return null;\n }\n if (raw === null) return null;\n\n const stored = parseStoredRun(raw);\n if (!stored) {\n removePersistedRun(workspaceId);\n return null;\n }\n if (stored.actorId !== actorId) return null;\n\n // Nothing follows a turn across a reload: a step that was in flight did not\n // land, so it drops back to `pending` for a later prompt (as `finishRun` does).\n const steps = { ...stored.steps };\n for (const key of STEP_KEYS) {\n if (steps[key] === 'running') steps[key] = 'pending';\n }\n return {\n hasRun: true,\n previewOpen: stored.previewOpen,\n steps,\n entityIds: stored.entityIds,\n updatedAt: stored.updatedAt,\n };\n}\n\n/** Keeps `run` for this workspace, owned by `actorId`. Best effort. */\nexport function writePersistedRun(workspaceId: string, actorId: string | null, run: PersistedRun): void {\n const storage = sessionStore();\n if (!storage) return;\n const stored: StoredRun = {\n v: 1,\n actorId,\n hasRun: true,\n previewOpen: run.previewOpen,\n steps: run.steps,\n entityIds: run.entityIds,\n updatedAt: run.updatedAt,\n };\n try {\n storage.setItem(runStorageKey(workspaceId), JSON.stringify(stored));\n } catch {\n // Quota or blocked storage: the preview simply does not survive a reload.\n }\n}\n\n/** Forgets the run kept for this workspace. Best effort. */\nexport function removePersistedRun(workspaceId: string): void {\n const storage = sessionStore();\n if (!storage) return;\n try {\n storage.removeItem(runStorageKey(workspaceId));\n } catch {\n // Blocked storage: there is nothing we could have read back either.\n }\n}\n"],"mappings":";AAiCA,IAAa,IAAqB,uCAiB5B,IAAuC;CAAC;CAAY;CAAa;CAAU;CAAS,EACpF,IAAc;CAAC;CAAY;CAAa;CAAS,EAQjD,IAAY;AAElB,SAAgB,EAAc,GAA6B;AACzD,QAAO,GAAG,IAAqB;;AAIjC,SAAS,IAA+B;AACtC,KAAI;AAGF,SAAc,WAAW,mBAAmB,SAAc,OAAO,WAAW;SACtE;AACN,SAAO;;;AAIX,SAAS,EAAS,GAAkD;AAClE,QAAO,OAAO,KAAU,cAAY,KAAkB,CAAC,MAAM,QAAQ,EAAM;;AAG7E,SAAS,EAAa,GAA4C;AAChE,QAAO,MAAU,aAAa,MAAU,aAAa,MAAU,UAAU,MAAU;;AAIrF,SAAS,EAAe,GAA+B;CACrD,IAAI;AACJ,KAAI;AACF,MAAQ,KAAK,MAAM,EAAI;SACjB;AACN,SAAO;;AAET,KAAI,CAAC,EAAS,EAAM,IAAI,EAAM,MAAM,KAAK,EAAM,WAAW,GAAM,QAAO;CAEvE,IAAM,EAAE,gBAAa,YAAS,cAAW,UAAO,iBAAc;AAK9D,KAJI,OAAO,KAAgB,aACvB,MAAY,QAAQ,OAAO,KAAY,YACvC,MAAc,SAAS,OAAO,KAAc,YAAY,CAAC,OAAO,SAAS,EAAU,KAEnF,CAAC,EAAS,EAAM,CAAE,QAAO;CAC7B,IAAM,EAAE,aAAU,cAAW,WAAQ,cAAW;AAKhD,KAJI,CAAC,EAAa,EAAS,IAAI,CAAC,EAAa,EAAU,IAAI,CAAC,EAAa,EAAO,IAAI,CAAC,EAAa,EAAO,IAIrG,CAAC,EAAS,EAAU,CAAE,QAAO;CACjC,IAAM,IAAiC,EAAE;AACzC,MAAK,IAAM,KAAO,GAAa;EAC7B,IAAM,IAAK,EAAU;AACjB,YAAO,KAAA,GACX;OAAI,OAAO,KAAO,YAAY,CAAC,EAAU,KAAK,EAAG,CAAE,QAAO;AAC1D,KAAI,KAAO;;;AAGb,QAAO;EACL,GAAG;EACH;EACA,QAAQ;EACR;EACA,OAAO;GAAE;GAAU;GAAW;GAAQ;GAAQ;EAC9C,WAAW;EACX;EACD;;AASH,SAAgB,EAAiB,GAAqB,GAA6C;CACjG,IAAM,IAAU,GAAc;AAC9B,KAAI,CAAC,EAAS,QAAO;CACrB,IAAI;AACJ,KAAI;AACF,MAAM,EAAQ,QAAQ,EAAc,EAAY,CAAC;SAC3C;AACN,SAAO;;AAET,KAAI,MAAQ,KAAM,QAAO;CAEzB,IAAM,IAAS,EAAe,EAAI;AAClC,KAAI,CAAC,EAEH,QADA,EAAmB,EAAY,EACxB;AAET,KAAI,EAAO,YAAY,EAAS,QAAO;CAIvC,IAAM,IAAQ,EAAE,GAAG,EAAO,OAAO;AACjC,MAAK,IAAM,KAAO,EAChB,CAAI,EAAM,OAAS,cAAW,EAAM,KAAO;AAE7C,QAAO;EACL,QAAQ;EACR,aAAa,EAAO;EACpB;EACA,WAAW,EAAO;EAClB,WAAW,EAAO;EACnB;;AAIH,SAAgB,EAAkB,GAAqB,GAAwB,GAAyB;CACtG,IAAM,IAAU,GAAc;AAC9B,KAAI,CAAC,EAAS;CACd,IAAM,IAAoB;EACxB,GAAG;EACH;EACA,QAAQ;EACR,aAAa,EAAI;EACjB,OAAO,EAAI;EACX,WAAW,EAAI;EACf,WAAW,EAAI;EAChB;AACD,KAAI;AACF,IAAQ,QAAQ,EAAc,EAAY,EAAE,KAAK,UAAU,EAAO,CAAC;SAC7D;;AAMV,SAAgB,EAAmB,GAA2B;CAC5D,IAAM,IAAU,GAAc;AACzB,OACL,KAAI;AACF,IAAQ,WAAW,EAAc,EAAY,CAAC;SACxC"}
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readPersistedRun as e, removePersistedRun as t, writePersistedRun as n } from "./assistantRunPersistence.js";
|
|
2
|
+
import { create as r } from "zustand";
|
|
3
|
+
import { getActorId as i } from "@burdenoff/fe-libs/shared/assistant/context";
|
|
2
4
|
//#region src/bigconsole/assistant/assistantRunStore.ts
|
|
3
|
-
var
|
|
5
|
+
var a = [
|
|
4
6
|
"datasink",
|
|
5
7
|
"dashboard",
|
|
6
8
|
"parser",
|
|
7
9
|
"widget"
|
|
8
|
-
],
|
|
10
|
+
], o = {
|
|
9
11
|
datasink: "pending",
|
|
10
12
|
dashboard: "pending",
|
|
11
13
|
parser: "pending",
|
|
12
14
|
widget: "pending"
|
|
13
|
-
},
|
|
15
|
+
}, s = /\/bigconsole\/(datasinks|dashboards|parsers)\/([A-Za-z0-9_-]+)/g, c = /widget\s+(added|created)/i, l = /^⏳\s*(.+?)…?$/, u = {
|
|
14
16
|
datasinks: "datasink",
|
|
15
17
|
dashboards: "dashboard",
|
|
16
18
|
parsers: "parser"
|
|
17
|
-
},
|
|
19
|
+
}, d = [
|
|
18
20
|
{
|
|
19
21
|
step: "datasink",
|
|
20
22
|
re: /"createDataSink"\s*:\s*\{[^{}]*?"id"\s*:\s*"([A-Za-z0-9_-]+)"/
|
|
@@ -27,8 +29,8 @@ var t = [
|
|
|
27
29
|
step: "parser",
|
|
28
30
|
re: /"createParser"\s*:\s*\{[^{}]*?"id"\s*:\s*"([A-Za-z0-9_-]+)"/
|
|
29
31
|
}
|
|
30
|
-
],
|
|
31
|
-
function
|
|
32
|
+
], f = /"createBigConsoleWidget"\s*:\s*\{/;
|
|
33
|
+
function p(e, t) {
|
|
32
34
|
let n = {
|
|
33
35
|
pending: 0,
|
|
34
36
|
running: 1,
|
|
@@ -37,69 +39,106 @@ function l(e, t) {
|
|
|
37
39
|
};
|
|
38
40
|
return n[t] > n[e] ? t : e;
|
|
39
41
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
function m() {
|
|
43
|
+
return {
|
|
44
|
+
isRunning: !1,
|
|
45
|
+
previewOpen: !1,
|
|
46
|
+
hasRun: !1,
|
|
47
|
+
prompt: "",
|
|
48
|
+
steps: { ...o },
|
|
49
|
+
entityIds: {},
|
|
50
|
+
caption: "",
|
|
51
|
+
error: null,
|
|
52
|
+
updatedAt: null
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function h(e, t) {
|
|
56
|
+
return e === null || t === null || e === t;
|
|
57
|
+
}
|
|
58
|
+
var g = r()((n, r) => ({
|
|
59
|
+
...m(),
|
|
44
60
|
richMounted: !1,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
let r = { ...e.steps };
|
|
53
|
-
for (let e of t) r[e] === "running" && (r[e] = "pending");
|
|
54
|
-
let i = t.find((e) => r[e] === "pending");
|
|
55
|
-
return i && (r[i] = "running"), {
|
|
61
|
+
workspaceId: null,
|
|
62
|
+
actorId: null,
|
|
63
|
+
startRun: (e) => n((t) => {
|
|
64
|
+
let n = { ...t.steps };
|
|
65
|
+
for (let e of a) n[e] === "running" && (n[e] = "pending");
|
|
66
|
+
let r = a.find((e) => n[e] === "pending");
|
|
67
|
+
return r && (n[r] = "running"), {
|
|
56
68
|
isRunning: !0,
|
|
57
69
|
previewOpen: !0,
|
|
58
70
|
hasRun: !0,
|
|
59
|
-
prompt:
|
|
60
|
-
steps:
|
|
71
|
+
prompt: e,
|
|
72
|
+
steps: n,
|
|
61
73
|
caption: "",
|
|
62
74
|
error: null,
|
|
63
75
|
updatedAt: Date.now()
|
|
64
76
|
};
|
|
65
77
|
}),
|
|
66
|
-
applyProgress: (
|
|
67
|
-
let
|
|
68
|
-
|
|
78
|
+
applyProgress: (e) => n((t) => {
|
|
79
|
+
let n = { ...t.steps }, r = { ...t.entityIds }, i = (e, t) => {
|
|
80
|
+
n[e] = p(n[e], "done"), t && e !== "widget" && (r[e] = t);
|
|
69
81
|
};
|
|
70
|
-
for (let
|
|
71
|
-
let
|
|
72
|
-
|
|
82
|
+
for (let t of e.matchAll(s)) {
|
|
83
|
+
let e = u[t[1] ?? ""];
|
|
84
|
+
e && t[2] && i(e, t[2]);
|
|
73
85
|
}
|
|
74
|
-
for (let { step:
|
|
75
|
-
let r =
|
|
76
|
-
r?.[1] &&
|
|
86
|
+
for (let { step: t, re: n } of d) {
|
|
87
|
+
let r = n.exec(e);
|
|
88
|
+
r?.[1] && i(t, r[1]);
|
|
77
89
|
}
|
|
78
|
-
(
|
|
79
|
-
let
|
|
80
|
-
return
|
|
81
|
-
steps:
|
|
82
|
-
entityIds:
|
|
83
|
-
caption:
|
|
90
|
+
(f.test(e) || c.test(e)) && (n.widget = p(n.widget, "done"), n.parser !== "done" && (n.parser = p(n.parser, "skipped")));
|
|
91
|
+
let o = a.find((e) => n[e] === "pending" || n[e] === "running");
|
|
92
|
+
return o && (n[o] = p(n[o], "running")), {
|
|
93
|
+
steps: n,
|
|
94
|
+
entityIds: r,
|
|
95
|
+
caption: e.split("\n").map((e) => e.trim()).reverse().map((e) => l.exec(e)?.[1]).find((e) => !!e) ?? t.caption,
|
|
84
96
|
updatedAt: Date.now()
|
|
85
97
|
};
|
|
86
98
|
}),
|
|
87
|
-
finishRun: (
|
|
88
|
-
let
|
|
89
|
-
for (let e of
|
|
99
|
+
finishRun: (e) => n((t) => {
|
|
100
|
+
let n = { ...t.steps };
|
|
101
|
+
for (let e of a) n[e] === "running" && (n[e] = "pending");
|
|
90
102
|
return {
|
|
91
103
|
isRunning: !1,
|
|
92
|
-
error:
|
|
93
|
-
steps:
|
|
104
|
+
error: e,
|
|
105
|
+
steps: n,
|
|
94
106
|
caption: "",
|
|
95
107
|
updatedAt: Date.now()
|
|
96
108
|
};
|
|
97
109
|
}),
|
|
98
|
-
openPreview: () =>
|
|
99
|
-
closePreview: () =>
|
|
100
|
-
setRichMounted: (
|
|
110
|
+
openPreview: () => n({ previewOpen: !0 }),
|
|
111
|
+
closePreview: () => n({ previewOpen: !1 }),
|
|
112
|
+
setRichMounted: (e) => n({ richMounted: e }),
|
|
113
|
+
bindWorkspace: (a) => {
|
|
114
|
+
let o = r().workspaceId;
|
|
115
|
+
if (o === a) return;
|
|
116
|
+
o !== null && t(o);
|
|
117
|
+
let s = a === null ? null : i(), c = a === null ? null : e(a, s);
|
|
118
|
+
n({
|
|
119
|
+
...m(),
|
|
120
|
+
...c,
|
|
121
|
+
workspaceId: a,
|
|
122
|
+
actorId: s
|
|
123
|
+
});
|
|
124
|
+
}
|
|
101
125
|
}));
|
|
126
|
+
g.subscribe((e, r) => {
|
|
127
|
+
if (e.workspaceId === null || e.workspaceId === r.workspaceId && e.hasRun === r.hasRun && e.previewOpen === r.previewOpen && e.steps === r.steps && e.entityIds === r.entityIds && e.updatedAt === r.updatedAt) return;
|
|
128
|
+
if (!e.hasRun) {
|
|
129
|
+
t(e.workspaceId);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
let i = {
|
|
133
|
+
hasRun: !0,
|
|
134
|
+
previewOpen: e.previewOpen,
|
|
135
|
+
steps: e.steps,
|
|
136
|
+
entityIds: e.entityIds,
|
|
137
|
+
updatedAt: e.updatedAt
|
|
138
|
+
};
|
|
139
|
+
n(e.workspaceId, e.actorId, i);
|
|
140
|
+
});
|
|
102
141
|
//#endregion
|
|
103
|
-
export {
|
|
142
|
+
export { a as PREVIEW_STEPS, h as runBelongsToWorkspace, g as useAssistantRunStore };
|
|
104
143
|
|
|
105
144
|
//# sourceMappingURL=assistantRunStore.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistantRunStore.js","names":[],"sources":["../../../src/bigconsole/assistant/assistantRunStore.ts"],"sourcesContent":["/**\n * What the assistant is building, right now.\n *\n * The agent's narration is the only live signal the frontend gets — there is no\n * event stream — so this store turns it into something a UI can render: a\n * monotone DataSink → Dashboard → Parser → Widget rail, plus the id of each\n * entity as it appears.\n *\n * Three rules make the parsing survive a model that phrases things differently:\n *\n * 1. Read the id from BOTH the link path (`/bigconsole/datasinks/<id>`) AND the\n * raw mutation JSON (`{ \"createDataSink\": { \"id\": \"<uuid>\", … } }`). The agent\n * freely prints one, the other, or both — if we only watched for the link a\n * turn that printed just JSON would leave the tab stuck on \"Waiting…\".\n * 2. Never regress. `progress.content` is REPLACED on every poll (not appended),\n * so a shorter string arriving later must not rewind the rail.\n * 3. Accumulate across an incremental conversation. Each prompt builds one piece;\n * a new prompt must NOT wipe the pieces already built, or the earlier tabs go\n * blank. `startRun` preserves prior steps/ids and only lights up the next one.\n */\nimport { create } from 'zustand';\n\nexport type PreviewStepKey = 'datasink' | 'dashboard' | 'parser' | 'widget';\nexport type PreviewStepStatus = 'pending' | 'running' | 'done' | 'skipped';\n\nexport const PREVIEW_STEPS: readonly PreviewStepKey[] = ['datasink', 'dashboard', 'parser', 'widget'];\n\ninterface AssistantRunState {\n /** A turn is in flight. */\n isRunning: boolean;\n /** The panel is on screen. Auto-opens when a turn starts; the user can close it. */\n previewOpen: boolean;\n /** True once any turn has run — gates the \"Preview\" button. */\n hasRun: boolean;\n /**\n * The full, context-rich panel (mounted inside BigConsoleRoot, with live entity\n * tabs) is on screen right now. The lightweight app-shell panel reads this and\n * steps aside so the two never render at once. It flips false the instant you\n * leave a /bigconsole page — that is what lets the mini panel take over and keep\n * the build visible on whatever screen you moved to.\n */\n richMounted: boolean;\n prompt: string;\n steps: Record<PreviewStepKey, PreviewStepStatus>;\n /** Ids harvested from the agent's links. A widget has no link of its own. */\n entityIds: { datasink?: string; dashboard?: string; parser?: string };\n /** Newest \"⏳ …\" line — what the agent says it is doing this second. */\n caption: string;\n /** Set when the turn fails, so the panel can stop pretending to be live. */\n error: string | null;\n /** Epoch ms of the last progress tick — drives the \"updated 3s ago\" strip. */\n updatedAt: number | null;\n\n startRun: (prompt: string) => void;\n applyProgress: (content: string) => void;\n finishRun: (error: string | null) => void;\n openPreview: () => void;\n closePreview: () => void;\n setRichMounted: (mounted: boolean) => void;\n}\n\nconst IDLE_STEPS: Record<PreviewStepKey, PreviewStepStatus> = {\n datasink: 'pending',\n dashboard: 'pending',\n parser: 'pending',\n widget: 'pending',\n};\n\n/** `/bigconsole/datasinks/<id>` — the trustworthy part of the narration. */\nconst ENTITY_LINK = /\\/bigconsole\\/(datasinks|dashboards|parsers)\\/([A-Za-z0-9_-]+)/g;\n/** The widget links to its dashboard, so it can only be detected from the prose. */\nconst WIDGET_DONE = /widget\\s+(added|created)/i;\nconst RUNNING_LINE = /^⏳\\s*(.+?)…?$/;\n\nconst LINK_TO_STEP: Record<string, PreviewStepKey> = {\n datasinks: 'datasink',\n dashboards: 'dashboard',\n parsers: 'parser',\n};\n\n/**\n * The id straight out of a raw mutation response the agent printed instead of\n * (or as well as) a link, e.g. `{ \"createDataSink\": { \"id\": \"<uuid>\", … } }`.\n * `[^{}]` keeps each match inside its own object so we grab that mutation's id,\n * not a neighbour's. This is what stops \"Waiting…\" when no link is narrated.\n */\nconst JSON_ID_STEPS: ReadonlyArray<{ step: Exclude<PreviewStepKey, 'widget'>; re: RegExp }> = [\n { step: 'datasink', re: /\"createDataSink\"\\s*:\\s*\\{[^{}]*?\"id\"\\s*:\\s*\"([A-Za-z0-9_-]+)\"/ },\n { step: 'dashboard', re: /\"createBigConsoleDashboard\"\\s*:\\s*\\{[^{}]*?\"id\"\\s*:\\s*\"([A-Za-z0-9_-]+)\"/ },\n { step: 'parser', re: /\"createParser\"\\s*:\\s*\\{[^{}]*?\"id\"\\s*:\\s*\"([A-Za-z0-9_-]+)\"/ },\n];\n/** The widget has no page of its own; its create response is enough to mark it done. */\nconst WIDGET_CREATE_JSON = /\"createBigConsoleWidget\"\\s*:\\s*\\{/;\n\n/** Only ever moves a step forward. */\nfunction advance(current: PreviewStepStatus, next: PreviewStepStatus): PreviewStepStatus {\n const rank: Record<PreviewStepStatus, number> = { pending: 0, running: 1, skipped: 2, done: 3 };\n return rank[next] > rank[current] ? next : current;\n}\n\nexport const useAssistantRunStore = create<AssistantRunState>()((set) => ({\n isRunning: false,\n previewOpen: false,\n hasRun: false,\n richMounted: false,\n prompt: '',\n steps: { ...IDLE_STEPS },\n entityIds: {},\n caption: '',\n error: null,\n updatedAt: null,\n\n startRun: (prompt) =>\n set((state) => {\n // A new prompt CONTINUES the same use case (incremental flow) — keep every\n // step and id already built. Clear any stale spinner from the prior turn,\n // then light up the earliest step that has not landed yet so the \"working\n // on it\" indicator sits on the right tab.\n const steps = { ...state.steps };\n for (const key of PREVIEW_STEPS) {\n if (steps[key] === 'running') steps[key] = 'pending';\n }\n const next = PREVIEW_STEPS.find((key) => steps[key] === 'pending');\n if (next) steps[next] = 'running';\n return {\n isRunning: true,\n // Auto-open: the whole point is that you SEE the build happen without\n // having to go looking for it.\n previewOpen: true,\n hasRun: true,\n prompt,\n steps,\n // entityIds intentionally preserved (merged) so earlier tabs keep content.\n caption: '',\n error: null,\n updatedAt: Date.now(),\n };\n }),\n\n applyProgress: (content) =>\n set((state) => {\n const steps = { ...state.steps };\n const entityIds = { ...state.entityIds };\n\n const markDone = (step: PreviewStepKey, id?: string) => {\n steps[step] = advance(steps[step], 'done');\n // Always record the freshest id seen this turn (never \"first wins\"): an\n // incremental prompt that rebuilds a piece must point the tab at the new\n // one. `step !== 'widget'` narrows to the keys entityIds actually holds.\n if (id && step !== 'widget') entityIds[step] = id;\n };\n\n // 1) Markdown link paths.\n for (const match of content.matchAll(ENTITY_LINK)) {\n const step = LINK_TO_STEP[match[1] ?? ''];\n if (step && match[2]) markDone(step, match[2]);\n }\n // 2) Raw mutation JSON (the agent often prints this instead of a link).\n for (const { step, re } of JSON_ID_STEPS) {\n const match = re.exec(content);\n if (match?.[1]) markDone(step, match[1]);\n }\n\n if (WIDGET_CREATE_JSON.test(content) || WIDGET_DONE.test(content)) {\n steps.widget = advance(steps.widget, 'done');\n // The parser is optional — the agent skips it when the rows are already\n // shaped. If the widget landed and the parser was never built, it was\n // skipped, not stuck.\n if (steps.parser !== 'done') steps.parser = advance(steps.parser, 'skipped');\n }\n\n // The first step that has not finished is the one being worked on.\n const active = PREVIEW_STEPS.find((key) => steps[key] === 'pending' || steps[key] === 'running');\n if (active) steps[active] = advance(steps[active], 'running');\n\n const caption =\n content\n .split('\\n')\n .map((line) => line.trim())\n .reverse()\n .map((line) => RUNNING_LINE.exec(line)?.[1])\n .find((label): label is string => Boolean(label)) ?? state.caption;\n\n return { steps, entityIds, caption, updatedAt: Date.now() };\n }),\n\n finishRun: (error) =>\n set((state) => {\n // The turn is over — stop the live spinner. A step still 'running' didn't\n // land this turn, so drop it back to 'pending' (a later prompt can pick it\n // up). NEVER blanket-skip pending steps: in the incremental flow the later\n // steps simply have not been asked for yet, and 'done' steps must stay done.\n const steps = { ...state.steps };\n for (const key of PREVIEW_STEPS) {\n if (steps[key] === 'running') steps[key] = 'pending';\n }\n return { isRunning: false, error, steps, caption: '', updatedAt: Date.now() };\n }),\n\n openPreview: () => set({ previewOpen: true }),\n closePreview: () => set({ previewOpen: false }),\n setRichMounted: (mounted) => set({ richMounted: mounted }),\n}));\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"assistantRunStore.js","names":[],"sources":["../../../src/bigconsole/assistant/assistantRunStore.ts"],"sourcesContent":["/**\n * What the assistant is building, right now.\n *\n * The agent's narration is the only live signal the frontend gets — there is no\n * event stream — so this store turns it into something a UI can render: a\n * monotone DataSink → Dashboard → Parser → Widget rail, plus the id of each\n * entity as it appears.\n *\n * Three rules make the parsing survive a model that phrases things differently:\n *\n * 1. Read the id from BOTH the link path (`/bigconsole/datasinks/<id>`) AND the\n * raw mutation JSON (`{ \"createDataSink\": { \"id\": \"<uuid>\", … } }`). The agent\n * freely prints one, the other, or both — if we only watched for the link a\n * turn that printed just JSON would leave the tab stuck on \"Waiting…\".\n * 2. Never regress. `progress.content` is REPLACED on every poll (not appended),\n * so a shorter string arriving later must not rewind the rail.\n * 3. Accumulate across an incremental conversation. Each prompt builds one piece;\n * a new prompt must NOT wipe the pieces already built, or the earlier tabs go\n * blank. `startRun` preserves prior steps/ids and only lights up the next one.\n *\n * ★★ It belongs to ONE workspace (BOFF-7799). The page surfaces bind the active\n * workspace (`bindWorkspace`); switching clears the run, and the slice the page\n * preview draws is kept in `sessionStorage` per workspace, so a reload in the\n * same tab brings the preview back beside the restored conversation. See\n * `assistantRunPersistence.ts` for what is kept and why.\n */\nimport { getActorId } from '@burdenoff/fe-libs/shared/assistant/context';\nimport { create } from 'zustand';\n\nimport { readPersistedRun, removePersistedRun, writePersistedRun, type PersistedRun } from './assistantRunPersistence';\n\nexport type PreviewStepKey = 'datasink' | 'dashboard' | 'parser' | 'widget';\nexport type PreviewStepStatus = 'pending' | 'running' | 'done' | 'skipped';\n\nexport const PREVIEW_STEPS: readonly PreviewStepKey[] = ['datasink', 'dashboard', 'parser', 'widget'];\n\ninterface AssistantRunState {\n /** A turn is in flight. */\n isRunning: boolean;\n /** The panel is on screen. Auto-opens when a turn starts; the user can close it. */\n previewOpen: boolean;\n /** True once any turn has run — gates the \"Preview\" button. */\n hasRun: boolean;\n /**\n * The full, context-rich panel (mounted inside BigConsoleRoot, with live entity\n * tabs) is on screen right now. The lightweight app-shell panel reads this and\n * steps aside so the two never render at once. It flips false the instant you\n * leave a /bigconsole page — that is what lets the mini panel take over and keep\n * the build visible on whatever screen you moved to.\n */\n richMounted: boolean;\n prompt: string;\n steps: Record<PreviewStepKey, PreviewStepStatus>;\n /** Ids harvested from the agent's links. A widget has no link of its own. */\n entityIds: { datasink?: string; dashboard?: string; parser?: string };\n /** Newest \"⏳ …\" line — what the agent says it is doing this second. */\n caption: string;\n /** Set when the turn fails, so the panel can stop pretending to be live. */\n error: string | null;\n /** Epoch ms of the last progress tick — drives the \"updated 3s ago\" strip. */\n updatedAt: number | null;\n /**\n * The workspace this run belongs to, and the one its kept copy is filed\n * under. Null until a page surface binds the active workspace.\n */\n workspaceId: string | null;\n /** The actor the kept copy is written for, read once when the workspace is bound. */\n actorId: string | null;\n\n startRun: (prompt: string) => void;\n applyProgress: (content: string) => void;\n finishRun: (error: string | null) => void;\n openPreview: () => void;\n closePreview: () => void;\n setRichMounted: (mounted: boolean) => void;\n /**\n * Point the store at the active workspace. A change CLEARS the run and forgets\n * the previous workspace's kept copy, then restores this workspace's copy, if\n * this tab kept one for this actor. Binding the same workspace is a no-op, so\n * every surface may call it.\n */\n bindWorkspace: (workspaceId: string | null) => void;\n}\n\nconst IDLE_STEPS: Record<PreviewStepKey, PreviewStepStatus> = {\n datasink: 'pending',\n dashboard: 'pending',\n parser: 'pending',\n widget: 'pending',\n};\n\n/** `/bigconsole/datasinks/<id>` — the trustworthy part of the narration. */\nconst ENTITY_LINK = /\\/bigconsole\\/(datasinks|dashboards|parsers)\\/([A-Za-z0-9_-]+)/g;\n/** The widget links to its dashboard, so it can only be detected from the prose. */\nconst WIDGET_DONE = /widget\\s+(added|created)/i;\nconst RUNNING_LINE = /^⏳\\s*(.+?)…?$/;\n\nconst LINK_TO_STEP: Record<string, PreviewStepKey> = {\n datasinks: 'datasink',\n dashboards: 'dashboard',\n parsers: 'parser',\n};\n\n/**\n * The id straight out of a raw mutation response the agent printed instead of\n * (or as well as) a link, e.g. `{ \"createDataSink\": { \"id\": \"<uuid>\", … } }`.\n * `[^{}]` keeps each match inside its own object so we grab that mutation's id,\n * not a neighbour's. This is what stops \"Waiting…\" when no link is narrated.\n */\nconst JSON_ID_STEPS: ReadonlyArray<{ step: Exclude<PreviewStepKey, 'widget'>; re: RegExp }> = [\n { step: 'datasink', re: /\"createDataSink\"\\s*:\\s*\\{[^{}]*?\"id\"\\s*:\\s*\"([A-Za-z0-9_-]+)\"/ },\n { step: 'dashboard', re: /\"createBigConsoleDashboard\"\\s*:\\s*\\{[^{}]*?\"id\"\\s*:\\s*\"([A-Za-z0-9_-]+)\"/ },\n { step: 'parser', re: /\"createParser\"\\s*:\\s*\\{[^{}]*?\"id\"\\s*:\\s*\"([A-Za-z0-9_-]+)\"/ },\n];\n/** The widget has no page of its own; its create response is enough to mark it done. */\nconst WIDGET_CREATE_JSON = /\"createBigConsoleWidget\"\\s*:\\s*\\{/;\n\n/** Only ever moves a step forward. */\nfunction advance(current: PreviewStepStatus, next: PreviewStepStatus): PreviewStepStatus {\n const rank: Record<PreviewStepStatus, number> = { pending: 0, running: 1, skipped: 2, done: 3 };\n return rank[next] > rank[current] ? next : current;\n}\n\ntype RunFields = Pick<\n AssistantRunState,\n 'isRunning' | 'previewOpen' | 'hasRun' | 'prompt' | 'steps' | 'entityIds' | 'caption' | 'error' | 'updatedAt'\n>;\n\n/** A run that has not started. `richMounted` is a mount flag, not run state, so it is not here. */\nfunction idleRun(): RunFields {\n return {\n isRunning: false,\n previewOpen: false,\n hasRun: false,\n prompt: '',\n steps: { ...IDLE_STEPS },\n entityIds: {},\n caption: '',\n error: null,\n updatedAt: null,\n };\n}\n\n/**\n * May a run that started in `runWorkspaceId` drive a store bound to\n * `boundWorkspaceId`?\n *\n * ★★ WHY IT EXISTS. A workspace switch clears the store, but a turn started in\n * the previous workspace keeps streaming. Without this its next snapshot would\n * refill the cleared store, and the refill would be kept under the NEW\n * workspace's key, which is the leak the switch exists to prevent.\n *\n * ★ Lenient on null, deliberately: fe-libs reports `null` when it cannot read\n * the workspace, and an unbound store has nothing to protect. Refusing either\n * would silently stop every preview rather than guard one.\n */\nexport function runBelongsToWorkspace(runWorkspaceId: string | null, boundWorkspaceId: string | null): boolean {\n return runWorkspaceId === null || boundWorkspaceId === null || runWorkspaceId === boundWorkspaceId;\n}\n\nexport const useAssistantRunStore = create<AssistantRunState>()((set, get) => ({\n ...idleRun(),\n richMounted: false,\n workspaceId: null,\n actorId: null,\n\n startRun: (prompt) =>\n set((state) => {\n // A new prompt CONTINUES the same use case (incremental flow) — keep every\n // step and id already built. Clear any stale spinner from the prior turn,\n // then light up the earliest step that has not landed yet so the \"working\n // on it\" indicator sits on the right tab.\n const steps = { ...state.steps };\n for (const key of PREVIEW_STEPS) {\n if (steps[key] === 'running') steps[key] = 'pending';\n }\n const next = PREVIEW_STEPS.find((key) => steps[key] === 'pending');\n if (next) steps[next] = 'running';\n return {\n isRunning: true,\n // Auto-open: the whole point is that you SEE the build happen without\n // having to go looking for it.\n previewOpen: true,\n hasRun: true,\n prompt,\n steps,\n // entityIds intentionally preserved (merged) so earlier tabs keep content.\n caption: '',\n error: null,\n updatedAt: Date.now(),\n };\n }),\n\n applyProgress: (content) =>\n set((state) => {\n const steps = { ...state.steps };\n const entityIds = { ...state.entityIds };\n\n const markDone = (step: PreviewStepKey, id?: string) => {\n steps[step] = advance(steps[step], 'done');\n // Always record the freshest id seen this turn (never \"first wins\"): an\n // incremental prompt that rebuilds a piece must point the tab at the new\n // one. `step !== 'widget'` narrows to the keys entityIds actually holds.\n if (id && step !== 'widget') entityIds[step] = id;\n };\n\n // 1) Markdown link paths.\n for (const match of content.matchAll(ENTITY_LINK)) {\n const step = LINK_TO_STEP[match[1] ?? ''];\n if (step && match[2]) markDone(step, match[2]);\n }\n // 2) Raw mutation JSON (the agent often prints this instead of a link).\n for (const { step, re } of JSON_ID_STEPS) {\n const match = re.exec(content);\n if (match?.[1]) markDone(step, match[1]);\n }\n\n if (WIDGET_CREATE_JSON.test(content) || WIDGET_DONE.test(content)) {\n steps.widget = advance(steps.widget, 'done');\n // The parser is optional — the agent skips it when the rows are already\n // shaped. If the widget landed and the parser was never built, it was\n // skipped, not stuck.\n if (steps.parser !== 'done') steps.parser = advance(steps.parser, 'skipped');\n }\n\n // The first step that has not finished is the one being worked on.\n const active = PREVIEW_STEPS.find((key) => steps[key] === 'pending' || steps[key] === 'running');\n if (active) steps[active] = advance(steps[active], 'running');\n\n const caption =\n content\n .split('\\n')\n .map((line) => line.trim())\n .reverse()\n .map((line) => RUNNING_LINE.exec(line)?.[1])\n .find((label): label is string => Boolean(label)) ?? state.caption;\n\n return { steps, entityIds, caption, updatedAt: Date.now() };\n }),\n\n finishRun: (error) =>\n set((state) => {\n // The turn is over — stop the live spinner. A step still 'running' didn't\n // land this turn, so drop it back to 'pending' (a later prompt can pick it\n // up). NEVER blanket-skip pending steps: in the incremental flow the later\n // steps simply have not been asked for yet, and 'done' steps must stay done.\n const steps = { ...state.steps };\n for (const key of PREVIEW_STEPS) {\n if (steps[key] === 'running') steps[key] = 'pending';\n }\n return { isRunning: false, error, steps, caption: '', updatedAt: Date.now() };\n }),\n\n openPreview: () => set({ previewOpen: true }),\n closePreview: () => set({ previewOpen: false }),\n setRichMounted: (mounted) => set({ richMounted: mounted }),\n\n bindWorkspace: (workspaceId) => {\n const previous = get().workspaceId;\n if (previous === workspaceId) return;\n if (previous !== null) removePersistedRun(previous);\n\n const actorId = workspaceId === null ? null : getActorId();\n const restored = workspaceId === null ? null : readPersistedRun(workspaceId, actorId);\n set({ ...idleRun(), ...restored, workspaceId, actorId });\n },\n}));\n\n/**\n * Keep the page preview's slice for the bound workspace on every change to it.\n *\n * ★ A subscription rather than a write in each action: five actions change the\n * slice, and a write missed in any one of them would restore a stale preview.\n * `bindWorkspace` also lands here, which is harmless: it rewrites what it read.\n */\nuseAssistantRunStore.subscribe((state, previous) => {\n if (state.workspaceId === null) return;\n if (\n state.workspaceId === previous.workspaceId &&\n state.hasRun === previous.hasRun &&\n state.previewOpen === previous.previewOpen &&\n state.steps === previous.steps &&\n state.entityIds === previous.entityIds &&\n state.updatedAt === previous.updatedAt\n ) {\n return;\n }\n if (!state.hasRun) {\n removePersistedRun(state.workspaceId);\n return;\n }\n const run: PersistedRun = {\n hasRun: true,\n previewOpen: state.previewOpen,\n steps: state.steps,\n entityIds: state.entityIds,\n updatedAt: state.updatedAt,\n };\n writePersistedRun(state.workspaceId, state.actorId, run);\n});\n"],"mappings":"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"}
|
|
@@ -1,60 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { jsx as c } from "react/jsx-runtime";
|
|
6
|
-
import { useI18n as l } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
7
|
-
import { AssistantEntityPreview as u, useAssistantStore as d } from "@burdenoff/fe-libs/shared/assistant";
|
|
1
|
+
import { runBelongsToWorkspace as e, useAssistantRunStore as t } from "./assistantRunStore.js";
|
|
2
|
+
import { previewRunAction as n } from "./previewRunAction.js";
|
|
3
|
+
import { useMemo as r } from "react";
|
|
4
|
+
import { useAssistantStore as i } from "@burdenoff/fe-libs/shared/assistant";
|
|
8
5
|
//#region src/bigconsole/assistant/previewController.tsx
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"data-variant": "assistant",
|
|
18
|
-
className: "min-w-0 p-3",
|
|
19
|
-
children: /* @__PURE__ */ c(u, {
|
|
20
|
-
title: o("bigconsole.widget.aiPreviewMini.title", "AI Preview"),
|
|
21
|
-
steps: h,
|
|
22
|
-
caption: s ? f || o("bigconsole.widget.aiPreviewMini.working", "Working…") : o("bigconsole.widget.aiPreviewMini.done", "Done"),
|
|
23
|
-
error: p,
|
|
24
|
-
meta: /* @__PURE__ */ c(n, {
|
|
25
|
-
state: s ? "running" : p ? "stopped" : "idle",
|
|
26
|
-
stateLabel: s ? o("bigconsole.widget.aiPreviewMini.live", "Live") : p ? o("bigconsole.widget.aiPreviewMini.stopped", "Stopped") : null,
|
|
27
|
-
updatedLabel: o("bigconsole.widget.aiPreviewMini.updated", "updated"),
|
|
28
|
-
updatedAt: m,
|
|
29
|
-
ago: (e) => i(e, o)
|
|
30
|
-
}),
|
|
31
|
-
labels: a(o)
|
|
32
|
-
})
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
var p = /* @__PURE__ */ new Set();
|
|
36
|
-
function m(e) {
|
|
37
|
-
let n = d.getState(), r = n.conversations.find((t) => t.id === e.run.conversationId)?.mode ?? n.mode;
|
|
38
|
-
if (o(e.kind, e.run.runId, r, p) === "ignore") return;
|
|
39
|
-
let i = t.getState();
|
|
40
|
-
if (e.kind === "start") {
|
|
41
|
-
p.add(e.run.runId), i.startRun("");
|
|
6
|
+
var a = /* @__PURE__ */ new Set();
|
|
7
|
+
function o(r) {
|
|
8
|
+
let o = i.getState(), s = o.conversations.find((e) => e.id === r.run.conversationId)?.mode ?? o.mode;
|
|
9
|
+
if (n(r.kind, r.run.runId, s, a) === "ignore") return;
|
|
10
|
+
let c = t.getState(), l = e(r.run.workspaceId, c.workspaceId);
|
|
11
|
+
if (r.kind === "start") {
|
|
12
|
+
if (!l) return;
|
|
13
|
+
a.add(r.run.runId), c.startRun("");
|
|
42
14
|
return;
|
|
43
15
|
}
|
|
44
|
-
if (
|
|
45
|
-
|
|
16
|
+
if (r.kind === "snapshot") {
|
|
17
|
+
if (!l) return;
|
|
18
|
+
c.applyProgress(r.content);
|
|
46
19
|
return;
|
|
47
20
|
}
|
|
48
|
-
|
|
21
|
+
a.delete(r.run.runId), l && c.finishRun(r.outcome === "error" ? r.error ?? "The build stopped." : null);
|
|
49
22
|
}
|
|
50
|
-
function
|
|
51
|
-
return
|
|
52
|
-
label: "Build preview",
|
|
53
|
-
render: () => /* @__PURE__ */ c(f, {}),
|
|
54
|
-
onEvent: m
|
|
55
|
-
}), []);
|
|
23
|
+
function s() {
|
|
24
|
+
return r(() => ({ onEvent: o }), []);
|
|
56
25
|
}
|
|
57
26
|
//#endregion
|
|
58
|
-
export {
|
|
27
|
+
export { s as useBigConsoleAssistantPreview };
|
|
59
28
|
|
|
60
29
|
//# sourceMappingURL=previewController.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"previewController.js","names":[],"sources":["../../../src/bigconsole/assistant/previewController.tsx"],"sourcesContent":["/**\n * BigConsole's preview capability for the shared assistant panel (BC3).\n *\n * ★★★ THIS IS WHAT REPLACES THE TRANSPORT AS THE RUN STORE'S DRIVER.\n *\n * Today `createSandboxAssistantTransport` calls `startRun` / `applyProgress` /\n * `finishRun` itself, because it owns the turn. The shared panel consumes NO\n * transport at all — `AssistantChatArea` calls fe-libs' own `assistant/api`\n * directly — so once BC4 mounts the panel there is nothing left to drive the\n * store, and the live build preview would simply stop moving.\n *\n * fe-libs' preview capability is that missing wire: the bridge announces\n * `start` / `snapshot` / `end` for the run in flight, and this controller\n * forwards them to the same three store actions the transport used to call. The\n * store, both preview panels, and everything downstream are untouched.\n *\n * ★★ ABSENCE IS THE OFF-SWITCH. A product that supplies no controller gets no\n * toggle and no overlay; there is deliberately no second `enablePreview` flag to\n * disagree with the controller's presence. So this hook always returns a\n * controller — gating belongs at the mount site (BC4), not here.\n */\nimport { useMemo } from 'react';\nimport {\n AssistantEntityPreview,\n useAssistantStore,\n type AssistantPreviewController,\n type AssistantPreviewEvent,\n type AssistantPreviewStep,\n} from '@burdenoff/fe-libs/shared/assistant';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nimport { PreviewMeta, STEP_LABEL, formatAgo, previewLabels } from '../components/preview/previewShared';\nimport { PREVIEW_STEPS, useAssistantRunStore } from './assistantRunStore';\nimport { previewRunAction, type PreviewRunAction } from './previewRunAction';\n\n/**\n * The surface fe-libs renders inside its overlay.\n *\n * ★★ A COMPONENT, returned as JSX from `render()` — never hooks called inside\n * `render()` itself. fe-libs invokes `render()` from a single `PreviewSurface`\n * component, so hooks called there would belong to THAT fiber and any change in\n * their layout becomes \"rendered more hooks than during the previous render\".\n * Returning an element gives this surface its own fiber, which is both safer and\n * what lets it subscribe to the store independently.\n *\n * ★ Store-only by construction: no Apollo, no BigConsole context, no data\n * fetching — the same discipline that lets `AiPreviewMiniPanel` mount above the\n * router. The rich per-entity tabs stay in `AiPreviewPanel`, which lives inside\n * `BigConsoleRoot` where the data layer actually exists.\n *\n * ★ It renders the shared `AssistantEntityPreview` with the SAME mapping the\n * mini panel uses, and reuses `STEP_LABEL` / `previewLabels` / `PreviewMeta`\n * rather than restating them. Those helpers were extracted precisely because the\n * two existing panels kept drifting apart; a third copy here would reintroduce\n * the bug they were created to kill.\n */\nfunction BigConsoleAssistantPreviewSurface() {\n const { t } = useI18n();\n const isRunning = useAssistantRunStore((state) => state.isRunning);\n const steps = useAssistantRunStore((state) => state.steps);\n const caption = useAssistantRunStore((state) => state.caption);\n const error = useAssistantRunStore((state) => state.error);\n const updatedAt = useAssistantRunStore((state) => state.updatedAt);\n\n const railSteps: AssistantPreviewStep[] = PREVIEW_STEPS.map((key) => ({\n id: key,\n label: t(STEP_LABEL[key].key, STEP_LABEL[key].fallback),\n status: steps[key],\n }));\n\n return (\n <div data-testid=\"ai-preview-panel\" data-variant=\"assistant\" className=\"min-w-0 p-3\">\n <AssistantEntityPreview\n title={t('bigconsole.widget.aiPreviewMini.title', 'AI Preview')}\n steps={railSteps}\n // ★★ TWO branches, not three. The shared surface renders\n // `{error ? '' : caption}` — it BLANKS the caption whenever `error` is\n // set — so a failure branch here would compute a string that is then\n // discarded. The failure is named in `meta` instead, which\n // `PreviewHeader` renders without consulting `error`.\n caption={\n isRunning\n ? caption || t('bigconsole.widget.aiPreviewMini.working', 'Working…')\n : t('bigconsole.widget.aiPreviewMini.done', 'Done')\n }\n error={error}\n meta={\n <PreviewMeta\n state={isRunning ? 'running' : error ? 'stopped' : 'idle'}\n stateLabel={\n isRunning\n ? t('bigconsole.widget.aiPreviewMini.live', 'Live')\n : error\n ? t('bigconsole.widget.aiPreviewMini.stopped', 'Stopped')\n : null\n }\n updatedLabel={t('bigconsole.widget.aiPreviewMini.updated', 'updated')}\n updatedAt={updatedAt}\n ago={(seconds) => formatAgo(seconds, t)}\n />\n }\n labels={previewLabels(t)}\n />\n </div>\n );\n}\n\n/**\n * ★ Re-exported so consumers keep ONE import path, while the RULE itself lives\n * in a module that loads without a DOM. Importing THIS file pulls fe-libs'\n * assistant barrel, which touches `document` at load time — so the decision had\n * to move out before it could be tested. See `previewRunAction.ts`.\n *\n * ★ Local bindings, not `export … from`: a bare re-export creates no local name,\n * so `applyPreviewEvent` below would be calling something this module does not\n * actually have.\n */\nexport { previewRunAction };\nexport type { PreviewRunAction };\n\n/**\n * The runs this controller has adopted — Build runs only.\n *\n * ★ Module-level, like the handler itself: it must outlive the controller memo\n * and every re-render, and nothing renders it.\n */\nconst adoptedRuns = new Set<string>();\n\n/**\n * Forward one bridge event to the run store.\n *\n * ★ Module-level and imperative (`getState()`), NOT a hook: the bridge calls\n * this from an effect, and reading the store through a selector here would make\n * the controller depend on state it never renders.\n */\nfunction applyPreviewEvent(event: AssistantPreviewEvent): void {\n /**\n * ★ The mode of THIS RUN's conversation, never the panel's. The panel's `mode`\n * is whatever the user last selected, which need not be the mode the run in\n * flight was started in. Falls back to the panel mode only when the\n * conversation is gone.\n */\n const assistant = useAssistantStore.getState();\n const modeOfRun = assistant.conversations.find((c) => c.id === event.run.conversationId)?.mode ?? assistant.mode;\n\n if (previewRunAction(event.kind, event.run.runId, modeOfRun, adoptedRuns) === 'ignore') {\n return;\n }\n\n const store = useAssistantRunStore.getState();\n\n if (event.kind === 'start') {\n adoptedRuns.add(event.run.runId);\n /**\n * ★★ THE PROMPT IS NOT AVAILABLE, and that is a real gap rather than an\n * oversight. `AssistantPreviewRun` carries `runId`, `conversationId`,\n * `messageId`, `workspaceId` and `actorId` — no turn text — so the store's\n * `prompt` (which only feeds an informational line) is left empty. The\n * transport could pass it because it owned the turn; the bridge cannot,\n * because it observes one. Everything the preview actually draws comes from\n * `applyProgress`, so nothing visible depends on this.\n */\n store.startRun('');\n return;\n }\n\n if (event.kind === 'snapshot') {\n /**\n * ★ `content` is the PROSE with the tool-activity lines already removed by\n * fe-libs. That is what this store wants: it harvests entity links, raw\n * mutation ids and the newest `⏳ …` caption out of the agent's narration,\n * all of which live in the prose. The structured `event.steps` describe TOOL\n * calls, which are a different rail from BigConsole's four build steps.\n */\n store.applyProgress(event.content);\n return;\n }\n\n /**\n * ★★ `abandoned` is neither success nor failure — it is a run the bridge can\n * no longer follow (cancelled, superseded, or navigated away from mid-turn),\n * and the turn may well still be executing. Passing its text as an error would\n * paint a red \"Build stopped\" banner over a build that is very likely still\n * running; passing nothing at all would leave the spinner going forever. So it\n * ends the run WITHOUT an error, which stops the spinner and leaves every step\n * already marked `done` exactly as it was.\n */\n /**\n * ★ Released here, and only here. Run ids are `conversationId:messageId`, so\n * they are never reused and this is a leak rather than a correctness bug — but\n * an unbounded set that grows for the life of the tab is still a leak, and\n * `end` is the one event guaranteed to arrive for every adopted run\n * (`abandoned` included — that outcome exists precisely so a run the bridge\n * can no longer follow still terminates).\n */\n adoptedRuns.delete(event.run.runId);\n store.finishRun(event.outcome === 'error' ? (event.error ?? 'The build stopped.') : null);\n}\n\n/**\n * BigConsole's preview controller, stable across renders.\n *\n * ★★★ THE MEMO IS LOAD-BEARING, not tidiness. fe-libs' bridge deliberately does\n * NOT reset its run state when the controller identity changes — precisely so\n * that the natural `preview={{ render, onEvent }}` object literal does not\n * re-announce `start` on every poll tick. The cost of that choice is that\n * REPLACING a controller mid-run leaves the newcomer without a `start` for the\n * run already in flight. An empty dependency list is what guarantees one\n * controller for the life of the mount.\n *\n * ★ `label` is the overlay's toggle text. Plain English here, like FluidGrids':\n * the seam's user-facing strings are translated in BC5 along with the rest of\n * the bundle, and a half-translated toggle beside untranslated mode labels is\n * worse than a consistently English one.\n */\nexport function useBigConsoleAssistantPreview(): AssistantPreviewController {\n return useMemo<AssistantPreviewController>(\n () => ({\n label: 'Build preview',\n render: () => <BigConsoleAssistantPreviewSurface />,\n onEvent: applyPreviewEvent,\n }),\n []\n );\n}\n"],"mappings":";;;;;;;;AAwDA,SAAS,IAAoC;CAC3C,IAAM,EAAE,SAAM,GAAS,EACjB,IAAY,GAAsB,MAAU,EAAM,UAAU,EAC5D,IAAQ,GAAsB,MAAU,EAAM,MAAM,EACpD,IAAU,GAAsB,MAAU,EAAM,QAAQ,EACxD,IAAQ,GAAsB,MAAU,EAAM,MAAM,EACpD,IAAY,GAAsB,MAAU,EAAM,UAAU,EAE5D,IAAoC,EAAc,KAAK,OAAS;EACpE,IAAI;EACJ,OAAO,EAAE,EAAW,GAAK,KAAK,EAAW,GAAK,SAAS;EACvD,QAAQ,EAAM;EACf,EAAE;AAEH,QACE,kBAAC,OAAD;EAAK,eAAY;EAAmB,gBAAa;EAAY,WAAU;YACrE,kBAAC,GAAD;GACE,OAAO,EAAE,yCAAyC,aAAa;GAC/D,OAAO;GAMP,SACE,IACI,KAAW,EAAE,2CAA2C,WAAW,GACnE,EAAE,wCAAwC,OAAO;GAEhD;GACP,MACE,kBAAC,GAAD;IACE,OAAO,IAAY,YAAY,IAAQ,YAAY;IACnD,YACE,IACI,EAAE,wCAAwC,OAAO,GACjD,IACE,EAAE,2CAA2C,UAAU,GACvD;IAER,cAAc,EAAE,2CAA2C,UAAU;IAC1D;IACX,MAAM,MAAY,EAAU,GAAS,EAAE;IACvC,CAAA;GAEJ,QAAQ,EAAc,EAAE;GACxB,CAAA;EACE,CAAA;;AAuBV,IAAM,oBAAc,IAAI,KAAa;AASrC,SAAS,EAAkB,GAAoC;CAO7D,IAAM,IAAY,EAAkB,UAAU,EACxC,IAAY,EAAU,cAAc,MAAM,MAAM,EAAE,OAAO,EAAM,IAAI,eAAe,EAAE,QAAQ,EAAU;AAE5G,KAAI,EAAiB,EAAM,MAAM,EAAM,IAAI,OAAO,GAAW,EAAY,KAAK,SAC5E;CAGF,IAAM,IAAQ,EAAqB,UAAU;AAE7C,KAAI,EAAM,SAAS,SAAS;AAW1B,EAVA,EAAY,IAAI,EAAM,IAAI,MAAM,EAUhC,EAAM,SAAS,GAAG;AAClB;;AAGF,KAAI,EAAM,SAAS,YAAY;AAQ7B,IAAM,cAAc,EAAM,QAAQ;AAClC;;AAqBF,CADA,EAAY,OAAO,EAAM,IAAI,MAAM,EACnC,EAAM,UAAU,EAAM,YAAY,UAAW,EAAM,SAAS,uBAAwB,KAAK;;AAmB3F,SAAgB,IAA4D;AAC1E,QAAO,SACE;EACL,OAAO;EACP,cAAc,kBAAC,GAAD,EAAqC,CAAA;EACnD,SAAS;EACV,GACD,EAAE,CACH"}
|
|
1
|
+
{"version":3,"file":"previewController.js","names":[],"sources":["../../../src/bigconsole/assistant/previewController.tsx"],"sourcesContent":["/**\n * BigConsole's preview capability for the shared assistant panel (BC3).\n *\n * ★★★ THIS IS WHAT REPLACES THE TRANSPORT AS THE RUN STORE'S DRIVER.\n *\n * Today `createSandboxAssistantTransport` calls `startRun` / `applyProgress` /\n * `finishRun` itself, because it owns the turn. The shared panel consumes NO\n * transport at all — `AssistantChatArea` calls fe-libs' own `assistant/api`\n * directly — so once BC4 mounts the panel there is nothing left to drive the\n * store, and the live build preview would simply stop moving.\n *\n * fe-libs' preview capability is that missing wire: the bridge announces\n * `start` / `snapshot` / `end` for the run in flight, and this controller\n * forwards them to the same three store actions the transport used to call. The\n * store, both preview panels, and everything downstream are untouched.\n *\n * ★★★ HEADLESS (BOFF-7799). The controller carries `onEvent` and nothing else.\n * It used to also `render` a copy of the preview INSIDE the panel, behind a\n * \"Build preview\" toggle. That copy was offered even with no run in the tab\n * (an empty rail captioned \"Done\"), and opening it made the page preview under\n * the breadcrumb stand down, so users saw the build only as an overlay in the\n * chat. fe-libs 2026.930.3 made `render` optional: with no `render` the panel\n * shows no toggle, never opens the overlay and never publishes\n * `data-assistant-overlay=\"preview\"`, while the bridge still delivers `start`,\n * `snapshot` and `end`. The page surfaces (`AiPreviewPanel`,\n * `AiPreviewMiniPanel`) are now the only surfaces, and they stay beside the\n * chat by reading `DOCK_CLEARANCE`.\n *\n * ★★ ABSENCE IS STILL THE OFF-SWITCH for the events. A product that supplies no\n * controller gets no events; there is deliberately no second `enablePreview`\n * flag. So this hook always returns a controller — gating belongs at the mount\n * site (BC4), not here.\n */\nimport { useMemo } from 'react';\nimport {\n useAssistantStore,\n type AssistantPreviewController,\n type AssistantPreviewEvent,\n} from '@burdenoff/fe-libs/shared/assistant';\n\nimport { runBelongsToWorkspace, useAssistantRunStore } from './assistantRunStore';\nimport { previewRunAction, type PreviewRunAction } from './previewRunAction';\n\n/**\n * ★ Re-exported so consumers keep ONE import path, while the RULE itself lives\n * in a module that loads without a DOM. Importing THIS file pulls fe-libs'\n * assistant barrel, which touches `document` at load time — so the decision had\n * to move out before it could be tested. See `previewRunAction.ts`.\n *\n * ★ Local bindings, not `export … from`: a bare re-export creates no local name,\n * so `applyPreviewEvent` below would be calling something this module does not\n * actually have.\n */\nexport { previewRunAction };\nexport type { PreviewRunAction };\n\n/**\n * The runs this controller has adopted — Build runs only.\n *\n * ★ Module-level, like the handler itself: it must outlive the controller memo\n * and every re-render, and nothing renders it.\n */\nconst adoptedRuns = new Set<string>();\n\n/**\n * Forward one bridge event to the run store.\n *\n * ★ Module-level and imperative (`getState()`), NOT a hook: the bridge calls\n * this from an effect, and reading the store through a selector here would make\n * the controller depend on state it never renders.\n */\nfunction applyPreviewEvent(event: AssistantPreviewEvent): void {\n /**\n * ★ The mode of THIS RUN's conversation, never the panel's. The panel's `mode`\n * is whatever the user last selected, which need not be the mode the run in\n * flight was started in. Falls back to the panel mode only when the\n * conversation is gone.\n */\n const assistant = useAssistantStore.getState();\n const modeOfRun = assistant.conversations.find((c) => c.id === event.run.conversationId)?.mode ?? assistant.mode;\n\n if (previewRunAction(event.kind, event.run.runId, modeOfRun, adoptedRuns) === 'ignore') {\n return;\n }\n\n const store = useAssistantRunStore.getState();\n /**\n * ★★ A run drives only the workspace it started in. A workspace switch clears\n * the store (`bindWorkspace`), but the previous workspace's turn keeps\n * streaming; its snapshots must not refill the cleared store, or the refill\n * is kept under the new workspace's key.\n */\n const belongs = runBelongsToWorkspace(event.run.workspaceId, store.workspaceId);\n\n if (event.kind === 'start') {\n if (!belongs) return;\n adoptedRuns.add(event.run.runId);\n /**\n * ★★ THE PROMPT IS NOT AVAILABLE, and that is a real gap rather than an\n * oversight. `AssistantPreviewRun` carries `runId`, `conversationId`,\n * `messageId`, `workspaceId` and `actorId` — no turn text — so the store's\n * `prompt` (which only feeds an informational line) is left empty. The\n * transport could pass it because it owned the turn; the bridge cannot,\n * because it observes one. Everything the preview actually draws comes from\n * `applyProgress`, so nothing visible depends on this.\n */\n store.startRun('');\n return;\n }\n\n if (event.kind === 'snapshot') {\n if (!belongs) return;\n /**\n * ★ `content` is the PROSE with the tool-activity lines already removed by\n * fe-libs. That is what this store wants: it harvests entity links, raw\n * mutation ids and the newest `⏳ …` caption out of the agent's narration,\n * all of which live in the prose. The structured `event.steps` describe TOOL\n * calls, which are a different rail from BigConsole's four build steps.\n */\n store.applyProgress(event.content);\n return;\n }\n\n /**\n * ★★ `abandoned` is neither success nor failure — it is a run the bridge can\n * no longer follow (cancelled, superseded, or navigated away from mid-turn),\n * and the turn may well still be executing. Passing its text as an error would\n * paint a red \"Build stopped\" banner over a build that is very likely still\n * running; passing nothing at all would leave the spinner going forever. So it\n * ends the run WITHOUT an error, which stops the spinner and leaves every step\n * already marked `done` exactly as it was.\n */\n /**\n * ★ Released here, and only here. Run ids are `conversationId:messageId`, so\n * they are never reused and this is a leak rather than a correctness bug — but\n * an unbounded set that grows for the life of the tab is still a leak, and\n * `end` is the one event guaranteed to arrive for every adopted run\n * (`abandoned` included — that outcome exists precisely so a run the bridge\n * can no longer follow still terminates).\n */\n adoptedRuns.delete(event.run.runId);\n if (!belongs) return;\n store.finishRun(event.outcome === 'error' ? (event.error ?? 'The build stopped.') : null);\n}\n\n/**\n * BigConsole's preview controller, stable across renders.\n *\n * ★★★ THE MEMO IS LOAD-BEARING, not tidiness. fe-libs' bridge deliberately does\n * NOT reset its run state when the controller identity changes — precisely so\n * that the natural `preview={{ render, onEvent }}` object literal does not\n * re-announce `start` on every poll tick. The cost of that choice is that\n * REPLACING a controller mid-run leaves the newcomer without a `start` for the\n * run already in flight. An empty dependency list is what guarantees one\n * controller for the life of the mount.\n *\n * ★★ NO `render` AND NO `label`, and never add them back conditionally.\n * fe-libs asks a product to decide once whether its controller is headless:\n * adding `render` later swaps the panel's hook layout mid-mount. A `label`\n * would be dead weight, because a headless controller has no toggle to name.\n */\nexport function useBigConsoleAssistantPreview(): AssistantPreviewController {\n return useMemo<AssistantPreviewController>(() => ({ onEvent: applyPreviewEvent }), []);\n}\n"],"mappings":";;;;;AA8DA,IAAM,oBAAc,IAAI,KAAa;AASrC,SAAS,EAAkB,GAAoC;CAO7D,IAAM,IAAY,EAAkB,UAAU,EACxC,IAAY,EAAU,cAAc,MAAM,MAAM,EAAE,OAAO,EAAM,IAAI,eAAe,EAAE,QAAQ,EAAU;AAE5G,KAAI,EAAiB,EAAM,MAAM,EAAM,IAAI,OAAO,GAAW,EAAY,KAAK,SAC5E;CAGF,IAAM,IAAQ,EAAqB,UAAU,EAOvC,IAAU,EAAsB,EAAM,IAAI,aAAa,EAAM,YAAY;AAE/E,KAAI,EAAM,SAAS,SAAS;AAC1B,MAAI,CAAC,EAAS;AAWd,EAVA,EAAY,IAAI,EAAM,IAAI,MAAM,EAUhC,EAAM,SAAS,GAAG;AAClB;;AAGF,KAAI,EAAM,SAAS,YAAY;AAC7B,MAAI,CAAC,EAAS;AAQd,IAAM,cAAc,EAAM,QAAQ;AAClC;;AAoBF,GAAY,OAAO,EAAM,IAAI,MAAM,EAC9B,KACL,EAAM,UAAU,EAAM,YAAY,UAAW,EAAM,SAAS,uBAAwB,KAAK;;AAmB3F,SAAgB,IAA4D;AAC1E,QAAO,SAA2C,EAAE,SAAS,GAAmB,GAAG,EAAE,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useAssistantRunStore as e } from "./assistantRunStore.js";
|
|
2
|
+
import { useEffect as t } from "react";
|
|
3
|
+
import { useActiveContext as n } from "@burdenoff/fe-libs/shared/providers/shell/ActiveContextProvider";
|
|
4
|
+
//#region src/bigconsole/assistant/useAssistantRunWorkspace.ts
|
|
5
|
+
function r() {
|
|
6
|
+
let { workspaceId: r } = n(), i = e((e) => e.bindWorkspace);
|
|
7
|
+
t(() => {
|
|
8
|
+
i(r || null);
|
|
9
|
+
}, [i, r]);
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { r as useAssistantRunWorkspace };
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=useAssistantRunWorkspace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAssistantRunWorkspace.js","names":[],"sources":["../../../src/bigconsole/assistant/useAssistantRunWorkspace.ts"],"sourcesContent":["import { useEffect } from 'react';\nimport { useActiveContext } from '@burdenoff/fe-libs/shared/providers/shell/ActiveContextProvider';\n\nimport { useAssistantRunStore } from './assistantRunStore';\n\n/**\n * Binds the build-preview run store to the ACTIVE workspace (BOFF-7799).\n *\n * ★★ Called by both page surfaces, which is what makes a reload in the same tab\n * restore the preview under the breadcrumb and a workspace switch clear it.\n * Binding is idempotent, so the rich panel and the app-shell mini panel may\n * both call it on the same page.\n *\n * ★ From the REACTIVE context, as `BigConsoleAssistant` reads it, never from a\n * helper that reads storage once: the shell does not remount on a workspace\n * switch, so a one-shot read would keep the previous workspace's build on\n * screen.\n *\n * ★ `|| null`: an empty string is \"no workspace\", not a workspace called \"\".\n */\nexport function useAssistantRunWorkspace(): void {\n const { workspaceId } = useActiveContext();\n const bindWorkspace = useAssistantRunStore((state) => state.bindWorkspace);\n\n useEffect(() => {\n bindWorkspace(workspaceId || null);\n }, [bindWorkspace, workspaceId]);\n}\n"],"mappings":";;;;AAoBA,SAAgB,IAAiC;CAC/C,IAAM,EAAE,mBAAgB,GAAkB,EACpC,IAAgB,GAAsB,MAAU,EAAM,cAAc;AAE1E,SAAgB;AACd,IAAc,KAAe,KAAK;IACjC,CAAC,GAAe,EAAY,CAAC"}
|