pilotswarm 0.5.26 → 0.5.27
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 +1 -1
- package/ui/core/src/controller.js +49 -4
- package/ui/core/src/selectors.js +52 -6
- package/ui/react/src/web-app.js +426 -80
- package/web/dist/assets/{abnfDiagram-VRR7QNED-C4qRR7va.js → abnfDiagram-VRR7QNED-DOZGtiQC.js} +1 -1
- package/web/dist/assets/{arc-BQw2V2pL.js → arc-CNT314TJ.js} +1 -1
- package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-DMdWnLGG.js → architectureDiagram-ZJ3FMSHR-Cmi5R1em.js} +1 -1
- package/web/dist/assets/{blockDiagram-677ZJIJ3-DZnALw8j.js → blockDiagram-677ZJIJ3-CFQt6Pnc.js} +1 -1
- package/web/dist/assets/{c4Diagram-LMCZKHZV-BGGZ42iX.js → c4Diagram-LMCZKHZV-v7-eGb6Y.js} +1 -1
- package/web/dist/assets/channel-DHDU75Fc.js +1 -0
- package/web/dist/assets/{chunk-2Q5K7J3B-Dastiegd.js → chunk-2Q5K7J3B-DoLSc3k6.js} +1 -1
- package/web/dist/assets/{chunk-32BRIVSS-B4XexRzf.js → chunk-32BRIVSS-l-i2NvX3.js} +1 -1
- package/web/dist/assets/{chunk-5VM5RSS4-D66MNGCN.js → chunk-5VM5RSS4-B3s5kXyR.js} +1 -1
- package/web/dist/assets/{chunk-EX3LRPZG-DeqPS4Xm.js → chunk-EX3LRPZG-Ckctm_wZ.js} +1 -1
- package/web/dist/assets/{chunk-JWPE2WC7-DIMWiXpM.js → chunk-JWPE2WC7-D5reV4oK.js} +1 -1
- package/web/dist/assets/{chunk-MOJQB5TN-vTyL0qgx.js → chunk-MOJQB5TN-B-bgmTfa.js} +1 -1
- package/web/dist/assets/{chunk-RYQCIY6F-C7U9MKGw.js → chunk-RYQCIY6F-CLliUW3T.js} +1 -1
- package/web/dist/assets/{chunk-V7JOEXUC-BzPwQ7n3.js → chunk-V7JOEXUC-CsAyO1PL.js} +1 -1
- package/web/dist/assets/{chunk-VR4S4FIN-CmDsyK3b.js → chunk-VR4S4FIN-Df6VMLs3.js} +1 -1
- package/web/dist/assets/{chunk-XXDRQBXY-ZJn6BaGL.js → chunk-XXDRQBXY-pOkdkM00.js} +1 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-DkWfQ9TO.js +1 -0
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-DkWfQ9TO.js +1 -0
- package/web/dist/assets/{cose-bilkent-JH36ORCC-CAA8JZZM.js → cose-bilkent-JH36ORCC-DF9LzGIn.js} +1 -1
- package/web/dist/assets/{cynefin-VYW2F7L2-B6pX-lUD.js → cynefin-VYW2F7L2-COwlV5eq.js} +1 -1
- package/web/dist/assets/{cynefinDiagram-TSTJHNR4-sKTHU_UH.js → cynefinDiagram-TSTJHNR4-Cmf8ISYO.js} +1 -1
- package/web/dist/assets/{dagre-VKFMJZFB-EuakJ578.js → dagre-VKFMJZFB-DanZ5sUs.js} +1 -1
- package/web/dist/assets/{diagram-FQU43EPY-DR3M_pic.js → diagram-FQU43EPY-xJWijzy7.js} +1 -1
- package/web/dist/assets/{diagram-G47NLZAW-BlS6yM0u.js → diagram-G47NLZAW-C2V1yOvV.js} +1 -1
- package/web/dist/assets/{diagram-NH7WQ7WH-CQNXe6dc.js → diagram-NH7WQ7WH-C8SrNQ_i.js} +1 -1
- package/web/dist/assets/{diagram-OA4YK3LP-BizBeSQo.js → diagram-OA4YK3LP-BATn2U0p.js} +1 -1
- package/web/dist/assets/{diagram-WEI45ONY-C-_KM6Gg.js → diagram-WEI45ONY-Dk1xkd8r.js} +1 -1
- package/web/dist/assets/{ebnfDiagram-CCIWWBDH-BapWEdic.js → ebnfDiagram-CCIWWBDH-JFfByguj.js} +1 -1
- package/web/dist/assets/{erDiagram-Q63AITRT-Btijn1Xj.js → erDiagram-Q63AITRT-DnlU9tGx.js} +1 -1
- package/web/dist/assets/{flowDiagram-23GEKE2U-D8zK8HkH.js → flowDiagram-23GEKE2U-Dnq_XrQ8.js} +1 -1
- package/web/dist/assets/{ganttDiagram-NO4QXBWP-DyjkOIMW.js → ganttDiagram-NO4QXBWP-MzgQ4Gkh.js} +1 -1
- package/web/dist/assets/{gitGraphDiagram-IHSO6WYX-3r9_QeYN.js → gitGraphDiagram-IHSO6WYX-C2LwEO5a.js} +1 -1
- package/web/dist/assets/{index-CIt9PdC-.js → index-BkjhSyx0.js} +2 -2
- package/web/dist/assets/{index-CT2_MRcC.css → index-DyL_-2Du.css} +1 -1
- package/web/dist/assets/{infoDiagram-FWYZ7A6U-Ccd8ACUz.js → infoDiagram-FWYZ7A6U-UMiiYK4p.js} +1 -1
- package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-DPRUC8V2.js → ishikawaDiagram-FXEZZL3T-CtzROVDb.js} +1 -1
- package/web/dist/assets/{journeyDiagram-5HDEW3XC-CkvayMsL.js → journeyDiagram-5HDEW3XC-C5UU06Ip.js} +1 -1
- package/web/dist/assets/{kanban-definition-HUTT4EX6-CAK9Z-ha.js → kanban-definition-HUTT4EX6-DYwJ4PpM.js} +1 -1
- package/web/dist/assets/{linear-B-_ZjN2w.js → linear-Dg7W_aPc.js} +1 -1
- package/web/dist/assets/{mermaid.core-BN9c--2r.js → mermaid.core-OUiCmXKB.js} +4 -4
- package/web/dist/assets/{mindmap-definition-LN4V7U3C-DuD_AHRU.js → mindmap-definition-LN4V7U3C-ffFo5mVE.js} +1 -1
- package/web/dist/assets/papaparse.min-DPfbFiel.js +10 -0
- package/web/dist/assets/{pegDiagram-2B236MQR-Cc1v58KA.js → pegDiagram-2B236MQR-DJ0r7gkW.js} +1 -1
- package/web/dist/assets/{pieDiagram-ENE6RG2P-Bc32--ur.js → pieDiagram-ENE6RG2P-BnpWDtAd.js} +1 -1
- package/web/dist/assets/pilotswarm-CU6jtK4w.js +94 -0
- package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-DpPTc2EP.js → quadrantDiagram-ABIIQ3AL-Bcxo8lQJ.js} +1 -1
- package/web/dist/assets/{railroadDiagram-RFXS5EU6-DXMzwejw.js → railroadDiagram-RFXS5EU6-YtDm2Hhr.js} +1 -1
- package/web/dist/assets/{requirementDiagram-TGXJPOKE-B3ImAxw-.js → requirementDiagram-TGXJPOKE-DIIFSM-x.js} +1 -1
- package/web/dist/assets/{sankeyDiagram-HTMAVEWB-hmVMffE2.js → sankeyDiagram-HTMAVEWB-BH3EkrzA.js} +1 -1
- package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-D53fk6uF.js → sequenceDiagram-DBY2YBRQ-DjD5j0HL.js} +1 -1
- package/web/dist/assets/{sizeCapture-X5ZJPWSS-4U-I5d2g.js → sizeCapture-X5ZJPWSS-L5FPJ9w8.js} +1 -1
- package/web/dist/assets/{stateDiagram-2N3HPSRC-0Y5TgWhW.js → stateDiagram-2N3HPSRC-o-MgnF-b.js} +1 -1
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-DkGZm6xe.js +1 -0
- package/web/dist/assets/{swimlanes-5IMT3BWC-Umhy4qUu.js → swimlanes-5IMT3BWC-B_7FbL2G.js} +2 -2
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-D9vnXGSI.js +8 -0
- package/web/dist/assets/{timeline-definition-FHXFAJF6-D5hOg3Qu.js → timeline-definition-FHXFAJF6-C8qUL6LV.js} +1 -1
- package/web/dist/assets/{vennDiagram-L72KCM5P-QVIcE3UD.js → vennDiagram-L72KCM5P-wS-9VnAs.js} +1 -1
- package/web/dist/assets/{wardleyDiagram-EHGQE667-BEj1M2cB.js → wardleyDiagram-EHGQE667-Cz8fglhX.js} +1 -1
- package/web/dist/assets/{xychartDiagram-FW5EYKEG-xojRIXIf.js → xychartDiagram-FW5EYKEG-CmsR-UfQ.js} +1 -1
- package/web/dist/index.html +3 -3
- package/web/dist/assets/channel-CB157dQ5.js +0 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-B6JwBYlv.js +0 -1
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-B6JwBYlv.js +0 -1
- package/web/dist/assets/pilotswarm-BgzdDy09.js +0 -93
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-DqIcGfAY.js +0 -1
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-DA9txLi6.js +0 -8
package/ui/react/src/web-app.js
CHANGED
|
@@ -12,6 +12,9 @@ import {
|
|
|
12
12
|
computeLegacyLayout,
|
|
13
13
|
createInitialState,
|
|
14
14
|
createStore,
|
|
15
|
+
formatCompactNumber,
|
|
16
|
+
formatCronTimestampForClient,
|
|
17
|
+
formatHumanDurationSeconds,
|
|
15
18
|
getPromptInputRows,
|
|
16
19
|
getTheme,
|
|
17
20
|
isThemeLight,
|
|
@@ -136,6 +139,9 @@ function supportsLocalFileOpen(controller) {
|
|
|
136
139
|
}
|
|
137
140
|
|
|
138
141
|
const SESSION_LINK_COPIED_STATUS = "Session link copied to clipboard";
|
|
142
|
+
// Mirrors SESSION_NAV_SETTLE_MS in the controller: the access probe rides the
|
|
143
|
+
// same "wait for the selection to stop moving" rule as the session fetch.
|
|
144
|
+
const SESSION_ACCESS_SETTLE_MS = 140;
|
|
139
145
|
const SESSION_LINK_PRIVATE_WARNING = "Only people with access can open this link.";
|
|
140
146
|
|
|
141
147
|
function buildSessionLinkUrl(sessionId) {
|
|
@@ -1080,6 +1086,194 @@ function stripYamlFrontmatter(content) {
|
|
|
1080
1086
|
return text.slice(3 + close.index + close[0].length);
|
|
1081
1087
|
}
|
|
1082
1088
|
|
|
1089
|
+
const TABULAR_ARTIFACT_RE = /\.(csv|tsv)$/i;
|
|
1090
|
+
|
|
1091
|
+
function isTabularArtifact(filename, contentType) {
|
|
1092
|
+
if (TABULAR_ARTIFACT_RE.test(String(filename || ""))) return true;
|
|
1093
|
+
return /^text\/(csv|tab-separated-values)$/i.test(String(contentType || "").trim());
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* Tabular preview.
|
|
1098
|
+
*
|
|
1099
|
+
* Parsing is delegated to Papa Parse rather than split(",") because the cases
|
|
1100
|
+
* that break naive parsers are exactly the ones real exports contain: quoted
|
|
1101
|
+
* fields holding commas, embedded newlines inside a quoted cell, and escaped
|
|
1102
|
+
* quotes. It is loaded lazily so opening a .md never pays for the parser.
|
|
1103
|
+
*
|
|
1104
|
+
* Rendering is our own table, not a viewer library's: the preview has to
|
|
1105
|
+
* inherit the active theme's chrome (Win95 bevels, MS-DOS reverse video), and
|
|
1106
|
+
* a drop-in viewer ships its own styling that cannot participate in that.
|
|
1107
|
+
*/
|
|
1108
|
+
const TABULAR_MAX_ROWS = 2000;
|
|
1109
|
+
|
|
1110
|
+
/**
|
|
1111
|
+
* Quote a cell for TSV the way a spreadsheet expects: only when the value
|
|
1112
|
+
* would otherwise break the row/column framing.
|
|
1113
|
+
*/
|
|
1114
|
+
function tsvCell(value) {
|
|
1115
|
+
const text = String(value ?? "");
|
|
1116
|
+
return /[\t\n\r"]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
function TabularPreview({ content, filename }) {
|
|
1120
|
+
const [parsed, setParsed] = React.useState(null);
|
|
1121
|
+
const wrapRef = React.useRef(null);
|
|
1122
|
+
const tableRef = React.useRef(null);
|
|
1123
|
+
const anchorCellRef = React.useRef(null);
|
|
1124
|
+
|
|
1125
|
+
React.useEffect(() => {
|
|
1126
|
+
let cancelled = false;
|
|
1127
|
+
setParsed(null);
|
|
1128
|
+
import("papaparse")
|
|
1129
|
+
.then((mod) => {
|
|
1130
|
+
if (cancelled) return;
|
|
1131
|
+
const Papa = mod.default || mod;
|
|
1132
|
+
const delimiter = /\.tsv$/i.test(String(filename || "")) ? "\t" : "";
|
|
1133
|
+
const out = Papa.parse(String(content || "").trim(), {
|
|
1134
|
+
delimiter,
|
|
1135
|
+
skipEmptyLines: "greedy",
|
|
1136
|
+
});
|
|
1137
|
+
setParsed({ rows: out.data || [], errors: out.errors || [] });
|
|
1138
|
+
})
|
|
1139
|
+
.catch(() => { if (!cancelled) setParsed({ rows: null, errors: [{ message: "parser unavailable" }] }); });
|
|
1140
|
+
return () => { cancelled = true; };
|
|
1141
|
+
}, [content, filename]);
|
|
1142
|
+
|
|
1143
|
+
// The drag can end anywhere, including outside the pane or the window.
|
|
1144
|
+
React.useEffect(() => {
|
|
1145
|
+
const release = () => { anchorCellRef.current = null; };
|
|
1146
|
+
window.addEventListener("mouseup", release);
|
|
1147
|
+
return () => window.removeEventListener("mouseup", release);
|
|
1148
|
+
}, []);
|
|
1149
|
+
|
|
1150
|
+
/** Select whole cells: snap both range ends to cell boundaries. */
|
|
1151
|
+
const selectCellSpan = React.useCallback((a, b) => {
|
|
1152
|
+
if (!a || !b) return;
|
|
1153
|
+
const selection = window.getSelection();
|
|
1154
|
+
if (!selection) return;
|
|
1155
|
+
const backwards = !!(a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_PRECEDING);
|
|
1156
|
+
const range = document.createRange();
|
|
1157
|
+
range.setStartBefore(backwards ? b : a);
|
|
1158
|
+
range.setEndAfter(backwards ? a : b);
|
|
1159
|
+
selection.removeAllRanges();
|
|
1160
|
+
selection.addRange(range);
|
|
1161
|
+
}, []);
|
|
1162
|
+
|
|
1163
|
+
const cellFrom = (event) => (event.target?.closest ? event.target.closest("td,th") : null);
|
|
1164
|
+
|
|
1165
|
+
const onMouseDown = React.useCallback((event) => {
|
|
1166
|
+
if (event.button !== 0) return;
|
|
1167
|
+
const cell = cellFrom(event);
|
|
1168
|
+
if (!cell) return;
|
|
1169
|
+
// Suppress the native character-level selection before it starts, then
|
|
1170
|
+
// take focus by hand (preventDefault would otherwise deny it, and the
|
|
1171
|
+
// wrapper needs focus for Ctrl+A to reach us).
|
|
1172
|
+
event.preventDefault();
|
|
1173
|
+
wrapRef.current?.focus();
|
|
1174
|
+
anchorCellRef.current = cell;
|
|
1175
|
+
selectCellSpan(cell, cell);
|
|
1176
|
+
}, [selectCellSpan]);
|
|
1177
|
+
|
|
1178
|
+
const onMouseMove = React.useCallback((event) => {
|
|
1179
|
+
if (!anchorCellRef.current || !(event.buttons & 1)) return;
|
|
1180
|
+
const cell = cellFrom(event);
|
|
1181
|
+
if (cell) selectCellSpan(anchorCellRef.current, cell);
|
|
1182
|
+
}, [selectCellSpan]);
|
|
1183
|
+
|
|
1184
|
+
// Ctrl/Cmd+A selects the TABLE, not the page. Scoped to the pane, so it only
|
|
1185
|
+
// applies once the table has focus.
|
|
1186
|
+
const onKeyDown = React.useCallback((event) => {
|
|
1187
|
+
if (!(event.metaKey || event.ctrlKey) || event.altKey) return;
|
|
1188
|
+
if (event.key !== "a" && event.key !== "A") return;
|
|
1189
|
+
const table = tableRef.current;
|
|
1190
|
+
const selection = window.getSelection();
|
|
1191
|
+
if (!table || !selection) return;
|
|
1192
|
+
event.preventDefault();
|
|
1193
|
+
event.stopPropagation();
|
|
1194
|
+
const range = document.createRange();
|
|
1195
|
+
range.selectNodeContents(table);
|
|
1196
|
+
selection.removeAllRanges();
|
|
1197
|
+
selection.addRange(range);
|
|
1198
|
+
}, []);
|
|
1199
|
+
|
|
1200
|
+
/**
|
|
1201
|
+
* Emit real TSV rather than trusting each browser's table serializer, so a
|
|
1202
|
+
* paste into a spreadsheet lands in the right cells every time.
|
|
1203
|
+
*/
|
|
1204
|
+
const onCopy = React.useCallback((event) => {
|
|
1205
|
+
const table = tableRef.current;
|
|
1206
|
+
const selection = window.getSelection();
|
|
1207
|
+
if (!table || !selection || selection.rangeCount === 0) return;
|
|
1208
|
+
const range = selection.getRangeAt(0);
|
|
1209
|
+
const rows = new Map();
|
|
1210
|
+
for (const cell of table.querySelectorAll("th,td")) {
|
|
1211
|
+
if (!range.intersectsNode(cell)) continue;
|
|
1212
|
+
const row = cell.parentElement;
|
|
1213
|
+
if (!rows.has(row)) rows.set(row, []);
|
|
1214
|
+
rows.get(row).push(tsvCell(cell.textContent));
|
|
1215
|
+
}
|
|
1216
|
+
// Nothing of the table is selected — leave the event alone.
|
|
1217
|
+
if (rows.size === 0) return;
|
|
1218
|
+
event.clipboardData.setData("text/plain", Array.from(rows.values(), (cells) => cells.join("\t")).join("\n"));
|
|
1219
|
+
event.preventDefault();
|
|
1220
|
+
}, []);
|
|
1221
|
+
|
|
1222
|
+
if (!parsed) {
|
|
1223
|
+
return React.createElement("div", { className: "ps-csv-status" }, "Parsing…");
|
|
1224
|
+
}
|
|
1225
|
+
// Fall back to the raw text rather than showing nothing: a file that will
|
|
1226
|
+
// not parse is still readable, and hiding it would be worse than plain.
|
|
1227
|
+
if (!parsed.rows || parsed.rows.length === 0) {
|
|
1228
|
+
return React.createElement("pre", { className: "ps-md-code-pre" },
|
|
1229
|
+
React.createElement("code", null, String(content || "")));
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
const [header, ...body] = parsed.rows;
|
|
1233
|
+
|
|
1234
|
+
// Width is the WIDEST row, not the header's. A data row with more cells
|
|
1235
|
+
// than the header is malformed but common (trailing delimiters, a short
|
|
1236
|
+
// header), and papaparse reports no error for it — iterating the header
|
|
1237
|
+
// would silently drop those cells, which is the one thing a data viewer
|
|
1238
|
+
// must never do. Reduce, not Math.max(...rows): spreading tens of
|
|
1239
|
+
// thousands of arguments overflows the stack.
|
|
1240
|
+
const columnCount = body.reduce((max, row) => Math.max(max, row.length), header.length);
|
|
1241
|
+
const columns = Array.from({ length: columnCount }, (_, i) => header[i] ?? "");
|
|
1242
|
+
|
|
1243
|
+
// Cap the DOM. A 1 MiB CSV (the artifact ceiling) can be ~20k rows, and
|
|
1244
|
+
// 20k × N cells makes the pane unusable. Showing a bounded window and
|
|
1245
|
+
// saying so is better than hanging the tab.
|
|
1246
|
+
const truncated = body.length > TABULAR_MAX_ROWS;
|
|
1247
|
+
const visible = truncated ? body.slice(0, TABULAR_MAX_ROWS) : body;
|
|
1248
|
+
|
|
1249
|
+
// No row/column tally — it is not part of the file, and a footer under the
|
|
1250
|
+
// table is one more thing to avoid when copying. The only notes worth
|
|
1251
|
+
// showing are the ones that say the view is NOT the whole truth.
|
|
1252
|
+
const notes = [];
|
|
1253
|
+
if (truncated) notes.push(`showing first ${TABULAR_MAX_ROWS} of ${body.length} rows`);
|
|
1254
|
+
if (parsed.errors.length) notes.push(`${parsed.errors.length} parse warning${parsed.errors.length === 1 ? "" : "s"}`);
|
|
1255
|
+
|
|
1256
|
+
return React.createElement("div", {
|
|
1257
|
+
className: "ps-csv-wrap",
|
|
1258
|
+
ref: wrapRef,
|
|
1259
|
+
tabIndex: 0,
|
|
1260
|
+
onMouseDown,
|
|
1261
|
+
onMouseMove,
|
|
1262
|
+
onKeyDown,
|
|
1263
|
+
onCopy,
|
|
1264
|
+
},
|
|
1265
|
+
React.createElement("table", { className: "ps-csv-table", ref: tableRef },
|
|
1266
|
+
React.createElement("thead", null,
|
|
1267
|
+
React.createElement("tr", null,
|
|
1268
|
+
columns.map((cell, i) => React.createElement("th", { key: `h${i}` }, String(cell ?? ""))))),
|
|
1269
|
+
React.createElement("tbody", null,
|
|
1270
|
+
visible.map((row, r) => React.createElement("tr", { key: `r${r}` },
|
|
1271
|
+
columns.map((_, c) => React.createElement("td", { key: `c${c}` }, String(row[c] ?? ""))))))),
|
|
1272
|
+
notes.length
|
|
1273
|
+
? React.createElement("div", { className: "ps-csv-status" }, notes.join(" · "))
|
|
1274
|
+
: null);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1083
1277
|
function codeLanguageForArtifact(filename) {
|
|
1084
1278
|
const ext = /\.([A-Za-z0-9]+)$/.exec(String(filename || ""));
|
|
1085
1279
|
if (!ext) return null;
|
|
@@ -2837,13 +3031,130 @@ function ScrollLinesPanel({ title, titleRight = null, color, focused, actions, l
|
|
|
2837
3031
|
// with a guide rail rather than an ASCII "└" prefix.
|
|
2838
3032
|
const SESSION_KIND_GLYPHS = { group: "🗂", system: "⚙", service: "⚗" };
|
|
2839
3033
|
|
|
2840
|
-
|
|
3034
|
+
/**
|
|
3035
|
+
* Selected-session details, pinned to the bottom of the Sessions panel.
|
|
3036
|
+
*
|
|
3037
|
+
* This exists so the ROWS can stay one clean line each. Everything that used
|
|
3038
|
+
* to unfold under the selected row (and, in the rich list, under every row)
|
|
3039
|
+
* lives here instead, at a fixed spot the eye can return to — so switching
|
|
3040
|
+
* sessions no longer reflows the list.
|
|
3041
|
+
*
|
|
3042
|
+
* Web-only by construction: the TUI renders `row.detailRuns` itself and is
|
|
3043
|
+
* untouched by this.
|
|
3044
|
+
*/
|
|
3045
|
+
const SESSION_DETAIL_NONE = "—";
|
|
3046
|
+
|
|
3047
|
+
function SessionDetailBox({ session, childCount = 0 }) {
|
|
3048
|
+
// EVERY field renders on EVERY selection, empty ones as an em dash. The box
|
|
3049
|
+
// is a fixed grid of rows, so moving through the list cannot change its
|
|
3050
|
+
// height — a box that grew and shrank would shove the list under the
|
|
3051
|
+
// cursor mid-scroll. That is also why each value is a single clamped line.
|
|
3052
|
+
const field = (label, value, extraClass = "") => {
|
|
3053
|
+
const text = (value == null || value === "") ? SESSION_DETAIL_NONE : String(value);
|
|
3054
|
+
const empty = text === SESSION_DETAIL_NONE;
|
|
3055
|
+
return React.createElement("div", {
|
|
3056
|
+
className: `ps-session-detail-field${extraClass ? ` ${extraClass}` : ""}${empty ? " is-none" : ""}`,
|
|
3057
|
+
key: label,
|
|
3058
|
+
},
|
|
3059
|
+
React.createElement("span", { className: "ps-session-detail-label" }, label),
|
|
3060
|
+
React.createElement("span", { className: "ps-session-detail-value", title: text }, text));
|
|
3061
|
+
};
|
|
3062
|
+
|
|
3063
|
+
const usage = session?.contextUsage;
|
|
3064
|
+
const hasUsage = usage && Number.isFinite(usage.currentTokens) && Number.isFinite(usage.tokenLimit) && usage.tokenLimit > 0;
|
|
3065
|
+
const percent = hasUsage ? Math.round((usage.currentTokens / usage.tokenLimit) * 100) : null;
|
|
3066
|
+
const context = hasUsage
|
|
3067
|
+
? `${formatCompactNumber(usage.currentTokens)} / ${formatCompactNumber(usage.tokenLimit)} · ${percent}%`
|
|
3068
|
+
: null;
|
|
3069
|
+
|
|
3070
|
+
// "Cron vs not" is a question the list could only answer with a glyph, so
|
|
3071
|
+
// spell it out — an unscheduled session says "off" rather than going blank,
|
|
3072
|
+
// which would be indistinguishable from "unknown".
|
|
3073
|
+
let cron = null;
|
|
3074
|
+
if (session && !session.isGroup) {
|
|
3075
|
+
cron = "off";
|
|
3076
|
+
if (session.cronActive === true) {
|
|
3077
|
+
cron = session.cronKind === "wall-clock"
|
|
3078
|
+
? `next ${formatCronTimestampForClient(session.cronNextFireAt)}`
|
|
3079
|
+
: typeof session.cronInterval === "number"
|
|
3080
|
+
? `every ${formatHumanDurationSeconds(session.cronInterval)}`
|
|
3081
|
+
: "on";
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
const model = session?.model
|
|
3086
|
+
? (session.reasoningEffort && !String(session.model).includes(String(session.reasoningEffort))
|
|
3087
|
+
? `${session.model}:${session.reasoningEffort}`
|
|
3088
|
+
: session.model)
|
|
3089
|
+
: null;
|
|
3090
|
+
|
|
3091
|
+
const access = !session ? null
|
|
3092
|
+
: session.visibility === "shared_write" ? "shared · write"
|
|
3093
|
+
: session.visibility === "shared_read" ? "shared · read"
|
|
3094
|
+
: "private";
|
|
3095
|
+
|
|
3096
|
+
// Groups have members rather than descendants; both answer "how many are
|
|
3097
|
+
// under this row", so they share the field.
|
|
3098
|
+
const children = session?.isGroup
|
|
3099
|
+
? (session.memberCount == null ? null : String(session.memberCount))
|
|
3100
|
+
: (childCount > 0 ? String(childCount) : null);
|
|
3101
|
+
|
|
3102
|
+
return React.createElement("div", { className: "ps-session-detail-box" },
|
|
3103
|
+
field("ID", session?.sessionId, "is-id"),
|
|
3104
|
+
field("Model", model),
|
|
3105
|
+
field("Context", context, percent != null && percent >= 85 ? "is-hot" : percent != null && percent >= 70 ? "is-warm" : ""),
|
|
3106
|
+
field("Cron", cron, session?.cronActive === true ? "is-armed" : ""),
|
|
3107
|
+
field("Agent", session?.agentId),
|
|
3108
|
+
field("Status", session?.status),
|
|
3109
|
+
field("Children", children),
|
|
3110
|
+
field("Access", access));
|
|
3111
|
+
}
|
|
3112
|
+
|
|
3113
|
+
/**
|
|
3114
|
+
* One session row, memoized.
|
|
3115
|
+
*
|
|
3116
|
+
* The list re-renders whenever the selection moves, but `selectSessionRows`
|
|
3117
|
+
* hands back the SAME row object for every row whose inputs did not change, so
|
|
3118
|
+
* memoizing here means a keypress reconciles two rows instead of the whole
|
|
3119
|
+
* fleet. That only holds while every prop is referentially stable — hence the
|
|
3120
|
+
* hoisted click handler and ref setter rather than closures built per row.
|
|
3121
|
+
*/
|
|
3122
|
+
const SessionListRow = React.memo(function SessionListRow({
|
|
3123
|
+
row, theme, rich, structuredRows, mobile, onRowClick, setRef,
|
|
3124
|
+
}) {
|
|
3125
|
+
const ref = React.useCallback((node) => setRef(row.sessionId, node), [setRef, row.sessionId]);
|
|
3126
|
+
const onClick = React.useCallback((event) => onRowClick(event, row), [onRowClick, row]);
|
|
3127
|
+
|
|
3128
|
+
return React.createElement("button", {
|
|
3129
|
+
type: "button",
|
|
3130
|
+
ref,
|
|
3131
|
+
className: `ps-list-button ps-session-list-button${row.active ? " is-selected" : ""}${row.selected ? " is-multiselected" : ""}${row.pinned ? " is-pinned" : ""}`,
|
|
3132
|
+
tabIndex: row.active ? 0 : -1,
|
|
3133
|
+
"aria-selected": row.active ? "true" : "false",
|
|
3134
|
+
onClick,
|
|
3135
|
+
},
|
|
3136
|
+
React.createElement("div", {
|
|
3137
|
+
className: rich ? "ps-session-row-content is-rich" : "ps-line ps-session-row-content",
|
|
3138
|
+
style: {
|
|
3139
|
+
paddingInlineStart: rich
|
|
3140
|
+
? `${Math.max(0, row.depth) * 14}px`
|
|
3141
|
+
: `${Math.max(0, row.depth) * 4}px`,
|
|
3142
|
+
},
|
|
3143
|
+
},
|
|
3144
|
+
rich
|
|
3145
|
+
? React.createElement(RichSessionRow, { row, theme, showDetail: mobile })
|
|
3146
|
+
: React.createElement(SessionRowContent, { row, theme, structured: structuredRows, showInlineDetail: mobile })));
|
|
3147
|
+
});
|
|
3148
|
+
|
|
3149
|
+
function RichSessionRow({ row, theme, showDetail = false }) {
|
|
2841
3150
|
const chrome = row.chrome;
|
|
2842
|
-
if (!chrome) return React.createElement(SessionRowContent, { row, theme, structured: true });
|
|
3151
|
+
if (!chrome) return React.createElement(SessionRowContent, { row, theme, structured: true, showInlineDetail: showDetail });
|
|
2843
3152
|
|
|
2844
3153
|
const kindGlyph = SESSION_KIND_GLYPHS[chrome.kind] || null;
|
|
2845
3154
|
const accent = resolveColor(theme, chrome.accentColor) || undefined;
|
|
2846
|
-
|
|
3155
|
+
// Mobile has no room for a detail box, so it keeps the inline detail line
|
|
3156
|
+
// it always had. Desktop moves that content to the panel footer.
|
|
3157
|
+
const detailRuns = showDetail && Array.isArray(row.detailRuns) ? row.detailRuns : [];
|
|
2847
3158
|
|
|
2848
3159
|
return React.createElement("div", { className: "ps-rich-session-row" },
|
|
2849
3160
|
React.createElement("div", { className: "ps-rich-session-main" },
|
|
@@ -2884,16 +3195,13 @@ function RichSessionRow({ row, theme }) {
|
|
|
2884
3195
|
style: { color: resolveColor(theme, chrome.ctx.color) || undefined },
|
|
2885
3196
|
}, chrome.ctx.text)
|
|
2886
3197
|
: null),
|
|
2887
|
-
// The detail line is ALWAYS rendered, selected or not. Swapping row
|
|
2888
|
-
// content on selection made the whole list jump; now the only things
|
|
2889
|
-
// that reflow are expand/collapse and the highlight moving.
|
|
2890
3198
|
detailRuns.length > 0
|
|
2891
3199
|
? React.createElement("div", { className: "ps-rich-session-detail" },
|
|
2892
3200
|
React.createElement(Runs, { runs: detailRuns, theme }))
|
|
2893
3201
|
: null);
|
|
2894
3202
|
}
|
|
2895
3203
|
|
|
2896
|
-
function SessionRowContent({ row, theme, structured = false }) {
|
|
3204
|
+
function SessionRowContent({ row, theme, structured = false, showInlineDetail = false }) {
|
|
2897
3205
|
const hasStructuredRuns = structured && Array.isArray(row.titleRuns);
|
|
2898
3206
|
if (!hasStructuredRuns) {
|
|
2899
3207
|
return Array.isArray(row.runs)
|
|
@@ -2902,14 +3210,16 @@ function SessionRowContent({ row, theme, structured = false }) {
|
|
|
2902
3210
|
}
|
|
2903
3211
|
|
|
2904
3212
|
// Dense row: the title takes one line (clamped by CSS) and the context %
|
|
2905
|
-
// is pinned to the right.
|
|
2906
|
-
//
|
|
3213
|
+
// is pinned to the right. id · time · model · ctx now live in the panel's
|
|
3214
|
+
// detail box rather than unfolding under the row, so selecting a session
|
|
3215
|
+
// no longer reflows the list.
|
|
2907
3216
|
const ctxRuns = Array.isArray(row.ctxRuns) ? row.ctxRuns : [];
|
|
3217
|
+
const hasCtx = ctxRuns.length > 0;
|
|
3218
|
+
// Mobile keeps the inline unfold under the selected row (no detail box).
|
|
2908
3219
|
const detailRuns = Array.isArray(row.detailRuns)
|
|
2909
3220
|
? row.detailRuns
|
|
2910
3221
|
: (Array.isArray(row.selectedMetaRuns) ? row.selectedMetaRuns : []);
|
|
2911
|
-
const
|
|
2912
|
-
const hasDetail = row.active && detailRuns.length > 0;
|
|
3222
|
+
const hasDetail = showInlineDetail && row.active && detailRuns.length > 0;
|
|
2913
3223
|
|
|
2914
3224
|
return React.createElement(React.Fragment, null,
|
|
2915
3225
|
React.createElement("div", { className: "ps-session-row-line" },
|
|
@@ -2956,6 +3266,9 @@ function useStableValue(value) {
|
|
|
2956
3266
|
}
|
|
2957
3267
|
|
|
2958
3268
|
function SessionPane({ controller, actions = null, panelClassName = "", structuredRows = false }) {
|
|
3269
|
+
// Mobile keeps its inline detail line and gets no detail box — a reserved
|
|
3270
|
+
// footer would eat a meaningful slice of a phone screen.
|
|
3271
|
+
const isMobilePane = String(panelClassName).includes("ps-mobile-session-pane");
|
|
2959
3272
|
const themeId = useControllerSelector(controller, (state) => state.ui.themeId);
|
|
2960
3273
|
const theme = getTheme(themeId);
|
|
2961
3274
|
const sessionButtonRefs = React.useRef(new Map());
|
|
@@ -2999,6 +3312,10 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
|
|
|
2999
3312
|
const activeSession = viewState.activeSessionId
|
|
3000
3313
|
? viewState.sessionsById[viewState.activeSessionId] || null
|
|
3001
3314
|
: null;
|
|
3315
|
+
// The row carries the computed child badge; the raw session does not.
|
|
3316
|
+
const activeRow = viewState.activeSessionId
|
|
3317
|
+
? rows.find((r) => r.sessionId === viewState.activeSessionId) || null
|
|
3318
|
+
: null;
|
|
3002
3319
|
// "Manage session" combines rename, model, and sharing in one tabbed modal
|
|
3003
3320
|
// (opened from the toolbar so the composer chrome stays minimal, esp. on
|
|
3004
3321
|
// mobile). Rename and sharing are owner/admin-only (session:manage /
|
|
@@ -3082,6 +3399,61 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
|
|
|
3082
3399
|
: (activeSession && !activeSession.isSystem && !activeSession.isGroup && !activeSession.parentSessionId ? [activeSession.sessionId] : []);
|
|
3083
3400
|
const canMoveToGroup = groupableIds.length > 0;
|
|
3084
3401
|
const combinedPanelClassName = `ps-session-pane${viewState.rich ? " is-rich" : ""}${panelClassName ? ` ${panelClassName}` : ""}`;
|
|
3402
|
+
// The click handler must be referentially stable or every memoized row
|
|
3403
|
+
// re-renders on each keypress, defeating the point. It reads the current
|
|
3404
|
+
// rows/viewState from a ref that is refreshed on every render instead of
|
|
3405
|
+
// closing over them.
|
|
3406
|
+
const clickEnv = React.useRef(null);
|
|
3407
|
+
clickEnv.current = { rows, viewState, controller };
|
|
3408
|
+
const handleRowClick = React.useCallback((event, row) => {
|
|
3409
|
+
const { rows: currentRows, viewState: current, controller: ctl } = clickEnv.current;
|
|
3410
|
+
// Cmd/Ctrl-click toggles multi-selection (any row).
|
|
3411
|
+
if (event.metaKey || event.ctrlKey) {
|
|
3412
|
+
event.preventDefault();
|
|
3413
|
+
if (!current.selectMode) {
|
|
3414
|
+
ctl.dispatch({ type: "sessions/selectMode", enabled: true });
|
|
3415
|
+
}
|
|
3416
|
+
ctl.dispatch({ type: "sessions/selectToggle", sessionId: row.sessionId });
|
|
3417
|
+
ctl.setFocus("sessions");
|
|
3418
|
+
return;
|
|
3419
|
+
}
|
|
3420
|
+
// Shift-click selects a contiguous range from the active row.
|
|
3421
|
+
if (event.shiftKey && current.activeSessionId) {
|
|
3422
|
+
event.preventDefault();
|
|
3423
|
+
const ids = currentRows.map((r) => r.sessionId);
|
|
3424
|
+
const startIndex = ids.indexOf(current.activeSessionId);
|
|
3425
|
+
const endIndex = ids.indexOf(row.sessionId);
|
|
3426
|
+
if (startIndex >= 0 && endIndex >= 0) {
|
|
3427
|
+
const [from, to] = startIndex <= endIndex
|
|
3428
|
+
? [startIndex, endIndex]
|
|
3429
|
+
: [endIndex, startIndex];
|
|
3430
|
+
const range = ids.slice(from, to + 1);
|
|
3431
|
+
const merged = Array.from(new Set([...(current.selectedIds || []), ...range]));
|
|
3432
|
+
ctl.setSessionSelection(merged);
|
|
3433
|
+
ctl.setFocus("sessions");
|
|
3434
|
+
return;
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
const shouldToggleChildren = row.hasChildren && row.active;
|
|
3438
|
+
if (shouldToggleChildren) {
|
|
3439
|
+
ctl.dispatch({
|
|
3440
|
+
type: row.collapsed ? "sessions/expand" : "sessions/collapse",
|
|
3441
|
+
sessionId: row.sessionId,
|
|
3442
|
+
});
|
|
3443
|
+
ctl.setFocus("sessions");
|
|
3444
|
+
return;
|
|
3445
|
+
}
|
|
3446
|
+
// A normal click on a different row clears any multi-selection and
|
|
3447
|
+
// switches the active session.
|
|
3448
|
+
if (current.selectMode) {
|
|
3449
|
+
ctl.dispatch({ type: "sessions/selectClear" });
|
|
3450
|
+
}
|
|
3451
|
+
ctl.setFocus("sessions");
|
|
3452
|
+
if (!row.active) {
|
|
3453
|
+
ctl.loadSession(row.sessionId).catch(() => {});
|
|
3454
|
+
}
|
|
3455
|
+
}, []);
|
|
3456
|
+
|
|
3085
3457
|
const setSessionButtonRef = React.useCallback((sessionId, node) => {
|
|
3086
3458
|
if (!sessionId) return;
|
|
3087
3459
|
if (node) {
|
|
@@ -3194,74 +3566,22 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
|
|
|
3194
3566
|
? React.createElement("div", { className: "ps-empty-state" }, viewState.filterQuery
|
|
3195
3567
|
? `No sessions matched "@@${viewState.filterQuery}".`
|
|
3196
3568
|
: "No sessions yet.")
|
|
3197
|
-
: rows.map((row) => React.createElement(
|
|
3569
|
+
: rows.map((row) => React.createElement(SessionListRow, {
|
|
3198
3570
|
key: row.sessionId,
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
return;
|
|
3214
|
-
}
|
|
3215
|
-
// Shift-click selects a contiguous range from the active row.
|
|
3216
|
-
if (event.shiftKey && viewState.activeSessionId) {
|
|
3217
|
-
event.preventDefault();
|
|
3218
|
-
const ids = rows.map((r) => r.sessionId);
|
|
3219
|
-
const startIndex = ids.indexOf(viewState.activeSessionId);
|
|
3220
|
-
const endIndex = ids.indexOf(row.sessionId);
|
|
3221
|
-
if (startIndex >= 0 && endIndex >= 0) {
|
|
3222
|
-
const [from, to] = startIndex <= endIndex
|
|
3223
|
-
? [startIndex, endIndex]
|
|
3224
|
-
: [endIndex, startIndex];
|
|
3225
|
-
const range = ids.slice(from, to + 1);
|
|
3226
|
-
const merged = Array.from(new Set([...(viewState.selectedIds || []), ...range]));
|
|
3227
|
-
controller.setSessionSelection(merged);
|
|
3228
|
-
controller.setFocus("sessions");
|
|
3229
|
-
return;
|
|
3230
|
-
}
|
|
3231
|
-
}
|
|
3232
|
-
const shouldToggleChildren = row.hasChildren && row.active;
|
|
3233
|
-
if (shouldToggleChildren) {
|
|
3234
|
-
controller.dispatch({
|
|
3235
|
-
type: row.collapsed ? "sessions/expand" : "sessions/collapse",
|
|
3236
|
-
sessionId: row.sessionId,
|
|
3237
|
-
});
|
|
3238
|
-
controller.setFocus("sessions");
|
|
3239
|
-
return;
|
|
3240
|
-
}
|
|
3241
|
-
// A normal click on a different row clears any
|
|
3242
|
-
// multi-selection and switches the active session.
|
|
3243
|
-
if (viewState.selectMode) {
|
|
3244
|
-
controller.dispatch({ type: "sessions/selectClear" });
|
|
3245
|
-
}
|
|
3246
|
-
controller.setFocus("sessions");
|
|
3247
|
-
if (!row.active) {
|
|
3248
|
-
controller.loadSession(row.sessionId).catch(() => {});
|
|
3249
|
-
}
|
|
3250
|
-
},
|
|
3251
|
-
},
|
|
3252
|
-
React.createElement("div", {
|
|
3253
|
-
className: viewState.rich ? "ps-session-row-content is-rich" : "ps-line ps-session-row-content",
|
|
3254
|
-
style: {
|
|
3255
|
-
paddingInlineStart: viewState.rich
|
|
3256
|
-
? `${Math.max(0, row.depth) * 14}px`
|
|
3257
|
-
: `${Math.max(0, row.depth) * 4}px`,
|
|
3258
|
-
},
|
|
3259
|
-
},
|
|
3260
|
-
viewState.rich
|
|
3261
|
-
? React.createElement(RichSessionRow, { row, theme })
|
|
3262
|
-
: React.createElement(SessionRowContent, { row, theme, structured: structuredRows })),
|
|
3263
|
-
)),
|
|
3264
|
-
)),
|
|
3571
|
+
row,
|
|
3572
|
+
theme,
|
|
3573
|
+
rich: viewState.rich,
|
|
3574
|
+
structuredRows,
|
|
3575
|
+
mobile: isMobilePane,
|
|
3576
|
+
onRowClick: handleRowClick,
|
|
3577
|
+
setRef: setSessionButtonRef,
|
|
3578
|
+
}))),
|
|
3579
|
+
isMobilePane
|
|
3580
|
+
? null
|
|
3581
|
+
: React.createElement(SessionDetailBox, {
|
|
3582
|
+
session: activeSession,
|
|
3583
|
+
childCount: activeRow?.childCount || 0,
|
|
3584
|
+
})),
|
|
3265
3585
|
(manageOpen && activeSession && !activeSession.isGroup)
|
|
3266
3586
|
? React.createElement(SessionModifyModal, {
|
|
3267
3587
|
controller,
|
|
@@ -3385,7 +3705,19 @@ function useActiveSessionAccess(controller, activeSessionId, isGroup) {
|
|
|
3385
3705
|
.catch(() => { if (!cancelled) setState({ access: null, loading: false }); });
|
|
3386
3706
|
return () => { cancelled = true; };
|
|
3387
3707
|
}, [controller, activeSessionId, isGroup]);
|
|
3388
|
-
|
|
3708
|
+
// Deferred for the same reason the session fetch is: this fires on every
|
|
3709
|
+
// activeSessionId change, and scrolling the list changes that per keypress.
|
|
3710
|
+
// Without the settle, moving through a list of sessions issued one access
|
|
3711
|
+
// round trip per row. `reload` stays immediate for callers that ask for it
|
|
3712
|
+
// explicitly (after a sharing change).
|
|
3713
|
+
React.useEffect(() => {
|
|
3714
|
+
let cancelInFlight;
|
|
3715
|
+
const timer = setTimeout(() => { cancelInFlight = reload(); }, SESSION_ACCESS_SETTLE_MS);
|
|
3716
|
+
return () => {
|
|
3717
|
+
clearTimeout(timer);
|
|
3718
|
+
if (typeof cancelInFlight === "function") cancelInFlight();
|
|
3719
|
+
};
|
|
3720
|
+
}, [reload]);
|
|
3389
3721
|
return { access: state.access, loading: state.loading, reload };
|
|
3390
3722
|
}
|
|
3391
3723
|
|
|
@@ -4171,7 +4503,21 @@ function FilesPane({ controller, focused, mobile = false, previewOnly = false, n
|
|
|
4171
4503
|
// In the mobile overlay the browser owns scrolling outright.
|
|
4172
4504
|
const previewPaneKey = nativeScroll ? null : "filePreview";
|
|
4173
4505
|
|
|
4174
|
-
const
|
|
4506
|
+
const previewIsTabular = previewReady
|
|
4507
|
+
&& !filesView.previewIsBinary
|
|
4508
|
+
&& isTabularArtifact(filesView.selectedFilename, filesView.previewContentType)
|
|
4509
|
+
&& Boolean(filesView.previewContent);
|
|
4510
|
+
|
|
4511
|
+
const previewPane = previewIsTabular
|
|
4512
|
+
? React.createElement(RenderedPreviewPanel, {
|
|
4513
|
+
controller, title: null, color: "cyan", focused: false, theme,
|
|
4514
|
+
paneKey: previewPaneKey, scrollOffset: viewState.previewScroll,
|
|
4515
|
+
className: "ps-csv-preview", focusRegion: previewFocusRegion,
|
|
4516
|
+
}, React.createElement(TabularPreview, {
|
|
4517
|
+
content: filesView.previewContent,
|
|
4518
|
+
filename: filesView.selectedFilename,
|
|
4519
|
+
}))
|
|
4520
|
+
: previewIsImage
|
|
4175
4521
|
? React.createElement(BinaryArtifactPreviewPanel, {
|
|
4176
4522
|
title: null,
|
|
4177
4523
|
color: "cyan",
|
package/web/dist/assets/{abnfDiagram-VRR7QNED-C4qRR7va.js → abnfDiagram-VRR7QNED-DOZGtiQC.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-
|
|
1
|
+
import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-B-bgmTfa.js";import{p as f}from"./chunk-JWPE2WC7-D5reV4oK.js";import{_ as n,l as o}from"./mermaid.core-OUiCmXKB.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-COwlV5eq.js";import"./pilotswarm-CU6jtK4w.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-
|
|
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-OUiCmXKB.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};
|