fix(tui): quantize CardStream window so boundary cards stop oscillating#702
Merged
Conversation
Same shape as #549. In pinned mode scrollRows tracks maxScroll, and maxScroll = inner.height - outer.height. The items useMemo depends on both scrollRows and outer.height, so a sibling row toggling (ToastRail appearing, ThinkingRow flipping) shifts outer.height by ±1 → window moves → a card straddling the live↔spacer boundary flips → inner.height changes by Δ → setMaxScroll → scrollRows changes → useMemo recomputes → card flips back. With flash streaming compounding inside the same React batch, the chain reaches MAX_NESTED_UPDATES and aborts inside ink's useBoxMetrics (#700). Quantize the window position to VISIBLE_BUFFER_ROWS buckets. Sub-bucket scrollRows / outer.height wiggles now map to the same window, so items is referentially-stable across them and the feedback loop dies on the first re-render. - extract computeCardStreamItems as a pure exported function for tests - new tests/card-stream-items.test.ts asserts live-set stability across every sub-bucket scrollRows delta and a range of outer.height values Closes #700
esengine
added a commit
that referenced
this pull request
May 12, 2026
…il, CardStream fix (#705) npm-only release. The Tauri desktop source is in the repo and the CLI subcommand works, but installer bundles for macOS / Windows / Linux don't ship this round (separate release once signing's settled). Highlights: - Tauri desktop client with multi-tab concurrent runtimes (#689) plus a near-full polish pass: wallet balance, version chip, active- plan rail, abortable pause-gates, edit-gate pill, en + zh-CN i18n, shared pause-policy module dedup'd with the CLI TUI (#701) - checkpoint API + git-changes panel in the embedded dashboard (#682) - outside-sandbox file access approval modal (#696) - MCP loading pill + readiness gate on tool dispatch (#687) - escalate-after flag for flash → pro threshold (#699) Fixes: - CardStream Maximum-update-depth crash, quantize window so boundary cards stop oscillating (#700, #702) - `reasonix code` bridges config key to env + lazy subagent client so fresh installs can reach the setup wizard (#703) - pinned-mode scroll shrinks coalesced (#666), generic CSI key decode (#692), shell-confirm preview clamp (#691), frontmatter BOM/folded lines (#690), MCP error classification (#688), and more
ChasLui
pushed a commit
to ChasLui/DeepSeek-Reasonix
that referenced
this pull request
May 23, 2026
…ng (esengine#702) Same shape as esengine#549. In pinned mode scrollRows tracks maxScroll, and maxScroll = inner.height - outer.height. The items useMemo depends on both scrollRows and outer.height, so a sibling row toggling (ToastRail appearing, ThinkingRow flipping) shifts outer.height by ±1 → window moves → a card straddling the live↔spacer boundary flips → inner.height changes by Δ → setMaxScroll → scrollRows changes → useMemo recomputes → card flips back. With flash streaming compounding inside the same React batch, the chain reaches MAX_NESTED_UPDATES and aborts inside ink's useBoxMetrics (esengine#700). Quantize the window position to VISIBLE_BUFFER_ROWS buckets. Sub-bucket scrollRows / outer.height wiggles now map to the same window, so items is referentially-stable across them and the feedback loop dies on the first re-render. - extract computeCardStreamItems as a pure exported function for tests - new tests/card-stream-items.test.ts asserts live-set stability across every sub-bucket scrollRows delta and a range of outer.height values Closes esengine#700
ChasLui
pushed a commit
to ChasLui/DeepSeek-Reasonix
that referenced
this pull request
May 23, 2026
…il, CardStream fix (esengine#705) npm-only release. The Tauri desktop source is in the repo and the CLI subcommand works, but installer bundles for macOS / Windows / Linux don't ship this round (separate release once signing's settled). Highlights: - Tauri desktop client with multi-tab concurrent runtimes (esengine#689) plus a near-full polish pass: wallet balance, version chip, active- plan rail, abortable pause-gates, edit-gate pill, en + zh-CN i18n, shared pause-policy module dedup'd with the CLI TUI (esengine#701) - checkpoint API + git-changes panel in the embedded dashboard (esengine#682) - outside-sandbox file access approval modal (esengine#696) - MCP loading pill + readiness gate on tool dispatch (esengine#687) - escalate-after flag for flash → pro threshold (esengine#699) Fixes: - CardStream Maximum-update-depth crash, quantize window so boundary cards stop oscillating (esengine#700, esengine#702) - `reasonix code` bridges config key to env + lazy subagent client so fresh installs can reach the setup wizard (esengine#703) - pinned-mode scroll shrinks coalesced (esengine#666), generic CSI key decode (esengine#692), shell-confirm preview clamp (esengine#691), frontmatter BOM/folded lines (esengine#690), MCP error classification (esengine#688), and more
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Second instance of the same setState-loop class that #549 fixed. CardStream's
itemsuseMemo depends on bothscrollRowsandouter.height. In pinned modescrollRows = maxScroll = inner.height − outer.height, so when a sibling row toggles (ToastRailappearing,ThinkingRowflipping based onbusy/streaming),outer.heightshifts by ±1 → window slides → a card sitting on the live/spacer boundary flips →inner.heightchanges by Δ →setMaxScroll→scrollRowsupdates → useMemo recomputes → card flips back. With flash streaming compounding inside the same React batch, the chain reachesMAX_NESTED_UPDATES = 50and Ink throws insideuseBoxMetrics(use-box-metrics.js:51).#549 patched one specific cause (the "↑ earlier" hint changing
outer.heightbased onscrollRows). #700 is the residual case where any sibling toggle does the same thing.Fix
Quantize the window position to
VISIBLE_BUFFER_ROWSbuckets. Sub-bucketscrollRows/outer.heightwiggles now map to the same window, so the items array is referentially-stable across them. The boundary card doesn't move on its own anymore — only on real scrolls that cross a bucket.Trade: a single bucket (30 rows) of "stickiness" at scroll boundaries. Imperceptible in practice — the existing pre-
#700buffer was already 30 rows.Test plan
tests/card-stream-items.test.tscovers the invariant: the live-card set is identical for every sub-bucketscrollRowsdelta (0..VISIBLE_BUFFER_ROWS−1) and forouter.heightvalues in [20..40]Closes #700