pilotswarm 0.5.30 → 0.5.32
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/tui/src/node-sdk-transport.js +4 -0
- package/ui/core/src/controller.js +102 -30
- package/ui/core/src/reducer.js +50 -1
- package/ui/core/src/selectors.js +68 -22
- package/ui/core/src/state.js +2 -0
- package/ui/core/src/themes/doom.js +106 -0
- package/ui/core/src/themes/helpers.js +7 -1
- package/ui/core/src/themes/index.js +4 -0
- package/ui/core/src/themes/winamp.js +104 -0
- package/ui/react/src/components.js +25 -0
- package/ui/react/src/web-app.js +320 -51
- package/web/api/ws.js +7 -0
- package/web/authz.js +14 -81
- package/web/dist/assets/{abnfDiagram-VRR7QNED-Cimc0f-u.js → abnfDiagram-VRR7QNED-mXFQ4a3c.js} +1 -1
- package/web/dist/assets/{arc-D8-_Dx5J.js → arc-BPuuvbge.js} +1 -1
- package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-lzMAuQHJ.js → architectureDiagram-ZJ3FMSHR-Dt4uUYIe.js} +1 -1
- package/web/dist/assets/{blockDiagram-677ZJIJ3-Dp2CWjVS.js → blockDiagram-677ZJIJ3-Ax8Q22vV.js} +1 -1
- package/web/dist/assets/{c4Diagram-LMCZKHZV-DF93UkOy.js → c4Diagram-LMCZKHZV-CPiukyHt.js} +1 -1
- package/web/dist/assets/channel-BCzDctYg.js +1 -0
- package/web/dist/assets/{chunk-2Q5K7J3B-pbsQRM-F.js → chunk-2Q5K7J3B-VJLqCZJX.js} +1 -1
- package/web/dist/assets/{chunk-32BRIVSS-B_f-gG1O.js → chunk-32BRIVSS-DqOshxhf.js} +1 -1
- package/web/dist/assets/{chunk-5VM5RSS4-gqlpJDmt.js → chunk-5VM5RSS4-KPqV21jV.js} +1 -1
- package/web/dist/assets/{chunk-EX3LRPZG-Cms7hPmM.js → chunk-EX3LRPZG-DfhiCVl_.js} +1 -1
- package/web/dist/assets/{chunk-JWPE2WC7-Boq5PtPI.js → chunk-JWPE2WC7-H6khJPuW.js} +1 -1
- package/web/dist/assets/{chunk-MOJQB5TN-DVn9i8zt.js → chunk-MOJQB5TN-BKMAzQxj.js} +1 -1
- package/web/dist/assets/{chunk-RYQCIY6F-a1vKs3UX.js → chunk-RYQCIY6F-DNXRTxtj.js} +1 -1
- package/web/dist/assets/{chunk-V7JOEXUC-i60p5fHd.js → chunk-V7JOEXUC-B56PM6Gv.js} +1 -1
- package/web/dist/assets/{chunk-VR4S4FIN-a8p0o-8B.js → chunk-VR4S4FIN-Boi3hGkB.js} +1 -1
- package/web/dist/assets/{chunk-XXDRQBXY-BwP1yBKd.js → chunk-XXDRQBXY-DmJyn890.js} +1 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-ChqjVZil.js +1 -0
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-ChqjVZil.js +1 -0
- package/web/dist/assets/{cose-bilkent-JH36ORCC-HW6kGgze.js → cose-bilkent-JH36ORCC-BRdFlFWt.js} +1 -1
- package/web/dist/assets/{cynefin-VYW2F7L2-YUFMn-vE.js → cynefin-VYW2F7L2-DqE-xib2.js} +1 -1
- package/web/dist/assets/{cynefinDiagram-TSTJHNR4-Brdy17i4.js → cynefinDiagram-TSTJHNR4-D-QBZjIL.js} +1 -1
- package/web/dist/assets/{dagre-VKFMJZFB-CAiSJJj-.js → dagre-VKFMJZFB-DYu9oQV6.js} +1 -1
- package/web/dist/assets/{diagram-FQU43EPY-B63Cqe0J.js → diagram-FQU43EPY-DOwYccCg.js} +1 -1
- package/web/dist/assets/{diagram-G47NLZAW-ByktrDJ2.js → diagram-G47NLZAW-DTSEmrPr.js} +1 -1
- package/web/dist/assets/{diagram-NH7WQ7WH-BLSfRMoE.js → diagram-NH7WQ7WH-OqbzzquA.js} +1 -1
- package/web/dist/assets/{diagram-OA4YK3LP-DNVu-zpk.js → diagram-OA4YK3LP-BMVG6A8I.js} +1 -1
- package/web/dist/assets/{diagram-WEI45ONY-CNf4Sz-I.js → diagram-WEI45ONY-Rn2Q5xaz.js} +1 -1
- package/web/dist/assets/{ebnfDiagram-CCIWWBDH-9CT3ergx.js → ebnfDiagram-CCIWWBDH-aL32_v5y.js} +1 -1
- package/web/dist/assets/{erDiagram-Q63AITRT-BPJHAMgl.js → erDiagram-Q63AITRT-DKCpyczx.js} +1 -1
- package/web/dist/assets/{flowDiagram-23GEKE2U-BVtpr6Iu.js → flowDiagram-23GEKE2U-CrZTPgR_.js} +1 -1
- package/web/dist/assets/{ganttDiagram-NO4QXBWP-BTHnv9sV.js → ganttDiagram-NO4QXBWP-DnSE1OtC.js} +1 -1
- package/web/dist/assets/{gitGraphDiagram-IHSO6WYX-BfmtqbL5.js → gitGraphDiagram-IHSO6WYX-C5SeYPUR.js} +1 -1
- package/web/dist/assets/{index-Bmb8qK39.js → index-B-TEKrRB.js} +6 -6
- package/web/dist/assets/index-D2B5bzSN.css +1 -0
- package/web/dist/assets/{infoDiagram-FWYZ7A6U-kU4i4HRu.js → infoDiagram-FWYZ7A6U-DDnJZp0X.js} +1 -1
- package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-Ch7i-E8O.js → ishikawaDiagram-FXEZZL3T-ewhzfFkq.js} +1 -1
- package/web/dist/assets/{journeyDiagram-5HDEW3XC-CPQxp0ps.js → journeyDiagram-5HDEW3XC-T0iWWkLM.js} +1 -1
- package/web/dist/assets/{kanban-definition-HUTT4EX6-BBgMVqoJ.js → kanban-definition-HUTT4EX6-D1MFQZjb.js} +1 -1
- package/web/dist/assets/{linear-rwksvzXJ.js → linear-BKfPK9wE.js} +1 -1
- package/web/dist/assets/{mermaid.core-o7PszRUc.js → mermaid.core-CyzD7dTc.js} +4 -4
- package/web/dist/assets/{mindmap-definition-LN4V7U3C-wgOTdzlq.js → mindmap-definition-LN4V7U3C-CWXtcTlP.js} +1 -1
- package/web/dist/assets/{papaparse.min-BRXtjEI6.js → papaparse.min-DLUoV5k5.js} +1 -1
- package/web/dist/assets/{pegDiagram-2B236MQR-BkhQqqTm.js → pegDiagram-2B236MQR-BFjZun-r.js} +1 -1
- package/web/dist/assets/{pieDiagram-ENE6RG2P-CcMnvxUz.js → pieDiagram-ENE6RG2P-DMDtW4g7.js} +1 -1
- package/web/dist/assets/pilotswarm-DA0u7Cq_.js +99 -0
- package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-_KD1QeEr.js → quadrantDiagram-ABIIQ3AL-CAwPVaYL.js} +1 -1
- package/web/dist/assets/{railroadDiagram-RFXS5EU6-CqI0olIe.js → railroadDiagram-RFXS5EU6-Co102PkQ.js} +1 -1
- package/web/dist/assets/{requirementDiagram-TGXJPOKE-1JxRwusF.js → requirementDiagram-TGXJPOKE-D19VlRSl.js} +1 -1
- package/web/dist/assets/{sankeyDiagram-HTMAVEWB-9IpFoKzi.js → sankeyDiagram-HTMAVEWB-Sd_a3ukL.js} +1 -1
- package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-teyObSQ9.js → sequenceDiagram-DBY2YBRQ-CojDRK1c.js} +1 -1
- package/web/dist/assets/{sizeCapture-X5ZJPWSS-DVhU2J_6.js → sizeCapture-X5ZJPWSS-XqXcxnDo.js} +1 -1
- package/web/dist/assets/{stateDiagram-2N3HPSRC-DHPoWpIQ.js → stateDiagram-2N3HPSRC-BcN_w699.js} +1 -1
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-BV_w1D3C.js +1 -0
- package/web/dist/assets/{swimlanes-5IMT3BWC-BxTtDQoB.js → swimlanes-5IMT3BWC-BcO6ai_I.js} +2 -2
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-BnYdP0qP.js +8 -0
- package/web/dist/assets/{timeline-definition-FHXFAJF6-D0wXZx-4.js → timeline-definition-FHXFAJF6-DOPfYxUq.js} +1 -1
- package/web/dist/assets/{vennDiagram-L72KCM5P-BSFyI3iV.js → vennDiagram-L72KCM5P-BSKLkEOC.js} +1 -1
- package/web/dist/assets/{wardleyDiagram-EHGQE667-BN-S7hh0.js → wardleyDiagram-EHGQE667-BdcYAVw2.js} +1 -1
- package/web/dist/assets/{xychartDiagram-FW5EYKEG-nDNuPs7o.js → xychartDiagram-FW5EYKEG-C6EJT4jB.js} +1 -1
- package/web/dist/index.html +3 -3
- package/web/runtime.js +55 -0
- package/web/server.js +7 -0
- package/web/dist/assets/channel-OwWeiCox.js +0 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-CDaP7nuC.js +0 -1
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-CDaP7nuC.js +0 -1
- package/web/dist/assets/index-B37T1c2s.css +0 -1
- package/web/dist/assets/pilotswarm-DYXtzuUN.js +0 -99
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-DJjZWYLS.js +0 -1
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-CV12vDcK.js +0 -8
package/ui/react/src/web-app.js
CHANGED
|
@@ -95,21 +95,23 @@ const INSPECTOR_TAB_LABELS = {
|
|
|
95
95
|
sequence: "Sequence",
|
|
96
96
|
logs: "Logs",
|
|
97
97
|
nodes: "Node Map",
|
|
98
|
-
history: "History",
|
|
98
|
+
history: "Duroxide Event History",
|
|
99
99
|
files: "Files",
|
|
100
100
|
stats: "Stats",
|
|
101
101
|
};
|
|
102
102
|
// Glyphs for the icon-only Inspector tab row (labels move into hover/long-press
|
|
103
103
|
// tooltips via IconButton, matching the session toolbar treatment).
|
|
104
|
-
//
|
|
105
|
-
//
|
|
104
|
+
// COMPONENTS, not codepoints: the text glyphs this row used to carry rendered a
|
|
105
|
+
// stroke-weight lighter than the SVG session-toolbar icons, fell back per
|
|
106
|
+
// platform, and duplicated each other ("≣" was both Logs and the header Summary
|
|
107
|
+
// button). Function declarations hoist, so referencing them up here is fine.
|
|
106
108
|
const INSPECTOR_TAB_ICONS = {
|
|
107
|
-
sequence:
|
|
108
|
-
logs:
|
|
109
|
-
nodes:
|
|
110
|
-
history:
|
|
111
|
-
files:
|
|
112
|
-
stats:
|
|
109
|
+
sequence: SequenceGlyph,
|
|
110
|
+
logs: LogsGlyph,
|
|
111
|
+
nodes: NodeMapGlyph,
|
|
112
|
+
history: DuroxideGlyph,
|
|
113
|
+
files: FilesGlyph,
|
|
114
|
+
stats: StatsGlyph,
|
|
113
115
|
};
|
|
114
116
|
|
|
115
117
|
function cycleTabs(tabs, current, delta) {
|
|
@@ -3470,7 +3472,8 @@ function SessionRowContent({ row, theme, structured = false, showInlineDetail =
|
|
|
3470
3472
|
return React.createElement(React.Fragment, null,
|
|
3471
3473
|
plain.statusColor ? React.createElement(StatusDot, { color: plain.statusColor, node: row.depth > 0 }) : null,
|
|
3472
3474
|
React.createElement(OwnerAvatar, { badge: row.ownerBadge }),
|
|
3473
|
-
React.createElement(
|
|
3475
|
+
React.createElement("span", { className: "ps-session-row-title__text" },
|
|
3476
|
+
React.createElement(Runs, { runs: plain.rest, theme })));
|
|
3474
3477
|
}
|
|
3475
3478
|
|
|
3476
3479
|
// Dense row: the title takes one line (clamped by CSS) and the context %
|
|
@@ -3491,7 +3494,13 @@ function SessionRowContent({ row, theme, structured = false, showInlineDetail =
|
|
|
3491
3494
|
React.createElement("div", { className: "ps-session-row-title" },
|
|
3492
3495
|
title.statusColor ? React.createElement(StatusDot, { color: title.statusColor, node: row.depth > 0 }) : null,
|
|
3493
3496
|
React.createElement(OwnerAvatar, { badge: row.ownerBadge }),
|
|
3494
|
-
|
|
3497
|
+
// Runs wrapped in ONE element: the title is a flex row so the
|
|
3498
|
+
// avatar centres against the text, and the runs keep normal
|
|
3499
|
+
// inline flow inside the wrapper. Flexing the runs directly
|
|
3500
|
+
// collapses the whitespace BETWEEN them and silently narrows
|
|
3501
|
+
// the title (measured: 225.8px → 210.8px).
|
|
3502
|
+
React.createElement("span", { className: "ps-session-row-title__text" },
|
|
3503
|
+
React.createElement(Runs, { runs: title.rest, theme }))),
|
|
3495
3504
|
hasCtx
|
|
3496
3505
|
? React.createElement("div", { className: "ps-session-row-ctx" },
|
|
3497
3506
|
React.createElement(Runs, { runs: ctxRuns, theme }))
|
|
@@ -3532,10 +3541,86 @@ function useStableValue(value) {
|
|
|
3532
3541
|
return ref.current;
|
|
3533
3542
|
}
|
|
3534
3543
|
|
|
3544
|
+
/**
|
|
3545
|
+
* Vertical-primary touch scrolling with an explicit horizontal opt-in.
|
|
3546
|
+
*
|
|
3547
|
+
* The session list has `width: max-content` rows, so it overflows sideways
|
|
3548
|
+
* whenever a title is long. Declaring `touch-action: pan-x pan-y` told the
|
|
3549
|
+
* browser both axes were equally on offer, and on a phone a swipe is never
|
|
3550
|
+
* perfectly vertical — so scrolling down kept sliding the list sideways.
|
|
3551
|
+
*
|
|
3552
|
+
* The fix is to give the browser ONE axis (`touch-action: pan-y`, set in CSS,
|
|
3553
|
+
* which keeps native vertical momentum) and drive horizontal ourselves, only
|
|
3554
|
+
* for a gesture that is unambiguously sideways. Once a gesture commits to an
|
|
3555
|
+
* axis it keeps it until the finger lifts: to scroll across you stop and make
|
|
3556
|
+
* a deliberate left/right swipe, which is exactly the intent.
|
|
3557
|
+
*/
|
|
3558
|
+
const PAN_COMMIT_PX = 10; // ignore the first few px — every swipe starts noisy
|
|
3559
|
+
const PAN_HORIZONTAL_RATIO = 1.6; // |dx| must beat |dy| by this much to go sideways
|
|
3560
|
+
|
|
3561
|
+
function useHorizontalPanOptIn(ref) {
|
|
3562
|
+
React.useEffect(() => {
|
|
3563
|
+
const el = ref.current;
|
|
3564
|
+
if (!el) return undefined;
|
|
3565
|
+
|
|
3566
|
+
let startX = 0;
|
|
3567
|
+
let startY = 0;
|
|
3568
|
+
let lastX = 0;
|
|
3569
|
+
let axis = null; // null = undecided, "x" | "y" = committed for this gesture
|
|
3570
|
+
|
|
3571
|
+
const onTouchStart = (event) => {
|
|
3572
|
+
if (event.touches.length !== 1) { axis = "y"; return; }
|
|
3573
|
+
const touch = event.touches[0];
|
|
3574
|
+
startX = lastX = touch.clientX;
|
|
3575
|
+
startY = touch.clientY;
|
|
3576
|
+
axis = null;
|
|
3577
|
+
};
|
|
3578
|
+
|
|
3579
|
+
const onTouchMove = (event) => {
|
|
3580
|
+
if (axis === "y" || event.touches.length !== 1) return;
|
|
3581
|
+
const touch = event.touches[0];
|
|
3582
|
+
const dx = touch.clientX - startX;
|
|
3583
|
+
const dy = touch.clientY - startY;
|
|
3584
|
+
|
|
3585
|
+
if (axis === null) {
|
|
3586
|
+
if (Math.abs(dx) < PAN_COMMIT_PX && Math.abs(dy) < PAN_COMMIT_PX) return;
|
|
3587
|
+
// Ties, and anything close to a tie, go to vertical. Vertical is
|
|
3588
|
+
// the primary axis; sideways has to be asked for.
|
|
3589
|
+
axis = Math.abs(dx) > Math.abs(dy) * PAN_HORIZONTAL_RATIO ? "x" : "y";
|
|
3590
|
+
lastX = touch.clientX;
|
|
3591
|
+
if (axis === "y") return;
|
|
3592
|
+
}
|
|
3593
|
+
|
|
3594
|
+
// Committed horizontal: the browser is not panning this axis, so we
|
|
3595
|
+
// do it. preventDefault stops the gesture also scrolling an
|
|
3596
|
+
// ancestor sideways.
|
|
3597
|
+
el.scrollLeft -= touch.clientX - lastX;
|
|
3598
|
+
lastX = touch.clientX;
|
|
3599
|
+
if (event.cancelable) event.preventDefault();
|
|
3600
|
+
};
|
|
3601
|
+
|
|
3602
|
+
const onTouchEnd = () => { axis = null; };
|
|
3603
|
+
|
|
3604
|
+
el.addEventListener("touchstart", onTouchStart, { passive: true });
|
|
3605
|
+
el.addEventListener("touchmove", onTouchMove, { passive: false });
|
|
3606
|
+
el.addEventListener("touchend", onTouchEnd, { passive: true });
|
|
3607
|
+
el.addEventListener("touchcancel", onTouchEnd, { passive: true });
|
|
3608
|
+
return () => {
|
|
3609
|
+
el.removeEventListener("touchstart", onTouchStart);
|
|
3610
|
+
el.removeEventListener("touchmove", onTouchMove);
|
|
3611
|
+
el.removeEventListener("touchend", onTouchEnd);
|
|
3612
|
+
el.removeEventListener("touchcancel", onTouchEnd);
|
|
3613
|
+
};
|
|
3614
|
+
}, [ref]);
|
|
3615
|
+
}
|
|
3616
|
+
|
|
3535
3617
|
function SessionPane({ controller, actions = null, panelClassName = "", structuredRows = false }) {
|
|
3536
3618
|
// Mobile keeps its inline detail line and gets no detail box — a reserved
|
|
3537
3619
|
// footer would eat a meaningful slice of a phone screen.
|
|
3538
3620
|
const isMobilePane = String(panelClassName).includes("ps-mobile-session-pane");
|
|
3621
|
+
// Vertical is the primary scroll axis; sideways takes a deliberate swipe.
|
|
3622
|
+
const sessionListRef = React.useRef(null);
|
|
3623
|
+
useHorizontalPanOptIn(sessionListRef);
|
|
3539
3624
|
// Selection reverts to plain taps wherever the primary input is a finger:
|
|
3540
3625
|
// the mobile pane, and the chat-focus overlay's list on a phone. Matches
|
|
3541
3626
|
// the `(pointer: fine)` guard on touch-action in the stylesheet.
|
|
@@ -4127,7 +4212,7 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
|
|
|
4127
4212
|
}),
|
|
4128
4213
|
React.createElement(IconButton, {
|
|
4129
4214
|
className: "ps-mini-button",
|
|
4130
|
-
icon: activeSession?.isSystem ?
|
|
4215
|
+
icon: React.createElement(activeSession?.isSystem ? RestartGlyph : TerminateGlyph),
|
|
4131
4216
|
onClick: () => controller.handleCommand(activeSession?.isGroup ? UI_COMMANDS.DELETE_SESSION : UI_COMMANDS.OPEN_TERMINATE_PICKER).catch(() => {}),
|
|
4132
4217
|
disabled: !canTerminate,
|
|
4133
4218
|
label: isBulkSelection
|
|
@@ -4170,6 +4255,7 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
|
|
|
4170
4255
|
className: combinedPanelClassName,
|
|
4171
4256
|
},
|
|
4172
4257
|
React.createElement("div", {
|
|
4258
|
+
ref: sessionListRef,
|
|
4173
4259
|
className: `ps-action-list ps-session-list${dragState.dragging ? " is-dragging" : ""}`,
|
|
4174
4260
|
// Clicking empty space below the rows clears the list selection while
|
|
4175
4261
|
// the other panes keep showing the session. With nothing selected the
|
|
@@ -4264,20 +4350,189 @@ const VISIBILITY_META = {
|
|
|
4264
4350
|
shared_write: { glyph: "✎", label: "Shared · write" },
|
|
4265
4351
|
};
|
|
4266
4352
|
|
|
4267
|
-
//
|
|
4268
|
-
//
|
|
4269
|
-
function
|
|
4353
|
+
// Shared wrapper for the line-art icon family. Same props PinGlyph shipped
|
|
4354
|
+
// inline, so every glyph inherits .ps-share-glyph sizing and currentColor.
|
|
4355
|
+
function Glyph({ children }) {
|
|
4270
4356
|
return React.createElement("svg", {
|
|
4271
4357
|
className: "ps-share-glyph", viewBox: "0 0 24 24", fill: "none",
|
|
4272
4358
|
stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round",
|
|
4273
4359
|
"aria-hidden": "true",
|
|
4360
|
+
}, children);
|
|
4361
|
+
}
|
|
4362
|
+
|
|
4363
|
+
// The "manage" glyph for the Manage session button. A wrench, not sliders:
|
|
4364
|
+
// sliders are the second most common FILTER icon in the wild, so they collided
|
|
4365
|
+
// with the funnel on the header toolbar. A wrench reads as "configure this one
|
|
4366
|
+
// session" and stays distinct from the cog, which opens the admin console.
|
|
4367
|
+
function ManageGlyph() {
|
|
4368
|
+
return React.createElement(Glyph, null,
|
|
4369
|
+
React.createElement("path", { d: "M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" }));
|
|
4370
|
+
}
|
|
4371
|
+
|
|
4372
|
+
// Filter — the funnel. Plain, with no "a filter is applied" dot or active
|
|
4373
|
+
// state: there is no cheap way to tell a narrowed list from the default one.
|
|
4374
|
+
// The owner filter starts at `all: false` for every signed-in user (see
|
|
4375
|
+
// defaultOwnerFilterForPrincipal), so any "is it filtered" test built on it
|
|
4376
|
+
// reads true from the moment the app loads and the button looks stuck on.
|
|
4377
|
+
function FunnelGlyph() {
|
|
4378
|
+
return React.createElement(Glyph, null,
|
|
4379
|
+
React.createElement("path", { d: "M21 4H3l7.2 8.5V19l3.6 2v-8.5L21 4z" }));
|
|
4380
|
+
}
|
|
4381
|
+
|
|
4382
|
+
// Summary — a written summary. Was "≣", the same codepoint the Logs tab used.
|
|
4383
|
+
function SummaryGlyph() {
|
|
4384
|
+
return React.createElement(Glyph, null,
|
|
4385
|
+
React.createElement("path", { d: "M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z" }),
|
|
4386
|
+
React.createElement("path", { d: "M14 3v5h5" }),
|
|
4387
|
+
React.createElement("line", { x1: "8.5", y1: "13", x2: "15.5", y2: "13" }),
|
|
4388
|
+
React.createElement("line", { x1: "8.5", y1: "17", x2: "13", y2: "17" }));
|
|
4389
|
+
}
|
|
4390
|
+
|
|
4391
|
+
// Back to the transcript. Replaces the 💬 emoji, which iOS force-renders in
|
|
4392
|
+
// colour against an otherwise monochrome row (see the note on the tab table).
|
|
4393
|
+
function TranscriptGlyph() {
|
|
4394
|
+
return React.createElement(Glyph, null,
|
|
4395
|
+
React.createElement("path", { d: "M21 14.5a2.5 2.5 0 0 1-2.5 2.5H8l-5 4V5.5A2.5 2.5 0 0 1 5.5 3h13A2.5 2.5 0 0 1 21 5.5z" }));
|
|
4396
|
+
}
|
|
4397
|
+
|
|
4398
|
+
function PlusGlyph() {
|
|
4399
|
+
return React.createElement(Glyph, null,
|
|
4400
|
+
React.createElement("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
|
|
4401
|
+
React.createElement("line", { x1: "5", y1: "12", x2: "19", y2: "12" }));
|
|
4402
|
+
}
|
|
4403
|
+
|
|
4404
|
+
// Theme picker. Deliberately a contrast disc rather than a sun/moon: the picker
|
|
4405
|
+
// offers DOOM, WinAMP, win95 and friends, not a light/dark binary.
|
|
4406
|
+
function ContrastGlyph() {
|
|
4407
|
+
return React.createElement(Glyph, null,
|
|
4408
|
+
React.createElement("circle", { cx: "12", cy: "12", r: "9" }),
|
|
4409
|
+
React.createElement("path", { d: "M12 3a9 9 0 0 1 0 18z", fill: "currentColor" }));
|
|
4410
|
+
}
|
|
4411
|
+
|
|
4412
|
+
function ExpandGlyph() {
|
|
4413
|
+
return React.createElement(Glyph, null,
|
|
4414
|
+
React.createElement("path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }),
|
|
4415
|
+
React.createElement("path", { d: "M16 3h3a2 2 0 0 1 2 2v3" }),
|
|
4416
|
+
React.createElement("path", { d: "M21 16v3a2 2 0 0 1-2 2h-3" }),
|
|
4417
|
+
React.createElement("path", { d: "M3 16v3a2 2 0 0 0 2 2h3" }));
|
|
4418
|
+
}
|
|
4419
|
+
|
|
4420
|
+
function CollapseGlyph() {
|
|
4421
|
+
return React.createElement(Glyph, null,
|
|
4422
|
+
React.createElement("path", { d: "M8 3v3a2 2 0 0 1-2 2H3" }),
|
|
4423
|
+
React.createElement("path", { d: "M21 8h-3a2 2 0 0 1-2-2V3" }),
|
|
4424
|
+
React.createElement("path", { d: "M16 21v-3a2 2 0 0 1 2-2h3" }),
|
|
4425
|
+
React.createElement("path", { d: "M3 16h3a2 2 0 0 1 2 2v3" }));
|
|
4426
|
+
}
|
|
4427
|
+
|
|
4428
|
+
function CogGlyph() {
|
|
4429
|
+
return React.createElement(Glyph, null,
|
|
4430
|
+
React.createElement("circle", { cx: "12", cy: "12", r: "3" }),
|
|
4431
|
+
React.createElement("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" }));
|
|
4432
|
+
}
|
|
4433
|
+
|
|
4434
|
+
// Restart a system session. Was "↻" — the one text codepoint in an otherwise
|
|
4435
|
+
// all-SVG row, so it rendered a weight lighter and sat a pixel high.
|
|
4436
|
+
function RestartGlyph() {
|
|
4437
|
+
return React.createElement(Glyph, null,
|
|
4438
|
+
React.createElement("path", { d: "M21 12a9 9 0 1 1-2.64-6.36" }),
|
|
4439
|
+
React.createElement("path", { d: "M21 3v5h-5" }));
|
|
4440
|
+
}
|
|
4441
|
+
|
|
4442
|
+
function UploadGlyph() {
|
|
4443
|
+
return React.createElement(Glyph, null,
|
|
4444
|
+
React.createElement("path", { d: "M12 16V4" }),
|
|
4445
|
+
React.createElement("path", { d: "M7.5 8.5L12 4l4.5 4.5" }),
|
|
4446
|
+
React.createElement("path", { d: "M4 15v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3" }));
|
|
4447
|
+
}
|
|
4448
|
+
|
|
4449
|
+
function DownloadGlyph() {
|
|
4450
|
+
return React.createElement(Glyph, null,
|
|
4451
|
+
React.createElement("path", { d: "M12 4v12" }),
|
|
4452
|
+
React.createElement("path", { d: "M7.5 11.5L12 16l4.5-4.5" }),
|
|
4453
|
+
React.createElement("path", { d: "M4 15v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3" }));
|
|
4454
|
+
}
|
|
4455
|
+
|
|
4456
|
+
// ── Inspector tab glyphs ────────────────────────────────────────────────────
|
|
4457
|
+
|
|
4458
|
+
// Two lifelines with a call and a return — an actual sequence diagram, rather
|
|
4459
|
+
// than the "⇶" stack of arrows, which only said "forward". The lifelines sit at
|
|
4460
|
+
// x4.5/x19.5 (a 15-unit gap): at the original 10 the arrowheads all but touched
|
|
4461
|
+
// them and the glyph read as one dense block at 18px.
|
|
4462
|
+
function SequenceGlyph() {
|
|
4463
|
+
return React.createElement(Glyph, null,
|
|
4464
|
+
React.createElement("line", { x1: "4.5", y1: "3", x2: "4.5", y2: "21" }),
|
|
4465
|
+
React.createElement("line", { x1: "19.5", y1: "3", x2: "19.5", y2: "21" }),
|
|
4466
|
+
React.createElement("path", { d: "M4.5 8.5h13" }),
|
|
4467
|
+
React.createElement("path", { d: "M15 6l2.5 2.5-2.5 2.5" }),
|
|
4468
|
+
React.createElement("path", { d: "M19.5 15.5h-13" }),
|
|
4469
|
+
React.createElement("path", { d: "M9 13l-2.5 2.5 2.5 2.5" }));
|
|
4470
|
+
}
|
|
4471
|
+
|
|
4472
|
+
// A side panel with its list rail — fronts the mobile "Sessions" toggle, which
|
|
4473
|
+
// opens exactly that. Paired with CollapseGlyph for "Exit focus" so the two
|
|
4474
|
+
// rail buttons match the icon vocabulary of the main toolbar.
|
|
4475
|
+
function SidebarGlyph() {
|
|
4476
|
+
return React.createElement(Glyph, null,
|
|
4477
|
+
React.createElement("path", { d: "M3 5.5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v13a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" }),
|
|
4478
|
+
React.createElement("path", { d: "M9.5 3.5v17" }));
|
|
4479
|
+
}
|
|
4480
|
+
|
|
4481
|
+
// Logs are console output; a prompt chevron says that and nothing else.
|
|
4482
|
+
function LogsGlyph() {
|
|
4483
|
+
return React.createElement(Glyph, null,
|
|
4484
|
+
React.createElement("path", { d: "M4 17l6-6-6-6" }),
|
|
4485
|
+
React.createElement("line", { x1: "12", y1: "19", x2: "20", y2: "19" }));
|
|
4486
|
+
}
|
|
4487
|
+
|
|
4488
|
+
// Three connected nodes. A bare "⬡" was a shape, not a map.
|
|
4489
|
+
function NodeMapGlyph() {
|
|
4490
|
+
return React.createElement(Glyph, null,
|
|
4491
|
+
React.createElement("circle", { cx: "6", cy: "6", r: "2.6" }),
|
|
4492
|
+
React.createElement("circle", { cx: "18", cy: "6", r: "2.6" }),
|
|
4493
|
+
React.createElement("circle", { cx: "12", cy: "18", r: "2.6" }),
|
|
4494
|
+
React.createElement("line", { x1: "8.6", y1: "6", x2: "15.4", y2: "6" }),
|
|
4495
|
+
React.createElement("line", { x1: "7.4", y1: "8.3", x2: "10.6", y2: "15.7" }),
|
|
4496
|
+
React.createElement("line", { x1: "16.6", y1: "8.3", x2: "13.4", y2: "15.7" }));
|
|
4497
|
+
}
|
|
4498
|
+
|
|
4499
|
+
// The Duroxide brand mark reduced to the 24 grid: hexagon ring, gear, and the
|
|
4500
|
+
// sweep arrow breaking out at the lower right. FILLS, not strokes — a 2px
|
|
4501
|
+
// stroked gear at r=3 is unreadable by the time the row renders it at 18px.
|
|
4502
|
+
// Monochrome currentColor, so it inherits every theme including win95 and DOOM.
|
|
4503
|
+
const DUROXIDE_HEX = "M12 2.3 20.4 7.15v9.7L12 21.7 3.6 16.85V7.15Z";
|
|
4504
|
+
const DUROXIDE_GEAR = "M15.2 10.1A4.2 4.2 0 0 1 15.2 11.9L14.23 11.69A3.2 3.2 0 0 1 13.8 12.73L14.64 13.26A4.2 4.2 0 0 1 13.36 14.54L12.83 13.7A3.2 3.2 0 0 1 11.79 14.13L12 15.1A4.2 4.2 0 0 1 10.2 15.1L10.41 14.13A3.2 3.2 0 0 1 9.37 13.7L8.84 14.54A4.2 4.2 0 0 1 7.56 13.26L8.4 12.73A3.2 3.2 0 0 1 7.97 11.69L7 11.9A4.2 4.2 0 0 1 7 10.1L7.97 10.31A3.2 3.2 0 0 1 8.4 9.27L7.56 8.74A4.2 4.2 0 0 1 8.84 7.46L9.37 8.3A3.2 3.2 0 0 1 10.41 7.87L10.2 6.9A4.2 4.2 0 0 1 12 6.9L11.79 7.87A3.2 3.2 0 0 1 12.83 8.3L13.36 7.46A4.2 4.2 0 0 1 14.64 8.74L13.8 9.27A3.2 3.2 0 0 1 14.23 10.31ZM12.8 11A1.7 1.7 0 0 0 9.4 11A1.7 1.7 0 0 0 12.8 11Z";
|
|
4505
|
+
const DUROXIDE_ARROW = "M7.23 15.01A5.7 5.7 0 0 1 17.76 14.55L19.11 15.04L15.1 16.57L14.71 13.44L16.06 13.93A3.9 3.9 0 0 0 8.87 14.25Z";
|
|
4506
|
+
function DuroxideGlyph() {
|
|
4507
|
+
return React.createElement("svg", {
|
|
4508
|
+
className: "ps-share-glyph", viewBox: "0 0 24 24", fill: "none",
|
|
4509
|
+
stroke: "currentColor", "aria-hidden": "true",
|
|
4274
4510
|
},
|
|
4275
|
-
React.createElement("
|
|
4276
|
-
React.createElement("
|
|
4277
|
-
React.createElement("
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4511
|
+
React.createElement("path", { d: DUROXIDE_HEX, strokeWidth: "1.9", strokeLinejoin: "round" }),
|
|
4512
|
+
React.createElement("path", { d: DUROXIDE_GEAR, fill: "currentColor", stroke: "none", fillRule: "evenodd" }),
|
|
4513
|
+
React.createElement("path", { d: DUROXIDE_ARROW, fill: "currentColor", stroke: "none" }));
|
|
4514
|
+
}
|
|
4515
|
+
|
|
4516
|
+
// Two overlapping PAGES — the front one folded — so the tab reads as "several
|
|
4517
|
+
// artifacts". The old "⧉" is the stacked-squares glyph every other app means
|
|
4518
|
+
// "duplicate" by, which made a destination look like an action.
|
|
4519
|
+
function FilesGlyph() {
|
|
4520
|
+
return React.createElement(Glyph, null,
|
|
4521
|
+
React.createElement("path", { d: "M9 2.8h4.2L17 6.6v8.6a1.8 1.8 0 0 1-1.8 1.8H15" }),
|
|
4522
|
+
React.createElement("path", { d: "M4.5 9.4a1.8 1.8 0 0 1 1.8-1.8h4.2l3.8 3.8v8a1.8 1.8 0 0 1-1.8 1.8H6.3a1.8 1.8 0 0 1-1.8-1.8z" }),
|
|
4523
|
+
React.createElement("path", { d: "M10.5 7.6v3.8h3.8" }));
|
|
4524
|
+
}
|
|
4525
|
+
|
|
4526
|
+
// Solid bars on a baseline. Three things made the first attempt at this read as
|
|
4527
|
+
// a cell-signal meter instead: monotonically ascending heights, round caps, and
|
|
4528
|
+
// nothing anchoring the bars. So these are solid square-shouldered rects with
|
|
4529
|
+
// the middle bar tallest, sitting on a rule.
|
|
4530
|
+
function StatsGlyph() {
|
|
4531
|
+
return React.createElement(Glyph, null,
|
|
4532
|
+
React.createElement("rect", { x: "4.2", y: "12", width: "3.6", height: "8", rx: "0.6", fill: "currentColor", stroke: "none" }),
|
|
4533
|
+
React.createElement("rect", { x: "10.2", y: "6.5", width: "3.6", height: "13.5", rx: "0.6", fill: "currentColor", stroke: "none" }),
|
|
4534
|
+
React.createElement("rect", { x: "16.2", y: "9.5", width: "3.6", height: "10.5", rx: "0.6", fill: "currentColor", stroke: "none" }),
|
|
4535
|
+
React.createElement("path", { d: "M3 21h18" }));
|
|
4281
4536
|
}
|
|
4282
4537
|
|
|
4283
4538
|
// The standard "link" glyph (two chain segments). Used for the Copy link button.
|
|
@@ -4954,7 +5209,7 @@ function InspectorTabs({ activeTab, controller }) {
|
|
|
4954
5209
|
return React.createElement("div", { className: "ps-tab-row ps-tab-row-icons" },
|
|
4955
5210
|
visibleTabs.map((tab) => React.createElement(IconButton, {
|
|
4956
5211
|
key: tab,
|
|
4957
|
-
icon: INSPECTOR_TAB_ICONS[tab]
|
|
5212
|
+
icon: INSPECTOR_TAB_ICONS[tab] ? React.createElement(INSPECTOR_TAB_ICONS[tab]) : "•",
|
|
4958
5213
|
label: INSPECTOR_TAB_LABELS[tab] || tab,
|
|
4959
5214
|
active: activeTab === tab,
|
|
4960
5215
|
onClick: () => {
|
|
@@ -5066,7 +5321,7 @@ function FilesPane({ controller, focused, mobile = false, previewOnly = false, n
|
|
|
5066
5321
|
}, `${markedSet.size} marked`),
|
|
5067
5322
|
viewState.canDeleteArtifacts
|
|
5068
5323
|
? React.createElement(IconButton, {
|
|
5069
|
-
icon:
|
|
5324
|
+
icon: React.createElement(TerminateGlyph),
|
|
5070
5325
|
label: `Delete ${markedSet.size} marked`,
|
|
5071
5326
|
onClick: () => controller.deleteMarkedArtifacts().catch(() => {}),
|
|
5072
5327
|
})
|
|
@@ -5090,19 +5345,21 @@ function FilesPane({ controller, focused, mobile = false, previewOnly = false, n
|
|
|
5090
5345
|
},
|
|
5091
5346
|
}),
|
|
5092
5347
|
React.createElement(IconButton, {
|
|
5093
|
-
icon:
|
|
5348
|
+
icon: React.createElement(UploadGlyph),
|
|
5094
5349
|
label: "Upload",
|
|
5095
5350
|
onClick: openUploadPicker,
|
|
5096
5351
|
disabled: !viewState.canBrowserUpload && !viewState.canPathUpload,
|
|
5097
5352
|
}),
|
|
5098
5353
|
React.createElement(IconButton, {
|
|
5099
|
-
icon:
|
|
5354
|
+
icon: React.createElement(DownloadGlyph),
|
|
5100
5355
|
label: "Download",
|
|
5101
5356
|
onClick: () => controller.handleCommand(UI_COMMANDS.DOWNLOAD_SELECTED_FILE).catch(() => {}),
|
|
5102
5357
|
disabled: !hasSelection,
|
|
5103
5358
|
}),
|
|
5359
|
+
// Trash, not "✕" — the bulk-delete two buttons away was already a trash
|
|
5360
|
+
// can, and "✕" means "clear marks" in this same row.
|
|
5104
5361
|
viewState.canDeleteArtifacts ? React.createElement(IconButton, {
|
|
5105
|
-
icon:
|
|
5362
|
+
icon: React.createElement(TerminateGlyph),
|
|
5106
5363
|
label: "Delete",
|
|
5107
5364
|
onClick: () => controller.handleCommand(UI_COMMANDS.DELETE_SELECTED_FILE).catch(() => {}),
|
|
5108
5365
|
disabled: !hasSelection,
|
|
@@ -5114,7 +5371,7 @@ function FilesPane({ controller, focused, mobile = false, previewOnly = false, n
|
|
|
5114
5371
|
disabled: !hasSelection,
|
|
5115
5372
|
}) : null,
|
|
5116
5373
|
React.createElement(IconButton, {
|
|
5117
|
-
icon:
|
|
5374
|
+
icon: React.createElement(FunnelGlyph),
|
|
5118
5375
|
label: "Filter",
|
|
5119
5376
|
onClick: () => controller.handleCommand(UI_COMMANDS.OPEN_FILES_FILTER).catch(() => {}),
|
|
5120
5377
|
}),
|
|
@@ -5122,7 +5379,7 @@ function FilesPane({ controller, focused, mobile = false, previewOnly = false, n
|
|
|
5122
5379
|
// back affordance belongs to the mobile overlay, which renders its own
|
|
5123
5380
|
// top bar — putting it here too would change desktop behavior.
|
|
5124
5381
|
React.createElement(IconButton, {
|
|
5125
|
-
icon: viewState.fullscreen ?
|
|
5382
|
+
icon: React.createElement(viewState.fullscreen ? CollapseGlyph : ExpandGlyph),
|
|
5126
5383
|
label: viewState.fullscreen ? "Exit fullscreen" : "Fullscreen",
|
|
5127
5384
|
onClick: () => controller.handleCommand(UI_COMMANDS.TOGGLE_FILE_PREVIEW_FULLSCREEN).catch(() => {}),
|
|
5128
5385
|
}));
|
|
@@ -5459,7 +5716,9 @@ function InspectorPane({ controller, mobile = false, panelClassName = "", extraA
|
|
|
5459
5716
|
}));
|
|
5460
5717
|
actions.push(React.createElement(IconButton, {
|
|
5461
5718
|
key: "filter",
|
|
5462
|
-
|
|
5719
|
+
// Same funnel as the session and artifact filters — filtering should
|
|
5720
|
+
// look identical wherever it appears.
|
|
5721
|
+
icon: React.createElement(FunnelGlyph),
|
|
5463
5722
|
label: "Filter",
|
|
5464
5723
|
onClick: () => controller.handleCommand(UI_COMMANDS.OPEN_LOG_FILTER).catch(() => {}),
|
|
5465
5724
|
}));
|
|
@@ -5608,17 +5867,12 @@ function ChatFocusOverlay({ controller, pane, onClose, mobile = false }) {
|
|
|
5608
5867
|
|
|
5609
5868
|
let content = null;
|
|
5610
5869
|
if (pane === "sessions") {
|
|
5870
|
+
// No close button: the rail's own Sessions toggle already closes this,
|
|
5871
|
+
// and a second ✕ crowded a header that already carries five actions.
|
|
5611
5872
|
content = React.createElement(SessionPane, {
|
|
5612
5873
|
controller,
|
|
5613
5874
|
panelClassName: "ps-chat-focus-pane",
|
|
5614
5875
|
structuredRows: mobile,
|
|
5615
|
-
actions: React.createElement("button", {
|
|
5616
|
-
type: "button",
|
|
5617
|
-
className: "ps-mini-button ps-overlay-close",
|
|
5618
|
-
onClick: onClose,
|
|
5619
|
-
"aria-label": "Close",
|
|
5620
|
-
title: "Close",
|
|
5621
|
-
}, "✕"),
|
|
5622
5876
|
});
|
|
5623
5877
|
} else if (pane === "inspector") {
|
|
5624
5878
|
content = React.createElement(InspectorPane, {
|
|
@@ -5656,18 +5910,23 @@ function ChatFocusOverlay({ controller, pane, onClose, mobile = false }) {
|
|
|
5656
5910
|
function ChatFocusWorkspace({ controller, openPane, onTogglePane, onExitFocus, mobile = false }) {
|
|
5657
5911
|
const focusRegion = useControllerSelector(controller, (state) => state.ui.focusRegion);
|
|
5658
5912
|
|
|
5913
|
+
// Icons, not words: two text buttons ate a whole row of phone height, and
|
|
5914
|
+
// IconButton carries the label as a hover tooltip / long-press on touch.
|
|
5659
5915
|
const rail = mobile
|
|
5660
5916
|
? React.createElement("div", { className: "ps-chat-focus-rail" },
|
|
5661
|
-
React.createElement(
|
|
5662
|
-
type: "button",
|
|
5917
|
+
React.createElement(IconButton, {
|
|
5663
5918
|
className: "ps-mini-button ps-chat-focus-button",
|
|
5919
|
+
icon: React.createElement(CollapseGlyph),
|
|
5920
|
+
label: "Exit focus mode",
|
|
5664
5921
|
onClick: onExitFocus,
|
|
5665
|
-
}
|
|
5666
|
-
React.createElement(
|
|
5667
|
-
|
|
5668
|
-
|
|
5922
|
+
}),
|
|
5923
|
+
React.createElement(IconButton, {
|
|
5924
|
+
className: "ps-mini-button ps-chat-focus-button",
|
|
5925
|
+
icon: React.createElement(SidebarGlyph),
|
|
5926
|
+
label: openPane === "sessions" ? "Hide sessions" : "Show sessions",
|
|
5927
|
+
active: openPane === "sessions",
|
|
5669
5928
|
onClick: () => onTogglePane("sessions"),
|
|
5670
|
-
}
|
|
5929
|
+
}))
|
|
5671
5930
|
: React.createElement("div", { className: "ps-chat-focus-rail" },
|
|
5672
5931
|
CHAT_FOCUS_PANES.map((pane) => React.createElement("button", {
|
|
5673
5932
|
key: pane.id,
|
|
@@ -6195,25 +6454,25 @@ function Toolbar({ controller, mobile, chatFocusMode = false, onToggleChatFocus
|
|
|
6195
6454
|
// toolbar could not spare.
|
|
6196
6455
|
{
|
|
6197
6456
|
key: "new",
|
|
6198
|
-
icon:
|
|
6457
|
+
icon: React.createElement(PlusGlyph),
|
|
6199
6458
|
label: "New session — choose model and agent",
|
|
6200
6459
|
onClick: () => controller.handleCommand(UI_COMMANDS.OPEN_MODEL_PICKER).catch(() => {}),
|
|
6201
6460
|
},
|
|
6202
6461
|
{
|
|
6203
6462
|
key: "filter",
|
|
6204
|
-
icon:
|
|
6463
|
+
icon: React.createElement(FunnelGlyph),
|
|
6205
6464
|
label: "Filter sessions",
|
|
6206
6465
|
onClick: () => controller.handleCommand(UI_COMMANDS.OPEN_SESSION_FILTER).catch(() => {}),
|
|
6207
6466
|
},
|
|
6208
6467
|
{
|
|
6209
6468
|
key: "theme",
|
|
6210
|
-
icon:
|
|
6469
|
+
icon: React.createElement(ContrastGlyph),
|
|
6211
6470
|
label: "Theme",
|
|
6212
6471
|
onClick: () => controller.handleCommand(UI_COMMANDS.OPEN_THEME_PICKER).catch(() => {}),
|
|
6213
6472
|
},
|
|
6214
6473
|
{
|
|
6215
6474
|
key: "summary",
|
|
6216
|
-
icon: chatView.mode === "summary" ?
|
|
6475
|
+
icon: React.createElement(chatView.mode === "summary" ? TranscriptGlyph : SummaryGlyph),
|
|
6217
6476
|
label: chatView.activeSessionIsGroup
|
|
6218
6477
|
? "Groups show group details"
|
|
6219
6478
|
: (chatView.mode === "summary" ? "Show chat transcript" : "Show summary"),
|
|
@@ -6223,7 +6482,7 @@ function Toolbar({ controller, mobile, chatFocusMode = false, onToggleChatFocus
|
|
|
6223
6482
|
},
|
|
6224
6483
|
...(onToggleChatFocus ? [{
|
|
6225
6484
|
key: "focus",
|
|
6226
|
-
icon: chatFocusMode ?
|
|
6485
|
+
icon: React.createElement(chatFocusMode ? CollapseGlyph : ExpandGlyph),
|
|
6227
6486
|
label: chatFocusMode ? "Exit focus mode" : "Focus the chat pane",
|
|
6228
6487
|
onClick: onToggleChatFocus,
|
|
6229
6488
|
disabled: chatFocusDisabled,
|
|
@@ -6234,7 +6493,7 @@ function Toolbar({ controller, mobile, chatFocusMode = false, onToggleChatFocus
|
|
|
6234
6493
|
// the button is omitted rather than shipped half-working.
|
|
6235
6494
|
...(mobile ? [] : [{
|
|
6236
6495
|
key: "admin",
|
|
6237
|
-
icon:
|
|
6496
|
+
icon: React.createElement(CogGlyph),
|
|
6238
6497
|
label: adminVisible ? "Close admin console" : "Admin console",
|
|
6239
6498
|
onClick: () => controller.handleCommand(adminVisible ? UI_COMMANDS.CLOSE_ADMIN_CONSOLE : UI_COMMANDS.OPEN_ADMIN_CONSOLE).catch(() => {}),
|
|
6240
6499
|
active: adminVisible,
|
|
@@ -8624,8 +8883,18 @@ export function PilotSwarmWebApp({ controller }) {
|
|
|
8624
8883
|
// workspace keeps rendering underneath so backing out restores it intact.
|
|
8625
8884
|
if (filesFullscreenActive && !mobile) mobileContent = React.createElement("div", { className: "ps-mobile-pane-fill" },
|
|
8626
8885
|
React.createElement(InspectorPane, { controller, mobile: true }));
|
|
8627
|
-
|
|
8628
|
-
|
|
8886
|
+
// Node Map is the one inspector tab whose detail lives in ANOTHER pane: the
|
|
8887
|
+
// Activity pane doubles as worker details, and on desktop it sits directly
|
|
8888
|
+
// below the inspector, which is what the "details fill the pane below" hint
|
|
8889
|
+
// means. On a phone Activity is a separate bottom tab, so tapping a node
|
|
8890
|
+
// filled a pane you could not see. Stack them here instead — the node list
|
|
8891
|
+
// scrolls, the details sit under it.
|
|
8892
|
+
else if (mobilePane === "inspector") mobileContent = (state.inspectorTab === "nodes")
|
|
8893
|
+
? React.createElement("div", { className: "ps-mobile-pane-fill ps-mobile-node-split" },
|
|
8894
|
+
React.createElement(InspectorPane, { controller, mobile: true }),
|
|
8895
|
+
React.createElement(ActivityPane, { controller, panelClassName: "ps-mobile-node-detail" }))
|
|
8896
|
+
: React.createElement("div", { className: "ps-mobile-pane-fill" },
|
|
8897
|
+
React.createElement(InspectorPane, { controller, mobile: true }));
|
|
8629
8898
|
else if (mobilePane === "activity") mobileContent = React.createElement("div", { className: "ps-mobile-pane-fill" },
|
|
8630
8899
|
React.createElement(ActivityPane, { controller }));
|
|
8631
8900
|
else mobileContent = React.createElement(MobileWorkspace, { controller });
|
package/web/api/ws.js
CHANGED
|
@@ -26,6 +26,13 @@ export function createConnectionHandler(runtime, { allowThemeMessages = false }
|
|
|
26
26
|
ws.close(auth.status === 403 ? 4403 : 4401, auth.error || (auth.status === 403 ? "Forbidden" : "Unauthorized"));
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
29
|
+
// Same sign-in role capture as the HTTP `requireAuth` path. This is
|
|
30
|
+
// the OTHER authenticated entry point, and it must not be forgotten:
|
|
31
|
+
// a client that connects once and then lives on the socket would
|
|
32
|
+
// otherwise never re-confirm its role, and the worker — which expires
|
|
33
|
+
// stale observations — would quietly drop that user's agent sessions
|
|
34
|
+
// to non-admin while the portal still showed them as admin.
|
|
35
|
+
runtime.noteSignInRole?.(auth);
|
|
29
36
|
|
|
30
37
|
const sessionSubscriptions = new Map();
|
|
31
38
|
let logUnsubscribe = null;
|