fix: detect_anchors recovers consecutive instant-turn submissions via scripted timing#20
Merged
Merged
Conversation
…ons using known scripted timing Two client-side-only turns back-to-back (e.g. "/context" then "/fast on") have no real thinking-gap between them, so the input-band pixel signal never dips long enough to split them -- they read as ONE continuous group instead of two, undercounting detected prompt submissions and raising in author.py (#18). The fix adds a targeted recovery for the len(groups) < n case: when the turns.json plan already flags a run of >=2 consecutive turns as "instant" (written by gen_capture_tape.py's render(), consumed via plan["turns"]) and the shortfall is EXACTLY explained by that run collapsing into one detected group, split the merged group back apart using the deterministic scripted timing (INSTANT_SETTLE + pre_enter + each turn's type_dur) instead of re-detecting split points from pixels -- there is no pixel signal left to re-detect from. If the shortfall isn't fully explained this way, it still raises: this is a recovery for a known, deterministic failure mode, not a blanket excuse to swallow a genuine detection miss. Regression tests added in tests/test_detect_anchors.py reproduce the exact 3-vs-4 mismatch from the issue with a synthetic merged input-band signal, and confirm the existing "shortfall not explained" case still raises. Closes #18.
…tion, not a hardcoded constant The just-landed #17/#19 fix (native CLI menu turns, e.g. "/theme") gives those turns the SAME turn_plan["instant"] = True flag/shape as the no-menu INSTANT_COMMANDS from #16, but they can use a LONGER settle (NATIVE_MENU_SETTLE under --tmux) than the no-menu INSTANT_SETTLE. My detect_anchors.py recovery (previous commit) assumed every "instant"-flagged turn used the module's INSTANT_SETTLE constant -- correct for #18's own "/context" + "/fast on" repro, but silently wrong (not raising, just mis-timed) for a merged run that includes a native-menu turn. gen_capture_tape.py now records the settle it actually used per-turn in turn_plan["settle"]; detect_anchors.py's split reads that value (turns[idx].get("settle", INSTANT_SETTLE)) instead of assuming one constant, falling back to the old default for capture.json plans predating this field. Added a regression test with a mixed run (a native-menu-settle turn followed by a plain-instant turn) proving the recovery uses the per-turn value.
htlin222
added a commit
that referenced
this pull request
Jul 5, 2026
… same settle duration (#22) #20's recovery treated a whole maximal run of consecutive `instant`-flagged turns as one atomic collapse candidate (either all merge into one detected group, or none do). Real pixel behavior isn't atomic once settle durations differ within a run: a NATIVE_MENU_SETTLE (10s) turn is long enough to stay its own visually-distinct group even sandwiched between short INSTANT_SETTLE (1.8s) turns, so only the short-settle sub-run actually merges (issue #21). Fix: if the whole-run collapse doesn't exactly explain the shortfall, retry using maximal SAME-settle sub-runs within it as the collapse candidates instead (e.g. "/theme" stays isolated, "/context"+"/fast on" merge). The original whole-run attempt runs first and unchanged, so every existing recovery path (including the per-turn-settle test where a run already fully collapses) is untouched; the sub-run fallback only kicks in for shortfalls the whole-run math can't already account for. Closes #21. Claude-Session: https://claude.ai/code/session_012EwptawkV4dcGhoGefzxgV Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
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.
Closes #18.
The bug
Two client-side-only turns back-to-back (e.g.
/contextthen/fast on,with no real model-invoking turn between them) have no "thinking gap" for
detect_anchors.py's pixel-based turn detector to split on. The mergedinput-band signal reads as ONE continuous group instead of two, so the
detector undercounts prompt submissions and
author.pyfails with:The fix
detect_turns()already receives each turn'sinstantflag (written bygen_capture_tape.py'srender()intocapture.json's per-turn plan, perPR #16 / #19) via the existing
turnsparameter — no call-site signaturechange was needed.
When
len(groups) < n, a new recovery path checks whether the shortfall isexactly explained by one or more maximal runs of consecutive
instantturns collapsing into a single detected group. If so, it splits the merged
group back apart using the known scripted timing — each instant turn's
settle Sleep +
pre_enter+ the next turn'stype_dur— instead of tryingto re-detect the split points from pixels (there's no pixel signal left to
re-detect from). If the shortfall isn't fully explained this way, it still
raises: this is a targeted recovery for a known deterministic failure mode,
not a blanket excuse to swallow a genuine detection miss. The existing
len(groups) > n"strongest peaks" fallback is untouched.While rebasing onto
mainI picked up the just-merged #17/#19 fix (nativeCLI menu turns, e.g.
/theme), which gives those turns the sameinstantflag/shape but can use a longer settle (NATIVE_MENU_SETTLEunder
--tmux) than the no-menuINSTANT_SETTLE. My recovery originallyassumed a single hardcoded settle constant, which would have silently
mis-timed (not raised, just produced wrong offsets for) a merged run
containing a native-menu turn. Fixed by having
gen_capture_tape.pyrecordthe settle it actually used per-turn (
turn_plan["settle"]) and having therecovery read that value (falling back to
INSTANT_SETTLEfor oldercapture.jsonplans that predate the field).Tests
tests/test_detect_anchors.py::test_detect_turns_recovers_merged_consecutive_instant_group— reproduces the exact 3-vs-4 mismatch from the issue with a synthetic
merged input-band signal; confirmed it fails with the original
SystemExitbefore the fix.
tests/test_detect_anchors.py::test_detect_turns_recovers_merged_group_using_per_turn_settle— proves the recovery uses each turn's own
settlevalue, not a hardcodedconstant (regression test for the native-menu interaction above).
tests/test_detect_anchors.py::test_detect_turns_raises_when_instant_shortfall_not_fully_explained— confirms a shortfall NOT fully explained by instant merges still raises.
len(groups) > nguided-selectionpath and the plain under-detection raise) still pass unmodified.
Local suite: 131 passed, 0 failed (
.venv/bin/python -m pytest tests/ -q).Not done
Did not run a real end-to-end
record-session(capture → author → splice →overlay → panel → lint) against a live
claudesession — that needs severalminutes plus real API usage.
vhs/tmux/ffmpeg/edge-tts/claudeareall present in this environment, so it's possible to run later if desired,
but per the task's own guidance the unit tests (with a faithful synthetic
repro of the exact reported mismatch) are the primary bar here.
🤖 Generated with Claude Code