@bastani/atomic 0.8.4-0 → 0.8.5-0
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/CHANGELOG.md +16 -0
- package/README.md +24 -23
- package/dist/builtin/intercom/README.md +5 -5
- package/dist/builtin/intercom/index.ts +1 -1
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/intercom/ui/compose.ts +19 -1
- package/dist/builtin/intercom/ui/session-list.ts +19 -1
- package/dist/builtin/mcp/README.md +3 -3
- package/dist/builtin/mcp/commands.ts +1 -1
- package/dist/builtin/mcp/host-html-template.ts +1 -1
- package/dist/builtin/mcp/mcp-panel.ts +14 -14
- package/dist/builtin/mcp/mcp-setup-panel.ts +4 -4
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/mcp/tool-result-renderer.ts +1 -1
- package/dist/builtin/subagents/README.md +3 -3
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/src/tui/render.ts +1844 -1062
- package/dist/builtin/web-access/README.md +1 -1
- package/dist/builtin/web-access/curator-page.ts +2 -2
- package/dist/builtin/web-access/index.ts +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/README.md +34 -7
- package/dist/builtin/workflows/builtin/deep-research-codebase.ts +23 -4
- package/dist/builtin/workflows/builtin/ralph.ts +1 -1
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/skills/workflow/SKILL.md +75 -16
- package/dist/builtin/workflows/skills/workflow/references/running-workflows.md +34 -11
- package/dist/builtin/workflows/skills/workflow/references/sdk-authoring.md +111 -20
- package/dist/builtin/workflows/src/extension/discovery.ts +32 -4
- package/dist/builtin/workflows/src/extension/index.ts +347 -63
- package/dist/builtin/workflows/src/extension/render-call.ts +3 -1
- package/dist/builtin/workflows/src/extension/render-result.ts +7 -0
- package/dist/builtin/workflows/src/extension/runtime.ts +4 -2
- package/dist/builtin/workflows/src/extension/wiring.ts +32 -8
- package/dist/builtin/workflows/src/extension/workflow-schema.ts +36 -14
- package/dist/builtin/workflows/src/runs/background/runner.ts +2 -2
- package/dist/builtin/workflows/src/runs/background/status.ts +89 -0
- package/dist/builtin/workflows/src/runs/foreground/executor.ts +338 -78
- package/dist/builtin/workflows/src/runs/foreground/stage-control-registry.ts +2 -0
- package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +55 -7
- package/dist/builtin/workflows/src/runs/shared/workflow-runner.ts +146 -10
- package/dist/builtin/workflows/src/shared/store.ts +29 -0
- package/dist/builtin/workflows/src/shared/types.ts +25 -4
- package/dist/builtin/workflows/src/tui/graph-canvas.ts +69 -2
- package/dist/builtin/workflows/src/tui/graph-view.ts +97 -182
- package/dist/builtin/workflows/src/tui/header.ts +36 -20
- package/dist/builtin/workflows/src/tui/inline-form-card.ts +129 -46
- package/dist/builtin/workflows/src/tui/inline-form-editor.ts +111 -36
- package/dist/builtin/workflows/src/tui/inputs-picker.ts +311 -91
- package/dist/builtin/workflows/src/tui/layout.ts +1 -1
- package/dist/builtin/workflows/src/tui/node-card.ts +66 -37
- package/dist/builtin/workflows/src/tui/overlay-adapter.ts +20 -6
- package/dist/builtin/workflows/src/tui/prompt-card.ts +262 -85
- package/dist/builtin/workflows/src/tui/run-detail.ts +50 -31
- package/dist/builtin/workflows/src/tui/session-confirm.ts +21 -14
- package/dist/builtin/workflows/src/tui/session-picker.ts +35 -26
- package/dist/builtin/workflows/src/tui/stage-chat-view.ts +531 -960
- package/dist/builtin/workflows/src/tui/status-helpers.ts +6 -0
- package/dist/builtin/workflows/src/tui/status-list.ts +8 -4
- package/dist/builtin/workflows/src/tui/store-widget-installer.ts +7 -2
- package/dist/builtin/workflows/src/tui/switcher.ts +55 -25
- package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +33 -1
- package/dist/builtin/workflows/src/tui/workflow-list.ts +10 -6
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +20 -6
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +3 -3
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +7 -7
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +2 -2
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +3 -3
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
- package/dist/core/export-html/tool-renderer.js.map +1 -1
- package/dist/core/extensions/loader.d.ts +3 -2
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +24 -12
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +6 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +28 -17
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/package-manager.d.ts +1 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +65 -28
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +13 -5
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts +3 -3
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +1 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +2 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +5 -3
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.d.ts +1 -1
- package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.js +1 -1
- package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/dialog-builder.d.ts +8 -8
- package/dist/core/tools/ask-user-question/view/dialog-builder.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/dialog-builder.js +6 -6
- package/dist/core/tools/ask-user-question/view/dialog-builder.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +1 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +1 -1
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +7 -4
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +3 -2
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +3 -2
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +2 -2
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts +2 -1
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/todos.d.ts.map +1 -1
- package/dist/core/tools/todos.js +1 -1
- package/dist/core/tools/todos.js.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.d.ts +4 -3
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +1 -1
- package/dist/core/tools/write.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +2 -2
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +3 -3
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +3 -3
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/chat-message-renderer.d.ts +2 -1
- package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +1 -1
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +3 -0
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +13 -3
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +1 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +2 -1
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +2 -1
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.d.ts +1 -0
- package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.js +47 -5
- package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +5 -5
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +3 -3
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts +2 -2
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +7 -7
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +8 -8
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +3 -3
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.js +2 -2
- package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +10 -12
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +3 -3
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/working-status.d.ts +25 -0
- package/dist/modes/interactive/components/working-status.d.ts.map +1 -0
- package/dist/modes/interactive/components/working-status.js +28 -0
- package/dist/modes/interactive/components/working-status.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +8 -7
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +8 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +5 -5
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/development.md +2 -2
- package/docs/extensions.md +7 -7
- package/docs/packages.md +11 -8
- package/docs/quickstart.md +2 -2
- package/docs/rpc.md +1 -1
- package/docs/sdk.md +14 -11
- package/docs/session-format.md +1 -1
- package/docs/sessions.md +10 -10
- package/docs/settings.md +1 -1
- package/docs/terminal-setup.md +9 -9
- package/docs/tmux.md +10 -10
- package/docs/tui.md +2 -2
- package/docs/usage.md +9 -9
- package/package.json +6 -1
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Visual contract (DESIGN.md):
|
|
5
5
|
* - No manual ASCII frame. `pi.ui.custom({ overlay: true })` provides the
|
|
6
|
-
* popup chrome; this renderer
|
|
7
|
-
*
|
|
6
|
+
* popup chrome; this renderer leaves one unpainted row above and below
|
|
7
|
+
* the panel, then paints content on the canvas (`bg`) with full-width
|
|
8
|
+
* chrome rows for the header (top) and hints (bottom).
|
|
8
9
|
* - Section labels use the `▎ LABEL` pattern: mauve glyph + `textMuted`
|
|
9
10
|
* bold caps.
|
|
10
11
|
* - Hints follow `<key> <label>` separated by ` · ` in `dim`, active key
|
|
@@ -16,6 +17,7 @@
|
|
|
16
17
|
* - DESIGN.md §4 (Elevation), §5 (Components)
|
|
17
18
|
*/
|
|
18
19
|
import type { Component } from "@earendil-works/pi-tui";
|
|
20
|
+
import { sliceByColumn } from "@earendil-works/pi-tui/dist/utils.js";
|
|
19
21
|
import {
|
|
20
22
|
matchesKey,
|
|
21
23
|
truncateToWidth,
|
|
@@ -118,7 +120,7 @@ const HINT_KEYS: Array<{ key: string; label: string }> = [
|
|
|
118
120
|
{ key: "↵", label: "attach" },
|
|
119
121
|
{ key: "/", label: "stages" },
|
|
120
122
|
{ key: "ctrl+d", label: "detach" },
|
|
121
|
-
{ key: "q", label: "
|
|
123
|
+
{ key: "q", label: "kill" },
|
|
122
124
|
];
|
|
123
125
|
|
|
124
126
|
/**
|
|
@@ -140,6 +142,7 @@ const MODE_PILL_LABEL = "GRAPH";
|
|
|
140
142
|
* same number of lines per frame regardless of game state.
|
|
141
143
|
*/
|
|
142
144
|
const OVERLAY_LINE_COUNT = 32;
|
|
145
|
+
const OVERLAY_VERTICAL_MARGIN_ROWS = 1;
|
|
143
146
|
|
|
144
147
|
/**
|
|
145
148
|
* Animation tick period. Overlay re-renders fire on this cadence so
|
|
@@ -228,6 +231,7 @@ export class GraphView implements Component {
|
|
|
228
231
|
this._intervalId = setInterval(() => {
|
|
229
232
|
this.requestRender?.();
|
|
230
233
|
}, ANIMATION_TICK_MS);
|
|
234
|
+
(this._intervalId as { unref?: () => void }).unref?.();
|
|
231
235
|
}
|
|
232
236
|
}
|
|
233
237
|
|
|
@@ -341,8 +345,10 @@ export class GraphView implements Component {
|
|
|
341
345
|
* Number of rows the overlay frame must paint. Pi-tui anchors the
|
|
342
346
|
* overlay vertically by counting rendered lines, so to truly fill the
|
|
343
347
|
* terminal under `maxHeight: "100%"` the component must emit
|
|
344
|
-
* approximately `terminal.rows` lines.
|
|
345
|
-
*
|
|
348
|
+
* approximately `terminal.rows` lines. When a host reports fewer
|
|
349
|
+
* than the legacy 32 rows, honour that shorter viewport where the
|
|
350
|
+
* graph chrome can still fit so the bottom status controls are not
|
|
351
|
+
* clipped by pi-tui's overlay max-height slicing. A host that doesn't
|
|
346
352
|
* surface terminal dimensions keeps the previous stable rectangle.
|
|
347
353
|
*/
|
|
348
354
|
private _overlayLineCount(): number {
|
|
@@ -350,9 +356,10 @@ export class GraphView implements Component {
|
|
|
350
356
|
if (typeof reported !== "number" || !Number.isFinite(reported)) {
|
|
351
357
|
return OVERLAY_LINE_COUNT;
|
|
352
358
|
}
|
|
353
|
-
// Header (3) + statusline (3) is the absolute
|
|
354
|
-
//
|
|
355
|
-
|
|
359
|
+
// Header (3) + one body row + statusline (3) is the absolute
|
|
360
|
+
// minimum useful frame. Margins are dropped automatically at this
|
|
361
|
+
// size by `_overlayVerticalMarginRows`.
|
|
362
|
+
return Math.max(7, Math.floor(reported));
|
|
356
363
|
}
|
|
357
364
|
|
|
358
365
|
/** Rows available for the graph body (between header and statusline). */
|
|
@@ -360,6 +367,36 @@ export class GraphView implements Component {
|
|
|
360
367
|
return Math.max(1, lineCount - 3 /* header */ - 3 /* statusline */);
|
|
361
368
|
}
|
|
362
369
|
|
|
370
|
+
private _overlayVerticalMarginRows(lineCount = this._overlayLineCount()): number {
|
|
371
|
+
return lineCount >= 9 ? OVERLAY_VERTICAL_MARGIN_ROWS : 0;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private _overlayPanelLineCount(): number {
|
|
375
|
+
const lineCount = this._overlayLineCount();
|
|
376
|
+
const margins = this._overlayVerticalMarginRows(lineCount) * 2;
|
|
377
|
+
return Math.max(7, lineCount - margins);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private _marginRow(width: number): string {
|
|
381
|
+
return " ".repeat(width);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
private _withVerticalMargins(panelLines: string[], width: number): string[] {
|
|
385
|
+
const expected = this._overlayLineCount();
|
|
386
|
+
const marginRows = this._overlayVerticalMarginRows(expected);
|
|
387
|
+
const panelTarget = this._overlayPanelLineCount();
|
|
388
|
+
const body = panelLines.slice(0, panelTarget);
|
|
389
|
+
while (body.length < panelTarget) body.push(this._blankRow(width));
|
|
390
|
+
const margins = Array.from({ length: marginRows }, () => this._marginRow(width));
|
|
391
|
+
const lines = [...margins, ...body, ...margins];
|
|
392
|
+
if (lines.length > expected) return lines.slice(0, expected);
|
|
393
|
+
while (lines.length < expected) {
|
|
394
|
+
const insertAt = marginRows > 0 ? Math.max(0, lines.length - marginRows) : lines.length;
|
|
395
|
+
lines.splice(insertAt, 0, this._blankRow(width));
|
|
396
|
+
}
|
|
397
|
+
return lines;
|
|
398
|
+
}
|
|
399
|
+
|
|
363
400
|
private _renderOverlay(width: number): string[] {
|
|
364
401
|
const frameWidth = Math.max(40, width);
|
|
365
402
|
const lines: string[] = [];
|
|
@@ -377,14 +414,12 @@ export class GraphView implements Component {
|
|
|
377
414
|
// 2. Graph occupies the full body. No section labels, no focused-
|
|
378
415
|
// stage panel — status colour on each card carries that signal.
|
|
379
416
|
const graphLines = this._renderGraph(frameWidth);
|
|
380
|
-
const bodyTarget = this._overlayBodyRows(this.
|
|
417
|
+
const bodyTarget = this._overlayBodyRows(this._overlayPanelLineCount());
|
|
381
418
|
const visibleGraph = this._visibleGraphLines(
|
|
382
419
|
graphLines,
|
|
383
420
|
frameWidth,
|
|
384
421
|
bodyTarget,
|
|
385
422
|
);
|
|
386
|
-
// Vertically centre short graphs; tall graphs are clipped to the
|
|
387
|
-
// scroll window managed by keyboard focus and mouse wheel events.
|
|
388
423
|
for (let i = 0; i < visibleGraph.topPad; i++)
|
|
389
424
|
lines.push(this._blankRow(frameWidth));
|
|
390
425
|
for (const line of visibleGraph.lines) {
|
|
@@ -396,17 +431,23 @@ export class GraphView implements Component {
|
|
|
396
431
|
|
|
397
432
|
// 3. Switcher overlay — floats over the body when open.
|
|
398
433
|
if (this.switcherOpen) {
|
|
399
|
-
const
|
|
434
|
+
const bodyStart = 3;
|
|
435
|
+
const bodyEnd = 3 + bodyTarget;
|
|
436
|
+
for (let row = bodyStart; row < bodyEnd; row++) {
|
|
437
|
+
lines[row] = this._blankRow(frameWidth);
|
|
438
|
+
}
|
|
439
|
+
const switcherWidth = Math.min(60, Math.max(40, frameWidth - 8));
|
|
400
440
|
const switcherLines = renderSwitcher(run.stages, this.switcherState, {
|
|
401
441
|
width: switcherWidth,
|
|
402
442
|
theme: this.graphTheme,
|
|
403
443
|
});
|
|
404
|
-
const insertAt =
|
|
444
|
+
const insertAt = Math.max(bodyStart, bodyStart + Math.min(2, Math.floor((bodyTarget - switcherLines.length) / 3)));
|
|
445
|
+
const switcherLeft = Math.max(2, Math.floor((frameWidth - switcherWidth) / 2));
|
|
405
446
|
for (let i = 0; i < switcherLines.length; i++) {
|
|
406
447
|
const lineIdx = insertAt + i;
|
|
407
|
-
|
|
448
|
+
if (lineIdx >= bodyEnd) break;
|
|
408
449
|
const base = lines[lineIdx] ?? this._blankRow(frameWidth);
|
|
409
|
-
const merged = this.
|
|
450
|
+
const merged = this._overlayCard(base, switcherLines[i]!, switcherLeft, frameWidth);
|
|
410
451
|
if (lineIdx < lines.length) lines[lineIdx] = merged;
|
|
411
452
|
else lines.push(merged);
|
|
412
453
|
}
|
|
@@ -453,7 +494,7 @@ export class GraphView implements Component {
|
|
|
453
494
|
// 5. Three-row statusline pinned to the bottom.
|
|
454
495
|
lines.push(...this._renderStatusline(frameWidth));
|
|
455
496
|
|
|
456
|
-
return lines;
|
|
497
|
+
return this._withVerticalMargins(lines, frameWidth);
|
|
457
498
|
}
|
|
458
499
|
|
|
459
500
|
private _renderEmptyState(width: number): string[] {
|
|
@@ -477,7 +518,7 @@ export class GraphView implements Component {
|
|
|
477
518
|
`${chromeBg} ${RESET}${mid}${chromeBg}${idleLabel}${filler}${" ".repeat(2)}${RESET}`,
|
|
478
519
|
`${chromeBg} ${RESET}${bot}${chromeBg}${" ".repeat(6 + fillerVisible)}${" ".repeat(2)}${RESET}`,
|
|
479
520
|
];
|
|
480
|
-
const bodyTarget = this._overlayBodyRows(this.
|
|
521
|
+
const bodyTarget = this._overlayBodyRows(this._overlayPanelLineCount());
|
|
481
522
|
const body: string[] = [
|
|
482
523
|
this._canvasRow(` ${muted}No active workflow run.${RESET}`, width),
|
|
483
524
|
this._canvasRow(
|
|
@@ -490,7 +531,7 @@ export class GraphView implements Component {
|
|
|
490
531
|
for (const l of body) lines.push(l);
|
|
491
532
|
while (lines.length < 3 + bodyTarget) lines.push(this._blankRow(width));
|
|
492
533
|
lines.push(...this._renderStatusline(width));
|
|
493
|
-
return lines;
|
|
534
|
+
return this._withVerticalMargins(lines, width);
|
|
494
535
|
}
|
|
495
536
|
|
|
496
537
|
private _renderGraph(width: number): string[] {
|
|
@@ -600,9 +641,8 @@ export class GraphView implements Component {
|
|
|
600
641
|
const leftPad = `${bg}${" ".repeat(leftMargin)}${RESET}`;
|
|
601
642
|
return composed.map((line) => {
|
|
602
643
|
const full = this._padCanvas(line, fullCanvasWidth);
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
cells,
|
|
644
|
+
const sliced = this._sliceColumns(
|
|
645
|
+
full,
|
|
606
646
|
this.graphScrollColOffset,
|
|
607
647
|
this.graphScrollColOffset + viewportWidth,
|
|
608
648
|
);
|
|
@@ -620,7 +660,7 @@ export class GraphView implements Component {
|
|
|
620
660
|
this.pendingEnsureFocusedVisible = false;
|
|
621
661
|
return {
|
|
622
662
|
lines: graphLines,
|
|
623
|
-
topPad: Math.max(0, Math.floor((bodyRows - graphLines.length) / 2)),
|
|
663
|
+
topPad: Math.min(3, Math.max(0, Math.floor((bodyRows - graphLines.length) / 2))),
|
|
624
664
|
};
|
|
625
665
|
}
|
|
626
666
|
|
|
@@ -696,9 +736,9 @@ export class GraphView implements Component {
|
|
|
696
736
|
* Plot a parent → child edge for the vertical orientation. The edge
|
|
697
737
|
* exits from the parent's bottom-centre, runs through a horizontal
|
|
698
738
|
* spine half-way down the gap, and re-enters from the child's
|
|
699
|
-
* top-centre.
|
|
700
|
-
*
|
|
701
|
-
*
|
|
739
|
+
* top-centre. Cells are merged by direction set so fan-out, fan-in,
|
|
740
|
+
* and crossings produce stable orthogonal junctions instead of
|
|
741
|
+
* stacked rounded corners.
|
|
702
742
|
*/
|
|
703
743
|
private _plotEdge(
|
|
704
744
|
canvas: GraphCanvas,
|
|
@@ -724,112 +764,34 @@ export class GraphView implements Component {
|
|
|
724
764
|
Math.min(childEntryRow, Math.floor((parentExitRow + childEntryRow) / 2)),
|
|
725
765
|
);
|
|
726
766
|
|
|
727
|
-
// Down stub from parent into the spine row
|
|
728
|
-
// a non-zero gap, otherwise vline would paint the spineRow corner
|
|
729
|
-
// cell before we get to set it.
|
|
767
|
+
// Down stub from parent into the spine row.
|
|
730
768
|
if (spineRow > parentExitRow) {
|
|
731
769
|
canvas.vline(parentCol, parentExitRow, spineRow - 1, color);
|
|
732
770
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
this._placeCornerOrTee(
|
|
736
|
-
canvas,
|
|
737
|
-
spineRow,
|
|
738
|
-
parentCol,
|
|
739
|
-
childCol > parentCol ? "╰" : "╯",
|
|
740
|
-
color,
|
|
741
|
-
);
|
|
771
|
+
this._placeJunction(canvas, spineRow, parentCol, ["u", childCol > parentCol ? "r" : "l"], color);
|
|
772
|
+
|
|
742
773
|
// Horizontal spine segment.
|
|
743
774
|
const hloCol = Math.min(parentCol, childCol) + 1;
|
|
744
775
|
const hhiCol = Math.max(parentCol, childCol) - 1;
|
|
745
776
|
if (hhiCol >= hloCol) {
|
|
746
777
|
canvas.hline(spineRow, hloCol, hhiCol, color);
|
|
747
778
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
spineRow,
|
|
752
|
-
childCol,
|
|
753
|
-
childCol > parentCol ? "╮" : "╭",
|
|
754
|
-
color,
|
|
755
|
-
);
|
|
756
|
-
// Down stub from spine into child — same zero-gap guard.
|
|
779
|
+
this._placeJunction(canvas, spineRow, childCol, [childCol > parentCol ? "l" : "r", "d"], color);
|
|
780
|
+
|
|
781
|
+
// Down stub from spine into child.
|
|
757
782
|
if (childEntryRow > spineRow) {
|
|
758
783
|
canvas.vline(childCol, spineRow + 1, childEntryRow, color);
|
|
759
784
|
}
|
|
760
785
|
}
|
|
761
786
|
|
|
762
|
-
|
|
763
|
-
* Place a corner glyph at `(row, col)`. When the target cell already
|
|
764
|
-
* holds a glyph from a previous edge, upgrade to the correct
|
|
765
|
-
* box-drawing junction so fan-out / fan-in points read as a single
|
|
766
|
-
* rule rather than two stacked corners.
|
|
767
|
-
*
|
|
768
|
-
* Junction table (existing ⊕ new → result):
|
|
769
|
-
* | ⊕ ╰ → ├ | ⊕ ╮ → ┤ | ⊕ ╯ → ┤ | ⊕ ╭ → ├
|
|
770
|
-
* ─ ⊕ ╭ → ┬ ─ ⊕ ╮ → ┬ ─ ⊕ ╰ → ┴ ─ ⊕ ╯ → ┴
|
|
771
|
-
* ╰ ⊕ ╮ → ┴ ╭ ⊕ ╯ → ┴ ╭ ⊕ ╮ → ┬ etc.
|
|
772
|
-
*
|
|
773
|
-
* The full table is small; we collapse it to: "existing-direction-set
|
|
774
|
-
* ∪ new-direction-set → best matching junction".
|
|
775
|
-
*/
|
|
776
|
-
private _placeCornerOrTee(
|
|
787
|
+
private _placeJunction(
|
|
777
788
|
canvas: GraphCanvas,
|
|
778
789
|
row: number,
|
|
779
790
|
col: number,
|
|
780
|
-
|
|
791
|
+
newDirs: Array<"u" | "d" | "l" | "r">,
|
|
781
792
|
color: string,
|
|
782
793
|
): void {
|
|
783
|
-
|
|
784
|
-
canvas as unknown as { rows: Map<number, Map<number, { ch: string }>> }
|
|
785
|
-
).rows
|
|
786
|
-
.get(row)
|
|
787
|
-
?.get(col)?.ch;
|
|
788
|
-
if (existing == null || existing === corner) {
|
|
789
|
-
canvas.setCell(row, col, corner, color);
|
|
790
|
-
return;
|
|
791
|
-
}
|
|
792
|
-
const dirs = (ch: string): Set<"u" | "d" | "l" | "r"> => {
|
|
793
|
-
switch (ch) {
|
|
794
|
-
case "│":
|
|
795
|
-
return new Set(["u", "d"]);
|
|
796
|
-
case "─":
|
|
797
|
-
return new Set(["l", "r"]);
|
|
798
|
-
case "╭":
|
|
799
|
-
return new Set(["d", "r"]);
|
|
800
|
-
case "╮":
|
|
801
|
-
return new Set(["d", "l"]);
|
|
802
|
-
case "╰":
|
|
803
|
-
return new Set(["u", "r"]);
|
|
804
|
-
case "╯":
|
|
805
|
-
return new Set(["u", "l"]);
|
|
806
|
-
case "├":
|
|
807
|
-
return new Set(["u", "d", "r"]);
|
|
808
|
-
case "┤":
|
|
809
|
-
return new Set(["u", "d", "l"]);
|
|
810
|
-
case "┬":
|
|
811
|
-
return new Set(["d", "l", "r"]);
|
|
812
|
-
case "┴":
|
|
813
|
-
return new Set(["u", "l", "r"]);
|
|
814
|
-
case "┼":
|
|
815
|
-
return new Set(["u", "d", "l", "r"]);
|
|
816
|
-
default:
|
|
817
|
-
return new Set();
|
|
818
|
-
}
|
|
819
|
-
};
|
|
820
|
-
const merged = new Set<"u" | "d" | "l" | "r">([
|
|
821
|
-
...dirs(existing),
|
|
822
|
-
...dirs(corner),
|
|
823
|
-
]);
|
|
824
|
-
const has = (...want: Array<"u" | "d" | "l" | "r">) =>
|
|
825
|
-
want.every((d) => merged.has(d));
|
|
826
|
-
let glyph = corner;
|
|
827
|
-
if (has("u", "d", "l", "r")) glyph = "┼";
|
|
828
|
-
else if (has("u", "d", "r")) glyph = "├";
|
|
829
|
-
else if (has("u", "d", "l")) glyph = "┤";
|
|
830
|
-
else if (has("d", "l", "r")) glyph = "┬";
|
|
831
|
-
else if (has("u", "l", "r")) glyph = "┴";
|
|
832
|
-
canvas.setCell(row, col, glyph, color);
|
|
794
|
+
canvas.mergeCell(row, col, newDirs, color);
|
|
833
795
|
}
|
|
834
796
|
|
|
835
797
|
/**
|
|
@@ -853,7 +815,6 @@ export class GraphView implements Component {
|
|
|
853
815
|
): string {
|
|
854
816
|
const bg = hexBg(this.graphTheme.bg);
|
|
855
817
|
const sorted = cards.slice().sort((a, b) => a.startCol - b.startCol);
|
|
856
|
-
const edgeVisible = this._splitVisible(edgeRow);
|
|
857
818
|
let cursor = 0;
|
|
858
819
|
let out = "";
|
|
859
820
|
for (const card of sorted) {
|
|
@@ -861,69 +822,27 @@ export class GraphView implements Component {
|
|
|
861
822
|
// Edge segment up to card start — prepend bg so empty cells
|
|
862
823
|
// in this stretch keep the body bg instead of falling back
|
|
863
824
|
// to the terminal default once any prior RESET fired.
|
|
864
|
-
out += `${bg}${this.
|
|
825
|
+
out += `${bg}${this._edgeSegment(edgeRow, cursor, card.startCol)}`;
|
|
865
826
|
cursor = card.startCol;
|
|
866
827
|
}
|
|
867
828
|
out += card.line;
|
|
868
829
|
cursor += card.width;
|
|
869
830
|
}
|
|
870
831
|
// Trailing edge tail — same bg re-priming.
|
|
871
|
-
out += `${bg}${this.
|
|
832
|
+
out += `${bg}${this._edgeSegment(edgeRow, cursor, Math.max(cursor, visibleWidth(edgeRow)))}`;
|
|
872
833
|
return out;
|
|
873
834
|
}
|
|
874
835
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
*/
|
|
881
|
-
private _splitVisible(line: string): Array<{ prefix: string; ch: string }> {
|
|
882
|
-
const cells: Array<{ prefix: string; ch: string }> = [];
|
|
883
|
-
let i = 0;
|
|
884
|
-
let pending = "";
|
|
885
|
-
while (i < line.length) {
|
|
886
|
-
const ch = line[i]!;
|
|
887
|
-
if (ch === "\x1b" && line[i + 1] === "[") {
|
|
888
|
-
const end = line.indexOf("m", i);
|
|
889
|
-
if (end === -1) break;
|
|
890
|
-
pending += line.slice(i, end + 1);
|
|
891
|
-
i = end + 1;
|
|
892
|
-
continue;
|
|
893
|
-
}
|
|
894
|
-
cells.push({ prefix: pending, ch });
|
|
895
|
-
pending = "";
|
|
896
|
-
i += 1;
|
|
897
|
-
}
|
|
898
|
-
return cells;
|
|
836
|
+
private _edgeSegment(line: string, fromCol: number, toCol: number): string {
|
|
837
|
+
if (fromCol >= toCol) return "";
|
|
838
|
+
const segment = this._sliceColumns(line, fromCol, toCol);
|
|
839
|
+
const visible = visibleWidth(segment);
|
|
840
|
+
return `${segment}${" ".repeat(Math.max(0, toCol - fromCol - visible))}`;
|
|
899
841
|
}
|
|
900
842
|
|
|
901
|
-
private
|
|
902
|
-
cells: Array<{ prefix: string; ch: string }>,
|
|
903
|
-
fromCol: number,
|
|
904
|
-
toCol: number,
|
|
905
|
-
): string {
|
|
843
|
+
private _sliceColumns(line: string, fromCol: number, toCol: number): string {
|
|
906
844
|
if (fromCol >= toCol) return "";
|
|
907
|
-
|
|
908
|
-
let lastPrefix = "";
|
|
909
|
-
for (let c = fromCol; c < toCol; c++) {
|
|
910
|
-
const cell = cells[c];
|
|
911
|
-
if (cell) {
|
|
912
|
-
if (cell.prefix && cell.prefix !== lastPrefix) {
|
|
913
|
-
out += cell.prefix;
|
|
914
|
-
lastPrefix = cell.prefix;
|
|
915
|
-
}
|
|
916
|
-
out += cell.ch;
|
|
917
|
-
} else {
|
|
918
|
-
if (lastPrefix !== "") {
|
|
919
|
-
out += RESET;
|
|
920
|
-
lastPrefix = "";
|
|
921
|
-
}
|
|
922
|
-
out += " ";
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
if (lastPrefix !== "") out += RESET;
|
|
926
|
-
return out;
|
|
845
|
+
return sliceByColumn(line, fromCol, toCol - fromCol, true);
|
|
927
846
|
}
|
|
928
847
|
|
|
929
848
|
// -------------------------------------------------------------------------
|
|
@@ -955,21 +874,16 @@ export class GraphView implements Component {
|
|
|
955
874
|
({ key, label }) =>
|
|
956
875
|
`${text}${BOLD}${key}${RESET}${chromeBg} ${muted}${label}${RESET}${chromeBg}`,
|
|
957
876
|
);
|
|
958
|
-
const
|
|
959
|
-
const hintsVisibleLen =
|
|
960
|
-
HINT_KEYS.reduce((sum, h) => sum + h.key.length + 1 + h.label.length, 0) +
|
|
961
|
-
(HINT_KEYS.length - 1) * 5; // " · "
|
|
877
|
+
const hintsStyledRaw = segments.join(sep);
|
|
962
878
|
|
|
963
879
|
const leftEdgePad = 1;
|
|
964
880
|
const rightEdgePad = 2;
|
|
965
|
-
const
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
);
|
|
881
|
+
const hintsBudget = Math.max(0, width - leftEdgePad - pillW - rightEdgePad);
|
|
882
|
+
const hintsStyled = truncateToWidth(hintsStyledRaw, hintsBudget, "");
|
|
883
|
+
const hintsVisibleLen = visibleWidth(hintsStyled);
|
|
884
|
+
const fillerVisible = Math.max(0, hintsBudget - hintsVisibleLen);
|
|
969
885
|
const filler = " ".repeat(fillerVisible);
|
|
970
|
-
const blankAcross = " ".repeat(
|
|
971
|
-
fillerVisible + hintsVisibleLen + rightEdgePad,
|
|
972
|
-
);
|
|
886
|
+
const blankAcross = " ".repeat(Math.max(0, width - leftEdgePad - pillW));
|
|
973
887
|
|
|
974
888
|
return [
|
|
975
889
|
`${chromeBg} ${RESET}${top}${chromeBg}${blankAcross}${RESET}`,
|
|
@@ -1033,19 +947,20 @@ export class GraphView implements Component {
|
|
|
1033
947
|
leftPad: number,
|
|
1034
948
|
totalWidth: number,
|
|
1035
949
|
): string {
|
|
1036
|
-
const baseCells = this._splitVisible(base);
|
|
1037
950
|
const overlayWidth = Math.min(
|
|
1038
951
|
Math.max(0, totalWidth - leftPad),
|
|
1039
952
|
visibleWidth(overlay),
|
|
1040
953
|
);
|
|
1041
|
-
const left = this.
|
|
954
|
+
const left = this._sliceColumns(base, 0, leftPad);
|
|
1042
955
|
const panel = truncateToWidth(overlay, overlayWidth, "", true);
|
|
1043
|
-
const right = this.
|
|
1044
|
-
|
|
956
|
+
const right = this._sliceColumns(
|
|
957
|
+
base,
|
|
1045
958
|
leftPad + overlayWidth,
|
|
1046
959
|
totalWidth,
|
|
1047
960
|
);
|
|
1048
|
-
|
|
961
|
+
const merged = `${left}${panel}${right}`;
|
|
962
|
+
const pad = Math.max(0, totalWidth - visibleWidth(merged));
|
|
963
|
+
return `${merged}${hexBg(this.graphTheme.bg)}${" ".repeat(pad)}${RESET}`;
|
|
1049
964
|
}
|
|
1050
965
|
|
|
1051
966
|
private _duration(stage: StageSnapshot): string {
|
|
@@ -1187,7 +1102,7 @@ export class GraphView implements Component {
|
|
|
1187
1102
|
return true;
|
|
1188
1103
|
}
|
|
1189
1104
|
// `q` kills the active run (no confirm). `h` hides the pane via
|
|
1190
|
-
// the overlay's setHidden() flag (not unmount); Escape closes.
|
|
1105
|
+
// the overlay's setHidden() flag (not unmount); Escape/Ctrl+C closes.
|
|
1191
1106
|
if (matchesKey(data, "q")) {
|
|
1192
1107
|
const run = this._getCurrentRun();
|
|
1193
1108
|
if (run && run.endedAt === undefined && this.onKill) {
|
|
@@ -1200,7 +1115,7 @@ export class GraphView implements Component {
|
|
|
1200
1115
|
this.onHide();
|
|
1201
1116
|
return true;
|
|
1202
1117
|
}
|
|
1203
|
-
if (matchesKey(data, "escape") || data === "\x1b") {
|
|
1118
|
+
if (matchesKey(data, "escape") || data === "\x1b" || data === "\x03") {
|
|
1204
1119
|
this.onClose?.();
|
|
1205
1120
|
return true;
|
|
1206
1121
|
}
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
import type { RunSnapshot } from "../shared/store-types.js";
|
|
23
23
|
import type { GraphTheme } from "./graph-theme.js";
|
|
24
24
|
import { hexToAnsi, hexBg, RESET, BOLD } from "./color-utils.js";
|
|
25
|
+
import { truncateToWidth, visibleWidth } from "./text-helpers.js";
|
|
25
26
|
|
|
26
27
|
export interface HeaderOpts {
|
|
27
28
|
width: number;
|
|
@@ -72,11 +73,12 @@ export function renderOutlinePill(
|
|
|
72
73
|
): { top: string; mid: string; bot: string; visibleWidth: number } {
|
|
73
74
|
const bc = hexToAnsi(borderHex);
|
|
74
75
|
const padded = ` ${label} `;
|
|
75
|
-
const
|
|
76
|
+
const paddedWidth = visibleWidth(padded);
|
|
77
|
+
const inner = "─".repeat(paddedWidth);
|
|
76
78
|
const top = `${chromeBg}${bc}╭${inner}╮${RESET}`;
|
|
77
79
|
const mid = `${chromeBg}${bc}│${BOLD}${padded}${RESET}${chromeBg}${bc}│${RESET}`;
|
|
78
80
|
const bot = `${chromeBg}${bc}╰${inner}╯${RESET}`;
|
|
79
|
-
return { top, mid, bot, visibleWidth:
|
|
81
|
+
return { top, mid, bot, visibleWidth: paddedWidth + 2 };
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
/**
|
|
@@ -96,42 +98,56 @@ export function renderBandHeader(opts: BandHeaderOpts): string[] {
|
|
|
96
98
|
const { top: pillTop, mid: pillMid, bot: pillBot, visibleWidth: pillW } =
|
|
97
99
|
renderOutlinePill(label, accentHex, chromeBg);
|
|
98
100
|
|
|
99
|
-
// Subtitle slot — quieter than the pill. Two-space gutter on each
|
|
100
|
-
// side keeps the pill from kissing the subtitle.
|
|
101
|
-
const subtitleVisible = subtitle.length > 0 ? ` ${subtitle}` : "";
|
|
102
|
-
const subtitleStyled =
|
|
103
|
-
subtitle.length > 0
|
|
104
|
-
? `${chromeBg} ${muted}${subtitle}${RESET}${chromeBg}`
|
|
105
|
-
: `${chromeBg}`;
|
|
106
|
-
|
|
107
101
|
// Right-side count badges. Status colour per badge, 2ch gap between.
|
|
108
102
|
const rightVisible = badges.map((b) => b.text).join(" ");
|
|
103
|
+
const rightW = visibleWidth(rightVisible);
|
|
109
104
|
const rightStyled = badges
|
|
110
105
|
.map((b) => `${hexToAnsi(b.fg)}${b.text}${RESET}${chromeBg}`)
|
|
111
106
|
.join(`${chromeBg} `);
|
|
112
107
|
|
|
113
108
|
const leftEdgePad = 1;
|
|
114
109
|
const rightEdgePad = 2;
|
|
110
|
+
|
|
111
|
+
// Subtitle slot — quieter than the pill. Two-space gutter keeps the
|
|
112
|
+
// pill from kissing the subtitle. Budget with terminal cell widths so
|
|
113
|
+
// long CJK/emoji/combining run names cannot push the chrome past `width`.
|
|
114
|
+
const subtitleBudget = Math.max(
|
|
115
|
+
0,
|
|
116
|
+
width - leftEdgePad - pillW - rightW - rightEdgePad,
|
|
117
|
+
);
|
|
118
|
+
const subtitleTextBudget = Math.max(0, subtitleBudget - 2);
|
|
119
|
+
const subtitleText =
|
|
120
|
+
subtitle.length > 0 && subtitleTextBudget > 0
|
|
121
|
+
? truncateToWidth(subtitle, subtitleTextBudget, "…")
|
|
122
|
+
: "";
|
|
123
|
+
const subtitleVisible = subtitleText.length > 0 ? ` ${subtitleText}` : "";
|
|
124
|
+
const subtitleW = visibleWidth(subtitleVisible);
|
|
125
|
+
const subtitleStyled =
|
|
126
|
+
subtitleText.length > 0
|
|
127
|
+
? `${chromeBg} ${muted}${subtitleText}${RESET}${chromeBg}`
|
|
128
|
+
: `${chromeBg}`;
|
|
129
|
+
|
|
115
130
|
const fillerTop = Math.max(
|
|
116
131
|
0,
|
|
117
|
-
width - leftEdgePad - pillW -
|
|
132
|
+
width - leftEdgePad - pillW - subtitleW - rightEdgePad,
|
|
118
133
|
);
|
|
119
134
|
const fillerMid = Math.max(
|
|
120
135
|
0,
|
|
121
|
-
width -
|
|
122
|
-
leftEdgePad -
|
|
123
|
-
pillW -
|
|
124
|
-
subtitleVisible.length -
|
|
125
|
-
rightVisible.length -
|
|
126
|
-
rightEdgePad,
|
|
136
|
+
width - leftEdgePad - pillW - subtitleW - rightW - rightEdgePad,
|
|
127
137
|
);
|
|
128
138
|
|
|
129
|
-
const
|
|
139
|
+
const fitChromeLine = (line: string): string => {
|
|
140
|
+
const fitted = truncateToWidth(line, width, "");
|
|
141
|
+
const pad = Math.max(0, width - visibleWidth(fitted));
|
|
142
|
+
return `${fitted}${chromeBg}${" ".repeat(pad)}${RESET}`;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const subtitleBlank = " ".repeat(subtitleW);
|
|
130
146
|
const top = `${chromeBg} ${RESET}${pillTop}${chromeBg}${subtitleBlank}${" ".repeat(fillerTop)}${" ".repeat(rightEdgePad)}${RESET}`;
|
|
131
147
|
const mid = `${chromeBg} ${RESET}${pillMid}${subtitleStyled}${" ".repeat(fillerMid)}${rightStyled}${chromeBg}${" ".repeat(rightEdgePad)}${RESET}`;
|
|
132
148
|
const bot = `${chromeBg} ${RESET}${pillBot}${chromeBg}${subtitleBlank}${" ".repeat(fillerTop)}${" ".repeat(rightEdgePad)}${RESET}`;
|
|
133
149
|
|
|
134
|
-
return [top, mid, bot];
|
|
150
|
+
return [fitChromeLine(top), fitChromeLine(mid), fitChromeLine(bot)];
|
|
135
151
|
}
|
|
136
152
|
|
|
137
153
|
/**
|
|
@@ -157,7 +173,7 @@ export function renderHeader(run: RunSnapshot, opts: HeaderOpts): string[] {
|
|
|
157
173
|
if (counts.completed > 0) badges.push({ text: `✓ ${counts.completed}`, fg: theme.success });
|
|
158
174
|
if (counts.running > 0) badges.push({ text: `● ${counts.running}`, fg: theme.warning });
|
|
159
175
|
if (counts.awaiting_input > 0) badges.push({ text: `↵ ${counts.awaiting_input}`, fg: theme.info });
|
|
160
|
-
if (counts.paused > 0) badges.push({ text:
|
|
176
|
+
if (counts.paused > 0) badges.push({ text: `❚❚ ${counts.paused}`, fg: theme.warning });
|
|
161
177
|
if (counts.pending > 0) badges.push({ text: `○ ${counts.pending}`, fg: theme.dim });
|
|
162
178
|
if (counts.failed > 0) badges.push({ text: `✗ ${counts.failed}`, fg: theme.error });
|
|
163
179
|
|