Skip to content

perf(web): cut re-render storms and continuous repaints in bot chats - #299

Open
leoisadev1 wants to merge 7 commits into
perf/ws-payloadsfrom
perf/web-render
Open

leoisadev1 wants to merge 7 commits into
perf/ws-payloadsfrom
perf/web-render

Conversation

@leoisadev1

@leoisadev1 leoisadev1 commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

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


Devin Review

@vercel

vercel Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
akeru-bot-landing Ready Ready Preview Sep 26, 2026 5:54pm UTC

Request Review

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL labels Sep 26, 2026
devin-ai-integration[bot]

This comment was marked as resolved.

@greptile-apps

greptile-apps Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Medium risk] Refactors chat message rendering and animation performance.

No outstanding findings block merging.

Summary

The PR reduces rendering and animation work in web bot chats. Drafts continue to save synchronously. No new findings remain.

Reviews (7) · Last reviewed commit: "fix(web): restore synchronous draft stor..."

greptile-apps[bot]

This comment was marked as resolved.

@greptile-apps

greptile-apps Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Comments Outside Diff

These 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.

  • P1 Preserve drafts across tabs apps/web/src/components/roster/botDraftStore.ts:44 ▶

    When two tabs save drafts for different bots at nearly the same time, both can read the shared storage entry before either writes. Each then replaces that entry with its own update, erasing the other bot’s draft. The person loses that draft when they return to it or reload.

devin-ai-integration[bot]

This comment was marked as resolved.

greptile-apps[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

greptile-apps[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

greptile-apps[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration 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.

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.

Devin Review

Comment thread .changeset/perf-web-render.md
leoisadev1 and others added 7 commits September 26, 2026 15:18
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>

This branch had an error being deployed

1 failed deployment
Preview — ada302b3 Deployed Sep 26, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant