mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
* fix(cli): make the ink useBoxMetrics loop guard independent of machine speed The measurement budget added in #11565 refilled on elapsed wall-clock time (32 measurements per 16ms), but React's nested-update cap is a fixed count of 50. The guard therefore only won that race while ~32 commits fit inside one 16ms window (~0.5ms each, including a Yoga layout pass). Under CPU contention or on a slower platform the window refilled mid-cascade, the count reset to 0, the budget never tripped, and React threw "Maximum update depth exceeded" (#185) out of the commit phase - which is what the regression suite caught on loaded CI runners, and why lastFrame() came back empty: onRender never ran. Spend budget only on measurements that actually change the metrics, since those are the only ones that schedule another render of the component, and refill it on events in React's own update chain rather than on elapsed milliseconds: a render the hook did not cause, a measurement that settled, and a terminal resize. The cap is 16 self-driven renders - the same burst the old budget allowed in practice, since updateMetrics runs twice per render - which sits well under React's 50 and no longer depends on how fast the machine is. No timer handle is introduced, so fake-timer tests still drain and React's act queue is unaffected. Adds a deterministic reproduction that pins machine speed by advancing performance.now() 16ms per read: it fails with the reported #185 stack before this change and passes after. Also adds regression coverage that the guard still stops an oscillation in far fewer commits than React tolerates, and that a box driven by external re-renders keeps reporting fresh metrics well past a single budget. Fixes #11817 Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-issue-patrol/jmu0yjkfs3z * fix(cli): settle the ink box-metrics budget per commit, not per render The guard settled its budget in the render body. React invokes a render body twice under StrictMode, which `DEBUG=1 npm run dev` renders the CLI inside: the first invocation spends budget and the second refills it, so the count never drains, the guard never trips, and React's #185 fires - the same failure #11817 reports, on the one code path that renders inside StrictMode. Settle the budget from the root layout listener instead. It runs once per commit and never during render, so a doubled render body cannot double count it, and the measurement that schedules a render marks the commit that follows as the hook's own: that commit spends budget, any other commit refills it. The cap of 16 and the resize refill are unchanged. Adds the StrictMode case to the regression suite - it fails with #185 against both the previous patch and unpatched ink - and rewords the wall-clock case to say what its performance.now() spy actually establishes. * style(cli): keep the guard test React import on one line Adding StrictMode to the existing one-line import is what Prettier produces; the expanded form failed the lint job's Prettier check. * test(cli): pin the resize refill, the budget bounds and the shared oscillator The resize refill is the only path that reaches an instance whose budget already tripped, and dropping it left every case green. The render bounds also admitted any constant up to React own cap and pinned no floor at all. Folding the counting fixture back into the oscillator removes the second copy of the oscillation rule, and the metrics dispatch is now skipped when the caller already knows the value did not change. * fix(cli): arm the ink box-metrics guard on a hasMeasured transition Round-2 review findings on the previous revision. The budget is now armed when `hasMeasured` flips as well as when a measurement changes: `setHasMeasured` schedules a commit of its own with no changed measurement behind it, so a consumer gating the measured element on `hasMeasured` took the refill branch on every commit and ran to React's cap. Only the transition arms it - that call is unconditional, and arming on the call would charge the external commits that keep a streaming box measuring. Test changes, all pinned by mutation: - The render counter is commit-scoped (reported from an effect, not the render body) and the budget bound now runs in both StrictMode modes. With a render-body counter the StrictMode arm reports 34 against `<= 24`. - The StrictMode case pins the clock, so it is red against the pre-PR guard instead of green; it now fails with `Maximum update depth exceeded` and the commit-phase stack. - Dropped the try/catch around that mount. It does not contain a #185 that escapes the commit phase - every case after it fails either way - so it only cost the stack trace. - Corrected the resize case's title and comment: any commit in the ink root refills a tripped instance, not only a resize. A resize is special in that ink recomputes it with no React commit at all. - Folded the clock pinning into one helper, restored in a `finally`. Verified at this head: guard suite 10/10; the 8-file `useBoxMetrics` consumer suite 173 passed; loaded A/B (6 CPU burners on 4 cores, 8 consecutive rounds, `--retry=0`) 8/8 green here against 8/8 red on the pre-PR guard. The patch round-trips: pristine `[email protected]` + `npx patch-package` reproduces `build/hooks/use-box-metrics.js` at blob `a6d1a4d`. Co-authored-by: Qwen-Coder <[email protected]> * test(cli): pin the multi-pass budget headroom and correct the guard comments The lower bound on the budget was only pinned as "more than 2 commits", and no cap tighter than the oscillator needs can violate that: the oscillator settles after cap + 2 commits, so a cap of 2 leaves the suite green while a consumer legitimately needing a second measurement pass freezes mid-convergence. Add a consumer that halves its own width on every measurement and assert the settled width reaches the frame. It settles at 3px on the current budget and freezes at 16px with the budget lowered to 2. The guard comments claimed a fixed per-commit trip depth, and that onLayout runs for any commit in the ink root. The charge happens per layout-listener invocation, and the listener is only registered while the measured ref resolves to a root, so a consumer that detaches its measured element is charged for roughly every other commit and trips at up to twice the count. Say that instead, and stop promising headroom the constant does not deliver for that shape. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmu1r4dew54 * test(cli): pin the commit-path refill and the gated cascade's depth Three review findings on the loop-guard suite, plus the PR description's stale numbers. The commit-path refill of an already-tripped instance had no witness: every other recovery in the file goes through stdout.emit('resize'), and the external-rerender case never trips (its count oscillates between 0 and 1), so the comment on the resize case asserted a guarantee nothing checked. The new case trips the oscillator, then bumps a sibling that owns its own state and shifts the oscillator's left, and asserts the oscillator runs a second cascade off that commit. Both refill paths are now pinned separately: deleting the onResize reset reddens only the resize case, while a partial refill or one gated on the instance not being tripped reddens only the new case (measured deltas 1 and 0, against 16 at this head). HasMeasuredGatedBox reports its commits through the same onRender counter its siblings use, and the case now asserts the cascade's depth (<= 34) alongside the frame. The frame alone is parity-blind - this fixture detaches its measured element every other commit, so whichever of its two states it freezes in, the last non-blank frame is the fallback render - and the suite stayed green with the budget at 23 or 26, which takes this cascade to 46 or 52 commits against React's own cap of 50. ConvergingBox drops the `from`/`floor` props no caller ever sets and inlines the 32 and 3 that its comment's arithmetic is about, so the multi-pass headroom property cannot be quietly disabled by a mount that parameterises it. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmu1zp0la5i * docs(cli): narrow the guard comments to the bound it delivers The vendored patch's two comments promised that "a cascade trips after the same number of charged invocations on any machine", which review measured to be false for two self-sustaining shapes: a cascade re-entering through a parent's state, and one where two instances in a single component refill each other's budget. Neither reaches the count - zero clamps - and only React's 50-update cap stops them, because the budget is per hook instance while the cap is per root. Both shapes are now named in the comment, with #11858 as the tracked rewrite. The detached-ref caveat and the wall-clock comparison are unchanged, and the hook is byte-identical apart from these comment lines: 12/12 tests pass. Refs #11817 * test(cli): pin the loop guard's exhaustion precondition and cascade floor Two recovery cases preconditioned on `afterTrip > 1` where the refill lines they pin depend on the budget having been exhausted, and the gated cascade case asserted a depth ceiling with no floor, so an early-settling hook passed both. Assert exhaustion (`>= 16`, the vendored budget) at both preconditions and floor the gated case's depth at the same constant. Mutation-verified on the real suite: a one-line `hasMeasured` latch drops the gated cascade from 32 commits to 2 and now reddens it (`2 >= 16`); a hook that dedupes seen layouts drops both recovery cases to `afterTrip=3` and now reddens them, where the resize case previously stayed green with the `onResize` reset deleted. Deleting either refill line alone still reddens only its own case. * fix(cli): scope the box metrics guard to the ink root --------- Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: yiliang114 <[email protected]>