@crouton-kit/crouter 0.3.60 → 0.3.62

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 (120) hide show
  1. package/dist/builtin-views/canvas/core.mjs +2 -1
  2. package/dist/clients/attach/__tests__/context-message.test.d.ts +1 -0
  3. package/dist/clients/attach/__tests__/context-message.test.js +46 -0
  4. package/dist/clients/attach/__tests__/transport-relay.test.d.ts +1 -0
  5. package/dist/clients/attach/__tests__/transport-relay.test.js +176 -0
  6. package/dist/clients/attach/attach-cmd.js +747 -743
  7. package/dist/clients/attach/canvas-panels.d.ts +4 -4
  8. package/dist/clients/attach/canvas-panels.js +108 -37
  9. package/dist/clients/attach/chat-view.js +11 -0
  10. package/dist/clients/attach/context-message.d.ts +3 -1
  11. package/dist/clients/attach/context-message.js +30 -21
  12. package/dist/clients/attach/graph-overlay.d.ts +8 -1
  13. package/dist/clients/attach/graph-overlay.js +201 -22
  14. package/dist/clients/attach/transport-relay.d.ts +51 -0
  15. package/dist/clients/attach/transport-relay.js +196 -0
  16. package/dist/clients/attach/transport-socket.d.ts +29 -0
  17. package/dist/clients/attach/transport-socket.js +164 -0
  18. package/dist/clients/attach/transport.d.ts +32 -0
  19. package/dist/clients/attach/transport.js +26 -0
  20. package/dist/clients/attach/view-socket.d.ts +40 -44
  21. package/dist/clients/attach/view-socket.js +94 -197
  22. package/dist/clients/web/server.js +1 -0
  23. package/dist/commands/memory/delete.js +3 -3
  24. package/dist/commands/memory/origin.js +1 -1
  25. package/dist/commands/memory/read.js +3 -3
  26. package/dist/commands/memory/shared.d.ts +8 -6
  27. package/dist/commands/memory/shared.js +23 -9
  28. package/dist/commands/memory/write.js +3 -3
  29. package/dist/commands/node-worktree.js +9 -9
  30. package/dist/commands/node.js +71 -23
  31. package/dist/commands/sys/doctor.js +1 -0
  32. package/dist/core/__tests__/broker-fork-seam.test.js +17 -2
  33. package/dist/core/__tests__/broker-snapshot-history.test.js +12 -2
  34. package/dist/core/__tests__/canvas-inbox-watcher.test.js +79 -0
  35. package/dist/core/__tests__/context-intro.test.js +11 -0
  36. package/dist/core/__tests__/fixtures/fake-engine.d.ts +9 -4
  37. package/dist/core/__tests__/fixtures/fake-engine.js +12 -4
  38. package/dist/core/__tests__/kickoff.test.js +21 -0
  39. package/dist/core/__tests__/spawn-root.test.js +60 -0
  40. package/dist/core/__tests__/worktree.test.js +24 -1
  41. package/dist/core/canvas/browse/app.d.ts +2 -0
  42. package/dist/core/canvas/browse/app.js +83 -48
  43. package/dist/core/canvas/db.js +3 -2
  44. package/dist/core/canvas/index.d.ts +1 -0
  45. package/dist/core/canvas/index.js +1 -0
  46. package/dist/core/canvas/nav-model.d.ts +5 -3
  47. package/dist/core/canvas/nav-model.js +7 -6
  48. package/dist/core/canvas/paths.d.ts +8 -0
  49. package/dist/core/canvas/paths.js +10 -0
  50. package/dist/core/canvas/render.d.ts +6 -0
  51. package/dist/core/canvas/render.js +117 -20
  52. package/dist/core/canvas/source.d.ts +26 -0
  53. package/dist/core/canvas/source.js +29 -0
  54. package/dist/core/canvas/status-glyph.js +1 -0
  55. package/dist/core/canvas/types.d.ts +4 -3
  56. package/dist/core/config.d.ts +18 -2
  57. package/dist/core/config.js +88 -10
  58. package/dist/core/fault-classifier.d.ts +2 -2
  59. package/dist/core/memory-resolver.d.ts +9 -6
  60. package/dist/core/memory-resolver.js +28 -4
  61. package/dist/core/profiles/__tests__/root-profile.test.d.ts +1 -0
  62. package/dist/core/profiles/__tests__/root-profile.test.js +69 -0
  63. package/dist/core/profiles/manifest.d.ts +2 -0
  64. package/dist/core/profiles/manifest.js +40 -7
  65. package/dist/core/profiles/select.d.ts +4 -4
  66. package/dist/core/profiles/select.js +13 -13
  67. package/dist/core/runtime/__tests__/spawn-env.test.d.ts +1 -0
  68. package/dist/core/runtime/__tests__/spawn-env.test.js +268 -0
  69. package/dist/core/runtime/bearings.d.ts +5 -4
  70. package/dist/core/runtime/bearings.js +12 -6
  71. package/dist/core/runtime/broker.js +59 -7
  72. package/dist/core/runtime/fault-recovery.js +4 -0
  73. package/dist/core/runtime/front-door.js +1 -1
  74. package/dist/core/runtime/host.js +43 -3
  75. package/dist/core/runtime/kickoff.js +6 -0
  76. package/dist/core/runtime/launch.d.ts +6 -1
  77. package/dist/core/runtime/launch.js +7 -1
  78. package/dist/core/runtime/nodes.d.ts +7 -6
  79. package/dist/core/runtime/nodes.js +3 -3
  80. package/dist/core/runtime/situational-context.d.ts +12 -0
  81. package/dist/core/runtime/situational-context.js +48 -0
  82. package/dist/core/runtime/spawn-env.d.ts +27 -0
  83. package/dist/core/runtime/spawn-env.js +169 -0
  84. package/dist/core/runtime/spawn.d.ts +16 -7
  85. package/dist/core/runtime/spawn.js +8 -2
  86. package/dist/core/substrate/session-cache.js +7 -1
  87. package/dist/core/substrate/subject.d.ts +1 -1
  88. package/dist/core/view/remote-canvas-target.d.ts +3 -0
  89. package/dist/core/view/remote-canvas-target.js +25 -0
  90. package/dist/core/worktree.d.ts +4 -4
  91. package/dist/core/worktree.js +41 -19
  92. package/dist/daemon/crtrd.js +32 -0
  93. package/dist/daemon/predicate-eval.js +12 -1
  94. package/dist/index.d.ts +1 -0
  95. package/dist/index.js +4 -0
  96. package/dist/pi-extensions/canvas-bash-valve.d.ts +2 -0
  97. package/dist/pi-extensions/canvas-bash-valve.js +238 -0
  98. package/dist/pi-extensions/canvas-inbox-watcher.d.ts +12 -0
  99. package/dist/pi-extensions/canvas-inbox-watcher.js +41 -5
  100. package/dist/types.d.ts +32 -0
  101. package/dist/types.js +7 -0
  102. package/dist/web-client/apple-touch-icon.png +0 -0
  103. package/dist/web-client/assets/index-BnmSLNLa.css +2 -0
  104. package/dist/web-client/assets/index-BvzxXXGU.js +77 -0
  105. package/dist/web-client/assets/workbox-window.prod.es5-Bd17z0YL.js +2 -0
  106. package/dist/web-client/icon-192.png +0 -0
  107. package/dist/web-client/icon-512.png +0 -0
  108. package/dist/web-client/icon-maskable-512.png +0 -0
  109. package/dist/web-client/index.html +6 -3
  110. package/dist/web-client/manifest.webmanifest +1 -0
  111. package/dist/web-client/sw.js +1 -0
  112. package/docs/public-api.md +5 -3
  113. package/package.json +4 -3
  114. package/dist/web-client/assets/fraunces-latin-ext-wght-normal-Ca2vKHc0.woff2 +0 -0
  115. package/dist/web-client/assets/fraunces-latin-wght-normal-ukD16Tqj.woff2 +0 -0
  116. package/dist/web-client/assets/fraunces-vietnamese-wght-normal-CnvboYUG.woff2 +0 -0
  117. package/dist/web-client/assets/index-72YQdji_.js +0 -77
  118. package/dist/web-client/assets/index-bDhImjJy.css +0 -2
  119. package/dist/web-client/assets/instrument-sans-latin-ext-wght-normal-B5bTHO_g.woff2 +0 -0
  120. package/dist/web-client/assets/instrument-sans-latin-wght-normal-BbzFLZTg.woff2 +0 -0
@@ -1,3 +1,4 @@
1
+ import { type CanvasSource } from '../../core/canvas/source.js';
1
2
  import type { AttachPalette } from './config-load.js';
2
3
  export interface CanvasPanelLines {
3
4
  /** Above-editor manager line(s) — empty for a root node (no manager). */
@@ -5,8 +6,7 @@ export interface CanvasPanelLines {
5
6
  /** Below-editor report row(s) + self's trailing ⚑ asks line — empty when none. */
6
7
  reports: string[];
7
8
  }
8
- /** Build the BASE panel line stacks for `nodeId` from canvas.db (ported verbatim
9
- * from canvas-nav.ts renderBase). `asks` is the per-node pending-ask map the
10
- * caller polls; pass `{}` to omit the ⚑ badges. `palette` (the live theme)
9
+ /** Build the BASE panel line stacks for `nodeId`. `asks` is the per-node
10
+ * pending-ask map the caller polls; pass `{}` to omit the badges. `palette`
11
11
  * drives the headers + dim cells; omit it and the panels render uncolored. */
12
- export declare function buildCanvasPanelLines(nodeId: string, asks: Record<string, number>, palette?: AttachPalette): CanvasPanelLines;
12
+ export declare function buildCanvasPanelLines(nodeId: string, asks: Record<string, number>, palette?: AttachPalette, source?: CanvasSource): Promise<CanvasPanelLines>;
@@ -1,18 +1,19 @@
1
1
  // canvas-panels.ts — the subscribed-node panel for `crtr surface attach` (Unit Q).
2
2
  //
3
- // Native reimplementation of canvas-nav.ts's BASE chrome (renderBase), reading
4
- // the subscription graph DIRECTLY from canvas.db via the shared nav-model layer
5
- // (no pi extension host, no broker frame). Produces the two line stacks the
6
- // viewer paints into Containers around the editor:
3
+ // Native implementation of canvas-nav.ts's BASE chrome (renderBase), reading
4
+ // through the CanvasSource seam so the viewer chrome and graph overlay share the
5
+ // same canvas read authority. Produces the two line stacks the viewer paints
6
+ // into Containers around the editor:
7
7
  // managers → ABOVE the editor: this node's manager (its first subscriber).
8
8
  // reports → BELOW the editor: this node's live reports (active|idle
9
9
  // subscriptions), plus a trailing ⚑ line for self's own asks.
10
10
  //
11
11
  // Pure: given a node id + the per-node ask map it returns ANSI strings; the
12
- // caller owns the Containers and the refresh trigger. beginFrame() memoizes the
13
- // canvas.db reads for this one build pass (mirrors canvas-nav's per-frame cache),
14
- // so a whole panel rebuild is a single fan-out of disk/db reads.
15
- import { beginFrame, cNode, managerOf, liveReports, climbRoot, computeSubtreeActivity, navLabel, nodeGlyph, truncate, tokensCell, cycleBadge, childBadge, liveBelowBadge, askBadge, activityCell, YELLOW, RESET, } from '../../core/canvas/nav-model.js';
12
+ // caller owns the Containers and the refresh trigger. beginFrame() clears the
13
+ // nav-model telemetry cache for this one build pass so token/activity cells are
14
+ // read fresh for every chrome frame.
15
+ import { beginFrame, navLabel, nodeGlyph, truncate, tokensCell, cycleBadge, askBadge, activityCell, YELLOW, RESET, DIM, GREEN, } from '../../core/canvas/nav-model.js';
16
+ import { localCanvasSource } from '../../core/canvas/source.js';
16
17
  /** Column widths for the aligned panel rows (visible columns). name is sized to
17
18
  * the widest label across BOTH panels (capped); kind is wide enough for the
18
19
  * longest persona kind (`orchestrator` = 12) so real kinds never truncate;
@@ -28,49 +29,119 @@ function nameCell(s, w) {
28
29
  function padStartVisible(s, w) {
29
30
  return s.length >= w ? s.slice(0, w) : ' '.repeat(w - s.length) + s;
30
31
  }
31
- /** Build the BASE panel line stacks for `nodeId` from canvas.db (ported verbatim
32
- * from canvas-nav.ts renderBase). `asks` is the per-node pending-ask map the
33
- * caller polls; pass `{}` to omit the ⚑ badges. `palette` (the live theme)
32
+ /** Build the BASE panel line stacks for `nodeId`. `asks` is the per-node
33
+ * pending-ask map the caller polls; pass `{}` to omit the badges. `palette`
34
34
  * drives the headers + dim cells; omit it and the panels render uncolored. */
35
- export function buildCanvasPanelLines(nodeId, asks, palette) {
36
- // Fresh snapshot: drop the previous build's memoized reads so this pass sees
37
- // current disk+db state, then read-once within it.
35
+ export async function buildCanvasPanelLines(nodeId, asks, palette, source = localCanvasSource) {
38
36
  beginFrame();
39
- // Styling: theme-driven when a palette is passed, plain otherwise (so the
40
- // panels still build for a non-attach caller / a test).
37
+ const nodes = new Map();
38
+ const children = new Map();
39
+ const managersOf = new Map();
40
+ const node = (id) => {
41
+ const hit = nodes.get(id);
42
+ if (hit !== undefined)
43
+ return hit;
44
+ const p = source.getNode(id).catch(() => null);
45
+ nodes.set(id, p);
46
+ return p;
47
+ };
48
+ const convoChildIds = (id) => {
49
+ const hit = children.get(id);
50
+ if (hit !== undefined)
51
+ return hit;
52
+ const p = (async () => {
53
+ const refs = await source.subscriptionsOf(id).catch(() => []);
54
+ const out = [];
55
+ for (const ref of refs) {
56
+ if ((await node(ref.node_id))?.kind !== 'human')
57
+ out.push(ref.node_id);
58
+ }
59
+ return out;
60
+ })();
61
+ children.set(id, p);
62
+ return p;
63
+ };
64
+ const managerOf = (id) => {
65
+ const hit = managersOf.get(id);
66
+ if (hit !== undefined)
67
+ return hit;
68
+ const p = source.subscribersOf(id).then((refs) => refs[0]?.node_id).catch(() => undefined);
69
+ managersOf.set(id, p);
70
+ return p;
71
+ };
72
+ const liveReports = async (id) => {
73
+ const out = [];
74
+ for (const cid of await convoChildIds(id)) {
75
+ const st = (await node(cid))?.status;
76
+ if (st === 'active' || st === 'idle')
77
+ out.push(cid);
78
+ }
79
+ return out;
80
+ };
81
+ const climbRoot = async (self) => {
82
+ let cur = self;
83
+ const seen = new Set([cur]);
84
+ for (;;) {
85
+ const mgr = await managerOf(cur);
86
+ if (mgr === undefined || seen.has(mgr))
87
+ break;
88
+ seen.add(mgr);
89
+ cur = mgr;
90
+ }
91
+ return cur;
92
+ };
93
+ const activeBelow = new Map();
94
+ const computeActivity = async (root) => {
95
+ const seen = new Set();
96
+ const visit = async (id) => {
97
+ const selfActive = (await node(id))?.status === 'active';
98
+ if (seen.has(id))
99
+ return selfActive ? 1 : 0;
100
+ seen.add(id);
101
+ let below = 0;
102
+ for (const c of await convoChildIds(id))
103
+ below += await visit(c);
104
+ activeBelow.set(id, below);
105
+ return below + (selfActive ? 1 : 0);
106
+ };
107
+ await visit(root);
108
+ };
109
+ const childBadgeFor = async (n) => {
110
+ if (n === null || n.mode !== 'orchestrator')
111
+ return '';
112
+ const m = (await convoChildIds(n.node_id)).length;
113
+ return m > 0 ? ` ${DIM}⤳${m}${RESET}` : '';
114
+ };
115
+ const liveBelowBadgeFor = (n) => {
116
+ if (n === null || n.status === 'active')
117
+ return '';
118
+ const count = activeBelow.get(n.node_id) ?? 0;
119
+ return count > 0 ? ` ${GREEN}⇣${count}${RESET}` : '';
120
+ };
41
121
  const muted = palette?.muted ?? ((s) => s);
42
122
  const faint = palette?.faint ?? ((s) => s);
43
- // One subtree-activity pass (rooted at the ancestry root) feeds the ⇣N
44
- // live-work-below badge on both the manager line and every report row.
45
- const activity = computeSubtreeActivity(climbRoot(nodeId), nodeId);
46
- // One shared name-column width across BOTH panels so the grids line up.
47
- const mgr = managerOf(nodeId);
48
- const live = liveReports(nodeId);
123
+ await computeActivity(await climbRoot(nodeId));
124
+ const mgr = await managerOf(nodeId);
125
+ const live = await liveReports(nodeId);
49
126
  const gridIds = [...(mgr !== undefined ? [mgr] : []), ...live];
127
+ const gridNodes = await Promise.all(gridIds.map((id) => node(id)));
50
128
  const nameW = gridIds.length > 0
51
- ? Math.min(NAME_CAP, Math.max(...gridIds.map((id) => navLabel(cNode(id), id).length)))
129
+ ? Math.min(NAME_CAP, Math.max(...gridIds.map((id, i) => navLabel(gridNodes[i] ?? null, id).length)))
52
130
  : NAME_CAP;
53
- // A node row laid out on the shared grid: glyph · name · kind · tokens · badges.
54
- const row = (id) => {
55
- const n = cNode(id);
131
+ const row = async (id) => {
132
+ const n = await node(id);
56
133
  const name = nameCell(navLabel(n, id), nameW);
57
134
  const kind = faint(nameCell(n?.kind ?? '', KIND_W));
58
135
  const tokens = muted(padStartVisible(tokensCell(id), TOK_W));
59
- const badges = `${cycleBadge(n)}${childBadge(n)}${liveBelowBadge(n, activity.activeBelow)}${askBadge(id, asks)}${activityCell(id, n)}`;
136
+ const badges = `${cycleBadge(n)}${await childBadgeFor(n)}${liveBelowBadgeFor(n)}${askBadge(id, asks)}${activityCell(id, n)}`;
60
137
  return truncate(` ${nodeGlyph(n)} ${name} ${kind} ${tokens}${badges}`);
61
138
  };
62
- // --- managers panel (↑) ---
63
139
  const managers = [];
64
- if (mgr !== undefined) {
65
- managers.push(row(mgr));
66
- }
67
- // --- reports panel (↓) ---
140
+ if (mgr !== undefined)
141
+ managers.push(await row(mgr));
68
142
  const reports = [];
69
- if (live.length > 0) {
70
- for (const id of live)
71
- reports.push(row(id));
72
- }
73
- // Self's own pending asks (no self row in BASE) → a trailing inline line.
143
+ for (const id of live)
144
+ reports.push(await row(id));
74
145
  const selfAsks = asks[nodeId] ?? 0;
75
146
  if (selfAsks > 0)
76
147
  reports.push(` ${YELLOW}⚑ ${selfAsks} pending${RESET}`);
@@ -25,6 +25,7 @@ import { AssistantMessageComponent, BashExecutionComponent, BranchSummaryMessage
25
25
  import { ContextMessageComponent } from './context-message.js';
26
26
  import { CRTR_OUTPUT_CUSTOM_TYPE, CrtrOutputMessageComponent, createCrtrBashToolDefinition, } from './crtr-output-render.js';
27
27
  import { CRTR_CYCLE_DIVIDER_CUSTOM_TYPE } from '../../core/runtime/session-cycles.js';
28
+ import { SITUATIONAL_CONTEXT_CUSTOM_TYPE } from '../../core/runtime/situational-context.js';
28
29
  import { transformMessageMermaid } from './mermaid-render.js';
29
30
  /** customType stamped on the injected `<crtr-context>` bearings block (mirrors
30
31
  * CONTEXT_INTRO_CUSTOM_TYPE in canvas-context-intro.ts — kept as a local literal
@@ -444,6 +445,16 @@ export class ChatView {
444
445
  (s) => this.spinnerStyle(this.labelStyle(s))));
445
446
  break;
446
447
  }
448
+ // The hidden ambient `<situational-context>` update (canvas-inbox-watcher's
449
+ // ~non-birth wake path). Rendered collapsed exactly like `crtr-context`
450
+ // (same component, distinct label) so an existing-node ambient update is
451
+ // never more prominent in the transcript than the bearings block it rides
452
+ // alongside on fresh birth — the LLM sees the full content either way; only
453
+ // the viewer's display treatment differs.
454
+ if (message.customType === SITUATIONAL_CONTEXT_CUSTOM_TYPE) {
455
+ this.append(new ContextMessageComponent(this.userMessageText(message), this.toolOutputExpanded, this.dimStyle, (s) => this.spinnerStyle(this.labelStyle(s)), 'situational context', ' ambient context update — ctrl+o to expand'));
456
+ break;
457
+ }
447
458
  if (message.customType === CRTR_OUTPUT_CUSTOM_TYPE) {
448
459
  this.append(new CrtrOutputMessageComponent(this.userMessageText(message), this.toolOutputExpanded, this.crtrOutputTheme()));
449
460
  break;
@@ -3,8 +3,10 @@ export declare class ContextMessageComponent implements Component {
3
3
  private readonly body;
4
4
  private readonly dim;
5
5
  private readonly label;
6
+ private readonly labelText;
7
+ private readonly collapsedHint;
6
8
  private expanded;
7
- constructor(body: string, expanded: boolean, dim: (s: string) => string, label: (s: string) => string);
9
+ constructor(body: string, expanded: boolean, dim: (s: string) => string, label: (s: string) => string, labelText?: string, collapsedHint?: string);
8
10
  /** Ctrl+O global tool-output toggle (ChatView.isExpandable duck-type). */
9
11
  setExpanded(expanded: boolean): void;
10
12
  render(width: number): string[];
@@ -1,15 +1,20 @@
1
- // context-message.ts — the viewer's foldable renderer for the `<crtr-context>`
2
- // bearings block (a `crtr-context` custom message). The broker runs the
3
- // canvas-context-intro extension, so the VIEWER has no message renderer for it
4
- // and would otherwise show the full block always. This reimplements the
5
- // extension's `renderContextMessage` natively (src/pi-extensions/
6
- // canvas-context-intro.ts): collapsed one dim line; expanded (Ctrl+O)
7
- // label + full wrapped body. It implements `setExpanded`, so the Ctrl+O
8
- // tool-output toggle (ChatView.toggleToolsExpanded isExpandable) folds it
9
- // alongside tool output.
1
+ // context-message.ts — the viewer's foldable renderer for hidden-context custom
2
+ // messages: the `<crtr-context>` bearings block (customType `crtr-context`) and
3
+ // the ambient `<situational-context>` update (customType
4
+ // `crtr-situational-context`). Both are broker-injected (canvas-context-intro,
5
+ // canvas-inbox-watcher), so the VIEWER has no message renderer for either and
6
+ // would otherwise show the full block always. This reimplements the bearings
7
+ // renderer natively (src/pi-extensions/canvas-context-intro.ts:
8
+ // `renderContextMessage`) and reuses the same collapsed/expand shape for
9
+ // situational context so both render consistently: collapsed → one dim line;
10
+ // expanded (Ctrl+O) → label + full wrapped body. It implements `setExpanded`,
11
+ // so the Ctrl+O tool-output toggle (ChatView.toggleToolsExpanded →
12
+ // isExpandable) folds it alongside tool output.
10
13
  import { truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';
11
- /** The label shown in both states. Mirrors the extension's customType stamp. */
14
+ /** Default label/hint for the `crtr-context` bearings block. Mirrors the
15
+ * extension's customType stamp. */
12
16
  const LABEL = 'crtr context';
17
+ const COLLAPSED_HINT = ' orienting bearings — ctrl+o to expand';
13
18
  /** Char-based wrap that never emits an over-width line (pi-tui hard-crashes on
14
19
  * one). Visible-width aware so wide glyphs in the bearings tree count right. */
15
20
  function wrapLine(line, width) {
@@ -34,11 +39,15 @@ export class ContextMessageComponent {
34
39
  body;
35
40
  dim;
36
41
  label;
42
+ labelText;
43
+ collapsedHint;
37
44
  expanded;
38
- constructor(body, expanded, dim, label) {
45
+ constructor(body, expanded, dim, label, labelText = LABEL, collapsedHint = COLLAPSED_HINT) {
39
46
  this.body = body;
40
47
  this.dim = dim;
41
48
  this.label = label;
49
+ this.labelText = labelText;
50
+ this.collapsedHint = collapsedHint;
42
51
  this.expanded = expanded;
43
52
  }
44
53
  /** Ctrl+O global tool-output toggle (ChatView.isExpandable duck-type). */
@@ -48,18 +57,18 @@ export class ContextMessageComponent {
48
57
  render(width) {
49
58
  const w = width > 0 ? width : 80;
50
59
  if (!this.expanded) {
51
- // Paint the [crtr context] label in the accent color and the rest dim, so
52
- // the label reads distinctly in BOTH states. Truncate BEFORE painting so the
53
- // ANSI wrappers never inflate the measured width (an over-wide line aborts
54
- // the whole TUI render).
55
- const labelText = `[${LABEL}]`;
56
- const rest = ' orienting bearings — ctrl+o to expand';
57
- const labelW = visibleWidth(labelText);
60
+ // Paint the [label] tag in the accent color and the rest dim, so the label
61
+ // reads distinctly in BOTH states. Truncate BEFORE painting so the ANSI
62
+ // wrappers never inflate the measured width (an over-wide line aborts the
63
+ // whole TUI render).
64
+ const labelTag = `[${this.labelText}]`;
65
+ const rest = this.collapsedHint;
66
+ const labelW = visibleWidth(labelTag);
58
67
  if (labelW >= w)
59
- return [this.label(truncateToWidth(labelText, w, '…'))];
60
- return [this.label(labelText) + this.dim(truncateToWidth(rest, w - labelW, '…'))];
68
+ return [this.label(truncateToWidth(labelTag, w, '…'))];
69
+ return [this.label(labelTag) + this.dim(truncateToWidth(rest, w - labelW, '…'))];
61
70
  }
62
- const lines = [this.label(truncateToWidth(`[${LABEL}]`, w, '…')), ''];
71
+ const lines = [this.label(truncateToWidth(`[${this.labelText}]`, w, '…')), ''];
63
72
  for (const raw of this.body.split('\n')) {
64
73
  for (const seg of wrapLine(raw, w))
65
74
  lines.push(seg);
@@ -1,10 +1,12 @@
1
1
  import { type Component, type TUI } from '@earendil-works/pi-tui';
2
+ import { type CanvasSource } from '../../core/canvas/source.js';
2
3
  import type { AttachPalette } from './config-load.js';
3
4
  export declare class GraphOverlay implements Component {
4
5
  private readonly tui;
5
6
  private readonly self;
6
7
  private readonly getAsks;
7
8
  private readonly palette;
9
+ private readonly source;
8
10
  private handle;
9
11
  /** Manual fold OVERRIDES (h collapses → userCollapsed, l expands → userExpanded);
10
12
  * both override the default activity-driven policy and survive open/close. */
@@ -17,7 +19,9 @@ export declare class GraphOverlay implements Component {
17
19
  private scrollTop;
18
20
  /** Transient y/n confirm gate (kill). */
19
21
  private pendingConfirm;
20
- constructor(tui: TUI, self: string, getAsks: () => Record<string, number>, palette: AttachPalette);
22
+ private snapshot;
23
+ private snapshotSeq;
24
+ constructor(tui: TUI, self: string, getAsks: () => Record<string, number>, palette: AttachPalette, source?: CanvasSource);
21
25
  isOpen(): boolean;
22
26
  open(): void;
23
27
  close(): void;
@@ -25,6 +29,8 @@ export declare class GraphOverlay implements Component {
25
29
  /** Repaint if shown (the low-rate ask poll calls this). */
26
30
  refresh(): void;
27
31
  invalidate(): void;
32
+ private buildSnapshot;
33
+ private renderSnapshotRow;
28
34
  render(width: number): string[];
29
35
  /** A top/bottom border row: `<lc>─ <label> ────<rc>`, frame in the theme border
30
36
  * color, the label keeping its own styling. The label is visible-truncated to
@@ -38,6 +44,7 @@ export declare class GraphOverlay implements Component {
38
44
  * `id` has no viewer yet a fresh one splits beside this pane; if it already has
39
45
  * one, that viewer is navigated in place (this pane is left as-is). We pass
40
46
  * TMUX_PANE explicitly so the anchor is THIS pane even if env resolution differs. */
47
+ private logAsyncFailure;
41
48
  private focusTarget;
42
49
  private shellCrtr;
43
50
  }