@pellux/goodvibes-tui 2.0.21 → 2.1.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 +58 -0
- package/README.md +16 -16
- package/docs/README.md +4 -4
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +16 -6
- package/src/cli/management.ts +5 -2
- package/src/cli/tui-startup.ts +34 -39
- package/src/core/agent-view-render.ts +157 -0
- package/src/core/approval-alert.ts +49 -11
- package/src/core/bookmark-navigation.ts +7 -1
- package/src/core/budget-breach-notifier.ts +27 -8
- package/src/core/compaction-receipt.ts +1 -1
- package/src/core/context-usage.ts +1 -1
- package/src/core/conversation-compaction-render.ts +3 -3
- package/src/core/conversation-display-only.ts +86 -0
- package/src/core/conversation-event-navigation.ts +27 -0
- package/src/core/conversation-line-cache.ts +216 -447
- package/src/core/conversation-render-context.ts +20 -27
- package/src/core/conversation-rendering.ts +157 -575
- package/src/core/conversation-splash-state.ts +6 -1
- package/src/core/conversation-types.ts +35 -0
- package/src/core/conversation-usage.ts +12 -0
- package/src/core/conversation.ts +130 -131
- package/src/core/long-task-notifier.ts +100 -31
- package/src/core/notices.ts +75 -0
- package/src/core/pairing-tailscale-gateway.ts +1 -1
- package/src/core/process-output-log.ts +143 -0
- package/src/core/rewind-receipt.ts +1 -1
- package/src/core/session-focus.ts +190 -0
- package/src/core/session-resume-core.ts +23 -81
- package/src/core/stream-event-wiring.ts +31 -5
- package/src/core/system-message-noise.ts +2 -2
- package/src/core/system-message-router.ts +16 -18
- package/src/core/turn-event-wiring.ts +272 -33
- package/src/core/work-tree-focus.ts +290 -0
- package/src/core/work-tree-fold-store.ts +143 -0
- package/src/core/work-tree-model.ts +799 -0
- package/src/core/work-tree-render.ts +384 -0
- package/src/core/work-tree-sources.ts +184 -0
- package/src/core/work-tree-wiring.ts +176 -0
- package/src/core/workstream-notification.ts +36 -6
- package/src/core/wrfc-notice-titles.ts +37 -0
- package/src/input/agents-modal-text.ts +119 -0
- package/src/input/agents-modal.ts +699 -0
- package/src/input/bookmark-modal.ts +33 -9
- package/src/input/changes-git.ts +251 -0
- package/src/input/changes-modal.ts +629 -0
- package/src/input/changes-model.ts +125 -0
- package/src/input/command-palette-types.ts +41 -0
- package/src/input/command-palette.ts +272 -0
- package/src/input/command-registry.ts +77 -35
- package/src/input/commands/acp-runtime.ts +19 -9
- package/src/input/commands/channel-runtime.ts +2 -2
- package/src/input/commands/ci-runtime.ts +1 -1
- package/src/input/commands/codebase-runtime.ts +1 -1
- package/src/input/commands/config.ts +7 -1
- package/src/input/commands/context-window.ts +25 -19
- package/src/input/commands/control-room-runtime.ts +18 -18
- package/src/input/commands/conversation-runtime.ts +0 -1
- package/src/input/commands/cost-runtime.ts +29 -20
- package/src/input/commands/diff-runtime.ts +35 -181
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/experience-runtime.ts +3 -7
- package/src/input/commands/fresh-conversation.ts +86 -0
- package/src/input/commands/git-runtime.ts +5 -22
- package/src/input/commands/graph-runtime.ts +1 -1
- package/src/input/commands/guidance-runtime.ts +1 -1
- package/src/input/commands/health-runtime.ts +8 -9
- package/src/input/commands/hooks-runtime.ts +2 -2
- package/src/input/commands/hosted-runtime.ts +3 -3
- package/src/input/commands/incident-runtime.ts +3 -3
- package/src/input/commands/intelligence-runtime.ts +6 -9
- package/src/input/commands/knowledge.ts +2 -2
- package/src/input/commands/legacy-panel-command.ts +57 -0
- package/src/input/commands/local-auth-runtime.ts +8 -9
- package/src/input/commands/local-provider-runtime.ts +7 -4
- package/src/input/commands/local-runtime.ts +12 -15
- package/src/input/commands/local-setup-review.ts +1 -1
- package/src/input/commands/local-setup.ts +13 -19
- package/src/input/commands/memory.ts +1 -1
- package/src/input/commands/notify-runtime.ts +9 -0
- package/src/input/commands/operator-runtime.ts +10 -12
- package/src/input/commands/permissions-provenance.ts +3 -3
- package/src/input/commands/planning-runtime.ts +7 -7
- package/src/input/commands/platform-sandbox-runtime.ts +2 -2
- package/src/input/commands/platform-services-runtime.ts +1 -1
- package/src/input/commands/policy-dispatch.ts +2 -2
- package/src/input/commands/policy.ts +3 -3
- package/src/input/commands/provider.ts +1 -1
- package/src/input/commands/remote-runtime.ts +1 -1
- package/src/input/commands/review-runtime.ts +39 -94
- package/src/input/commands/rewind-runtime.ts +81 -108
- package/src/input/commands/runtime-services.ts +15 -17
- package/src/input/commands/services-runtime.ts +2 -2
- package/src/input/commands/session-content.ts +21 -5
- package/src/input/commands/session-workflow.ts +13 -38
- package/src/input/commands/settings-sync-runtime.ts +2 -2
- package/src/input/commands/shell-core.ts +19 -4
- package/src/input/commands/skills-runtime.ts +2 -2
- package/src/input/commands/status-runtime.ts +13 -0
- package/src/input/commands/subscription-runtime.ts +1 -1
- package/src/input/commands/tasks-runtime.ts +2 -8
- package/src/input/commands/work-plan-runtime.ts +4 -4
- package/src/input/commands/workstream-attempts.ts +24 -47
- package/src/input/commands/workstream-runtime.ts +2 -2
- package/src/input/commands/worktree-runtime.ts +3 -3
- package/src/input/commands.ts +3 -1
- package/src/input/composer-editor.ts +44 -0
- package/src/input/concealed-input.ts +1 -1
- package/src/input/config-modal-types.ts +20 -8
- package/src/input/config-modal.ts +106 -105
- package/src/input/confirm-dialog-types.ts +28 -0
- package/src/input/confirm-dialog.ts +70 -0
- package/src/input/delete-key-policy.ts +3 -3
- package/src/input/feed-context-factory.ts +15 -16
- package/src/input/handler-command-route.ts +7 -42
- package/src/input/handler-content-actions.ts +16 -15
- package/src/input/handler-feed-routes.ts +26 -211
- package/src/input/handler-feed.ts +68 -95
- package/src/input/handler-interactions.ts +5 -3
- package/src/input/handler-modal-routes.ts +215 -216
- package/src/input/handler-modal-stack.ts +81 -11
- package/src/input/handler-modal-token-routes.ts +4 -0
- package/src/input/handler-onboarding.ts +0 -1
- package/src/input/handler-picker-routes.ts +65 -107
- package/src/input/handler-session-view-route.ts +149 -0
- package/src/input/handler-shortcuts.ts +16 -154
- package/src/input/handler-types.ts +4 -2
- package/src/input/handler-ui-state.ts +45 -15
- package/src/input/handler-work-tree-route.ts +123 -0
- package/src/input/handler.ts +25 -70
- package/src/input/keybindings.ts +16 -71
- package/src/input/masked-entry-modal.ts +117 -0
- package/src/input/mcp-workspace.ts +48 -17
- package/src/input/model-picker-filter.ts +8 -0
- package/src/input/model-picker-open.ts +99 -0
- package/src/input/model-picker-types.ts +1 -1
- package/src/input/model-picker.ts +92 -9
- package/src/input/notifications-modal.ts +123 -0
- package/src/input/overlay-filter.ts +26 -0
- package/src/input/profile-picker-modal.ts +37 -8
- package/src/input/search.ts +3 -3
- package/src/input/selection-modal-queue.ts +2 -0
- package/src/input/selection-modal.ts +28 -16
- package/src/input/session-picker-modal.ts +33 -8
- package/src/input/settings-modal-activation.ts +7 -2
- package/src/input/settings-modal-data.ts +11 -6
- package/src/input/settings-modal-types.ts +1 -1
- package/src/input/settings-modal.ts +20 -20
- package/src/input/submission-intent.ts +1 -1
- package/src/input/submission-router.ts +4 -9
- package/src/input/surface-modal-host.ts +110 -0
- package/src/input/theme-settings-actions.ts +82 -0
- package/src/input/usage-modal.ts +183 -0
- package/src/input/views.ts +97 -0
- package/src/main.ts +175 -165
- package/src/permissions/hunk-selection.ts +1 -1
- package/src/permissions/prompt-card.ts +511 -0
- package/src/permissions/prompt.ts +59 -622
- package/src/renderer/agents-modal.ts +310 -0
- package/src/renderer/autocomplete-overlay.ts +42 -220
- package/src/renderer/back-to-bottom.ts +80 -0
- package/src/renderer/block-actions-overlay.ts +30 -48
- package/src/renderer/bookmark-modal.ts +48 -79
- package/src/renderer/changes-modal.ts +325 -0
- package/src/renderer/code-block.ts +184 -106
- package/src/renderer/command-palette.ts +93 -0
- package/src/renderer/composer-fragments.ts +4 -4
- package/src/renderer/composer.ts +146 -0
- package/src/renderer/compositor.ts +26 -160
- package/src/renderer/config-modal.ts +185 -81
- package/src/renderer/confirm-dialog.ts +50 -0
- package/src/renderer/context-inspector.ts +116 -154
- package/src/renderer/context-status-hint.ts +4 -4
- package/src/renderer/conversation-layout.ts +20 -0
- package/src/renderer/conversation-overlays.ts +94 -84
- package/src/renderer/conversation-surface.ts +19 -38
- package/src/renderer/diff-tint.ts +98 -0
- package/src/renderer/file-picker-overlay.ts +63 -90
- package/src/renderer/footer-targets.ts +38 -0
- package/src/renderer/git-status.ts +3 -4
- package/src/renderer/header-line.ts +140 -117
- package/src/renderer/help-overlay.ts +133 -219
- package/src/renderer/history-search-overlay.ts +15 -12
- package/src/renderer/lane-graph/bead.ts +800 -0
- package/src/renderer/lane-graph/glyphs.ts +134 -0
- package/src/renderer/lane-graph/layout.ts +248 -0
- package/src/renderer/lane-graph/paint.ts +537 -0
- package/src/renderer/lane-graph/semantic-memo.ts +77 -0
- package/src/renderer/layout-engine.ts +2 -57
- package/src/renderer/layout.ts +7 -13
- package/src/renderer/markdown-inline.ts +2 -1
- package/src/renderer/markdown-table.ts +17 -12
- package/src/renderer/markdown.ts +92 -34
- package/src/renderer/masked-entry-modal.ts +78 -0
- package/src/renderer/mcp-workspace.ts +166 -203
- package/src/renderer/model-workspace.ts +381 -436
- package/src/renderer/notifications-modal.ts +116 -0
- package/src/renderer/onboarding/onboarding-wizard.ts +178 -588
- package/src/renderer/process-view.ts +192 -0
- package/src/renderer/profile-picker-modal.ts +58 -110
- package/src/renderer/search-overlay.ts +6 -3
- package/src/renderer/selection-modal-overlay.ts +87 -327
- package/src/renderer/semantic-diff.ts +2 -2
- package/src/renderer/session-chips.ts +102 -0
- package/src/renderer/session-picker-modal.ts +177 -297
- package/src/renderer/settings-modal-connections.ts +14 -23
- package/src/renderer/settings-modal-context.ts +367 -0
- package/src/renderer/settings-modal-helpers.ts +6 -11
- package/src/renderer/settings-modal.ts +322 -726
- package/src/renderer/shell-surface.ts +277 -175
- package/src/renderer/shortcuts-overlay.ts +217 -0
- package/src/renderer/status-line.ts +475 -0
- package/src/renderer/surface-compose.ts +174 -0
- package/src/renderer/surface-kit-extra.ts +303 -0
- package/src/renderer/surface-kit-list.ts +366 -0
- package/src/renderer/surface-kit-parts.ts +360 -0
- package/src/renderer/surface-kit.ts +568 -0
- package/src/renderer/syntax-highlighter.ts +280 -197
- package/src/renderer/syntax-theme.ts +51 -0
- package/src/renderer/system-message.ts +0 -1
- package/src/renderer/term-caps.ts +42 -1
- package/src/renderer/terminal-bg-probe.ts +116 -19
- package/src/renderer/terminal-palette-probe.ts +322 -0
- package/src/renderer/terminal-palette-reader.ts +183 -0
- package/src/renderer/terminal-palette.ts +58 -0
- package/src/renderer/theme-mode-config.ts +16 -1
- package/src/renderer/theme.ts +234 -207
- package/src/renderer/thinking.ts +0 -1
- package/src/renderer/throbber.ts +188 -0
- package/src/renderer/toast-center.ts +119 -0
- package/src/renderer/ui-factory.ts +39 -602
- package/src/renderer/ui-primitives.ts +8 -25
- package/src/renderer/usage-modal.ts +353 -0
- package/src/renderer/user-message.ts +58 -0
- package/src/renderer/voice-capture-chip.ts +53 -0
- package/src/runtime/bootstrap-command-context.ts +0 -5
- package/src/runtime/bootstrap-command-parts.ts +38 -89
- package/src/runtime/bootstrap-core.ts +13 -15
- package/src/runtime/bootstrap-hook-bridge.ts +12 -21
- package/src/runtime/bootstrap-notifier-sync.ts +17 -1
- package/src/runtime/bootstrap-shell.ts +16 -50
- package/src/runtime/bootstrap.ts +14 -14
- package/src/runtime/client/fleet-union.ts +5 -5
- package/src/runtime/client/hosted-exit.ts +2 -2
- package/src/runtime/client/hosted-sessions.ts +1 -1
- package/src/runtime/client/spine-adoption.ts +1 -1
- package/src/runtime/context-accounting-source.ts +5 -4
- package/src/runtime/index.ts +1 -0
- package/src/runtime/notification-dispatch.ts +102 -29
- package/src/runtime/recovery-offer-wiring.ts +4 -1
- package/src/runtime/recovery-prompt.ts +31 -15
- package/src/runtime/runtime-services-types.ts +10 -6
- package/src/runtime/services.ts +17 -12
- package/src/runtime/session-continuity-hints.ts +2 -5
- package/src/runtime/session-inbound-inputs.ts +1 -1
- package/src/runtime/session-resume-liveness-confirm.ts +14 -2
- package/src/runtime/startup-typeahead-gate.ts +103 -0
- package/src/runtime/store/domains/domain-read-matrix.ts +0 -1
- package/src/runtime/store/domains/index.ts +0 -8
- package/src/runtime/store/selectors/index.ts +0 -28
- package/src/runtime/store/state.ts +0 -8
- package/src/runtime/ui-services.ts +4 -6
- package/src/runtime/usage-tracker.ts +348 -0
- package/src/runtime/wrfc-persistence.ts +4 -4
- package/src/shell/blocking-input.ts +92 -2
- package/src/shell/recovery-input-helpers.ts +1 -1
- package/src/shell/session-views.ts +539 -0
- package/src/shell/status-report.ts +86 -0
- package/src/shell/transcript-scroll.ts +68 -0
- package/src/shell/ui-openers.ts +84 -172
- package/src/shell/view-openers.ts +205 -0
- package/src/tools/wrfc-agent-guard.ts +22 -2
- package/src/utils/format-duration.ts +4 -8
- package/src/utils/splash-lines.ts +9 -1
- package/src/utils/terminal-width.ts +56 -0
- package/src/verification/verification-ledger.ts +18 -16
- package/src/{panels → views}/agent-inspector-shared.ts +2 -2
- package/src/{panels → views}/builtin-modals.ts +22 -21
- package/src/views/builtin-views.ts +135 -0
- package/src/{panels → views}/confirm-state.ts +9 -9
- package/src/{panels → views}/diff-review-model.ts +10 -3
- package/src/{panels → views}/eval-registry.ts +1 -1
- package/src/{panels → views}/fleet-acts.ts +49 -85
- package/src/{panels/fleet-panel-format.ts → views/fleet-format.ts} +40 -40
- package/src/{panels → views}/fleet-gateway.ts +5 -5
- package/src/{panels → views}/fleet-observed-render.ts +16 -16
- package/src/{panels → views}/fleet-read-model.ts +9 -9
- package/src/{panels/fleet-tabs.ts → views/fleet-session-tabs.ts} +13 -13
- package/src/{panels → views}/fleet-spawn.ts +33 -49
- package/src/{panels → views}/fleet-steer.ts +12 -12
- package/src/{panels → views}/fleet-stop.ts +4 -4
- package/src/{panels → views}/fleet-transcript.ts +26 -27
- package/src/{panels/fleet-panel-worktree-detail.ts → views/fleet-worktree-detail.ts} +7 -7
- package/src/{panels → views}/hosted-session-feed.ts +6 -6
- package/src/views/modal-surface-registry.ts +41 -0
- package/src/{panels → views}/modals/devices-modal.ts +2 -2
- package/src/{panels → views}/modals/hooks-modal.ts +13 -13
- package/src/{panels → views}/modals/keybindings-modal.ts +19 -27
- package/src/{panels → views}/modals/knowledge-modal.ts +6 -5
- package/src/{panels → views}/modals/local-auth-modal.ts +22 -21
- package/src/{panels → views}/modals/marketplace-modal.ts +11 -11
- package/src/{panels → views}/modals/memory-modal.ts +8 -8
- package/src/{panels → views}/modals/modal-surface-helpers.ts +15 -5
- package/src/{panels → views}/modals/pairing-modal.ts +9 -9
- package/src/{panels → views}/modals/planning-modal.ts +15 -20
- package/src/{panels → views}/modals/plugins-modal.ts +6 -6
- package/src/{panels → views}/modals/policy-modal.ts +13 -5
- package/src/{panels → views}/modals/provider-health-modal.ts +4 -4
- package/src/{panels → views}/modals/remote-modal.ts +3 -3
- package/src/{panels → views}/modals/sandbox-modal.ts +4 -4
- package/src/{panels → views}/modals/security-modal.ts +22 -16
- package/src/{panels → views}/modals/services-modal.ts +3 -3
- package/src/{panels → views}/modals/settings-sync-modal.ts +4 -4
- package/src/{panels → views}/modals/skills-modal.ts +9 -9
- package/src/{panels → views}/modals/subscription-modal.ts +3 -3
- package/src/{panels → views}/modals/work-plan-modal.ts +10 -13
- package/src/views/notifications-feed.ts +314 -0
- package/src/{panels → views}/polish-core.ts +38 -36
- package/src/{panels → views}/polish-tables.ts +35 -26
- package/src/{panels → views}/polish.ts +101 -95
- package/src/{panels → views}/project-planning-answer-actions.ts +3 -3
- package/src/{panels → views}/provider-health-routes.ts +14 -14
- package/src/{panels → views}/search-focus.ts +8 -8
- package/src/{panels → views}/session-maintenance.ts +11 -11
- package/src/views/skills-discovery.ts +159 -0
- package/src/views/view-deps.ts +80 -0
- package/src/views/view-panel-adapter.ts +53 -0
- package/src/{panels → views}/workstream-graph-render.ts +1 -1
- package/src/core/conversation-fold.ts +0 -101
- package/src/core/conversation-turn-structure.ts +0 -464
- package/src/core/conversation-user-receipts.ts +0 -42
- package/src/input/commands/operator-panel-runtime.ts +0 -186
- package/src/input/panel-integration-actions.ts +0 -33
- package/src/input/panel-mouse-geometry.ts +0 -97
- package/src/input/panel-paste-flood-guard.ts +0 -86
- package/src/panels/base-panel.ts +0 -301
- package/src/panels/builtin/agent.ts +0 -27
- package/src/panels/builtin/development.ts +0 -92
- package/src/panels/builtin/knowledge.ts +0 -12
- package/src/panels/builtin/operations.ts +0 -177
- package/src/panels/builtin/session.ts +0 -81
- package/src/panels/builtin/shared.ts +0 -357
- package/src/panels/builtin-panels.ts +0 -28
- package/src/panels/cost-tracker-panel.ts +0 -746
- package/src/panels/diff-panel.ts +0 -797
- package/src/panels/diff-review-panel.ts +0 -395
- package/src/panels/expandable-list-panel.ts +0 -189
- package/src/panels/fleet-deep-link.ts +0 -31
- package/src/panels/fleet-panel.ts +0 -799
- package/src/panels/git-panel.ts +0 -800
- package/src/panels/hosted-session-panel.ts +0 -221
- package/src/panels/index.ts +0 -21
- package/src/panels/local-auth-panel.ts +0 -478
- package/src/panels/notifications-feed.ts +0 -127
- package/src/panels/notifications-panel.ts +0 -160
- package/src/panels/panel-confirm-overlay.ts +0 -72
- package/src/panels/panel-manager.ts +0 -791
- package/src/panels/plugins-panel.ts +0 -371
- package/src/panels/scrollable-list-panel.ts +0 -498
- package/src/panels/skills-panel.ts +0 -454
- package/src/panels/token-budget-panel.ts +0 -688
- package/src/panels/types.ts +0 -191
- package/src/panels/wrfc-panel-format.ts +0 -133
- package/src/renderer/diff-view.ts +0 -134
- package/src/renderer/fleet-tab-strip.ts +0 -56
- package/src/renderer/footer-tips.ts +0 -53
- package/src/renderer/fullscreen-primitives.ts +0 -146
- package/src/renderer/fullscreen-workspace.ts +0 -194
- package/src/renderer/hint-grammar.ts +0 -52
- package/src/renderer/modal-factory.ts +0 -480
- package/src/renderer/overlay-box.ts +0 -154
- package/src/renderer/overlay-viewport.ts +0 -104
- package/src/renderer/panel-composite.ts +0 -145
- package/src/renderer/panel-workspace-bar.ts +0 -54
- package/src/renderer/process-indicator.ts +0 -163
- package/src/renderer/tab-strip.ts +0 -222
- package/src/renderer/text-layout.ts +0 -31
- package/src/renderer/tool-call.ts +0 -276
- package/src/renderer/tool-result-expanded-lines.ts +0 -48
- package/src/renderer/tool-result-summary.ts +0 -219
- package/src/runtime/diagnostics/panels/index.ts +0 -21
- package/src/runtime/diagnostics/panels/ops.ts +0 -156
- package/src/runtime/perf/panel-contracts.ts +0 -32
- package/src/runtime/store/domains/panels.ts +0 -117
- /package/src/{panels → views}/memory-consolidation-gateway.ts +0 -0
- /package/src/{panels → views}/modals/modal-theme.ts +0 -0
- /package/src/{panels → views}/provider-health-domains.ts +0 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lane-graph/glyphs.ts, the characters the conversation work tree draws with.
|
|
3
|
+
*
|
|
4
|
+
* The layout (layout.ts) speaks in ROLES ("a vertical lane line", "the corner
|
|
5
|
+
* where a lane merges back"); this module turns a role into a character for
|
|
6
|
+
* the chosen set, so the same layout draws rounded, square or ascii without a
|
|
7
|
+
* second code path.
|
|
8
|
+
*
|
|
9
|
+
* `display.treeGlyphs` picks the set. A terminal the capability probe reports
|
|
10
|
+
* as limited (TERM=dumb, NO_COLOR, or a locale without UTF-8) draws ascii no
|
|
11
|
+
* matter what the setting says, because box drawing on such a terminal prints
|
|
12
|
+
* as garbage.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export type TreeGlyphSetName = 'rounded' | 'square' | 'ascii';
|
|
16
|
+
|
|
17
|
+
const TREE_GLYPH_SET_NAMES: readonly TreeGlyphSetName[] = ['rounded', 'square', 'ascii'];
|
|
18
|
+
|
|
19
|
+
/** Every drawing role the lane layout emits. */
|
|
20
|
+
export type LaneGlyphRole =
|
|
21
|
+
| 'vert' // │ a lane running down
|
|
22
|
+
| 'horiz' // ─ a horizontal run between lanes
|
|
23
|
+
| 'branch' // ├ where a lane leaves its parent, or merges back into it
|
|
24
|
+
| 'cross' // ┼ a horizontal run crossing a live lane
|
|
25
|
+
| 'open' // ╮ where a new lane starts
|
|
26
|
+
| 'merge' // ╯ where a lane ends into its parent
|
|
27
|
+
| 'head' // ╭ the top of a turn's spine
|
|
28
|
+
| 'close' // ╰ the end of a turn's spine (the answer row)
|
|
29
|
+
| 'folded' // ◉ a finished lane folded to one bead
|
|
30
|
+
| 'turn'; // ◆ a folded turn's header
|
|
31
|
+
|
|
32
|
+
export interface LaneGlyphs {
|
|
33
|
+
readonly name: TreeGlyphSetName;
|
|
34
|
+
readonly role: Readonly<Record<LaneGlyphRole, string>>;
|
|
35
|
+
/** Bead marks, one per status. `run` is the spinner's frames. */
|
|
36
|
+
readonly ok: string;
|
|
37
|
+
readonly warn: string;
|
|
38
|
+
readonly err: string;
|
|
39
|
+
readonly run: readonly string[];
|
|
40
|
+
readonly wait: string;
|
|
41
|
+
readonly cancel: string;
|
|
42
|
+
readonly background: string;
|
|
43
|
+
/** Body triangles: closed (has a body) and open. */
|
|
44
|
+
readonly closed: string;
|
|
45
|
+
readonly opened: string;
|
|
46
|
+
/** The focused row's edge mark. */
|
|
47
|
+
readonly focus: string;
|
|
48
|
+
/** "↳" linking a WRFC fix to its finding. */
|
|
49
|
+
readonly answers: string;
|
|
50
|
+
/** "…" in "… N more". */
|
|
51
|
+
readonly ellipsis: string;
|
|
52
|
+
/** "·" between summary pieces. */
|
|
53
|
+
readonly dot: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const ROUNDED: LaneGlyphs = {
|
|
57
|
+
name: 'rounded',
|
|
58
|
+
role: { vert: '│', horiz: '─', branch: '├', cross: '┼', open: '╮', merge: '╯', head: '╭', close: '╰', folded: '◉', turn: '◆' },
|
|
59
|
+
ok: '✓',
|
|
60
|
+
warn: '!',
|
|
61
|
+
err: '✕',
|
|
62
|
+
run: ['◐', '◓', '◑', '◒'],
|
|
63
|
+
wait: '●',
|
|
64
|
+
cancel: '○',
|
|
65
|
+
background: '▶',
|
|
66
|
+
closed: '▸',
|
|
67
|
+
opened: '▾',
|
|
68
|
+
focus: '┃',
|
|
69
|
+
answers: '↳',
|
|
70
|
+
ellipsis: '…',
|
|
71
|
+
dot: '·',
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const SQUARE: LaneGlyphs = {
|
|
75
|
+
...ROUNDED,
|
|
76
|
+
name: 'square',
|
|
77
|
+
role: { ...ROUNDED.role, open: '┐', merge: '┘', head: '┌', close: '└', folded: '●' },
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Plain ASCII: | for lanes, + for every junction and corner except the
|
|
82
|
+
* spine's end (`), - for horizontal runs. Beads: * done, ! look at this,
|
|
83
|
+
* x failed, o running, ? waiting on you, . cancelled, > background,
|
|
84
|
+
* @ a folded lane, # a folded turn.
|
|
85
|
+
*/
|
|
86
|
+
const ASCII: LaneGlyphs = {
|
|
87
|
+
name: 'ascii',
|
|
88
|
+
role: { vert: '|', horiz: '-', branch: '+', cross: '+', open: '+', merge: '+', head: '+', close: '`', folded: '@', turn: '#' },
|
|
89
|
+
ok: '*',
|
|
90
|
+
warn: '!',
|
|
91
|
+
err: 'x',
|
|
92
|
+
run: ['o', 'O'],
|
|
93
|
+
wait: '?',
|
|
94
|
+
cancel: '.',
|
|
95
|
+
background: '>',
|
|
96
|
+
closed: '>',
|
|
97
|
+
opened: 'v',
|
|
98
|
+
focus: '|',
|
|
99
|
+
answers: '->',
|
|
100
|
+
ellipsis: '...',
|
|
101
|
+
dot: '-',
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const SETS: Readonly<Record<TreeGlyphSetName, LaneGlyphs>> = { rounded: ROUNDED, square: SQUARE, ascii: ASCII };
|
|
105
|
+
|
|
106
|
+
function isTreeGlyphSetName(value: unknown): value is TreeGlyphSetName {
|
|
107
|
+
return typeof value === 'string' && (TREE_GLYPH_SET_NAMES as readonly string[]).includes(value);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The glyph set for a name; anything unrecognised draws rounded. */
|
|
111
|
+
export function laneGlyphs(name: TreeGlyphSetName): LaneGlyphs {
|
|
112
|
+
return SETS[name];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The set to draw with: the configured name, unless the terminal cannot draw
|
|
117
|
+
* unicode, in which case ascii.
|
|
118
|
+
*/
|
|
119
|
+
export function resolveTreeGlyphSet(configured: unknown, unicodeCapable: boolean): TreeGlyphSetName {
|
|
120
|
+
if (!unicodeCapable) return 'ascii';
|
|
121
|
+
return isTreeGlyphSetName(configured) ? configured : 'rounded';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const ASCII_TEXT: ReadonlyArray<readonly [RegExp, string]> = [
|
|
125
|
+
[/·/g, '-'], [/[−–—]/g, '-'], [/…/g, '...'], [/↳/g, '->'], [/[✓✔]/g, '*'], [/[✕✗]/g, 'x'], [/◈/g, '*'], [/[▸▶]/g, '>'], [/▾/g, 'v'], [/⋯/g, '...'],
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
/** Row text as the glyph set draws it: plain ASCII for the ascii set, unchanged otherwise. */
|
|
129
|
+
export function glyphText(text: string, glyphs: Pick<LaneGlyphs, 'name'>): string {
|
|
130
|
+
if (glyphs.name !== 'ascii') return text;
|
|
131
|
+
let out = text;
|
|
132
|
+
for (const [pattern, replacement] of ASCII_TEXT) out = out.replace(pattern, replacement);
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lane-graph/layout.ts, where every lane of a turn's work tree sits, row by
|
|
3
|
+
* row.
|
|
4
|
+
*
|
|
5
|
+
* A turn is drawn the way `git log --graph` draws history: the turn's own
|
|
6
|
+
* work is a vertical SPINE, a subagent (or a WRFC chain, or a hosted agent)
|
|
7
|
+
* BRANCHES off the lane that started it into a lane of its own, its tool
|
|
8
|
+
* calls are beads on that lane, and it MERGES back into exactly the lane it
|
|
9
|
+
* came from. Lanes that run at the same time sit side by side.
|
|
10
|
+
*
|
|
11
|
+
* Input: the turn's rows in display order, each described only by what it
|
|
12
|
+
* does to the lanes (see LaneEvent). Output: one gutter row per event, made
|
|
13
|
+
* of drawing ROLES (glyphs.ts turns a role into a character), plus the
|
|
14
|
+
* continuation gutter that runs beside anything drawn under that row (wrapped
|
|
15
|
+
* text, an opened bead's body).
|
|
16
|
+
*
|
|
17
|
+
* Geometry: every lane is 2 columns wide, the lane line on the even column,
|
|
18
|
+
* the odd column free for horizontal runs. A new lane takes the lowest free
|
|
19
|
+
* slot to the right of its parent, so a child is always right of its parent
|
|
20
|
+
* and lanes never move sideways once placed. The text column is fixed for the
|
|
21
|
+
* whole turn (see gutterWidthFor) so text never shifts with depth.
|
|
22
|
+
*
|
|
23
|
+
* Everything here is pure: no theme, no width, no glyph set.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import type { LaneGlyphRole } from './glyphs.ts';
|
|
27
|
+
|
|
28
|
+
export type LaneId = string;
|
|
29
|
+
|
|
30
|
+
/** The turn's own lane. */
|
|
31
|
+
export const SPINE: LaneId = 'spine';
|
|
32
|
+
|
|
33
|
+
export type LaneEvent =
|
|
34
|
+
/** The turn header: the spine opens with ╭─. */
|
|
35
|
+
| { readonly kind: 'head' }
|
|
36
|
+
/** A folded turn's header: a single ◆, no lane opens. */
|
|
37
|
+
| { readonly kind: 'turn' }
|
|
38
|
+
/** A row with no mark of its own (text): every live lane runs past it. */
|
|
39
|
+
| { readonly kind: 'row' }
|
|
40
|
+
/** A tool call on `lane`: its bead sits on the lane. */
|
|
41
|
+
| { readonly kind: 'bead'; readonly lane: LaneId }
|
|
42
|
+
/** A finished lane folded to one ◉ bead on its parent's lane, in its own color. */
|
|
43
|
+
| { readonly kind: 'folded'; readonly lane: LaneId; readonly child: LaneId }
|
|
44
|
+
/** `lane` branches off `parent` (├─╮). */
|
|
45
|
+
| { readonly kind: 'spawn'; readonly parent: LaneId; readonly lane: LaneId }
|
|
46
|
+
/** `lane` merges back into the lane it came from (├─╯). */
|
|
47
|
+
| { readonly kind: 'merge'; readonly lane: LaneId }
|
|
48
|
+
/** The answer's first row: the spine closes with ╰─. */
|
|
49
|
+
| { readonly kind: 'answer' };
|
|
50
|
+
|
|
51
|
+
/** One drawn cell of a gutter row. `color` is the lane's color index (creation order). */
|
|
52
|
+
export interface GutterCell {
|
|
53
|
+
readonly role: LaneGlyphRole | 'bead';
|
|
54
|
+
readonly color: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type GutterRow = ReadonlyArray<GutterCell | null>;
|
|
58
|
+
|
|
59
|
+
export interface LaneLayout {
|
|
60
|
+
/** One gutter row per input event. */
|
|
61
|
+
readonly rows: readonly GutterRow[];
|
|
62
|
+
/** The gutter under each event's row: every lane still live after it, as │. */
|
|
63
|
+
readonly cont: readonly GutterRow[];
|
|
64
|
+
/** Column (within the gutter) of the row's bead, ◉, ◆ or the lane end the row is about. */
|
|
65
|
+
readonly markCol: ReadonlyArray<number | undefined>;
|
|
66
|
+
/** Most lanes live at once anywhere in the turn. */
|
|
67
|
+
readonly maxLive: number;
|
|
68
|
+
/** Highest slot used + 1. */
|
|
69
|
+
readonly slots: number;
|
|
70
|
+
/** Columns from the gutter's first column to the turn's text column. */
|
|
71
|
+
readonly gutterWidth: number;
|
|
72
|
+
/** Color index of every lane that appeared. */
|
|
73
|
+
readonly laneColor: ReadonlyMap<LaneId, number>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Columns from the gutter's first column to the text column: 6 while one lane
|
|
78
|
+
* is live, 10 for up to four, and two more per lane past that. Fixed per turn
|
|
79
|
+
* so a turn's text starts in one column however deep its lanes go.
|
|
80
|
+
*/
|
|
81
|
+
function gutterWidthFor(slots: number): number {
|
|
82
|
+
if (slots <= 1) return 6;
|
|
83
|
+
if (slots <= 4) return 10;
|
|
84
|
+
return slots * 2 + 2;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface LaneState {
|
|
88
|
+
readonly id: LaneId;
|
|
89
|
+
readonly parent: LaneId | undefined;
|
|
90
|
+
readonly slot: number;
|
|
91
|
+
readonly color: number;
|
|
92
|
+
live: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function layoutLaneGraph(events: readonly LaneEvent[]): LaneLayout {
|
|
96
|
+
const lanes = new Map<LaneId, LaneState>();
|
|
97
|
+
const bySlot: Array<LaneState | undefined> = [];
|
|
98
|
+
let nextColor = 0;
|
|
99
|
+
let maxLive = 0;
|
|
100
|
+
let maxSlot = -1;
|
|
101
|
+
|
|
102
|
+
const colorOf = (id: LaneId): number => {
|
|
103
|
+
const known = lanes.get(id);
|
|
104
|
+
if (known) return known.color;
|
|
105
|
+
return nextColor++;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const liveCount = (): number => bySlot.filter((l) => l?.live).length;
|
|
109
|
+
|
|
110
|
+
const openLane = (id: LaneId, parent: LaneId | undefined, minSlot: number): LaneState => {
|
|
111
|
+
let slot = Math.max(0, minSlot);
|
|
112
|
+
while (bySlot[slot]?.live) slot++;
|
|
113
|
+
const state: LaneState = { id, parent, slot, color: colorOf(id), live: true };
|
|
114
|
+
lanes.set(id, state);
|
|
115
|
+
bySlot[slot] = state;
|
|
116
|
+
maxSlot = Math.max(maxSlot, slot);
|
|
117
|
+
maxLive = Math.max(maxLive, liveCount());
|
|
118
|
+
return state;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/** A lane an event names but that never opened (defensive): open it off the spine. */
|
|
122
|
+
const ensureLane = (id: LaneId): LaneState => {
|
|
123
|
+
const known = lanes.get(id);
|
|
124
|
+
if (known && known.live) return known;
|
|
125
|
+
if (id === SPINE) return openLane(SPINE, undefined, 0);
|
|
126
|
+
const parent = lanes.get(SPINE);
|
|
127
|
+
return openLane(id, SPINE, (parent?.live ? parent.slot : -1) + 1);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/** The nearest ancestor of `lane` that is still live. */
|
|
131
|
+
const openAncestor = (lane: LaneState): LaneState | undefined => {
|
|
132
|
+
let cursor = lane.parent !== undefined ? lanes.get(lane.parent) : undefined;
|
|
133
|
+
while (cursor && !cursor.live) cursor = cursor.parent !== undefined ? lanes.get(cursor.parent) : undefined;
|
|
134
|
+
return cursor;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/** A row of │ for every live lane. */
|
|
138
|
+
const baseRow = (): Array<GutterCell | null> => {
|
|
139
|
+
const row: Array<GutterCell | null> = [];
|
|
140
|
+
bySlot.forEach((lane, slot) => {
|
|
141
|
+
if (lane?.live) row[slot * 2] = { role: 'vert', color: lane.color };
|
|
142
|
+
});
|
|
143
|
+
return row;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/** Draw a horizontal run from slot `from` (exclusive) to slot `to` (exclusive), in `color`. */
|
|
147
|
+
const run = (row: Array<GutterCell | null>, from: number, to: number, color: number): void => {
|
|
148
|
+
for (let col = from * 2 + 1; col < to * 2; col++) {
|
|
149
|
+
const lane = col % 2 === 0 ? bySlot[col / 2] : undefined;
|
|
150
|
+
row[col] = lane?.live ? { role: 'cross', color: lane.color } : { role: 'horiz', color };
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const rows: GutterRow[] = [];
|
|
155
|
+
const cont: GutterRow[] = [];
|
|
156
|
+
const markCol: Array<number | undefined> = [];
|
|
157
|
+
|
|
158
|
+
for (const event of events) {
|
|
159
|
+
let row: Array<GutterCell | null>;
|
|
160
|
+
let mark: number | undefined;
|
|
161
|
+
switch (event.kind) {
|
|
162
|
+
case 'head': {
|
|
163
|
+
const spine = lanes.get(SPINE)?.live ? lanes.get(SPINE)! : openLane(SPINE, undefined, 0);
|
|
164
|
+
row = baseRow();
|
|
165
|
+
row[spine.slot * 2] = { role: 'head', color: spine.color };
|
|
166
|
+
row[spine.slot * 2 + 1] = { role: 'horiz', color: spine.color };
|
|
167
|
+
mark = spine.slot * 2;
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case 'turn': {
|
|
171
|
+
const color = colorOf(SPINE);
|
|
172
|
+
if (!lanes.has(SPINE)) lanes.set(SPINE, { id: SPINE, parent: undefined, slot: 0, color, live: false });
|
|
173
|
+
row = baseRow();
|
|
174
|
+
row[0] = { role: 'turn', color };
|
|
175
|
+
maxSlot = Math.max(maxSlot, 0);
|
|
176
|
+
maxLive = Math.max(maxLive, 1);
|
|
177
|
+
mark = 0;
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
case 'row': {
|
|
181
|
+
row = baseRow();
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
case 'bead': {
|
|
185
|
+
const lane = ensureLane(event.lane);
|
|
186
|
+
row = baseRow();
|
|
187
|
+
row[lane.slot * 2] = { role: 'bead', color: lane.color };
|
|
188
|
+
mark = lane.slot * 2;
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case 'folded': {
|
|
192
|
+
const parent = ensureLane(event.lane);
|
|
193
|
+
const color = colorOf(event.child);
|
|
194
|
+
if (!lanes.has(event.child)) lanes.set(event.child, { id: event.child, parent: event.lane, slot: parent.slot, color, live: false });
|
|
195
|
+
row = baseRow();
|
|
196
|
+
row[parent.slot * 2] = { role: 'folded', color };
|
|
197
|
+
mark = parent.slot * 2;
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
case 'spawn': {
|
|
201
|
+
const parent = ensureLane(event.parent);
|
|
202
|
+
row = baseRow();
|
|
203
|
+
const child = openLane(event.lane, event.parent, parent.slot + 1);
|
|
204
|
+
row[parent.slot * 2] = { role: 'branch', color: parent.color };
|
|
205
|
+
run(row, parent.slot, child.slot, child.color);
|
|
206
|
+
row[child.slot * 2] = { role: 'open', color: child.color };
|
|
207
|
+
mark = child.slot * 2;
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
case 'merge': {
|
|
211
|
+
const lane = lanes.get(event.lane);
|
|
212
|
+
row = baseRow();
|
|
213
|
+
if (!lane || !lane.live) break;
|
|
214
|
+
const target = openAncestor(lane);
|
|
215
|
+
if (target) {
|
|
216
|
+
row[target.slot * 2] = { role: 'branch', color: target.color };
|
|
217
|
+
run(row, target.slot, lane.slot, lane.color);
|
|
218
|
+
row[lane.slot * 2] = { role: 'merge', color: lane.color };
|
|
219
|
+
} else {
|
|
220
|
+
// Nothing left to merge into (its parents already ended): the lane just ends.
|
|
221
|
+
row[lane.slot * 2] = { role: 'close', color: lane.color };
|
|
222
|
+
}
|
|
223
|
+
mark = lane.slot * 2;
|
|
224
|
+
lane.live = false;
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
case 'answer': {
|
|
228
|
+
const spine = lanes.get(SPINE);
|
|
229
|
+
row = baseRow();
|
|
230
|
+
const slot = spine?.slot ?? 0;
|
|
231
|
+
const color = spine?.color ?? colorOf(SPINE);
|
|
232
|
+
row[slot * 2] = { role: 'close', color };
|
|
233
|
+
row[slot * 2 + 1] = { role: 'horiz', color };
|
|
234
|
+
if (spine) spine.live = false;
|
|
235
|
+
mark = slot * 2;
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
rows.push(row);
|
|
240
|
+
cont.push(baseRow());
|
|
241
|
+
markCol.push(mark);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const slots = maxSlot + 1;
|
|
245
|
+
const laneColor = new Map<LaneId, number>();
|
|
246
|
+
for (const [id, lane] of lanes) laneColor.set(id, lane.color);
|
|
247
|
+
return { rows, cont, markCol, maxLive, slots, gutterWidth: gutterWidthFor(slots), laneColor };
|
|
248
|
+
}
|