pilotswarm 0.5.37 → 0.5.39
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/mcp/dist/src/tools/agent-packages.d.ts.map +1 -1
- package/mcp/dist/src/tools/agent-packages.js +34 -14
- package/mcp/dist/src/tools/agent-packages.js.map +1 -1
- package/package.json +3 -3
- package/tui/src/node-sdk-transport.js +113 -18
- package/ui/core/src/controller.js +196 -49
- package/ui/core/src/layout.js +10 -0
- package/ui/core/src/reducer.js +265 -20
- package/ui/core/src/selectors.js +132 -10
- package/ui/core/src/state.js +90 -2
- package/ui/core/src/themes/index.js +1 -1
- package/ui/react/src/web-app.js +1097 -220
- package/web/auth/config.js +3 -0
- package/web/auth/index.js +8 -1
- package/web/auth/normalize/proxy.js +48 -0
- package/web/auth/providers/proxy.js +141 -0
- package/web/dist/assets/{abnfDiagram-VRR7QNED-DlvPH-8O.js → abnfDiagram-VRR7QNED-Hsly1fgf.js} +1 -1
- package/web/dist/assets/{arc-r1liz5e5.js → arc-DiQLL9vM.js} +1 -1
- package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-Bci5dNH3.js → architectureDiagram-ZJ3FMSHR-B8ry8WNZ.js} +1 -1
- package/web/dist/assets/{blockDiagram-677ZJIJ3-Bug32AXp.js → blockDiagram-677ZJIJ3-Z4PcukIo.js} +1 -1
- package/web/dist/assets/{c4Diagram-LMCZKHZV-DwxSFqgE.js → c4Diagram-LMCZKHZV-Cahkc0gc.js} +1 -1
- package/web/dist/assets/channel-BHLBPE50.js +1 -0
- package/web/dist/assets/{chunk-2Q5K7J3B-B_dbI_xg.js → chunk-2Q5K7J3B-CtZuWH4J.js} +1 -1
- package/web/dist/assets/{chunk-32BRIVSS-BkF68W-R.js → chunk-32BRIVSS-C0M390-k.js} +1 -1
- package/web/dist/assets/{chunk-5VM5RSS4-BSbNIbJu.js → chunk-5VM5RSS4-ClvX9u5j.js} +1 -1
- package/web/dist/assets/{chunk-EX3LRPZG-Blv_Iqa2.js → chunk-EX3LRPZG-BVAet5KG.js} +1 -1
- package/web/dist/assets/{chunk-JWPE2WC7-Dk_dBFKd.js → chunk-JWPE2WC7-BccmZmrr.js} +1 -1
- package/web/dist/assets/{chunk-MOJQB5TN-DqNFBrfw.js → chunk-MOJQB5TN-BN5MKe94.js} +1 -1
- package/web/dist/assets/{chunk-RYQCIY6F-Dlv7Svpb.js → chunk-RYQCIY6F-DfWw-ACP.js} +1 -1
- package/web/dist/assets/{chunk-V7JOEXUC-DozGikwu.js → chunk-V7JOEXUC-BgUEoLnN.js} +1 -1
- package/web/dist/assets/{chunk-VR4S4FIN-CmkqvGT4.js → chunk-VR4S4FIN-k1eKfFNA.js} +1 -1
- package/web/dist/assets/{chunk-XXDRQBXY-DyByai5N.js → chunk-XXDRQBXY-DPyV10yR.js} +1 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-rj9D2TSL.js +1 -0
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-rj9D2TSL.js +1 -0
- package/web/dist/assets/{cose-bilkent-JH36ORCC-Bv-gRVvA.js → cose-bilkent-JH36ORCC-Bt_rpukK.js} +1 -1
- package/web/dist/assets/{cynefin-VYW2F7L2-DRnAsyZU.js → cynefin-VYW2F7L2-Z8eMzFMv.js} +1 -1
- package/web/dist/assets/{cynefinDiagram-TSTJHNR4-B1fnUtjo.js → cynefinDiagram-TSTJHNR4-BJSNawFi.js} +1 -1
- package/web/dist/assets/{dagre-VKFMJZFB-DM6-QENB.js → dagre-VKFMJZFB-D6dNl8jc.js} +1 -1
- package/web/dist/assets/{diagram-FQU43EPY-BPxxnLdU.js → diagram-FQU43EPY-Cy1nbGsU.js} +1 -1
- package/web/dist/assets/{diagram-G47NLZAW-BgtJT3qF.js → diagram-G47NLZAW-CmrbuA_d.js} +1 -1
- package/web/dist/assets/{diagram-NH7WQ7WH-uekeIzsy.js → diagram-NH7WQ7WH-PoLNQE2j.js} +1 -1
- package/web/dist/assets/{diagram-OA4YK3LP-BVy3JZWE.js → diagram-OA4YK3LP-BZwNFcSV.js} +1 -1
- package/web/dist/assets/{diagram-WEI45ONY-BWdjUr32.js → diagram-WEI45ONY-pKw0b_fN.js} +1 -1
- package/web/dist/assets/{ebnfDiagram-CCIWWBDH-DD4fwhNK.js → ebnfDiagram-CCIWWBDH-UNctuQCV.js} +1 -1
- package/web/dist/assets/{erDiagram-Q63AITRT-BllD_6Kh.js → erDiagram-Q63AITRT-DxIETMif.js} +1 -1
- package/web/dist/assets/{flowDiagram-23GEKE2U-QEYKkAUN.js → flowDiagram-23GEKE2U-C06IdNKV.js} +1 -1
- package/web/dist/assets/{ganttDiagram-NO4QXBWP-uZcfKTSW.js → ganttDiagram-NO4QXBWP-fU7aGyP1.js} +1 -1
- package/web/dist/assets/{gitGraphDiagram-IHSO6WYX-XPdbPaH-.js → gitGraphDiagram-IHSO6WYX-9UKkPJm6.js} +1 -1
- package/web/dist/assets/index-B8nGlHwb.js +24 -0
- package/web/dist/assets/index-DtL0lbis.css +1 -0
- package/web/dist/assets/{infoDiagram-FWYZ7A6U-DXCnQOJs.js → infoDiagram-FWYZ7A6U-C6F06SQJ.js} +1 -1
- package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-687_4FBy.js → ishikawaDiagram-FXEZZL3T-CB-O9NfO.js} +1 -1
- package/web/dist/assets/{journeyDiagram-5HDEW3XC-GXlKuda8.js → journeyDiagram-5HDEW3XC-B-8MOf-H.js} +1 -1
- package/web/dist/assets/{kanban-definition-HUTT4EX6-C38-2DvN.js → kanban-definition-HUTT4EX6-DX0XA3hI.js} +1 -1
- package/web/dist/assets/{linear-lGPXiBcz.js → linear-D60PrjTW.js} +1 -1
- package/web/dist/assets/{mermaid.core-C_neEtab.js → mermaid.core-bonzwapf.js} +4 -4
- package/web/dist/assets/{mindmap-definition-LN4V7U3C-BaJ9tpEG.js → mindmap-definition-LN4V7U3C-Y-6Dum33.js} +1 -1
- package/web/dist/assets/{papaparse.min-rDwKYbBf.js → papaparse.min-Cas1JfRG.js} +1 -1
- package/web/dist/assets/{pegDiagram-2B236MQR-BdXJLYgY.js → pegDiagram-2B236MQR-_paO0yDw.js} +1 -1
- package/web/dist/assets/{pieDiagram-ENE6RG2P-DU19t6fM.js → pieDiagram-ENE6RG2P-xT1_Gpn4.js} +1 -1
- package/web/dist/assets/pilotswarm-BT6tOknQ.js +96 -0
- package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-CBkOdPp0.js → quadrantDiagram-ABIIQ3AL-1usdLFOF.js} +1 -1
- package/web/dist/assets/{railroadDiagram-RFXS5EU6-D2DMcGDw.js → railroadDiagram-RFXS5EU6-R3lwHOz9.js} +1 -1
- package/web/dist/assets/{requirementDiagram-TGXJPOKE-Bx9tF1pr.js → requirementDiagram-TGXJPOKE-BL8J1__Z.js} +1 -1
- package/web/dist/assets/{sankeyDiagram-HTMAVEWB-DcLlRfsi.js → sankeyDiagram-HTMAVEWB-Dz6--0pl.js} +1 -1
- package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-xr6UVRBO.js → sequenceDiagram-DBY2YBRQ-CG2Xpkct.js} +1 -1
- package/web/dist/assets/{sizeCapture-X5ZJPWSS-0fUkSXmg.js → sizeCapture-X5ZJPWSS-BPbYfjxj.js} +1 -1
- package/web/dist/assets/{stateDiagram-2N3HPSRC-BWgJGGpg.js → stateDiagram-2N3HPSRC-46x_y_aA.js} +1 -1
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-C2tnolSo.js +1 -0
- package/web/dist/assets/{swimlanes-5IMT3BWC-CZKya4_i.js → swimlanes-5IMT3BWC-SXdMo4zJ.js} +2 -2
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-Dh6Sn__t.js +8 -0
- package/web/dist/assets/{timeline-definition-FHXFAJF6-Df8BpeAu.js → timeline-definition-FHXFAJF6-CGgT-b38.js} +1 -1
- package/web/dist/assets/{vennDiagram-L72KCM5P-ChX5bjT6.js → vennDiagram-L72KCM5P-BGS3xBa_.js} +1 -1
- package/web/dist/assets/{wardleyDiagram-EHGQE667-Dr48BgaJ.js → wardleyDiagram-EHGQE667-m2U3wEZJ.js} +1 -1
- package/web/dist/assets/{xychartDiagram-FW5EYKEG-Cia_MFfc.js → xychartDiagram-FW5EYKEG-DI6tQysg.js} +1 -1
- package/web/dist/index.html +3 -3
- package/web/runtime.js +30 -7
- package/web/dist/assets/channel-jVpSYat3.js +0 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-C78zSajE.js +0 -1
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-C78zSajE.js +0 -1
- package/web/dist/assets/index-BuDSoRIL.css +0 -1
- package/web/dist/assets/index-D1itTxvB.js +0 -24
- package/web/dist/assets/pilotswarm-BNKGG936.js +0 -96
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-DmAC8DdB.js +0 -1
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-CVYx20_X.js +0 -8
package/ui/core/src/reducer.js
CHANGED
|
@@ -9,13 +9,30 @@ import {
|
|
|
9
9
|
normalizeSessionOwnerFilter,
|
|
10
10
|
normalizeStoredActiveSessionId,
|
|
11
11
|
normalizeStoredCanvasPrefs,
|
|
12
|
+
canvasKey,
|
|
13
|
+
parseCanvasKey,
|
|
12
14
|
normalizeStoredCollapsedSessionIds,
|
|
15
|
+
normalizeStoredDesktopPanes,
|
|
13
16
|
normalizeStoredLayoutAdjustments,
|
|
14
17
|
normalizeStoredPinnedSessionIds,
|
|
15
18
|
normalizeStoredSessionOrder,
|
|
16
19
|
createInitialState,
|
|
17
20
|
} from "./state.js";
|
|
18
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Is this package detail/tree/changelog response for a selection the user has
|
|
24
|
+
* already left? Responses carry the selectionSeq captured when their fetch
|
|
25
|
+
* started; the NAME alone cannot tell two copies of a shadowed name apart
|
|
26
|
+
* (scope shadowing: one name, two packages), so a name-only guard let a slow
|
|
27
|
+
* response for one copy land on top of the other. Name-only actions (older
|
|
28
|
+
* hosts) keep the historical name compare.
|
|
29
|
+
*/
|
|
30
|
+
function packageResponseIsStale(packages, action) {
|
|
31
|
+
if (packages.selectedName !== action.name) return true;
|
|
32
|
+
if (typeof action.seq === "number") return (packages.selectionSeq ?? 0) !== action.seq;
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
19
36
|
function cloneHistoryMap(historyMap) {
|
|
20
37
|
return new Map(historyMap);
|
|
21
38
|
}
|
|
@@ -112,10 +129,15 @@ function mergeCanvasPrefs(local, stored, byId) {
|
|
|
112
129
|
optedOut: storedEntry ? storedEntry.optedOut : Boolean(localEntry?.optedOut),
|
|
113
130
|
lastViewedRev: Math.max(storedEntry?.lastViewedRev || 0, localEntry?.lastViewedRev || 0),
|
|
114
131
|
lastViewedDataRev: Math.max(storedEntry?.lastViewedDataRev || 0, localEntry?.lastViewedDataRev || 0),
|
|
132
|
+
// Latest write wins is unknowable here; the local value is the one
|
|
133
|
+
// this window just dragged, so it outranks the poll.
|
|
134
|
+
...((localEntry?.zenRailPx || storedEntry?.zenRailPx)
|
|
135
|
+
? { zenRailPx: localEntry?.zenRailPx || storedEntry?.zenRailPx }
|
|
136
|
+
: {}),
|
|
115
137
|
};
|
|
116
|
-
if (!merged.optedOut && merged.lastViewedRev === 0 && merged.lastViewedDataRev === 0) continue;
|
|
117
|
-
const session = byId?.[sessionId];
|
|
118
|
-
if (session &&
|
|
138
|
+
if (!merged.optedOut && merged.lastViewedRev === 0 && merged.lastViewedDataRev === 0 && !merged.zenRailPx) continue;
|
|
139
|
+
const session = byId?.[parseCanvasKey(sessionId).sessionId];
|
|
140
|
+
if (session && session.isGroup) continue;
|
|
119
141
|
out[sessionId] = merged;
|
|
120
142
|
}
|
|
121
143
|
return out;
|
|
@@ -611,6 +633,7 @@ function applyVisibleSessionSelection(state, nextSessions) {
|
|
|
611
633
|
sessions = {
|
|
612
634
|
...sessions,
|
|
613
635
|
activeSessionId: intentTargetId,
|
|
636
|
+
listDeselected: false,
|
|
614
637
|
navigationIntent: intent.status === "resolved"
|
|
615
638
|
? intent
|
|
616
639
|
: { sessionId: intent.sessionId, status: "resolved" },
|
|
@@ -808,6 +831,17 @@ export function appReducer(state, action) {
|
|
|
808
831
|
&& typeof settings.touchScale === "boolean";
|
|
809
832
|
const hasRightPaneMode = Object.prototype.hasOwnProperty.call(settings, "rightPaneMode")
|
|
810
833
|
&& (settings.rightPaneMode === "canvas" || settings.rightPaneMode === "panes");
|
|
834
|
+
// The two independent columns. A profile written by a build that
|
|
835
|
+
// predates them carries only rightPaneMode, so fall back to
|
|
836
|
+
// migrating that — see normalizeStoredDesktopPanes.
|
|
837
|
+
const hasDesktopPanes = Object.prototype.hasOwnProperty.call(settings, "desktopPanes")
|
|
838
|
+
&& settings.desktopPanes && typeof settings.desktopPanes === "object";
|
|
839
|
+
const nextDesktopPanes = (hasDesktopPanes || hasRightPaneMode)
|
|
840
|
+
? normalizeStoredDesktopPanes(
|
|
841
|
+
hasDesktopPanes ? settings.desktopPanes : null,
|
|
842
|
+
hasRightPaneMode ? settings.rightPaneMode : null,
|
|
843
|
+
)
|
|
844
|
+
: null;
|
|
811
845
|
const hasCanvasPrefs = Object.prototype.hasOwnProperty.call(settings, "canvasPrefs");
|
|
812
846
|
const hasPins = Object.prototype.hasOwnProperty.call(settings, "pinnedSessionIds");
|
|
813
847
|
const hasCollapsed = Object.prototype.hasOwnProperty.call(settings, "collapsedSessionIds");
|
|
@@ -872,6 +906,9 @@ export function appReducer(state, action) {
|
|
|
872
906
|
themeId: hasTheme ? settings.themeId.trim() : selection.ui.themeId,
|
|
873
907
|
touchScale: hasTouchScale ? settings.touchScale : selection.ui.touchScale,
|
|
874
908
|
rightPaneMode: hasRightPaneMode ? settings.rightPaneMode : selection.ui.rightPaneMode,
|
|
909
|
+
...(nextDesktopPanes
|
|
910
|
+
? { canvasOpen: nextDesktopPanes.canvasOpen, diagnosticsOpen: nextDesktopPanes.diagnosticsOpen, canvasZen: nextDesktopPanes.zen === true }
|
|
911
|
+
: {}),
|
|
875
912
|
layout: nextLayout,
|
|
876
913
|
},
|
|
877
914
|
canvas: hasCanvasPrefs
|
|
@@ -946,6 +983,46 @@ export function appReducer(state, action) {
|
|
|
946
983
|
},
|
|
947
984
|
};
|
|
948
985
|
|
|
986
|
+
// Where the canvas/diagnostics seam sits when both columns are open.
|
|
987
|
+
// A pixel delta from an even split of the right side, like the other
|
|
988
|
+
// adjusts. Closing a column is a separate action — see
|
|
989
|
+
// CanvasDiagnosticsResizeHandle, which decides that on release.
|
|
990
|
+
case "ui/canvasPaneAdjust":
|
|
991
|
+
return {
|
|
992
|
+
...state,
|
|
993
|
+
ui: {
|
|
994
|
+
...state.ui,
|
|
995
|
+
layout: {
|
|
996
|
+
...(state.ui.layout || {}),
|
|
997
|
+
canvasPaneAdjust: Number(action.canvasPaneAdjust) || 0,
|
|
998
|
+
},
|
|
999
|
+
},
|
|
1000
|
+
};
|
|
1001
|
+
|
|
1002
|
+
case "ui/diagnosticsSplitAdjust":
|
|
1003
|
+
return {
|
|
1004
|
+
...state,
|
|
1005
|
+
ui: {
|
|
1006
|
+
...state.ui,
|
|
1007
|
+
layout: {
|
|
1008
|
+
...(state.ui.layout || {}),
|
|
1009
|
+
diagnosticsSplitAdjust: Number(action.diagnosticsSplitAdjust) || 0,
|
|
1010
|
+
},
|
|
1011
|
+
},
|
|
1012
|
+
};
|
|
1013
|
+
|
|
1014
|
+
case "ui/diagnosticsPaneAdjust":
|
|
1015
|
+
return {
|
|
1016
|
+
...state,
|
|
1017
|
+
ui: {
|
|
1018
|
+
...state.ui,
|
|
1019
|
+
layout: {
|
|
1020
|
+
...(state.ui.layout || {}),
|
|
1021
|
+
diagnosticsPaneAdjust: Number(action.diagnosticsPaneAdjust) || 0,
|
|
1022
|
+
},
|
|
1023
|
+
},
|
|
1024
|
+
};
|
|
1025
|
+
|
|
949
1026
|
case "ui/activityPaneAdjust":
|
|
950
1027
|
return {
|
|
951
1028
|
...state,
|
|
@@ -964,6 +1041,15 @@ export function appReducer(state, action) {
|
|
|
964
1041
|
ui: {
|
|
965
1042
|
...state.ui,
|
|
966
1043
|
focusRegion: action.focusRegion,
|
|
1044
|
+
// The RAW region the caller asked for, before layout
|
|
1045
|
+
// normalization. A workspace region (sessions/chat) with no
|
|
1046
|
+
// slot in the current layout normalizes to the first
|
|
1047
|
+
// focusable pane — inspector, on a phone — so the normalized
|
|
1048
|
+
// value alone cannot tell "focus the inspector" from
|
|
1049
|
+
// "sessions got rewritten to inspector". The mobile
|
|
1050
|
+
// follow-focus pane switch reads this to avoid opening
|
|
1051
|
+
// diagnostics when the user only asked for the workspace.
|
|
1052
|
+
requestedFocusRegion: action.requestedFocusRegion ?? action.focusRegion,
|
|
967
1053
|
},
|
|
968
1054
|
};
|
|
969
1055
|
|
|
@@ -1025,6 +1111,10 @@ export function appReducer(state, action) {
|
|
|
1025
1111
|
if (!sessionId) return state;
|
|
1026
1112
|
const nextSessions = {
|
|
1027
1113
|
...state.sessions,
|
|
1114
|
+
// A deep link is explicit navigation. Keep the list highlight
|
|
1115
|
+
// aligned with the linked chat even if empty-space click had
|
|
1116
|
+
// previously deselected the session row.
|
|
1117
|
+
listDeselected: false,
|
|
1028
1118
|
navigationIntent: { sessionId, status: "pending" },
|
|
1029
1119
|
filterExceptionId: null,
|
|
1030
1120
|
};
|
|
@@ -2257,6 +2347,7 @@ export function appReducer(state, action) {
|
|
|
2257
2347
|
workerState: Array.isArray(action.workerState) ? action.workerState : [],
|
|
2258
2348
|
fetchedAt: Date.now(),
|
|
2259
2349
|
selectedName: stillThere ? previous.selectedName : null,
|
|
2350
|
+
selectedSelector: stillThere ? (previous.selectedSelector ?? null) : null,
|
|
2260
2351
|
...(stillThere ? {} : { detail: null, workspace: { ...previous.workspace, tree: null, selectedPath: null, file: null } }),
|
|
2261
2352
|
},
|
|
2262
2353
|
},
|
|
@@ -2274,6 +2365,18 @@ export function appReducer(state, action) {
|
|
|
2274
2365
|
packages: {
|
|
2275
2366
|
...state.admin.packages,
|
|
2276
2367
|
selectedName: action.name || null,
|
|
2368
|
+
// WHICH copy of the name: {scope, owner} from the row
|
|
2369
|
+
// the user clicked. One name can be two packages
|
|
2370
|
+
// (scope shadowing) — without this every read and
|
|
2371
|
+
// action fell back to "own copy first" and could
|
|
2372
|
+
// target the row the user was NOT looking at.
|
|
2373
|
+
selectedSelector: action.selector || null,
|
|
2374
|
+
// Monotonic selection token. The stale-response guards
|
|
2375
|
+
// below compare it, because comparing the NAME alone
|
|
2376
|
+
// cannot tell two copies of a shadowed name apart — a
|
|
2377
|
+
// slow response for the user copy could land on top of
|
|
2378
|
+
// the shared copy the user just clicked.
|
|
2379
|
+
selectionSeq: (state.admin.packages.selectionSeq ?? 0) + 1,
|
|
2277
2380
|
detail: null,
|
|
2278
2381
|
detailLoading: Boolean(action.name),
|
|
2279
2382
|
detailError: null,
|
|
@@ -2292,17 +2395,17 @@ export function appReducer(state, action) {
|
|
|
2292
2395
|
};
|
|
2293
2396
|
}
|
|
2294
2397
|
case "admin/packages/detail/loaded": {
|
|
2295
|
-
if (state.admin.packages
|
|
2398
|
+
if (packageResponseIsStale(state.admin.packages, action)) return state;
|
|
2296
2399
|
return { ...state, admin: { ...state.admin, packages: { ...state.admin.packages, detail: action.detail, detailLoading: false, detailError: null } } };
|
|
2297
2400
|
}
|
|
2298
2401
|
case "admin/packages/detail/loadFailed": {
|
|
2299
|
-
if (state.admin.packages
|
|
2402
|
+
if (packageResponseIsStale(state.admin.packages, action)) return state;
|
|
2300
2403
|
return { ...state, admin: { ...state.admin, packages: { ...state.admin.packages, detail: null, detailLoading: false, detailError: action.error || "Failed to load package" } } };
|
|
2301
2404
|
}
|
|
2302
2405
|
case "admin/packages/changelog/loaded": {
|
|
2303
2406
|
// Stale-response guard, same as the tree: a slow changelog for a
|
|
2304
2407
|
// previously-selected package must not surface under the current one.
|
|
2305
|
-
if (state.admin.packages
|
|
2408
|
+
if (packageResponseIsStale(state.admin.packages, action)) return state;
|
|
2306
2409
|
return {
|
|
2307
2410
|
...state,
|
|
2308
2411
|
admin: {
|
|
@@ -2315,7 +2418,7 @@ export function appReducer(state, action) {
|
|
|
2315
2418
|
};
|
|
2316
2419
|
}
|
|
2317
2420
|
case "admin/packages/tree/loaded": {
|
|
2318
|
-
if (state.admin.packages
|
|
2421
|
+
if (packageResponseIsStale(state.admin.packages, action)) return state;
|
|
2319
2422
|
const topDirs = Array.isArray(action.tree?.dirs)
|
|
2320
2423
|
? action.tree.dirs.filter((dir) => !dir.includes("/"))
|
|
2321
2424
|
: [];
|
|
@@ -2338,7 +2441,7 @@ export function appReducer(state, action) {
|
|
|
2338
2441
|
};
|
|
2339
2442
|
}
|
|
2340
2443
|
case "admin/packages/tree/loadFailed": {
|
|
2341
|
-
if (state.admin.packages
|
|
2444
|
+
if (packageResponseIsStale(state.admin.packages, action)) return state;
|
|
2342
2445
|
return { ...state, admin: { ...state.admin, packages: { ...state.admin.packages, workspace: { ...state.admin.packages.workspace, treeLoading: false, treeError: action.error || "Failed to load package files" } } } };
|
|
2343
2446
|
}
|
|
2344
2447
|
case "admin/packages/toggleDir": {
|
|
@@ -2352,11 +2455,17 @@ export function appReducer(state, action) {
|
|
|
2352
2455
|
return { ...state, admin: { ...state.admin, packages: { ...state.admin.packages, workspace: { ...state.admin.packages.workspace, selectedPath: action.path, file: null, fileLoading: true, fileError: null } } } };
|
|
2353
2456
|
}
|
|
2354
2457
|
case "admin/packages/file/loaded": {
|
|
2458
|
+
// Path AND selection token: two copies of a shadowed name have the
|
|
2459
|
+
// SAME file paths (both ship plugin.json), so a slow preview for
|
|
2460
|
+
// the previously-selected copy would otherwise land under the
|
|
2461
|
+
// current one. The seq check tells the two copies apart.
|
|
2355
2462
|
if (state.admin.packages.workspace.selectedPath !== action.file?.path) return state;
|
|
2463
|
+
if (typeof action.seq === "number" && (state.admin.packages.selectionSeq ?? 0) !== action.seq) return state;
|
|
2356
2464
|
return { ...state, admin: { ...state.admin, packages: { ...state.admin.packages, workspace: { ...state.admin.packages.workspace, file: action.file, fileLoading: false, fileError: null } } } };
|
|
2357
2465
|
}
|
|
2358
2466
|
case "admin/packages/file/loadFailed": {
|
|
2359
2467
|
if (state.admin.packages.workspace.selectedPath !== action.path) return state;
|
|
2468
|
+
if (typeof action.seq === "number" && (state.admin.packages.selectionSeq ?? 0) !== action.seq) return state;
|
|
2360
2469
|
return { ...state, admin: { ...state.admin, packages: { ...state.admin.packages, workspace: { ...state.admin.packages.workspace, fileLoading: false, fileError: action.error || "Failed to load file" } } } };
|
|
2361
2470
|
}
|
|
2362
2471
|
case "admin/packages/action/pending": {
|
|
@@ -2638,24 +2747,114 @@ export function appReducer(state, action) {
|
|
|
2638
2747
|
}
|
|
2639
2748
|
return {
|
|
2640
2749
|
...state,
|
|
2641
|
-
ui: { ...state.ui, rightPaneMode: mode },
|
|
2750
|
+
ui: { ...state.ui, rightPaneMode: mode, canvasOpen: mode === "canvas" },
|
|
2751
|
+
canvas: { ...state.canvas, prefs },
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
// Canvas as a column of its own. Same opt-out bookkeeping as the old
|
|
2756
|
+
// mode switch above — closing by hand still says "do not flip me back",
|
|
2757
|
+
// opening by hand still withdraws that — but closing the canvas no
|
|
2758
|
+
// longer implies showing anything else, because Diagnostics is now a
|
|
2759
|
+
// separate column with its own toggle.
|
|
2760
|
+
//
|
|
2761
|
+
// `open` may be omitted to mean "toggle".
|
|
2762
|
+
case "ui/canvasOpen": {
|
|
2763
|
+
const open = typeof action.open === "boolean" ? action.open : !state.ui.canvasOpen;
|
|
2764
|
+
const sessionId = action.sessionId || state.sessions.activeSessionId || null;
|
|
2765
|
+
let prefs = state.canvas.prefs;
|
|
2766
|
+
if (sessionId) {
|
|
2767
|
+
const prev = prefs[sessionId] || { optedOut: false, lastViewedRev: 0 };
|
|
2768
|
+
if (open && prev.optedOut) {
|
|
2769
|
+
prefs = { ...prefs, [sessionId]: { ...prev, optedOut: false } };
|
|
2770
|
+
} else if (!open && action.manual && !prev.optedOut) {
|
|
2771
|
+
prefs = { ...prefs, [sessionId]: { ...prev, optedOut: true } };
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
return {
|
|
2775
|
+
...state,
|
|
2776
|
+
// rightPaneMode is kept in step for anything still reading it.
|
|
2777
|
+
// Closing always drops full screen: otherwise the next open
|
|
2778
|
+
// comes back covering the workspace with no warning.
|
|
2779
|
+
ui: {
|
|
2780
|
+
...state.ui,
|
|
2781
|
+
canvasOpen: open,
|
|
2782
|
+
rightPaneMode: open ? "canvas" : "panes",
|
|
2783
|
+
...(open ? {} : { canvasMaximized: false, canvasZen: false }),
|
|
2784
|
+
},
|
|
2642
2785
|
canvas: { ...state.canvas, prefs },
|
|
2643
2786
|
};
|
|
2644
2787
|
}
|
|
2645
2788
|
|
|
2789
|
+
// Diagnostics — today's Inspector + Activity, travelling as one column.
|
|
2790
|
+
// No canvas bookkeeping: it has nothing to do with the canvas, which is
|
|
2791
|
+
// the whole point of splitting the old enum in two.
|
|
2792
|
+
// Zen: sessions hidden, chat a narrow rail, canvas the workbench.
|
|
2793
|
+
// Turning it on opens the canvas; it means nothing without one.
|
|
2794
|
+
case "ui/canvasZen": {
|
|
2795
|
+
const on = typeof action.on === "boolean" ? action.on : !state.ui.canvasZen;
|
|
2796
|
+
if (on === Boolean(state.ui.canvasZen)) return state;
|
|
2797
|
+
return {
|
|
2798
|
+
...state,
|
|
2799
|
+
ui: { ...state.ui, canvasZen: on, ...(on ? { canvasOpen: true, rightPaneMode: "canvas" } : {}) },
|
|
2800
|
+
};
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
// The zen chat-rail width for one session. Clamped so a wild drag can
|
|
2804
|
+
// never wedge the rail off screen or crush the canvas.
|
|
2805
|
+
case "canvas/zenRail": {
|
|
2806
|
+
const sessionId = String(action.sessionId || "").trim();
|
|
2807
|
+
const px = Number(action.px);
|
|
2808
|
+
if (!sessionId || !Number.isFinite(px)) return state;
|
|
2809
|
+
const prev = state.canvas.prefs[sessionId] || { optedOut: false, lastViewedRev: 0 };
|
|
2810
|
+
const clamped = Math.max(260, Math.min(720, Math.round(px)));
|
|
2811
|
+
return {
|
|
2812
|
+
...state,
|
|
2813
|
+
canvas: { ...state.canvas, prefs: { ...state.canvas.prefs, [sessionId]: { ...prev, zenRailPx: clamped } } },
|
|
2814
|
+
};
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2817
|
+
// Full-screen canvas. The header stays put and the canvas covers the
|
|
2818
|
+
// workspace below it, so there is always a way back.
|
|
2819
|
+
case "ui/canvasMaximized": {
|
|
2820
|
+
const on = typeof action.on === "boolean" ? action.on : !state.ui.canvasMaximized;
|
|
2821
|
+
if (on === Boolean(state.ui.canvasMaximized)) return state;
|
|
2822
|
+
return { ...state, ui: { ...state.ui, canvasMaximized: on } };
|
|
2823
|
+
}
|
|
2824
|
+
|
|
2825
|
+
case "ui/diagnosticsOpen": {
|
|
2826
|
+
const open = typeof action.open === "boolean" ? action.open : !state.ui.diagnosticsOpen;
|
|
2827
|
+
if (open === state.ui.diagnosticsOpen) return state;
|
|
2828
|
+
return { ...state, ui: { ...state.ui, diagnosticsOpen: open } };
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2646
2831
|
// The agent-driven flip. The guards (active session, freshness,
|
|
2647
2832
|
// opt-out) live in the controller; by the time this dispatches the
|
|
2648
2833
|
// flip is decided. flipSeq ticks even when the mode is already
|
|
2649
2834
|
// "canvas" — a phone can be off the canvas tab while the persisted
|
|
2650
2835
|
// mode still says canvas, and the tick is what brings its tab back.
|
|
2651
2836
|
case "canvas/flip": {
|
|
2837
|
+
const slot = Number.isInteger(Number(action.slot)) && Number(action.slot) >= 1 && Number(action.slot) <= 5
|
|
2838
|
+
? Number(action.slot) : 1;
|
|
2652
2839
|
return {
|
|
2653
2840
|
...state,
|
|
2654
|
-
|
|
2841
|
+
// Opens the canvas column ON THE SLOT THAT DREW; leaves
|
|
2842
|
+
// Diagnostics exactly as the user left it. An agent drawing
|
|
2843
|
+
// must not close their panels.
|
|
2844
|
+
ui: { ...state.ui, rightPaneMode: "canvas", canvasOpen: true, canvasSlot: slot },
|
|
2655
2845
|
canvas: { ...state.canvas, flipSeq: (state.canvas.flipSeq || 0) + 1 },
|
|
2656
2846
|
};
|
|
2657
2847
|
}
|
|
2658
2848
|
|
|
2849
|
+
// Which of the active session's canvases the pane shows. In-memory
|
|
2850
|
+
// only: a reload lands on slot 1 (or wherever the next flip points).
|
|
2851
|
+
case "ui/canvasSlot": {
|
|
2852
|
+
const slot = Number.isInteger(Number(action.slot)) && Number(action.slot) >= 1 && Number(action.slot) <= 5
|
|
2853
|
+
? Number(action.slot) : 1;
|
|
2854
|
+
if (slot === (state.ui.canvasSlot || 1)) return state;
|
|
2855
|
+
return { ...state, ui: { ...state.ui, canvasSlot: slot } };
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2659
2858
|
// The surface's receipt: revision `rev` (a draw) and/or `dataRev` (a
|
|
2660
2859
|
// tick) actually rendered on screen. Both feed the unseen badge.
|
|
2661
2860
|
case "canvas/viewed": {
|
|
@@ -2663,7 +2862,8 @@ export function appReducer(state, action) {
|
|
|
2663
2862
|
if (!sessionId) return state;
|
|
2664
2863
|
const rev = Number(action.rev);
|
|
2665
2864
|
const dataRev = Number(action.dataRev);
|
|
2666
|
-
const
|
|
2865
|
+
const key = canvasKey(sessionId, action.slot);
|
|
2866
|
+
const prev = state.canvas.prefs[key] || { optedOut: false, lastViewedRev: 0, lastViewedDataRev: 0 };
|
|
2667
2867
|
const nextRev = Number.isFinite(rev) && rev > (prev.lastViewedRev || 0) ? rev : (prev.lastViewedRev || 0);
|
|
2668
2868
|
const nextDataRev = Number.isFinite(dataRev) && dataRev > (prev.lastViewedDataRev || 0) ? dataRev : (prev.lastViewedDataRev || 0);
|
|
2669
2869
|
if (nextRev === (prev.lastViewedRev || 0) && nextDataRev === (prev.lastViewedDataRev || 0)) return state;
|
|
@@ -2671,16 +2871,49 @@ export function appReducer(state, action) {
|
|
|
2671
2871
|
...state,
|
|
2672
2872
|
canvas: {
|
|
2673
2873
|
...state.canvas,
|
|
2674
|
-
prefs: { ...state.canvas.prefs, [
|
|
2675
|
-
},
|
|
2676
|
-
};
|
|
2874
|
+
prefs: { ...state.canvas.prefs, [key]: { ...prev, lastViewedRev: nextRev, lastViewedDataRev: nextDataRev } },
|
|
2875
|
+
},
|
|
2876
|
+
};
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2879
|
+
// The sessions-list seed: rev+name per slot from session_canvases,
|
|
2880
|
+
// merged under the live entries. Never regresses a rev a live event
|
|
2881
|
+
// already delivered, never sets snapshotLoaded (the full snapshot —
|
|
2882
|
+
// note, contract, data replay — still loads on selection).
|
|
2883
|
+
case "canvas/seed": {
|
|
2884
|
+
const entries = Array.isArray(action.entries) ? action.entries : [];
|
|
2885
|
+
if (!entries.length) return state;
|
|
2886
|
+
let next = null;
|
|
2887
|
+
for (const e of entries) {
|
|
2888
|
+
const sessionId = String(e?.sessionId || "").trim();
|
|
2889
|
+
const rev = Number(e?.latestRev);
|
|
2890
|
+
const slotN = Number(e?.slot);
|
|
2891
|
+
if (!sessionId || !Number.isFinite(rev) || rev <= 0) continue;
|
|
2892
|
+
if (!Number.isInteger(slotN) || slotN < 1 || slotN > 5) continue;
|
|
2893
|
+
const key = canvasKey(sessionId, slotN);
|
|
2894
|
+
const existing = (next || state.canvas.bySessionId)[key] || {};
|
|
2895
|
+
if ((existing.latestRev || 0) >= rev && existing.name !== undefined) continue;
|
|
2896
|
+
next = next || { ...state.canvas.bySessionId };
|
|
2897
|
+
next[key] = {
|
|
2898
|
+
...existing,
|
|
2899
|
+
slot: slotN,
|
|
2900
|
+
latestRev: Math.max(existing.latestRev || 0, rev),
|
|
2901
|
+
...(typeof e.name === "string" && existing.name === undefined ? { name: e.name } : {}),
|
|
2902
|
+
...(existing.sizeBytes === undefined && e.sizeBytes !== undefined ? { sizeBytes: e.sizeBytes } : {}),
|
|
2903
|
+
};
|
|
2904
|
+
}
|
|
2905
|
+
if (!next) return state;
|
|
2906
|
+
return { ...state, canvas: { ...state.canvas, bySessionId: next } };
|
|
2677
2907
|
}
|
|
2678
2908
|
|
|
2679
2909
|
case "canvas/updated": {
|
|
2680
2910
|
const sessionId = String(action.sessionId || "").trim();
|
|
2681
2911
|
const rev = Number(action.rev);
|
|
2682
2912
|
if (!sessionId || !Number.isFinite(rev) || rev <= 0) return state;
|
|
2683
|
-
const
|
|
2913
|
+
const slot = Number.isInteger(Number(action.slot)) && Number(action.slot) >= 1 && Number(action.slot) <= 5
|
|
2914
|
+
? Number(action.slot) : 1;
|
|
2915
|
+
const key = canvasKey(sessionId, slot);
|
|
2916
|
+
const existing = state.canvas.bySessionId[key] || {};
|
|
2684
2917
|
// Monotonic: replays and out-of-order merges must never regress
|
|
2685
2918
|
// the known revision (freshness gates the FLIP, never the data).
|
|
2686
2919
|
if ((existing.latestRev || 0) >= rev) return state;
|
|
@@ -2690,8 +2923,10 @@ export function appReducer(state, action) {
|
|
|
2690
2923
|
...state.canvas,
|
|
2691
2924
|
bySessionId: {
|
|
2692
2925
|
...state.canvas.bySessionId,
|
|
2693
|
-
[
|
|
2926
|
+
[key]: {
|
|
2694
2927
|
...existing,
|
|
2928
|
+
slot,
|
|
2929
|
+
...(typeof action.name === "string" ? { name: action.name } : {}),
|
|
2695
2930
|
latestRev: rev,
|
|
2696
2931
|
note: typeof action.note === "string" ? action.note : "",
|
|
2697
2932
|
sizeBytes: Number.isFinite(Number(action.sizeBytes)) ? Number(action.sizeBytes) : null,
|
|
@@ -2712,9 +2947,12 @@ export function appReducer(state, action) {
|
|
|
2712
2947
|
// latestDataRev vs lastViewedDataRev. Monotonic like the draw rev.
|
|
2713
2948
|
case "canvas/data": {
|
|
2714
2949
|
const sessionId = String(action.sessionId || "").trim();
|
|
2950
|
+
const slot = Number.isInteger(Number(action.slot)) && Number(action.slot) >= 1 && Number(action.slot) <= 5
|
|
2951
|
+
? Number(action.slot) : 1;
|
|
2952
|
+
const key = canvasKey(sessionId, slot);
|
|
2715
2953
|
const dataRev = Number(action.dataRev);
|
|
2716
2954
|
if (!sessionId || !Number.isFinite(dataRev) || dataRev <= 0) return state;
|
|
2717
|
-
const existing = state.canvas.bySessionId[
|
|
2955
|
+
const existing = state.canvas.bySessionId[key] || {};
|
|
2718
2956
|
if ((existing.latestDataRev || 0) >= dataRev) return state;
|
|
2719
2957
|
return {
|
|
2720
2958
|
...state,
|
|
@@ -2722,7 +2960,7 @@ export function appReducer(state, action) {
|
|
|
2722
2960
|
...state.canvas,
|
|
2723
2961
|
bySessionId: {
|
|
2724
2962
|
...state.canvas.bySessionId,
|
|
2725
|
-
[
|
|
2963
|
+
[key]: {
|
|
2726
2964
|
...existing,
|
|
2727
2965
|
latestDataRev: dataRev,
|
|
2728
2966
|
dataPayload: action.payload && typeof action.payload === "object" ? action.payload : null,
|
|
@@ -2735,7 +2973,10 @@ export function appReducer(state, action) {
|
|
|
2735
2973
|
case "canvas/snapshot": {
|
|
2736
2974
|
const sessionId = String(action.sessionId || "").trim();
|
|
2737
2975
|
if (!sessionId) return state;
|
|
2738
|
-
const
|
|
2976
|
+
const slot = Number.isInteger(Number(action.slot)) && Number(action.slot) >= 1 && Number(action.slot) <= 5
|
|
2977
|
+
? Number(action.slot) : 1;
|
|
2978
|
+
const key = canvasKey(sessionId, slot);
|
|
2979
|
+
const existing = state.canvas.bySessionId[key] || {};
|
|
2739
2980
|
const rev = Number(action.rev) || 0;
|
|
2740
2981
|
return {
|
|
2741
2982
|
...state,
|
|
@@ -2743,8 +2984,10 @@ export function appReducer(state, action) {
|
|
|
2743
2984
|
...state.canvas,
|
|
2744
2985
|
bySessionId: {
|
|
2745
2986
|
...state.canvas.bySessionId,
|
|
2746
|
-
[
|
|
2987
|
+
[key]: {
|
|
2747
2988
|
...existing,
|
|
2989
|
+
slot,
|
|
2990
|
+
...(typeof action.name === "string" && ((existing.latestRev || 0) <= rev) ? { name: action.name } : {}),
|
|
2748
2991
|
// Events may have beaten the snapshot; never regress.
|
|
2749
2992
|
latestRev: Math.max(existing.latestRev || 0, rev),
|
|
2750
2993
|
note: (existing.latestRev || 0) > rev ? existing.note : (typeof action.note === "string" ? action.note : existing.note || ""),
|
|
@@ -2765,6 +3008,8 @@ export function appReducer(state, action) {
|
|
|
2765
3008
|
const sessionId = String(action.sessionId || "").trim();
|
|
2766
3009
|
const existing = state.canvas.bySessionId[sessionId];
|
|
2767
3010
|
if (!sessionId || !existing?.snapshotLoaded) return state;
|
|
3011
|
+
// Slots 2-5 of the same session invalidate with slot 1: the flag
|
|
3012
|
+
// that gates re-take lives on the slot-1 entry.
|
|
2768
3013
|
return {
|
|
2769
3014
|
...state,
|
|
2770
3015
|
canvas: {
|