pi-better-background-tasks 0.2.1 → 0.2.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-better-background-tasks",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Pi extension for durable background shell tasks, watchers, logs, and status inspection.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -83,6 +83,7 @@ type NavigatorState = {
83
83
  deps?: HostDeps;
84
84
  lastHint?: string | null;
85
85
  lastMainListLines?: string[];
86
+ lastMainListSignature?: string;
86
87
  mainListWidgetInstalled?: boolean;
87
88
  mainListRequestRender?: () => void;
88
89
  mainListSelectedId?: string;
@@ -110,7 +111,6 @@ const MAIN_LIST_FALLBACK_WIDTH = 100;
110
111
  const DETAIL_OVERLAY_FOOTER_ROWS = 3;
111
112
  const EVIDENCE_SECTION_ID = "__evidence__";
112
113
  const RUNNING_DOT_GLYPH = "●";
113
- const RUNNING_DOT_FRAMES = ["dim", "accent", "accent", "dim"] as const;
114
114
 
115
115
  function state(): NavigatorState {
116
116
  const g = globalThis as typeof globalThis & { [GLOBAL_KEY]?: NavigatorState };
@@ -199,6 +199,7 @@ export function disposeBackgroundWorkNavigator(ctx?: ExtensionContext): void {
199
199
  }
200
200
  s.lastHint = undefined;
201
201
  s.lastMainListLines = undefined;
202
+ s.lastMainListSignature = undefined;
202
203
  s.mainListDeadlineScheduler?.dispose();
203
204
  s.mainListDeadlineScheduler = undefined;
204
205
  s.mainListWidgetInstalled = false;
@@ -263,6 +264,12 @@ function refreshMainListWidget(): void {
263
264
  s.mainListDeadlineScheduler?.schedule(nextExpiry - now);
264
265
  }
265
266
  syncMainListSelection(rows);
267
+ const nextSignature = rows.length ? mainListSignature(rows, {
268
+ selectedId: s.mainListFocused ? s.mainListSelectedId : undefined,
269
+ focused: s.mainListFocused === true,
270
+ }) : undefined;
271
+ const changed = nextSignature !== s.lastMainListSignature;
272
+ s.lastMainListSignature = nextSignature;
266
273
  s.lastMainListLines = rows.length ? buildMainListLines(rows, MAIN_LIST_FALLBACK_WIDTH, deps.truncate, themeFg(ctx), {
267
274
  selectedId: s.mainListFocused ? s.mainListSelectedId : undefined,
268
275
  focused: s.mainListFocused === true,
@@ -272,6 +279,7 @@ function refreshMainListWidget(): void {
272
279
  try { (ctx.ui as any).setWidget?.(MAIN_LIST_WIDGET_KEY, undefined); } catch { /* ignore */ }
273
280
  s.mainListWidgetInstalled = false;
274
281
  s.mainListRequestRender = undefined;
282
+ s.lastMainListSignature = undefined;
275
283
  return;
276
284
  }
277
285
  if (!s.mainListWidgetInstalled) {
@@ -280,7 +288,9 @@ function refreshMainListWidget(): void {
280
288
  s.mainListWidgetInstalled = true;
281
289
  } catch { /* ignore */ }
282
290
  }
283
- try { s.mainListRequestRender?.(); } catch { /* ignore */ }
291
+ if (changed) {
292
+ try { s.mainListRequestRender?.(); } catch { /* ignore */ }
293
+ }
284
294
  }
285
295
 
286
296
  function themeFg(ctx: ExtensionContext): (color: string, value: string) => string {
@@ -481,9 +491,40 @@ function statusGlyph(row: InternalRow): string {
481
491
  }
482
492
 
483
493
  function statusIndicator(row: InternalRow, now = Date.now()): { glyph: string; color: string } {
494
+ void now;
484
495
  if (row.statusTone !== "running") return { glyph: statusGlyph(row), color: toneColor(row.statusTone, row.status) };
485
- const frame = Math.floor(now / DETAIL_TICK_MS) % RUNNING_DOT_FRAMES.length;
486
- return { glyph: RUNNING_DOT_GLYPH, color: RUNNING_DOT_FRAMES[frame]! };
496
+ return { glyph: RUNNING_DOT_GLYPH, color: "accent" };
497
+ }
498
+
499
+ function mainListSignature(rows: InternalRow[], options: { selectedId?: string; focused?: boolean } = {}): string {
500
+ return JSON.stringify({
501
+ focused: options.focused === true,
502
+ selectedId: options.focused === true ? options.selectedId ?? null : null,
503
+ rows: rows.map((row) => ({
504
+ navigatorId: row.navigatorId,
505
+ providerLabel: row.providerLabel,
506
+ parentRow: row.parentRow === true,
507
+ providerId: row.providerId,
508
+ id: row.id,
509
+ name: row.name ?? null,
510
+ model: row.model ?? null,
511
+ effort: row.effort ?? null,
512
+ tool: row.tool ?? null,
513
+ tokens: row.tokens ?? null,
514
+ command: row.command ?? null,
515
+ status: row.status,
516
+ statusTone: row.statusTone,
517
+ kind: row.kind,
518
+ primary: row.primary,
519
+ secondary: row.secondary ?? null,
520
+ facts: stableFacts(row.facts),
521
+ sortStartedAt: row.sortStartedAt,
522
+ })),
523
+ });
524
+ }
525
+
526
+ function stableFacts(facts: string[] | undefined): string[] {
527
+ return (facts ?? []).filter((fact) => !/\bleft$/.test(singleLine(fact)));
487
528
  }
488
529
 
489
530
  function rowSummary(row: InternalRow): string {
@@ -868,13 +909,13 @@ function createOverlayComponent(
868
909
  width,
869
910
  deps.truncate,
870
911
  fg,
871
- { minRows: detailRows },
912
+ { minRows: detailRows, bottomFooter: false, logTailRows },
872
913
  );
873
914
  } else {
874
915
  transcriptDetail = null;
875
916
  transcriptComponent = null;
876
917
  contentLines = mode === "detail"
877
- ? buildDetailLines(detail, width, deps.truncate, fg, { expandedSections, logTailRows, minRows: detailRows })
918
+ ? buildDetailLines(detail, width, deps.truncate, fg, { expandedSections, logTailRows, minRows: detailRows, bottomFooter: false })
878
919
  : buildListLines(overlayState, width, deps.truncate, fg);
879
920
  }
880
921
  if (mode !== "detail") return contentLines;
@@ -953,9 +994,11 @@ function buildTranscriptDetailLines(
953
994
  width: number,
954
995
  truncate: (s: string, width: number) => string,
955
996
  fg: (color: string, value: string) => string,
956
- options: { minRows?: number } = {},
997
+ options: { minRows?: number; bottomFooter?: boolean; logTailRows?: number } = {},
957
998
  ): string[] {
958
999
  const actions = [...(detail.footerActions ?? ["x close"]), "Esc close"].join(" · ");
1000
+ const tailRows = options.logTailRows ?? DEFAULT_LOG_TAIL_ROWS;
1001
+ const shownTranscriptLines = transcriptLines.length ? transcriptLines.slice(-tailRows) : [" (no transcript yet)"];
959
1002
  const lines: string[] = [
960
1003
  fg("accent", rule(detail.title, width)),
961
1004
  dim(` ← main · ${actions}`, fg),
@@ -964,10 +1007,11 @@ function buildTranscriptDetailLines(
964
1007
  ];
965
1008
  if (detail.subtitle) lines.push(` summary ${detail.subtitle}`);
966
1009
  for (const item of detail.metadata) lines.push(` ${item.label.padEnd(8, " ").slice(0, 8)} ${item.value}`);
967
- lines.push("", dim(section("transcript", width), fg));
1010
+ lines.push("", dim(section(`transcript · latest ${tailRows} rows`, width), fg));
968
1011
  if (detail.transcriptDiagnostic) lines.push(` ${dim(detail.transcriptDiagnostic, fg)}`);
969
- lines.push(...(transcriptLines.length ? transcriptLines : [" (no transcript yet)"]));
1012
+ lines.push(...shownTranscriptLines);
970
1013
  lines.push("");
1014
+ if (options.bottomFooter === false) return lines.map((line) => safeTruncate(line, width, truncate));
971
1015
  const footerLines = [dim(` ← main · ${actions}`, fg), dim(rule("", width), fg)];
972
1016
  padBeforeFooter(lines, footerLines.length, options.minRows);
973
1017
  lines.push(...footerLines);
@@ -1051,10 +1095,11 @@ function buildDetailLines(
1051
1095
  width: number,
1052
1096
  truncate: (s: string, width: number) => string,
1053
1097
  fg: (color: string, value: string) => string,
1054
- options: { expandedSections?: Set<string>; logTailRows?: number; minRows?: number } = {},
1098
+ options: { expandedSections?: Set<string>; logTailRows?: number; minRows?: number; bottomFooter?: boolean } = {},
1055
1099
  ): string[] {
1056
1100
  if (!detail) {
1057
1101
  const lines = [rule("Work unavailable", width), dim(" ← back · Esc close", fg), ""];
1102
+ if (options.bottomFooter === false) return lines.map((line) => safeTruncate(line, width, truncate));
1058
1103
  const footerLines = [dim(rule("", width), fg)];
1059
1104
  padBeforeFooter(lines, footerLines.length, options.minRows);
1060
1105
  lines.push(...footerLines);
@@ -1108,14 +1153,17 @@ function buildDetailLines(
1108
1153
  for (const raw of shown) lines.push(raw ? ` ${raw}` : " ");
1109
1154
  }
1110
1155
  } else {
1111
- const evidenceLabel = /log/i.test(detail.evidence.label) ? `${detail.evidence.label} · latest ${tailRows} rows` : detail.evidence.label;
1156
+ const tailEvidence = isTailEvidence(detail);
1157
+ const evidenceLabel = tailEvidence ? `${detail.evidence.label} · latest ${tailRows} rows` : detail.evidence.label;
1112
1158
  lines.push(dim(section(evidenceLabel, width), fg));
1113
1159
  const wrapped = /log/i.test(detail.evidence.label)
1114
1160
  ? wrapLogText(body, width - 6)
1115
- : body.split(/\r?\n/);
1116
- for (const raw of wrapped) lines.push(raw ? ` ${raw}` : " ");
1161
+ : wrapEvidenceText(body, width - 6);
1162
+ const shown = tailEvidence ? wrapped.slice(-tailRows) : wrapped;
1163
+ for (const raw of shown) lines.push(raw ? ` ${raw}` : " ");
1117
1164
  }
1118
1165
  lines.push("");
1166
+ if (options.bottomFooter === false) return lines.map((line) => safeTruncate(line, width, truncate));
1119
1167
  const footerLines = [dim(` ← back · ${actions}`, fg), dim(rule("", width), fg)];
1120
1168
  padBeforeFooter(lines, footerLines.length, options.minRows);
1121
1169
  lines.push(...footerLines);
@@ -1141,7 +1189,11 @@ function applyDefaultExpandedSections(detail: BackgroundWorkDetail | null, expan
1141
1189
  }
1142
1190
 
1143
1191
  function isFoldableEvidence(detail: BackgroundWorkDetail): boolean {
1144
- return !/log/i.test(detail.evidence.label);
1192
+ return !isTailEvidence(detail);
1193
+ }
1194
+
1195
+ function isTailEvidence(detail: BackgroundWorkDetail): boolean {
1196
+ return /log|transcript/i.test(detail.evidence.label);
1145
1197
  }
1146
1198
 
1147
1199
  function sectionHeader(label: string, width: number): string {