pi-goosedump 0.12.32 → 0.12.33

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 (2) hide show
  1. package/dist/index.ts +92 -16
  2. package/package.json +2 -2
package/dist/index.ts CHANGED
@@ -1251,6 +1251,40 @@ async function runSearchPanel(ctx, query) {
1251
1251
  async function runSessionsPanel(ctx, executablePath) {
1252
1252
  const OVERLAY_WIDTH = 84;
1253
1253
  const VIEWPORT = 18;
1254
+ function buildChildrenMap(sessions) {
1255
+ const byKey = new Map(sessions.map((s) => [`${s.provider}:${s.id}`, s]));
1256
+ const childrenMap = new Map;
1257
+ for (const session of sessions) {
1258
+ if (!session.parent)
1259
+ continue;
1260
+ const parentKey = `${session.provider}:${session.parent}`;
1261
+ if (!byKey.has(parentKey))
1262
+ continue;
1263
+ const siblings = childrenMap.get(parentKey) ?? [];
1264
+ siblings.push(session);
1265
+ childrenMap.set(parentKey, siblings);
1266
+ }
1267
+ for (const siblings of childrenMap.values()) {
1268
+ siblings.sort((a, b) => {
1269
+ const timeA = a.from ? new Date(a.from).getTime() : 0;
1270
+ const timeB = b.from ? new Date(b.from).getTime() : 0;
1271
+ return timeB - timeA;
1272
+ });
1273
+ }
1274
+ return childrenMap;
1275
+ }
1276
+ function flattenTree(roots, childrenMap, expanded, depth) {
1277
+ const rows = [];
1278
+ for (const session of roots) {
1279
+ const key = `${session.provider}:${session.id}`;
1280
+ const children = childrenMap.get(key) ?? [];
1281
+ rows.push({ session, depth, hasChildren: children.length > 0 });
1282
+ if (children.length > 0 && expanded.has(key)) {
1283
+ rows.push(...flattenTree(children, childrenMap, expanded, depth + 1));
1284
+ }
1285
+ }
1286
+ return rows;
1287
+ }
1254
1288
  await ctx.ui.custom((tui, theme, _kb, done) => {
1255
1289
  const currentTarget = currentSessionTarget(ctx);
1256
1290
  const borderFg = (s) => theme.fg("border", s);
@@ -1260,38 +1294,55 @@ async function runSessionsPanel(ctx, executablePath) {
1260
1294
  const dim = (s) => theme.fg("dim", s);
1261
1295
  const muted = (s) => theme.fg("muted", s);
1262
1296
  let sessions = [];
1297
+ let childrenMap = new Map;
1298
+ let roots = [];
1299
+ let displayList = [];
1300
+ let expandedNodes = new Set;
1263
1301
  let error = null;
1264
1302
  let selected = 0;
1265
1303
  let scroll = 0;
1304
+ function rebuildDisplayList() {
1305
+ displayList = flattenTree(roots, childrenMap, expandedNodes, 0);
1306
+ }
1266
1307
  async function loadSessions() {
1267
1308
  try {
1268
1309
  const all = await goosedumpListSessions(undefined, executablePath);
1269
- const cwdSessions = all.filter((s) => s.cwd === ctx.cwd);
1310
+ const cwdIds = new Set(all.filter((s) => s.cwd === ctx.cwd).map((s) => `${s.provider}:${s.id}`));
1311
+ const cwdSessions = all.filter((s) => s.cwd === ctx.cwd || s.parent !== null && cwdIds.has(`${s.provider}:${s.parent}`));
1270
1312
  cwdSessions.sort((a, b) => {
1271
1313
  const timeA = a.from ? new Date(a.from).getTime() : 0;
1272
1314
  const timeB = b.from ? new Date(b.from).getTime() : 0;
1273
1315
  return timeB - timeA;
1274
1316
  });
1275
1317
  sessions = cwdSessions;
1318
+ childrenMap = buildChildrenMap(sessions);
1319
+ const sessionKeys = new Set(sessions.map((s) => `${s.provider}:${s.id}`));
1320
+ roots = sessions.filter((s) => s.parent === null || !sessionKeys.has(`${s.provider}:${s.parent}`));
1321
+ expandedNodes = new Set([...expandedNodes].filter((key) => sessionKeys.has(key)));
1322
+ rebuildDisplayList();
1276
1323
  if (sessions.length === 0) {
1277
1324
  error = "No sessions found for current directory.";
1278
1325
  } else {
1279
1326
  error = null;
1280
1327
  }
1281
- selected = Math.min(selected, Math.max(0, sessions.length - 1));
1282
- scroll = Math.min(scroll, Math.max(0, sessions.length - VIEWPORT));
1328
+ selected = Math.min(selected, Math.max(0, displayList.length - 1));
1329
+ scroll = Math.min(scroll, Math.max(0, displayList.length - VIEWPORT));
1283
1330
  tui.requestRender();
1284
1331
  } catch (err) {
1285
1332
  sessions = [];
1333
+ childrenMap = new Map;
1334
+ roots = [];
1335
+ displayList = [];
1286
1336
  error = err instanceof Error ? err.message : String(err);
1287
1337
  tui.requestRender();
1288
1338
  }
1289
1339
  }
1290
1340
  loadSessions();
1291
1341
  async function deleteSelected() {
1292
- const s = sessions[selected];
1293
- if (!s)
1342
+ const row = displayList[selected];
1343
+ if (!row)
1294
1344
  return;
1345
+ const s = row.session;
1295
1346
  const target = formatTarget(s.provider, s.id);
1296
1347
  if (currentTarget === target) {
1297
1348
  ctx.ui.notify("Cannot delete the active session.", "error");
@@ -1319,29 +1370,33 @@ async function runSessionsPanel(ctx, executablePath) {
1319
1370
  lines.push(`${borderFg("╰")}${borderFg("─".repeat(width - 2))}${borderFg("╯")}`);
1320
1371
  return lines;
1321
1372
  }
1322
- const maxScroll = Math.max(0, sessions.length - VIEWPORT);
1373
+ const maxScroll = Math.max(0, displayList.length - VIEWPORT);
1323
1374
  const clampScroll = Math.min(scroll, maxScroll);
1324
- const window = sessions.slice(clampScroll, clampScroll + VIEWPORT);
1375
+ const window = displayList.slice(clampScroll, clampScroll + VIEWPORT);
1325
1376
  for (let i = 0;i < window.length; i++) {
1326
1377
  const absolute = clampScroll + i;
1327
- const s = window[i];
1378
+ const row = window[i];
1379
+ const s = row.session;
1328
1380
  const isSelected = absolute === selected;
1329
1381
  const isActive = currentTarget === formatTarget(s.provider, s.id);
1382
+ const key = `${s.provider}:${s.id}`;
1330
1383
  const cursor = isSelected ? accent("▶") : " ";
1384
+ const indent = " ".repeat(row.depth * 2);
1385
+ const toggle = row.hasChildren ? expandedNodes.has(key) ? muted("−") : muted("+") : " ";
1331
1386
  const activeMark = isActive ? accent("(active)") : "";
1332
1387
  const idText = isSelected ? accent(s.id) : text(s.id);
1333
1388
  const providerTag = muted(`[${s.provider}]`);
1334
1389
  const countTag = dim(`${s.count} entries`);
1335
1390
  const dateTag = s.from ? dim(new Date(s.from).toLocaleDateString()) : "";
1336
- const rowContent = ` ${cursor} ${idText} ${providerTag} ${countTag} ${dateTag} ${activeMark}`.trimEnd();
1391
+ const rowContent = ` ${cursor}${indent}${toggle} ${idText} ${providerTag} ${countTag} ${dateTag} ${activeMark}`.trimEnd();
1337
1392
  lines.push(makeRow(rowContent, innerW, border));
1338
1393
  }
1339
1394
  for (let i = window.length;i < VIEWPORT; i++)
1340
1395
  lines.push(makeRow("", innerW, border));
1341
- const count = sessions.length;
1396
+ const count = displayList.length;
1342
1397
  const position = count > VIEWPORT ? `${clampScroll + 1}-${Math.min(clampScroll + VIEWPORT, count)} of ${count}` : `${count} session${count === 1 ? "" : "s"}`;
1343
1398
  lines.push(makeRow("", innerW, border));
1344
- lines.push(makeRow(dim(`↑↓ navigate enter resume d delete esc close (${position})`), innerW, border));
1399
+ lines.push(makeRow(dim(`↑↓ navigate space/+/− fold enter resume d delete esc close (${position})`), innerW, border));
1345
1400
  lines.push(`${borderFg("╰")}${borderFg("─".repeat(width - 2))}${borderFg("╯")}`);
1346
1401
  return lines;
1347
1402
  },
@@ -1350,9 +1405,9 @@ async function runSessionsPanel(ctx, executablePath) {
1350
1405
  done(undefined);
1351
1406
  return;
1352
1407
  }
1353
- if (error && sessions.length === 0)
1408
+ if (error && displayList.length === 0)
1354
1409
  return;
1355
- const maxScroll = Math.max(0, sessions.length - VIEWPORT);
1410
+ const maxScroll = Math.max(0, displayList.length - VIEWPORT);
1356
1411
  if (matchesKey(data, Key.up)) {
1357
1412
  selected = Math.max(0, selected - 1);
1358
1413
  if (selected < scroll)
@@ -1361,7 +1416,7 @@ async function runSessionsPanel(ctx, executablePath) {
1361
1416
  return;
1362
1417
  }
1363
1418
  if (matchesKey(data, Key.down)) {
1364
- selected = Math.min(Math.max(0, sessions.length - 1), selected + 1);
1419
+ selected = Math.min(Math.max(0, displayList.length - 1), selected + 1);
1365
1420
  if (selected >= scroll + VIEWPORT)
1366
1421
  scroll = selected - VIEWPORT + 1;
1367
1422
  tui.requestRender();
@@ -1374,17 +1429,38 @@ async function runSessionsPanel(ctx, executablePath) {
1374
1429
  return;
1375
1430
  }
1376
1431
  if (matchesKey(data, Key.pageDown)) {
1377
- selected = Math.min(sessions.length - 1, selected + VIEWPORT);
1432
+ selected = Math.min(displayList.length - 1, selected + VIEWPORT);
1378
1433
  scroll = Math.min(maxScroll, Math.max(scroll, selected - VIEWPORT + 1));
1379
1434
  tui.requestRender();
1380
1435
  return;
1381
1436
  }
1437
+ const row = displayList[selected];
1438
+ if (row && row.hasChildren && (data === " " || data === "+" || data === "-")) {
1439
+ const key = `${row.session.provider}:${row.session.id}`;
1440
+ if (data === "+") {
1441
+ expandedNodes.add(key);
1442
+ } else if (data === "-") {
1443
+ expandedNodes.delete(key);
1444
+ } else if (expandedNodes.has(key)) {
1445
+ expandedNodes.delete(key);
1446
+ } else {
1447
+ expandedNodes.add(key);
1448
+ }
1449
+ rebuildDisplayList();
1450
+ const newIndex = displayList.findIndex((d) => `${d.session.provider}:${d.session.id}` === key);
1451
+ if (newIndex >= 0)
1452
+ selected = newIndex;
1453
+ if (selected < scroll)
1454
+ scroll = selected;
1455
+ tui.requestRender();
1456
+ return;
1457
+ }
1382
1458
  if (data === "d" || data === "D" || matchesKey(data, Key.delete)) {
1383
1459
  deleteSelected();
1384
1460
  return;
1385
1461
  }
1386
1462
  if (matchesKey(data, Key.enter) || matchesKey(data, Key.return)) {
1387
- const s = sessions[selected];
1463
+ const s = displayList[selected]?.session;
1388
1464
  if (!s)
1389
1465
  return;
1390
1466
  if (s.provider !== "pi") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goosedump",
3
- "version": "0.12.32",
3
+ "version": "0.12.33",
4
4
  "description": "Pi extension for goosedump session search, durable memory, session management, and compaction",
5
5
  "keywords": [
6
6
  "compaction",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@earendil-works/pi-tui": "^0.82.1",
41
- "@jarkkojs/goosedump": "^0.12.32",
41
+ "@jarkkojs/goosedump": "^0.12.33",
42
42
  "@sinclair/typebox": "^0.34.49"
43
43
  },
44
44
  "devDependencies": {