Skip to content

feat(tui): add optional message timestamps - #1206

Open
codeg-dev wants to merge 14 commits into
code-yeongyu:mainfrom
codeg-dev:codeg-dev/tui-timestamps
Open

feat(tui): add optional message timestamps#1206
codeg-dev wants to merge 14 commits into
code-yeongyu:mainfrom
codeg-dev:codeg-dev/tui-timestamps

Conversation

@codeg-dev

@codeg-dev codeg-dev commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add a default-off Message timestamps row to /settings
  • prefix the first non-empty assistant content line with local HH:MM:SS
  • stamp the first updateContent() only, so streaming updates and resize rerenders keep the original arrival time
  • reserve prefix width before Markdown rendering and propagate the setting across restored, streaming, segmented, and reloaded components

Closes #1205.

Why this is in the built-in TUI

The extension API cannot add a native /settings row or change construction/rendering of every built-in assistant message component. The nearest changes.md trackers document all four upstream-owned production paths and the expected merge-conflict zones.

Verification

  • npm install --ignore-scripts
  • npm run check
  • 6 affected coding-agent test files: 144 tests passed
  • mutation check: replacing first-write ??= with = makes the two-update arrival-time regression fail
  • scripts/check-pr-changelog.mjs with no-changelog: 4/4 upstream-owned production paths covered
  • scripts/audit-changes-md.mjs: 253/253 divergent paths covered, 0 uncovered
  • real tmux TUI QA with an isolated fake provider: settings default-off, immediate toggle/persistence, streamed timestamp, Markdown code block, and resize stability observed
  • packaged @code-yeongyu/senpi@2026.8.28-2 QA: syntax, first-arrival behavior, idempotence, rollback, partial-state refusal, and real packaged TUI rendering passed on the candidate and two macOS targets

AI authorship disclosure

I used OpenAI Codex to help implement and test this change and to draft the issue/PR text. I reviewed the code, understand the settings/streaming/rendering/reload interactions, and verified the behavior through unit, mutation, and real CLI QA. I read CONTRIBUTING.md; this repository currently has no CLA.md or CLA workflow.


Summary by cubic

Adds a default-off Message timestamps row to /settings that prefixes the first non-empty assistant content line with local HH:MM:SS arrival time. Previously assistant messages never showed when they arrived; now operators can opt in, the setting persists across sessions, and timestamps survive streaming updates, resize rerenders, transcript reloads, and upstream merges. Closes #1205.

New Features

  • Exactly one timestamp per logical message: stamped on the first visible content update and shared across streaming chunks, smooth-stream frames, and tool-separated segments.
  • Timestamps drop entirely when a narrow viewport cannot fit the prefix plus content, so nothing wraps or overflows.
  • Toggling the setting updates the prefix immediately without rebuilding content children, and reload refreshes the setting before transcript reconstruction.

Written for commit d6606bf. Summary will update on new commits.

Review in cubic

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 30, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-30T12:37:47.796452Z d1e3c37 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@codeg-dev

Copy link
Copy Markdown
Contributor Author

Maintainer gate note: CONTRIBUTING.md says contributors must not edit CHANGELOG.md. This runtime PR passes scripts/check-pr-changelog.mjs with the existing no-changelog label path and has complete nearest-changes.md coverage, but the fork contributor account cannot apply labels. Please apply no-changelog (or add the maintainer-owned changelog entry) so the required gate can rerun.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f39ee77dae

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// First delta only. updateContent runs once per streaming chunk, so stamping
// unconditionally would march the clock forward while the message is still
// being written and land on the completion time rather than the start.
this.arrivedAt ??= new Date();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve arrival times when reconstructing messages

When a saved session is resumed, reloaded, or otherwise rebuilt with timestamps enabled, constructing each component immediately calls updateContent(message), so this line stamps every historical message with the current reconstruction time and ignores the persisted message.timestamp. As a result, old messages display the resume/reload time—and can change again after settings-triggered transcript rebuilds—instead of a stable historical time; reconstructed components need to derive or restore a persisted arrival value while empty streaming components can continue stamping their first update.

Useful? React with 👍 / 👎.

@codeg-dev codeg-dev Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is intentional for the contract in #1205: arrivedAt is the time a component first receives content in the current TUI, not the provider/model timestamp persisted on the message. Reusing message.timestamp would change that meaning, and the feature deliberately avoids adding persisted transcript schema. A restored or reloaded transcript is reconstructed into the current UI and receives its current UI-arrival time; live updates remain fixed by arrivedAt ??=. The regression suite locks the two-update stability contract.

setShowTimestamps(show: boolean): void {
if (this.showTimestamps === show) return;
this.showTimestamps = show;
this.invalidate();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid rebuilding message trees when toggling timestamps

When this setting is toggled in a long transcript, InteractiveMode calls this method for every assistant component, and invalidate() clears renderDescriptors before refreshContent(), causing reconciliation to dispose and recreate every Markdown/Text child even though only the prefix changed. This makes a simple settings toggle synchronously rebuild the complete assistant tree and can stall large sessions; clear only the outer render cache here rather than invoking the full content invalidation path.

AGENTS.md reference: packages/coding-agent/src/modes/interactive/components/AGENTS.md:L41-L41

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in the current head. setShowTimestamps() and setTimestampEligible() now invalidate only the outer render cache; they no longer clear or reconcile content descriptors. The regression test captures the memoized content children, toggles both states, and asserts object identity is unchanged.

@codeg-dev

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 320cd5b482

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const prefix = this.timestampPrefix();
const prefixWidth = visibleWidth(prefix);
const showTimestamp = prefixWidth > 0 && width > prefixWidth;
const lines = super.render(showTimestamp ? width - prefixWidth : width);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve full width after the timestamped line

When timestamps are enabled, this subtracts the nine-column prefix from the width used to render the entire assistant component, but the prefix is added only to the first non-empty line. Every continuation line therefore wraps nine columns early while still starting at column zero, unnecessarily increasing transcript height and narrowing tables or code blocks; only the prefixed line should surrender that width.

AGENTS.md reference: packages/coding-agent/src/modes/interactive/components/AGENTS.md:L34-L34

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The narrower Markdown render is intentional and follows #1205's stated contract to reserve the visible prefix width before Markdown rendering. Using one width for the component keeps Markdown/table/code wrapping internally consistent and provides a simple hard width bound; the prefix is still attached only to the first non-empty line. Boundary tests cover widths 9–12 and real TUI QA covers 120x34 to 80x24 resize.

this.hiddenThinkingLabel,
this.outputPad,
this.getMarkdownTransformers(),
this.showMessageTimestamps,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Timestamp each streamed assistant message only once

When a streamed assistant response contains text after a tool call, the head component already has a timestamp, but every synthetic trailing-text segment created here also receives timestamping and records a separate arrival time. The live transcript consequently shows multiple timestamps for one assistant message, while reconstructing the same persisted message produces one component and one timestamp; pass timestamp ownership only to the head component rather than each trailing segment.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in the current head. Trailing segments share the streaming head arrivedAt, start ineligible, and syncStreamingMessageTimestampEligibility() assigns exactly one visible owner across the head and sorted segments. The tool-separated regression asserts exactly one timestamp and the tool-first regression asserts the original arrival time is retained.

@codeg-dev

Copy link
Copy Markdown
Contributor Author

@codex review

Please review exact head 497eb17e21bd63fe118e0eb3499aeb17f5453669; prior reviews target stale commits.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: 497eb17e21

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@codeg-dev

Copy link
Copy Markdown
Contributor Author

@code-yeongyu Final contributor gate note for exact head 497eb17e21bd63fe118e0eb3499aeb17f5453669:

  • Codex exact-head review completed with no major issues.
  • Independent code review found 0 functional findings.
  • Focused verification passed: 159 coding-agent tests, 39 TUI renderer tests, static checks/build, and 253/253 changes.md coverage.
  • Independent real-TUI QA passed default-off, settings toggle/persistence, stable streaming arrival time, Markdown, resize, and teardown.
  • The PR's coding-agent failures match the already-red base run 33293958113 (18 inherited failures); the one extra compaction failure passed on exact-head rerun after the CI-parity build. PR fix(rpc): restore main CI after audit2 merge #1203 is now fully green/CLEAN and repairs that base.
  • CONTRIBUTING.md forbids contributor changelog edits, so please apply no-changelog or add the maintainer-owned changelog entry.

Please merge #1203 first if that is the intended main repair, then apply the changelog action here. I will update this branch to the repaired main, rerun exact-SHA gates, and keep this PR on merge-commit delivery.

@codeg-dev

Copy link
Copy Markdown
Contributor Author

@codex review

Please review exact head 05a18162dec07316d633cf8d19c31e6bd6f6255f; earlier reviews target superseded commits. The only post-main follow-up is a one-line default-off getter in the classic chrome runtime fixture, added after CI exposed the missing modeled settings method.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

Reviewed commit: 05a18162de

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@codeg-dev

Copy link
Copy Markdown
Contributor Author

@codex review

Please review exact head 825fe82211975e9ce70351eb1f05c51d3979d250; earlier review requests target superseded fixture commits. Both constructor test runtimes now model the default-off timestamp getter, and the combined focused run passes 169/169.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: 825fe82211

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@codeg-dev

Copy link
Copy Markdown
Contributor Author

@codex review

Please review exact head 34c06c7f4e8c10153a65b1c95f9628245449a701; prior requests target superseded commits. The final test-only follow-up prevents a swallowed reload-fixture exception by asserting the chat rebuild and intended sentinel error path.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: 34c06c7f4e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@codeg-dev

Copy link
Copy Markdown
Contributor Author

@code-yeongyu Exact-head gate update for 34c06c7f4e8c10153a65b1c95f9628245449a701:

  • Independent code review: PASS, 0 findings.
  • Independent real-TUI QA: PASS (default-off, toggle/persistence/restart, stable streaming arrival time, Markdown/code, 80x24 resize, official smoke 5/5, cleanup).
  • Local exact gates: npm run check, build, 181 focused tests, 39 TUI renderer tests, and 253/253 changes coverage all pass.
  • GitHub CI: static, coding-agent 1/3 and 2/3, workspace/terminal/inspector gates are green or completing. Coding-agent 3/3 has exactly the same single rpc-socket-host.test.ts RPC-record timeout as upstream main d425f112 (2,821 passed / 1 failed); please rerun that failed job.
  • CONTRIBUTING.md forbids contributor changelog edits. Please apply no-changelog (local gate passes with it) or add the maintainer-owned changelog entry.

Once those two maintainer-owned gates are green, please merge with a merge commit; this branch already contains a two-parent merge from current main.

@codeg-dev

Copy link
Copy Markdown
Contributor Author

@codex review\n\nExact candidate: d1e3c37b753598a77d6f4b5489b50c38be66af13. The branch now includes a merge commit from current upstream main; local exact-SHA checks, focused tests, changes.md coverage, independent code/gate review, and real built-CLI QA all passed.

@codeg-dev

codeg-dev commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

Maintainer action required (@code-yeongyu) for exact head d6606bfcfb497252fa1167c9b83e3599124e7fc0:

  • The branch contains a two-parent merge from current upstream main at 4b1bb45be9ef8c470137aec20319e69060ace225; GitHub reports it mergeable.
  • Every non-changelog exact-head GitHub check is green: static checks, all three coding-agent shards, workspaces/scripts, terminal tools, inspector handoff, aggregate check, GitGuardian, and Cubic.
  • Local exact-SHA gates also pass: non-bypassed npm run check, full six-phase build, 12/12 focused regressions, independent code/gate review with 0 findings, real built-CLI QA inherited by exact feature blob parity, and 253/253 whole-tree changes.md coverage.
  • The simulated changelog gate passes with no-changelog and covers all 6 production paths, including two Biome-normalized upstream barrel exports.
  • Contributor policy reserves CHANGELOG.md curation for release/audit work. Please apply no-changelog so the label-triggered gate can pass, then merge with a merge commit (not squash/rebase).
  • This contributor account cannot add repository labels, request reviewers, or enable auto-merge; GitHub denied those mutations.

AI authorship is disclosed in the PR body, and this repository has no CLA.md or CLA workflow to complete.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d1e3c37b75

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let assigned = false;
for (const component of [this.streamingComponent, ...segments]) {
const eligible = !assigned && component.hasVisibleContent();
component.setTimestampEligible(eligible);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep timestamp eligibility through smooth-stream buffering

When timestamps and the default smooth streaming are enabled, the first target is initially rendered with zero revealed units, so hasVisibleContent() is false and this loop disables the head component's timestamp eligibility. Later reveal ticks call only updateContent() and request a render, never this synchronization method, so the response remains visibly unprefixed throughout streaming and gains its timestamp only at message_end. Preserve eligibility for the streaming head or resynchronize when the first visible unit is revealed.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in the current head. When all components are still empty, syncStreamingMessageTimestampEligibility() now keeps the streaming head as the fallback owner. A real StreamingRevealController regression starts with an empty paced frame, reveals visible content without another eligibility sync, and asserts the timestamp appears on that first reveal; built-dist smooth-stream QA also observes the timestamped partial marker before completion.

Preserve timestamp ownership through the initial empty smooth-stream frame.
Refresh the feature branch against the latest compaction and model-selection changes.
Refresh the feature branch with the latest PTY cancellation fixture.
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.

feat(tui): add optional assistant message timestamps

1 participant