pilotswarm 0.5.64 → 0.5.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/package.json +2 -2
  2. package/ui/core/src/index.js +1 -0
  3. package/ui/core/src/reducer.js +4 -3
  4. package/ui/core/src/selectors.js +144 -9
  5. package/ui/core/src/session-search.js +151 -0
  6. package/ui/react/src/web-app.js +105 -2
  7. package/web/dist/assets/{abnfDiagram-VRR7QNED-gIPHF2gV.js → abnfDiagram-VRR7QNED-B-04UhgW.js} +1 -1
  8. package/web/dist/assets/{arc-DMW_q5Ml.js → arc-C5XCnSQg.js} +1 -1
  9. package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-DAKsrxcB.js → architectureDiagram-ZJ3FMSHR-R-xjkIQE.js} +1 -1
  10. package/web/dist/assets/{blockDiagram-677ZJIJ3-BUjSLpP3.js → blockDiagram-677ZJIJ3-DrthdK1L.js} +1 -1
  11. package/web/dist/assets/{c4Diagram-LMCZKHZV-Dw2nPoes.js → c4Diagram-LMCZKHZV-DRZ4aZB3.js} +1 -1
  12. package/web/dist/assets/channel-trVVWAw_.js +1 -0
  13. package/web/dist/assets/{chunk-2Q5K7J3B-BAL28Rv0.js → chunk-2Q5K7J3B-Df7tPwEB.js} +1 -1
  14. package/web/dist/assets/{chunk-32BRIVSS-DDlt9K8s.js → chunk-32BRIVSS-bNd88tsn.js} +1 -1
  15. package/web/dist/assets/{chunk-5VM5RSS4-BLQGJGoP.js → chunk-5VM5RSS4-ylLOcwFv.js} +1 -1
  16. package/web/dist/assets/{chunk-EX3LRPZG-XFMb3HRy.js → chunk-EX3LRPZG-CWgx_6ev.js} +1 -1
  17. package/web/dist/assets/{chunk-JWPE2WC7-DWnKTQ72.js → chunk-JWPE2WC7-BD-w4617.js} +1 -1
  18. package/web/dist/assets/{chunk-MOJQB5TN-MltzhnwP.js → chunk-MOJQB5TN-De76fp2s.js} +1 -1
  19. package/web/dist/assets/{chunk-RYQCIY6F-Ce3q0-mJ.js → chunk-RYQCIY6F-CpWyBMvz.js} +1 -1
  20. package/web/dist/assets/{chunk-V7JOEXUC-ThXLYY88.js → chunk-V7JOEXUC-C-NJGbbs.js} +1 -1
  21. package/web/dist/assets/{chunk-VR4S4FIN-DbhAykiW.js → chunk-VR4S4FIN-DK28nbHC.js} +1 -1
  22. package/web/dist/assets/{chunk-XXDRQBXY-BpkNB0mD.js → chunk-XXDRQBXY-Bk5aYmwf.js} +1 -1
  23. package/web/dist/assets/classDiagram-OUVF2IWQ-Bsdx911B.js +1 -0
  24. package/web/dist/assets/classDiagram-v2-EOCWNBFH-Bsdx911B.js +1 -0
  25. package/web/dist/assets/{cose-bilkent-JH36ORCC-rctqAPvj.js → cose-bilkent-JH36ORCC-OLeC_lx0.js} +1 -1
  26. package/web/dist/assets/{cynefin-VYW2F7L2-Dg1cltg6.js → cynefin-VYW2F7L2-C-gW4wVJ.js} +1 -1
  27. package/web/dist/assets/{cynefinDiagram-TSTJHNR4-Ci23Sv4Z.js → cynefinDiagram-TSTJHNR4-CFbownfZ.js} +1 -1
  28. package/web/dist/assets/{dagre-VKFMJZFB-BpWTGhpv.js → dagre-VKFMJZFB-e4s9tuv2.js} +1 -1
  29. package/web/dist/assets/{diagram-FQU43EPY-B69JpiAe.js → diagram-FQU43EPY-BjE9UaN5.js} +1 -1
  30. package/web/dist/assets/{diagram-G47NLZAW-BHMk1TOO.js → diagram-G47NLZAW-_3b8-LSz.js} +1 -1
  31. package/web/dist/assets/{diagram-NH7WQ7WH-CtvZIIsk.js → diagram-NH7WQ7WH-2B55Dr-P.js} +1 -1
  32. package/web/dist/assets/{diagram-OA4YK3LP-BFvKh800.js → diagram-OA4YK3LP-DI42Mpfa.js} +1 -1
  33. package/web/dist/assets/{diagram-WEI45ONY-CgB8sWPe.js → diagram-WEI45ONY-DwdeclWS.js} +1 -1
  34. package/web/dist/assets/{ebnfDiagram-CCIWWBDH-DyqPNydM.js → ebnfDiagram-CCIWWBDH-D0mgBmPT.js} +1 -1
  35. package/web/dist/assets/{erDiagram-Q63AITRT-CwpvZQ3Q.js → erDiagram-Q63AITRT-9uDmXDxu.js} +1 -1
  36. package/web/dist/assets/{flowDiagram-23GEKE2U-EBKSKxfC.js → flowDiagram-23GEKE2U-czg0H1o9.js} +1 -1
  37. package/web/dist/assets/{ganttDiagram-NO4QXBWP-q5tMPAuC.js → ganttDiagram-NO4QXBWP-klkCbthP.js} +1 -1
  38. package/web/dist/assets/{gitGraphDiagram-IHSO6WYX-DCRbi4KE.js → gitGraphDiagram-IHSO6WYX-KkCVvJUf.js} +1 -1
  39. package/web/dist/assets/{index-BfF7e08d.js → index-CZgFyGc_.js} +2 -2
  40. package/web/dist/assets/index-D4OiytLj.css +1 -0
  41. package/web/dist/assets/{infoDiagram-FWYZ7A6U-Bex5pSIX.js → infoDiagram-FWYZ7A6U-Dt2S9vn1.js} +1 -1
  42. package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-Dj0li_TY.js → ishikawaDiagram-FXEZZL3T-Dwm-j64z.js} +1 -1
  43. package/web/dist/assets/{journeyDiagram-5HDEW3XC-BtEGEeVr.js → journeyDiagram-5HDEW3XC-BF0hgIwR.js} +1 -1
  44. package/web/dist/assets/{kanban-definition-HUTT4EX6-BTFfZvYt.js → kanban-definition-HUTT4EX6-qu3pSjQ4.js} +1 -1
  45. package/web/dist/assets/{linear-C3SQCsip.js → linear-D2wggRGK.js} +1 -1
  46. package/web/dist/assets/{mermaid.core-DALDJDu8.js → mermaid.core-CEMxYLqP.js} +4 -4
  47. package/web/dist/assets/{mindmap-definition-LN4V7U3C-BZf3uw0u.js → mindmap-definition-LN4V7U3C-DtDqO1Si.js} +1 -1
  48. package/web/dist/assets/{papaparse.min-Bh9MZ-fk.js → papaparse.min-Ca3zFRUj.js} +1 -1
  49. package/web/dist/assets/{pegDiagram-2B236MQR-PuOleiMg.js → pegDiagram-2B236MQR-Bptd8cxe.js} +1 -1
  50. package/web/dist/assets/{pieDiagram-ENE6RG2P-CcFORUaW.js → pieDiagram-ENE6RG2P-Cbvo_diJ.js} +1 -1
  51. package/web/dist/assets/pilotswarm-BJidJRMR.js +109 -0
  52. package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-CEm0mp4y.js → quadrantDiagram-ABIIQ3AL-Dsms43Mu.js} +1 -1
  53. package/web/dist/assets/{railroadDiagram-RFXS5EU6-DeH-uX0_.js → railroadDiagram-RFXS5EU6-l5T4OGip.js} +1 -1
  54. package/web/dist/assets/{requirementDiagram-TGXJPOKE-CMbJVMc1.js → requirementDiagram-TGXJPOKE-D7N_xPkI.js} +1 -1
  55. package/web/dist/assets/{sankeyDiagram-HTMAVEWB-C9gL5w_b.js → sankeyDiagram-HTMAVEWB-BSppPWwp.js} +1 -1
  56. package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-BUo8TT74.js → sequenceDiagram-DBY2YBRQ-drRDz5Uo.js} +1 -1
  57. package/web/dist/assets/{sizeCapture-X5ZJPWSS-C5lqG0ei.js → sizeCapture-X5ZJPWSS-BljhCGnZ.js} +1 -1
  58. package/web/dist/assets/{stateDiagram-2N3HPSRC-DyeISrEU.js → stateDiagram-2N3HPSRC-CssZ3S0X.js} +1 -1
  59. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-DDkaZvTf.js +1 -0
  60. package/web/dist/assets/{swimlanes-5IMT3BWC-DawuBk_a.js → swimlanes-5IMT3BWC-Cap4r3qG.js} +2 -2
  61. package/web/dist/assets/swimlanesDiagram-G3AALYLV-CiHBj-pL.js +8 -0
  62. package/web/dist/assets/{timeline-definition-FHXFAJF6-BZJ2YJcm.js → timeline-definition-FHXFAJF6-CRlJg6Zj.js} +1 -1
  63. package/web/dist/assets/{vennDiagram-L72KCM5P-BMXBVCfe.js → vennDiagram-L72KCM5P-BLgXv6xY.js} +1 -1
  64. package/web/dist/assets/{wardleyDiagram-EHGQE667-DWedKsrQ.js → wardleyDiagram-EHGQE667-EbTHIyRd.js} +1 -1
  65. package/web/dist/assets/{xychartDiagram-FW5EYKEG--XdfYceC.js → xychartDiagram-FW5EYKEG-DPR0SaNZ.js} +1 -1
  66. package/web/dist/index.html +3 -3
  67. package/web/dist/assets/channel-CcOa1LtY.js +0 -1
  68. package/web/dist/assets/classDiagram-OUVF2IWQ-B2VhHRSV.js +0 -1
  69. package/web/dist/assets/classDiagram-v2-EOCWNBFH-B2VhHRSV.js +0 -1
  70. package/web/dist/assets/index-uhc851H3.css +0 -1
  71. package/web/dist/assets/pilotswarm-B9rLtXpI.js +0 -109
  72. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-5FZbaloZ.js +0 -1
  73. package/web/dist/assets/swimlanesDiagram-G3AALYLV-DL8PgtmM.js +0 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pilotswarm",
3
- "version": "0.5.64",
3
+ "version": "0.5.66",
4
4
  "description": "PilotSwarm application package: terminal UI, browser portal + Web API server, and MCP server — one install, three bins.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -81,7 +81,7 @@
81
81
  "ink": "^6.8.0",
82
82
  "jose": "^6.2.2",
83
83
  "mermaid": "^11.16.0",
84
- "pilotswarm-sdk": "0.5.64",
84
+ "pilotswarm-sdk": "0.5.66",
85
85
  "react": "^19.2.4",
86
86
  "react-dom": "^19.2.4",
87
87
  "ws": "^8.18.2"
@@ -6,6 +6,7 @@ export * from "./layout.js";
6
6
  export * from "./repo-links.js";
7
7
  export * from "./repo-import.js";
8
8
  export * from "./session-tree.js";
9
+ export * from "./session-search.js";
9
10
  export * from "./state.js";
10
11
  export * from "./reducer.js";
11
12
  export * from "./portal-links.js";
@@ -1385,11 +1385,12 @@ function baseReducer(state, action) {
1385
1385
  filterExceptionId: null,
1386
1386
  ownerFilterAutoAdmitted: null,
1387
1387
  };
1388
- const selection = applyVisibleSessionSelection(state, nextSessions);
1389
1388
  return {
1390
1389
  ...state,
1391
- sessions: selection.sessions,
1392
- ui: selection.ui,
1390
+ // Search filters the list, not the attached chat. Keeping
1391
+ // the active session stable also prevents a keystroke in
1392
+ // the search box from moving the list and main pane.
1393
+ sessions: nextSessions,
1393
1394
  };
1394
1395
  }
1395
1396
 
@@ -1,7 +1,12 @@
1
1
  import { INSPECTOR_TABS, FOCUS_REGIONS } from "./commands.js";
2
2
  import { chatCallLine, firstCallLine } from "./chat-activity.js";
3
3
  import { canvasKey as canvasSlotKey, parseCanvasKey } from "./state.js";
4
- import { isManuallyOrderableSession } from "./session-tree.js";
4
+ import { buildSessionTree, isManuallyOrderableSession } from "./session-tree.js";
5
+ import {
6
+ buildSessionSearchDocument,
7
+ parseSessionSearchQuery,
8
+ scoreSessionSearchDocument,
9
+ } from "./session-search.js";
5
10
  import {
6
11
  createSplashCard,
7
12
  isRehydrationNoticeText,
@@ -1287,6 +1292,132 @@ function matchesSearchQuery(value, query) {
1287
1292
  return String(value || "").toLowerCase().includes(normalizedQuery);
1288
1293
  }
1289
1294
 
1295
+ const searchedSessionFlatCache = new WeakMap();
1296
+
1297
+ function sessionGroupTitle(session, byId) {
1298
+ if (session?.isGroup) return session.title || "";
1299
+ if (session?.groupId) return byId?.[`group:${session.groupId}`]?.title || "";
1300
+ let current = session;
1301
+ const seen = new Set();
1302
+ while (current?.parentSessionId && !seen.has(current.parentSessionId)) {
1303
+ seen.add(current.parentSessionId);
1304
+ current = byId?.[current.parentSessionId];
1305
+ if (current?.groupId) return byId?.[`group:${current.groupId}`]?.title || "";
1306
+ }
1307
+ return "";
1308
+ }
1309
+
1310
+ function sessionSearchOwner(session, byId) {
1311
+ const direct = effectiveSessionOwner(session, byId);
1312
+ if (direct) return direct;
1313
+ let current = session;
1314
+ const seen = new Set();
1315
+ while (current?.parentSessionId && !seen.has(current.parentSessionId)) {
1316
+ seen.add(current.parentSessionId);
1317
+ current = byId?.[current.parentSessionId];
1318
+ if (current?.owner) return current.owner;
1319
+ }
1320
+ return null;
1321
+ }
1322
+
1323
+ function buildSearchedSessionFlat(state, query) {
1324
+ const existingFlat = Array.isArray(state.sessions?.flat) ? state.sessions.flat : [];
1325
+ if (!query) return existingFlat;
1326
+ const cached = searchedSessionFlatCache.get(existingFlat);
1327
+ if (cached?.query === query) return cached.result;
1328
+
1329
+ const byId = state.sessions?.byId || {};
1330
+ const parsed = parseSessionSearchQuery(query);
1331
+ const expanded = buildSessionTree(
1332
+ Object.values(byId),
1333
+ new Set(),
1334
+ state.sessions?.orderById,
1335
+ state.sessions?.pinnedIds,
1336
+ state.sessions?.manualOrder,
1337
+ );
1338
+ const parentById = new Map();
1339
+ const entryById = new Map();
1340
+ const baseIndex = new Map();
1341
+ const stack = [];
1342
+ for (let index = 0; index < expanded.length; index += 1) {
1343
+ const entry = expanded[index];
1344
+ stack[entry.depth] = entry.sessionId;
1345
+ stack.length = entry.depth + 1;
1346
+ parentById.set(entry.sessionId, entry.depth > 0 ? stack[entry.depth - 1] : null);
1347
+ entryById.set(entry.sessionId, entry);
1348
+ baseIndex.set(entry.sessionId, index);
1349
+ }
1350
+
1351
+ const directScores = new Map();
1352
+ for (const entry of expanded) {
1353
+ const session = byId[entry.sessionId] || entry.standIn;
1354
+ if (!session || !matchesOwnerFilter(session, state.sessions?.ownerFilter, state.auth || {}, byId)) continue;
1355
+ const owner = sessionSearchOwner(session, byId);
1356
+ const document = buildSessionSearchDocument(session, {
1357
+ owner,
1358
+ groupTitle: sessionGroupTitle(session, byId),
1359
+ });
1360
+ const score = scoreSessionSearchDocument(document, parsed);
1361
+ if (score > 0) directScores.set(entry.sessionId, score);
1362
+ }
1363
+ const matchedIds = new Set(directScores.keys());
1364
+ // A just-created or deep-linked session may be admitted explicitly while
1365
+ // the user's prior query still excludes it. Preserve that established
1366
+ // visibility contract and rank the exception first until the user edits
1367
+ // the search (which clears filterExceptionId in the reducer).
1368
+ const exceptionId = state.sessions?.filterExceptionId;
1369
+ if (exceptionId && entryById.has(exceptionId)) {
1370
+ directScores.set(exceptionId, Number.MAX_SAFE_INTEGER);
1371
+ }
1372
+
1373
+ const included = new Set();
1374
+ const subtreeScores = new Map();
1375
+ for (const [sessionId, score] of directScores) {
1376
+ let current = sessionId;
1377
+ const seen = new Set();
1378
+ while (current && !seen.has(current)) {
1379
+ seen.add(current);
1380
+ included.add(current);
1381
+ subtreeScores.set(current, Math.max(subtreeScores.get(current) || 0, score));
1382
+ current = parentById.get(current) || null;
1383
+ }
1384
+ }
1385
+
1386
+ const children = new Map();
1387
+ for (const sessionId of included) {
1388
+ const parentId = parentById.get(sessionId) || null;
1389
+ if (!children.has(parentId)) children.set(parentId, []);
1390
+ children.get(parentId).push(sessionId);
1391
+ }
1392
+ for (const siblings of children.values()) {
1393
+ siblings.sort((a, b) => (subtreeScores.get(b) || 0) - (subtreeScores.get(a) || 0)
1394
+ || (baseIndex.get(a) || 0) - (baseIndex.get(b) || 0));
1395
+ }
1396
+
1397
+ const result = [];
1398
+ const visit = (sessionId, depth) => {
1399
+ const entry = entryById.get(sessionId);
1400
+ if (!entry) return;
1401
+ const childIds = children.get(sessionId) || [];
1402
+ result.push({
1403
+ ...entry,
1404
+ depth,
1405
+ collapsed: false,
1406
+ hasChildren: childIds.length > 0,
1407
+ searchIncluded: true,
1408
+ searchMatch: matchedIds.has(sessionId),
1409
+ searchScore: directScores.get(sessionId) || 0,
1410
+ });
1411
+ for (const childId of childIds) visit(childId, depth + 1);
1412
+ };
1413
+ for (const rootId of children.get(null) || []) visit(rootId, 0);
1414
+ // A user can type an unbounded number of distinct queries while the
1415
+ // catalog array keeps the same identity. Keep only the latest result for
1416
+ // that catalog rather than retaining every intermediate keystroke.
1417
+ searchedSessionFlatCache.set(existingFlat, { query, result });
1418
+ return result;
1419
+ }
1420
+
1290
1421
  // The transient deep-link filter exception covers the linked session AND its
1291
1422
  // ancestor chain (real parents plus the synthetic group:<id> row) so the
1292
1423
  // linked row renders with its tree context instead of as an orphan.
@@ -1330,22 +1461,25 @@ function sameRowDeps(a, b) {
1330
1461
  }
1331
1462
 
1332
1463
  export function selectSessionRows(state) {
1464
+ // Keep field punctuation and quotes for the parser (`author:"Ada Lovelace"`).
1465
+ // Individual values are normalized inside session-search.
1466
+ const query = String(state.sessions?.filterQuery || "").trim();
1467
+ const sessionFlat = buildSearchedSessionFlat(state, query);
1333
1468
  const totalDescendantCounts = getTotalDescendantCounts(state.sessions.byId);
1334
- const visibleDescendantCounts = getVisibleDescendantCounts(state.sessions.flat, state.sessions.byId);
1335
- const query = normalizeSearchQuery(state.sessions?.filterQuery || "");
1469
+ const visibleDescendantCounts = getVisibleDescendantCounts(sessionFlat, state.sessions.byId);
1336
1470
  const ownerFilter = state.sessions?.ownerFilter || { all: true };
1337
1471
  const auth = state.auth || {};
1338
1472
  const pinnedSet = new Set(Array.isArray(state.sessions?.pinnedIds) ? state.sessions.pinnedIds : []);
1339
1473
  const selectedSet = new Set(Array.isArray(state.sessions?.selectedIds) ? state.sessions.selectedIds : []);
1340
1474
  const filterExceptionIds = collectFilterExceptionIds(state.sessions);
1341
1475
 
1342
- let memo = sessionRowMemo.get(state.sessions.flat);
1476
+ let memo = sessionRowMemo.get(sessionFlat);
1343
1477
  if (!memo) {
1344
1478
  memo = new Map();
1345
- sessionRowMemo.set(state.sessions.flat, memo);
1479
+ sessionRowMemo.set(sessionFlat, memo);
1346
1480
  }
1347
1481
 
1348
- return state.sessions.flat.filter((entry) => (
1482
+ return sessionFlat.filter((entry) => (
1349
1483
  // A row whose session is in neither place can render nothing but an
1350
1484
  // owner chip and a dash - the "[?]" ghost. Drop it rather than show it.
1351
1485
  Boolean(state.sessions.byId[entry.sessionId] || entry.standIn)
@@ -1433,10 +1567,13 @@ export function selectSessionRows(state) {
1433
1567
  // `chrome` as well so a host that draws its own row (the phone's
1434
1568
  // list) can act on it without reading the portal's descriptor.
1435
1569
  pause: rowView.chrome?.pause || null,
1570
+ searchMatch: entry.searchMatch === true,
1571
+ searchScore: Number(entry.searchScore) || 0,
1436
1572
  };
1437
1573
  memo.set(entry.sessionId, { deps, row });
1438
1574
  return row;
1439
1575
  }).filter((row) => {
1576
+ if (query) return true; // searchedSessionFlat already contains only matches and their ancestors.
1440
1577
  if (filterExceptionIds?.has(row.sessionId)) return true;
1441
1578
  const session = state.sessions.byId[row.sessionId];
1442
1579
  if (!matchesOwnerFilter(session, ownerFilter, auth, state.sessions.byId)) return false;
@@ -1444,11 +1581,9 @@ export function selectSessionRows(state) {
1444
1581
  const ownerSearchText = effectiveOwner
1445
1582
  ? `${ownerDisplayName(effectiveOwner, "")} ${effectiveOwner.email || ""}`
1446
1583
  : "";
1447
- const summarySearchText = "";
1448
1584
  return matchesSearchQuery(row.text, query)
1449
1585
  || matchesSearchQuery(row.sessionId, query)
1450
- || matchesSearchQuery(ownerSearchText, query)
1451
- || matchesSearchQuery(summarySearchText, query);
1586
+ || matchesSearchQuery(ownerSearchText, query);
1452
1587
  });
1453
1588
  }
1454
1589
 
@@ -0,0 +1,151 @@
1
+ const FIELD_ALIASES = Object.freeze({
2
+ author: "author",
3
+ owner: "author",
4
+ agent: "agent",
5
+ status: "status",
6
+ group: "group",
7
+ folder: "group",
8
+ model: "model",
9
+ id: "id",
10
+ topic: "topic",
11
+ summary: "summary",
12
+ });
13
+
14
+ const FREE_FIELD_WEIGHTS = Object.freeze({
15
+ title: 12,
16
+ id: 11,
17
+ author: 10,
18
+ agent: 9,
19
+ summary: 7,
20
+ group: 6,
21
+ model: 5,
22
+ status: 4,
23
+ });
24
+
25
+ export function normalizeSessionSearchText(value) {
26
+ return String(value || "")
27
+ .normalize("NFKD")
28
+ .replace(/[\u0300-\u036f]/g, "")
29
+ .toLowerCase()
30
+ .replace(/[^a-z0-9@._+-]+/g, " ")
31
+ .trim()
32
+ .replace(/\s+/g, " ");
33
+ }
34
+
35
+ export function parseSessionSearchQuery(value) {
36
+ const source = String(value || "").trim();
37
+ const terms = [];
38
+ const filters = [];
39
+ const pattern = /(?:(\w+):)?(?:"([^"]+)"|(\S+))/g;
40
+ let match;
41
+ while ((match = pattern.exec(source)) !== null) {
42
+ const rawField = String(match[1] || "").toLowerCase();
43
+ const rawValue = match[2] ?? match[3] ?? "";
44
+ const normalized = normalizeSessionSearchText(rawValue);
45
+ if (!normalized) continue;
46
+ const field = FIELD_ALIASES[rawField];
47
+ if (field) filters.push({ field, value: normalized });
48
+ else terms.push(normalizeSessionSearchText(rawField ? `${rawField} ${rawValue}` : rawValue));
49
+ }
50
+ return { source, terms: terms.filter(Boolean), filters };
51
+ }
52
+
53
+ function boundedDistance(a, b, limit) {
54
+ if (Math.abs(a.length - b.length) > limit) return limit + 1;
55
+ let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
56
+ for (let i = 1; i <= a.length; i += 1) {
57
+ const current = [i];
58
+ let rowMinimum = current[0];
59
+ for (let j = 1; j <= b.length; j += 1) {
60
+ const value = Math.min(
61
+ current[j - 1] + 1,
62
+ previous[j] + 1,
63
+ previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1),
64
+ );
65
+ current.push(value);
66
+ rowMinimum = Math.min(rowMinimum, value);
67
+ }
68
+ if (rowMinimum > limit) return limit + 1;
69
+ previous = current;
70
+ }
71
+ return previous[b.length];
72
+ }
73
+
74
+ function matchStrength(haystack, needle) {
75
+ if (!haystack || !needle) return 0;
76
+ if (haystack === needle) return 16;
77
+ const words = haystack.split(" ");
78
+ if (words.includes(needle)) return 14;
79
+ if (haystack.includes(needle)) return needle.includes(" ") ? 13 : 11;
80
+ if (needle.length >= 2 && words.some((word) => word.startsWith(needle))) return 9;
81
+ if (needle.length >= 4 && words.some((word) => word.includes(needle))) return 7;
82
+ if (needle.length < 4) return 0;
83
+ // One adjacent transposition is two edits under Levenshtein distance
84
+ // ("session" → "sessoin"). Permit that for ordinary words while keeping
85
+ // short tokens exact/prefix-only.
86
+ const limit = needle.length >= 6 ? 2 : 1;
87
+ return words.some((word) => boundedDistance(word, needle, limit) <= limit) ? 4 : 0;
88
+ }
89
+
90
+ function summaryStrings(summaryState) {
91
+ if (!summaryState || typeof summaryState !== "object") return [];
92
+ return [summaryState.topic, summaryState.intent, summaryState.summary, summaryState.title]
93
+ .filter((value) => typeof value === "string" && value.trim());
94
+ }
95
+
96
+ export function buildSessionSearchDocument(session, { owner = null, groupTitle = "" } = {}) {
97
+ const title = normalizeSessionSearchText(session?.title);
98
+ const summary = normalizeSessionSearchText([
99
+ session?.shortSummary,
100
+ ...summaryStrings(session?.summaryState),
101
+ ].filter(Boolean).join(" "));
102
+ const author = normalizeSessionSearchText([
103
+ owner?.displayName,
104
+ owner?.email,
105
+ owner?.subject,
106
+ ].filter(Boolean).join(" "));
107
+ return {
108
+ title,
109
+ summary,
110
+ topic: normalizeSessionSearchText(`${title} ${summary}`),
111
+ author,
112
+ agent: normalizeSessionSearchText(session?.agentId),
113
+ status: normalizeSessionSearchText(session?.status),
114
+ group: normalizeSessionSearchText(groupTitle),
115
+ model: normalizeSessionSearchText(`${session?.model || ""} ${session?.reasoningEffort || ""}`),
116
+ id: normalizeSessionSearchText(session?.sessionId),
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Score one already-authorized, browser-resident session. Structured filters
122
+ * and free-text terms are ANDed; each free term may match any field. Exact,
123
+ * prefix and substring matches beat bounded typo matches.
124
+ */
125
+ export function scoreSessionSearchDocument(document, query) {
126
+ const parsed = typeof query === "string" ? parseSessionSearchQuery(query) : query;
127
+ if (!parsed || (parsed.terms.length === 0 && parsed.filters.length === 0)) return 1;
128
+ let score = 0;
129
+ for (const filter of parsed.filters) {
130
+ const strength = matchStrength(document?.[filter.field] || "", filter.value);
131
+ if (!strength) return 0;
132
+ score += strength * 14;
133
+ }
134
+ for (const term of parsed.terms) {
135
+ let best = 0;
136
+ for (const [field, weight] of Object.entries(FREE_FIELD_WEIGHTS)) {
137
+ // Short numbers such as "Session 2" occur in the fixed portions
138
+ // of every UUID. Treat IDs as an explicit/long-fragment field so
139
+ // the human title remains the natural interpretation; `id:...`
140
+ // is always available for deliberate ID lookup.
141
+ if (field === "id" && (/^\d+$/.test(term) || term.length < 8)) continue;
142
+ best = Math.max(best, matchStrength(document?.[field] || "", term) * weight);
143
+ }
144
+ if (!best) return 0;
145
+ score += best;
146
+ }
147
+ const phrase = parsed.terms.join(" ");
148
+ if (phrase && document?.title?.includes(phrase)) score += 120;
149
+ else if (phrase && document?.summary?.includes(phrase)) score += 60;
150
+ return score;
151
+ }
@@ -5053,6 +5053,71 @@ function useAxisLockedPan(ref, enabled = true) {
5053
5053
  }, [ref, enabled]);
5054
5054
  }
5055
5055
 
5056
+ function SessionSearchControl({ query = "", onQuery, matchCount = 0, mobile = false, listRef = null }) {
5057
+ const [mobileOpen, setMobileOpen] = React.useState(Boolean(query));
5058
+ const inputRef = React.useRef(null);
5059
+ React.useEffect(() => {
5060
+ if (query) setMobileOpen(true);
5061
+ }, [query]);
5062
+ const open = () => {
5063
+ setMobileOpen(true);
5064
+ requestAnimationFrame(() => inputRef.current?.focus({ preventScroll: true }));
5065
+ };
5066
+ const closeOrClear = () => {
5067
+ if (query) onQuery?.("");
5068
+ else setMobileOpen(false);
5069
+ };
5070
+ const resultLabel = query
5071
+ ? `${matchCount} ${matchCount === 1 ? "match" : "matches"}`
5072
+ : "";
5073
+ return React.createElement("div", {
5074
+ className: `ps-session-search${mobileOpen ? " is-open" : ""}`,
5075
+ "data-session-search": "true",
5076
+ },
5077
+ mobile ? React.createElement("button", {
5078
+ type: "button",
5079
+ className: "ps-session-search-trigger",
5080
+ "aria-label": "Search sessions",
5081
+ title: "Search sessions",
5082
+ onClick: open,
5083
+ }, React.createElement(SearchGlyph)) : null,
5084
+ React.createElement("div", { className: "ps-session-search-fields" },
5085
+ React.createElement(SearchGlyph),
5086
+ React.createElement("input", {
5087
+ ref: inputRef,
5088
+ type: "text",
5089
+ inputMode: "search",
5090
+ "aria-label": "Find a session",
5091
+ placeholder: "Find a session…",
5092
+ value: query,
5093
+ onChange: (event) => onQuery?.(event.target.value),
5094
+ onKeyDown: (event) => {
5095
+ if (event.key === "Escape") {
5096
+ event.preventDefault();
5097
+ closeOrClear();
5098
+ return;
5099
+ }
5100
+ if (event.key !== "ArrowDown") return;
5101
+ const first = listRef?.current?.querySelector?.(".ps-session-list-button");
5102
+ if (!first) return;
5103
+ event.preventDefault();
5104
+ first.focus({ preventScroll: true });
5105
+ },
5106
+ }),
5107
+ resultLabel ? React.createElement("span", {
5108
+ className: "ps-session-search-count",
5109
+ role: "status",
5110
+ "aria-live": "polite",
5111
+ }, resultLabel) : null,
5112
+ (query || mobile) ? React.createElement("button", {
5113
+ type: "button",
5114
+ className: "ps-session-search-clear",
5115
+ "aria-label": query ? "Clear session search" : "Close session search",
5116
+ title: query ? "Clear" : "Close",
5117
+ onClick: closeOrClear,
5118
+ }, "×") : null));
5119
+ }
5120
+
5056
5121
  function SessionPane({ controller, actions = null, panelClassName = "", structuredRows = false, showDetailBox = null, selection = null, actionsOnly = false, actionsHost = null, onAction = null, onDialogChange = null }) {
5057
5122
  // Mobile keeps its inline detail line and normally gets no detail box — a
5058
5123
  // reserved footer would eat a meaningful slice of a phone screen. The
@@ -5138,6 +5203,32 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
5138
5203
  }), [viewState.activeSessionId, viewState.auth, viewState.branding, viewState.budgetPaused, viewState.canvasBySessionId, viewState.canvasPrefs, viewState.connectionMode, viewState.filterQuery, viewState.listDeselected, viewState.ownerFilter, viewState.pinnedIds, viewState.manualOrder, viewState.selectedIds, viewState.selectMode, viewState.sessionsById, viewState.sessionsFlat]);
5139
5204
  // Hold the previous rows when a poll produced identical output.
5140
5205
  const rows = useStableValue(computedRows);
5206
+ const searchScrollRef = React.useRef({ top: 0, left: 0, restore: false });
5207
+ const searchEnvRef = React.useRef(null);
5208
+ searchEnvRef.current = {
5209
+ query: viewState.filterQuery,
5210
+ setQuery: selection ? selection.onQuery : (value) => controller.setSessionFilterQuery(value),
5211
+ };
5212
+ const setSearchQuery = React.useCallback((value) => {
5213
+ const next = String(value || "");
5214
+ const current = String(searchEnvRef.current?.query || "");
5215
+ const list = sessionListRef.current;
5216
+ if (!current.trim() && next.trim() && list) {
5217
+ searchScrollRef.current = { top: list.scrollTop, left: list.scrollLeft, restore: false };
5218
+ } else if (current.trim() && !next.trim()) {
5219
+ searchScrollRef.current.restore = true;
5220
+ }
5221
+ searchEnvRef.current?.setQuery?.(next);
5222
+ }, []);
5223
+ React.useLayoutEffect(() => {
5224
+ if (viewState.filterQuery || !searchScrollRef.current.restore || !sessionListRef.current) return;
5225
+ sessionListRef.current.scrollTop = searchScrollRef.current.top;
5226
+ sessionListRef.current.scrollLeft = searchScrollRef.current.left;
5227
+ searchScrollRef.current.restore = false;
5228
+ }, [rows, viewState.filterQuery]);
5229
+ const searchMatchCount = viewState.filterQuery
5230
+ ? rows.reduce((count, row) => count + (row.searchMatch ? 1 : 0), 0)
5231
+ : 0;
5141
5232
  const activeSession = viewState.activeSessionId
5142
5233
  ? viewState.sessionsById[viewState.activeSessionId] || null
5143
5234
  : null;
@@ -5845,7 +5936,6 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
5845
5936
  }) : null, actions) : panelActions,
5846
5937
  className: combinedPanelClassName,
5847
5938
  },
5848
- selection ? React.createElement("input", { className: "ps-modal-input", "aria-label": "Find a session", placeholder: "Find a session…", value: selection.query || "", onChange: e => selection.onQuery?.(e.target.value) }) : null,
5849
5939
  React.createElement("div", {
5850
5940
  ref: sessionListRef,
5851
5941
  onKeyDown: selection ? event => {
@@ -5873,7 +5963,7 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
5873
5963
 
5874
5964
  rows.length === 0
5875
5965
  ? React.createElement("div", { className: "ps-empty-state" }, viewState.filterQuery
5876
- ? `No sessions matched "@@${viewState.filterQuery}".`
5966
+ ? `No sessions matched "${viewState.filterQuery}".`
5877
5967
  : "No sessions yet.")
5878
5968
  : rows.map((row) => React.createElement(SessionListRow, {
5879
5969
  key: row.sessionId,
@@ -5887,6 +5977,13 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
5887
5977
  // touch hijacks the finger that should be scrolling the list.
5888
5978
  drag: selection || touchInput ? null : dragHandlers,
5889
5979
  }))),
5980
+ React.createElement(SessionSearchControl, {
5981
+ query: viewState.filterQuery,
5982
+ onQuery: setSearchQuery,
5983
+ matchCount: searchMatchCount,
5984
+ mobile: isMobilePane,
5985
+ listRef: sessionListRef,
5986
+ }),
5890
5987
  (showDetailBox === null ? !isMobilePane : showDetailBox)
5891
5988
  ? React.createElement(SessionDetailBox, {
5892
5989
  session: activeSession,
@@ -5994,6 +6091,12 @@ function FunnelGlyph() {
5994
6091
  React.createElement("path", { d: "M21 4H3l7.2 8.5V19l3.6 2v-8.5L21 4z" }));
5995
6092
  }
5996
6093
 
6094
+ function SearchGlyph() {
6095
+ return React.createElement(Glyph, null,
6096
+ React.createElement("circle", { cx: "11", cy: "11", r: "7" }),
6097
+ React.createElement("path", { d: "m20 20-4-4" }));
6098
+ }
6099
+
5997
6100
  // Summary — a written summary. Was "≣", the same codepoint the Logs tab used.
5998
6101
 
5999
6102
  // Back to the transcript. Replaces the 💬 emoji, which iOS force-renders in
@@ -1 +1 @@
1
- import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-MltzhnwP.js";import{p as f}from"./chunk-JWPE2WC7-DWnKTQ72.js";import{_ as n,l as o}from"./mermaid.core-DALDJDu8.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-Dg1cltg6.js";import"./pilotswarm-B9rLtXpI.js";var v=d().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),l=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:l,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},B={parser:R,db:a,renderer:u,styles:p};export{B as diagram};
1
+ import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-De76fp2s.js";import{p as f}from"./chunk-JWPE2WC7-BD-w4617.js";import{_ as n,l as o}from"./mermaid.core-CEMxYLqP.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-C-gW4wVJ.js";import"./pilotswarm-BJidJRMR.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-DALDJDu8.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,z,a){var O=I-l,i=D-h,n=z-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function U(l,h,I,D,v,A,z){var a=l-I,O=h-D,i=(z?A:-A)/J(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,B=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*J(on(0,T*T*R-P*P)),Y=(P*g-m*S)/R,$=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=Y-B,e=$-t,r=w-B,C=p-t;return x*x+e*e>r*r+C*C&&(Y=w,$=p),{cx:Y,cy:$,x01:-n,y01:-d,x11:Y*(v/T-1),y11:$*(v/T-1)}}function hn(){var l=cn,h=yn,I=j(0),D=null,v=gn,A=dn,z=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-un,c=A.apply(this,arguments)-un,B=rn(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(B>tn-y)a.moveTo(s*F(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*F(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=B,S=B,Y=z.apply(this,arguments)/2,$=Y>y&&(D?+D.apply(this,arguments):J(u*u+s*s)),w=_(rn(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if($>y){var C=sn($/u*q(Y)),K=sn($/s*q(Y));(P-=C*2)>y?(C*=t?1:-1,R+=C,T-=C):(P=0,R=T=(f+c)/2),(S-=K*2)>y?(K*=t?1:-1,m+=K,g-=K):(S=0,m=g=(f+c)/2)}var G=s*F(m),H=s*q(m),L=u*F(T),M=u*q(T);if(w>y){var N=s*F(g),Q=s*q(g),V=u*F(R),W=u*q(R),E;if(B<an)if(E=pn(G,H,V,W,N,Q,L,M)){var X=G-E[0],Z=H-E[1],b=N-E[0],k=Q-E[1],nn=1/q(fn((X*b+Z*k)/(J(X*X+Z*Z)*J(b*b+k*k)))/2),en=J(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=U(V,W,G,H,s,x,t),r=U(N,Q,L,M,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(G,H),a.arc(0,0,s,m,g,!t)):a.moveTo(G,H),!(u>y)||!(P>y)?a.lineTo(L,M):p>y?(e=U(L,M,N,Q,u,-p,t),r=U(G,H,V,W,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-an/2;return[F(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:j(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:j(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:j(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:j(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:j(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:j(+n),i):A},i.padAngle=function(n){return arguments.length?(z=typeof n=="function"?n:j(+n),i):z},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};
1
+ import{Y as ln,$ as an,a0 as F,a1 as q,a2 as j,a3 as un,a4 as y,a5 as tn,a6 as J,a7 as _,a8 as rn,a9 as o,aa as on,ab as sn,ac as fn}from"./mermaid.core-CEMxYLqP.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};