pilotswarm 0.5.76 → 0.5.78

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.
Files changed (72) hide show
  1. package/package.json +2 -2
  2. package/tui/src/embedded-workers.js +3 -0
  3. package/ui/core/src/controller.js +4 -1
  4. package/ui/react/src/web-app.js +171 -90
  5. package/web/dist/assets/{abnfDiagram-VRR7QNED-DHZAKybB.js → abnfDiagram-VRR7QNED-C2HGSvvf.js} +1 -1
  6. package/web/dist/assets/{arc-Bw0S8L-3.js → arc-C3faoYvN.js} +1 -1
  7. package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-CIdYlIBv.js → architectureDiagram-ZJ3FMSHR-CQhrAa5t.js} +1 -1
  8. package/web/dist/assets/{blockDiagram-677ZJIJ3-D9PkQfSJ.js → blockDiagram-677ZJIJ3-CVGwBieB.js} +1 -1
  9. package/web/dist/assets/{c4Diagram-LMCZKHZV-D9YbJQu6.js → c4Diagram-LMCZKHZV-CmdrEA77.js} +1 -1
  10. package/web/dist/assets/channel-CTmDTaW4.js +1 -0
  11. package/web/dist/assets/{chunk-2Q5K7J3B-BTdwvy5p.js → chunk-2Q5K7J3B-Bm1hu4Hq.js} +1 -1
  12. package/web/dist/assets/{chunk-32BRIVSS--ow0RNjy.js → chunk-32BRIVSS--_hODe_V.js} +1 -1
  13. package/web/dist/assets/{chunk-5VM5RSS4-DXsS8QVF.js → chunk-5VM5RSS4-COI5t4m6.js} +1 -1
  14. package/web/dist/assets/{chunk-EX3LRPZG-HKSF2xwD.js → chunk-EX3LRPZG-BCFKy6tL.js} +1 -1
  15. package/web/dist/assets/{chunk-JWPE2WC7-DY6_m3mf.js → chunk-JWPE2WC7-Ddh7J2Rp.js} +1 -1
  16. package/web/dist/assets/{chunk-MOJQB5TN-B-y9e9g1.js → chunk-MOJQB5TN-DuIGV22R.js} +1 -1
  17. package/web/dist/assets/{chunk-RYQCIY6F-UK1vqINl.js → chunk-RYQCIY6F-BNQbAlZV.js} +1 -1
  18. package/web/dist/assets/{chunk-V7JOEXUC-8KQ7DOjY.js → chunk-V7JOEXUC-DmoLjQx9.js} +1 -1
  19. package/web/dist/assets/{chunk-VR4S4FIN-CkDJLoRb.js → chunk-VR4S4FIN-BTOlqom4.js} +1 -1
  20. package/web/dist/assets/{chunk-XXDRQBXY-DrvpqdcI.js → chunk-XXDRQBXY-DTsc-Dgs.js} +1 -1
  21. package/web/dist/assets/classDiagram-OUVF2IWQ-0JetCpCm.js +1 -0
  22. package/web/dist/assets/classDiagram-v2-EOCWNBFH-0JetCpCm.js +1 -0
  23. package/web/dist/assets/{cose-bilkent-JH36ORCC-BZimjiO7.js → cose-bilkent-JH36ORCC-QEqZLCny.js} +1 -1
  24. package/web/dist/assets/{cynefin-VYW2F7L2-DgXpH0xt.js → cynefin-VYW2F7L2-Deleul8f.js} +1 -1
  25. package/web/dist/assets/{cynefinDiagram-TSTJHNR4-GhvhOnjl.js → cynefinDiagram-TSTJHNR4-D0HrNQd4.js} +1 -1
  26. package/web/dist/assets/{dagre-VKFMJZFB-_likmiuO.js → dagre-VKFMJZFB-CLdKXtyZ.js} +1 -1
  27. package/web/dist/assets/{diagram-FQU43EPY-DQctGbIk.js → diagram-FQU43EPY-C0DnCIiG.js} +1 -1
  28. package/web/dist/assets/{diagram-G47NLZAW-FVvT1dxp.js → diagram-G47NLZAW-DzQa8iDK.js} +1 -1
  29. package/web/dist/assets/{diagram-NH7WQ7WH-Xg1HHD3c.js → diagram-NH7WQ7WH-CazRIZg4.js} +1 -1
  30. package/web/dist/assets/{diagram-OA4YK3LP-BcQiRMrv.js → diagram-OA4YK3LP-gZanfxjg.js} +1 -1
  31. package/web/dist/assets/{diagram-WEI45ONY-PB48iGxu.js → diagram-WEI45ONY-CHoHyTz-.js} +1 -1
  32. package/web/dist/assets/{ebnfDiagram-CCIWWBDH-Cl3gvgL7.js → ebnfDiagram-CCIWWBDH-CeHz2Jh9.js} +1 -1
  33. package/web/dist/assets/{erDiagram-Q63AITRT-BqTsMK1r.js → erDiagram-Q63AITRT-C95MErio.js} +1 -1
  34. package/web/dist/assets/{flowDiagram-23GEKE2U-Defo8220.js → flowDiagram-23GEKE2U-AdgXiTao.js} +1 -1
  35. package/web/dist/assets/{ganttDiagram-NO4QXBWP-BNS92EuV.js → ganttDiagram-NO4QXBWP-D7Ltt8Pp.js} +1 -1
  36. package/web/dist/assets/{gitGraphDiagram-IHSO6WYX-BY0WUkLV.js → gitGraphDiagram-IHSO6WYX-9Xi-xmei.js} +1 -1
  37. package/web/dist/assets/index-C7_15DMS.css +1 -0
  38. package/web/dist/assets/index-IxnOJbWb.js +24 -0
  39. package/web/dist/assets/{infoDiagram-FWYZ7A6U-DAslesLk.js → infoDiagram-FWYZ7A6U-fRlxZHtD.js} +1 -1
  40. package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-DRqAlMhQ.js → ishikawaDiagram-FXEZZL3T-CEVSVKfj.js} +1 -1
  41. package/web/dist/assets/{journeyDiagram-5HDEW3XC-cZgLClOz.js → journeyDiagram-5HDEW3XC-CJhad1Ay.js} +1 -1
  42. package/web/dist/assets/{kanban-definition-HUTT4EX6-CE-eXw4K.js → kanban-definition-HUTT4EX6-CE6OFSTa.js} +1 -1
  43. package/web/dist/assets/{linear-CZiOQoKS.js → linear-BuexMwfW.js} +1 -1
  44. package/web/dist/assets/{mermaid.core-AH4mlith.js → mermaid.core-DWYsDHXh.js} +4 -4
  45. package/web/dist/assets/{mindmap-definition-LN4V7U3C-BNghnDqd.js → mindmap-definition-LN4V7U3C-6_lFebqE.js} +1 -1
  46. package/web/dist/assets/{papaparse.min-DVijrOyx.js → papaparse.min-DjlrGi59.js} +1 -1
  47. package/web/dist/assets/{pegDiagram-2B236MQR-DoRp-9Zp.js → pegDiagram-2B236MQR-B1fuWjDp.js} +1 -1
  48. package/web/dist/assets/{pieDiagram-ENE6RG2P-H2Em8U8J.js → pieDiagram-ENE6RG2P-BWsDDxls.js} +1 -1
  49. package/web/dist/assets/pilotswarm-3GsGwA3r.js +109 -0
  50. package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-CvUqxqnx.js → quadrantDiagram-ABIIQ3AL-CxzBQxBX.js} +1 -1
  51. package/web/dist/assets/{railroadDiagram-RFXS5EU6-tQwCHxpV.js → railroadDiagram-RFXS5EU6-Coqs5FXi.js} +1 -1
  52. package/web/dist/assets/{requirementDiagram-TGXJPOKE-D2wuvVrI.js → requirementDiagram-TGXJPOKE-v1GaofBK.js} +1 -1
  53. package/web/dist/assets/{sankeyDiagram-HTMAVEWB-C4r0hO5k.js → sankeyDiagram-HTMAVEWB-DGaKjdrZ.js} +1 -1
  54. package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-DCdGwm9a.js → sequenceDiagram-DBY2YBRQ-UD5oqT-Q.js} +1 -1
  55. package/web/dist/assets/{sizeCapture-X5ZJPWSS-BjRhMoeP.js → sizeCapture-X5ZJPWSS-ClCMN9Ry.js} +1 -1
  56. package/web/dist/assets/{stateDiagram-2N3HPSRC-biMUw9ek.js → stateDiagram-2N3HPSRC-CU96VzpK.js} +1 -1
  57. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-TNMXnnTc.js +1 -0
  58. package/web/dist/assets/{swimlanes-5IMT3BWC-BItVHm4_.js → swimlanes-5IMT3BWC-5pw00Cr8.js} +2 -2
  59. package/web/dist/assets/swimlanesDiagram-G3AALYLV-BOvR3DN_.js +8 -0
  60. package/web/dist/assets/{timeline-definition-FHXFAJF6-CC39ef73.js → timeline-definition-FHXFAJF6-Dfd6fPWH.js} +1 -1
  61. package/web/dist/assets/{vennDiagram-L72KCM5P-BgmPqz-h.js → vennDiagram-L72KCM5P-CaJB0ZOh.js} +1 -1
  62. package/web/dist/assets/{wardleyDiagram-EHGQE667-DRNFO87H.js → wardleyDiagram-EHGQE667-Cau_Q6Bk.js} +1 -1
  63. package/web/dist/assets/{xychartDiagram-FW5EYKEG-D_VfxQSu.js → xychartDiagram-FW5EYKEG-DTUEy024.js} +1 -1
  64. package/web/dist/index.html +3 -3
  65. package/web/dist/assets/channel-g-BbN7P5.js +0 -1
  66. package/web/dist/assets/classDiagram-OUVF2IWQ-BFAQi-H9.js +0 -1
  67. package/web/dist/assets/classDiagram-v2-EOCWNBFH-BFAQi-H9.js +0 -1
  68. package/web/dist/assets/index-CSMHeAhZ.css +0 -1
  69. package/web/dist/assets/index-Q9Y44qRk.js +0 -24
  70. package/web/dist/assets/pilotswarm-CN9fLEV0.js +0 -109
  71. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-Dv--LJTg.js +0 -1
  72. package/web/dist/assets/swimlanesDiagram-G3AALYLV-CEULUMiK.js +0 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pilotswarm",
3
- "version": "0.5.76",
3
+ "version": "0.5.78",
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.76",
84
+ "pilotswarm-sdk": "0.5.78",
85
85
  "react": "^19.2.4",
86
86
  "react-dom": "^19.2.4",
87
87
  "ws": "^8.18.2"
@@ -44,6 +44,9 @@ export async function startEmbeddedWorkers({ count, store }) {
44
44
  useManagedIdentity: ["1", "true", "yes", "on"].includes(
45
45
  (process.env.PILOTSWARM_USE_MANAGED_IDENTITY || "").trim().toLowerCase(),
46
46
  ),
47
+ blobUseManagedIdentity: process.env.PILOTSWARM_BLOB_USE_MANAGED_IDENTITY?.trim()
48
+ ? ["1", "true", "yes", "on"].includes(process.env.PILOTSWARM_BLOB_USE_MANAGED_IDENTITY.trim().toLowerCase())
49
+ : undefined,
47
50
  cmsFactsDatabaseUrl: process.env.PILOTSWARM_CMS_FACTS_DATABASE_URL || undefined,
48
51
  aadDbUser: process.env.PILOTSWARM_DB_AAD_USER || undefined,
49
52
  blobAccountUrl: process.env.AZURE_STORAGE_ACCOUNT_URL || undefined,
@@ -9698,6 +9698,7 @@ export class PilotSwarmUiController {
9698
9698
  await this.maybeAutoExpandActiveHistory(requestedScrollOffset, {
9699
9699
  pages: AUTO_HISTORY_SCROLL_PAGE_COUNT,
9700
9700
  preserveDomAnchor: options.preserveDomAnchor === true,
9701
+ onLoadStarted: options.onLoadStarted,
9701
9702
  // Always bypass the offset gate. Every caller of this method fires
9702
9703
  // only when the DOM scroller is already AT the top — a direct
9703
9704
  // measurement. The gate is a second, weaker guess that compares a
@@ -10035,7 +10036,7 @@ export class PilotSwarmUiController {
10035
10036
  if (targetOffset < maxOffset) return;
10036
10037
  }
10037
10038
 
10038
- await this.expandSessionHistory(sessionId, {
10039
+ const load = this.expandSessionHistory(sessionId, {
10039
10040
  requestedScrollOffset: targetOffset,
10040
10041
  autoTriggered: true,
10041
10042
  pages: options.pages,
@@ -10044,6 +10045,8 @@ export class PilotSwarmUiController {
10044
10045
  // only, so each page is transcript instead of raw event noise.
10045
10046
  eventTypes: CHAT_HISTORY_EVENT_TYPES,
10046
10047
  });
10048
+ options.onLoadStarted?.();
10049
+ await load;
10047
10050
  }
10048
10051
 
10049
10052
  async expandSessionHistory(sessionId, options = {}) {
@@ -94,7 +94,8 @@ const SCROLL_BOTTOM_EPSILON_PX = 0.5;
94
94
  const PROGRAMMATIC_SCROLL_TOLERANCE_PX = SCROLL_BOTTOM_EPSILON_PX;
95
95
  // Minimum downward finger travel (px) while at the top of the chat pane before
96
96
  // a touch pull counts as a load-older-history request.
97
- const TOUCH_TOP_PULL_THRESHOLD_PX = 24;
97
+ const TOUCH_TOP_PULL_THRESHOLD_PX = 56;
98
+ const TOUCH_TOP_PULL_MAX_PX = 84;
98
99
  // How long after the last user-driven scroll event the pane still counts as
99
100
  // momentum-scrolling (native flick glide), during which programmatic scrollTop
100
101
  // restores are suppressed so they don't kill the glide.
@@ -1107,7 +1108,24 @@ function scrollBoundaryIdentity(lines, fromEnd = false) {
1107
1108
  return boundary.map(scrollLineIdentity).join("\u241e");
1108
1109
  }
1109
1110
 
1110
- function useScrollSync(ref, lines, scrollOffset, scrollMode, paneKey, controller, { stickyBottom = false } = {}) {
1111
+ // History loading belongs to the outer transcript. Let a tool result, activity
1112
+ // log or agent update consume the gesture first when it still has room.
1113
+ function nestedViewportCanScroll(viewport, target, deltaY) {
1114
+ if (!deltaY) return false;
1115
+ for (let node = target?.nodeType === 1 ? target : target?.parentElement;
1116
+ node && node !== viewport; node = node.parentElement) {
1117
+ const maxScroll = node.scrollHeight - node.clientHeight;
1118
+ if (maxScroll <= 1 || !/^(auto|scroll)$/.test(getComputedStyle(node).overflowY)) continue;
1119
+ if (deltaY < 0 ? node.scrollTop > 1 : node.scrollTop < maxScroll - 1) return true;
1120
+ }
1121
+ return false;
1122
+ }
1123
+
1124
+ function useScrollSync(ref, lines, scrollOffset, scrollMode, paneKey, controller, {
1125
+ stickyBottom = false,
1126
+ historyPullEnabled = false,
1127
+ historySessionId = null,
1128
+ } = {}) {
1111
1129
  const normalizedLines = React.useMemo(() => normalizeLines(lines), [lines]);
1112
1130
  // Programmatic scrollTop assignments fire a 'scroll' event that would
1113
1131
  // otherwise call onScroll → dispatch ui/scroll → clobber the user's
@@ -1122,6 +1140,9 @@ function useScrollSync(ref, lines, scrollOffset, scrollMode, paneKey, controller
1122
1140
  const previousContentRef = React.useRef(null);
1123
1141
  const historyScrollRef = React.useRef(null);
1124
1142
  React.useEffect(() => () => { historyScrollRef.current = null; }, []);
1143
+ const [topPullDistance, setTopPullDistance] = React.useState(0);
1144
+ const [loadingHistorySessionId, setLoadingHistorySessionId] = React.useState(null);
1145
+ React.useEffect(() => { setTopPullDistance(0); }, [historySessionId]);
1125
1146
  // Touch scrolling relies on native momentum for speed: a hard flick keeps
1126
1147
  // scrolling long after the finger lifts. Re-asserting scrollTop from state
1127
1148
  // on every render (live events, status updates) kills that momentum at the
@@ -1309,7 +1330,7 @@ function useScrollSync(ref, lines, scrollOffset, scrollMode, paneKey, controller
1309
1330
  const node = ref.current;
1310
1331
  const state = controller.getState();
1311
1332
  const sessionId = state.sessions.activeSessionId;
1312
- if (!node || !sessionId || historyScrollRef.current) return;
1333
+ if (!node || !sessionId || !historyPullEnabled || historyScrollRef.current) return;
1313
1334
  const top = node.getBoundingClientRect().top;
1314
1335
  const element = [...node.children].find(child => child.textContent?.trim() && child.getBoundingClientRect().bottom > top + 6);
1315
1336
  const pending = {
@@ -1320,19 +1341,22 @@ function useScrollSync(ref, lines, scrollOffset, scrollMode, paneKey, controller
1320
1341
  historyScrollRef.current = pending;
1321
1342
  const load = controller.handleChatTopHistoryScrollIntent?.(
1322
1343
  (node.scrollHeight - node.clientHeight) / SCROLL_ROW_HEIGHT,
1323
- { force: true, preserveDomAnchor: true },
1344
+ { force: true, preserveDomAnchor: true,
1345
+ onLoadStarted: () => setLoadingHistorySessionId(sessionId) },
1324
1346
  );
1325
1347
  Promise.resolve(load).catch(() => {}).finally(() => {
1348
+ setLoadingHistorySessionId(current => current === sessionId ? null : current);
1326
1349
  if (historyScrollRef.current !== pending) return;
1327
1350
  const oldest = controller.getState().history.bySessionId.get(sessionId)?.events?.[0]?.seq;
1328
1351
  if (!(oldest < pending.oldestSeq)) historyScrollRef.current = null;
1329
1352
  else setViewportRevision(revision => revision + 1);
1330
1353
  });
1331
- }, [controller, ref]);
1354
+ }, [controller, historyPullEnabled, ref]);
1332
1355
 
1333
1356
  const onWheel = React.useCallback((event) => {
1334
1357
  const node = ref.current;
1335
1358
  if (!node || paneKey !== "chat" || !event.deltaY || event.ctrlKey) return;
1359
+ if (nestedViewportCanScroll(node, event.target, event.deltaY)) return;
1336
1360
  const pending = historyScrollRef.current;
1337
1361
  if (pending && event.deltaY > 0) historyScrollRef.current = null;
1338
1362
  else if (pending) {
@@ -1354,13 +1378,19 @@ function useScrollSync(ref, lines, scrollOffset, scrollMode, paneKey, controller
1354
1378
  // scroll events either — so mobile had no way to request older history.
1355
1379
  // A downward pull that starts while the pane is at (or near) the top fires
1356
1380
  // the same top-history intent, once per gesture.
1357
- const touchPullRef = React.useRef({ startY: null, fired: false });
1381
+ const touchPullRef = React.useRef({ startY: null, lastY: null, distance: 0 });
1358
1382
  const onTouchStart = React.useCallback((event) => {
1359
1383
  userScrollRef.current.touching = true;
1360
1384
  if (historyScrollRef.current?.applied) historyScrollRef.current = null;
1361
- if (paneKey !== "chat") return;
1362
- touchPullRef.current = { startY: event.touches?.[0]?.clientY ?? null, fired: false };
1363
- }, [paneKey]);
1385
+ const node = ref.current;
1386
+ touchPullRef.current = {
1387
+ startY: event.touches?.length === 1 && historyPullEnabled && loadingHistorySessionId !== historySessionId
1388
+ && paneKey === "chat" && node?.scrollTop <= PROGRAMMATIC_SCROLL_TOLERANCE_PX
1389
+ ? event.touches?.[0]?.clientY ?? null : null,
1390
+ lastY: event.touches?.[0]?.clientY ?? null,
1391
+ distance: 0,
1392
+ };
1393
+ }, [historyPullEnabled, historySessionId, loadingHistorySessionId, paneKey, ref]);
1364
1394
  const onTouchEnd = React.useCallback(() => {
1365
1395
  userScrollRef.current.touching = false;
1366
1396
  // Momentum continues past the finger lift; onScroll keeps refreshing
@@ -1369,27 +1399,42 @@ function useScrollSync(ref, lines, scrollOffset, scrollMode, paneKey, controller
1369
1399
  if (historyScrollRef.current?.applied) {
1370
1400
  historyScrollRef.current.releaseAt = performance.now() + HISTORY_SCROLL_GESTURE_GAP_MS;
1371
1401
  }
1402
+ const distance = touchPullRef.current.distance;
1403
+ touchPullRef.current = { startY: null, lastY: null, distance: 0 };
1404
+ setTopPullDistance(0);
1405
+ if (distance >= TOUCH_TOP_PULL_THRESHOLD_PX
1406
+ && ref.current?.scrollTop <= PROGRAMMATIC_SCROLL_TOLERANCE_PX) requestHistoryPage();
1407
+ }, [ref, requestHistoryPage]);
1408
+ const onTouchCancel = React.useCallback(() => {
1409
+ userScrollRef.current.touching = false;
1410
+ touchPullRef.current = { startY: null, lastY: null, distance: 0 };
1411
+ setTopPullDistance(0);
1372
1412
  }, []);
1373
1413
  const onTouchMove = React.useCallback((event) => {
1374
1414
  const node = ref.current;
1375
1415
  const pull = touchPullRef.current;
1376
1416
  if (!node || paneKey !== "chat" || event.touches?.length !== 1) return;
1417
+ const y = event.touches[0].clientY;
1418
+ const deltaY = pull.lastY == null ? 0 : pull.lastY - y;
1419
+ pull.lastY = y;
1420
+ if (nestedViewportCanScroll(node, event.target, deltaY)) {
1421
+ // Movement within a box must not accumulate into a history pull.
1422
+ if (pull.startY != null) pull.startY = y;
1423
+ pull.distance = 0;
1424
+ setTopPullDistance(0);
1425
+ return;
1426
+ }
1377
1427
  if (historyScrollRef.current) {
1378
1428
  historyScrollRef.current.releaseAt = performance.now() + HISTORY_SCROLL_GESTURE_GAP_MS;
1379
1429
  event.preventDefault();
1380
1430
  return;
1381
1431
  }
1382
- if (pull.fired || pull.startY == null) return;
1432
+ if (pull.startY == null) return;
1383
1433
  if (node.scrollTop > PROGRAMMATIC_SCROLL_TOLERANCE_PX) return;
1384
- const y = event.touches?.[0]?.clientY;
1385
- if (y == null || y - pull.startY < TOUCH_TOP_PULL_THRESHOLD_PX) return;
1386
- pull.fired = true;
1387
- // A deliberate pull at the top of the pane is an unambiguous request:
1388
- // Request one page at the measured DOM boundary. The shared gesture
1389
- // guard absorbs repeated wheel events and the remainder of this pull.
1390
- event.preventDefault();
1391
- requestHistoryPage();
1392
- }, [paneKey, ref, requestHistoryPage]);
1434
+ pull.distance = Math.min(TOUCH_TOP_PULL_MAX_PX, Math.max(0, y - pull.startY));
1435
+ setTopPullDistance(pull.distance);
1436
+ if (pull.distance > 0) event.preventDefault();
1437
+ }, [paneKey, ref]);
1393
1438
 
1394
1439
  React.useEffect(() => {
1395
1440
  const node = ref.current;
@@ -1404,7 +1449,8 @@ function useScrollSync(ref, lines, scrollOffset, scrollMode, paneKey, controller
1404
1449
  };
1405
1450
  }, [ref, onWheel, onTouchMove]);
1406
1451
 
1407
- return { normalizedLines, onScroll, onTouchStart, onTouchEnd };
1452
+ return { normalizedLines, onScroll, onTouchStart, onTouchEnd, onTouchCancel,
1453
+ topPullDistance, loadingOlderHistory: loadingHistorySessionId === historySessionId };
1408
1454
  }
1409
1455
 
1410
1456
  function Runs({ runs, theme }) {
@@ -1529,15 +1575,11 @@ function ChatActivityRun({ calls }) {
1529
1575
  followRef.current = getScrollDistanceToBottom(event.currentTarget) <= 24;
1530
1576
  },
1531
1577
  onWheel: (event) => {
1532
- event.stopPropagation();
1533
1578
  if (event.deltaY < 0) followRef.current = false;
1534
1579
  },
1535
- onTouchStart: (event) => {
1536
- event.stopPropagation();
1580
+ onTouchStart: () => {
1537
1581
  followRef.current = false;
1538
1582
  },
1539
- onTouchMove: (event) => event.stopPropagation(),
1540
- onTouchEnd: (event) => event.stopPropagation(),
1541
1583
  }, calls.map((call) => React.createElement(ChatCallLine, { key: call.callKey, line: call }))),
1542
1584
  React.createElement("div", { className: "ps-activity-run-footer" },
1543
1585
  `Showing ${calls.length} ${calls.length === 1 ? "entry" : "entries"} · Scroll for earlier activity`));
@@ -3785,7 +3827,9 @@ const PreviewMessageContent = React.memo(function PreviewMessageContent({ conten
3785
3827
  });
3786
3828
 
3787
3829
  const AssistantPreviewCard = React.memo(function AssistantPreviewCard({ line, theme, controller }) {
3788
- const [open, setOpen] = React.useState(false);
3830
+ // Saved updates should be readable immediately. The viewport below keeps
3831
+ // long updates scrollable without taking over the whole chat pane.
3832
+ const [open, setOpen] = React.useState(line.text === "Agent update");
3789
3833
  const [revealed, setRevealed] = React.useState(
3790
3834
  !line.liveStartedAt || Date.now() - line.liveStartedAt >= 200,
3791
3835
  );
@@ -3794,6 +3838,10 @@ const AssistantPreviewCard = React.memo(function AssistantPreviewCard({ line, th
3794
3838
  const followRef = React.useRef(true);
3795
3839
  const snapshotRef = React.useRef(null);
3796
3840
  const expanded = open || line.final;
3841
+ React.useLayoutEffect(() => {
3842
+ // A live preview can become a saved update in the same card.
3843
+ if (line.text === "Agent update") setOpen(true);
3844
+ }, [line.text]);
3797
3845
  // Keep already-mounted markdown/disclosures when closed, but don't parse
3798
3846
  // new hidden deltas. Reopening catches up in a single render.
3799
3847
  if (expanded) snapshotRef.current = line;
@@ -3856,15 +3904,12 @@ const AssistantPreviewCard = React.memo(function AssistantPreviewCard({ line, th
3856
3904
  },
3857
3905
  onWheel: (event) => {
3858
3906
  if (!line.final) {
3859
- event.stopPropagation();
3860
3907
  if (event.deltaY < 0) followRef.current = false;
3861
3908
  }
3862
3909
  },
3863
- onTouchStart: (event) => {
3864
- if (!line.final) { event.stopPropagation(); followRef.current = false; }
3910
+ onTouchStart: () => {
3911
+ if (!line.final) followRef.current = false;
3865
3912
  },
3866
- onTouchMove: (event) => { if (!line.final) event.stopPropagation(); },
3867
- onTouchEnd: (event) => { if (!line.final) event.stopPropagation(); },
3868
3913
  onKeyDown: (event) => {
3869
3914
  if (!line.final && ["ArrowUp", "ArrowDown", "PageUp", "PageDown", "Home", "End", " "].includes(event.key)) {
3870
3915
  event.stopPropagation();
@@ -4224,7 +4269,7 @@ function focusRegionForPaneKey(paneKey, override = null) {
4224
4269
  return PANE_KEY_FOCUS_REGIONS[String(paneKey || "")] || null;
4225
4270
  }
4226
4271
 
4227
- function ScrollLinesPanel({ title, titleRight = null, color, focused, actions, lines, stickyLines = [], bottomStickyLines = [], reserveBottomSticky = false, scrollOffset = 0, scrollMode = "top", paneKey, controller, className = "", panelClassName = "", topContent = null, bottomContent = null, structuredBlocks = false, stickyBottom = false, renderBody = null, focusRegion = null, panelRef = null, ariaLive = null }) {
4272
+ function ScrollLinesPanel({ title, titleRight = null, color, focused, actions, lines, stickyLines = [], bottomStickyLines = [], reserveBottomSticky = false, scrollOffset = 0, scrollMode = "top", paneKey, controller, className = "", panelClassName = "", topContent = null, bottomContent = null, structuredBlocks = false, stickyBottom = false, historyPullEnabled = false, historySessionId = null, renderBody = null, focusRegion = null, panelRef = null, ariaLive = null }) {
4228
4273
  const themeId = useControllerSelector(controller, (state) => state.ui.themeId);
4229
4274
  const theme = getTheme(themeId);
4230
4275
  const ref = React.useRef(null);
@@ -4237,7 +4282,7 @@ function ScrollLinesPanel({ title, titleRight = null, color, focused, actions, l
4237
4282
  }, [panelRef]);
4238
4283
  const stickyRef = React.useRef(null);
4239
4284
  const syncingHorizontalRef = React.useRef(false);
4240
- const { normalizedLines, onScroll, onTouchStart, onTouchEnd } = useScrollSync(ref, lines, scrollOffset, scrollMode, paneKey, controller, { stickyBottom });
4285
+ const { normalizedLines, onScroll, onTouchStart, onTouchEnd, onTouchCancel, topPullDistance, loadingOlderHistory } = useScrollSync(ref, lines, scrollOffset, scrollMode, paneKey, controller, { stickyBottom, historyPullEnabled, historySessionId });
4241
4286
  const normalizedSticky = React.useMemo(() => normalizeLines(stickyLines), [stickyLines]);
4242
4287
  const normalizedBottomSticky = React.useMemo(() => normalizeLines(bottomStickyLines), [bottomStickyLines]);
4243
4288
  const preserveHorizontalScroll = className.includes("is-preserve") && panelClassName.includes("has-preserved-sticky");
@@ -4326,7 +4371,7 @@ function ScrollLinesPanel({ title, titleRight = null, color, focused, actions, l
4326
4371
  }, [preserveHorizontalScroll, syncScrollLeft]);
4327
4372
 
4328
4373
  return React.createElement(Panel, { title, titleRight, color, focused, actions, theme, className: panelClassName },
4329
- topContent,
4374
+ typeof topContent === "function" ? topContent({ topPullDistance, loadingOlderHistory }) : topContent,
4330
4375
  normalizedSticky.length > 0
4331
4376
  ? React.createElement("div", {
4332
4377
  ref: stickyRef,
@@ -4336,7 +4381,7 @@ function ScrollLinesPanel({ title, titleRight = null, color, focused, actions, l
4336
4381
  normalizedSticky.map((line, index) => React.createElement(Line, { key: `sticky:${index}`, line, theme })),
4337
4382
  )
4338
4383
  : null,
4339
- React.createElement("div", { ref: setPanelNode, className: `ps-scroll-panel ${className}${scrollShadow.down ? " is-scrolled-down" : ""}${scrollShadow.up ? " is-scrolled-up" : ""}`.trim(), "data-session-scroll": focusRegion === "sessions" ? "1" : undefined, "aria-live": ariaLive || undefined, "aria-atomic": ariaLive ? "false" : undefined, onScroll: handleBodyScroll, onMouseDown: claimFocus, onTouchStart, onTouchEnd, onTouchCancel: onTouchEnd },
4384
+ React.createElement("div", { ref: setPanelNode, className: `ps-scroll-panel ${className}${scrollShadow.down ? " is-scrolled-down" : ""}${scrollShadow.up ? " is-scrolled-up" : ""}`.trim(), "data-session-scroll": focusRegion === "sessions" ? "1" : undefined, "aria-live": ariaLive || undefined, "aria-atomic": ariaLive ? "false" : undefined, onScroll: handleBodyScroll, onMouseDown: claimFocus, onTouchStart, onTouchEnd, onTouchCancel },
4340
4385
  typeof renderBody === "function"
4341
4386
  ? renderBody(normalizedLines, theme)
4342
4387
  : structuredBlocks
@@ -6898,6 +6943,39 @@ function SessionHeaderStatus({ controller }) {
6898
6943
  queue ? React.createElement("span", { className: "ps-mobile-queue", title: queue }, queue) : null);
6899
6944
  }
6900
6945
 
6946
+ function HistoryUpGlyph() {
6947
+ return React.createElement(Glyph, null,
6948
+ React.createElement("path", { d: "M12 19V5m-5 5 5-5 5 5" }));
6949
+ }
6950
+
6951
+ function HistorySpinnerGlyph() {
6952
+ return React.createElement(Glyph, null,
6953
+ React.createElement("path", { d: "M20.5 12a8.5 8.5 0 1 1-8.5-8.5" }));
6954
+ }
6955
+
6956
+ function HistoryLoadIndicator({ loading, manual, pullDistance, onManualLoad }) {
6957
+ const ready = pullDistance >= TOUCH_TOP_PULL_THRESHOLD_PX;
6958
+ const label = loading ? "Loading earlier messages…"
6959
+ : manual ? "Load older messages"
6960
+ : ready ? "Release to load earlier messages"
6961
+ : "Pull for earlier messages";
6962
+ return React.createElement("div", {
6963
+ className: `ps-history-load${loading ? " is-loading" : ""}${manual ? " is-manual" : ""}${ready ? " is-ready" : ""}`,
6964
+ style: pullDistance > 0 ? { "--ps-history-pull": `${Math.min(pullDistance, TOUCH_TOP_PULL_MAX_PX)}px` } : undefined,
6965
+ role: "status", "aria-live": "polite",
6966
+ },
6967
+ React.createElement("span", { className: "ps-history-track", "aria-hidden": "true" },
6968
+ React.createElement("span", { className: "ps-history-tick is-first" }),
6969
+ React.createElement("span", { className: "ps-history-tick is-second" }),
6970
+ React.createElement("span", { className: "ps-history-marker" },
6971
+ loading ? React.createElement(HistorySpinnerGlyph) : React.createElement(HistoryUpGlyph),
6972
+ loading ? React.createElement("span", { className: "ps-history-text-spinner" },
6973
+ ["|", "/", "—", "\\"].map((frame, index) => React.createElement("span", { key: index }, frame))) : null)),
6974
+ manual && !loading
6975
+ ? React.createElement("button", { type: "button", className: "ps-mini-button ps-load-older-button", onClick: onManualLoad }, label)
6976
+ : React.createElement("span", { className: "ps-history-label" }, label));
6977
+ }
6978
+
6901
6979
  function ChatPane({ controller, mobile = false, fullWidth = false, showComposer = true, onEnterZen = null, activityInHeader = false }) {
6902
6980
  const themeId = useControllerSelector(controller, (state) => state.ui.themeId);
6903
6981
  const theme = getTheme(themeId);
@@ -6980,7 +7058,7 @@ function ChatPane({ controller, mobile = false, fullWidth = false, showComposer
6980
7058
  () => appendAnimatedDotsToRuns(chrome.titleRight, chrome.animateTitleRight ? animatedDots : ""),
6981
7059
  [animatedDots, chrome.animateTitleRight, chrome.titleRight],
6982
7060
  );
6983
- const [loadingOlder, setLoadingOlder] = React.useState(false);
7061
+ const [manualLoadingSessionId, setManualLoadingSessionId] = React.useState(null);
6984
7062
  // Scroll-up expands the transcript automatically until the soft cap, then
6985
7063
  // refuses — and the portal had no control to ask for more, so a busy
6986
7064
  // session's history became unreachable from the browser. Surface the
@@ -7079,25 +7157,27 @@ function ChatPane({ controller, mobile = false, fullWidth = false, showComposer
7079
7157
  scrollMode: viewState.followBottom ? "bottom" : "top",
7080
7158
  stickyBottom: true,
7081
7159
  paneKey: "chat",
7160
+ historySessionId: viewState.activeSessionId,
7161
+ historyPullEnabled: Boolean(viewState.activeHistory?.hasOlderEvents && !showLoadOlder),
7082
7162
  ariaLive: "polite",
7083
7163
  className: "is-wrapped",
7084
7164
  panelClassName: "ps-chat-panel",
7085
7165
  panelRef,
7086
7166
  bottomContent: composer,
7087
- topContent: showLoadOlder
7088
- ? React.createElement("div", { className: "ps-load-older-bar" },
7089
- React.createElement("button", {
7090
- type: "button",
7091
- className: "ps-load-older-button",
7092
- disabled: loadingOlder,
7093
- onClick: () => {
7094
- setLoadingOlder(true);
7095
- controller.handleCommand(UI_COMMANDS.EXPAND_HISTORY)
7096
- .catch(() => {})
7097
- .finally(() => setLoadingOlder(false));
7098
- },
7099
- }, loadingOlder ? "Loading older messages…" : "↑ Load older messages"))
7100
- : null,
7167
+ topContent: ({ topPullDistance, loadingOlderHistory }) => {
7168
+ const loading = loadingOlderHistory || manualLoadingSessionId === viewState.activeSessionId;
7169
+ if (!showLoadOlder && topPullDistance <= 0 && !loading) return null;
7170
+ return React.createElement(HistoryLoadIndicator, {
7171
+ loading, manual: showLoadOlder, pullDistance: loading ? 0 : topPullDistance,
7172
+ onManualLoad: () => {
7173
+ const sessionId = viewState.activeSessionId;
7174
+ setManualLoadingSessionId(sessionId);
7175
+ controller.handleCommand(UI_COMMANDS.EXPAND_HISTORY)
7176
+ .catch(() => {})
7177
+ .finally(() => setManualLoadingSessionId(current => current === sessionId ? null : current));
7178
+ },
7179
+ });
7180
+ },
7101
7181
  structuredBlocks: true,
7102
7182
  renderBody: null,
7103
7183
  });
@@ -9254,13 +9334,21 @@ function PromptComposer({ controller, mobile, compact = false, active = true, on
9254
9334
  // A pane-splitter drag or a font-size change re-wraps the text with
9255
9335
  // no window resize; watch the box itself.
9256
9336
  const node = inputRef.current;
9337
+ let frame = 0;
9257
9338
  const observer = node && typeof ResizeObserver !== "undefined"
9258
- ? new ResizeObserver(() => growInput(true))
9339
+ ? new ResizeObserver(() => {
9340
+ // growInput writes the same box's height. Defer those writes
9341
+ // out of ResizeObserver delivery to prevent WebKit's resize
9342
+ // loop error when a narrow pane wraps the placeholder.
9343
+ cancelAnimationFrame(frame);
9344
+ frame = requestAnimationFrame(() => growInput(true));
9345
+ })
9259
9346
  : null;
9260
9347
  if (observer && node) observer.observe(node);
9261
9348
  return () => {
9262
9349
  window.removeEventListener("resize", onResize);
9263
9350
  observer?.disconnect();
9351
+ cancelAnimationFrame(frame);
9264
9352
  };
9265
9353
  }, [growInput]);
9266
9354
 
@@ -9723,7 +9811,7 @@ function IconButton({ icon, label, onClick, disabled = false, active = false, pr
9723
9811
  tooltipNode);
9724
9812
  }
9725
9813
 
9726
- function Toolbar({ controller, mobile, moa = null, canvasPaneOpen = false, onToggleCanvasPane = null, mobilePane = "workspace", onSelectMobilePane = null, mobileMainLayout = "split" }) {
9814
+ function Toolbar({ controller, mobile, moa = null, viewNavigation = null, canvasPaneOpen = false, onToggleCanvasPane = null, mobilePane = "workspace", onSelectMobilePane = null, mobileMainLayout = "split" }) {
9727
9815
  const [headerSlot, setHeaderSlot] = React.useState(null);
9728
9816
  React.useEffect(() => {
9729
9817
  if (typeof document === "undefined") return;
@@ -9877,6 +9965,15 @@ function Toolbar({ controller, mobile, moa = null, canvasPaneOpen = false, onTog
9877
9965
  },
9878
9966
  });
9879
9967
 
9968
+ if (!mobile && moa?.desktop !== false && viewNavigation) for (const direction of ["back", "forward"]) buttonDefs.push({
9969
+ key: direction,
9970
+ icon: React.createElement("svg", { viewBox: "0 0 20 20", width: 16, height: 16, fill: "none", stroke: "currentColor", strokeWidth: 1.7, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true },
9971
+ React.createElement("path", { d: direction === "back" ? "M16 10H4m5-5-5 5 5 5" : "M4 10h12m-5-5 5 5-5 5" })),
9972
+ label: viewNavigation[`${direction}Label`],
9973
+ disabled: !viewNavigation[direction === "back" ? "canBack" : "canForward"],
9974
+ onClick: viewNavigation[direction],
9975
+ });
9976
+
9880
9977
  const renderButton = (def) => {
9881
9978
  const button = React.createElement(IconButton, {
9882
9979
  key: def.badge ? undefined : def.key,
@@ -9945,18 +10042,11 @@ function Toolbar({ controller, mobile, moa = null, canvasPaneOpen = false, onTog
9945
10042
  );
9946
10043
  }
9947
10044
 
9948
- // Two kinds of button, two clusters (desktop only):
9949
- //
9950
- // left : the workspace's — new, filter, canvas, diagnostics
9951
- // right : modes [workspace, budget, admin] │ theme · sign-out
9952
- //
9953
- // The whole left cluster belongs to the workspace — new session, filter,
9954
- // canvas and diagnostics all act on it — so it exists only in Workspace
9955
- // mode. Budget and the Admin Console replace the workspace; nothing on
9956
- // the left applies there. Modes are exclusive (the controller closes one
9957
- // when the other opens) and the Workspace button is the way back.
10045
+ // Keep navigation in one permanent slot in every desktop mode. Context
10046
+ // controls occupy equal-width flanks; empty flanks still reserve space.
9958
10047
  const mode = moa?.active ? "moa" : adminVisible ? "admin" : (budgetOpen ? "budget" : "workspace");
9959
10048
  const ACTIONS = ["new", "filter"];
10049
+ const HISTORY = ["back", "forward"];
9960
10050
  const PANELS = ["canvas", "diagnostics"];
9961
10051
  const MODES = ["workspace", "moa", "budget", "admin"];
9962
10052
 
@@ -9966,7 +10056,7 @@ function Toolbar({ controller, mobile, moa = null, canvasPaneOpen = false, onTog
9966
10056
  // it — is the confusing half of every full-screen mode. The Canvas toggle
9967
10057
  // is exempt: it already means "put the canvas away", and closing drops
9968
10058
  // the flag in the reducer.
9969
- const withRestore = (def) => ((!canvasMaximized || def.key === "canvas") && !(moa?.active && ["workspace", "budget", "admin"].includes(def.key)) ? def : {
10059
+ const withRestore = (def) => (HISTORY.includes(def.key) || (!canvasMaximized || def.key === "canvas") && !(moa?.active && ["workspace", "budget", "admin"].includes(def.key)) ? def : {
9970
10060
  ...def,
9971
10061
  onClick: (...args) => {
9972
10062
  controller.dispatch({ type: "ui/canvasMaximized", on: false });
@@ -9981,10 +10071,6 @@ function Toolbar({ controller, mobile, moa = null, canvasPaneOpen = false, onTog
9981
10071
  .map(renderButton);
9982
10072
  const divider = (key) => React.createElement("span", { key, className: "ps-toolbar-divider", "aria-hidden": "true" });
9983
10073
 
9984
- // One run, no bar: all four act on the workspace.
9985
- const leftCluster = mode === "workspace"
9986
- ? [...pick(ACTIONS), ...pick(PANELS)]
9987
- : [];
9988
10074
  const rightCluster = [
9989
10075
  ...pick(MODES),
9990
10076
  divider("mode-divider"),
@@ -9992,28 +10078,21 @@ function Toolbar({ controller, mobile, moa = null, canvasPaneOpen = false, onTog
9992
10078
  ];
9993
10079
 
9994
10080
  const toolbar = React.createElement("div", { className: `ps-toolbar${moa?.active ? " is-moa" : ""}` },
9995
- // Three columns: left rail | main controls | right rail. Full-screen
9996
- // canvas uses the otherwise-empty left rail for the normal actions so
9997
- // they do not crowd the canvas metadata and controls across the top.
9998
10081
  React.createElement("div", { className: "ps-toolbar-side is-left" },
9999
- moa?.active ? React.createElement("div", { id: "ps-moa-status-slot" }) : canvasMaximized ? leftCluster : null),
10000
- React.createElement("div", { className: "ps-toolbar-actions" },
10001
- moa?.active ? React.createElement("div", { id: "ps-moa-header-slot" }) : canvasMaximized ? null : leftCluster),
10082
+ moa?.active ? React.createElement("div", { id: "ps-moa-status-slot" }) : null,
10083
+ // Canvas controls retain their state in CanvasPane. Their portal
10084
+ // gets the flexible rail, so names/revisions cannot move history.
10085
+ canvasMaximized && !moa?.active
10086
+ ? React.createElement("span", { className: "ps-toolbar-canvas-slot", id: "ps-toolbar-canvas-slot" })
10087
+ : null),
10088
+ React.createElement("div", { className: "ps-toolbar-actions ps-toolbar-navigation", role: "navigation", "aria-label": moa?.active ? "Master of Agents" : "View navigation" },
10089
+ React.createElement("div", { className: "ps-toolbar-context is-before", id: "ps-toolbar-before-history" },
10090
+ mode === "workspace" ? pick(ACTIONS) : null),
10091
+ React.createElement("div", { className: "ps-toolbar-history" }, pick(HISTORY)),
10092
+ React.createElement("div", { className: "ps-toolbar-context is-after", id: "ps-toolbar-after-history" },
10093
+ mode === "workspace" ? pick(PANELS) : null)),
10002
10094
  React.createElement("div", { className: "ps-toolbar-side is-right" },
10003
- // The portal header parks its version/status meta here, LEFT of
10004
- // the tool buttons, and its sign-out glyph in the slot after
10005
- // them — one right-aligned cluster: meta · mode · theme · out.
10006
10095
  React.createElement("span", { className: "ps-toolbar-meta-slot", id: "ps-toolbar-meta-slot" }),
10007
- // While the canvas is full screen its header controls (rev, zoom,
10008
- // zen, restore) portal INTO this slot from CanvasPane — zoom state
10009
- // lives there, so the controls come to the toolbar rather than
10010
- // their state moving out.
10011
- canvasMaximized
10012
- ? React.createElement("span", { className: "ps-toolbar-canvas-slot", id: "ps-toolbar-canvas-slot" })
10013
- : null,
10014
- canvasMaximized
10015
- ? React.createElement("span", { className: "ps-toolbar-divider", "aria-hidden": "true" })
10016
- : null,
10017
10096
  React.createElement("div", { className: "ps-toolbar-actions is-tools" }, rightCluster),
10018
10097
  React.createElement("span", { className: "ps-toolbar-signout-slot", id: "ps-toolbar-signout-slot" })),
10019
10098
  );
@@ -14778,7 +14857,7 @@ export function createWebPilotSwarmController({ transport, mode = "remote", bran
14778
14857
  return new PilotSwarmUiController({ store, transport });
14779
14858
  }
14780
14859
 
14781
- export function PilotSwarmWebApp({ controller, suspended = false, moa = null }) {
14860
+ export function PilotSwarmWebApp({ controller, suspended = false, moa = null, viewNavigation = null }) {
14782
14861
  const viewportRef = React.useRef(null);
14783
14862
  const mainGridRef = React.useRef(null);
14784
14863
  const viewport = useMeasuredViewport(viewportRef, suspended);
@@ -14914,7 +14993,9 @@ export function PilotSwarmWebApp({ controller, suspended = false, moa = null })
14914
14993
  const appliedProfileSettingsJsonRef = React.useRef(null);
14915
14994
  const defaultProfileSettingsRef = React.useRef(null);
14916
14995
  const [mobilePane, setMobilePane] = React.useState("workspace");
14917
- const mobile = (viewport.width || window.innerWidth || 0) < MOBILE_BREAKPOINT;
14996
+ // Match the CSS and MoA breakpoint. The padded shell is narrower than
14997
+ // the browser and otherwise flips to mobile while the header is desktop.
14998
+ const mobile = useMediaQuery(`(max-width: ${MOBILE_BREAKPOINT}px)`);
14918
14999
  const readOnlyChatPane = state.activeSessionIsGroup;
14919
15000
  const effectivePromptRows = readOnlyChatPane ? 0 : state.promptRows;
14920
15001
 
@@ -15542,7 +15623,7 @@ export function PilotSwarmWebApp({ controller, suspended = false, moa = null })
15542
15623
  : null;
15543
15624
 
15544
15625
  if (suspended) return React.createElement(ControllerContext.Provider, { value: controller },
15545
- React.createElement(Toolbar, { controller, mobile: false, moa }),
15626
+ React.createElement(Toolbar, { controller, mobile: false, moa, viewNavigation }),
15546
15627
  React.createElement(ModalLayer, { controller }));
15547
15628
  return React.createElement(ControllerContext.Provider, { value: controller },
15548
15629
  React.createElement("div", { ref: viewportRef, className: "ps-web-shell" },
@@ -15550,7 +15631,7 @@ export function PilotSwarmWebApp({ controller, suspended = false, moa = null })
15550
15631
  // plus the Main layout cycle), so it renders on every pane — hiding it
15551
15632
  // anywhere would trap the user with no way back.
15552
15633
  React.createElement(Toolbar, {
15553
- controller, moa,
15634
+ controller, moa, viewNavigation,
15554
15635
  mobile,
15555
15636
  canvasPaneOpen: mobileCanvasOpen,
15556
15637
  onToggleCanvasPane: toggleMobileCanvas,
@@ -1 +1 @@
1
- import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-B-y9e9g1.js";import{p as f}from"./chunk-JWPE2WC7-DY6_m3mf.js";import{_ as n,l as o}from"./mermaid.core-AH4mlith.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-DgXpH0xt.js";import"./pilotswarm-CN9fLEV0.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};
1
+ import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-DuIGV22R.js";import{p as f}from"./chunk-JWPE2WC7-Ddh7J2Rp.js";import{_ as n,l as o}from"./mermaid.core-DWYsDHXh.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-Deleul8f.js";import"./pilotswarm-3GsGwA3r.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};
@@ -1 +1 @@
1
- import{Y as ln,$ as an,a0 as F,a1 as q,a2 as j,a3 as un,a4 as y,a5 as tn,a6 as J,a7 as _,a8 as rn,a9 as o,aa as on,ab as sn,ac as fn}from"./mermaid.core-AH4mlith.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,z,a){var O=I-l,i=D-h,n=z-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function U(l,h,I,D,v,A,z){var a=l-I,O=h-D,i=(z?A:-A)/J(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,B=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*J(on(0,T*T*R-P*P)),Y=(P*g-m*S)/R,$=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=Y-B,e=$-t,r=w-B,C=p-t;return x*x+e*e>r*r+C*C&&(Y=w,$=p),{cx:Y,cy:$,x01:-n,y01:-d,x11:Y*(v/T-1),y11:$*(v/T-1)}}function hn(){var l=cn,h=yn,I=j(0),D=null,v=gn,A=dn,z=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-un,c=A.apply(this,arguments)-un,B=rn(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(B>tn-y)a.moveTo(s*F(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*F(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=B,S=B,Y=z.apply(this,arguments)/2,$=Y>y&&(D?+D.apply(this,arguments):J(u*u+s*s)),w=_(rn(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if($>y){var C=sn($/u*q(Y)),K=sn($/s*q(Y));(P-=C*2)>y?(C*=t?1:-1,R+=C,T-=C):(P=0,R=T=(f+c)/2),(S-=K*2)>y?(K*=t?1:-1,m+=K,g-=K):(S=0,m=g=(f+c)/2)}var G=s*F(m),H=s*q(m),L=u*F(T),M=u*q(T);if(w>y){var N=s*F(g),Q=s*q(g),V=u*F(R),W=u*q(R),E;if(B<an)if(E=pn(G,H,V,W,N,Q,L,M)){var X=G-E[0],Z=H-E[1],b=N-E[0],k=Q-E[1],nn=1/q(fn((X*b+Z*k)/(J(X*X+Z*Z)*J(b*b+k*k)))/2),en=J(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=U(V,W,G,H,s,x,t),r=U(N,Q,L,M,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(G,H),a.arc(0,0,s,m,g,!t)):a.moveTo(G,H),!(u>y)||!(P>y)?a.lineTo(L,M):p>y?(e=U(L,M,N,Q,u,-p,t),r=U(G,H,V,W,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-an/2;return[F(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:j(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:j(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:j(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:j(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:j(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:j(+n),i):A},i.padAngle=function(n){return arguments.length?(z=typeof n=="function"?n:j(+n),i):z},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};
1
+ import{Y as ln,$ as an,a0 as F,a1 as q,a2 as j,a3 as un,a4 as y,a5 as tn,a6 as J,a7 as _,a8 as rn,a9 as o,aa as on,ab as sn,ac as fn}from"./mermaid.core-DWYsDHXh.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,z,a){var O=I-l,i=D-h,n=z-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function U(l,h,I,D,v,A,z){var a=l-I,O=h-D,i=(z?A:-A)/J(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,B=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*J(on(0,T*T*R-P*P)),Y=(P*g-m*S)/R,$=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=Y-B,e=$-t,r=w-B,C=p-t;return x*x+e*e>r*r+C*C&&(Y=w,$=p),{cx:Y,cy:$,x01:-n,y01:-d,x11:Y*(v/T-1),y11:$*(v/T-1)}}function hn(){var l=cn,h=yn,I=j(0),D=null,v=gn,A=dn,z=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-un,c=A.apply(this,arguments)-un,B=rn(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(B>tn-y)a.moveTo(s*F(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*F(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=B,S=B,Y=z.apply(this,arguments)/2,$=Y>y&&(D?+D.apply(this,arguments):J(u*u+s*s)),w=_(rn(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if($>y){var C=sn($/u*q(Y)),K=sn($/s*q(Y));(P-=C*2)>y?(C*=t?1:-1,R+=C,T-=C):(P=0,R=T=(f+c)/2),(S-=K*2)>y?(K*=t?1:-1,m+=K,g-=K):(S=0,m=g=(f+c)/2)}var G=s*F(m),H=s*q(m),L=u*F(T),M=u*q(T);if(w>y){var N=s*F(g),Q=s*q(g),V=u*F(R),W=u*q(R),E;if(B<an)if(E=pn(G,H,V,W,N,Q,L,M)){var X=G-E[0],Z=H-E[1],b=N-E[0],k=Q-E[1],nn=1/q(fn((X*b+Z*k)/(J(X*X+Z*Z)*J(b*b+k*k)))/2),en=J(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=U(V,W,G,H,s,x,t),r=U(N,Q,L,M,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(G,H),a.arc(0,0,s,m,g,!t)):a.moveTo(G,H),!(u>y)||!(P>y)?a.lineTo(L,M):p>y?(e=U(L,M,N,Q,u,-p,t),r=U(G,H,V,W,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-an/2;return[F(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:j(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:j(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:j(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:j(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:j(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:j(+n),i):A},i.padAngle=function(n){return arguments.length?(z=typeof n=="function"?n:j(+n),i):z},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};