@pellux/goodvibes-tui 2.0.20 → 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 +70 -0
- package/README.md +16 -16
- package/docs/README.md +4 -4
- package/docs/foundation-artifacts/operator-contract.json +59 -4
- 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
|
@@ -3,7 +3,6 @@ export interface ShellLayoutRequest {
|
|
|
3
3
|
readonly height: number;
|
|
4
4
|
readonly headerHeight: number;
|
|
5
5
|
readonly footerHeight: number;
|
|
6
|
-
readonly panelWidth?: number;
|
|
7
6
|
}
|
|
8
7
|
|
|
9
8
|
export interface Rect {
|
|
@@ -19,23 +18,13 @@ export interface ShellLayout {
|
|
|
19
18
|
readonly body: Rect;
|
|
20
19
|
readonly footer: Rect;
|
|
21
20
|
readonly conversation: Rect;
|
|
22
|
-
readonly panel?: Rect;
|
|
23
|
-
readonly separatorX?: number;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface SplitPaneLayout {
|
|
27
|
-
readonly totalRows: number;
|
|
28
|
-
readonly topTabRows: number;
|
|
29
|
-
readonly bottomTabRows: number;
|
|
30
|
-
readonly separatorRows: number;
|
|
31
|
-
readonly topContentRows: number;
|
|
32
|
-
readonly bottomContentRows: number;
|
|
33
21
|
}
|
|
34
22
|
|
|
35
23
|
function clamp(value: number, min: number, max: number): number {
|
|
36
24
|
return Math.max(min, Math.min(max, value));
|
|
37
25
|
}
|
|
38
26
|
|
|
27
|
+
/** The screen's rows: header, the conversation body (full width), footer. */
|
|
39
28
|
export function createShellLayout(request: ShellLayoutRequest): ShellLayout {
|
|
40
29
|
const width = Math.max(1, request.width);
|
|
41
30
|
const height = Math.max(1, request.height);
|
|
@@ -43,55 +32,11 @@ export function createShellLayout(request: ShellLayoutRequest): ShellLayout {
|
|
|
43
32
|
const footerHeight = clamp(request.footerHeight, 0, Math.max(0, height - headerHeight));
|
|
44
33
|
const bodyHeight = Math.max(0, height - headerHeight - footerHeight);
|
|
45
34
|
|
|
46
|
-
const hasPanel = typeof request.panelWidth === 'number' && request.panelWidth > 0;
|
|
47
|
-
const safePanelWidth = hasPanel
|
|
48
|
-
? clamp(request.panelWidth as number, 1, Math.max(1, width - 2))
|
|
49
|
-
: 0;
|
|
50
|
-
const separatorWidth = hasPanel ? 1 : 0;
|
|
51
|
-
const conversationWidth = hasPanel ? Math.max(1, width - safePanelWidth - separatorWidth) : width;
|
|
52
|
-
|
|
53
35
|
return {
|
|
54
36
|
screen: { x: 0, y: 0, width, height },
|
|
55
37
|
header: { x: 0, y: 0, width, height: headerHeight },
|
|
56
38
|
body: { x: 0, y: headerHeight, width, height: bodyHeight },
|
|
57
39
|
footer: { x: 0, y: headerHeight + bodyHeight, width, height: footerHeight },
|
|
58
|
-
conversation: { x: 0, y: headerHeight, width
|
|
59
|
-
panel: hasPanel
|
|
60
|
-
? {
|
|
61
|
-
x: conversationWidth + separatorWidth,
|
|
62
|
-
y: headerHeight,
|
|
63
|
-
width: safePanelWidth,
|
|
64
|
-
height: bodyHeight,
|
|
65
|
-
}
|
|
66
|
-
: undefined,
|
|
67
|
-
separatorX: hasPanel ? conversationWidth : undefined,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function createSplitPaneLayout(
|
|
72
|
-
totalRows: number,
|
|
73
|
-
ratio: number,
|
|
74
|
-
options: {
|
|
75
|
-
readonly topTabRows?: number;
|
|
76
|
-
readonly bottomTabRows?: number;
|
|
77
|
-
readonly separatorRows?: number;
|
|
78
|
-
} = {},
|
|
79
|
-
): SplitPaneLayout {
|
|
80
|
-
const topTabRows = options.topTabRows ?? 1;
|
|
81
|
-
const bottomTabRows = options.bottomTabRows ?? 1;
|
|
82
|
-
const separatorRows = options.separatorRows ?? 1;
|
|
83
|
-
const chromeRows = topTabRows + bottomTabRows + separatorRows;
|
|
84
|
-
const contentRows = Math.max(0, totalRows - chromeRows);
|
|
85
|
-
const normalizedRatio = clamp(ratio, 0.2, 0.8);
|
|
86
|
-
const topContentRows = contentRows <= 1 ? contentRows : Math.max(1, Math.floor(contentRows * normalizedRatio));
|
|
87
|
-
const bottomContentRows = contentRows <= 1 ? 0 : Math.max(1, contentRows - topContentRows);
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
totalRows,
|
|
91
|
-
topTabRows,
|
|
92
|
-
bottomTabRows,
|
|
93
|
-
separatorRows,
|
|
94
|
-
topContentRows,
|
|
95
|
-
bottomContentRows,
|
|
40
|
+
conversation: { x: 0, y: headerHeight, width, height: bodyHeight },
|
|
96
41
|
};
|
|
97
42
|
}
|
package/src/renderer/layout.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { GLYPHS, SPINNER_FRAMES
|
|
1
|
+
import { GLYPHS, SPINNER_FRAMES } from './ui-primitives.ts';
|
|
2
|
+
import { activeUiTones } from './theme.ts';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Layout constants, single source of truth for margins and content width.
|
|
@@ -9,17 +10,10 @@ import { GLYPHS, SPINNER_FRAMES, UI_TONES } from './ui-primitives.ts';
|
|
|
9
10
|
*/
|
|
10
11
|
export { TRANSCRIPT_LAYOUT as LAYOUT } from '@pellux/goodvibes-terminal-shell';
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
SUCCESS_ICON: GLYPHS.status.success,
|
|
14
|
-
SPINNER_FRAMES,
|
|
15
|
-
FAIL_ICON: GLYPHS.status.failure,
|
|
16
|
-
PENDING_ICON: GLYPHS.status.pending,
|
|
17
|
-
TOOL_NAME_PAD: 8,
|
|
18
|
-
} as const;
|
|
19
|
-
|
|
13
|
+
// `color` is a getter so it always reads the active theme.
|
|
20
14
|
export const BORDERS = {
|
|
21
|
-
THINKING: { char: '▌', color:
|
|
22
|
-
ERROR: { char: '▌', color:
|
|
23
|
-
WARNING: { char: '▌', color:
|
|
24
|
-
INFO: { char: '▌', color:
|
|
15
|
+
THINKING: { char: '▌', get color(): string { return activeUiTones().state.reasoning; } },
|
|
16
|
+
ERROR: { char: '▌', get color(): string { return activeUiTones().state.bad; } },
|
|
17
|
+
WARNING: { char: '▌', get color(): string { return activeUiTones().state.warn; } },
|
|
18
|
+
INFO: { char: '▌', get color(): string { return activeUiTones().state.info; } },
|
|
25
19
|
} as const;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type { Cell } from '@pellux/goodvibes-sdk/platform/types';
|
|
12
|
+
import { activeTheme } from './theme.ts';
|
|
12
13
|
|
|
13
14
|
/** Module-level set of inline markdown special characters (hoisted out of hot loop). */
|
|
14
15
|
const INLINE_SPECIAL_CHARS = new Set(['[', '`', '*', '_', '~']);
|
|
@@ -102,7 +103,7 @@ export function renderInlineMarkdown(text: string): InlineToken[] {
|
|
|
102
103
|
if (text.slice(i, i + 2) === '~~') {
|
|
103
104
|
const end = text.indexOf('~~', i + 2);
|
|
104
105
|
if (end !== -1) {
|
|
105
|
-
tokens.push({ type: 'text', text: text.slice(i + 2, end), style: { strikethrough: true, fg:
|
|
106
|
+
tokens.push({ type: 'text', text: text.slice(i + 2, end), style: { strikethrough: true, fg: activeTheme().strikethrough } });
|
|
106
107
|
i = end + 2;
|
|
107
108
|
continue;
|
|
108
109
|
}
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
import { type Line, type Cell, createStyledCell } from '@pellux/goodvibes-sdk/platform/types';
|
|
42
42
|
import { UIFactory } from './ui-factory.ts';
|
|
43
43
|
import { getDisplayWidth } from '../utils/terminal-width.ts';
|
|
44
|
-
import { activeTheme } from './theme.ts';
|
|
44
|
+
import { activeTheme, activeTokens } from './theme.ts';
|
|
45
45
|
import { renderInlineMarkdown } from './markdown-inline.ts';
|
|
46
46
|
|
|
47
47
|
/** Width a column is given when the budget allows, even if its content is narrower. */
|
|
@@ -111,7 +111,7 @@ function flattenCellChars(raw: string, isHdr: boolean): StyledChar[] {
|
|
|
111
111
|
for (const token of renderInlineMarkdown(raw)) {
|
|
112
112
|
let style: Partial<Cell>;
|
|
113
113
|
if (token.type === 'code') {
|
|
114
|
-
style = { fg:
|
|
114
|
+
style = { fg: activeTokens().markdownCode };
|
|
115
115
|
} else if (token.type === 'link') {
|
|
116
116
|
style = { fg: T.link, underline: true };
|
|
117
117
|
} else {
|
|
@@ -121,7 +121,7 @@ function flattenCellChars(raw: string, isHdr: boolean): StyledChar[] {
|
|
|
121
121
|
style.fg = style.fg || T.heading1;
|
|
122
122
|
style.bold = true;
|
|
123
123
|
} else {
|
|
124
|
-
style.fg = style.fg ||
|
|
124
|
+
style.fg = style.fg || activeTokens().text;
|
|
125
125
|
}
|
|
126
126
|
for (const ch of token.text) out.push({ char: ch, style });
|
|
127
127
|
}
|
|
@@ -203,7 +203,7 @@ function padLineToCells(lineChars: readonly StyledChar[], maxW: number, isHdr: b
|
|
|
203
203
|
w += cw;
|
|
204
204
|
}
|
|
205
205
|
while (w < maxW) {
|
|
206
|
-
cells.push(createStyledCell(' ', isHdr ? { fg: T.heading1 } : { fg:
|
|
206
|
+
cells.push(createStyledCell(' ', isHdr ? { fg: T.heading1 } : { fg: activeTokens().text }));
|
|
207
207
|
w++;
|
|
208
208
|
}
|
|
209
209
|
return cells;
|
|
@@ -315,16 +315,17 @@ function renderStackedTable(
|
|
|
315
315
|
hasSeparator: boolean,
|
|
316
316
|
width: number,
|
|
317
317
|
indent: number,
|
|
318
|
+
rightGutter: number,
|
|
318
319
|
): Line[] {
|
|
319
320
|
const lines: Line[] = [];
|
|
320
|
-
const availW = Math.max(1, width - indent);
|
|
321
|
+
const availW = Math.max(1, width - rightGutter - indent);
|
|
321
322
|
const headerRow = hasSeparator ? parsedRows[0] : undefined;
|
|
322
323
|
const bodyRows = hasSeparator ? parsedRows.slice(1) : [...parsedRows];
|
|
323
324
|
// A header-only table still has to show its headers, so it renders as one
|
|
324
325
|
// record with empty values rather than as nothing at all.
|
|
325
326
|
const records: readonly string[][] = bodyRows.length > 0 ? bodyRows : [[]];
|
|
326
|
-
const separatorStyle: Partial<Cell> = { fg:
|
|
327
|
-
const rule = UIFactory.stringToLine(' '.repeat(indent) + '─'.repeat(availW), width, { fg:
|
|
327
|
+
const separatorStyle: Partial<Cell> = { fg: activeTokens().textFaint };
|
|
328
|
+
const rule = UIFactory.stringToLine(' '.repeat(indent) + '─'.repeat(availW), width, { fg: activeTokens().textFaint });
|
|
328
329
|
|
|
329
330
|
for (let r = 0; r < records.length; r++) {
|
|
330
331
|
if (r > 0) lines.push(rule);
|
|
@@ -348,7 +349,11 @@ function renderStackedTable(
|
|
|
348
349
|
* Render a markdown table with box-drawing borders. Cells wrap to as many
|
|
349
350
|
* physical lines as their content needs; nothing is ever truncated.
|
|
350
351
|
*/
|
|
351
|
-
|
|
352
|
+
/**
|
|
353
|
+
* `rightGutter` columns on the right stay clear: the table (boxed or stacked)
|
|
354
|
+
* ends at width - rightGutter - 1 or earlier.
|
|
355
|
+
*/
|
|
356
|
+
export function renderTable(rows: string[], width: number, indent: number, rightGutter = 0): Line[] {
|
|
352
357
|
const lines: Line[] = [];
|
|
353
358
|
|
|
354
359
|
// Parse rows into cells, skip separator
|
|
@@ -367,7 +372,7 @@ export function renderTable(rows: string[], width: number, indent: number): Line
|
|
|
367
372
|
if (parsedRows.length === 0) return lines;
|
|
368
373
|
|
|
369
374
|
const colCount = Math.max(...parsedRows.map((r) => r.length));
|
|
370
|
-
const availW = width - indent;
|
|
375
|
+
const availW = width - rightGutter - indent;
|
|
371
376
|
|
|
372
377
|
// Measure using stripped text (no markdown markers), both the natural width
|
|
373
378
|
// and the widest single word, the latter is what a column needs before it
|
|
@@ -391,11 +396,11 @@ export function renderTable(rows: string[], width: number, indent: number): Line
|
|
|
391
396
|
// the end of the line buffer and silently dropped, so the box is abandoned
|
|
392
397
|
// for the stacked layout instead.
|
|
393
398
|
const boxWidth = indent + 1 + colWidths.reduce((sum, w) => sum + w + 3, 0);
|
|
394
|
-
if (boxWidth > width) {
|
|
395
|
-
return renderStackedTable(parsedRows, colCount, hasSeparator, width, indent);
|
|
399
|
+
if (boxWidth > width - rightGutter) {
|
|
400
|
+
return renderStackedTable(parsedRows, colCount, hasSeparator, width, indent, rightGutter);
|
|
396
401
|
}
|
|
397
402
|
|
|
398
|
-
const bc =
|
|
403
|
+
const bc = activeTokens().textFaint; // border color
|
|
399
404
|
|
|
400
405
|
const makeBorder = (left: string, mid: string, right: string, horiz: string): Line => {
|
|
401
406
|
let s = ' '.repeat(indent) + left;
|
package/src/renderer/markdown.ts
CHANGED
|
@@ -2,8 +2,7 @@ import { type Line, type Cell, createStyledCell } from '@pellux/goodvibes-sdk/pl
|
|
|
2
2
|
import { UIFactory } from './ui-factory.ts';
|
|
3
3
|
import { renderCodeBlock } from './code-block.ts';
|
|
4
4
|
import { getDisplayWidth } from '../utils/terminal-width.ts';
|
|
5
|
-
import {
|
|
6
|
-
import { activeTheme } from './theme.ts';
|
|
5
|
+
import { activeTheme, activeTokens } from './theme.ts';
|
|
7
6
|
import { renderInlineMarkdown, type InlineToken } from './markdown-inline.ts';
|
|
8
7
|
import { isLikelyTableHeaderRow, isLikelyTableSeparatorRow, renderTable } from './markdown-table.ts';
|
|
9
8
|
|
|
@@ -25,6 +24,11 @@ export interface MarkdownRenderOptions {
|
|
|
25
24
|
* renderMarkdown - Parse markdown text into styled Line[].
|
|
26
25
|
* Thin wrapper over renderMarkdownTracked for callers that don't need code-block metadata.
|
|
27
26
|
*/
|
|
27
|
+
/** Column where markdown prose starts (the Measurements table's text column). */
|
|
28
|
+
export const MARKDOWN_TEXT_COL = 5;
|
|
29
|
+
/** Columns kept clear on the right: wrapped prose ends at width-6 or earlier. */
|
|
30
|
+
const MARKDOWN_RIGHT_GUTTER = 5;
|
|
31
|
+
|
|
28
32
|
export function renderMarkdown(text: string, width: number, options: MarkdownRenderOptions = {}): Line[] {
|
|
29
33
|
return renderMarkdownTracked(text, width, options).lines;
|
|
30
34
|
}
|
|
@@ -58,14 +62,39 @@ export function renderMarkdownTracked(
|
|
|
58
62
|
let codeBlockLines: string[] = [];
|
|
59
63
|
let fenceChar = '`';
|
|
60
64
|
let fenceIndent = 0;
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
// Prose starts at column 5 (the Measurements table's text column, the
|
|
66
|
+
// same column user-message and composer text use) and wraps before
|
|
67
|
+
// width-5, so the right edge never reaches a fill's padding band.
|
|
68
|
+
const indent = MARKDOWN_TEXT_COL;
|
|
69
|
+
const contentWidth = Math.max(1, width - MARKDOWN_RIGHT_GUTTER - indent);
|
|
70
|
+
const tk = activeTokens();
|
|
71
|
+
|
|
72
|
+
// Code blocks, tables, block quotes and rules get exactly one blank row
|
|
73
|
+
// above and below, whatever the source had. `openBlock` trims the blank
|
|
74
|
+
// rows already emitted to one; `blankAfter` owes one blank row to whatever
|
|
75
|
+
// comes next (and swallows extra source blanks until then).
|
|
76
|
+
const isBlankLine = (line: Line): boolean => line.every((c) => c.char === ' ' || c.char === '');
|
|
77
|
+
let blankAfter = false;
|
|
78
|
+
let inQuote = false;
|
|
79
|
+
const openBlock = (): void => {
|
|
80
|
+
blankAfter = false;
|
|
81
|
+
while (lines.length > 0 && isBlankLine(lines[lines.length - 1]!)) lines.pop();
|
|
82
|
+
if (lines.length > 0) lines.push(UIFactory.stringToLine('', width));
|
|
83
|
+
};
|
|
84
|
+
const closeBlock = (): void => { blankAfter = true; };
|
|
85
|
+
const beforeContent = (): void => {
|
|
86
|
+
if (blankAfter) {
|
|
87
|
+
lines.push(UIFactory.stringToLine('', width));
|
|
88
|
+
blankAfter = false;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
63
91
|
|
|
64
92
|
for (let i = 0; i < rawLines.length; i++) {
|
|
65
93
|
const raw = rawLines[i];
|
|
66
94
|
|
|
67
95
|
const fenceMatch = raw.match(/^(\s*)(```|~~~)\s*([\w-]*)/);
|
|
68
96
|
if (fenceMatch && !inCodeBlock) {
|
|
97
|
+
if (inQuote) { inQuote = false; closeBlock(); }
|
|
69
98
|
inCodeBlock = true;
|
|
70
99
|
fenceIndent = fenceMatch[1].length;
|
|
71
100
|
fenceChar = fenceMatch[2][0]; // '`' or '~'
|
|
@@ -77,6 +106,7 @@ export function renderMarkdownTracked(
|
|
|
77
106
|
// Close fence: same char, same or less indentation, at least 3 of that char
|
|
78
107
|
const closeFenceRe = new RegExp(`^\\s{0,${fenceIndent}}${fenceChar === '`' ? '```' : '~~~'}`);
|
|
79
108
|
if (closeFenceRe.test(raw)) {
|
|
109
|
+
openBlock();
|
|
80
110
|
const blockStart = lines.length;
|
|
81
111
|
const rendered = renderCodeBlock(codeBlockLines, codeBlockLang, width, {
|
|
82
112
|
showLineNumbers: options.codeBlockLineNumbers ?? true,
|
|
@@ -88,6 +118,7 @@ export function renderMarkdownTracked(
|
|
|
88
118
|
rawContent: codeBlockLines.join('\n'),
|
|
89
119
|
});
|
|
90
120
|
lines.push(...rendered);
|
|
121
|
+
closeBlock();
|
|
91
122
|
inCodeBlock = false;
|
|
92
123
|
codeBlockLang = '';
|
|
93
124
|
codeBlockLines = [];
|
|
@@ -100,25 +131,32 @@ export function renderMarkdownTracked(
|
|
|
100
131
|
}
|
|
101
132
|
|
|
102
133
|
if (raw.trim() === '') {
|
|
134
|
+
if (inQuote) { inQuote = false; closeBlock(); }
|
|
135
|
+
if (blankAfter) continue; // the owed blank row is emitted before the next content
|
|
103
136
|
lines.push(UIFactory.stringToLine('', width));
|
|
104
137
|
continue;
|
|
105
138
|
}
|
|
106
139
|
|
|
140
|
+
const isQuoteLine = /^> ?/.test(raw);
|
|
141
|
+
if (inQuote && !isQuoteLine) { inQuote = false; closeBlock(); }
|
|
142
|
+
const isRule = /^[-*_]{3,}$/.test(raw.trim());
|
|
143
|
+
const isTableStart = raw.includes('|') && i + 1 < rawLines.length && isLikelyTableHeaderRow(raw) && isLikelyTableSeparatorRow(rawLines[i + 1]);
|
|
144
|
+
if (!isQuoteLine && !isRule && !isTableStart) beforeContent();
|
|
145
|
+
|
|
107
146
|
const h3 = raw.match(/^### (.+)/);
|
|
108
147
|
const h2 = raw.match(/^## (.+)/);
|
|
109
148
|
const h1 = raw.match(/^# (.+)/);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
lines.push(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
lines.push(UIFactory.stringToLine(' '.repeat(indent) + h3[1], width, { fg: T.heading3, bold: true }));
|
|
149
|
+
// Headings keep their own case and carry no rule rows; the heading
|
|
150
|
+
// color and weight are what mark them.
|
|
151
|
+
const heading = h1?.[1] ?? h2?.[1] ?? h3?.[1];
|
|
152
|
+
if (heading !== undefined) {
|
|
153
|
+
// A heading is a block: one blank row above (unless it opens the
|
|
154
|
+
// message) and one below.
|
|
155
|
+
openBlock();
|
|
156
|
+
lines.push(...compositeInlineLine(' '.repeat(indent), renderInlineMarkdown(heading).map((tok) => (
|
|
157
|
+
tok.type === 'text' ? { ...tok, style: { ...tok.style, fg: tk.markdownHeading, bold: true } } : tok
|
|
158
|
+
)), width, {}, indent));
|
|
159
|
+
closeBlock();
|
|
122
160
|
continue;
|
|
123
161
|
}
|
|
124
162
|
|
|
@@ -131,8 +169,8 @@ export function renderMarkdownTracked(
|
|
|
131
169
|
const checkbox = checked ? '\u2611 ' : '\u2610 ';
|
|
132
170
|
const rendered = renderInlineMarkdown(taskMatch[3]);
|
|
133
171
|
const prefix = ' '.repeat(bulletX) + checkbox;
|
|
134
|
-
const style = checked ? { fg:
|
|
135
|
-
lines.push(...compositeInlineLine(prefix, rendered, width, { fg: checked ? T.checkboxChecked :
|
|
172
|
+
const style = checked ? { fg: activeTokens().textMuted, strikethrough: true } : {};
|
|
173
|
+
lines.push(...compositeInlineLine(prefix, rendered, width, { fg: checked ? T.checkboxChecked : activeTokens().text, ...style }, textStartX));
|
|
136
174
|
continue;
|
|
137
175
|
}
|
|
138
176
|
|
|
@@ -143,7 +181,7 @@ export function renderMarkdownTracked(
|
|
|
143
181
|
const textStartX = bulletX + 2;
|
|
144
182
|
const rendered = renderInlineMarkdown(ulMatch[2]);
|
|
145
183
|
const prefix = ' '.repeat(bulletX) + '• ';
|
|
146
|
-
lines.push(...compositeInlineLine(prefix, rendered, width, { fg:
|
|
184
|
+
lines.push(...compositeInlineLine(prefix, rendered, width, { fg: tk.textMuted, bold: false }, textStartX));
|
|
147
185
|
continue;
|
|
148
186
|
}
|
|
149
187
|
|
|
@@ -155,24 +193,32 @@ export function renderMarkdownTracked(
|
|
|
155
193
|
const textStartX = bulletX + numStr.length;
|
|
156
194
|
const rendered = renderInlineMarkdown(olMatch[3]);
|
|
157
195
|
const prefix = ' '.repeat(bulletX) + numStr;
|
|
158
|
-
lines.push(...compositeInlineLine(prefix, rendered, width, { fg:
|
|
196
|
+
lines.push(...compositeInlineLine(prefix, rendered, width, { fg: tk.textMuted, bold: false }, textStartX));
|
|
159
197
|
continue;
|
|
160
198
|
}
|
|
161
199
|
|
|
162
|
-
if (
|
|
163
|
-
|
|
200
|
+
if (isRule) {
|
|
201
|
+
openBlock();
|
|
202
|
+
lines.push(UIFactory.stringToLine(' '.repeat(indent) + '─'.repeat(contentWidth), width, { fg: tk.markdownHorizontalRule }));
|
|
203
|
+
closeBlock();
|
|
164
204
|
continue;
|
|
165
205
|
}
|
|
166
206
|
|
|
167
|
-
const bqMatch = raw.match(/^> (.*)/);
|
|
207
|
+
const bqMatch = raw.match(/^> ?(.*)/);
|
|
168
208
|
if (bqMatch) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
209
|
+
if (!inQuote) { openBlock(); inQuote = true; }
|
|
210
|
+
// The thin bar runs down every wrapped row of the quote, and the text
|
|
211
|
+
// keeps the quote color and italic on every row.
|
|
212
|
+
const rendered = renderInlineMarkdown(bqMatch[1]).map((tok) => (
|
|
213
|
+
tok.type === 'text' ? { ...tok, style: { fg: tk.markdownBlockQuote, italic: true, ...tok.style } } : tok
|
|
214
|
+
));
|
|
215
|
+
const prefix = ' '.repeat(indent) + '│ ';
|
|
216
|
+
lines.push(...compositeInlineLine(prefix, rendered, width, { fg: tk.markdownBlockQuote }, indent + 2, prefix));
|
|
172
217
|
continue;
|
|
173
218
|
}
|
|
174
219
|
|
|
175
|
-
if (
|
|
220
|
+
if (isTableStart) {
|
|
221
|
+
openBlock();
|
|
176
222
|
const tableRows: string[] = [];
|
|
177
223
|
let j = i;
|
|
178
224
|
while (j < rawLines.length && rawLines[j].includes('|')) {
|
|
@@ -180,7 +226,8 @@ export function renderMarkdownTracked(
|
|
|
180
226
|
j++;
|
|
181
227
|
}
|
|
182
228
|
i = j - 1;
|
|
183
|
-
lines.push(...renderTable(tableRows, width, indent));
|
|
229
|
+
lines.push(...renderTable(tableRows, width, indent, MARKDOWN_RIGHT_GUTTER));
|
|
230
|
+
closeBlock();
|
|
184
231
|
continue;
|
|
185
232
|
}
|
|
186
233
|
|
|
@@ -189,6 +236,7 @@ export function renderMarkdownTracked(
|
|
|
189
236
|
}
|
|
190
237
|
|
|
191
238
|
if (inCodeBlock && codeBlockLines.length > 0) {
|
|
239
|
+
openBlock();
|
|
192
240
|
const blockStart = lines.length;
|
|
193
241
|
const rendered = renderCodeBlock(codeBlockLines, codeBlockLang, width, {
|
|
194
242
|
showLineNumbers: options.codeBlockLineNumbers ?? true,
|
|
@@ -214,9 +262,12 @@ function compositeInlineLine(
|
|
|
214
262
|
tokens: InlineToken[],
|
|
215
263
|
width: number,
|
|
216
264
|
prefixStyle: Partial<Cell>,
|
|
217
|
-
textStartX: number
|
|
265
|
+
textStartX: number,
|
|
266
|
+
/** Drawn on every wrapped row after the first (a block quote's bar); blank when omitted. */
|
|
267
|
+
continuationPrefix?: string,
|
|
218
268
|
): Line[] {
|
|
219
269
|
const T = activeTheme();
|
|
270
|
+
const tk = activeTokens();
|
|
220
271
|
const lines: Line[] = [];
|
|
221
272
|
|
|
222
273
|
// Flatten tokens to [char, style] pairs
|
|
@@ -227,7 +278,7 @@ function compositeInlineLine(
|
|
|
227
278
|
if (token.type === 'text') {
|
|
228
279
|
for (const ch of token.text) chars.push({ char: ch, style: token.style });
|
|
229
280
|
} else if (token.type === 'code') {
|
|
230
|
-
for (const ch of token.text) chars.push({ char: ch, style: { fg:
|
|
281
|
+
for (const ch of token.text) chars.push({ char: ch, style: { fg: tk.markdownCode } });
|
|
231
282
|
} else if (token.type === 'link') {
|
|
232
283
|
// Resolve URL: if url is empty or relative, treat as text; if it's a file path, use file:// protocol
|
|
233
284
|
let resolvedUrl = token.url;
|
|
@@ -238,8 +289,8 @@ function compositeInlineLine(
|
|
|
238
289
|
}
|
|
239
290
|
}
|
|
240
291
|
|
|
241
|
-
//
|
|
242
|
-
const availW = width - textStartX;
|
|
292
|
+
// Wrap before the right gutter so text keeps clear of the screen edge.
|
|
293
|
+
const availW = width - MARKDOWN_RIGHT_GUTTER - textStartX;
|
|
243
294
|
if (availW <= 0) return lines;
|
|
244
295
|
|
|
245
296
|
let lineChars: StyledChar[] = [];
|
|
@@ -248,9 +299,10 @@ function compositeInlineLine(
|
|
|
248
299
|
const flushLine = (isFirst: boolean) => {
|
|
249
300
|
const line = new Array(width).fill(null).map(() => createStyledCell(' ')) as Cell[];
|
|
250
301
|
// Write prefix on first line
|
|
251
|
-
|
|
302
|
+
const rowPrefix = isFirst ? prefix : continuationPrefix;
|
|
303
|
+
if (rowPrefix !== undefined) {
|
|
252
304
|
let px = 0;
|
|
253
|
-
for (const ch of
|
|
305
|
+
for (const ch of rowPrefix) {
|
|
254
306
|
if (px >= width) break;
|
|
255
307
|
const cw = getDisplayWidth(ch);
|
|
256
308
|
line[px] = createStyledCell(ch, { fg: prefixStyle.fg, bg: prefixStyle.bg, bold: prefixStyle.bold, dim: prefixStyle.dim, underline: prefixStyle.underline, italic: prefixStyle.italic, strikethrough: prefixStyle.strikethrough });
|
|
@@ -313,9 +365,15 @@ function compositeInlineLine(
|
|
|
313
365
|
// Space: flush current word, then add the space
|
|
314
366
|
flushWord();
|
|
315
367
|
if (lineW + cw > availW && lineW > 0) {
|
|
368
|
+
// The row is full: this space is the break itself and is consumed by
|
|
369
|
+
// it, so the next row starts at the text column, not one to the right.
|
|
316
370
|
flushLine(isFirstLine);
|
|
317
371
|
isFirstLine = false;
|
|
372
|
+
continue;
|
|
318
373
|
}
|
|
374
|
+
// A wrapped row never opens with a break space (a run of spaces that
|
|
375
|
+
// straddles a break). The first row keeps its leading spaces.
|
|
376
|
+
if (!isFirstLine && lineW === 0) continue;
|
|
319
377
|
lineChars.push(sc);
|
|
320
378
|
lineW += cw;
|
|
321
379
|
} else {
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* renderMaskedEntryModal, the local-auth password prompt.
|
|
3
|
+
*
|
|
4
|
+
* ✦ Rotate password alice esc
|
|
5
|
+
*
|
|
6
|
+
* The password is typed here and never shown, stored in history, or
|
|
7
|
+
* written to the transcript.
|
|
8
|
+
*
|
|
9
|
+
* Password ●●●●●●●●▏
|
|
10
|
+
*
|
|
11
|
+
* ⏎ save esc cancel
|
|
12
|
+
*
|
|
13
|
+
* The password row shows one dot per character; the characters themselves
|
|
14
|
+
* never reach a cell. Adding a user first asks for the username (plain text,
|
|
15
|
+
* usernames are not secret) when none was given.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { activeTokens } from './theme.ts';
|
|
19
|
+
import { beginModal, finishModal, clipText, type KitHint, type SurfaceLayer } from './surface-kit.ts';
|
|
20
|
+
import { drawTextBlock, modalHeightFor, modalTextWidth, textBlockHeight } from './surface-kit-extra.ts';
|
|
21
|
+
|
|
22
|
+
/** What the renderer reads from the modal (never the password itself). */
|
|
23
|
+
export interface MaskedEntryView {
|
|
24
|
+
readonly kind: 'add-user' | 'rotate-password';
|
|
25
|
+
/** The username (typed or given). */
|
|
26
|
+
readonly username: string;
|
|
27
|
+
/** Which field is being typed. */
|
|
28
|
+
readonly step: 'username' | 'password';
|
|
29
|
+
/** Characters typed into the password field (only the count is drawn). */
|
|
30
|
+
readonly passwordLength: number;
|
|
31
|
+
/** An error from the last attempt, or null. */
|
|
32
|
+
readonly error: string | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const MODAL_WIDTH = 72;
|
|
36
|
+
|
|
37
|
+
function intro(view: MaskedEntryView): string {
|
|
38
|
+
return view.step === 'username'
|
|
39
|
+
? 'Type the username for the new local user, then its password on the next step.'
|
|
40
|
+
: 'The password is typed here and never shown, kept in history, or written to the transcript.';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function renderMaskedEntryModal(view: MaskedEntryView, screenWidth: number, screenHeight: number): SurfaceLayer {
|
|
44
|
+
const t = activeTokens();
|
|
45
|
+
const hints: KitHint[] = [['⏎', view.step === 'username' ? 'next' : 'save'], ['esc', 'cancel']];
|
|
46
|
+
const width = modalTextWidth(screenWidth, screenHeight, MODAL_WIDTH);
|
|
47
|
+
const introRows = textBlockHeight([{ text: intro(view) }], width);
|
|
48
|
+
const errorRows = view.error ? textBlockHeight([{ text: view.error }], width) + 1 : 0;
|
|
49
|
+
const fieldRows = view.kind === 'add-user' ? 2 : 1;
|
|
50
|
+
const height = modalHeightFor(screenWidth, screenHeight, { width: MODAL_WIDTH, hints }, introRows + 1 + fieldRows + errorRows);
|
|
51
|
+
const f = beginModal(screenWidth, screenHeight, {
|
|
52
|
+
title: view.kind === 'add-user' ? 'Add local user' : 'Rotate password',
|
|
53
|
+
sub: view.step === 'password' ? view.username : undefined,
|
|
54
|
+
width: MODAL_WIDTH,
|
|
55
|
+
height,
|
|
56
|
+
center: true,
|
|
57
|
+
cap: 'warning',
|
|
58
|
+
hints,
|
|
59
|
+
});
|
|
60
|
+
let y = drawTextBlock(f.canvas, f.l, f.top, width, [{ text: intro(view), style: { fg: t.textMuted } }], f.bottom) + 1;
|
|
61
|
+
const labelW = 11;
|
|
62
|
+
const valueX = f.l + labelW;
|
|
63
|
+
const room = Math.max(1, f.r - valueX);
|
|
64
|
+
if (view.kind === 'add-user' && y <= f.bottom) {
|
|
65
|
+
f.canvas.put(f.l, y, 'Username', { fg: t.textMuted });
|
|
66
|
+
const end = f.canvas.put(valueX, y, clipText(view.username, room), { fg: t.text });
|
|
67
|
+
if (view.step === 'username') f.canvas.put(end, y, '▏', { fg: t.brand });
|
|
68
|
+
y++;
|
|
69
|
+
}
|
|
70
|
+
if (view.step === 'password' && y <= f.bottom) {
|
|
71
|
+
f.canvas.put(f.l, y, 'Password', { fg: t.textMuted });
|
|
72
|
+
const end = f.canvas.put(valueX, y, '●'.repeat(Math.min(view.passwordLength, room)), { fg: t.text });
|
|
73
|
+
f.canvas.put(end, y, '▏', { fg: t.brand });
|
|
74
|
+
y++;
|
|
75
|
+
}
|
|
76
|
+
if (view.error) drawTextBlock(f.canvas, f.l, y + 1, width, [{ text: view.error, style: { fg: t.error } }], f.bottom);
|
|
77
|
+
return finishModal(f);
|
|
78
|
+
}
|