perf(web): cut re-render storms and continuous repaints in bot chats - #299
leoisadev1 wants to merge 7 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Comments Outside DiffThese findings sit on lines the diff does not cover, so they could not be posted inline. Each one leaves this list once its file changes.
|
593527e to
2cd3105
Compare
5c13f71 to
8cc05ca
Compare
There was a problem hiding this comment.
Devin Review found 4 new potential issues.
🐛 3 issues in files not directly in the diff
🐛 Existing per-bot drafts disappear
When a draft exists under a v2 per-bot key, readBotDraft returns an empty string instead. The composer cannot restore that unsent message after reload.
🐛 Concurrent tabs lose unrelated bot drafts
When tabs edit different bots, writeBotDraft can overwrite one tab's draft with the other's stale snapshot. Both edits replace the same storage key, so one draft disappears.
⚠️ Every keystroke rewrites all bot drafts
Each composer keystroke calls writeBotDraft, which synchronously parses and rewrites every saved draft. Typing slows as unrelated drafts accumulate.
Thread refs are memoized on ids so chat markdown stops re-parsing per render, message rows are memoized with content-visibility, spoken text is cached per reply, the roster reads derived per-bot atoms, working avatars tick at ~30fps with stepped shimmer, and drafts persist on a debounce. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ross-tab draft clears Observe messages whenever the playback context is set, so a fresh thread baseline is not taken from the first reply. Reset the avatar eye write cache when the eye nodes are replaced. Drop pending draft edits for keys another tab changed so a delayed flush cannot restore a cleared draft. Memoize the playback message signature. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Dropping pending drafts on storage events trusted event delivery order, so a newer local edit could be lost to an older write from another tab. Store each draft's last edit or clear time and skip a pending flush only when storage already holds a newer one. Also format files the check flagged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Wall-clock stamps broke on clock rollback and same-millisecond writes. Each pending edit now remembers the storage version it saw when typed, and a flush writes only if storage still holds that version. Versions come from a stored counter that only grows. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Debouncing draft writes kept edits in memory, and every cross-tab ordering scheme on top of that left a way for an older edit to beat a newer write or clear. Store each draft under its own key and write it synchronously: a keystroke now costs one small write instead of parsing and rewriting every draft, and the last write wins as before. Drafts in the old shared map move to per-draft keys once per page load. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… cached Every change to how drafts reach storage opened a new cross-tab or upgrade ordering hole, and the saving was small. Restore the base draft store so drafts keep their existing, correct write-through behavior. The spoken-text cache cleared itself when full, so a pass over a chat with more replies than the limit missed on every revisit. Evict only the oldest entry and raise the limit to 2,000 replies. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
4557508 to
f537d9c
Compare
3fce541 to
ada302b
Compare
Stacked on perf/ws-payloads.
Bot chats re-parsed their entire transcript's markdown on every render because the runtime hooks memoized the linked thread ref on a freshly allocated target object, so downstream memoization never bailed out. The landing subscribes to the environment snapshot, meaning this fired on every streaming batch from any bot. Spoken-text derivation re-ran per message per render, message rows were unmemoized inline maps, working avatars ran a requestAnimationFrame loop at display refresh mutating SVG mask attributes, the status shimmer repainted text every vsync, the roster sidebar re-rendered all rows per shell event.
Thread refs are now memoized on their primitive ids. Message rows moved into memoized components with value-aware comparison and content-visibility on rows, chosen over a virtualized list because the conversation scroll area's follow-to-end logic would fight one. Spoken text is cached per reply and invalidated when text changes. The working avatar pose ticks near 30fps and skips unchanged DOM writes, while the shimmer and ultrathink animations use stepped keyframes with reduced-motion guards. New derived atoms map each bot to its latest thread with stable identity, so roster rows subscribe narrowly. A debounced draft store was tried here and dropped in review: every variant opened a cross-tab race, so draft writes stay synchronous. The spoken-text cache instead evicts its oldest entries rather than clearing wholesale, so long chats stay cached, and review also hardened the playback baseline and the avatar eye cache.
Verified with 305 roster and 169 lib tests passing, new tests for the spoken-text cache and its eviction, row memoization, and derived-atom identity, and a clean typecheck. An integrated pass on a fixture environment confirmed the chat renders and actions work. Not visually verified: streaming-state animations, which need a live provider turn.
Created with Claude Fable 5.1 in Claude Code.
🤖 Generated with Claude Code