@pellux/goodvibes-tui 1.0.0 → 1.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +66 -127
  2. package/README.md +16 -7
  3. package/docs/foundation-artifacts/operator-contract.json +1 -1
  4. package/package.json +2 -2
  5. package/src/cli/ensure-goodvibes-gitignore.ts +32 -0
  6. package/src/cli/entrypoint.ts +2 -0
  7. package/src/core/conversation.ts +40 -0
  8. package/src/core/stream-event-wiring.ts +104 -2
  9. package/src/core/stream-stall-watchdog.ts +41 -17
  10. package/src/export/cost-utils.ts +90 -8
  11. package/src/input/command-registry.ts +16 -0
  12. package/src/input/commands/diff-runtime.ts +61 -30
  13. package/src/input/commands/session-content.ts +12 -0
  14. package/src/input/commands/session-workflow.ts +3 -0
  15. package/src/input/commands/share-runtime.ts +9 -2
  16. package/src/input/handler-content-actions.ts +22 -8
  17. package/src/input/handler-feed-routes.ts +63 -1
  18. package/src/input/handler-feed.ts +12 -0
  19. package/src/input/handler-interactions.ts +2 -0
  20. package/src/input/handler-types.ts +1 -0
  21. package/src/input/handler.ts +4 -0
  22. package/src/main.ts +17 -17
  23. package/src/panels/agent-inspector-shared.ts +33 -10
  24. package/src/panels/builtin/development.ts +1 -1
  25. package/src/panels/cockpit-read-model.ts +16 -11
  26. package/src/panels/cost-tracker-panel.ts +28 -5
  27. package/src/panels/debug-panel.ts +11 -5
  28. package/src/panels/diff-panel.ts +112 -18
  29. package/src/panels/docs-panel.ts +9 -0
  30. package/src/panels/file-explorer-panel.ts +9 -0
  31. package/src/panels/git-panel.ts +9 -9
  32. package/src/panels/knowledge-graph-panel.ts +9 -0
  33. package/src/panels/local-auth-panel.ts +10 -0
  34. package/src/panels/panel-list-panel.ts +9 -0
  35. package/src/panels/project-planning-panel.ts +10 -0
  36. package/src/panels/provider-health-tracker.ts +5 -1
  37. package/src/panels/provider-health-views.ts +8 -1
  38. package/src/panels/scrollable-list-panel.ts +9 -0
  39. package/src/panels/session-browser-panel.ts +9 -0
  40. package/src/panels/token-budget-panel.ts +5 -3
  41. package/src/panels/types.ts +13 -0
  42. package/src/panels/work-plan-panel.ts +10 -0
  43. package/src/renderer/agent-detail-modal.ts +5 -4
  44. package/src/renderer/process-modal.ts +17 -2
  45. package/src/renderer/shell-surface.ts +8 -1
  46. package/src/renderer/ui-factory.ts +91 -7
  47. package/src/runtime/bootstrap-command-context.ts +3 -0
  48. package/src/runtime/bootstrap-command-parts.ts +3 -1
  49. package/src/runtime/bootstrap-core.ts +5 -0
  50. package/src/runtime/bootstrap-hook-bridge.ts +5 -0
  51. package/src/runtime/bootstrap-shell.ts +12 -1
  52. package/src/version.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -4,138 +4,70 @@ All notable changes to GoodVibes TUI.
4
4
 
5
5
  ---
6
6
 
7
- ## [1.0.0] — 2026-07-03
7
+ ## [1.1.0] — 2026-07-03
8
8
 
9
9
  ### Changes
10
- - d26fc23f ci: drop temporary renderer-excellence branch trigger after merge
11
- - 532c900b integrate renderer-excellence: renderer performance and correctness effort (v1.0.0 scope)
12
- - cdf5931d fix(input): normalize CR line endings from bracketed pastes
13
- - 1318959f chore(perf): re-capture baseline reference medians on a quiet box
14
- - 946a2c83 integrate WO-210 into renderer-excellence
15
- - 209ade5f perf(budgets): ratchet transcript.append_one 20ms->6ms; streaming tail proven non-material (WO-210)
16
- - c7e445c2 integrate WO-209 into renderer-excellence
17
- - df4d8d03 integrate WO-208 into renderer-excellence
18
- - f5476715 perf(conversation): per-message Line[] cache append-one 45ms→0.9ms (WO-209)
19
- - 76539cd8 perf(renderer): WO-208 coalesce main.ts render() fan-out into one same-tick composite
20
- - 204342c4 refactor(renderer): delete the six dead exports parked during the R2 literal-ownership split
21
- - 9c1dbdae integrate WO-207 into renderer-excellence
22
- - ae660b2a integrate WO-206 into renderer-excellence
23
- - 6acd74f6 integrate WO-205 into renderer-excellence
24
- - c6eb004c feat(architecture): WO-206 no-unused-exports rule for src/renderer
25
- - 8997719b feat(renderer): WO-207 palette unification across the renderer layer
26
- - 3ec18bb1 refactor(renderer): WO-205 dedup + dead code + honest picker
27
- - 43eb8165 ci: run full CI on renderer-excellence pushes for the v1.0.0 effort
28
- - 6e483f55 fix(renderer): diff panel keeps its shipped colors — the panels are the reference, not diff-view
29
- - 50755108 integrate WO-204 into renderer-excellence
30
- - 17425f6a integrate WO-203 into renderer-excellence
31
- - 68bc13c3 fix(renderer): WO-203 verified correctness fixes
32
- - 721f4d91 feat(renderer): WO-204 — restore conversation diff rendering, align diff surface colors
33
- - c5aff410 integrate WO-202 into renderer-excellence
34
- - 0f03a124 integrate WO-201 into renderer-excellence
35
- - e5ba2978 test(renderer): WO-201 golden contract expansion — splash, transcript scenes, all overlays
36
- - 210d5814 perf(scripts): WO-202 line-production benchmarks above the compositor
37
- - bf6459af fix(tests): skills delete test polls the rescanned state, not just file removal
10
+ - e3d20840 chore(artifacts): regenerate foundation artifacts against SDK 0.36.0
11
+ - 2dab222b integrate wave0: trust repairs (v1.1.0 scope)
12
+ - d3116e0a chore(deps): bump @pellux/goodvibes-sdk to 0.36.0 (Wave-0 trust repairs)
13
+ - 08caa3a3 test(tools): pin the SDK 0.36 tail-preserving truncation contract
14
+ - e1e124b7 chore: trim stall-gate comment to keep main.ts under the 800-line cap
15
+ - 57a9c5bd fix(stream): suppress stall indicator false-positive during tool execution
16
+ - 96225275 fix(test-runner): fence git discovery at the per-file temp root
17
+ - efbfc8de chore(wave0): trim main.ts/git-panel.ts back to the 800-line cap after wave merges
18
+ - e7d166e4 integrate WO-315 (W0.10 cost-truth unpriced markers) into wave0
19
+ - 83d77e50 integrate WO-314 (W0.9 per-agent stats + footer usage hydration) into wave0
20
+ - 5801ced8 integrate WO-313 (W0.7 stream stall honesty, TUI half) into wave0
21
+ - 5abfed54 integrate WO-312 (W0.4/5/6/11/12 diff+slash+ctrlc cluster) into wave0
22
+ - 40fdee43 integrate WO-311 (W0.8 panel focus/burst/indicator) into wave0
23
+ - 8e61b248 fix(wo312): Wave 0 TUI trust-repair cluster (W0.4/W0.5/W0.6/W0.11/W0.12)
24
+ - 210b4b80 fix(W0.9): honest per-agent usage display + footer token hydration after resume
25
+ - b791dbd2 fix(cost): catalog-backed pricing with honest unpriced state (WO-315/W0.10)
26
+ - 1af7105e fix(input,renderer): panel entry points reachable + burst guard + focus indicator (W0.8)
27
+ - 000250a1 fix(streaming): honest stall indicator + mid-stream watchdog re-arm (W0.7)
28
+ - 74141d53 feat(dev): local SDK overlay for rapid iteration, with release-blocking gates
29
+ - 69c282a1 docs: reconcile documentation with the shipped 0.29.0 and 1.0.0 releases
30
+
31
+ ## [1.0.0] 2026-07-03
32
+
33
+ The renderer milestone: the full rendering pipeline measured, cached, gated, and visually pinned — plus a fix for a long-shipped multi-line paste defect. 1.0.0 marks the stability posture promised since early development: semver, with incompatible changes landing only in major releases.
34
+
35
+ ### Features
36
+ - Restored conversation diff rendering: expanded diff-shaped tool results render with green add / red delete / blue hunk-header gutters through the same color tokens as the diff panel and the git panel's inline diff, so diffs look identical on every surface. This path had been unwired since v0.9.6; the new no-unused-exports gate exists so nothing goes silently dead again.
37
+ - Unified the renderer palette onto the `UI_TONES` token set — byte-identical color swaps; genuinely distinct palettes (agent-detail neon theme, code-block VS-Code-Dark+ fallback) are preserved exactly as named accents.
38
+ - Long-conversation append performance: a per-message line cache renders only the new message on append (~0.9ms median) instead of rebuilding the whole transcript (~45.5ms and ~71MB of throwaway allocations at 1,000 messages).
39
+ - Render coalescing: same-tick render requests collapse into one composite — streaming bursts previously triggered up to 7 redundant composites per tick.
40
+ - Honest profile picker: dropped the hardcoded fake "Settings" column that showed identical text for every profile; adopted proportional column widths.
41
+
42
+ ### Fixes
43
+ - Multi-line paste works: terminals transmit the line breaks inside a bracketed paste as carriage returns, which the composer inserted verbatim — the input box never grew, ghost text overwrote the prompt marker, and edits acted on an invisible layout. Line endings are now normalized at the paste boundary, and history entries persisted before the fix are laundered on load. (A shipped defect since bracketed paste landed, not a regression from this effort.)
44
+ - Seven rendering-correctness fixes at narrow/short terminal sizes: real viewport height in the agent-detail modal, overlay width clamped ahead of its 20/24-column floors, consistent compact-footer height math, and wide-glyph (CJK/fullwidth) boundary fixes — each landed with a test that failed before the fix.
45
+
46
+ ### Internal
47
+ - Golden-frame harness expanded to 35 committed byte-exact reference frames: the splash at 60/100/140 columns, transcript scenes (plain and diff tool results collapsed/expanded, code blocks on both tokenizer paths, thinking blocks, streaming partials), and every overlay at normal and hostile sizes. Splash goldens render with a pinned version fixture so release version bumps can never break them (the first v1.0.0 validate run failed on exactly that).
48
+ - Performance baseline harness with enforced CI budgets (`scripts/perf-baseline.json`, `perf:check`): startup load, frame-composite p95/p99, and line-production budgets for transcript build/append/resize, panel build, markdown, code blocks, and overlay open. `transcript.append_one` ratcheted 20ms → 6ms.
49
+ - No-unused-exports architecture gate for the renderer layer; three dead renderer modules deleted (`progress.ts`, `file-tree.ts`, `status-token.ts`).
50
+ - Splash art and its wide/narrow glyph typography byte-identical throughout — enforced by an empty-diff check every wave plus the splash goldens.
38
51
 
39
52
  ## [0.29.0] — 2026-07-03
40
53
 
41
- ### Changes
42
- - 2842a791 perf(ci): parallelize the per-file test runner; raise the test job timeout
43
- - 218bb217 merge: panel-system remediation — 38 work orders, consoles not signposts (0.29.0)
44
- - a37c028e fix(input): WO-160 smoke findings overlay write-back clobber, /ops view parity, panel-list honesty, modal viewport
45
- - 5741f633 fix(tests): isolate GitPanel non-repo test from the runner's in-tree TMPDIR
46
- - 6eaea7c4 fix(panels): WO-160 consistency sweep alias tests, action-substitute signposts
47
- - 42164e34 fix(panels): cockpit c/i leave the key unconsumed on terminal or absent roster entries
48
- - 10247db1 feat(input): make Ctrl+O a real keybind for the Ops Control panel
49
- - ac598536 refactor(panels): route selected-row reads through getSelectedItem and ban raw [this.selectedIndex]
50
- - 34bac3f0 chore(panels): tighten hex-literal baseline to exact actual counts
51
- - 3d2caf90 integrate WO-153 into panel-ux-remediation
52
- - 1f0ed5ca integrate WO-152 into panel-ux-remediation
53
- - 8b6fbce6 integrate WO-151 into panel-ux-remediation
54
- - ce0b7cb8 fix(panels): closure-audit findings — incident/settings-sync filtered desyncs, subscription start signposts
55
- - ba7db35e feat(panels): WO-152 registry taxonomy, icons, always-register, lifecycle flags
56
- - 7cbdd4ec feat(panels): WO-153 filter-model convergence onto ScrollableListPanel
57
- - 40cb9153 feat(panels): WO-151 keybindings, help discoverability, one hint grammar
58
- - 782e7752 fix(panels): services test/detail act on the filtered selection
59
- - e8783a89 integrate WO-154 into panel-ux-remediation
60
- - cdb399b9 integrate WO-140 into panel-ux-remediation
61
- - 943089ea integrate WO-139 into panel-ux-remediation
62
- - d527c223 integrate WO-141 into panel-ux-remediation
63
- - a65ee307 integrate WO-150 into panel-ux-remediation
64
- - a73e874b feat(panels): WO-140 planning cluster polish (wrfc/project-planning/work-plan)
65
- - e1921ab8 feat(panels): WO-141 keep-panel hardening + residual signpost sweep
66
- - 8b018746 feat(panels): WO-139 provider-cost leftovers — subscription/local-auth/cost-tracker
67
- - da7b9550 feat(panels): WO-150 unify focus ownership in PanelManager
68
- - 4c569ae8 feat(shell): WO-154 main-UX shell splash, footer cost/compact, overlay posture
69
- - 7218349c fix(panels): wave 3a review findings — filtered-selection desyncs, residual signposts, dead constants
70
- - 1cc9f195 integrate WO-138 into panel-ux-remediation
71
- - 179a9a91 integrate WO-137 into panel-ux-remediation
72
- - 4df3036e integrate WO-136 into panel-ux-remediation
73
- - 20533671 integrate WO-135 into panel-ux-remediation
74
- - 2f116f82 integrate WO-134 into panel-ux-remediation
75
- - 595df267 integrate WO-133 into panel-ux-remediation
76
- - 490dc881 feat(panels): WO-137 diagnostics trio — security, debug, system-messages
77
- - 066fd91a feat(panels): WO-138 session-comm quad real actions (remote/communication/routes/qr)
78
- - a9a1ebe0 feat(panels): WO-134 Plugins + Hooks control rooms
79
- - e4380896 feat(panels): WO-136 Docs + Intelligence live actions
80
- - 8f6a3f45 feat(panels): WO-133 explorer + preview lazy loading, git decorations, diff/reload keys
81
- - 744dc460 feat(panels): WO-135 marketplace + skills real actions
82
- - a04c86e9 fix(panels): reserve summary row inside stacked scrollable section budgets
83
- - b2a43d9b fix(panels): reserve a row for the window-summary inside the scrollable budget
84
- - acb3e3b5 fix(panels): bind w to dispatch teamwork review from Tasks; stop git panel swallowing i on loaded repos
85
- - 4bc04330 integrate WO-130 into panel-ux-remediation
86
- - e15c010b integrate WO-128 into panel-ux-remediation
87
- - 3020d2e1 integrate WO-127 into panel-ux-remediation
88
- - 1a0c0d76 integrate WO-132 into panel-ux-remediation
89
- - 79ac40bd integrate WO-131 into panel-ux-remediation
90
- - 61092aa0 integrate WO-126 into panel-ux-remediation
91
- - f83d6820 feat(panels): WO-131 agent-runtime trio — tools + tasks + orchestration
92
- - 8dafc5c0 feat(panels): WO-132 Git + Diff consoles — stage/unstage/commit, explicit init confirm, self-loading diff panel
93
- - 24c5127e feat(panels): WO-128 plan dashboard revival
94
- - 2c8e8d1f feat(panels): WO-126 symbol outline on tree-sitter
95
- - 2d424e1c feat(panels): WO-130 Cockpit operator polish
96
- - 66c0deeb feat(panels): WO-127 eval console — baseline seed, live run, regressions
97
- - 1a53ef2c fix(panels): drop redundant slash-command guidance from knowledge review mode
98
- - 16ba104d fix(tests): operator-surfaces gate verifies forensics alias resolves to incident
99
- - a5b8f4e9 integrate FOLLOWUPS into panel-ux-remediation
100
- - f60988eb integrate WO-125 into panel-ux-remediation
101
- - 39996bfd integrate WO-124 into panel-ux-remediation
102
- - 78fb4916 integrate WO-123 into panel-ux-remediation
103
- - e5294103 integrate WO-122 into panel-ux-remediation
104
- - 371f4d60 integrate WO-121 into panel-ux-remediation
105
- - 1ff1e0ae integrate WO-120 into panel-ux-remediation
106
- - 933b09a3 feat(panels): WO-120 register ops-control panel and wire operator actions
107
- - 39bfc1a1 feat(panels): WO-125 wire worktree and sandbox lifecycle verbs to their registries
108
- - 4992fd03 feat(panels): WO-121 control-plane approvals console
109
- - ea1c47b0 feat(panels): WO-123 knowledge console replaces static command catalogue
110
- - 91273a60 feat(panels): WO-124 settings-sync console — resolve conflicts in-panel, Tab browse modes
111
- - 74f5d39d feat(panels): WO-122 policy governance console actions and depth
112
- - f4679a7e fix(panels): broaden hex-literal ratchet to 3/6/8-digit forms, clear remaining raw hex in fullscreen-primitives and diff-panel
113
- - 6e1b814c fix(panels): forensics resolves as alias to incident, not duplicate registration
114
- - 6a0f4405 fix(tests): operator-surfaces gate asserts merged provider-health id instead of retired providers id
115
- - 09d81371 fix(tests): retarget legacy-turn-bus ban from deleted context-visualizer-panel to its WO-113 successor token-budget-panel
116
- - e835a821 integrate WO-114 into panel-ux-remediation
117
- - 433b4c25 integrate WO-113 into panel-ux-remediation
118
- - 70466337 integrate WO-112 into panel-ux-remediation
119
- - a1ebf5da fix(tests): contract module count 30 after WO-110+WO-111 both deleted a module
120
- - fd25fb19 integrate WO-111 into panel-ux-remediation
121
- - 0d72a2ef integrate WO-110 into panel-ux-remediation
122
- - 0a344b60 WO-112: Provider console merge: providers + accounts into provider-health
123
- - d4c7c8b4 WO-111: Automation console merge: schedule + watchers into automation
124
- - 1fc22d27 WO-110: Agent console merge: agent-logs into inspector
125
- - 0d4858ff WO-114: Incident console merge: forensics into incident
126
- - a4cdfb3c WO-113: Context/token merge: context into tokens
127
- - fd8e84ef integrate WO-002 into panel-ux-remediation
128
- - 1a85ece3 WO-002: Panel palette and chrome sweep (extendPalette everywhere)
129
- - 1cd556da integrate WO-006 into panel-ux-remediation
130
- - d6effd51 integrate WO-005 into panel-ux-remediation
131
- - d7735d20 integrate WO-004 into panel-ux-remediation
132
- - 9b1de69a integrate WO-003 into panel-ux-remediation
133
- - e149323f integrate WO-001 into panel-ux-remediation
134
- - 45e90fa7 WO-006: Shared-test decongestion: per-panel spec modules
135
- - 2d7d5777 WO-001: Theme-token backbone + architecture-gate hex enforcement
136
- - 207cd46b WO-003: Action-callback plumbing pattern + ConfirmState generalization
137
- - abf6771b WO-005: session-maintenance evaluator dedup
138
- - 4fe16129 WO-004: Dead code and dead keybinding removal
54
+ The panel remediation release: a 27-finding audit closed across 38 work orders — panels became working consoles instead of slash-command signposts.
55
+
56
+ ### Features
57
+ - Panels perform their actions directly: ops task/agent interventions, approval resolution, policy simulate/preflight/promote/rollback, knowledge review, settings-sync conflict resolution, worktree/sandbox lifecycle, and git operations run from the panel — keyed, with confirm gates on destructive actions — instead of printing "run /some-command".
58
+ - `Ctrl+O` opens the Ops Control panel as a real, rebindable keybinding.
59
+ - Five panel merges with the old ids preserved as aliases: agent-logs→inspector, context→tokens, providers+accounts→provider-health, forensics→incident.
60
+
61
+ ### Fixes
62
+ - `/shortcuts` and `/commands` overlays actually display: the token-feed pipeline was reverting overlay flags that command handlers set mid-feed, so both commands silently did nothing. Fixed with change-detecting write-back plus a regression test.
63
+ - Every Alt-modifier keybinding works again after a tokenizer/matcher mismatch left them all dead at runtime (Alt+B/Alt+F word navigation, Alt+D and Alt+Y kill-ring bindings).
64
+ - A stale-selection bug class across the Marketplace, Services, Incident, and Settings-sync panels: after filtering, an action could silently apply to the wrong row. Fixed via a shared `getSelectedItem()` accessor plus an architecture rule banning raw selected-index access.
65
+ - Panel focus ownership centralized in PanelManager with a self-healing invariant, replacing scattered per-panel focus bookkeeping.
66
+
67
+ ### Internal
68
+ - Parallel per-file test runner (`scripts/run-tests.ts`): min(8, cores−1) workers with isolated TMPDIRs — the suite dropped from ~10.5 minutes to under 2 locally; the CI test-job cap raised to 20 minutes as a hang backstop.
69
+ - New architecture gates: a raw-hex-literal ratchet (424 → 262 literals, exact per-file baseline), the selected-index rule, registry-time icon uniqueness, panel contract-module parity, an 800-line source-file cap, and a focus-invariant test.
70
+ - +278 tests across the remediation.
139
71
 
140
72
  ## [0.28.0] — 2026-06-30
141
73
 
@@ -185,6 +117,13 @@ Full deep-review audit of the TUI (33 findings fixed, each reviewed to a score o
185
117
  - 97f74119 chore: upgrade @pellux/goodvibes-sdk to 0.34.0
186
118
  - 6de2627a revert: remove daemon control-plane buildout — duplicated @pellux/goodvibes-sdk 0.34.0
187
119
 
120
+ ## [0.25.0] — 2026-06-21
121
+
122
+ ### Changes
123
+ - 5353a7d9 chore: release v0.25.0
124
+ - a8a4cd4f fix(daemon/remote): make runProcess timeout resolve promptly under orphaned children
125
+ - c00461be feat(daemon): implement control-plane operator surfaces for channels, email, calendar, and remote execution
126
+
188
127
  ## [0.24.1] — 2026-06-17
189
128
 
190
129
  ### Changes
package/README.md CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  [![CI](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml/badge.svg)](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
- [![Version](https://img.shields.io/badge/version-1.0.0-blue.svg)](https://github.com/mgd34msu/goodvibes-tui)
5
+ [![Version](https://img.shields.io/badge/version-1.1.0-blue.svg)](https://github.com/mgd34msu/goodvibes-tui)
6
6
 
7
7
  A terminal-native AI coding, operations, automation, knowledge, and integration console with a typed runtime, omnichannel surfaces, structured memory/knowledge, and a raw ANSI renderer.
8
8
 
9
- > ⚠️ **Active early development pre-1.0.** This project is under active early development. CLI flags, config keys, slash commands, key bindings, daemon routes, and on-disk layouts can and do change quickly sometimes across patch releases. There are no legacy/compat shims. Documentation always describes the **current** behavior, not historical behavior. When 1.0.0 ships the project freezes to enterprise-grade stability guarantees (semver, deprecation windows, migration guides). Until then: pin exact versions and read `CHANGELOG.md` before upgrading.
9
+ > **1.0.0 shipped 2026-07-03.** From 1.0.0 the project follows the stability posture promised for this milestone: semver, with incompatible changes to CLI flags, config keys, slash commands, key bindings, daemon routes, and on-disk layouts landing only in major releases, and deprecations noted in `CHANGELOG.md` first. Documentation always describes the **current** behavior, not historical behavior.
10
10
 
11
11
  <!-- screenshot -->
12
12
 
@@ -146,6 +146,7 @@ The TUI now consumes the extracted `@pellux/goodvibes-sdk` (`0.34.0`) platform l
146
146
  - Shared panel workspace layout budgeting with renderer-owned visible-row budgets
147
147
  - Copy/selection logic that strips decorative gutters and visual scaffolding from clipboard output
148
148
  - Line-count indicator rendered right-aligned inside the prompt border on multi-line input (footer height stays stable regardless of prompt line count)
149
+ - Multi-line clipboard paste handled at the input boundary: bracketed-paste carriage-return line separators are normalized to newlines, the composer grows to the pasted line count, and pastes longer than 8 lines collapse to a compact paste marker
149
150
 
150
151
  ### Conversation And Transcript Workflow
151
152
  - Markdown rendering, syntax highlighting, inline diffs, collapsible blocks, bookmarks, block copy, and block save
@@ -1318,7 +1319,8 @@ Those pieces cover conversation-noise routing, panel-health/performance budgets,
1318
1319
  | `/diff [target]` | `/d` | Show unified diff: session, head, working, staged, or a git ref |
1319
1320
  | `/mcp [add\|remove\|reload\|tools]` | — | Manage MCP servers at runtime and list connected tools |
1320
1321
  | `/help [command]` | `/h`, `/?` | Show available commands and keyboard shortcuts |
1321
- | `/quit` | `/q`, `/:q` | Exit the application |
1322
+ | `/quit` | `/:q` | Exit the application |
1323
+ | `/wq` | `/:wq` | Commit all git changes, then exit |
1322
1324
 
1323
1325
  > **Tip:** Use the `/add-provider` skill for interactive guided provider setup with smart defaults for popular providers.
1324
1326
  >
@@ -1386,9 +1388,13 @@ All shortcuts are customizable via `~/.goodvibes/tui/keybindings.json`. Use `/ke
1386
1388
  | Key | Action |
1387
1389
  |-----|--------|
1388
1390
  | `Ctrl+P` | Toggle panel sidebar |
1389
- | `Ctrl+}` | Next panel tab |
1390
- | `Ctrl+~` | Previous panel tab |
1391
- | `,` / `.` | Cycle panel tabs (when panel focused) |
1391
+ | `Ctrl+]` | Next panel tab |
1392
+ | `Ctrl+[` | Previous panel tab |
1393
+ | `Alt+1`…`Alt+9` | Jump to panel tab 1–9 |
1394
+ | `Ctrl+X` | Close the focused panel |
1395
+ | `Ctrl+Shift+X` | Close all panels |
1396
+ | `Ctrl+O` | Open the Ops Control panel |
1397
+ | `Ctrl+G` | Toggle focus between split panes |
1392
1398
 
1393
1399
  ### System
1394
1400
 
@@ -1652,6 +1658,9 @@ src/
1652
1658
  - **Backend-first external surface** — the daemon/control plane exposes typed HTTP/gateway methods for knowledge, artifacts, media, search, channels, and remote peers so future clients do not have to reimplement runtime logic
1653
1659
  - **Plugin system** — manifest.json + sandboxed API surface for commands, providers, tools, gateway methods, channels, embeddings, voice, media, and search
1654
1660
  - **Crash recovery** — periodic JSONL snapshots with recovery prompt on next startup; an fsync-per-record append-only transcript journal between snapshots is replayed at every resume seam (command resume, Ctrl+R crash recovery, panel resume) for SIGKILL-proof durability
1661
+ - **Golden-frame regression harness** — 35 committed byte-exact reference frames (including the splash at three widths) pin the renderer's output; any unintended visual change fails CI
1662
+ - **Render coalescing** — same-tick render requests collapse into one composite via `src/runtime/render-scheduler.ts`, so a streaming burst pays for one frame instead of several
1663
+ - **Per-message line cache** — `src/core/conversation-line-cache.ts` renders only the appended message on transcript growth (~0.9ms) instead of rebuilding the whole conversation (~45ms at 1,000 messages)
1655
1664
 
1656
1665
  ---
1657
1666
 
@@ -1669,7 +1678,7 @@ bun run dev
1669
1678
  bun test
1670
1679
  ```
1671
1680
 
1672
- 8,978 tests across contract, security, release gate, runtime, renderer, panel, integration, and UX anti-regression suites. Performance budget gate runs as part of CI — the build fails if any of the 5 perf budgets (store update latency, event dispatch latency, tool execution overhead, compaction duration, startup time) are exceeded.
1681
+ The suite spans 623 test files (contract, security, release gate, runtime, renderer, panel, integration, and UX anti-regression), run through a parallel per-file runner (`scripts/run-tests.ts`). Performance budgets run as a CI gate — the build fails if startup load, frame-composite p95/p99, or any line-production budget (transcript build/append/resize, panel two-pane build, markdown render, code-block render on both tokenizer paths, overlay open) exceeds the ratcheted values in `scripts/perf-baseline.json`. Architecture gates run alongside: import-cycle and layer rules, an 800-line cap on non-test source files, a raw-hex-literal ratchet, icon-uniqueness and panel contract-parity checks, and a no-unused-exports rule for the renderer layer (`scripts/check-architecture.ts`, `scripts/no-unused-exports-rule.ts`).
1673
1682
 
1674
1683
  ### Build standalone binary
1675
1684
 
@@ -3,7 +3,7 @@
3
3
  "product": {
4
4
  "id": "goodvibes",
5
5
  "surface": "operator",
6
- "version": "0.35.0"
6
+ "version": "0.36.0"
7
7
  },
8
8
  "auth": {
9
9
  "modes": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-tui",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Terminal-native GoodVibes product for coding, operations, automation, knowledge, channels, and daemon-backed control-plane workflows.",
5
5
  "type": "module",
6
6
  "main": "src/main.ts",
@@ -99,7 +99,7 @@
99
99
  "@anthropic-ai/vertex-sdk": "^0.16.0",
100
100
  "@ast-grep/napi": "^0.42.0",
101
101
  "@aws/bedrock-token-generator": "^1.1.0",
102
- "@pellux/goodvibes-sdk": "0.35.0",
102
+ "@pellux/goodvibes-sdk": "0.36.0",
103
103
  "bash-language-server": "^5.6.0",
104
104
  "fuse.js": "^7.1.0",
105
105
  "graphql": "^16.13.2",
@@ -0,0 +1,32 @@
1
+ import { existsSync, readFileSync, appendFileSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ /**
5
+ * The TUI's own `.goodvibes/` directory (logs, overflow buffers, exec output,
6
+ * cache, session state, etc.) is transient scratch space, not project source —
7
+ * but nothing else in the codebase excludes it from the *project's* git
8
+ * history. Any `git add -A` (write-quit's auto-commit, a WRFC chain's own
9
+ * commit, etc.) sweeps the whole tree in unless the project's .gitignore
10
+ * already happens to exclude it. Idempotent and append-only: never rewrites
11
+ * or reorders a project's existing .gitignore, only adds the rule if it is
12
+ * verifiably absent, and never touches a directory that isn't a git repo.
13
+ */
14
+ const GOODVIBES_IGNORE_PATTERN = /(^|\n)[ \t]*\/?\.goodvibes\/?\*?[ \t]*(\n|$)/;
15
+
16
+ export function ensureGoodvibesGitignore(projectRoot: string): void {
17
+ try {
18
+ if (!existsSync(join(projectRoot, '.git'))) return; // only matters for git projects
19
+ const gitignorePath = join(projectRoot, '.gitignore');
20
+ const existing = existsSync(gitignorePath) ? readFileSync(gitignorePath, 'utf-8') : '';
21
+ if (GOODVIBES_IGNORE_PATTERN.test(existing)) return; // already excluded
22
+ const rule = '# goodvibes-tui transient state\n.goodvibes/\n';
23
+ if (existing.length === 0) {
24
+ writeFileSync(gitignorePath, rule);
25
+ return;
26
+ }
27
+ const separator = existing.endsWith('\n') ? '\n' : '\n\n';
28
+ appendFileSync(gitignorePath, separator + rule);
29
+ } catch {
30
+ // Best-effort only — never block startup on a gitignore write failure.
31
+ }
32
+ }
@@ -22,6 +22,7 @@ import {
22
22
  renderOnboardingCliStatus,
23
23
  } from './index.ts';
24
24
  import { buildCliServicePosture } from './service-posture.ts';
25
+ import { ensureGoodvibesGitignore } from './ensure-goodvibes-gitignore.ts';
25
26
 
26
27
  type ShellEntrypointOwnership = {
27
28
  readonly workingDirectory: string;
@@ -95,6 +96,7 @@ export async function prepareShellCliRuntime(
95
96
  homeDirectory: bootstrapHomeDirectory,
96
97
  } = resolveShellEntrypointOwnership(roots, cli.flags.workingDir ?? (cli.command === 'tui' ? cli.commandArgs[0] : undefined));
97
98
  configureActivityLogger(join(bootstrapWorkingDir, '.goodvibes', 'logs'));
99
+ ensureGoodvibesGitignore(bootstrapWorkingDir);
98
100
  const configManager = new ConfigManager({
99
101
  workingDir: bootstrapWorkingDir,
100
102
  homeDir: bootstrapHomeDirectory,
@@ -49,6 +49,46 @@ export type { BlockMeta };
49
49
  import type { ConversationMessageSnapshot } from '@pellux/goodvibes-sdk/platform/core';
50
50
  type Message = ConversationMessageSnapshot;
51
51
 
52
+ /** Matches Orchestrator.usage's shape (SDK platform/core/orchestrator.ts). Not
53
+ * exported by the SDK as a standalone type, so mirrored here. */
54
+ export interface OrchestratorUsageTotals {
55
+ input: number;
56
+ output: number;
57
+ cacheRead: number;
58
+ cacheWrite: number;
59
+ }
60
+
61
+ /**
62
+ * sumConversationUsage - Fold every assistant message's per-turn usage into
63
+ * running totals, plus the *last* assistant message's own input-token figure
64
+ * (context-window occupancy — not a running sum).
65
+ *
66
+ * TUI-side counterpart to SDK Orchestrator.usage: after a session resume
67
+ * replays historical messages into a freshly-constructed Orchestrator (whose
68
+ * `usage` starts at all zeros — bootstrap.ts always constructs a fresh
69
+ * instance, see W0.9), this lets the caller hydrate the footer's token
70
+ * counters from messages that already carry real usage data instead of
71
+ * waiting for the first new turn to populate them.
72
+ */
73
+ export function sumConversationUsage(
74
+ messages: readonly ConversationMessageSnapshot[],
75
+ ): { usage: OrchestratorUsageTotals; lastInputTokens: number } {
76
+ const usage: OrchestratorUsageTotals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
77
+ let lastInputTokens = 0;
78
+ for (const message of messages) {
79
+ if (message.role !== 'assistant' || !message.usage) continue;
80
+ usage.input += message.usage.inputTokens;
81
+ usage.output += message.usage.outputTokens;
82
+ usage.cacheRead += message.usage.cacheReadTokens ?? 0;
83
+ usage.cacheWrite += message.usage.cacheWriteTokens ?? 0;
84
+ // Context-window occupancy tracks the most recent turn only, not a sum.
85
+ lastInputTokens = message.usage.inputTokens
86
+ + (message.usage.cacheReadTokens ?? 0)
87
+ + (message.usage.cacheWriteTokens ?? 0);
88
+ }
89
+ return { usage, lastInputTokens };
90
+ }
91
+
52
92
  export class ConversationManager extends SdkConversationManager {
53
93
  public history = new InfiniteBuffer();
54
94
  private _getWidth: () => number;
@@ -23,6 +23,29 @@ export interface StreamMetrics {
23
23
  activeToolStartedAtMs: number | undefined;
24
24
  /** Name of the currently executing tool; cleared when execution completes. */
25
25
  activeToolName: string | undefined;
26
+ /**
27
+ * Epoch ms of the most recent STREAM_START or STREAM_DELTA; undefined when
28
+ * idle (no turn in flight). Read every render frame — not just on the
29
+ * watchdog's one-shot hint — so "ms since last byte" can be computed even
30
+ * when no new SDK event has arrived at all (a no-delta stall watchdog for
31
+ * the render loop itself, independent of the low-priority system message).
32
+ */
33
+ lastDeltaAtMs: number | undefined;
34
+ /**
35
+ * 1-based count of stall episodes the watchdog has fired for the current
36
+ * turn; 0 = no stall yet. Increments each time a fresh silence (after a
37
+ * recovery) crosses the stall threshold.
38
+ */
39
+ stallEpisode: number;
40
+ /**
41
+ * Populated from the SDK's STREAM_RETRY event when present. SDK 0.35.0 (the
42
+ * pinned dependency) has no such event on the TurnEvent union yet — these
43
+ * fields are consumed structurally (see looseTurnsFeed below) and stay
44
+ * undefined until a future SDK version emits it. Cleared on STREAM_DELTA
45
+ * (a byte arriving means the reconnect, if any, succeeded).
46
+ */
47
+ reconnectAttempt: number | undefined;
48
+ reconnectMaxAttempts: number | undefined;
26
49
  }
27
50
 
28
51
  /** Minimal orchestrator surface required for stream token-speed calculation. */
@@ -60,6 +83,33 @@ interface StreamSystemMessageRouter {
60
83
  low(message: string): void;
61
84
  }
62
85
 
86
+ /**
87
+ * Loosely-typed variant of the turns event feed, used only to subscribe to
88
+ * event names not yet present in the SDK's TurnEvent union (STREAM_RETRY,
89
+ * STREAM_STALL — see the structural-consumption comment at the subscription
90
+ * site below). `events.turns.on` itself stays fully typed against the real
91
+ * TurnEvent union everywhere else in this file.
92
+ */
93
+ interface LooseTurnEventFeed {
94
+ on(type: string, listener: (payload: unknown) => void): () => void;
95
+ }
96
+
97
+ /** Payload shape expected from a future SDK STREAM_RETRY event. */
98
+ interface StreamRetryLikePayload {
99
+ readonly attempt: number;
100
+ readonly maxAttempts: number;
101
+ }
102
+
103
+ /** Runtime guard validating an unknown STREAM_RETRY-like payload's shape. */
104
+ function isStreamRetryLikePayload(payload: unknown): payload is StreamRetryLikePayload {
105
+ return (
106
+ typeof payload === 'object' &&
107
+ payload !== null &&
108
+ typeof (payload as Record<string, unknown>).attempt === 'number' &&
109
+ typeof (payload as Record<string, unknown>).maxAttempts === 'number'
110
+ );
111
+ }
112
+
63
113
  /**
64
114
  * Minimal cost lookup surface for attaching cost-delta information to failover notices.
65
115
  * Returns USD-per-1M-token pricing for the given model ID.
@@ -197,10 +247,19 @@ export function wireStreamEventMetrics(
197
247
  metrics.tokenSpeed = 0;
198
248
  metrics.ttftMs = undefined;
199
249
  metrics.ttftRecorded = false;
250
+ metrics.lastDeltaAtMs = Date.now();
251
+ metrics.stallEpisode = 0;
252
+ metrics.reconnectAttempt = undefined;
253
+ metrics.reconnectMaxAttempts = undefined;
200
254
  }));
201
255
 
202
256
  unsubs.push(events.turns.on('STREAM_DELTA', () => {
203
257
  metrics.deltaCount++;
258
+ metrics.lastDeltaAtMs = Date.now();
259
+ // A byte arrived: any in-flight reconnect (if the SDK reported one)
260
+ // has succeeded.
261
+ metrics.reconnectAttempt = undefined;
262
+ metrics.reconnectMaxAttempts = undefined;
204
263
  const elapsed = (Date.now() - metrics.startTime) / 1000;
205
264
  // Record TTFT on the first delta of each turn.
206
265
  if (!metrics.ttftRecorded) {
@@ -296,10 +355,16 @@ export function wireStreamEventMetrics(
296
355
  render();
297
356
  }));
298
357
 
299
- // --- Stream stall watchdog: emit one low hint if STREAM_START has no delta within 30s ---
358
+ // --- Stream stall watchdog: emit a low hint each time a no-delta gap (at
359
+ // start of turn OR mid-stream, after a re-arm) crosses 30s. The transient
360
+ // system message below is the initial alert; streamMetrics.stallEpisode
361
+ // (read every render frame) drives the persistent "stalled Ns" indicator
362
+ // in the thinking fragment, so the two coexist rather than conflict — the
363
+ // message announces the stall, the indicator tracks it ongoing.
300
364
  const stallWatchdog = createStreamStallWatchdog({
301
365
  events: events.turns,
302
- onStall: (providerName) => {
366
+ onStall: (providerName, episode) => {
367
+ metrics.stallEpisode = episode;
303
368
  systemMessageRouter.low(`Still waiting on ${providerName}… Ctrl+C to cancel`);
304
369
  render();
305
370
  },
@@ -308,22 +373,59 @@ export function wireStreamEventMetrics(
308
373
  });
309
374
  unsubs.push(() => stallWatchdog.dispose());
310
375
 
376
+ // --- Structural consumption of STREAM_RETRY / STREAM_STALL ---
377
+ // Neither event exists on the pinned SDK's (0.35.0) TurnEvent union today —
378
+ // both are proposed additions for the transport-level withRetry() callback
379
+ // (see the stall-honesty audit brief). The typed `events.turns.on` feed
380
+ // rejects unknown event names at compile time, so this casts the feed to a
381
+ // loosely-typed variant and validates each payload with a runtime guard
382
+ // instead of importing a type name that doesn't exist yet — same pattern
383
+ // used elsewhere in this codebase for settings pending SDK schema additions
384
+ // (see src/input/settings-modal-data.ts). Compiles today against 0.35.0;
385
+ // lights up automatically once the SDK adds the real event.
386
+ const looseTurnsFeed = events.turns as unknown as LooseTurnEventFeed;
387
+ unsubs.push(looseTurnsFeed.on('STREAM_RETRY', (payload) => {
388
+ if (!isStreamRetryLikePayload(payload)) return;
389
+ metrics.reconnectAttempt = payload.attempt;
390
+ metrics.reconnectMaxAttempts = payload.maxAttempts;
391
+ render();
392
+ }));
393
+ unsubs.push(looseTurnsFeed.on('STREAM_STALL', () => {
394
+ // Informational only: the TUI's own createStreamStallWatchdog above is
395
+ // the authoritative no-delta detector and already drives the visible
396
+ // indicator via streamMetrics.stallEpisode. This subscription exists so
397
+ // the SDK's own signal (once it lands) is observed rather than silently
398
+ // dropped, without duplicating or fighting the local watchdog.
399
+ render();
400
+ }));
401
+
311
402
  unsubs.push(events.tools.on('TOOL_EXECUTING', (ev) => {
312
403
  metrics.activeToolStartedAtMs = ev.startedAt;
313
404
  metrics.activeToolName = ev.tool;
314
405
  render();
315
406
  }));
407
+ // On every tool-completion path, reset lastDeltaAtMs to "now" so the
408
+ // post-tool silence window starts fresh. Tool execution suppresses stall
409
+ // detection at the render call site (see main.ts), but lastDeltaAtMs itself
410
+ // keeps its pre-tool value the whole time the tool runs — without this
411
+ // reset, the instant a tool completes (potentially long after the last real
412
+ // delta), the very next frame would read msSinceLastDelta as the full
413
+ // tool-execution duration and immediately report a stall, even though the
414
+ // model hasn't had a chance to resume producing tokens yet.
316
415
  unsubs.push(events.tools.on('TOOL_SUCCEEDED', () => {
317
416
  metrics.activeToolStartedAtMs = undefined;
318
417
  metrics.activeToolName = undefined;
418
+ metrics.lastDeltaAtMs = Date.now();
319
419
  }));
320
420
  unsubs.push(events.tools.on('TOOL_FAILED', () => {
321
421
  metrics.activeToolStartedAtMs = undefined;
322
422
  metrics.activeToolName = undefined;
423
+ metrics.lastDeltaAtMs = Date.now();
323
424
  }));
324
425
  unsubs.push(events.tools.on('TOOL_CANCELLED', () => {
325
426
  metrics.activeToolStartedAtMs = undefined;
326
427
  metrics.activeToolName = undefined;
428
+ metrics.lastDeltaAtMs = Date.now();
327
429
  }));
328
430
 
329
431
  let _errorSurfacedCb: ((exhausted: boolean) => void) | undefined;