perf(ui): cut composer per-keystroke costs (LC-229) #293

Merged
nrupard merged 2 commits from perf/lc-229-composer-keystroke-costs into main 2026-06-01 17:01:09 +02:00
Owner

Closes LC-229.

Three per-keystroke costs in room/composer.html compounded into laggy typing (staging report; siblings: LC-227 mention debounce #271, LC-228 NO_CONTENT #292).

Changes

  • rAF-batched auto-resize. The inline oninput did write -> scrollHeight read -> write, forcing a synchronous reflow per keystroke (~0.3-1ms even when height unchanged). Now wrapped in a single requestAnimationFrame with cancel-and-reschedule, so rapid input collapses into one frame's resize. Teardown cancels a pending rAF so it cannot run against a detached textarea (same pattern as the LC-216/LC-227 timer cleanup).
  • Success-only post-send focus. hx-on::after-request called ta.focus() unconditionally, including the LC-216 failure-debounced path; on mobile that snapped the keyboard back open 2s after submit. Focus now lives in the event.detail.successful branch only. Rapid-fire send (success path) unchanged.
  • Cached send button. The send-disabled toggle did document.getElementById('composer-send') every keystroke. Button captured once in the setup IIFE (mirrors the existing ta/slot/bcastSlot captures); toggle moved to an input listener closing over it.

Testing

  • just check clean (clippy standalone + saas + desktop, fmt).
  • just test and just test-saas pass, 0 failures.
  • Acceptance items needing a browser (DevTools layout-thrash check, mobile keyboard smoke) left for manual verification on staging.

🤖 Generated with Claude Code

Closes [LC-229](https://yt.a8n.run/issue/LC-229). Three per-keystroke costs in `room/composer.html` compounded into laggy typing (staging report; siblings: LC-227 mention debounce #271, LC-228 NO_CONTENT #292). ## Changes - **rAF-batched auto-resize.** The inline `oninput` did write -> `scrollHeight` read -> write, forcing a synchronous reflow per keystroke (~0.3-1ms even when height unchanged). Now wrapped in a single `requestAnimationFrame` with cancel-and-reschedule, so rapid input collapses into one frame's resize. Teardown cancels a pending rAF so it cannot run against a detached textarea (same pattern as the LC-216/LC-227 timer cleanup). - **Success-only post-send focus.** `hx-on::after-request` called `ta.focus()` unconditionally, including the LC-216 failure-debounced path; on mobile that snapped the keyboard back open 2s after submit. Focus now lives in the `event.detail.successful` branch only. Rapid-fire send (success path) unchanged. - **Cached send button.** The send-disabled toggle did `document.getElementById('composer-send')` every keystroke. Button captured once in the setup IIFE (mirrors the existing `ta`/`slot`/`bcastSlot` captures); toggle moved to an `input` listener closing over it. ## Testing - `just check` clean (clippy standalone + saas + desktop, fmt). - `just test` and `just test-saas` pass, 0 failures. - Acceptance items needing a browser (DevTools layout-thrash check, mobile keyboard smoke) left for manual verification on staging. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
perf(ui): cut composer per-keystroke costs (LC-229)
All checks were successful
check-secrets / Nosey parker (push) Successful in 3s
check-secrets / Kingfisher (push) Successful in 4s
check-secrets / TruffleHog (push) Successful in 5s
check-secrets / Nosey parker (pull_request) Successful in 3s
check-secrets / TruffleHog (pull_request) Successful in 3s
check-secrets / Kingfisher (pull_request) Successful in 5s
Check / clippy + fmt + tests (pull_request) Successful in 2m0s
0ce36bc3fd
Three per-keystroke costs in room/composer.html compounded into laggy typing on fast typists (staging report; sibling fixes LC-227 mention debounce, LC-228 NO_CONTENT response).

- Auto-resize forced reflow: the inline oninput did write -> scrollHeight read -> write, forcing a synchronous layout pass per keystroke. Now batched into a single requestAnimationFrame with cancel-and-reschedule so rapid input collapses into one frame's resize. Pending rAF is cancelled in the IIFE teardown so it cannot run against a detached textarea.
- Always-focus after send: hx-on::after-request called ta.focus() unconditionally, including the LC-216 failure-debounced path, which on mobile snapped the keyboard back open 2s after submit. Focus now fires only on the event.detail.successful branch (rapid-fire send case unchanged).
- Per-keystroke DOM query: the send-disabled toggle did document.getElementById('composer-send') on every keystroke. The button is now captured once in the setup IIFE (alongside the existing ta/slot/bcastSlot captures) and the toggle moved to an input listener closing over it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
perf(ui): consolidate send-button writers, fix review findings (LC-229)
All checks were successful
check-secrets / Nosey parker (pull_request) Successful in 3s
check-secrets / Kingfisher (push) Successful in 4s
check-secrets / TruffleHog (pull_request) Successful in 4s
check-secrets / Nosey parker (push) Successful in 5s
check-secrets / Kingfisher (pull_request) Successful in 5s
check-secrets / TruffleHog (push) Successful in 6s
Check / clippy + fmt + tests (pull_request) Successful in 2m11s
Create release / Create release from merged PR (pull_request) Has been skipped
3e8acc88bb
Code-review follow-up on PR #293; addresses 5 findings.

- Single shared send-disabled writer: window.__lcRefreshSend (same window-exposed pattern as __lcClearAttach) replaces the 5 independent writers of #composer-send.disabled (after-request, input listener, upload IIFE, voice IIFE, __lcInsertSlash). Fixes the predicate drift where __lcInsertSlash checked only ta.value.trim() and ignored an attached file_id, and removes the remaining per-call DOM queries in the upload IIFE and __lcInsertSlash. __lcClearAttach now also refreshes the button so removing an attachment with an empty textarea correctly re-disables Send.
- Decoupled the send toggle from the mention popover: __lcRefreshSend is registered before the IIFE's `if (!ta || !slot) return;` guard, so the toggle no longer silently dies if #lc-mention-popover is ever absent.
- Cancel pending resize rAF in the after-request success branch: with LC-228's 204 response a sub-frame round-trip can land before the last keystroke's rAF fires, which would re-measure the just-cleared textarea and undercut the 'auto' height by the border width.
- thread_panel.html now mirrors the composer: rAF-coalesced auto-resize (separate __lcThreadResizeRaf handle), cached send button via __lcThreadRefreshSend, and the same rAF cancel on successful send. Pre-LC-229 the thread reply textarea still had the sync write-read-write reflow plus an uncached querySelector per keystroke.
- Honest comment on the rAF resize: the forced layout is coalesced to at most one per painted frame (bursts: paste, IME, autocomplete inserts), not eliminated; the 'auto' reset is required for shrink-to-fit measurement.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard deleted branch perf/lc-229-composer-keystroke-costs 2026-06-01 17:01:09 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!293
No description provided.