Skip to content

fix(webview): stop flushing throttled state for partial say messages - #1852

Merged
edelauna merged 2 commits into
Zoo-Code-Org:mainfrom
daewoongoh:fix/webview-state-throttle-coalescing
Oct 1, 2026
Merged

edelauna merged 2 commits into
Zoo-Code-Org:mainfrom
daewoongoh:fix/webview-state-throttle-coalescing

Conversation

@daewoongoh

@daewoongoh daewoongoh commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Related GitHub Issue

Closes: #1851

Description

The webview state debounce (led by #1078) was effectively inert on the streaming path:
addToClineMessages called flushPostStateToWebviewThrottled() for every partial (partial === true) message. With lodash debounce configured as { leading: true, trailing: true }, a flush issued right after a leading-edge invocation has no pending trailing invocation to run, so it only cancels the trailing timer — which makes the next call hit the leading edge and post immediately. Result: one full-state post per message, defeating the debounce entirely.

This PR drops partial from the immediate-state condition. Partial say messages are safe on the throttled path: the trailing/maxWait post carries the message’s current text, so a messageUpdated dropped for a ts the webview does not know yet is superseded rather than lost. Partial asks are unaffected — Task#ask adds them without isAnswered, so they keep flushing through the ask clause and retain the ordering guarantee unanswered asks depend on.

Against lodash.debounce 4.0.8 with the provider’s own settings (500ms wait, 1000ms maxWait), 20 messages arriving 100ms apart produced 20 full-state posts with the interleaved flush vs 3 without it.

The existing Task-level suite mocked the provider, so it asserted flush was called rather than that posts were actually coalesced. Retargeted the partial-message test at the new contract and added a characterization test in ClineProvider.spec.ts that pins the debounce semantics against the real timer.

Test Procedure

  1. Type-check: pnpm --dir src exec tsc --noEmit (pre-push hook runs full turbo check-types).
  2. Run the targeted suites:
    • cd src && npx vitest run core/task/__tests__/Task.spec.ts
    • cd src && npx vitest run core/webview/__tests__/ClineProvider.spec.ts
  3. Manual: run a tool-heavy task and confirm the UI updates coalesce (far fewer full refreshes than messages).

Pre-Submission Checklist

Visual Snapshots

N/A (no user-visible rendered state change; behavioral bug fix).

Videos (interaction / animation only)

N/A.

Documentation Updates

  • No documentation updates are required.

Additional Notes

None.

Get in Touch

hehegwk_23849

`addToClineMessages` called `flushPostStateToWebviewThrottled()` for every
message with `partial === true`. With lodash debounce configured
`{ leading: true, trailing: true }`, a flush issued right after a leading-edge
invocation has no pending trailing invocation to run, so it only cancels the
trailing timer. The next call then hits the leading edge again and posts
immediately, so the debounce never coalesced anything on the streaming path.

Measured against lodash.debounce 4.0.8 with the provider's own settings
(500ms wait, 1000ms maxWait), 20 messages arriving 100ms apart produced:

  with the interleaved flush: 20 full-state posts
  without it:                  3 full-state posts

Because `requiresImmediateState` matched every partial first chunk, throttling
was effectively inert for tool-heavy tasks and the gray-screen OOM that Zoo-Code-Org#1078
set out to fix remained reproducible at high message counts.

Drop `partial` from the condition. Partial `say` messages are safe on the
throttled path: the trailing/maxWait post carries the message's current text,
so a `messageUpdated` dropped for a `ts` the webview does not know yet is
superseded rather than lost. Partial asks are unaffected — `Task#ask` adds them
without `isAnswered`, so they keep flushing through the ask clause and retain
the ordering guarantee unanswered asks depend on.

The existing suite could not catch this: every Task-level test mocks the
provider, so it asserted that flush was *called* rather than that state posts
were actually coalesced. Retarget the partial-message test at the new contract
and add a characterization test in ClineProvider.spec.ts that pins the
debounce semantics against the real timer.
@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (1)
  • coderabbit-review-active

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository: Zoo-Code-Org/Zoo-Code/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 73704535-be84-4c8b-890a-466ecf90f6ca

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: Zoo-Code-Org/Zoo-Code/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: a8dc2167-8530-4feb-96af-6b010132ccce

📥 Commits

Reviewing files that changed from the base of the PR and between 778ad3e and d03dfad.

📒 Files selected for processing (3)
  • src/core/task/Task.ts
  • src/core/task/__tests__/Task.spec.ts
  • src/core/webview/__tests__/ClineProvider.spec.ts

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

📜 Recent review details
🧰 Additional context used
📓 Path-based instructions (6)
Check persistence and lifecycle invariants: awaited atomic writes, rollback or explicit partial-failure behavior, cross-window state consistency, stale listeners/watchers, cancellation, idempotency, and safe restart/resume without lost or d...

⚙️ CodeRabbit configuration file

Files:

  • src/core/task/Task.ts
  • src/core/task/__tests__/Task.spec.ts
For persisted settings, verify the complete schema/storage/runtime/webview round trip, shared default semantics, and focused true plus false/unset tests.

⚙️ CodeRabbit configuration file

Files:

  • src/core/webview/__tests__/ClineProvider.spec.ts
Require regression coverage at the lowest valid harness with behavior-focused assertions, including relevant negative, error, false/unset, and boundary cases.

⚙️ CodeRabbit configuration file

Files:

  • src/core/webview/__tests__/ClineProvider.spec.ts
  • src/core/task/__tests__/Task.spec.ts
Check strict typing and exhaustive behavior across normal, boundary, error, cancellation, retry, and compatibility paths.

⚙️ CodeRabbit configuration file

Files:

  • src/core/webview/__tests__/ClineProvider.spec.ts
  • src/core/task/Task.ts
  • src/core/task/__tests__/Task.spec.ts
Verify extension/webview contracts, cancellation and error propagation, VS Code lifecycle correctness, and behavior under retries and partial failure.

⚙️ CodeRabbit configuration file

Files:

  • src/core/webview/__tests__/ClineProvider.spec.ts
  • src/core/task/Task.ts
  • src/core/task/__tests__/Task.spec.ts
Act as an adversarial second-opinion reviewer.

⚙️ CodeRabbit configuration file

Files:

  • src/core/webview/__tests__/ClineProvider.spec.ts
  • src/core/task/Task.ts
  • src/core/task/__tests__/Task.spec.ts
🔇 Additional comments (3)
src/core/task/Task.ts (1)

1164-1164: LGTM!

src/core/task/__tests__/Task.spec.ts (1)

2071-2072: LGTM!

Also applies to: 2077-2078

src/core/webview/__tests__/ClineProvider.spec.ts (1)

1023-1043: LGTM!


📝 Summary

Summary by CodeRabbit

  • Improvements
    • Partial messages now follow the throttled update path instead of triggering an immediate refresh. Unanswered prompts continue to appear immediately.

Walkthrough

Partial messages now use throttled state posting instead of triggering an immediate flush. Unanswered asks still trigger an immediate flush. Tests check partial-message updates and how flushes affect throttled posts.

Changes

Partial message state throttling

Layer / File(s) Summary
Throttle partial message state updates
src/core/task/Task.ts, src/core/task/__tests__/Task.spec.ts, src/core/webview/__tests__/ClineProvider.spec.ts
Partial messages use throttled state posting, while unanswered asks retain immediate flushing. Task tests assert that adding and updating a partial message does not flush. Provider tests compare posts with interleaved flushes against a burst without flushes.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to d03df

Partial-message state updates are throttled while unanswered asks retain immediate flushing. No concrete merge-blocking regression is established; the change appears ready to merge subject to normal checks.

Security Architecture Review

Security architecture risk: 🔵 Low · up to d03df

Streaming messages now use coalesced interface updates, while unanswered prompts still update immediately. No new access path or weakened security control was identified. Delivery after a failed interface post remains uncertain.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The assessed change affects when existing task-message text reaches its webview, not who may send it or a newly privileged destination.

Trust Boundaries and Controls

  • observed — The unanswered-ask flush remains in place before the created-message event, preserving the inspected ordering control for prompts requiring a response.

Resilience and Maintainability Implications

  • observed — Webview post failures can be dropped without delivery acknowledgement. Later state activity or abort offers another posting opportunity, but recovery after a final failed post was not established; this was not shown to be a newly weakened security control.
🚥 Pre-merge checks | ✅ 8
✅ Passed checks (8 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The change satisfies [#1851]. Task.addToClineMessages no longer flushes immediately for partial messages, so partial updates use postStateToWebviewThrottled(). Unanswered asks still use the immedi…
Out of Scope Changes check ✅ Passed All changed production code implements the debounce fix described in [#1851]. The Task test and provider characterization test directly verify partial-message throttling and flush behavior. No unrelat…
Regression Evidence ✅ Passed The changed throttling behavior has focused unit coverage. Task.spec.ts verifies that a new partial say message schedules postStateToWebviewThrottled without calling `flushPostStateToWebviewThro…
Security Boundaries ✅ Passed No changed path meets the security failure conditions. src/core/task/Task.ts only changes whether partial messages flush the existing throttled webview state post. It does not add secret/PII handlin…
Persistence Integrity ✅ Passed No changed persistence path exists. The production diff only changes when Task.addToClineMessages flushes webview state for partial messages. saveClineMessages still awaits saveTaskMessages, and…
Lifecycle Resource Cleanup ✅ Passed No changed lifecycle path creates or retains an unmanaged resource. The PR only changes Task.addToClineMessages so partial messages skip the existing provider-owned debounce flush; it does not add l…
Title check ✅ Passed The title clearly and concisely describes the main change: stopping immediate state flushes for partial say messages.
Description check ✅ Passed The description is complete and relevant. It links issue #1851, explains the cause and implementation, documents targeted tests and manual verification, and completes the required checklist sections.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

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.

@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Review status

Thanks for contributing. This comment tracks the review sequence and the next action.

Current step: Wait for GitHub to finish calculating mergeability.

Review-state labels are managed by this workflow; do not edit them manually. community-approved is managed the same way — do not add or remove it manually. It signals a fresh community code approval for the current head as an advisory priority only; maintainer review is still required.

@codecov

codecov Bot commented Sep 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions github-actions Bot added coderabbit-review-active Required CI passed; CodeRabbit review is active awaiting-coderabbit Waiting for CodeRabbit to approve the latest commit labels Sep 29, 2026
@github-actions github-actions Bot added awaiting-maintainer CodeRabbit approved; waiting for a human maintainer and removed coderabbit-review-active Required CI passed; CodeRabbit review is active awaiting-coderabbit Waiting for CodeRabbit to approve the latest commit labels Sep 29, 2026
@github-actions github-actions Bot added awaiting-maintainer CodeRabbit approved; waiting for a human maintainer and removed awaiting-maintainer CodeRabbit approved; waiting for a human maintainer labels Sep 29, 2026

@edelauna edelauna 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.

Nice - thank you.

@edelauna
edelauna added this pull request to the merge queue Oct 1, 2026
Merged via the queue into Zoo-Code-Org:main with commit 0aa636c Oct 1, 2026
17 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting-maintainer CodeRabbit approved; waiting for a human maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Tool-heavy runs refresh the whole UI for every message (lag/crash at high volume)

2 participants