Skip to content

feat(expose): instrument first paint timing - #370

Merged
TraderSamwise merged 7 commits into
masterfrom
feat/expose-open-timing
Jul 20, 2026
Merged

feat(expose): instrument first paint timing#370
TraderSamwise merged 7 commits into
masterfrom
feat/expose-open-timing

Conversation

@TraderSamwise

@TraderSamwise TraderSamwise commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Summary

  • add Exposé open-path timing events for startup, first render, item load, first live capture, focus, and exit
  • log timing events through the existing debug logger while exposing an optional test/runtime sink
  • strengthen the preview snapshot test to prove snapshot first paint happens before live tmux capture

Verification

  • yarn vitest run src/tmux/expose.test.ts src/tmux/expose-model.test.ts src/expose-preview-cache.test.ts
  • yarn typecheck
  • yarn lint
  • yarn build
  • yarn test
  • push hook: yarn typecheck && yarn lint && yarn test

Summary by CodeRabbit

  • New Features

    • Added structured timing instrumentation across the Exposé lifecycle, including startup milestones, first render, live capture start/end, item-loading start/stale/error/end, focus start/end, and exit.
    • Exposed timing through a new optional callback for diagnostics and integrations.
  • Tests

    • Expanded timing coverage, validating expected event presence/order, preview capture vs warm output ordering, non-negative elapsed times, and correct timing behavior for empty states, load failures, stale refresh, and early Exposé exit focus closure.

@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
app Ready Ready Preview, Comment Jul 20, 2026 5:19pm

@TraderSamwise

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

runTmuxExpose now emits typed timing events for lifecycle, rendering, loading, capture, focus, and exit paths. Tests validate event ordering, stale and failed reload reporting, preview-before-capture behavior, callback error handling, and elapsed-time validity.

Changes

Exposé timing instrumentation

Layer / File(s) Summary
Timing contract and lifecycle emitter
src/tmux/expose.ts
Adds exported timing types, an optional callback, elapsed-time measurement, debug logging, and lifecycle events.
Render, capture, and reload timing
src/tmux/expose.ts, src/tmux/expose.test.ts
Tracks preview snapshots and emits render, capture, load, stale, and error events with corresponding assertions.
Focus timing and sequencing validation
src/tmux/expose.ts, src/tmux/expose.test.ts
Closes focus timing across synchronous, asynchronous, and rejection paths, with tests for event ordering and elapsed values.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ExposeRuntime
  participant Output
  participant RuntimeManager
  participant TimingCallback
  ExposeRuntime->>TimingCallback: emit lifecycle and render events
  ExposeRuntime->>Output: write warm preview
  ExposeRuntime->>TimingCallback: emit first-live-capture-start
  ExposeRuntime->>RuntimeManager: captureTarget
  RuntimeManager-->>ExposeRuntime: return capture result
  ExposeRuntime->>TimingCallback: emit first-live-capture-end
  ExposeRuntime->>TimingCallback: emit reload and focus outcomes
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding Exposé timing instrumentation around first paint and related startup events.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/expose-open-timing

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/tmux/expose.ts`:
- Around line 375-386: Update the markTiming function so exceptions from
options.onTiming?.(event) are caught and treated as non-fatal, while preserving
the existing timing event creation and debug logging. Ensure callers such as
exit continue to perform timer cleanup and terminal restoration even when the
instrumentation callback throws.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 25137c44-3140-46e6-a07e-965c9587b281

📥 Commits

Reviewing files that changed from the base of the PR and between f866aad and 39a62e5.

📒 Files selected for processing (2)
  • src/tmux/expose.test.ts
  • src/tmux/expose.ts

Comment thread src/tmux/expose.ts Outdated
@TraderSamwise

Copy link
Copy Markdown
Owner Author

Sub-agent finding fixed in 90565b0: empty item lists now skip live capture timing entirely, so a later populated scope can still record the real first capture.

@TraderSamwise

Copy link
Copy Markdown
Owner Author

Sub-agent finding fixed in 90565b0: Expose timing logs now use the existing tmux debug category, so the documented diagnostic filter captures them.

@TraderSamwise

Copy link
Copy Markdown
Owner Author

Sub-agent finding fixed in 90565b0: item load timing now has terminal stale and error events, keeping every load-start pairable even when a reload is superseded or fails.

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/tmux/expose.ts (1)

377-389: 🩺 Stability & Availability | 🟠 Major

Keep timing sinks non-fatal.

options.onTiming?.(event) is still outside a try/catch. If it throws during terminal-ready, Exposé can leave the terminal in raw/alternate-screen mode; during exit, timer cleanup and terminal restoration are skipped. This is the same unresolved issue from the previous review.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tmux/expose.ts` around lines 377 - 389, The markTiming function must keep
timing sinks non-fatal: wrap options.onTiming?.(event) in a try/catch so
exceptions from the callback are swallowed or safely logged, allowing subsequent
terminal-ready and exit cleanup to continue. Keep timing event construction and
debug logging behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/tmux/expose.ts`:
- Around line 377-389: The markTiming function must keep timing sinks non-fatal:
wrap options.onTiming?.(event) in a try/catch so exceptions from the callback
are swallowed or safely logged, allowing subsequent terminal-ready and exit
cleanup to continue. Keep timing event construction and debug logging behavior
unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1550c470-ec23-4226-862d-3bb48f68460e

📥 Commits

Reviewing files that changed from the base of the PR and between 39a62e5 and 90565b0.

📒 Files selected for processing (2)
  • src/tmux/expose.test.ts
  • src/tmux/expose.ts

@TraderSamwise

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@TraderSamwise

Copy link
Copy Markdown
Owner Author

Sub-agent finding fixed in 053c31e: refresh reloads now skip inline live capture until after the first non-empty item render, preserving preview-first first paint for empty-to-populated scopes.

@TraderSamwise

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/tmux/expose.ts (1)

808-824: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Terminate pending focus timing on early exit.

If Exposé finishes while focusExposeItem is pending, both settlement handlers return at finished before emitting focus-end. This leaves a reachable focus-start without a terminal event; emit a single focus-end or explicit cancellation event when the operation is abandoned.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tmux/expose.ts` around lines 808 - 824, Update the pending focus flow
around focusExposeItem so an Exposé early exit while the promise is pending
emits exactly one terminal focus event, such as focus-end or an explicit
cancellation event. Ensure the settlement handlers and the early-finish path
coordinate through existing state to prevent duplicate emission while preserving
the current success and retry behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/tmux/expose.ts`:
- Around line 808-824: Update the pending focus flow around focusExposeItem so
an Exposé early exit while the promise is pending emits exactly one terminal
focus event, such as focus-end or an explicit cancellation event. Ensure the
settlement handlers and the early-finish path coordinate through existing state
to prevent duplicate emission while preserving the current success and retry
behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d1ed4194-76ac-4863-8d3d-af370cd52969

📥 Commits

Reviewing files that changed from the base of the PR and between 5b0cd27 and 053c31e.

📒 Files selected for processing (2)
  • src/tmux/expose.test.ts
  • src/tmux/expose.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/tmux/expose.test.ts

@TraderSamwise

Copy link
Copy Markdown
Owner Author

CodeRabbit outside-diff finding fixed in 4ebfcf9: pending focus timing now closes through the shared finish path, with a guard to emit exactly one focus-end across success, retry, and early-exit paths.

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/tmux/expose.ts (1)

694-703: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Render newly populated items before live capture.

When a reload changes an empty scope to non-empty, this guard skips capture inside reload(), but the caller immediately invokes refreshCaptures() before the first item render. Live tmux output can therefore replace seeded previews before first-items-render, violating the preview-first contract.

Render once after the committed reload when firstItemsRenderMarked is still false, then perform the live capture. Add a regression test for empty → populated refresh ordering.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tmux/expose.ts` around lines 694 - 703, Update the committed reload flow
in reload() so that when items become populated and firstItemsRenderMarked is
still false, it performs the initial item render before calling
refreshCaptures(). Preserve the existing capture behavior for already-rendered
items, and add a regression test covering empty-to-populated reload ordering to
ensure first-items-render precedes live capture.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/tmux/expose.ts`:
- Around line 694-703: Update the committed reload flow in reload() so that when
items become populated and firstItemsRenderMarked is still false, it performs
the initial item render before calling refreshCaptures(). Preserve the existing
capture behavior for already-rendered items, and add a regression test covering
empty-to-populated reload ordering to ensure first-items-render precedes live
capture.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b47bb500-c2e5-4cc3-ba59-83b77a7ec976

📥 Commits

Reviewing files that changed from the base of the PR and between 053c31e and 4ebfcf9.

📒 Files selected for processing (2)
  • src/tmux/expose.test.ts
  • src/tmux/expose.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/tmux/expose.test.ts

@TraderSamwise

Copy link
Copy Markdown
Owner Author

Sub-agent finding fixed in 9f39409: fatal signal cleanup now shares the focus timing closeout and signal-detach path, so pending focus spans emit a matching focus-end before exit.

@TraderSamwise

Copy link
Copy Markdown
Owner Author

Sub-agent finding fixed in 9f39409: reload() now observes the finished state before starting and immediately after awaited item loads, preventing item-load terminal events or item mutations after Exposé has emitted exit.

@TraderSamwise

Copy link
Copy Markdown
Owner Author

Sub-agent finding fixed in cfde1e6: scheduleRefresh now exits early when Exposé is finished and rechecks after awaited reloads before handling stale results, so late refresh reloads cannot re-arm timers or write/capture after exit.

@TraderSamwise

Copy link
Copy Markdown
Owner Author

CodeRabbit preview-first summary is already addressed in current head: 053c31e added the empty-to-populated regression, reload() skips inline capture until firstItemsRenderMarked, loadInitialItems/zoomOut render before calling refreshCaptures(), and the periodic refresh path short-circuits capture on reloadedItems before rendering.

@TraderSamwise
TraderSamwise merged commit 9d6dea7 into master Jul 20, 2026
3 checks passed
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.

1 participant