Files
qwen-code/patches
e6e075996a fix(cli): break Ink useBoxMetrics commit-phase setState loop (React #185) (#11565)
* fix(cli): break Ink useBoxMetrics commit-phase setState loop (React #185)

* fix(cli): scope the ink useBoxMetrics loop guard per instance

The guard added to the vendored Ink patch kept its 32-entry budget in
module scope, so every useBoxMetrics instance shared one budget, and the
budget counted invocations rather than oscillation cycles. With 40 boxes
mounting in a single commit, instances past the 32nd invocation never
measured: hasMeasured stayed false for good, because no later commit
re-fires their layout listener. Hold the budget in a ref so each instance
refills on its own.

Cancel the pending budget-refill timer on unmount. Vitest fakes
setImmediate by default and vi.useRealTimers() discards a pending faked
immediate, so the stored handle stayed non-null after the timer was gone
and the counter never refilled.

Drop the functional same-value bail for setHasMeasured. The useState setter
already bails for an identical primitive, and the functional form cannot be
eagerly bailed - React has to schedule the render to evaluate the updater.
Reverted to upstream's setHasMeasured(Boolean(ref.current)).

Regenerate patches/ink+7.0.3.patch with patch-package instead of
hand-appending the hunk, so it lands in path order with an index line and
function-context headers. The regeneration also reformats the pre-existing
hunks (index hashes, hunk-context labels, offsets); that churn is cosmetic,
and the only content change outside the new hunk is one
"\ No newline at end of file" marker.

Adds packages/cli/src/ui/use-box-metrics-loop-guard.test.tsx, which renders
the real hook and pins three behaviours: an oscillating box settles without
React #185, a later resize still re-measures once an oscillation tripped the
guard, and 40 instances all measure within one commit.

* fix(cli): refill the ink useBoxMetrics budget from elapsed time

The measurement budget added to the vendored ink hook refilled itself from
a setImmediate callback. A test that installs fake timers only runs that
callback when it advances the clock, and the InputPrompt suite flushes
renders with microtask yields inside act(), so the refill stayed pending
and React's act queue never drained with it. Ten tests in that file each
burned their full 15s timeout; on CI, where the suite runs with --retry=2,
that single file outlived the 60 minute job limit and the lane was
cancelled without printing a summary.

Refilling from elapsed wall-clock time keeps the same per-instance budget
and the same protection against the commit -> layout listener -> setState
loop, and leaves no handle for a test to drain, so the unmount cleanup is
no longer needed either.

---------

Co-authored-by: yiliang114 <[email protected]>
Co-authored-by: yiliang114 <[email protected]>
2026-09-13 06:05:26 +00:00
..