fix: marking the same child twice should leave you with one child - #40
Merged
Conversation
A user marking their kid on the footage — play, pause, click, play, pause, click — ended up with eight athletes, seven of them named "My athlete", created inside three minutes, each bound to exactly one fragment. Scoring reads the focal flag, so a dozen careful selections collapsed to whichever click happened last. Their words: "it created each focal selection into a duplicate player and in the end it just uses one selection not all of them". `new` in the identify route means "I have not told you who this is", and it was creating an athlete unconditionally. The client sends `new` whenever its own athlete list has not loaded yet, which is every click made faster than a page reload — so the faster you worked, the more duplicates you got. It now prefers the athlete already being followed, and only creates one when there genuinely is none. The second half is which fragments survive. A caller that said `new` did not know who this was and cannot have sent the fragments already bound, so replacing the set silently discarded every earlier pick; those calls now add. The picker, which holds the whole selection and names the athlete, still replaces — that is what makes unticking a crop work. apps/web/src/identifytwice.test.ts drives the real route: three clicks with no loaded state give three athletes before this change and one athlete with three fragments after it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 10, 2026
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.
Reported from real use: "it created each focal selection into a duplicate player and in the end it just uses one selection not all of them."
Exactly right. The live project had 8 athletes, 7 of them "My athlete", created inside three minutes, one fragment each:
Why
newin the identify route means "I have not told you who this is", and it was callingaddAthleteunconditionally. The client sendsnewwhenever its own athlete list hasn't loaded — which is every click made faster than a page reload. So the faster you worked, the more duplicates you got, and each new athlete was made focal in turn. Scoring reads the focal flag, so all that careful selecting collapsed to the last click.The fix
Two halves:
newprefers the athlete already being followed and only creates one when there genuinely is none. Identifying is still the one step that can't be skipped, so first-run behaviour is unchanged.A
newcall adds; a named call replaces. A caller that saidnewdidn't know who this was and can't have sent the fragments already bound, so replacing silently discarded every earlier pick. The picker holds the whole selection and names the athlete, so it still replaces — that's what makes unticking a crop work.Test
apps/web/src/identifytwice.test.tsdrives the real route throughregisterActions, simulating clicks with no loaded client state.expected […] to have a length of 1 but got 3)574 tests pass, 8 skipped. Lint and build clean.
Not in this PR
Also raised in the same report, and worth separate changes: jersey/team shown in the picker so "#14 white" is distinguishable from "#14 black"; ball detection recall; and replacing the full-page reloads with live updates and progress so it's clear when to wait and when to act.
🤖 Generated with Claude Code