mirror of
https://github.com/cogwheel0/conduit.git
synced 2026-08-28 21:12:11 +00:00
* Fix streaming body strobing between content and loading skeleton (#540) Photosensitivity/seizure hazard: during a response the assistant body flashed at ~13Hz between the rendered markdown and the MarkdownLoadingSkeleton. Root cause: StreamingMarkdownWidget let the loading skeleton (and a second SizedBox.shrink blank path) REPLACE already-rendered content. When the turn phase latches to completed mid-response (responseDone gap / status+tool phases), the body is treated as non-streaming while answer tokens keep arriving; each non-streaming content growth left the compiled document stale-but-valid during the async recompile, and the gate regressed to the skeleton instead of keeping the rendered content. Make the skeleton/blank strictly a first-paint state: once a compiled document exists it is always rendered (a stale doc lags at most one frame and is superseded by _applyCompiledDocumentState), so the skeleton can only appear when there is no renderable content yet. Removed the now-dead _preserveStaleCompiledDocumentUntilFreshFinal field. Adds a deterministic regression test (delayed compile service) reproducing the non-streaming content-growth stale window; fails before the fix (skeleton over rendered content), passes after. * Clear stale content on scope change so a version switch can't show the old body (#541 review) Macroscope + Greptile flagged that the #540 fix ('always render a non-null doc') lets a reused StreamingMarkdownWidget show the previous message/version's content while the new body compiles asynchronously. Fix without re-introducing the #540 strobe: thread a clearStaleDocument signal so a stateScopeId change (a new message/version) clears the stale document — the skeleton covers the gap — while same-scope content growth keeps its document. Added a regression test (fails before: the previous version's text lingers on a scope switch). * Don't make a stale streaming document selectable (#541 review) Macroscope: the #540 fix removed the blank path that previously prevented a stale (non-streaming, not-yet-fresh) document from being wrapped in SelectionArea. During the responseDone gap the body is non-streaming while content still grows, so a stale doc was made selectable — re-enabling the concurrent-modification crash in Flutter's selection system. Gate SelectionArea on hasFreshCompiledDocument so a stale doc renders (no #540 flash) but isn't selectable until the compile settles. Extended the regression test to assert it. * Clear stale streaming document on scope change (#541 review) Greptile: the scope-change clear only ran on the non-streaming path. A scope change while streaming (e.g. switching from an old version back to the live, still-streaming current) kept the previous scope's compiled document visible until the deferred refresh landed, flashing a prior message under the new scope. Extend the existing clearDocumentWhenAsync mechanism to resolveStreamingPrepared (same async-only clear, so cached/sync compiles never flash), thread it through _resolveCompiledDocument's streaming branch, and carry the scope change into the deferred streaming refresh via _pendingClearStaleDocument so only the first post-scope-change refresh clears (same-scope growth still keeps its doc, #540). Adds a regression test for the deferred streaming scope-change path. (cherry picked from commit 11d75779fcab4c0fb3e338f8d4d4b96c7d950448) * Clear the stale streaming document immediately on scope change (#541 review) CodeRabbit + Greptile: the deferred streaming scope-change path only armed a pending clear (_pendingClearStaleDocument), consumed later by the scheduled refresh. build() now keeps rendering any existing compiled document while streaming (#540), so the previous scope painted for one frame under the new stateScopeId before the deferred refresh landed — exactly the flash this guards. Clear synchronously in didUpdateWidget instead: add MarkdownDocumentController.clearDocument() (cancels in-flight resolves, nulls the document, notifies) and call it on the deferred-path scope change; the scheduled refresh then compiles the new content. Removes the _pendingClearStaleDocument flag machinery entirely. The sync streaming and non-streaming paths already clear synchronously via clearDocumentWhenAsync, so all scope-change clears now happen within didUpdateWidget — no first-frame stale paint on any path. clearDocument() always invalidates pending async resolves first (even when no document is rendered yet) so a stale compile started under the old scope can't land after the clear. Test asserts the FIRST post-switch frame is already clear, that the new body stays absent until the compile is released (proving a genuine async gap), and pins the harness key so the switch exercises didUpdateWidget reuse rather than a remount. Verified: the first-frame assertion fails with the deferred behaviour, passes with the immediate clear. (cherry picked from commit 2798fe542d9477fe1fc3a4f518a2aac549282390) |
||
|---|---|---|
| .. | ||
| services | ||
| theme | ||
| utils | ||
| widgets | ||