Skip to content

perf(client): stop idle Bot mode from re-rendering long timelines every frame - #2428

Merged
DoodleBears merged 5 commits into
mainfrom
claude/project-thread-ur3eyf
Oct 20, 2026
Merged

DoodleBears merged 5 commits into
mainfrom
claude/project-thread-ur3eyf

Conversation

@DoodleBears

@DoodleBears DoodleBears commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Vain · project thread

Why the change

Since 1.4.0, Bot mode keeps the main thread busy while idle on a long conversation, so the app feels laggy. 1.3.1 did not have this. 自 1.4.0 起,长对话在空闲时持续占用主线程,使用体验变卡;1.3.1 没有这个问题。

Refs #2413

The walking Tob companion (default-on since 1.4.0) repaints every frame. Three 1.4.0 changes made each repaint or re-render much more expensive on an un-virtualized timeline:

  1. Float bubble hover actions over the top-right edge #1843 Float bubble hover actions: every message keeps a hidden opacity: 0 toolbar placed with a transform. Chromium then rebuilds compositing for every bubble on each repaint (Layerize 29–35 ms/frame → 1.9 ms/frame). The toolbar is now placed with bottom; geometry is unchanged (toolbar bottom stays 8 px below the bubble top, verified).
  2. fix: discover compatible plugin updates when entering Bot mode #2177 plugin update discovery: BotPanel subscribed to the whole onboarding state, so the 5 s onboarding poll re-rendered the whole conversation. It now subscribes to the derived releaseDeferred boolean.
  3. Timeline QoL: new-messages divider, kept place, folding, per-message time, image preview, reactions, search #1769 Timeline QoL: fullTime built a new Intl.DateTimeFormat for every message on every render (≈325 ms per 15 s), and MessageFold created one ResizeObserver per message. Formatters are now cached and one shared observer serves the timeline.

Special things to note

  • Merge risk: low and reversible. Each fix is local (one CSS rule, one subscription selector, a formatter cache, a shared observer).
  • Verification:
    • Idle measurement, isolated DSH Host, 400-message group, 1440×900 headless Chromium, 15 s, BOTHARNESS_CLIENT_DIAGNOSTICS=0, CDP TaskDuration:

      Scenario 1.3.1 1.4.1 1.4.1 + this PR
      Companion off 3–6% 8–12% 3–4%
      Companion on ~30% (4.6 ms/frame) ~100% (40 ms/frame) ~40% (7.1 ms/frame)

      1.4.0 measured the same as 1.4.1 (~111% with companion on).

    • Remaining gap with the companion on (not in this PR): per-message fold ResizeObserver targets (~0.75 ms/frame), larger DOM per bubble (89k vs 81k nodes), and Tob being default-on. Follow-ups worth considering: virtualize the timeline or use content-visibility, observe fold sizes only while content can change, and pause the companion walk while idle.

    • pnpm --filter @botharness/client test: passes except the two pre-existing node:sqlite bundling failures that also fail on main. Updated styles-tokens test asserts the toolbar has no transform. pnpm changelog:check passes.

    • Hover toolbar checked visually in light and dark (below). Not run: native mobile (no mobile change).

  • CI test plan: affected (client only); no new config, test directory or repo-reading test.

Change outline

Hover toolbar position is unchanged / 悬浮工具栏位置不变:

Before After
Light before light after light
Dark before dark after dark
  • packages/client/src/client/styles.ts: .bh-bubble-toolbar uses bottom: calc(100% - x3) instead of top: 0 + transform.
  • packages/client/src/client/bot-main.tsx: cached Intl.DateTimeFormat; BotPanel subscribes to releaseDeferred(state).
  • packages/client/src/client/message-fold.tsx: one shared ResizeObserver.
  • changes/2428-idle-timeline.md: bilingual Fixed entry.

🤖 Generated with Claude Code

https://claude.ai/code/session_013edVLcdSJ7xqZLPLFFYCRT

…ry frame

Three changes from 1.4.0 made an idle Bot-mode conversation cost grow with
its history (#2413):

- The hover toolbar every bubble keeps hidden was placed with a transform,
  so each repaint (the window companion repaints every frame) rebuilt
  compositing for every message. It is now placed with bottom instead.
- BotPanel subscribed to the whole onboarding state, so the onboarding poll
  re-rendered the full conversation every five seconds. It now subscribes to
  the one boolean it reads.
- Each message built new Intl date formatters on every render, and kept its
  own ResizeObserver. Formatters are now reused and the fold check shares
  one observer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013edVLcdSJ7xqZLPLFFYCRT
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013edVLcdSJ7xqZLPLFFYCRT
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013edVLcdSJ7xqZLPLFFYCRT
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013edVLcdSJ7xqZLPLFFYCRT
@coderabbitai

coderabbitai Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 7 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6f6b8346-7004-4f18-a70e-e36d2be55386
📥 Commits

Reviewing files that changed from the base of the PR and between 9d0c4fb and 842e986.

⛔ Files ignored due to path filters: 4
  • .github/pr-screenshots/2413-idle-timeline/after-dark.png is excluded by !**/*.png
  • .github/pr-screenshots/2413-idle-timeline/after-light.png is excluded by !**/*.png
  • .github/pr-screenshots/2413-idle-timeline/before-dark.png is excluded by !**/*.png
  • .github/pr-screenshots/2413-idle-timeline/before-light.png is excluded by !**/*.png
📒 Files selected for processing: 7
  • changes/2428-idle-timeline.md
  • packages/client/src/client/bot-main.tsx
  • packages/client/src/client/message-fold.tsx
  • packages/client/src/client/styles.ts
  • packages/client/test/channel-notice-main.test.ts
  • packages/client/test/styles-tokens.test.ts
  • packages/client/test/timeline-working-rows-main.test.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@DoodleBears DoodleBears self-assigned this Oct 20, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013edVLcdSJ7xqZLPLFFYCRT
@DoodleBears
DoodleBears merged commit fb64050 into main Oct 20, 2026
11 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.

2 participants