Skip to content

feat(tui): show session title in terminal tab - #81

Open
penghuizhang wants to merge 1 commit into
kingsword09:mainfrom
penghuizhang:feat/tui-session-title
Open

feat(tui): show session title in terminal tab#81
penghuizhang wants to merge 1 commit into
kingsword09:mainfrom
penghuizhang:feat/tui-session-title

Conversation

@penghuizhang

Copy link
Copy Markdown

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

  • Emit OSC 0 (ESC ] 0; ... BEL) ZC | <title> after the first user message
  • While a turn is running, show ZC | ⠋ <activity>
  • Clear the terminal title on /new
  • Title = whitespace-collapsed first message, capped at 50 code points

Why

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

  • New unit tests: test/session-title.test.ts (11 cases: title derivation, OSC emission, TTY guard, clearing)
  • bun test 502 pass, bun run typecheck clean

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 kingsword09 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Suspended login can leave the title stuck at ZC | ⠋ signing in….
  2. 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 failed
  • smoke-tui-clear: passed
  • smoke-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();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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:

  1. A resumed session initially has no content-derived terminal title.
  2. 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("")}…`;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants