Skip to content

fix(renderer): count IME commits and coalesced echoes in the typing probe - #16952

Closed
norinus wants to merge 3 commits into
stablyai:mainfrom
norinus:norinus/typingLatencyProbeFixes
Closed

norinus wants to merge 3 commits into
stablyai:mainfrom
norinus:norinus/typingLatencyProbeFixes

Conversation

@norinus

@norinus norinus commented Aug 28, 2026

Copy link
Copy Markdown

ELI5

There is a built-in stopwatch for "how long from keypress to seeing the character" (window.__orcaTypingDiagnostic). Typing Korean produced zero readings, so it looked like Korean had no latency at all. It also threw away most of a fast burst and kept the slowest ones. This fixes the stopwatch, then adds the readings that say where the time went.

What Changed

CJK input is sampled at all. isEchoingKey returns false while event.isComposing is true, so a composed character was never recorded. Composing jamo genuinely send nothing to the pty — the commit is what carries the echo — so compositionend starts the timer, and IME keydowns are now excluded explicitly rather than by accident.

Fast bursts are no longer dropped. A TUI redraws on its own frame clock, so typing faster than that clock puts several keystrokes into one redraw. oldestUnparsed credited only the oldest to that redraw and the rest stayed pending forever, so the surviving samples were the ones that had waited longest and p50 was biased upward. The whole waiting set is credited now; coalescing on the sample says how many shared the write, and each reports the redraw's byte count so the two divide.

Two shared slots that let one sample carry another's figures. The coalescing count lived on the pane, so a second write parsing before the render that drained the first batch overwrote it — it is stored per keystroke now, assigned where the echo is marked. The blocking census measured up to whenever census() was called, including idle time after stop() had detached the observer — the window closes at stop().

New readings.

  • mainThreadBlocking — long-task count, blocked milliseconds, and share of the window. Answers "was the renderer even free to serve this keystroke", which the echo timings alone cannot
  • byInputSource — IME commits and plain keypresses summarized separately, plus imeCommitChars. Pooling them hides which of the two is slow
  • dispatchMs — keystroke to the moment xterm handed bytes toward the pty, separating the renderer's share from the host round trip
  • echoPaintHistogram — fixed buckets rather than derived ones, because the paths a keystroke can take sit at known delays (a ~16-32ms fast release vs a 250ms hold-safety fallback vs a 1s coalesce). Percentiles average two modes into one number that matches neither

Why

This is the tool the other two fixes were measured with, so it had to be right first. Concretely: with the old probe, an investigation into Korean typing latency reads zero samples and concludes there is nothing to fix — which is the opposite of the truth.

Linked Issue

Fixes #16950

Visual Proof

N/A — no UI. This code is inert until window.__orcaTypingDiagnostic.start() is called from the renderer console.

What the fixed probe reports, for reference (agent pane, idle, 16 composed syllables):

blocked      0 tasks / 0%
dispatch p50 1.6ms          ← renderer's share
paint    p50 28.5ms  p95 43.4ms
bytes    p50 58             ← vs 6 in a plain PowerShell pane
coalescing   1

The 58-vs-6 bytes split is the kind of thing the old probe could not surface, and it is what identifies the agent TUI's own redraw — not Orca — as the dominant per-syllable cost in that pane.

Testing

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

typing-latency-echo-instrumentation.test.ts gained coverage for several keystrokes resolving through one redraw, and for the ordering regression: two writes parsing before a single render must report [2, 2, 1], not [1, 1, 1] or [1, 1, ...] from a shared slot.

tsc --noEmit (web project)   0 errors
oxlint (repo)                0 errors
vitest typing-latency-*      9 passed  (echo instrumentation)
vitest src/renderer/src/lib/ 460 files, 4413 passed, 1 skipped
pnpm run build:desktop       passes

Platform: Windows 11. All of it is renderer-side probe logic and platform-independent. pnpm test could not run in my environment — the ensure-native-runtime --runtime=node prestep fails to rebuild node-pty for Node 24 — so vitest was invoked directly.

AI Disclosure

Written with Claude Opus 5 (Claude Code).

Review

Cross-platform — no platform branch; PerformanceObserver with the longtask entry type is feature-detected and degrades to supported: false with zeroed counts rather than throwing.

SSH / remote — renderer-local. No transport, pty, or execution-host code touched.

Agents / integrations — agent-neutral. byInputSource splits by how the character was entered, not by which agent is running.

Backwards compatibility — TypingLatencyReport gains fields; existing ones keep their meaning. recordKeystroke takes a third source argument, and its only callers are in this file's module group.

Performance — inert until start(). While running, samples are bounded to a rolling 2000 per series, and the write wrapper is restored on detach.

Security — no new IPC, no new input path. It reads keystroke timings and byte counts, never keystroke content.

Known gaps

  • bytesPerKeystroke reports the whole echoing write for every keystroke that shared it, with coalescing as the divisor. That is deliberate — pre-dividing would lose "how many bytes did one redraw cost" — but the field name invites the other reading, and a consumer that ignores coalescing will overcount.
  • The probe still cannot see the composer in the Native Chat view: echo instrumentation attaches to xterm panes only, so a chat-side measurement needs a separate page-side probe.

norinus and others added 2 commits August 28, 2026 11:46
…robe

The probe missed CJK entirely: isEchoingKey drops a composing keydown, so
typing Hangul produced zero samples and the percentiles read as "no
latency". A composed commit is the keystroke that carries the echo, so
compositionend is where the timer starts.

Crediting only the oldest pending keystroke also biased the numbers: a TUI
redraws on its own clock, so a fast burst shares one redraw, and the rest
stayed pending forever — the surviving samples were the ones that had
waited longest. The whole waiting set is credited now, with `coalescing`
saying how many shared the write and bytes attributed to each.

Adds a main-thread blocking census (long tasks), an IME-vs-direct split
with a fixed-bucket histogram, and dispatchMs, which separates the
renderer's share from the host round trip.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…umbers

Two shared slots let one sample carry another's figures.

The coalescing count lived on the pane, so a second write parsing before
the render that drained the first batch overwrote it, and the earlier
samples reported the later batch's size. It is stored per keystroke now,
assigned where the echo is marked.

The blocking census measured up to whichever moment census() was called,
including idle time after stop() detached the observer, which divided the
same blocked milliseconds by a longer window and understated the share.
The window closes at stop().

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0a3c3865-5c87-4d08-b6cc-e51a77074196

📥 Commits

Reviewing files that changed from the base of the PR and between a63651e and 07fb209.

📒 Files selected for processing (2)
  • src/renderer/src/lib/typing-latency-diagnostic.ts
  • src/renderer/src/lib/typing-latency-main-thread-blocking.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/renderer/src/lib/typing-latency-main-thread-blocking.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 5 remain after this review.


📝 Walkthrough

Walkthrough

The typing-latency probe now supports direct and IME input classification. Echo samples include source, dispatch latency, coalescing, and write metrics. Source-specific reports include bounded percentiles, paint histograms, and IME commit counts. The probe also records main-thread long-task statistics and exposes them in reports. Latency samples use fixed buckets with an overflow bucket. Tests cover grouped echo attribution and independent coalescing counts.

Merge Risk: ⚪ Minimal · up to 07fb2

This PR updates localized renderer-side typing diagnostics without changing application behavior, privileges, or external interfaces. No actionable merge-blocking risk remains beyond normal checks and review.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 20 functions across 6 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the primary changes: IME commit recording and coalesced echo accounting in the typing probe.
Description check ✅ Passed The description is detailed and covers the change, rationale, linked issue, visual proof, testing, AI disclosure, review considerations, and known gaps. The template checklist and separate Notes secti…
Linked Issues check ✅ Passed The changes satisfy issue #16950 by recording compositionend commits, crediting all keystrokes in shared redraws, storing coalescing per keystroke, closing the blocking window at stop(), and adding di…
Out of Scope Changes check ✅ Passed The added histogram, dispatch, input-source, main-thread blocking, coalescing, and percentile logic directly support the missing measurements and defects described in issue #16950. The tests cover the…
Full details: Description check

Explanation

The description is detailed and covers the change, rationale, linked issue, visual proof, testing, AI disclosure, review considerations, and known gaps. The template checklist and separate Notes section are not reproduced, but the required information is otherwise substantially complete.

Full details: Linked Issues check

Explanation

The changes satisfy issue #16950 by recording compositionend commits, crediting all keystrokes in shared redraws, storing coalescing per keystroke, closing the blocking window at stop(), and adding dispatch, blocking, input-source, and paint-path measurements.

Full details: Out of Scope Changes check

Explanation

The added histogram, dispatch, input-source, main-thread blocking, coalescing, and percentile logic directly support the missing measurements and defects described in issue #16950. The tests cover the related regressions.

  • Fix all pre-merge checks with AI

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 546b60a6-d3ab-4064-83c0-e1f01258c533

📥 Commits

Reviewing files that changed from the base of the PR and between 25fa6a9 and a63651e.

📒 Files selected for processing (6)
  • src/renderer/src/lib/typing-latency-diagnostic-summary.ts
  • src/renderer/src/lib/typing-latency-diagnostic.ts
  • src/renderer/src/lib/typing-latency-echo-instrumentation.test.ts
  • src/renderer/src/lib/typing-latency-echo-instrumentation.ts
  • src/renderer/src/lib/typing-latency-input-source.ts
  • src/renderer/src/lib/typing-latency-main-thread-blocking.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review.

… commit characters

The two summaries sit side by side in one report and disagreed: the echo
summary is nearest-rank, the blocking census took floor(n * fraction), so
with 20 samples its p50 read the 11th value and its p95 read the maximum.

IME commit size counted UTF-16 units, so a supplementary-plane commit
counted as two characters.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nwparker

Copy link
Copy Markdown
Contributor

Closing as superseded: issue #16950 is fixed by merged PR #17171 (commit 17171), with acceptance coverage documented in #17172.

@nwparker nwparker closed this Aug 31, 2026
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.

[Bug]: The typing-latency probe records zero samples for CJK input

2 participants