pilotswarm 0.5.70 → 0.5.72
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/package.json +2 -2
- package/ui/core/src/controller.js +3 -1
- package/ui/core/src/moa.js +2 -2
- package/ui/core/src/reducer.js +14 -2
- package/ui/core/src/selectors.js +3 -3
- package/ui/core/src/session-sort.js +45 -0
- package/ui/core/src/state.js +3 -0
- package/ui/react/src/web-app.js +145 -49
- package/web/dist/assets/{abnfDiagram-VRR7QNED-CidJDJrY.js → abnfDiagram-VRR7QNED-D__RWhK7.js} +1 -1
- package/web/dist/assets/{arc-BDO1TDjs.js → arc-CjtcEiTR.js} +1 -1
- package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-BIcgz3Aa.js → architectureDiagram-ZJ3FMSHR-BDI8FioV.js} +1 -1
- package/web/dist/assets/{blockDiagram-677ZJIJ3-CWtdYLva.js → blockDiagram-677ZJIJ3-D62iIbEl.js} +1 -1
- package/web/dist/assets/{c4Diagram-LMCZKHZV-u_IOLAS7.js → c4Diagram-LMCZKHZV-BlJ596a2.js} +1 -1
- package/web/dist/assets/channel-CF_IY6bX.js +1 -0
- package/web/dist/assets/{chunk-2Q5K7J3B-daWW1UxL.js → chunk-2Q5K7J3B-CnVtHjtN.js} +1 -1
- package/web/dist/assets/{chunk-32BRIVSS-DyxnM56K.js → chunk-32BRIVSS-g8IU-e70.js} +1 -1
- package/web/dist/assets/{chunk-5VM5RSS4-D7taC4Nx.js → chunk-5VM5RSS4-KKxfFv2y.js} +1 -1
- package/web/dist/assets/{chunk-EX3LRPZG-BWo08pDw.js → chunk-EX3LRPZG-C0eEpQtc.js} +1 -1
- package/web/dist/assets/{chunk-JWPE2WC7-AhysI3TD.js → chunk-JWPE2WC7-ZfmDblU2.js} +1 -1
- package/web/dist/assets/{chunk-MOJQB5TN-CDyGPb8L.js → chunk-MOJQB5TN-BOfHzBMe.js} +1 -1
- package/web/dist/assets/{chunk-RYQCIY6F-RhKa7iem.js → chunk-RYQCIY6F-GvY7zGfa.js} +1 -1
- package/web/dist/assets/{chunk-V7JOEXUC-DLgws19l.js → chunk-V7JOEXUC-Bi28CvqE.js} +1 -1
- package/web/dist/assets/{chunk-VR4S4FIN-CyLmmpZk.js → chunk-VR4S4FIN-H1DY3Xw7.js} +1 -1
- package/web/dist/assets/{chunk-XXDRQBXY-DYN0KnEU.js → chunk-XXDRQBXY-JMwCi2he.js} +1 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-BoTycRRp.js +1 -0
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-BoTycRRp.js +1 -0
- package/web/dist/assets/{cose-bilkent-JH36ORCC-62MpINYU.js → cose-bilkent-JH36ORCC-DfFOvDA3.js} +1 -1
- package/web/dist/assets/{cynefin-VYW2F7L2-CHVeQsGv.js → cynefin-VYW2F7L2-CD5a_UMt.js} +1 -1
- package/web/dist/assets/{cynefinDiagram-TSTJHNR4-BYgzn_3A.js → cynefinDiagram-TSTJHNR4-CRsYXGC9.js} +1 -1
- package/web/dist/assets/{dagre-VKFMJZFB-C3qJqyHj.js → dagre-VKFMJZFB-BuPdi3xA.js} +1 -1
- package/web/dist/assets/{diagram-FQU43EPY-BBYvv2Hx.js → diagram-FQU43EPY-MIVIpIBC.js} +1 -1
- package/web/dist/assets/{diagram-G47NLZAW-1Ca1QwqV.js → diagram-G47NLZAW-D7-s_wbT.js} +1 -1
- package/web/dist/assets/{diagram-NH7WQ7WH-CicNpuah.js → diagram-NH7WQ7WH-fmSlDjpy.js} +1 -1
- package/web/dist/assets/{diagram-OA4YK3LP-BDGMGAu8.js → diagram-OA4YK3LP-DwEwWIum.js} +1 -1
- package/web/dist/assets/{diagram-WEI45ONY-EgaBHcZC.js → diagram-WEI45ONY-Gvb_KdCz.js} +1 -1
- package/web/dist/assets/{ebnfDiagram-CCIWWBDH-BEGQ-CpA.js → ebnfDiagram-CCIWWBDH-BOWGc2OC.js} +1 -1
- package/web/dist/assets/{erDiagram-Q63AITRT-BifquFiz.js → erDiagram-Q63AITRT-CrigFfDY.js} +1 -1
- package/web/dist/assets/{flowDiagram-23GEKE2U-BOEV0nau.js → flowDiagram-23GEKE2U-618PQgNe.js} +1 -1
- package/web/dist/assets/{ganttDiagram-NO4QXBWP-Die6C1uw.js → ganttDiagram-NO4QXBWP-lHe8_OHE.js} +1 -1
- package/web/dist/assets/{gitGraphDiagram-IHSO6WYX-BS6qOLxN.js → gitGraphDiagram-IHSO6WYX-CM3Jo1NF.js} +1 -1
- package/web/dist/assets/index-DHFBzAwY.css +1 -0
- package/web/dist/assets/index-G-9aAxKP.js +24 -0
- package/web/dist/assets/{infoDiagram-FWYZ7A6U-C3dgkkDc.js → infoDiagram-FWYZ7A6U-CtDt5Yg1.js} +1 -1
- package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-BmGRDlL-.js → ishikawaDiagram-FXEZZL3T-DMDXe5yy.js} +1 -1
- package/web/dist/assets/{journeyDiagram-5HDEW3XC-De0y-2Pp.js → journeyDiagram-5HDEW3XC-BxUjwmwF.js} +1 -1
- package/web/dist/assets/{kanban-definition-HUTT4EX6-CPVl9fbH.js → kanban-definition-HUTT4EX6-Cey1IRnt.js} +1 -1
- package/web/dist/assets/{linear-BwcW14FN.js → linear-BAaF8u7Q.js} +1 -1
- package/web/dist/assets/{mermaid.core-BPC8y2lr.js → mermaid.core-D7ywN4EU.js} +4 -4
- package/web/dist/assets/{mindmap-definition-LN4V7U3C-CmTjYZ6-.js → mindmap-definition-LN4V7U3C-BTSESh2V.js} +1 -1
- package/web/dist/assets/{papaparse.min-DwUBIiJJ.js → papaparse.min-BhnWt-zK.js} +1 -1
- package/web/dist/assets/{pegDiagram-2B236MQR-DI77hWU9.js → pegDiagram-2B236MQR-BTWzwmna.js} +1 -1
- package/web/dist/assets/{pieDiagram-ENE6RG2P-CQL62GJk.js → pieDiagram-ENE6RG2P-qI9jL2eV.js} +1 -1
- package/web/dist/assets/pilotswarm-Dccv5YRO.js +109 -0
- package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-CQoKIYFZ.js → quadrantDiagram-ABIIQ3AL-B54Va5N3.js} +1 -1
- package/web/dist/assets/{railroadDiagram-RFXS5EU6-B2KkUNRk.js → railroadDiagram-RFXS5EU6-CVXDflXo.js} +1 -1
- package/web/dist/assets/{requirementDiagram-TGXJPOKE-Dgl152x0.js → requirementDiagram-TGXJPOKE-C7abNeEc.js} +1 -1
- package/web/dist/assets/{sankeyDiagram-HTMAVEWB-B-SjWvvt.js → sankeyDiagram-HTMAVEWB-ND6Feq2w.js} +1 -1
- package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-CuuRcXdJ.js → sequenceDiagram-DBY2YBRQ-DXGwvQdf.js} +1 -1
- package/web/dist/assets/{sizeCapture-X5ZJPWSS-TzEpZfA7.js → sizeCapture-X5ZJPWSS-CrXKKsBI.js} +1 -1
- package/web/dist/assets/{stateDiagram-2N3HPSRC-XAi55Ttk.js → stateDiagram-2N3HPSRC-BJWpN6s6.js} +1 -1
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-BQY9l8FM.js +1 -0
- package/web/dist/assets/{swimlanes-5IMT3BWC-C6ATJwDO.js → swimlanes-5IMT3BWC-Cy67JyBd.js} +2 -2
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-DZ0Jn4Gp.js +8 -0
- package/web/dist/assets/{timeline-definition-FHXFAJF6-DhUjhdEH.js → timeline-definition-FHXFAJF6-CeDd4R0u.js} +1 -1
- package/web/dist/assets/{vennDiagram-L72KCM5P-CxoeZ7Ri.js → vennDiagram-L72KCM5P-BDrPrK0B.js} +1 -1
- package/web/dist/assets/{wardleyDiagram-EHGQE667-ChgUaxkB.js → wardleyDiagram-EHGQE667-Bp9PEqNv.js} +1 -1
- package/web/dist/assets/{xychartDiagram-FW5EYKEG-iV0ACrP1.js → xychartDiagram-FW5EYKEG-DI_8u8T1.js} +1 -1
- package/web/dist/index.html +3 -3
- package/web/dist/assets/channel-KxoOVq0K.js +0 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-B6yEKJ21.js +0 -1
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-B6yEKJ21.js +0 -1
- package/web/dist/assets/index-CCniDa-9.css +0 -1
- package/web/dist/assets/index-DpkfY3jc.js +0 -24
- package/web/dist/assets/pilotswarm-BnfVgmru.js +0 -109
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-CXVciPM_.js +0 -1
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-Dk-VTEgD.js +0 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pilotswarm",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.72",
|
|
4
4
|
"description": "PilotSwarm application package: terminal UI, browser portal + Web API server, and MCP server — one install, three bins.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"ink": "^6.8.0",
|
|
82
82
|
"jose": "^6.2.2",
|
|
83
83
|
"mermaid": "^11.16.0",
|
|
84
|
-
"pilotswarm-sdk": "0.5.
|
|
84
|
+
"pilotswarm-sdk": "0.5.72",
|
|
85
85
|
"react": "^19.2.4",
|
|
86
86
|
"react-dom": "^19.2.4",
|
|
87
87
|
"ws": "^8.18.2"
|
|
@@ -7545,6 +7545,7 @@ export class PilotSwarmUiController {
|
|
|
7545
7545
|
force = true,
|
|
7546
7546
|
fullscreen = false,
|
|
7547
7547
|
pane = false,
|
|
7548
|
+
preserveSession = false,
|
|
7548
7549
|
} = {}) {
|
|
7549
7550
|
const resolvedSessionId = sessionId || this.getState().sessions.activeSessionId;
|
|
7550
7551
|
const name = String(filename || "").trim();
|
|
@@ -7558,7 +7559,7 @@ export class PilotSwarmUiController {
|
|
|
7558
7559
|
restoreArtifactId: this.getState().files.selectedArtifactId || null,
|
|
7559
7560
|
});
|
|
7560
7561
|
|
|
7561
|
-
if (this.getState().sessions.activeSessionId !== resolvedSessionId) {
|
|
7562
|
+
if (!preserveSession && this.getState().sessions.activeSessionId !== resolvedSessionId) {
|
|
7562
7563
|
await this.loadSession(resolvedSessionId).catch(() => null);
|
|
7563
7564
|
}
|
|
7564
7565
|
// The reader REPLACES the inspector, so it has no business changing
|
|
@@ -8996,6 +8997,7 @@ export class PilotSwarmUiController {
|
|
|
8996
8997
|
return;
|
|
8997
8998
|
}
|
|
8998
8999
|
|
|
9000
|
+
this.dispatch({ type: "sessions/used", sessionId });
|
|
8999
9001
|
const activePendingQuestion = activeSession?.pendingQuestion || null;
|
|
9000
9002
|
const answeringPendingQuestion = Boolean(activePendingQuestion?.question);
|
|
9001
9003
|
const promptEdit = this.getPromptEditSessionMatch(sessionId);
|
package/ui/core/src/moa.js
CHANGED
|
@@ -42,7 +42,7 @@ export function normalizeMoa(value) {
|
|
|
42
42
|
return safeLayout(dashboard, key, `MoA ${index + 1}`);
|
|
43
43
|
});
|
|
44
44
|
if (!dashboards.length) dashboards.push(safeLayout(null));
|
|
45
|
-
return { version: 3, activeDashboardId: dashboards.some(d => d.id === value.activeDashboardId) ? value.activeDashboardId : dashboards[0].id, dashboards };
|
|
45
|
+
return { version: 3, composerMode: value?.composerMode === "shared" ? "shared" : "per-chat", activeDashboardId: dashboards.some(d => d.id === value.activeDashboardId) ? value.activeDashboardId : dashboards[0].id, dashboards };
|
|
46
46
|
}
|
|
47
47
|
let source = value;
|
|
48
48
|
if (!(value?.version === 2 || (object(value) && Object.hasOwn(value, "tree")))) {
|
|
@@ -51,7 +51,7 @@ export function normalizeMoa(value) {
|
|
|
51
51
|
const active = Number.isInteger(value?.activeSlot) ? Math.max(0, Math.min(4, value.activeSlot)) : 0;
|
|
52
52
|
source = safeLayout(slots[active]).tree ? slots[active] : slots.find(slot => safeLayout(slot).tree);
|
|
53
53
|
}
|
|
54
|
-
return { version: 3, activeDashboardId: "moa-1", dashboards: [safeLayout(source)] };
|
|
54
|
+
return { version: 3, composerMode: value?.composerMode === "shared" ? "shared" : "per-chat", activeDashboardId: "moa-1", dashboards: [safeLayout(source)] };
|
|
55
55
|
}
|
|
56
56
|
export const activeMoaDashboard = value => value.dashboards.find(d => d.id === value.activeDashboardId) || value.dashboards[0];
|
|
57
57
|
export function updateMoaDashboard(value, dashboardId, patch) {
|
package/ui/core/src/reducer.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeSessionSortMode, normalizeSessionUsage, reconcileSessionSort } from "./session-sort.js";
|
|
1
2
|
import { normalizeMoa } from "./moa.js";
|
|
2
3
|
import { retainSessionWarnings } from "./session-errors.js";
|
|
3
4
|
import { buildSessionTree, isManuallyOrderableSession } from "./session-tree.js";
|
|
@@ -846,7 +847,7 @@ export function appReducer(state, action) {
|
|
|
846
847
|
if (sessionId && contentUpdate && state.sessions?.goneIds?.includes(sessionId)) return state;
|
|
847
848
|
const next = baseReducer(state, action);
|
|
848
849
|
if (next === state) return next;
|
|
849
|
-
return reconcileSessionView(state, next, action);
|
|
850
|
+
return reconcileSessionView(state, reconcileSessionSort(state, next, action), action);
|
|
850
851
|
}
|
|
851
852
|
|
|
852
853
|
// Per-session desktop views, kept in step with the ui slice.
|
|
@@ -1101,6 +1102,7 @@ function baseReducer(state, action) {
|
|
|
1101
1102
|
const nextActiveSessionId = hasActive
|
|
1102
1103
|
? normalizeStoredActiveSessionId(settings.activeSessionId)
|
|
1103
1104
|
: state.sessions.activeSessionId;
|
|
1105
|
+
const remoteUsage = normalizeSessionUsage(settings.sessionUsedAt);
|
|
1104
1106
|
const nextSessions = {
|
|
1105
1107
|
...state.sessions,
|
|
1106
1108
|
...(hasOwnerFilter
|
|
@@ -1117,6 +1119,9 @@ function baseReducer(state, action) {
|
|
|
1117
1119
|
: {}),
|
|
1118
1120
|
pinnedIds: nextPinnedIds,
|
|
1119
1121
|
manualOrder: nextManualOrder,
|
|
1122
|
+
sortMode: Object.hasOwn(settings, "sessionSortMode") ? normalizeSessionSortMode(settings.sessionSortMode) : state.sessions.sortMode,
|
|
1123
|
+
usedAt: Object.fromEntries([...new Set([...Object.keys(remoteUsage), ...Object.keys(state.sessions.usedAt || {})])]
|
|
1124
|
+
.map(id => [id, Math.max(remoteUsage[id] || 0, state.sessions.usedAt?.[id] || 0)])),
|
|
1120
1125
|
collapsedIds: nextCollapsedIds,
|
|
1121
1126
|
collapsedIdsExplicit: hasCollapsed ? true : state.sessions.collapsedIdsExplicit,
|
|
1122
1127
|
activeSessionId: nextActiveSessionId,
|
|
@@ -1891,8 +1896,15 @@ function baseReducer(state, action) {
|
|
|
1891
1896
|
};
|
|
1892
1897
|
}
|
|
1893
1898
|
|
|
1899
|
+
case "sessions/sortMode":
|
|
1900
|
+
return { ...state, sessions: { ...state.sessions, sortMode: normalizeSessionSortMode(action.mode) } };
|
|
1901
|
+
case "sessions/refreshSort":
|
|
1902
|
+
return { ...state, sessions: { ...state.sessions } };
|
|
1903
|
+
case "sessions/used":
|
|
1904
|
+
if (!action.sessionId) return state;
|
|
1905
|
+
return { ...state, sessions: { ...state.sessions, usedAt: { ...state.sessions.usedAt, [action.sessionId]: action.at || Date.now() } } };
|
|
1894
1906
|
case "sessions/selected": {
|
|
1895
|
-
state = { ...state, sessions: { ...state.sessions, listDeselected: false, localSelectionMade: true } };
|
|
1907
|
+
state = { ...state, sessions: { ...state.sessions, listDeselected: false, localSelectionMade: true, usedAt: action.sessionId ? { ...state.sessions.usedAt, [action.sessionId]: Date.now() } : state.sessions.usedAt } };
|
|
1896
1908
|
// Per-session chat scroll memory: stash both the outgoing offset
|
|
1897
1909
|
// and whether it follows the bottom. A paused reading position
|
|
1898
1910
|
// stays paused as new messages arrive and across session switches.
|
package/ui/core/src/selectors.js
CHANGED
|
@@ -1332,7 +1332,7 @@ function sessionSearchOwner(session, byId) {
|
|
|
1332
1332
|
|
|
1333
1333
|
function sessionSearchIndex(state) {
|
|
1334
1334
|
const byId = state.sessions?.byId || {};
|
|
1335
|
-
const deps = [state.sessions?.orderById, state.sessions?.pinnedIds, state.sessions?.manualOrder];
|
|
1335
|
+
const deps = [state.sessions?.orderById, state.sessions?.pinnedIds, (state.sessions?.sortMode && state.sessions.sortMode !== "saved" ? state.sessions.sortSnapshot : state.sessions?.manualOrder)];
|
|
1336
1336
|
const cached = sessionSearchIndexCache.get(byId);
|
|
1337
1337
|
if (cached && sameRowDeps(cached.deps, deps)) return cached;
|
|
1338
1338
|
const expanded = buildSessionTree(
|
|
@@ -1340,7 +1340,7 @@ function sessionSearchIndex(state) {
|
|
|
1340
1340
|
new Set(),
|
|
1341
1341
|
state.sessions?.orderById,
|
|
1342
1342
|
state.sessions?.pinnedIds,
|
|
1343
|
-
state.sessions?.manualOrder,
|
|
1343
|
+
(state.sessions?.sortMode && state.sessions.sortMode !== "saved" ? state.sessions.sortSnapshot : state.sessions?.manualOrder),
|
|
1344
1344
|
);
|
|
1345
1345
|
const parentById = new Map();
|
|
1346
1346
|
const entryById = new Map();
|
|
@@ -1371,7 +1371,7 @@ function buildSearchedSessionFlat(state, query) {
|
|
|
1371
1371
|
if (!query) return existingFlat;
|
|
1372
1372
|
const cached = searchedSessionFlatCache.get(existingFlat);
|
|
1373
1373
|
const deps = [state.sessions?.byId, state.sessions?.ownerFilter, state.auth, state.sessions?.orderById,
|
|
1374
|
-
state.sessions?.pinnedIds, state.sessions?.manualOrder, state.sessions?.filterExceptionId];
|
|
1374
|
+
state.sessions?.pinnedIds, (state.sessions?.sortMode && state.sessions.sortMode !== "saved" ? state.sessions.sortSnapshot : state.sessions?.manualOrder), state.sessions?.filterExceptionId];
|
|
1375
1375
|
if (cached?.query === query && sameRowDeps(cached.deps, deps)) return cached.result;
|
|
1376
1376
|
|
|
1377
1377
|
const byId = state.sessions?.byId || {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { buildSessionTree } from "./session-tree.js";
|
|
2
|
+
|
|
3
|
+
export const normalizeSessionSortMode = mode => ["used", "updated"].includes(mode) ? mode : "saved";
|
|
4
|
+
export function normalizeSessionUsage(value) {
|
|
5
|
+
return Object.fromEntries(Object.entries(value || {}).filter(([id, at]) => id && Number.isFinite(at) && at > 0));
|
|
6
|
+
}
|
|
7
|
+
export function snapshotSessionOrder(sessions, mode) {
|
|
8
|
+
const saved = buildSessionTree(Object.values(sessions.byId), new Set(), sessions.orderById, sessions.pinnedIds, sessions.manualOrder);
|
|
9
|
+
const rank = new Map(saved.map((row, index) => [row.sessionId, index]));
|
|
10
|
+
const timestamp = session => {
|
|
11
|
+
if (mode === "used") return sessions.usedAt?.[session.sessionId] || 0;
|
|
12
|
+
const raw = session.updatedAt ?? session.summaryUpdatedAt ?? session.latestSummaryUpdatedAt;
|
|
13
|
+
const value = typeof raw === "number" ? raw : Date.parse(raw || "");
|
|
14
|
+
return Number.isFinite(value) ? value : 0;
|
|
15
|
+
};
|
|
16
|
+
return Object.values(sessions.byId).sort((a, b) => timestamp(b) - timestamp(a)
|
|
17
|
+
|| (rank.get(a.sessionId) ?? Infinity) - (rank.get(b.sessionId) ?? Infinity))
|
|
18
|
+
.map(session => session.sessionId);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Live catalog and usage changes update row contents, never existing positions.
|
|
22
|
+
// A mode change or explicit refresh captures the next order. New rows append.
|
|
23
|
+
export function reconcileSessionSort(previous, next, action) {
|
|
24
|
+
const sessions = next.sessions;
|
|
25
|
+
if (!sessions) return next;
|
|
26
|
+
const mode = normalizeSessionSortMode(sessions.sortMode);
|
|
27
|
+
const modeChanged = mode !== normalizeSessionSortMode(previous.sessions?.sortMode);
|
|
28
|
+
const refresh = action.type === "sessions/refreshSort";
|
|
29
|
+
const rebuild = sessions.flat !== previous.sessions?.flat || sessions.byId !== previous.sessions?.byId || modeChanged || refresh;
|
|
30
|
+
if (!rebuild) return next;
|
|
31
|
+
let sortSnapshot = sessions.sortSnapshot;
|
|
32
|
+
if (mode !== "saved") {
|
|
33
|
+
if (!sessions.listingSeen) sortSnapshot = [];
|
|
34
|
+
else if (modeChanged || refresh || !sortSnapshot?.length) sortSnapshot = snapshotSessionOrder(sessions, mode);
|
|
35
|
+
else {
|
|
36
|
+
const known = new Set(sortSnapshot);
|
|
37
|
+
const added = Object.keys(sessions.byId).filter(id => !known.has(id));
|
|
38
|
+
if (added.length) sortSnapshot = [...sortSnapshot, ...added];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const flat = mode === "saved" && !modeChanged ? sessions.flat : buildSessionTree(
|
|
42
|
+
Object.values(sessions.byId), sessions.collapsedIds, sessions.orderById,
|
|
43
|
+
sessions.pinnedIds, mode === "saved" ? sessions.manualOrder : sortSnapshot);
|
|
44
|
+
return { ...next, sessions: { ...sessions, sortSnapshot, flat } };
|
|
45
|
+
}
|
package/ui/core/src/state.js
CHANGED
|
@@ -480,6 +480,9 @@ export function createInitialState({ mode = "local", branding = null, docs = nul
|
|
|
480
480
|
pinnedIds: normalizeStoredPinnedSessionIds(pinnedSessionIds),
|
|
481
481
|
// Explicit user placement (desktop drag); read by every surface.
|
|
482
482
|
manualOrder: normalizeStoredSessionOrder(sessionOrder),
|
|
483
|
+
sortMode: "saved",
|
|
484
|
+
sortSnapshot: [],
|
|
485
|
+
usedAt: {},
|
|
483
486
|
// Whether a listing of each kind has been processed. "This row is
|
|
484
487
|
// new" is only answerable against a previous listing OF THE SAME
|
|
485
488
|
// KIND — inferring it from byId being non-empty was wrong, because
|
package/ui/react/src/web-app.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeSessionSortMode, normalizeSessionUsage } from "../../core/src/session-sort.js";
|
|
1
2
|
import { ChatCallLine } from "./chat-call-line.js";
|
|
2
3
|
import React from "react";
|
|
3
4
|
import { FeatureFlagsPanel } from "./feature-flags-panel.js";
|
|
@@ -291,6 +292,8 @@ function clearBrowserPreferenceCache() {
|
|
|
291
292
|
function normalizeProfileSettings(settings) {
|
|
292
293
|
const candidate = settings && typeof settings === "object" && !Array.isArray(settings) ? settings : {};
|
|
293
294
|
const normalized = {};
|
|
295
|
+
if (hasOwn(candidate, "sessionSortMode")) normalized.sessionSortMode = normalizeSessionSortMode(candidate.sessionSortMode);
|
|
296
|
+
if (hasOwn(candidate, "sessionUsedAt")) normalized.sessionUsedAt = normalizeSessionUsage(candidate.sessionUsedAt);
|
|
294
297
|
if (hasOwn(candidate, "moa")) normalized.moa = normalizeMoa(candidate.moa);
|
|
295
298
|
if (typeof candidate.themeId === "string" && candidate.themeId.trim()) {
|
|
296
299
|
normalized.themeId = candidate.themeId.trim();
|
|
@@ -441,7 +444,7 @@ function profileViewState(root) {
|
|
|
441
444
|
return {
|
|
442
445
|
...root.ui, ...root.ui.layout,
|
|
443
446
|
ownerFilter: root.sessions.ownerFilter, pinnedIds: root.sessions.pinnedIds,
|
|
444
|
-
manualOrder: root.sessions.manualOrder, collapsedSessionIds: root.sessions.collapsedIds,
|
|
447
|
+
manualOrder: root.sessions.manualOrder, sessionSortMode: root.sessions.sortMode, sessionUsedAt: root.sessions.usedAt, collapsedSessionIds: root.sessions.collapsedIds,
|
|
445
448
|
activeSessionId: root.sessions.activeSessionId, canvasPrefs: root.canvas?.prefs,
|
|
446
449
|
};
|
|
447
450
|
}
|
|
@@ -464,6 +467,8 @@ function profileSettingsFromViewState(state, preservedOtherTouchScale = null, pr
|
|
|
464
467
|
},
|
|
465
468
|
pinnedSessionIds: state.pinnedIds,
|
|
466
469
|
sessionOrder: state.manualOrder,
|
|
470
|
+
sessionSortMode: state.sessionSortMode,
|
|
471
|
+
sessionUsedAt: state.sessionUsedAt,
|
|
467
472
|
collapsedSessionIds: state.collapsedSessionIds,
|
|
468
473
|
activeSessionId: state.activeSessionId,
|
|
469
474
|
// rightPaneMode describes the DESKTOP right column; a phone has no
|
|
@@ -528,6 +533,8 @@ function buildDefaultProfileSettingsFromState(state, preservedOtherTouchScale =
|
|
|
528
533
|
layoutAdjustments: state?.ui?.layout,
|
|
529
534
|
pinnedSessionIds: state?.sessions?.pinnedIds,
|
|
530
535
|
sessionOrder: state?.sessions?.manualOrder,
|
|
536
|
+
sessionSortMode: state?.sessions?.sortMode,
|
|
537
|
+
sessionUsedAt: state?.sessions?.usedAt,
|
|
531
538
|
collapsedSessionIds: state?.sessions?.collapsedIds,
|
|
532
539
|
activeSessionId: state?.sessions?.activeSessionId,
|
|
533
540
|
...(isNarrowViewport()
|
|
@@ -575,6 +582,8 @@ function materializeProfileSettings(remoteSettings, defaults) {
|
|
|
575
582
|
// here is DROPPED at startup — and the save effect then writes the
|
|
576
583
|
// empty value straight back over the stored one. Leaving sessionOrder
|
|
577
584
|
// out made every placement survive exactly until the next reload.
|
|
585
|
+
sessionSortMode: normalizedRemote.sessionSortMode ?? normalizedDefaults.sessionSortMode,
|
|
586
|
+
sessionUsedAt: normalizedRemote.sessionUsedAt ?? normalizedDefaults.sessionUsedAt,
|
|
578
587
|
sessionOrder: hasOwn(normalizedRemote, "sessionOrder")
|
|
579
588
|
? normalizedRemote.sessionOrder
|
|
580
589
|
: normalizedDefaults.sessionOrder,
|
|
@@ -2030,6 +2039,10 @@ function ArtifactLink({ artifactRef }) {
|
|
|
2030
2039
|
*/
|
|
2031
2040
|
function openArtifactFromChat(controller, sessionId, filename) {
|
|
2032
2041
|
if (!controller?.revealArtifact) return;
|
|
2042
|
+
if (controller.openChatArtifact) {
|
|
2043
|
+
Promise.resolve(controller.openChatArtifact(sessionId, filename)).catch(() => {});
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
2033
2046
|
const isPhone = typeof window !== "undefined"
|
|
2034
2047
|
&& typeof window.matchMedia === "function"
|
|
2035
2048
|
&& window.matchMedia("(max-width: 920px)").matches;
|
|
@@ -4545,28 +4558,26 @@ function SessionDetailBox({ session, childCount = 0, pause = null, controller =
|
|
|
4545
4558
|
* fleet. That only holds while every prop is referentially stable — hence the
|
|
4546
4559
|
* hoisted click handler and ref setter rather than closures built per row.
|
|
4547
4560
|
*/
|
|
4548
|
-
/**
|
|
4549
|
-
|
|
4550
|
-
* pointerdown and needs `touch-action: none` to receive a move stream — which
|
|
4551
|
-
* on a touch screen also means the list can no longer be scrolled by dragging
|
|
4552
|
-
* it, so the gesture is withheld there entirely.
|
|
4553
|
-
*/
|
|
4554
|
-
function useCoarsePointer() {
|
|
4555
|
-
const query = "(pointer: coarse)";
|
|
4561
|
+
/** Subscribe to viewport or input changes without remounting the pane. */
|
|
4562
|
+
function useMediaQuery(query) {
|
|
4556
4563
|
const read = () => (typeof window !== "undefined" && typeof window.matchMedia === "function"
|
|
4557
4564
|
? window.matchMedia(query).matches
|
|
4558
4565
|
: false);
|
|
4559
|
-
const [
|
|
4566
|
+
const [matches, setMatches] = React.useState(read);
|
|
4560
4567
|
React.useEffect(() => {
|
|
4561
4568
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return undefined;
|
|
4562
4569
|
const media = window.matchMedia(query);
|
|
4563
|
-
const onChange = (event) =>
|
|
4570
|
+
const onChange = (event) => setMatches(event.matches);
|
|
4571
|
+
setMatches(media.matches);
|
|
4564
4572
|
media.addEventListener?.("change", onChange);
|
|
4565
4573
|
return () => media.removeEventListener?.("change", onChange);
|
|
4566
|
-
}, []);
|
|
4567
|
-
return
|
|
4574
|
+
}, [query]);
|
|
4575
|
+
return matches;
|
|
4568
4576
|
}
|
|
4569
4577
|
|
|
4578
|
+
// Withhold drag-to-folder on touch screens so finger drags can scroll the list.
|
|
4579
|
+
function useCoarsePointer() { return useMediaQuery("(pointer: coarse)"); }
|
|
4580
|
+
|
|
4570
4581
|
const RAIL_ORIGIN_PX = 13;
|
|
4571
4582
|
const RAIL_STEP_PX = 11;
|
|
4572
4583
|
const RAIL_WIDTH_PX = 1.5;
|
|
@@ -5039,11 +5050,10 @@ function useAxisLockedPan(ref, enabled = true) {
|
|
|
5039
5050
|
}, [ref, enabled]);
|
|
5040
5051
|
}
|
|
5041
5052
|
|
|
5042
|
-
const SessionSearchControl = React.memo(function SessionSearchControl({ query = "", onQuery, matchCount = 0, mobile = false, listRef = null }) {
|
|
5053
|
+
const SessionSearchControl = React.memo(function SessionSearchControl({ query = "", onQuery, matchCount = 0, mobile = false, expanded = false, onOpen, onClose, triggerRef, listRef = null }) {
|
|
5043
5054
|
// Keep typing local: expensive list filtering and external-store updates
|
|
5044
5055
|
// must never be part of the input's immediate render path.
|
|
5045
5056
|
const [draft, setDraft] = React.useState(query);
|
|
5046
|
-
const [mobileOpen, setMobileOpen] = React.useState(Boolean(query));
|
|
5047
5057
|
const timer = React.useRef(null);
|
|
5048
5058
|
const published = React.useRef(query);
|
|
5049
5059
|
const composing = React.useRef(false);
|
|
@@ -5062,29 +5072,24 @@ const SessionSearchControl = React.memo(function SessionSearchControl({ query =
|
|
|
5062
5072
|
cancel(); published.current = query; setDraft(query);
|
|
5063
5073
|
}, [query]);
|
|
5064
5074
|
const inputRef = React.useRef(null);
|
|
5065
|
-
React.useEffect(() => {
|
|
5066
|
-
if (query) setMobileOpen(true);
|
|
5067
|
-
}, [query]);
|
|
5068
|
-
const open = () => {
|
|
5069
|
-
setMobileOpen(true);
|
|
5070
|
-
requestAnimationFrame(() => inputRef.current?.focus({ preventScroll: true }));
|
|
5071
|
-
};
|
|
5072
5075
|
const closeOrClear = () => {
|
|
5073
5076
|
if (draft || query) { setDraft(""); publish(""); }
|
|
5074
|
-
else
|
|
5077
|
+
else onClose?.();
|
|
5075
5078
|
};
|
|
5076
5079
|
const resultLabel = draft !== query ? "Searching…" : draft
|
|
5077
5080
|
? `${matchCount} ${matchCount === 1 ? "match" : "matches"}` : "";
|
|
5078
5081
|
return React.createElement("div", {
|
|
5079
|
-
className: `ps-session-search${
|
|
5082
|
+
className: `ps-session-search${expanded ? " is-open" : ""}`,
|
|
5080
5083
|
"data-session-search": "true",
|
|
5081
5084
|
},
|
|
5082
5085
|
mobile ? React.createElement("button", {
|
|
5083
5086
|
type: "button",
|
|
5084
5087
|
className: "ps-session-search-trigger",
|
|
5088
|
+
ref: triggerRef,
|
|
5089
|
+
"aria-expanded": expanded,
|
|
5085
5090
|
"aria-label": "Search sessions",
|
|
5086
5091
|
title: "Search sessions",
|
|
5087
|
-
onClick:
|
|
5092
|
+
onClick: onOpen,
|
|
5088
5093
|
}, React.createElement(SearchGlyph)) : null,
|
|
5089
5094
|
React.createElement("div", { className: "ps-session-search-fields" },
|
|
5090
5095
|
React.createElement(SearchGlyph),
|
|
@@ -5115,7 +5120,7 @@ const SessionSearchControl = React.memo(function SessionSearchControl({ query =
|
|
|
5115
5120
|
role: "status",
|
|
5116
5121
|
"aria-live": "polite",
|
|
5117
5122
|
}, resultLabel) : null,
|
|
5118
|
-
|
|
5123
|
+
draft ? React.createElement("button", {
|
|
5119
5124
|
type: "button",
|
|
5120
5125
|
className: "ps-session-search-clear",
|
|
5121
5126
|
"aria-label": draft ? "Clear session search" : "Close session search",
|
|
@@ -5132,6 +5137,11 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
|
|
|
5132
5137
|
// sessions-ONLY layout is the exception: it has the whole screen and the
|
|
5133
5138
|
// detail box is the point of asking for it.
|
|
5134
5139
|
const isMobilePane = String(panelClassName).includes("ps-mobile-session-pane");
|
|
5140
|
+
const compactSearch = useMediaQuery("(max-width: 920px)");
|
|
5141
|
+
const [searchOpen, setSearchOpen] = React.useState(false);
|
|
5142
|
+
const searchOverlay = compactSearch && searchOpen;
|
|
5143
|
+
const searchDialogRef = React.useRef(null);
|
|
5144
|
+
const searchTriggerRef = React.useRef(null);
|
|
5135
5145
|
// Vertical is the primary scroll axis; sideways takes a deliberate swipe.
|
|
5136
5146
|
const sessionListRef = React.useRef(null);
|
|
5137
5147
|
// Selection reverts to plain taps wherever the primary input is a finger:
|
|
@@ -5159,6 +5169,8 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
|
|
|
5159
5169
|
ownerFilter: state.sessions.ownerFilter,
|
|
5160
5170
|
pinnedIds: state.sessions.pinnedIds,
|
|
5161
5171
|
manualOrder: state.sessions.manualOrder,
|
|
5172
|
+
sortMode: state.sessions.sortMode,
|
|
5173
|
+
sortSnapshot: state.sessions.sortSnapshot,
|
|
5162
5174
|
selectedIds: state.sessions.selectedIds,
|
|
5163
5175
|
selectMode: state.sessions.selectMode,
|
|
5164
5176
|
auth: state.auth,
|
|
@@ -5198,6 +5210,8 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
|
|
|
5198
5210
|
ownerFilter: viewState.ownerFilter,
|
|
5199
5211
|
pinnedIds: viewState.pinnedIds,
|
|
5200
5212
|
manualOrder: viewState.manualOrder,
|
|
5213
|
+
sortMode: viewState.sortMode,
|
|
5214
|
+
sortSnapshot: viewState.sortSnapshot,
|
|
5201
5215
|
selectedIds: viewState.selectedIds,
|
|
5202
5216
|
selectMode: viewState.selectMode,
|
|
5203
5217
|
listDeselected: viewState.listDeselected,
|
|
@@ -5216,7 +5230,7 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
|
|
|
5216
5230
|
prefs: viewState.canvasPrefs,
|
|
5217
5231
|
},
|
|
5218
5232
|
budget: { paused: viewState.budgetPaused },
|
|
5219
|
-
}), [viewState.activeSessionId, viewState.auth, viewState.branding, viewState.budgetPaused, viewState.canvasBySessionId, viewState.canvasPrefs, viewState.connectionMode, viewState.filterQuery, viewState.listDeselected, viewState.ownerFilter, viewState.pinnedIds, viewState.manualOrder, viewState.selectedIds, viewState.selectMode, viewState.sessionsById, viewState.sessionsFlat]);
|
|
5233
|
+
}), [viewState.activeSessionId, viewState.auth, viewState.branding, viewState.budgetPaused, viewState.canvasBySessionId, viewState.canvasPrefs, viewState.connectionMode, viewState.filterQuery, viewState.listDeselected, viewState.ownerFilter, viewState.pinnedIds, viewState.manualOrder, viewState.sortMode, viewState.sortSnapshot, viewState.selectedIds, viewState.selectMode, viewState.sessionsById, viewState.sessionsFlat]);
|
|
5220
5234
|
// Hold the previous rows when a poll produced identical output.
|
|
5221
5235
|
const rows = useStableValue(computedRows);
|
|
5222
5236
|
const searchScrollRef = React.useRef({ top: 0, left: 0, restore: false });
|
|
@@ -5236,6 +5250,35 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
|
|
|
5236
5250
|
}
|
|
5237
5251
|
searchEnvRef.current?.setQuery?.(next);
|
|
5238
5252
|
}, []);
|
|
5253
|
+
const closeSearch = React.useCallback(() => {
|
|
5254
|
+
setSearchOpen(false);
|
|
5255
|
+
setSearchQuery("");
|
|
5256
|
+
requestAnimationFrame(() => searchTriggerRef.current?.focus({ preventScroll: true }));
|
|
5257
|
+
}, [setSearchQuery]);
|
|
5258
|
+
React.useLayoutEffect(() => {
|
|
5259
|
+
const dialog = searchDialogRef.current;
|
|
5260
|
+
if (!searchOverlay || !dialog) return undefined;
|
|
5261
|
+
const viewport = window.visualViewport;
|
|
5262
|
+
const resize = () => {
|
|
5263
|
+
dialog.style.top = `${(viewport?.offsetTop || 0) + 8}px`;
|
|
5264
|
+
dialog.style.left = `${(viewport?.offsetLeft || 0) + 8}px`;
|
|
5265
|
+
dialog.style.width = `${(viewport?.width || window.innerWidth) - 16}px`;
|
|
5266
|
+
dialog.style.height = `${(viewport?.height || window.innerHeight) - 16}px`;
|
|
5267
|
+
};
|
|
5268
|
+
resize();
|
|
5269
|
+
dialog.showModal();
|
|
5270
|
+
dialog.querySelector("input")?.focus({ preventScroll: true });
|
|
5271
|
+
viewport?.addEventListener("resize", resize);
|
|
5272
|
+
viewport?.addEventListener("scroll", resize);
|
|
5273
|
+
window.addEventListener("resize", resize);
|
|
5274
|
+
return () => {
|
|
5275
|
+
viewport?.removeEventListener("resize", resize);
|
|
5276
|
+
viewport?.removeEventListener("scroll", resize);
|
|
5277
|
+
window.removeEventListener("resize", resize);
|
|
5278
|
+
dialog.close();
|
|
5279
|
+
};
|
|
5280
|
+
}, [searchOverlay]);
|
|
5281
|
+
React.useEffect(() => { if (!compactSearch) setSearchOpen(false); }, [compactSearch]);
|
|
5239
5282
|
React.useLayoutEffect(() => {
|
|
5240
5283
|
if (viewState.filterQuery || !searchScrollRef.current.restore || !sessionListRef.current) return;
|
|
5241
5284
|
sessionListRef.current.scrollTop = searchScrollRef.current.top;
|
|
@@ -5369,7 +5412,7 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
|
|
|
5369
5412
|
// rows/viewState from a ref that is refreshed on every render instead of
|
|
5370
5413
|
// closing over them.
|
|
5371
5414
|
const clickEnv = React.useRef(null);
|
|
5372
|
-
clickEnv.current = { rows, viewState, controller, selection };
|
|
5415
|
+
clickEnv.current = { rows, viewState, controller, selection, searchOverlay, closeSearch };
|
|
5373
5416
|
// ── Drag sessions into / out of groups ────────────────────────────
|
|
5374
5417
|
// Pointer-driven so it works from <button> rows, can render a collection
|
|
5375
5418
|
// ghost for a multi-selection, and can highlight the destination folder.
|
|
@@ -5695,6 +5738,15 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
|
|
|
5695
5738
|
|
|
5696
5739
|
const handleRowClick = React.useCallback((event, row) => {
|
|
5697
5740
|
const { rows: currentRows, viewState: current, controller: ctl, selection: pick } = clickEnv.current;
|
|
5741
|
+
if (clickEnv.current.searchOverlay && !row.isGroup) {
|
|
5742
|
+
if (pick) pick.onSelect(row.sessionId);
|
|
5743
|
+
else {
|
|
5744
|
+
ctl.setFocus("sessions");
|
|
5745
|
+
ctl.loadSession(row.sessionId).catch(() => {});
|
|
5746
|
+
}
|
|
5747
|
+
clickEnv.current.closeSearch();
|
|
5748
|
+
return;
|
|
5749
|
+
}
|
|
5698
5750
|
if (pick) {
|
|
5699
5751
|
if (row.isGroup || (row.hasChildren && row.active)) ctl.dispatch({ type: row.collapsed ? "sessions/expand" : "sessions/collapse", sessionId: row.sessionId });
|
|
5700
5752
|
if (!row.isGroup) pick.onSelect(row.sessionId);
|
|
@@ -5938,14 +5990,12 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
|
|
|
5938
5990
|
: "Release to cancel"))
|
|
5939
5991
|
: null;
|
|
5940
5992
|
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
actionsOnly ? (actionsHost ? createPortal(React.createElement("div", { className: "ps-moa-control-actions", onClick: onAction }, panelActions), actionsHost) : null) : React.createElement(Panel, {
|
|
5944
|
-
title: [{ text: "Sessions", color: "yellow", bold: true }],
|
|
5993
|
+
const sessionPanel = actionsOnly ? (actionsHost ? createPortal(React.createElement("div", { className: "ps-moa-control-actions", onClick: onAction }, panelActions), actionsHost) : null) : React.createElement(Panel, {
|
|
5994
|
+
title: [{ text: searchOverlay ? "Find a session" : "Sessions", color: "yellow", bold: true }],
|
|
5945
5995
|
color: "yellow",
|
|
5946
5996
|
focused: viewState.focused,
|
|
5947
5997
|
theme,
|
|
5948
|
-
actions: selection ? React.createElement(React.Fragment, null,
|
|
5998
|
+
actions: searchOverlay ? React.createElement(IconButton, { label: "Close session search", icon: "×", onClick: closeSearch }) : selection ? React.createElement(React.Fragment, null,
|
|
5949
5999
|
selection.onCreate ? React.createElement(IconButton, {
|
|
5950
6000
|
className: "ps-mini-button", icon: React.createElement(PlusGlyph),
|
|
5951
6001
|
label: "Create New Session", onClick: selection.onCreate,
|
|
@@ -5991,16 +6041,45 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
|
|
|
5991
6041
|
setRef: setSessionButtonRef,
|
|
5992
6042
|
// Drag-to-folder is a fine-pointer gesture: arming it on
|
|
5993
6043
|
// touch hijacks the finger that should be scrolling the list.
|
|
5994
|
-
drag: selection || touchInput ? null : dragHandlers,
|
|
6044
|
+
drag: selection || touchInput || viewState.sortMode !== "saved" ? null : dragHandlers,
|
|
5995
6045
|
}))),
|
|
5996
|
-
React.createElement(
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6046
|
+
React.createElement("div", {
|
|
6047
|
+
className: "ps-session-find-controls",
|
|
6048
|
+
onPointerDown: (event) => {
|
|
6049
|
+
// Keep the search keyboard open while tapping sort, refresh, or clear.
|
|
6050
|
+
// Safari otherwise blurs the field without focusing the button.
|
|
6051
|
+
if (compactSearch && event.button === 0 && event.target.closest?.("button")
|
|
6052
|
+
&& event.currentTarget.contains(document.activeElement)
|
|
6053
|
+
&& document.activeElement?.matches("input")) event.preventDefault();
|
|
6054
|
+
},
|
|
6055
|
+
},
|
|
6056
|
+
React.createElement("div", { className: "ps-session-sort-controls" },
|
|
6057
|
+
React.createElement("div", { className: "ps-session-sort-modes", role: "group", "aria-label": "Session sort order" },
|
|
6058
|
+
...[["used", "Recently used", "M12 8v4l3 2", true], ["updated", "Recently updated", "M3 12h4l3-7 4 14 3-7h4"], ["saved", "Saved order", "M6 3h12v18l-6-4-6 4Z"]].map(([mode, label, path, clock]) => React.createElement(IconButton, {
|
|
6059
|
+
key: mode, label, className: "ps-session-sort-button", active: (viewState.sortMode || "saved") === mode, pressed: (viewState.sortMode || "saved") === mode,
|
|
6060
|
+
icon: React.createElement("svg", { viewBox: "0 0 24 24", width: 18, height: 18, fill: "none", stroke: "currentColor", strokeWidth: 1.8, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true }, clock ? React.createElement("circle", { cx: 12, cy: 12, r: 9 }) : null, React.createElement("path", { d: path })),
|
|
6061
|
+
onClick: () => controller.dispatch({ type: "sessions/sortMode", mode }),
|
|
6062
|
+
}))),
|
|
6063
|
+
React.createElement(IconButton, {
|
|
6064
|
+
label: "Refresh session order", className: "ps-session-sort-refresh",
|
|
6065
|
+
icon: React.createElement("svg", { viewBox: "0 0 24 24", width: 18, height: 18, fill: "none", stroke: "currentColor", strokeWidth: 1.8, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true }, React.createElement("path", { d: "M20 7v5h-5M20 12a8 8 0 1 0-2 5M20 7v5" })),
|
|
6066
|
+
onClick: async () => {
|
|
6067
|
+
try { await controller.refreshSessions(); controller.dispatch({ type: "sessions/refreshSort" }); }
|
|
6068
|
+
catch { controller.dispatch({ type: "ui/status", text: "Could not refresh sessions. Try again." }); }
|
|
6069
|
+
},
|
|
6070
|
+
})),
|
|
6071
|
+
React.createElement(SessionSearchControl, {
|
|
6072
|
+
query: viewState.filterQuery,
|
|
6073
|
+
onQuery: setSearchQuery,
|
|
6074
|
+
matchCount: searchMatchCount,
|
|
6075
|
+
mobile: compactSearch,
|
|
6076
|
+
expanded: searchOverlay,
|
|
6077
|
+
onOpen: () => setSearchOpen(true),
|
|
6078
|
+
onClose: closeSearch,
|
|
6079
|
+
triggerRef: searchTriggerRef,
|
|
6080
|
+
listRef: sessionListRef,
|
|
6081
|
+
})),
|
|
6082
|
+
!searchOverlay && (showDetailBox === null ? !isMobilePane : showDetailBox)
|
|
6004
6083
|
? React.createElement(SessionDetailBox, {
|
|
6005
6084
|
session: activeSession,
|
|
6006
6085
|
childCount: activeRow?.childCount || 0,
|
|
@@ -6014,7 +6093,21 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
|
|
|
6014
6093
|
collapsed: !detailCollapsed,
|
|
6015
6094
|
}),
|
|
6016
6095
|
})
|
|
6017
|
-
: null)
|
|
6096
|
+
: null);
|
|
6097
|
+
|
|
6098
|
+
return React.createElement(React.Fragment, null,
|
|
6099
|
+
dragGhost,
|
|
6100
|
+
!searchOverlay ? sessionPanel : null,
|
|
6101
|
+
React.createElement("dialog", {
|
|
6102
|
+
ref: searchDialogRef,
|
|
6103
|
+
className: "ps-session-search-overlay",
|
|
6104
|
+
"aria-label": "Find a session",
|
|
6105
|
+
onCancel: event => { event.preventDefault(); closeSearch(); },
|
|
6106
|
+
onKeyDownCapture: event => {
|
|
6107
|
+
if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); closeSearch(); }
|
|
6108
|
+
},
|
|
6109
|
+
onClick: event => { if (event.target === event.currentTarget) closeSearch(); },
|
|
6110
|
+
}, searchOverlay ? sessionPanel : null),
|
|
6018
6111
|
(manageOpen && activeSession && !activeSession.isGroup)
|
|
6019
6112
|
? React.createElement(SessionModifyModal, {
|
|
6020
6113
|
controller,
|
|
@@ -7192,14 +7285,14 @@ export function evaluateKeyboardTakeover(baseline, viewportWidth, viewportHeight
|
|
|
7192
7285
|
}
|
|
7193
7286
|
|
|
7194
7287
|
/**
|
|
7195
|
-
* True while the on-screen keyboard is up AND
|
|
7288
|
+
* True while the on-screen keyboard is up AND a matching control has focus.
|
|
7196
7289
|
* Both conditions matter: height alone would fire for a modal's text field
|
|
7197
7290
|
* and collapse the workspace behind the modal; focus alone would fire for
|
|
7198
7291
|
* hardware keyboards that shrink nothing. State-driven on the keyboard
|
|
7199
7292
|
* itself, so it reverts the moment the keyboard goes away — including the
|
|
7200
7293
|
* iOS swipe-dismiss that closes the keyboard without blurring the input.
|
|
7201
7294
|
*/
|
|
7202
|
-
function useKeyboardTakeover(enabled) {
|
|
7295
|
+
function useKeyboardTakeover(enabled, focusSelector = ".ps-prompt-input") {
|
|
7203
7296
|
const [takeover, setTakeover] = React.useState(false);
|
|
7204
7297
|
React.useEffect(() => {
|
|
7205
7298
|
if (!enabled || typeof window === "undefined" || !window.visualViewport) {
|
|
@@ -7211,7 +7304,7 @@ function useKeyboardTakeover(enabled) {
|
|
|
7211
7304
|
let frame = 0;
|
|
7212
7305
|
const evaluate = () => {
|
|
7213
7306
|
frame = 0;
|
|
7214
|
-
const composerFocused = Boolean(document.activeElement?.
|
|
7307
|
+
const composerFocused = Boolean(document.activeElement?.matches?.(focusSelector));
|
|
7215
7308
|
const next = evaluateKeyboardTakeover(baseline, viewport.width, viewport.height, composerFocused);
|
|
7216
7309
|
baseline = next.baseline;
|
|
7217
7310
|
setTakeover(next.takeover);
|
|
@@ -7229,7 +7322,7 @@ function useKeyboardTakeover(enabled) {
|
|
|
7229
7322
|
window.removeEventListener("focusin", schedule);
|
|
7230
7323
|
window.removeEventListener("focusout", schedule);
|
|
7231
7324
|
};
|
|
7232
|
-
}, [enabled]);
|
|
7325
|
+
}, [enabled, focusSelector]);
|
|
7233
7326
|
return enabled ? takeover : false;
|
|
7234
7327
|
}
|
|
7235
7328
|
|
|
@@ -9381,7 +9474,7 @@ function StatusStrip({ controller }) {
|
|
|
9381
9474
|
// (hold ~450ms to see the label, release to dismiss — the long-press does not
|
|
9382
9475
|
// fire onClick). aria-label carries the meaning for assistive tech.
|
|
9383
9476
|
const ICON_HOVER_TOOLTIP_MS = 1000;
|
|
9384
|
-
function IconButton({ icon, label, onClick, disabled = false, active = false, className = "ps-toolbar-button" }) {
|
|
9477
|
+
function IconButton({ icon, label, onClick, disabled = false, active = false, pressed = undefined, className = "ps-toolbar-button" }) {
|
|
9385
9478
|
// The tooltip is portaled to <body> so it escapes the toolbar/pane
|
|
9386
9479
|
// overflow-clipping and stacking contexts (nested tooltips were hidden
|
|
9387
9480
|
// behind, or bled through by, the panes). Coordinates are computed from
|
|
@@ -9524,6 +9617,7 @@ function IconButton({ icon, label, onClick, disabled = false, active = false, cl
|
|
|
9524
9617
|
onClick: handleClick,
|
|
9525
9618
|
disabled,
|
|
9526
9619
|
"aria-label": label,
|
|
9620
|
+
"aria-pressed": pressed,
|
|
9527
9621
|
onPointerEnter: startHover,
|
|
9528
9622
|
onPointerLeave: endHover,
|
|
9529
9623
|
onPointerDown: startPress,
|
|
@@ -14666,6 +14760,8 @@ export function PilotSwarmWebApp({ controller, suspended = false, moa = null })
|
|
|
14666
14760
|
// never persists — the reducer would reorder the list correctly and
|
|
14667
14761
|
// the placement would vanish on reload.
|
|
14668
14762
|
manualOrder: rootState.sessions.manualOrder,
|
|
14763
|
+
sessionSortMode: rootState.sessions.sortMode,
|
|
14764
|
+
sessionUsedAt: rootState.sessions.usedAt,
|
|
14669
14765
|
// Pass the live Set reference here so shallow-equal sees the same
|
|
14670
14766
|
// identity across renders (a fresh array would break memoization).
|
|
14671
14767
|
// Conversion to a sorted array happens inside `normalizeProfileSettings`.
|
|
@@ -15007,7 +15103,7 @@ export function PilotSwarmWebApp({ controller, suspended = false, moa = null })
|
|
|
15007
15103
|
});
|
|
15008
15104
|
}, 400);
|
|
15009
15105
|
return undefined;
|
|
15010
|
-
}, [controller, state.moa, state.activeSessionId, state.activityPaneAdjust, state.agentPickerUsage, state.canvasOpen, state.canvasPaneAdjust, state.canvasPrefs, state.canvasZen, state.collapsedSessionIds, state.diagnosticsOpen, state.diagnosticsPaneAdjust, state.diagnosticsSplitAdjust, state.ownerFilter, state.paneAdjust, state.manualOrder, state.pinnedIds, state.portalSessionColumnAdjust, state.rightPaneMode, state.sessionDetailCollapsed, state.sessionPaneAdjust, state.sessionViews, state.themeId, state.touchScale]);
|
|
15106
|
+
}, [controller, state.moa, state.activeSessionId, state.activityPaneAdjust, state.agentPickerUsage, state.canvasOpen, state.canvasPaneAdjust, state.canvasPrefs, state.canvasZen, state.collapsedSessionIds, state.diagnosticsOpen, state.diagnosticsPaneAdjust, state.diagnosticsSplitAdjust, state.ownerFilter, state.paneAdjust, state.manualOrder, state.sessionSortMode, state.sessionUsedAt, state.pinnedIds, state.portalSessionColumnAdjust, state.rightPaneMode, state.sessionDetailCollapsed, state.sessionPaneAdjust, state.sessionViews, state.themeId, state.touchScale]);
|
|
15011
15107
|
|
|
15012
15108
|
React.useEffect(() => {
|
|
15013
15109
|
applyDocumentTheme(state.themeId);
|
package/web/dist/assets/{abnfDiagram-VRR7QNED-CidJDJrY.js → abnfDiagram-VRR7QNED-D__RWhK7.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-
|
|
1
|
+
import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-BOfHzBMe.js";import{p as f}from"./chunk-JWPE2WC7-ZfmDblU2.js";import{_ as n,l as o}from"./mermaid.core-D7ywN4EU.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-CD5a_UMt.js";import"./pilotswarm-Dccv5YRO.js";var v=d().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),l=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:l,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},B={parser:R,db:a,renderer:u,styles:p};export{B as diagram};
|