feat(tui): show session title in terminal tab - #81
Conversation
Mirror opencode's live terminal title ("OC | <title>") so hosts like
Orca show a content-derived tab label instead of the process name.
- Emit OSC 0 "ZC | <first-message title>" after the first user message
- Show "ZC | ⠋ <activity>" while a turn is running
- Clear the title on /new
- Title: whitespace-collapsed first message, 50 code points max
kingsword09
left a comment
There was a problem hiding this comment.
The overall implementation is focused and well structured. Extracting terminal-title derivation and OSC emission into a dedicated module keeps the change small, and reusing sanitizeTerminalText provides the necessary terminal-control protection.
I am requesting changes for two lifecycle issues:
- Suspended login can leave the title stuck at
ZC | ⠋ signing in…. - Restored and resumed sessions do not initialize the title from their first restored user message, causing the next prompt to become the title instead.
These issues directly affect the behavior introduced by this PR, so they should be addressed before merging. The 50/51-code-point discrepancy is non-blocking.
Local validation against b859791:
bun run typecheck: passed- TUI build: passed
bun test: 511 passed, 0 failedsmoke-tui-clear: passedsmoke-tui-features: passed- PTY lifecycle reproduction confirmed both blocking issues
AI-assisted review using Codex. The blocking findings were reproduced locally against the PR head.
| ): QueuedSubmission | undefined { | ||
| this.activeSubmissions = Math.max(0, this.activeSubmissions - 1); | ||
| if (this.activeTurnEpoch !== turnEpoch) return undefined; | ||
| this.refreshSessionTerminalTitle(); |
There was a problem hiding this comment.
[P2] Refresh the terminal title on every turn-finalization path
This refresh only covers finishPrimaryTurnSubmission(). The suspended login path decrements activeSubmissions and then calls finishTurn(), but finishTurn() clears activity directly without refreshing the terminal title.
As a result, running /login in an already titled session leaves the terminal title stuck at ZC | ⠋ signing in….
Please move the refresh into the common finishTurn() path after clearing activity, or explicitly refresh it from runSuspendedLogin(). Centralizing this in finishTurn() would avoid future lifecycle paths missing the same cleanup.
Please also add a regression test covering: session title → /login working title → restored idle session title.
| this.clearTranscriptProjection(); | ||
| this.workflowView = undefined; | ||
| this.sessionId = undefined; | ||
| this.sessionTitleEmitted = false; |
There was a problem hiding this comment.
[P2] Initialize the title from restored session messages
resetSessionProjection is also returned by /resume together with restoredMessages. This code clears the terminal-title state and restores the transcript, but it never derives the title from the first restored user message.
Consequently:
- A resumed session initially has no content-derived terminal title.
- The next prompt is incorrectly treated as the first message of the session and becomes the title.
The startup restoreInitialTranscript() path has the same issue.
Please introduce a shared helper that derives the title from the first user message in a normalized restored transcript, and use it for both startup restoration and /resume restoration. For /new, where the restored transcript is empty, the title should remain cleared.
Please add regression coverage for both restored startup sessions and in-app /resume.
| if (characters.length <= MAX_SESSION_TITLE_CHARS) { | ||
| return normalized; | ||
| } | ||
| return `${characters.slice(0, MAX_SESSION_TITLE_CHARS).join("")}…`; |
There was a problem hiding this comment.
[non-blocking] Keep the final title within the declared 50-code-point limit
When truncation occurs, this keeps 50 code points and then appends an ellipsis, producing a final title of 51 code points. The test currently codifies that behavior, but it differs from the PR description of being capped at 50 code points.
Please either keep the ellipsis within the 50-code-point limit, or clarify that MAX_SESSION_TITLE_CHARS excludes the ellipsis and update the PR description accordingly.
Mirror opencode's live terminal title ("OC | <title>") so terminal hosts (Orca, iTerm, etc.) show a content-derived tab label instead of the process name
zcode.What
ESC ] 0; ... BEL)ZC | <title>after the first user messageZC | ⠋ <activity>/newWhy
The runtime already auto-generates LLM session titles, but the TUI never exposes them to the terminal host, so tab labels stay
zcode. This is the display-side counterpart (TUI-level, no protocol change needed).Test
test/session-title.test.ts(11 cases: title derivation, OSC emission, TTY guard, clearing)bun test502 pass,bun run typecheckclean