Skip to content

fix(player): refresh track menus from deferred inventory pushes - #218

Merged
randrini merged 7 commits into
mainfrom
fix/track-menu-refresh
Oct 4, 2026
Merged

randrini merged 7 commits into
mainfrom
fix/track-menu-refresh

Conversation

@randrini

@randrini randrini commented Oct 4, 2026

Copy link
Copy Markdown
Collaborator

Problem

Related issue: N/A
Validation tasks: none

During ongoing playback the subtitle and audio menu lists keep the plan-time snapshot and never pick up the verified tracks; only closing and resuming the player shows the right lists. Follows up the #215 deferred-push work: identity-carrying inventory pushes are refused against URI-less settled plans, and the refused revision is then permanently recorded, so redeliveries die in the revision gate too.

Approach

Admit same-file inventory pushes against URI-less plans while still refusing genuine same-file sibling collisions and file-only pushes under a concrete live candidate; record only revisions actually folded so a refused revision can apply after a later matching rotation. Adds regression tests for the URI-less, file-only, ordering, and redelivery cases.

Validation

  • vitest usePlaybackSession.test.ts: 121 passed
  • prettier, eslint, tsc clean on touched files
  • Independent review of the first revision found wrong-candidate admit risks; all addressed and covered by tests that fail without the fix
  • Full CI green on the final head required

Risks

Track menus can now adopt inventory from a same-file push the old code dropped. Sibling collisions and cross-file rotations are still refused. No API, migration, or config impact.

Checklist

  • I read and can explain the complete diff.
  • This pull request addresses one concern.

AI Disclosure

  • Harness: OpenCode
  • Tool(s): OpenCode (explorer, fixer, oracle subagents)
  • Model(s): opencode-go/muse-spark-1.3-contributor
  • Involvement: AI-assisted
  • Adversarial review: oracle review of the first revision raised 3 high-severity wrong-candidate findings; all fixed with regression tests

@drondeseries

Copy link
Copy Markdown
Collaborator

Production-readiness review: Request changes (Grade 68/100)

Reviewed ff82b46 (frontend-only, +685/-70). CI is 8/8 green, and the revision-bookkeeping fix is sound — refusing a delivery is not applying its revision. But three P1 identity-authority problems remain. No merge.

Blocker 1 — P1: outgoing candidate can veto an exact match to the winning plan

web/src/player/hooks/usePlaybackSession.ts:324-357 — the new baseline loop runs before the replaced-plan exact-match check. Outgoing (file 8, A) + queued inventory (file 8, B) + replacement plan explicitly selecting (file 8, B): the loop rejects B against A, and the authoritative winner is never consulted. Winning candidate's track menus stay empty. This contradicts the documented precedence.

Fix: for a replaced plan with a concrete URI, decide by exact plan identity first. Keep collision checks for cases where the winner cannot resolve candidate identity.
Test: outgoing A → explicitly selected winning B, same file; B's inventory must apply.

Blocker 2 — P1: concrete-source exception still rejects the newer same-file source commit

usePlaybackSession.ts:324-332, 2321-2328 — URI-bearing source commits skip applied but not outgoing, and capture advances the live identity for every deferred source commit. Queueing source C then source B on the same file captures C as B's outgoing baseline, so B is rejected as a sibling collision. A URI-less winner can retain C even though transport committed B later. The new C-then-B test uses inventory events, which don't advance that capture baseline — it doesn't exercise this failure.

Fix: under an admissible URI-less winner, replay authoritative concrete source commits in sequence without letting an earlier captured candidate veto the later commit. Preserve inventory collision protection; don't broadly disable it.
Test: deferred source commits C → B, same file, URI-less winner; B must remain the final identity and inventory.

Blocker 3 — P1: URI-only admission can attach an outgoing inventory to an unrelated replacement file

usePlaybackSession.ts:359-363, 2607-2615 — the URI-less branch treats a missing file ID as admission, while the session-change exception permits identity-bearing entries across replacement. Outgoing file 7 has no URI; a URI-only inventory for its candidate arrives during a switch; the replacement settles on file 8 without a URI. Neither baseline proves a collision, so the inventory folds onto file 8. No spoofing required — just a delayed outgoing event. Absence of contradictory evidence is not evidence the URI belongs to the replacement file.

Fix: require corroboration for URI-only cross-adoption inventory. Preserve admission when the URI ties to the settled source; for ambiguous cross-file replacement, defer to a current-source refresh rather than inheriting the replacement file ID.
Test: outgoing file 7 → replacement file 8, URI-only outgoing inventory; it must not populate file 8.

CI

All 8 checks pass on this head — green CI demonstrates the covered cases but doesn't close the three predicate gaps above.

Acceptance: the three regressions above green, predicates fixed, focused tests + gate rerun. No middleware or /api/v2 changes needed. No merge until then.

…ushes

Reconcile deferred realtime pushes against the adoption that actually won,
closing three frontend review blockers.

- Decide a replaced plan that names a candidate URI by exact identity before
  the outgoing/applied ambiguity guards, so an outgoing candidate the session
  was leaving cannot veto the plan the server just selected.
- Carry whether an entry's arrival-time outgoing baseline was itself produced
  by an earlier deferred source commit from the same queue. A URI-bearing source
  commit is judged by arrival order, so such a queue-produced baseline no longer
  vetoes a newer same-file commit; an external poll fold still does.
- Require corroboration for a URI-only push against a URI-less winner: admit it
  only when the arrival-time live source was already on the settled file, so an
  unrelated replacement file never inherits its candidate.

Add regressions for all three; the primary relaxation (same-file
candidate-bearing pushes against URI-less plans; refused revisions refoldable)
is preserved.

@drondeseries drondeseries left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent review (oracle, final): 74/100 — needs work, not ready to merge.

The change is still needed after #220 (no file overlap; complementary).

Blocker — poll vs deferred-source ordering (web/src/player/hooks/usePlaybackSession.ts:356, :2624, :2675-2682):
A URI-bearing source commit ignores appliedIdentity even when a poll folded a newer candidate after it was queued. Example: outgoing file A, deferred commit selects file B/candidate X, a later poll applies B/candidate Y, the replacement plan names B without a URI. The outgoing baseline is cross-file, so X passes and overwrites Y. Sorting covers order inside the queue, not between the queue and an applied poll. :318-319 and :2669-2671 describe protection the code lacks.

Minimal fix: keep the applied-candidate guard for URI-bearing commits (tell older applied state apart from polls newer than the deferred entry if needed). Add regression tests for both orderings so a stale baseline cannot veto a newer commit.

Non-blocking: fix the stale wording at :318-319 and :1003-1007 (refused revisions are deliberately not recorded), and add the null-URI bogus-candidate test.

Please rebase onto current main and require green CI before merge.

randrini added 2 commits October 4, 2026 23:54
A URI-bearing source commit deferred behind a start/switch/replan ignored
the applied candidate entirely, so a poll that folded a newer candidate
after the commit was queued could be overwritten once the plan settled.
In-queue sequence sorting covers order inside the queue, not between the
queue and an applied poll.

Judge a URI-bearing commit against its own arrival baseline and the
queue's earlier commits by arrival order, but still veto it with a
candidate a poll folded after the entry was queued, tracked by a
source-identity transition clock. Record the poll's candidate so it
survives the winning plan's own adoption overwriting the menu mirror
before the flush runs.

Also fix stale wording about refused revisions being unrecorded at the
admissibility doc and revision-scope comment, and add a null-URI
bogus-candidate regression.
@randrini

randrini commented Oct 4, 2026

Copy link
Copy Markdown
Collaborator Author

Review follow-up addressed and pushed on fix/track-menu-refresh (8c83b8da, post-#220 main merged in ea463f12):

  • Poll vs deferred-source ordering (blocker): URI-bearing source commits now carry a transition-clock reading and are vetoed against a poll candidate folded after the entry was queued; both orderings covered by new regression tests (one verified to fail without the fix).
  • Stale wording: admissibility doc and revision-scope comment now state refused revisions are deliberately not recorded.
  • Null-URI bogus candidate: new test — a push with neither URI nor file identity does not fold against a URI-less winner.
  • Verified: 127 vitest passing, prettier clean, tsc clean. CI is the final gate.

@drondeseries drondeseries left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review (oracle, final): 93/100 — APPROVED for the targeted fix in 8c83b8d.

Verified present: arrivalTransitionClock on deferred pushes, identityTransitionClockRef + lastPollAppliedRef, appliedPollNewer threaded into the admissibility gate, baselines [outgoing] + poll-newer veto for URI commits. Clock raised by poll folds and plan adoptions; poll identity survives adoption; both orderings covered by regression tests.

The one Go-test failure on this head (TestServeExtractTextWindowDoesNotPoisonFullTrack, internal/playback/subtitle_cache_test.go:151) is unrelated to this PR: the branch touches only the two web session files, and the failing package is untouched. Reads as a main-side flake or ordering issue, not this change — a retry should confirm.

Merging once CI is green on a retry.

@randrini

randrini commented Oct 4, 2026

Copy link
Copy Markdown
Collaborator Author

Approval noted, thanks. Direct rerun of the failed Go-test run was rejected ("workflow file may be broken"), so CI was retriggered via empty commit 6cee4f1d to confirm TestServeExtractTextWindowDoesNotPoisonFullTrack is the unrelated flake. Merging once green.

@randrini
randrini merged commit f70f23e into main Oct 4, 2026
8 checks passed
@randrini randrini self-assigned this Oct 4, 2026
@randrini randrini added the enhancement New feature or request label Oct 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants