Verification mission · Perform redesign · perform-dev branch

Walk every inch of Perform — one account, three personas, ~200 visual states.

Brief Use browser-harness to navigate every Perform tab × persona × tab-state. One logged-in user (coach1@shot.com) carries coach + athlete + parent-of-minor privileges on the same team — no juggling profiles. Past events bootstrapped via the existing seed chain so pre / post / coach evaluations populate. Every captured frame gets crop-zoom analysis via ImageMagick. Anything not impeccable lands as a bead, gets fixed, then re-walks until the persona-tab cell is green.
/start-local perform-dev seed chain browser-harness /media-processing /impeccable bd create → fix → re-walk
Accounts
1 (triple-role)
Personas
3 (coach·athlete·parent)
UI states + eval flows
~200 + ~52
Past events seeded
11 + 12 historical
01

Milestones

Five linear phases. Each phase is independently verifiable. No advance until the gate clears — including phase 5's bead-fix loop.

Phase 1

Bootstrap account & environment

Confirm perform-dev branch online. Run seed-perform-dev-coach1-dual-role.sh (idempotent — already shipped). Patch athlete-5 (existing UUID 70792af0…) in place to account_status='parent_managed' + is_minor=true + DOB <13y. Re-run get_team_athlete_trends after patch and confirm row counts hold. Start Vite dev server in tmux against https://crmlprsypfztkqvueoez.supabase.co.

/start-localscripts/test-data/seed-perform-dev-coach1-dual-role.shPATCH profiles + parent_relationshipsdecision-A locked
Phase 2a

Verify framework loaded & assign to team

Presume scripts/evaluations/load_all_frameworks_v2.sh local has already loaded the 37 CSV files across 10 sports into public.evaluation_framework_metadata + public.evaluation_criteria on perform-dev. Assert: SELECT count(*) FROM evaluation_framework_metadata > 0 and matching evaluation_criteria rows for the team's sport. Stamp the team's framework: confirm teams.sport_type matches a loaded framework and that future event inserts will default events.sport_framework to a real framework (not the 'SHOT' COALESCE fallback). If the team isn't bound to a loadable framework, file as bead #0 before anything else.

scripts/evaluations/load_all_frameworks_v2.shteams.sport_type → frameworkevents.sport_framework
Phase 2b

Seed past events + every evaluation kind

Run seed-perform-dev-events.sh (11 events: 3 past · 2 today · 2 tomorrow · 4 future · 4 event_types). Run populate-historical-evaluations edge function (weeksOfHistory=4, eventsPerWeek=3, completion=85%) so trends compute, framework gaps populate, and FeedbackRAGQueue has owed feedback. Backfill coach_feedback text on ~70% of past response rows. Run intensity-aware score patch: intensity=3 → all three scores, intensity=0 → NULL all.

seed-perform-dev-events.shpopulate-historical-evaluationsplayer_evaluation_intensity
Phase 3

Walk every persona × tab via browser-harness

Login as coach1. Switch role to coach → walk Spotlight / Events (4 modes) / History (Team + Athlete) / Club. Switch role to player → walk Spotlight / Events / History / Club. Switch role to parent, pick the minor child → walk Spotlight / Events / History / Club. For each tab capture: empty / loading / populated / one sheet open. ~75 base tab-state frames; ~125 sheet/modal overlays.

browser-harnessRoleSwitchService.switchRole?tab= URL param
Phase 4

UI impeccability pass — pragmatic bar, ImageMagick crop & zoom

Per captured frame: detect tall screenshots (height > 4000px) → split into 3000px sections. Targeted crops at +150% zoom on header / sub-nav / card row / sheet header / drag-handle area. Apply -auto-level -adaptive-sharpen 0x1.5 for legibility. Pragmatic bar (locked): block on structural defects (dead buttons, wrong copy, broken layout, missing data, "Player" instead of "Athlete", wrong brand-pillar accent for the persona). LOW-severity polish (1-2px spacing nits, contrast AA-but-not-AAA, font-weight micro-drift) batched into ONE perform-polish-pass bead per persona — ship after every cell is structurally green.

/media-processing/impeccable craftImageMagickdecision-B locked
Phase 5

Defect → bead → spin-off agent → fix into branch → re-run · halt after 3 clean passes

Every non-impeccable finding (UI defect, dead button, broken eval submit, RPC failure, wrong copy, wrong pillar accent): bd create --type=bug with screenshot + crop + zone + persona×tab×eval-kind address. Group siblings under one parent bead. Spin off a focused agent (superstar-engineer for full-stack, focused-repository-analyzer for diagnosis-only, backend-developer for RPC/RLS) to investigate and fix the issue into the same feat/perform-redesign branch (no PRs, atomic commits per the project rule). After the fix commits, re-run the affected matrix cell + its eval-flow row in browser-harness. Mark the cell green only when the fix sticks. Halt rule (locked): stop when the matrix is all-green for three consecutive walks. Stricter than the default 2-walk rule — catches intermittent regressions that only surface on the second re-render.

bd createsuperstar-engineerfocused-repository-analyzerbackend-developerfix into feat/perform-redesignre-run-affected-celldecision-C locked: 3-walk converge
02

Single-account multi-persona architecture

One auth identity. profile.privileges = ['member','coach','player','parent']. RoleSwitchService writes profiles.preferred_role + localStorage shot_current_role. Perform.tsx switches the rendered sub-page; the URL stays at /perform.

coach1@shot.com single Supabase auth user privileges: [member,coach,player,parent] RoleSwitchService profiles.preferred_role localStorage shot_current_role Perform.tsx switch(currentRole) /perform?tab=<t> CoachPerform team_coaches row · teal accent Spotlight · CoachTriage D1 Events · 4-mode toggle History · Team + Athlete Club · merged Team+Admin + 8 sheet kinds feedback · event · improver assessment · announcement evals-outstanding · eval-row · team PlayerPerform team_members role=player · purple accent Spotlight · AthleteStoryFeed D2 Events · Week / Month History · premium-gated viewer Club · safe roster (no PII) + 5 sheet kinds feedback · event · self-eval announcement · eval-row + AthleteIDPReport modal ParentPerform parent_relationships · gold accent Spotlight · ChildFocused D6 Events · child filter History · guardian role Club · safe roster (no PII) + 6 sheet kinds event-parent · parent-eval feedback · announcement · eval-row + AddChild / JoinClub modals useCurrentRole() cross-tab sync when current = coach when current = player when current = parent (+ child) Same login. Switch via the role-switcher → page re-renders. No re-auth, no second account.

Solid grey = synchronous render path. Dashed clay = role decision branch driven by RoleSwitchService.

03

Seed phase DAG — past events & all eval kinds

Phases 1-2 (frameworks + team) already seeded on perform-dev. Phase 3 onward is what this mission writes. Triggers fan out: every events insert + event_participants insert auto-creates evaluation rows. Coach feedback is the only manual PATCH.

Phase 1 ✓ done evaluation_framework_metadata evaluation_criteria · public (SHOT-2025 already shipped) Phase 2 ✓ done teams · team_members · team_coaches parent_relationships (coach1→child) privileges array stamped Phase 2.5 patch profile: account_status='parent_managed' date_of_birth → < 13y is_minor=true · parent_id=coach1 Phase 3 · insert events 11 rows · training·match·assessment·recovery 3 past · 2 today · 2 tomorrow · 4 future assess_enabled=true Phase 3b · event_participants 55 rows · 11 events × 5 athletes invitation_status='confirmed' role='player' Phase 3c · intensities player_evaluation_intensity 5 rows · per athlete mix of 0,1,2,3 for coverage Phase 4 · auto-trigger fan-out (DB) auto_create_evaluation_on_player_add() fires → evaluations.evaluations · 55 rows → evaluations.evaluation_responses · ~275 rows (NULL scores) Phase 5 · score backfill PATCH coach_score / pre_score / post_score ~70% of past responses intensity-aware (3→all · 1→coach only) Phase 6 · verify trends get_team_athlete_trends(team, 30) returns top/bottom 3 FeedbackRAGQueue has owed rows · History chart populates DB trigger fires

Green nodes = already on perform-dev. White = inserts. Dark = auto-triggered cascade. Clay = manual PATCH. The fan-out from Phase 3b → Phase 4 is the load-bearing trigger — no app-code is involved.

04

Verification matrix — every cell must go green

4 tabs × 3 personas × 4 base states = 48 base cells. Add ~11 sheet-kind overlays per persona and the modal interrupts and the count lands near 200. The "states to verify" column is the minimum frames captured per cell — empty / loading / populated / one card-tap.

Persona Tab Surfaces & sub-components Sheet kinds opened States
COACHSpotlightFeedbackRAGQueue · NextEventCard · TopMoverCard · AssessmentList · AnnouncementsDigest · 3-CTA row (Create Event / Announce / Eval)feedback event improver assessment announcement evals-outstanding10
COACHEvents4-mode toggle (Week/List/Month/Past) · EventFilterCard (team + event_type chips) · DayHeader · EventCard · EventListRow · PastEventsAccordionevent12
COACHHistoryTeam mode: Match Record · Framework Gaps · Athlete summary · Athlete mode: chip-rail picker · drill-down EvaluationFocusInlineSnapshot8
COACHClubClubTeamSwitcher pill · TeamRosterTab · AdminActionCard grid (Settings / Intensity / Framework / Clubs)team5
ATHLETESpotlightAthleteStoryFeed D2: coach-feedback card · self-eval-due card (48h gate) · next-event card · streak badge · My Development CTAfeedback self-eval event7
ATHLETEEvents2-mode toggle (Week/Month) · RsvpSummaryCard · DayHeader (purple accent) · EventCard · PastEventsAccordionevent8
ATHLETEHistoryPerformanceHistoryViewer (premium-gated): chart · SeasonSelector · EventTypeFilter · PerformanceInsights · CategoryDrillDown6
ATHLETEClubSafeRosterList header · roster grid (display_name + avatar + position + jersey, NO PII)4
PARENTSpotlightChildIdentityCard · NextEventCard · ParentEvalOwed banner · ChildEvaluationHistory · "View Development" CTA · ParentTeamSwitcherparent-eval event7
PARENTEvents2-mode toggle · ChildSelectorChip · RsvpSummaryCard (child) · DayHeader (gold accent) · EventCard · EvalOwedCard · PastEventsAccordionevent-parent9
PARENTHistoryPerformanceHistoryViewer scoped to activeUserId · viewerRole='guardian' · same drill-down as athlete6
PARENTClubHeader (child + club + team) · SafeRosterList4
TOTAL · per-cell average ×4 frames~86
+ ~11 sheet overlays × 3 personas × 4 sheet-states = ~125 sheet/modal overlays + edge cases~125
GRAND TOTAL — frames the mission captures~200

Persona-pill colour matches the brand-pillar accent used inside that persona's UI (teal / purple / gold). The mission only marks a cell green when every state for that cell renders impeccably.

04b

Evaluation flows — submission states, per persona × per kind

The matrix above counts tab-level frames. Evaluations are flow-based — each one is a multi-step submit interaction that crosses Spotlight (entry banner) → sheet (form) → submit → success → reflect back on Spotlight/History. Every cell below also has to go green for the persona-tab cell above to be considered done.

Persona Eval kind Entry points Submission states to verify DB column written
ATHLETE Pre-eval
(self · question_pre · answers_pre)
Spotlight self-eval-due card (48h gate) · Events eval-owed banner on a future event empty form · partial fill · all sliders set · submit pending · success · past-deadline locked · validation error on empty submit evaluation_responses.pre_score · evaluations.pre_submitted_at
ATHLETE Post-eval
(self · question_post · answers_post)
Spotlight reflect-prompt card (post-event window) · Events past-event card · History past-event row empty · partial · submit · submitted · reflect/comments composed · past window locked evaluation_responses.post_score · evaluations.post_submitted_at
ATHLETE Coach feedback view
(read-only, gated by share_coach_feedback)
Spotlight feedback card · History per-event drill new unread badge · marked-seen state · "no feedback shared" empty · share_coach_feedback=false (hidden) evaluation_responses.coach_score read · evaluations.coach_submitted_at read
PARENT Parent-eval
(on behalf of minor child)
Spotlight parent-eval-owed banner · Events EvalOwedCard on child's event empty · partial · all set · submit · submitted · "consent_revoked" gated (banner suppressed) · child not parent_managed (banner suppressed) evaluation_responses.pre_score (shared row · RLS via parent_relationships.verified_at)
PARENT Coach feedback for child
(read-only via guardian role)
Spotlight ChildEvaluationHistory row tap · History drill with viewerRole='guardian' populated card · "no feedback shared" empty · share_coach_feedback=false (hidden) · child-deselected placeholder read only (no write) — bound by evaluations.is_parent_of RPC
COACH Coach scoring
(per athlete · per event · per question)
Spotlight FeedbackRAGQueue row tap · evals-outstanding sheet list · Event sheet → "view evaluations" empty (NULL scores) · partial scoring · all sliders set · save draft · publish · share_coach_feedback toggle on/off · RAG dot updates (red→amber→green) · validation on incomplete evaluation_responses.coach_score · coach_evaluator_name · evaluations.coach_submitted_at
COACH Coach feedback compose
(free-text per athlete)
eval-row sheet from FeedbackRAGQueue · per-athlete drill empty · text entered · character limit hint · save-publish · share-or-keep-private toggle · already-published shows as "edit" evaluation_responses.coach_feedback · share_coach_feedback
COACH Bulk eval review
(event-level grid)
Event sheet → "View evaluations" drill (5 athletes × question grid) all NULL · mixed (some scored, some pending) · all scored · in-session vs evaluating vs completed event-status variants multi-row PATCH via get_responses_by_evaluation_ids RPC
EVAL FLOWS · sum of submission states ~52

Three principles for the eval walk: (1) every kind exercised at least once with valid data — submit succeeds, DB row updates, sheet closes, UI reflects; (2) the corresponding error path exercised — empty submit, past-deadline, consent-revoked; (3) RAG-status semantics verified across the FeedbackRAGQueue after each coach scoring run.

Cross-persona invariant the walk MUST check: a coach publishes feedback for athlete-3 with share_coach_feedback=true → switch to athlete role → that feedback appears in athlete's Spotlight; switch to athlete-3's parent (coach1 acts as parent of athlete-5 patched minor; for athlete-3 use the role-switch surrogate where applicable) → feedback appears in ChildEvaluationHistory. Same coach toggles share_coach_feedback=false → both views update to "no feedback shared". This is the load-bearing visibility-gate test.

05

What each captured frame gets put through

Browser-harness captures full-frame. /media-processing handles the lens. /impeccable reads the lens output and produces verdicts.

browser-harness click → screenshot() full-frame PNG split if > 4000px ImageMagick crop 100%×3000 sections targeted crops header · subnav · card sheet header · drag-handle +150% zoom -auto-level -adaptive-sharpen 0x1.5 /impeccable craft typography · contrast · spacing pillar accents · copy ("Athlete") cell green matrix cell flips advance to next defect found bd create --type=bug → fix → re-walk this cell

Frame in. Lens applied. Verdict out. Defect routes to bead-create-fix-revalidate. No cell ships orange or red.

06

Risks & mitigations

Risk
Sev
Mitigation
Same-account role switcher may not be wired on every Perform sub-page, leaving Spotlight stuck on the persona the user logged in as.
HIGH
Phase 1 includes a hard switcher check: switch via the UI, then verify profile.preferred_role + localStorage + page contents all flipped. If any layer lags, file as the first bead.
RLS cascading timeout when the seed runs against authenticated client instead of service_role — eval_responses queries can timeout at the 8s gate.
HIGH
All seed writes go through PostgREST with the service_role key. All trend/feedback reads from the UI go through the SECURITY DEFINER RPCs (get_team_athlete_trends, get_event_responses, get_responses_by_evaluation_ids).
Patching an existing seeded athlete to parent_managed may collide with existing evaluations and break trend computation for that athlete.
MED
Use a "fresh" athlete (athlete-5 of the seeded 5) for the minor patch, leaving athletes 1-4 untouched. Re-run get_team_athlete_trends after the patch and confirm row counts.
Browser-harness session can wedge if Chrome's remote-debugging socket dies mid-walk (Apple notarisation popups, profile-picker race).
MED
Pin the Chrome profile via the harness's bootstrap. On wedge, restart_daemon() then re-walk only the affected cell — the matrix tracks progress per-cell so resume is trivial.
"Impeccable" is subjective. Different runs may grade the same frame differently and the matrix never converges.
MED
Lock the rubric in Phase 4 against .impeccable.md at the repo root: typography hierarchy (≥1.25 ratio), WCAG-AA contrast on text, brand-pillar accents per persona, "Athlete" not "Player", no orphan loaders, no dead buttons, drag-handle visible, sheet header non-duplicated.
Defect-to-bead-to-fix loop runs hot and starts cherry-picking trivial polish at the expense of structural issues.
LOW
Beads tagged with severity: field. Each loop pulls highest severity first. Polish only after every cell is structurally green.
07

Decisions locked

Stevie reviewed the 3 open questions and locked the three knobs below before mission generation. All three flow into the /make-a-goal mega-prompt.

A · Minor child — Patch athlete-5 in place
Update existing player UUID 70792af0… directly: account_status='parent_managed', is_minor=true, date_of_birth < 13y, parent_id=coach1. Reuses every seeded event + evaluation row. Re-run get_team_athlete_trends immediately after the patch to confirm trend math still computes; if it breaks, that's bead #1.
→ Phase 1 step 3
B · Impeccability bar — Pragmatic, structural first
Block on structural defects (dead buttons, broken layout, missing data, wrong copy like "Player" instead of "Athlete", wrong pillar accent for persona). LOW-severity polish (1-2px spacing nits, AA-but-not-AAA contrast, font-weight micro-drift) batched into ONE perform-polish-pass bead per persona. Polish wave only after every structural cell is green.
→ Phase 4 lens config
C · Halt rule — 3 consecutive green walks
Stricter than the default 2-walk rule. Catches the intermittent regressions that only surface on a second re-render (HMR race, stale cache, subscription reconcile). Costs ~30% extra runtime in the worst case where one persistent flake keeps tripping a single cell — accepted.
→ Phase 5 termination gate