Skip to content

patch-cc 0.3.3 — follow the state into the stream store

Choose a tag to compare

@anfreire anfreire released this 19 Aug 22:51
· 16 commits to main since this release

Repairs live thinking on Claude 2.1.236. On that build live-thinking
lost its required prop-threading step, so the fixpoint dropped the whole
patch and thinking stopped streaming mid-turn — every other patch landed.
Upgrade and re-bake (patch-cc apply --from-cache, or re-run the menu).

What broke

A semantic move, not a spelling: 2.1.236 took the streaming state out of the
App component's useState and put it in an external stream store
(subscribe/getSnapshot/_publish — the useSyncExternalStore shape),
handing the reducer <store>.setStreamingThinking instead of a bare setter.
The resolution knew only the shape every archived build had carried — the
array pattern binding the handed setter — so it read a build that plainly
holds the state as holding none. It failed the way it is built to: loudly, by
name, with every other patch landing and the binary intact.

Upstream is converging on the feature from below: the store names the field
streamingThinking, its setter takes functional updaters (the contract the
spliced updates already speak), it hides a finished block after 30 s — its own
linger — and the cancel path now preserves interrupted thinking. What it still
does not do is accumulate thinking_delta text: the delta arm feeds a token
counter, and nothing renders live. The patch is still the feature.

The fix

What the handed setter is names the state both ways. A bare identifier is a
useState setter and the state is its array-pattern sibling, as before. A
member read names its store, and the state is the snapshot pattern the same
scope destructures from the hook call it hands that store —
{streamingToolUses:…}=useX(<store>) — extended with upstream's own field
name, or read from upstream's own binding the day they thread it themselves.
The pattern is proven the snapshot read by the store expression as the call's
only argument, one answer or none (js.only) — a second argument is a
selector whose result is no longer the snapshot. The insertion is witnessed
against the bundle's own naming of the field (the snapshot initialiser, the
publish call), so a renamed field reports the store instead of threading
undefined with every count green.

final-summary moved homes on the same build — an experiment gate now sits
between the thinking test and the summary it guards — so its guard is found by
what its condition tests, never by being the nearest if, bounded by the
function that declares the block.

Also in this release

  • The corpus is the whole published span now: one pristine binary per
    version, 2.1.210 → 2.1.236 (2.1.230 never shipped), 26 in all — every
    tree-move measurement is re-checkable rather than historical
    (docs/corpus.md).
  • The skip notes fold into a count: 4 conversation render(s), 3 with no live-thinking state in scope — one line where 57 identical sentences per
    sweep said less, and the per-build number (2 renders threaded through
    2.1.222, 1 after) is legible at a glance.

Swept over all 26 pristine builds: 2.1.236 flips to green at cand=14
level with 2.1.235 — and no candidate count moves on any older build.
Verified at runtime on patched 2.1.236: thinking streams mid-turn, drawn
inline while the spinner still counts.