pilotswarm 0.5.32 → 0.5.34
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/app.js +10 -0
- package/tui/src/node-sdk-transport.js +11 -0
- package/ui/core/src/controller.js +381 -12
- package/ui/core/src/reducer.js +125 -28
- package/ui/core/src/selectors.js +158 -268
- package/ui/core/src/state.js +6 -1
- package/ui/core/src/themes/catppuccin-mocha.js +2 -1
- package/ui/core/src/themes/cobalt2.js +2 -1
- package/ui/core/src/themes/doom.js +2 -1
- package/ui/core/src/themes/dracula.js +2 -1
- package/ui/core/src/themes/github-dark.js +2 -1
- package/ui/core/src/themes/github-light.js +2 -1
- package/ui/core/src/themes/gruvbox-dark.js +2 -1
- package/ui/core/src/themes/hacker-x-matrix.js +2 -1
- package/ui/core/src/themes/hacker-x-orion-prime.js +2 -1
- package/ui/core/src/themes/helpers.js +22 -4
- package/ui/core/src/themes/high-contrast-mono.js +2 -1
- package/ui/core/src/themes/index.js +24 -5
- package/ui/core/src/themes/ms-dos.js +2 -1
- package/ui/core/src/themes/noctis-obscuro.js +2 -1
- package/ui/core/src/themes/noctis.js +2 -1
- package/ui/core/src/themes/rust.js +125 -0
- package/ui/core/src/themes/spongebob.js +110 -0
- package/ui/core/src/themes/terminal-green.js +2 -1
- package/ui/core/src/themes/win95.js +2 -1
- package/ui/core/src/themes/winamp.js +2 -1
- package/ui/core/src/themes/workspace-dark.js +2 -1
- package/ui/react/src/web-app.js +240 -266
- package/web/dist/assets/{abnfDiagram-VRR7QNED-mXFQ4a3c.js → abnfDiagram-VRR7QNED-BFOimVWK.js} +1 -1
- package/web/dist/assets/{arc-BPuuvbge.js → arc-uUHjvwEo.js} +1 -1
- package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-Dt4uUYIe.js → architectureDiagram-ZJ3FMSHR-CxeX_vea.js} +1 -1
- package/web/dist/assets/{blockDiagram-677ZJIJ3-Ax8Q22vV.js → blockDiagram-677ZJIJ3-Cno8kltG.js} +1 -1
- package/web/dist/assets/{c4Diagram-LMCZKHZV-CPiukyHt.js → c4Diagram-LMCZKHZV-viONVybf.js} +1 -1
- package/web/dist/assets/channel-IURmSsjn.js +1 -0
- package/web/dist/assets/{chunk-2Q5K7J3B-VJLqCZJX.js → chunk-2Q5K7J3B-CKfICIGD.js} +1 -1
- package/web/dist/assets/{chunk-32BRIVSS-DqOshxhf.js → chunk-32BRIVSS-CxmwEY56.js} +1 -1
- package/web/dist/assets/{chunk-5VM5RSS4-KPqV21jV.js → chunk-5VM5RSS4-_eVYLGll.js} +1 -1
- package/web/dist/assets/{chunk-EX3LRPZG-DfhiCVl_.js → chunk-EX3LRPZG-C4aqbjrf.js} +1 -1
- package/web/dist/assets/{chunk-JWPE2WC7-H6khJPuW.js → chunk-JWPE2WC7-Bh8ZxrI8.js} +1 -1
- package/web/dist/assets/{chunk-MOJQB5TN-BKMAzQxj.js → chunk-MOJQB5TN-BXgLlWVP.js} +1 -1
- package/web/dist/assets/{chunk-RYQCIY6F-DNXRTxtj.js → chunk-RYQCIY6F-B1rMIdfF.js} +1 -1
- package/web/dist/assets/{chunk-V7JOEXUC-B56PM6Gv.js → chunk-V7JOEXUC-C3AnSHXk.js} +1 -1
- package/web/dist/assets/{chunk-VR4S4FIN-Boi3hGkB.js → chunk-VR4S4FIN-CeoMJIBs.js} +1 -1
- package/web/dist/assets/{chunk-XXDRQBXY-DmJyn890.js → chunk-XXDRQBXY-DzaxQ9iP.js} +1 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-FsfpM3iu.js +1 -0
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-FsfpM3iu.js +1 -0
- package/web/dist/assets/{cose-bilkent-JH36ORCC-BRdFlFWt.js → cose-bilkent-JH36ORCC-G46fvUOx.js} +1 -1
- package/web/dist/assets/{cynefin-VYW2F7L2-DqE-xib2.js → cynefin-VYW2F7L2-DXwqVhWA.js} +1 -1
- package/web/dist/assets/{cynefinDiagram-TSTJHNR4-D-QBZjIL.js → cynefinDiagram-TSTJHNR4-DNNFmaXb.js} +1 -1
- package/web/dist/assets/{dagre-VKFMJZFB-DYu9oQV6.js → dagre-VKFMJZFB-BCfh-h3o.js} +1 -1
- package/web/dist/assets/{diagram-FQU43EPY-DOwYccCg.js → diagram-FQU43EPY-CyG9b6bj.js} +1 -1
- package/web/dist/assets/{diagram-G47NLZAW-DTSEmrPr.js → diagram-G47NLZAW-DdxDahK3.js} +1 -1
- package/web/dist/assets/{diagram-NH7WQ7WH-OqbzzquA.js → diagram-NH7WQ7WH-CjrZ_eja.js} +1 -1
- package/web/dist/assets/{diagram-OA4YK3LP-BMVG6A8I.js → diagram-OA4YK3LP-5NAjQpk2.js} +1 -1
- package/web/dist/assets/{diagram-WEI45ONY-Rn2Q5xaz.js → diagram-WEI45ONY-fLuDb54-.js} +1 -1
- package/web/dist/assets/{ebnfDiagram-CCIWWBDH-aL32_v5y.js → ebnfDiagram-CCIWWBDH-DL9qnZpa.js} +1 -1
- package/web/dist/assets/{erDiagram-Q63AITRT-DKCpyczx.js → erDiagram-Q63AITRT-C_ynfrWY.js} +1 -1
- package/web/dist/assets/{flowDiagram-23GEKE2U-CrZTPgR_.js → flowDiagram-23GEKE2U-dYuH8HYv.js} +1 -1
- package/web/dist/assets/{ganttDiagram-NO4QXBWP-DnSE1OtC.js → ganttDiagram-NO4QXBWP-e8Meh3f3.js} +1 -1
- package/web/dist/assets/{gitGraphDiagram-IHSO6WYX-C5SeYPUR.js → gitGraphDiagram-IHSO6WYX-D_tZ4VL9.js} +1 -1
- package/web/dist/assets/index-Ct7SQv1M.css +1 -0
- package/web/dist/assets/{index-B-TEKrRB.js → index-DcQes9h8.js} +6 -6
- package/web/dist/assets/{infoDiagram-FWYZ7A6U-DDnJZp0X.js → infoDiagram-FWYZ7A6U-Dffhqp73.js} +1 -1
- package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-ewhzfFkq.js → ishikawaDiagram-FXEZZL3T-DYt9H7kU.js} +1 -1
- package/web/dist/assets/{journeyDiagram-5HDEW3XC-T0iWWkLM.js → journeyDiagram-5HDEW3XC-Qv1vVu0F.js} +1 -1
- package/web/dist/assets/{kanban-definition-HUTT4EX6-D1MFQZjb.js → kanban-definition-HUTT4EX6-CTAUo40G.js} +1 -1
- package/web/dist/assets/{linear-BKfPK9wE.js → linear-CqLsWFLj.js} +1 -1
- package/web/dist/assets/{mermaid.core-CyzD7dTc.js → mermaid.core-okRCGPQQ.js} +4 -4
- package/web/dist/assets/{mindmap-definition-LN4V7U3C-CWXtcTlP.js → mindmap-definition-LN4V7U3C-DVOOrIwD.js} +1 -1
- package/web/dist/assets/{papaparse.min-DLUoV5k5.js → papaparse.min-Ddflu8oC.js} +1 -1
- package/web/dist/assets/{pegDiagram-2B236MQR-BFjZun-r.js → pegDiagram-2B236MQR-BLJlC0MZ.js} +1 -1
- package/web/dist/assets/{pieDiagram-ENE6RG2P-DMDtW4g7.js → pieDiagram-ENE6RG2P-DdOwICOg.js} +1 -1
- package/web/dist/assets/pilotswarm-CKh2MxfL.js +99 -0
- package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-CAwPVaYL.js → quadrantDiagram-ABIIQ3AL-B0Th1YO5.js} +1 -1
- package/web/dist/assets/{railroadDiagram-RFXS5EU6-Co102PkQ.js → railroadDiagram-RFXS5EU6-BF2XKMKf.js} +1 -1
- package/web/dist/assets/{requirementDiagram-TGXJPOKE-D19VlRSl.js → requirementDiagram-TGXJPOKE-Mr1iSwbS.js} +1 -1
- package/web/dist/assets/{sankeyDiagram-HTMAVEWB-Sd_a3ukL.js → sankeyDiagram-HTMAVEWB-CrOjVZyg.js} +1 -1
- package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-CojDRK1c.js → sequenceDiagram-DBY2YBRQ-H0X4Lydk.js} +1 -1
- package/web/dist/assets/{sizeCapture-X5ZJPWSS-XqXcxnDo.js → sizeCapture-X5ZJPWSS-Dus1QEqh.js} +1 -1
- package/web/dist/assets/{stateDiagram-2N3HPSRC-BcN_w699.js → stateDiagram-2N3HPSRC-BOBUSwdR.js} +1 -1
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-DABTDwFh.js +1 -0
- package/web/dist/assets/{swimlanes-5IMT3BWC-BcO6ai_I.js → swimlanes-5IMT3BWC-BoqT-Lh7.js} +2 -2
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-BF5mFN4t.js +8 -0
- package/web/dist/assets/{timeline-definition-FHXFAJF6-DOPfYxUq.js → timeline-definition-FHXFAJF6-BOrqUQHX.js} +1 -1
- package/web/dist/assets/{vennDiagram-L72KCM5P-BSKLkEOC.js → vennDiagram-L72KCM5P-DDkNL-Oo.js} +1 -1
- package/web/dist/assets/{wardleyDiagram-EHGQE667-BdcYAVw2.js → wardleyDiagram-EHGQE667-XUAJQ2X0.js} +1 -1
- package/web/dist/assets/{xychartDiagram-FW5EYKEG-C6EJT4jB.js → xychartDiagram-FW5EYKEG-ph69PV9y.js} +1 -1
- package/web/dist/index.html +3 -3
- package/ui/core/src/themes/dark-high-contrast.js +0 -56
- package/ui/core/src/themes/workspace-dark-rich.js +0 -65
- package/web/dist/assets/channel-BCzDctYg.js +0 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-ChqjVZil.js +0 -1
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-ChqjVZil.js +0 -1
- package/web/dist/assets/index-D2B5bzSN.css +0 -1
- package/web/dist/assets/pilotswarm-DA0u7Cq_.js +0 -99
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-BV_w1D3C.js +0 -1
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-BnYdP0qP.js +0 -8
package/ui/core/src/selectors.js
CHANGED
|
@@ -80,19 +80,66 @@ function shouldCompactPaneTitleMetadata(width) {
|
|
|
80
80
|
function getSessionVisualStatus(session) {
|
|
81
81
|
if (!session) return "unknown";
|
|
82
82
|
const status = session.status || "unknown";
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
&& (status === "waiting" || status === "idle" || status === "unknown")
|
|
86
|
-
) {
|
|
83
|
+
const dormant = status === "waiting" || status === "idle" || status === "unknown";
|
|
84
|
+
if (session.cronActive === true && dormant) {
|
|
87
85
|
return "cron_waiting";
|
|
88
86
|
}
|
|
87
|
+
// `activeChildCount` is stamped by the reducer (applyActiveDescendantCounts)
|
|
88
|
+
// and counts only running/input_required descendants — a sub-agent that has
|
|
89
|
+
// finished stays alive and idle, so a plain child count would leave every
|
|
90
|
+
// parent stuck in this state forever. Checked after cron so a scheduled
|
|
91
|
+
// session keeps reading as scheduled.
|
|
92
|
+
if (dormant && (Number(session.activeChildCount) || 0) > 0) {
|
|
93
|
+
return "awaiting_children";
|
|
94
|
+
}
|
|
95
|
+
return status;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function sessionStatusLabelForVisualStatus(session, status) {
|
|
99
|
+
if (status === "cron_waiting") return "waiting";
|
|
100
|
+
if (status === "awaiting_children") {
|
|
101
|
+
// The row status is debounced (see mergeSessionRowVisualStatus), so it
|
|
102
|
+
// can outlive the count that produced it by a few seconds.
|
|
103
|
+
const active = Number(session?.activeChildCount) || 0;
|
|
104
|
+
return active > 0 ? `waiting on ${active}` : "waiting on children";
|
|
105
|
+
}
|
|
89
106
|
return status;
|
|
90
107
|
}
|
|
91
108
|
|
|
92
109
|
function getSessionSummaryStatusLabel(session) {
|
|
93
110
|
const status = getSessionVisualStatus(session);
|
|
94
111
|
if (!status || status === "unknown") return "";
|
|
95
|
-
return
|
|
112
|
+
return sessionStatusLabelForVisualStatus(session, status);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The label + timestamp the session-detail box shows, as ONE derivation shared
|
|
117
|
+
* with the list row.
|
|
118
|
+
*
|
|
119
|
+
* The detail box used to print `session.status` raw, and that is why it flipped
|
|
120
|
+
* between "idle" and "waiting" while a turn sat parked: two writers disagree
|
|
121
|
+
* mid-turn — the 4s catalog poll carries the CMS row state, the post-event
|
|
122
|
+
* detail sync carries the live orchestration customStatus — and whichever
|
|
123
|
+
* landed last won. The list has never had that problem because it reads the
|
|
124
|
+
* DEBOUNCED status (mergeSessionRowVisualStatus holds a change for 5s for
|
|
125
|
+
* exactly this reason) and because the cron-aware derivation folds
|
|
126
|
+
* idle/waiting/unknown into one steady state for a scheduled session.
|
|
127
|
+
*
|
|
128
|
+
* Reading raw meant the box opted out of both. This is the same accessor the
|
|
129
|
+
* row uses, so the two can no longer disagree with each other either.
|
|
130
|
+
*/
|
|
131
|
+
export function selectSessionStatusSummary(session) {
|
|
132
|
+
if (!session) return null;
|
|
133
|
+
const timestampMs = session.updatedAt
|
|
134
|
+
|| session.summaryUpdatedAt
|
|
135
|
+
|| session.latestSummaryUpdatedAt
|
|
136
|
+
|| session.createdAt
|
|
137
|
+
|| 0;
|
|
138
|
+
return {
|
|
139
|
+
status: getSessionRowStatusLabel(session) || "unknown",
|
|
140
|
+
timestampMs: timestampMs || null,
|
|
141
|
+
relative: timestampMs ? formatRelativeTime(timestampMs) : "—",
|
|
142
|
+
};
|
|
96
143
|
}
|
|
97
144
|
|
|
98
145
|
function getSessionRowStatusLabel(session) {
|
|
@@ -104,7 +151,7 @@ function getSessionRowStatusLabel(session) {
|
|
|
104
151
|
// already smoothed. The summary view keeps the immediate raw label above.
|
|
105
152
|
const status = getSessionRowVisualStatus(session);
|
|
106
153
|
if (!status || status === "unknown") return "";
|
|
107
|
-
return
|
|
154
|
+
return sessionStatusLabelForVisualStatus(session, status);
|
|
108
155
|
}
|
|
109
156
|
|
|
110
157
|
function getSessionRowVisualStatus(session) {
|
|
@@ -132,6 +179,7 @@ function getSessionVisualKind(session, mode = "local") {
|
|
|
132
179
|
mode === "remote"
|
|
133
180
|
&& isTerminalOrchestrationStatus(session?.orchestrationStatus)
|
|
134
181
|
&& status !== "cron_waiting"
|
|
182
|
+
&& status !== "awaiting_children"
|
|
135
183
|
&& status !== "waiting"
|
|
136
184
|
&& status !== "input_required"
|
|
137
185
|
) {
|
|
@@ -151,6 +199,7 @@ function getSessionRowVisualKind(session, mode = "local") {
|
|
|
151
199
|
mode === "remote"
|
|
152
200
|
&& isTerminalOrchestrationStatus(session?.orchestrationStatus)
|
|
153
201
|
&& status !== "cron_waiting"
|
|
202
|
+
&& status !== "awaiting_children"
|
|
154
203
|
&& status !== "waiting"
|
|
155
204
|
&& status !== "input_required"
|
|
156
205
|
) {
|
|
@@ -165,6 +214,9 @@ function sessionStatusColor(session, mode = "local") {
|
|
|
165
214
|
switch (getSessionRowVisualKind(session, mode)) {
|
|
166
215
|
case "running": return "green";
|
|
167
216
|
case "cron_waiting": return "yellow";
|
|
217
|
+
// Distinct from both running (this session is not doing the work) and
|
|
218
|
+
// waiting (nothing is happening) — something IS running, one level down.
|
|
219
|
+
case "awaiting_children": return "magenta";
|
|
168
220
|
case "waiting": return "yellow";
|
|
169
221
|
case "input_required": return "cyan";
|
|
170
222
|
case "cancelled": return "gray";
|
|
@@ -181,6 +233,7 @@ function sessionStatusIcon(session, mode = "local") {
|
|
|
181
233
|
switch (getSessionRowVisualKind(session, mode)) {
|
|
182
234
|
case "running": return "*";
|
|
183
235
|
case "cron_waiting": return "~";
|
|
236
|
+
case "awaiting_children": return ">";
|
|
184
237
|
case "waiting": return "~";
|
|
185
238
|
case "input_required": return "?";
|
|
186
239
|
case "cancelled": return "x";
|
|
@@ -242,7 +295,11 @@ function ownerInitials(owner) {
|
|
|
242
295
|
*/
|
|
243
296
|
export function manualOrderContainerKey(session, pinned = false) {
|
|
244
297
|
if (!isManuallyOrderableSession(session)) return null;
|
|
245
|
-
|
|
298
|
+
// Pinned folders and pinned sessions are SEPARATE bands (rankSessionBand
|
|
299
|
+
// 1 and 2), so they cannot share a container: the drag would offer drop
|
|
300
|
+
// positions among the pinned sessions that the sort then refuses, and the
|
|
301
|
+
// row landed somewhere other than the insertion line the user aimed at.
|
|
302
|
+
if (pinned) return session.isGroup ? "pinned:folders" : "pinned:sessions";
|
|
246
303
|
if (session.isGroup) return "folders";
|
|
247
304
|
if (session.groupId) return `g:${session.groupId}`;
|
|
248
305
|
return "";
|
|
@@ -591,10 +648,18 @@ function buildTotalDescendantCounts(byId) {
|
|
|
591
648
|
const { childMap } = buildChildMaps(byId);
|
|
592
649
|
const counts = new Map();
|
|
593
650
|
|
|
651
|
+
// `visiting` bounds a malformed parent chain (self-parent, cycle). Without
|
|
652
|
+
// it the recursion below never terminates and the whole UI hangs on a
|
|
653
|
+
// RangeError rather than rendering a slightly wrong count.
|
|
654
|
+
const visiting = new Set();
|
|
655
|
+
|
|
594
656
|
function countFor(sessionId) {
|
|
595
657
|
if (counts.has(sessionId)) return counts.get(sessionId);
|
|
658
|
+
if (visiting.has(sessionId)) return 0;
|
|
659
|
+
visiting.add(sessionId);
|
|
596
660
|
const children = childMap.get(sessionId) || [];
|
|
597
661
|
const total = children.reduce((sum, childId) => sum + 1 + countFor(childId), 0);
|
|
662
|
+
visiting.delete(sessionId);
|
|
598
663
|
counts.set(sessionId, total);
|
|
599
664
|
return total;
|
|
600
665
|
}
|
|
@@ -754,8 +819,15 @@ function buildSessionRowView(entry, session, state, totalDescendantCounts, visib
|
|
|
754
819
|
// Multi-selection reads as a selection BAR on the chosen rows (plus the
|
|
755
820
|
// host's own row styling) — not a checkbox column that shifts every row
|
|
756
821
|
// the moment selection mode turns on.
|
|
822
|
+
// role:"selection" so a host that already SHOWS selection some other way
|
|
823
|
+
// can drop the glyph instead of paying a cell for it — the same contract
|
|
824
|
+
// role:"status" and role:"depth" already use. The portal outlines the row,
|
|
825
|
+
// so the bar there was pure duplication that cost one column: selected
|
|
826
|
+
// rows truncated one character earlier than their neighbours, which
|
|
827
|
+
// clipped the pin/folder/system icon on any row whose glyph happened to
|
|
828
|
+
// land on the boundary. The TUI ignores the tag and keeps the bar.
|
|
757
829
|
if (isSelected) {
|
|
758
|
-
prefixRuns.push({ text: "▌", color: "cyan", bold: true });
|
|
830
|
+
prefixRuns.push({ text: "▌", color: "cyan", bold: true, role: "selection" });
|
|
759
831
|
}
|
|
760
832
|
|
|
761
833
|
// Pin column renders first on top-level non-system rows so pinned and
|
|
@@ -2698,238 +2770,6 @@ export function selectChatLines(state, maxWidth = 80, options = {}) {
|
|
|
2698
2770
|
return memoize(lines.length > 0 ? lines : [{ text: "No messages yet.", color: "gray" }]);
|
|
2699
2771
|
}
|
|
2700
2772
|
|
|
2701
|
-
// True when a chat message is plain user/assistant prose the rich (block)
|
|
2702
|
-
// renderer can format as markdown directly. Everything with terminal-era
|
|
2703
|
-
// special handling — splash art, thinking cards, chrome-less summaries,
|
|
2704
|
-
// system/PilotSwarm cards, asked-and-answered exchanges, embedded
|
|
2705
|
-
// [SYSTEM: …] notice segments — stays on the line renderer, which already
|
|
2706
|
-
// knows every edge case.
|
|
2707
|
-
function isRichRenderableChatMessage(message) {
|
|
2708
|
-
if (!message) return false;
|
|
2709
|
-
if (message.kind === "epoch-divider" || message.kind === "regen-refused" || message.kind === "regen-failed") return false;
|
|
2710
|
-
if (message.splash || message.thinking || message.noChrome) return false;
|
|
2711
|
-
if (message.role !== "user" && message.role !== "assistant") return false;
|
|
2712
|
-
if (message.role === "user" && parseAskedAndAnsweredExchange(message?.text || "")) return false;
|
|
2713
|
-
const segments = splitSystemNoticeSegments(message?.text || "");
|
|
2714
|
-
if (segments.some((segment) => segment.kind === "system")) return false;
|
|
2715
|
-
return true;
|
|
2716
|
-
}
|
|
2717
|
-
|
|
2718
|
-
/**
|
|
2719
|
-
* Message-level view of the active chat for the rich (desktop-style) web
|
|
2720
|
-
* renderer. Returns an ordered array of blocks:
|
|
2721
|
-
*
|
|
2722
|
-
* { kind: "message", id, role, header, text, pendingPhase, attachments }
|
|
2723
|
-
* — plain user/assistant prose. `header` is describeChatMessageHeader()
|
|
2724
|
-
* output; `text` is the artifact-link-decorated markdown source the
|
|
2725
|
-
* renderer formats itself; `attachments` is { sessionId, attachments }
|
|
2726
|
-
* (the sentinel shape) or null.
|
|
2727
|
-
*
|
|
2728
|
-
* { kind: "lines", variant, lines }
|
|
2729
|
-
* — everything else (splash, thinking cards, system cards, dividers),
|
|
2730
|
-
* pre-rendered through the exact same line builders the terminal
|
|
2731
|
-
* transcript uses, so no edge case has two implementations.
|
|
2732
|
-
*
|
|
2733
|
-
* The TUI never calls this; selectChatLines remains the terminal path.
|
|
2734
|
-
*/
|
|
2735
|
-
// Rich message blocks, cached on the message object. Keyed weakly so entries
|
|
2736
|
-
// die with the transcript they came from.
|
|
2737
|
-
const chatMessageBlockCache = new WeakMap();
|
|
2738
|
-
|
|
2739
|
-
export function selectChatBlocks(state, maxWidth = 80, options = {}) {
|
|
2740
|
-
const messages = selectActiveChat(state);
|
|
2741
|
-
if (!messages || messages.length === 0) return [];
|
|
2742
|
-
|
|
2743
|
-
const viewerKey = ownerKeyForOwner(state?.auth?.principal);
|
|
2744
|
-
const activeSessionId = state?.sessions?.activeSessionId;
|
|
2745
|
-
const activeSession = activeSessionId ? state?.sessions?.byId?.[activeSessionId] : null;
|
|
2746
|
-
const ownerKey = ownerKeyForOwner(activeSession?.owner);
|
|
2747
|
-
const sharedContext = Boolean(activeSession && (
|
|
2748
|
-
(activeSession.visibility && activeSession.visibility !== "private")
|
|
2749
|
-
|| (ownerKey && viewerKey && ownerKey !== viewerKey)
|
|
2750
|
-
));
|
|
2751
|
-
const buildOptions = {
|
|
2752
|
-
...(options?.tableMode ? { tableMode: options.tableMode } : {}),
|
|
2753
|
-
...(viewerKey ? { viewerKey } : {}),
|
|
2754
|
-
...(ownerKey ? { ownerKey } : {}),
|
|
2755
|
-
...(sharedContext ? { sharedContext: true } : {}),
|
|
2756
|
-
};
|
|
2757
|
-
|
|
2758
|
-
const blocks = [];
|
|
2759
|
-
for (const message of messages) {
|
|
2760
|
-
// Fast path FIRST. Classifying a message as rich-renderable scans its
|
|
2761
|
-
// text (asked-and-answered parsing, system-notice splitting), and that
|
|
2762
|
-
// ran for every message on every call — so a pane-splitter drag paid
|
|
2763
|
-
// it across the whole transcript per width step. A cache entry only
|
|
2764
|
-
// exists for a message already classified rich, and `kind` is part of
|
|
2765
|
-
// what is compared, so a hit is safe to take before classifying.
|
|
2766
|
-
// Object.is, NOT ===: `createdAt` is NaN whenever the source event had
|
|
2767
|
-
// no parseable timestamp, and NaN === NaN is false, so a plain equality
|
|
2768
|
-
// check could never hit for those messages — the cache would silently
|
|
2769
|
-
// do nothing while still costing a lookup. Same trap as comparing
|
|
2770
|
-
// Infinity by subtraction.
|
|
2771
|
-
const cached = message ? chatMessageBlockCache.get(message) : null;
|
|
2772
|
-
if (cached
|
|
2773
|
-
&& Object.is(cached.text, message.text)
|
|
2774
|
-
&& Object.is(cached.kind, message.kind)
|
|
2775
|
-
&& Object.is(cached.role, message.role)
|
|
2776
|
-
&& Object.is(cached.id, message.id)
|
|
2777
|
-
&& Object.is(cached.pendingPhase, message.pendingPhase)
|
|
2778
|
-
&& Object.is(cached.stopped, message.stopped)
|
|
2779
|
-
// The header is derived from these, and the chips from attachments.
|
|
2780
|
-
// Messages are mutated in place (pendingPhase provably is), so every
|
|
2781
|
-
// field the block is built from has to be compared — not just the
|
|
2782
|
-
// obvious ones.
|
|
2783
|
-
&& Object.is(cached.createdAt, message.createdAt)
|
|
2784
|
-
&& Object.is(cached.time, message.time)
|
|
2785
|
-
&& Object.is(cached.sender, message.sender)
|
|
2786
|
-
&& Object.is(cached.attachments, message.attachments)
|
|
2787
|
-
&& Object.is(cached.tableMode, buildOptions.tableMode)
|
|
2788
|
-
&& Object.is(cached.viewerKey, buildOptions.viewerKey)
|
|
2789
|
-
&& Object.is(cached.ownerKey, buildOptions.ownerKey)
|
|
2790
|
-
&& Object.is(cached.sharedContext, buildOptions.sharedContext)) {
|
|
2791
|
-
blocks.push(cached.block);
|
|
2792
|
-
continue;
|
|
2793
|
-
}
|
|
2794
|
-
|
|
2795
|
-
if (message?.kind === "epoch-divider") {
|
|
2796
|
-
blocks.push({ kind: "lines", variant: "divider", lines: [buildEpochDividerLine(message, maxWidth)] });
|
|
2797
|
-
continue;
|
|
2798
|
-
}
|
|
2799
|
-
if (message?.kind === "regen-failed") {
|
|
2800
|
-
blocks.push({ kind: "lines", variant: "divider", lines: [buildRegenFailedLine(message, maxWidth)] });
|
|
2801
|
-
continue;
|
|
2802
|
-
}
|
|
2803
|
-
if (message?.kind === "regen-refused") {
|
|
2804
|
-
blocks.push({ kind: "lines", variant: "divider", lines: [buildRegenRefusedLine(message, maxWidth)] });
|
|
2805
|
-
continue;
|
|
2806
|
-
}
|
|
2807
|
-
if (isRichRenderableChatMessage(message)) {
|
|
2808
|
-
// A rich message block does NOT depend on maxWidth — the browser
|
|
2809
|
-
// wraps this text with CSS; only the terminal needs a column count.
|
|
2810
|
-
// Rebuilding it on every width step meant dragging a pane splitter
|
|
2811
|
-
// re-parsed artifact links and re-derived headers for the entire
|
|
2812
|
-
// loaded transcript to produce byte-identical output. Cache on the
|
|
2813
|
-
// message itself, keyed by the inputs that CAN change it.
|
|
2814
|
-
const attachmentLines = buildAttachmentChipLines(message, { tableMode: "sentinel" });
|
|
2815
|
-
const attachmentSentinel = attachmentLines.find((line) => line?.kind === "imageAttachments") || null;
|
|
2816
|
-
const block = {
|
|
2817
|
-
kind: "message",
|
|
2818
|
-
id: message?.id ?? null,
|
|
2819
|
-
role: message.role,
|
|
2820
|
-
header: describeChatMessageHeader(message, buildOptions),
|
|
2821
|
-
text: decorateArtifactLinksForChat(message?.text || ""),
|
|
2822
|
-
pendingPhase: message?.pendingPhase || null,
|
|
2823
|
-
attachments: attachmentSentinel
|
|
2824
|
-
? { sessionId: attachmentSentinel.sessionId, attachments: attachmentSentinel.attachments }
|
|
2825
|
-
: null,
|
|
2826
|
-
};
|
|
2827
|
-
chatMessageBlockCache.set(message, {
|
|
2828
|
-
block,
|
|
2829
|
-
text: message.text,
|
|
2830
|
-
kind: message.kind,
|
|
2831
|
-
createdAt: message.createdAt,
|
|
2832
|
-
time: message.time,
|
|
2833
|
-
sender: message.sender,
|
|
2834
|
-
attachments: message.attachments,
|
|
2835
|
-
role: message.role,
|
|
2836
|
-
id: message.id,
|
|
2837
|
-
pendingPhase: message.pendingPhase,
|
|
2838
|
-
stopped: message.stopped,
|
|
2839
|
-
tableMode: buildOptions.tableMode,
|
|
2840
|
-
viewerKey: buildOptions.viewerKey,
|
|
2841
|
-
ownerKey: buildOptions.ownerKey,
|
|
2842
|
-
sharedContext: buildOptions.sharedContext,
|
|
2843
|
-
});
|
|
2844
|
-
blocks.push(block);
|
|
2845
|
-
continue;
|
|
2846
|
-
}
|
|
2847
|
-
const lineBlock = cachedLineBlock(message, maxWidth, buildOptions);
|
|
2848
|
-
if (lineBlock) blocks.push(lineBlock);
|
|
2849
|
-
}
|
|
2850
|
-
return blocks;
|
|
2851
|
-
}
|
|
2852
|
-
|
|
2853
|
-
/**
|
|
2854
|
-
* Non-message blocks (thinking cards, system cards, splash, chrome-less
|
|
2855
|
-
* summaries) go through the terminal line builders, which DO depend on width —
|
|
2856
|
-
* so unlike message blocks these cannot be made width-independent. What they
|
|
2857
|
-
* can avoid is being rebuilt when the width has not moved, which is every poll:
|
|
2858
|
-
* the poll replaces `session`, the portal's useMemo sees a new selectorState,
|
|
2859
|
-
* and the whole transcript is re-derived. Measured at 1500 messages with 10%
|
|
2860
|
-
* line blocks that was 1.17ms a time, and 5.26ms at 50%.
|
|
2861
|
-
*
|
|
2862
|
-
* Only the LAST width is kept per message. A drag sweeps widths and will miss
|
|
2863
|
-
* on every step — that work is genuinely required, since these lines are
|
|
2864
|
-
* wrapped to a column count.
|
|
2865
|
-
*
|
|
2866
|
-
* The compared fields were enumerated from buildChatMessageLines and the
|
|
2867
|
-
* builders it delegates to (message/thinking/system-notice cards). `stopped` is
|
|
2868
|
-
* included because history.js sets it IN PLACE on an existing message.
|
|
2869
|
-
*/
|
|
2870
|
-
const chatLineBlockCache = new WeakMap();
|
|
2871
|
-
|
|
2872
|
-
function cachedLineBlock(message, maxWidth, buildOptions) {
|
|
2873
|
-
if (!message || typeof message !== "object") {
|
|
2874
|
-
const lines = buildChatMessageLines(message, maxWidth, buildOptions);
|
|
2875
|
-
return lines.length > 0 ? { kind: "lines", variant: "event", lines } : null;
|
|
2876
|
-
}
|
|
2877
|
-
const cached = chatLineBlockCache.get(message);
|
|
2878
|
-
if (cached
|
|
2879
|
-
&& Object.is(cached.maxWidth, maxWidth)
|
|
2880
|
-
&& Object.is(cached.text, message.text)
|
|
2881
|
-
&& Object.is(cached.mobileText, message.mobileText)
|
|
2882
|
-
&& Object.is(cached.role, message.role)
|
|
2883
|
-
&& Object.is(cached.time, message.time)
|
|
2884
|
-
&& Object.is(cached.createdAt, message.createdAt)
|
|
2885
|
-
&& Object.is(cached.splash, message.splash)
|
|
2886
|
-
&& Object.is(cached.thinking, message.thinking)
|
|
2887
|
-
&& Object.is(cached.thinkingLabel, message.thinkingLabel)
|
|
2888
|
-
&& Object.is(cached.progressKind, message.progressKind)
|
|
2889
|
-
&& Object.is(cached.noChrome, message.noChrome)
|
|
2890
|
-
&& Object.is(cached.cardTitle, message.cardTitle)
|
|
2891
|
-
&& Object.is(cached.cardTitleColor, message.cardTitleColor)
|
|
2892
|
-
&& Object.is(cached.cardBorderColor, message.cardBorderColor)
|
|
2893
|
-
&& Object.is(cached.stopped, message.stopped)
|
|
2894
|
-
&& Object.is(cached.tableMode, buildOptions.tableMode)
|
|
2895
|
-
&& Object.is(cached.viewerKey, buildOptions.viewerKey)
|
|
2896
|
-
&& Object.is(cached.ownerKey, buildOptions.ownerKey)
|
|
2897
|
-
&& Object.is(cached.sharedContext, buildOptions.sharedContext)) {
|
|
2898
|
-
return cached.block;
|
|
2899
|
-
}
|
|
2900
|
-
const lines = buildChatMessageLines(message, maxWidth, buildOptions);
|
|
2901
|
-
const block = lines.length > 0 ? { kind: "lines", variant: "event", lines } : null;
|
|
2902
|
-
chatLineBlockCache.set(message, {
|
|
2903
|
-
block,
|
|
2904
|
-
maxWidth,
|
|
2905
|
-
text: message.text,
|
|
2906
|
-
mobileText: message.mobileText,
|
|
2907
|
-
role: message.role,
|
|
2908
|
-
time: message.time,
|
|
2909
|
-
createdAt: message.createdAt,
|
|
2910
|
-
splash: message.splash,
|
|
2911
|
-
thinking: message.thinking,
|
|
2912
|
-
thinkingLabel: message.thinkingLabel,
|
|
2913
|
-
progressKind: message.progressKind,
|
|
2914
|
-
noChrome: message.noChrome,
|
|
2915
|
-
cardTitle: message.cardTitle,
|
|
2916
|
-
cardTitleColor: message.cardTitleColor,
|
|
2917
|
-
cardBorderColor: message.cardBorderColor,
|
|
2918
|
-
stopped: message.stopped,
|
|
2919
|
-
tableMode: buildOptions.tableMode,
|
|
2920
|
-
viewerKey: buildOptions.viewerKey,
|
|
2921
|
-
ownerKey: buildOptions.ownerKey,
|
|
2922
|
-
sharedContext: buildOptions.sharedContext,
|
|
2923
|
-
});
|
|
2924
|
-
return block;
|
|
2925
|
-
}
|
|
2926
|
-
|
|
2927
|
-
// A centered inline rule ("──── label ────") for transcript markers. The dash
|
|
2928
|
-
// runs are CAPPED (not stretched to maxWidth): the web portal wraps by pixel
|
|
2929
|
-
// width, and a maxWidth-long "─" run overflows a narrower pane and wraps the
|
|
2930
|
-
// rule mid-label. A short symmetric rule reads as a divider on every width, and
|
|
2931
|
-
// when the label alone will not fit it is rendered bare (wraps as plain text,
|
|
2932
|
-
// never as dangling dash fragments).
|
|
2933
2773
|
function buildRuleLine(label, color, maxWidth) {
|
|
2934
2774
|
const safeWidth = Math.max(24, Number(maxWidth) || 80);
|
|
2935
2775
|
const room = safeWidth - label.length;
|
|
@@ -4383,7 +4223,9 @@ export function selectStatusBar(state) {
|
|
|
4383
4223
|
if (state.ui.modal?.type === "sessionAgentPicker") {
|
|
4384
4224
|
return {
|
|
4385
4225
|
left: "Select an agent for the new session",
|
|
4386
|
-
|
|
4226
|
+
// Enter no longer always creates — on a section header it opens or
|
|
4227
|
+
// closes — and the disclosure keys have to be named somewhere.
|
|
4228
|
+
right: "up/down move · ←/→ open/close · enter start · esc cancel",
|
|
4387
4229
|
};
|
|
4388
4230
|
}
|
|
4389
4231
|
if (state.ui.modal?.type === "sessionGroupPicker") {
|
|
@@ -5608,36 +5450,52 @@ export function selectSessionAgentPickerModal(state, maxWidth = 76) {
|
|
|
5608
5450
|
const items = Array.isArray(modal.items) ? modal.items : [];
|
|
5609
5451
|
const selectedIndex = Math.max(0, Number(modal.selectedIndex) || 0);
|
|
5610
5452
|
const contentWidth = Math.max(24, maxWidth - 4);
|
|
5611
|
-
// Items
|
|
5612
|
-
//
|
|
5613
|
-
//
|
|
5614
|
-
// mechanism the model picker uses.
|
|
5615
|
-
const GROUP_HEADINGS = { shared: "Shared", mine: "My agents", generic: "" };
|
|
5616
|
-
// Package-scope vocabulary only makes sense when packages exist: a pure
|
|
5617
|
-
// baked deployment keeps the classic flat list, no headings.
|
|
5618
|
-
const hasPackageItems = items.some((item) => item?.packageName);
|
|
5453
|
+
// Items ARE rows, headings included: navigation walks onto a heading and
|
|
5454
|
+
// Enter toggles it, so every section is reachable from the keyboard.
|
|
5455
|
+
// rowItemIndexes stays 1:1 for the renderers that map a click back to an
|
|
5456
|
+
// item (the same mechanism the model picker uses).
|
|
5619
5457
|
const rows = [];
|
|
5620
5458
|
const rowItemIndexes = [];
|
|
5621
|
-
let lastGroup = null;
|
|
5622
5459
|
items.forEach((item, index) => {
|
|
5623
|
-
const group = item?.group || "shared";
|
|
5624
|
-
if (hasPackageItems && group !== lastGroup) {
|
|
5625
|
-
const heading = GROUP_HEADINGS[group] ?? "";
|
|
5626
|
-
rows.push([{ text: heading ? `── ${heading} ──` : "──", color: "gray" }]);
|
|
5627
|
-
rowItemIndexes.push(null);
|
|
5628
|
-
lastGroup = group;
|
|
5629
|
-
}
|
|
5630
5460
|
const isSelected = index === selectedIndex;
|
|
5631
|
-
const
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5461
|
+
const indent = " ".repeat(Math.max(0, Number(item?.depth) || 0) * 2);
|
|
5462
|
+
let labelRuns;
|
|
5463
|
+
|
|
5464
|
+
if (item?.kind === "section") {
|
|
5465
|
+
const arrow = item.collapsed ? "▸" : "▾";
|
|
5466
|
+
// Only when the row's scope disagrees with the shelf it is on:
|
|
5467
|
+
// an admin sees other users' user-scoped packages, and those land
|
|
5468
|
+
// under Shared while not being shared at all. Otherwise the
|
|
5469
|
+
// section heading already carries it.
|
|
5470
|
+
const scopeBadge = item.sectionKind === "package" && item.packageScope === "user" && !item.mine
|
|
5471
|
+
? " [private]"
|
|
5472
|
+
: "";
|
|
5473
|
+
// The count runs INLINE after the title, not right-aligned with
|
|
5474
|
+
// padding. `contentWidth` is a character count, and the portal's
|
|
5475
|
+
// row is a fluid-width button — padding a row out to a guessed
|
|
5476
|
+
// column width simply overflowed it, and the run wrapped mid-phrase
|
|
5477
|
+
// ("2 / entries · 4 agents"). Inline can only ever break at a
|
|
5478
|
+
// space.
|
|
5479
|
+
labelRuns = fitRuns([
|
|
5480
|
+
{ text: `${indent}${arrow} `, color: "cyan" },
|
|
5481
|
+
{ text: item.title || "Section", color: "white", bold: true },
|
|
5482
|
+
...(scopeBadge ? [{ text: scopeBadge, color: "cyan" }] : []),
|
|
5483
|
+
...(item.meta ? [{ text: ` · ${item.meta}`, color: "gray" }] : []),
|
|
5484
|
+
], contentWidth);
|
|
5485
|
+
} else {
|
|
5486
|
+
const callable = item?.kind === "generic" || item?.supportsDirectStart !== false;
|
|
5487
|
+
// A sub-agent that cannot be started is shown, not hidden — seeing
|
|
5488
|
+
// what a package is made of is most of the value of grouping it.
|
|
5489
|
+
// Dimming plus the └ connector carries it; the detail pane spells
|
|
5490
|
+
// out why. An extra "called only" tag on the row was redundant with
|
|
5491
|
+
// both, and read as a sentence fragment.
|
|
5492
|
+
const glyph = item?.kind === "generic" ? "○ " : (item?.depth || 0) > 0 && item?.parentAgentName ? "└ " : "★ ";
|
|
5493
|
+
labelRuns = fitRuns([
|
|
5494
|
+
{ text: `${indent}${glyph}`, color: callable ? "yellow" : "gray" },
|
|
5495
|
+
{ text: item?.title || item?.agentName || item?.id || "Agent", color: callable ? "white" : "gray", bold: callable },
|
|
5496
|
+
], contentWidth);
|
|
5497
|
+
}
|
|
5498
|
+
|
|
5641
5499
|
rows.push(isSelected
|
|
5642
5500
|
? buildActiveHighlightLine(labelRuns.map((run) => run.text).join("").padEnd(contentWidth, " "))
|
|
5643
5501
|
: labelRuns);
|
|
@@ -5647,7 +5505,29 @@ export function selectSessionAgentPickerModal(state, maxWidth = 76) {
|
|
|
5647
5505
|
const selectedItem = items[selectedIndex] || null;
|
|
5648
5506
|
const selectedModel = modal.sessionOptions?.model || null;
|
|
5649
5507
|
const selectedReasoningEffort = modal.sessionOptions?.reasoningEffort || null;
|
|
5650
|
-
const detailsLines = selectedItem
|
|
5508
|
+
const detailsLines = selectedItem?.kind === "section"
|
|
5509
|
+
? [
|
|
5510
|
+
[{ text: selectedItem.title || "Section", color: "white", bold: true }],
|
|
5511
|
+
...(selectedItem.sectionKind === "package"
|
|
5512
|
+
? [
|
|
5513
|
+
[{ text: `${selectedItem.packageName || "package"}@${selectedItem.packageSemver || "?"}`, color: "gray" }],
|
|
5514
|
+
...(selectedItem.ownerLabel ? [[{ text: `Published by ${selectedItem.ownerLabel}`, color: "gray" }]] : []),
|
|
5515
|
+
]
|
|
5516
|
+
: [[{
|
|
5517
|
+
text: selectedItem.sectionKind === "builtin"
|
|
5518
|
+
? "Agents baked into this deployment."
|
|
5519
|
+
: "Agent packages installed on this deployment.",
|
|
5520
|
+
color: "gray",
|
|
5521
|
+
}]]),
|
|
5522
|
+
[{ text: "", color: "gray" }],
|
|
5523
|
+
[{ text: selectedItem.meta || "", color: "gray" }],
|
|
5524
|
+
[{ text: "", color: "gray" }],
|
|
5525
|
+
[{
|
|
5526
|
+
text: selectedItem.collapsed ? "Enter or → to open." : "Enter or ← to close.",
|
|
5527
|
+
color: "gray",
|
|
5528
|
+
}],
|
|
5529
|
+
]
|
|
5530
|
+
: selectedItem
|
|
5651
5531
|
? [
|
|
5652
5532
|
[{
|
|
5653
5533
|
text: selectedItem.title || selectedItem.agentName || selectedItem.id || "Agent",
|
|
@@ -5660,6 +5540,12 @@ export function selectSessionAgentPickerModal(state, maxWidth = 76) {
|
|
|
5660
5540
|
...(selectedItem.packageName
|
|
5661
5541
|
? [[{ text: `Package: ${selectedItem.packageName}@${selectedItem.packageSemver || "?"} · ${selectedItem.packageScope || "shared"}`, color: "gray" }]]
|
|
5662
5542
|
: (selectedItem.kind === "agent" ? [[{ text: "Built-in agent", color: "gray" }]] : [])),
|
|
5543
|
+
...(selectedItem.startedBy?.length
|
|
5544
|
+
? [[{ text: `Started by: ${selectedItem.startedBy.join(", ")}`, color: "gray" }]]
|
|
5545
|
+
: []),
|
|
5546
|
+
...(selectedItem.kind !== "generic" && selectedItem.supportsDirectStart === false
|
|
5547
|
+
? [[{ text: "Cannot be started on its own", color: "yellow" }]]
|
|
5548
|
+
: []),
|
|
5663
5549
|
...(selectedModel ? [[{ text: `Model: ${selectedModel}`, color: "gray" }]] : []),
|
|
5664
5550
|
...(selectedReasoningEffort ? [[{ text: `Reasoning: ${selectedReasoningEffort}`, color: "gray" }]] : []),
|
|
5665
5551
|
[{ text: "", color: "gray" }],
|
|
@@ -7625,6 +7511,10 @@ export function selectThemePickerModal(state, maxWidth = 80) {
|
|
|
7625
7511
|
const detailsLines = selectedItem
|
|
7626
7512
|
? [
|
|
7627
7513
|
[{ text: `theme: ${selectedItem.description || "Shared theme for the portal and native TUI."}`, color: "white" }],
|
|
7514
|
+
// The portal draws group headings between rows; the TUI list is a
|
|
7515
|
+
// flat scroller with no room for them, so the group is surfaced
|
|
7516
|
+
// here instead. The ORDER still clusters by group in both.
|
|
7517
|
+
...(selectedItem.group ? [[{ text: `group: ${selectedItem.group}`, color: "gray" }]] : []),
|
|
7628
7518
|
{ text: "", color: "gray" },
|
|
7629
7519
|
[{
|
|
7630
7520
|
text: previewingTheme
|
package/ui/core/src/state.js
CHANGED
|
@@ -162,7 +162,8 @@ export function createInitialState({ mode = "local", branding = null, docs = nul
|
|
|
162
162
|
nodeMapSelectedNode: null,
|
|
163
163
|
sequenceExpandedTurns: [],
|
|
164
164
|
sequenceSelectedTurn: null,
|
|
165
|
-
// "rich"
|
|
165
|
+
// "rich" was retired as a view mode; a persisted "rich" falls
|
|
166
|
+
// through to the transcript.
|
|
166
167
|
chatViewMode: chatViewMode === "summary" ? "summary" : "transcript",
|
|
167
168
|
statsViewMode: "session",
|
|
168
169
|
prompt: "",
|
|
@@ -174,6 +175,10 @@ export function createInitialState({ mode = "local", branding = null, docs = nul
|
|
|
174
175
|
// A persisted theme id may name a theme that no longer ships —
|
|
175
176
|
// fall back to the default instead of crashing theme consumers.
|
|
176
177
|
themeId: getTheme(themeId)?.id || DEFAULT_THEME_ID,
|
|
178
|
+
// Touch-friendly scale: bumps type and control sizes one step. A phone in
|
|
179
|
+
// portrait is not the only case — a wall display or a shared screen wants
|
|
180
|
+
// the same thing — so it is a preference, not a viewport query.
|
|
181
|
+
touchScale: false,
|
|
177
182
|
modal: null,
|
|
178
183
|
fullscreenPane: null,
|
|
179
184
|
layout: {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { createTheme } from "./helpers.js";
|
|
1
|
+
import { createTheme, THEME_GROUP_VSCODE } from "./helpers.js";
|
|
2
2
|
|
|
3
3
|
const catppuccinMochaTheme = createTheme({
|
|
4
4
|
id: "catppuccin-mocha",
|
|
5
5
|
label: "Catppuccin Mocha",
|
|
6
6
|
description: "Creamy dark palette with soft pastels and high legibility.",
|
|
7
|
+
group: THEME_GROUP_VSCODE,
|
|
7
8
|
page: {
|
|
8
9
|
background: "#1e1e2e",
|
|
9
10
|
foreground: "#cdd6f4",
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { createTheme } from "./helpers.js";
|
|
1
|
+
import { createTheme, THEME_GROUP_VSCODE } from "./helpers.js";
|
|
2
2
|
|
|
3
3
|
const cobalt2Theme = createTheme({
|
|
4
4
|
id: "cobalt2",
|
|
5
5
|
label: "Cobalt2",
|
|
6
6
|
description: "Electric blue shell with bright yellow accents and saturated syntax colors.",
|
|
7
|
+
group: THEME_GROUP_VSCODE,
|
|
7
8
|
page: {
|
|
8
9
|
background: "#193549",
|
|
9
10
|
foreground: "#ffffff",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createTheme } from "./helpers.js";
|
|
1
|
+
import { createTheme, THEME_GROUP_RETRO } from "./helpers.js";
|
|
2
2
|
|
|
3
3
|
// DOOM (id Software, 1993) — the status bar, not the shooter.
|
|
4
4
|
//
|
|
@@ -19,6 +19,7 @@ const doomTheme = createTheme({
|
|
|
19
19
|
id: "doom",
|
|
20
20
|
label: "DOOM",
|
|
21
21
|
description: "STBAR brown metal, bone type, and the three keycards as the status palette.",
|
|
22
|
+
group: THEME_GROUP_RETRO,
|
|
22
23
|
// The face, drawn as data rather than markup so ui/core stays free of any
|
|
23
24
|
// framework and nothing has to inject raw HTML. The portal renders it in
|
|
24
25
|
// place of the deployment's logo; the TUI ignores it.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { createTheme } from "./helpers.js";
|
|
1
|
+
import { createTheme, THEME_GROUP_VSCODE } from "./helpers.js";
|
|
2
2
|
|
|
3
3
|
const draculaTheme = createTheme({
|
|
4
4
|
id: "dracula",
|
|
5
5
|
label: "Dracula",
|
|
6
6
|
description: "Classic Dracula contrast with neon green, pink, and violet accents.",
|
|
7
|
+
group: THEME_GROUP_VSCODE,
|
|
7
8
|
page: {
|
|
8
9
|
background: "#282a36",
|
|
9
10
|
foreground: "#f8f8f2",
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { createTheme } from "./helpers.js";
|
|
1
|
+
import { createTheme, THEME_GROUP_VSCODE } from "./helpers.js";
|
|
2
2
|
|
|
3
3
|
const githubDarkTheme = createTheme({
|
|
4
4
|
id: "github-dark",
|
|
5
5
|
label: "GitHub Dark",
|
|
6
6
|
description: "GitHub dark palette with soft blue accents and cool neutrals.",
|
|
7
|
+
group: THEME_GROUP_VSCODE,
|
|
7
8
|
page: {
|
|
8
9
|
background: "#0d1117",
|
|
9
10
|
foreground: "#e6edf3",
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { createTheme } from "./helpers.js";
|
|
1
|
+
import { createTheme, THEME_GROUP_VSCODE } from "./helpers.js";
|
|
2
2
|
|
|
3
3
|
const githubLightTheme = createTheme({
|
|
4
4
|
id: "github-light",
|
|
5
5
|
label: "GitHub Light",
|
|
6
6
|
description: "GitHub light palette with crisp neutrals, blue actions, and high-contrast status colors.",
|
|
7
|
+
group: THEME_GROUP_VSCODE,
|
|
7
8
|
page: {
|
|
8
9
|
// Canvas, deliberately NOT the same as tui.surface (#ffffff). When the
|
|
9
10
|
// page and the panes are both pure white there is nothing for a pane to
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { createTheme } from "./helpers.js";
|
|
1
|
+
import { createTheme, THEME_GROUP_VSCODE } from "./helpers.js";
|
|
2
2
|
|
|
3
3
|
const gruvboxDarkTheme = createTheme({
|
|
4
4
|
id: "gruvbox-dark",
|
|
5
5
|
label: "Gruvbox Dark",
|
|
6
6
|
description: "Retro warm palette with earthy browns, oranges, and high readability.",
|
|
7
|
+
group: THEME_GROUP_VSCODE,
|
|
7
8
|
page: {
|
|
8
9
|
background: "#282828",
|
|
9
10
|
foreground: "#ebdbb2",
|