Workout Parse — QA Plan
Manual test script for the paste-to-structure flow. Automated coverage exists (apps/web/e2e/specs/paste-workout.spec.ts + unit/integration specs); this plan is the human pass over the pieces automation can’t judge — real LLM output quality, copy, RTL, and the cross-org/tier gates.
Setup
- Org on tier Pro (has
workout_builder), owner role. - Canonical exercise library seeded (
pnpm seed:movements). ANTHROPIC_API_KEYset (scenarios 4+ make one real Sonnet call each, ~5–15s).- A second org on Lite tier for scenario 3.
Sample paste (used in several scenarios):
Strength
Back Squat 5x5 @ 80%
Metcon — AMRAP 12
10 Thrusters 42.5/30
15 Pull-ups
200m RunScenarios
- Freeform first.
/dashboard/workouts/newopens freeform directly. - Translation is available. Clicking “Convert to structured workout” replaces the freeform form with the parse input on the same route.
- Tier gate. On a Lite org,
?mode=translateshows the upgrade card, never the textarea. Direct APIPOST /parse→ 403 (tier). - Golden path — paste, preview, commit. Paste the sample text and submit. Expect the original source with movement highlights alongside two structured sections. Commit → success toast → structured workout opens in the normal builder. Its description contains only explicit coach comments, not the pasted source; the parse job retains the complete source. For the Service Cup fixture, verify two sections/nine movements, Part A cap 15 minutes and shuttle definition, female 95/male 135 lb on its four lifts only, and Part B start 900/duration 180 seconds.
- Non-English paste. Paste a Hebrew workout (e.g.
אימון כוח: סקוואט 5x5). Expect a normal preview — no language error, source retained literally; movements the catalog can’t match showunresolvedchips. Then paste English programming under a Hebrew coaching preamble: the preamble becomes the coach note and the sections carry the English programming. The job row’slanguageholds the detected tag. - Multi-workout warning (D4). Paste a week of workouts separated by day headers (
Day 1 …\nDay 2 …). Expect the warning banner (parse-multi-warning) and a preview containing only the first day’s content; later days appear nowhere in the tree. - Adjustment — pick a candidate. Paste text with an ambiguous movement (e.g. “DB press”). An amber
suggestedchip appears; clicking opens the popover showing the mention, the raw line, and up to 5 candidates with qualitative confidence labels (no raw numbers). Pick one → chip clears, outstanding counter decrements. - Adjustment — create org-local exercise. Include a made-up movement (e.g. “Bosu burpee slam”). Red
unresolvedchip → popover → “Create new exercise” (name prefilled) → create. Expect success toast, movement resolves to the new exercise. Then verify in a second parse of the same text that the new exercise now auto/suggest-matches (embedding enrichment on create, D10 — allow the queue a minute). - Adjustment — drop to note. On another unresolved movement choose “Drop to note”. The movement row leaves the tree and its raw line appears in the section description.
- Commit gating. While any suggested/unresolved movement is unactioned: commit button disabled, “N movements need attention” counter visible, tooltip on hover explains why. After actioning all → enabled.
- Discard. Click discard → confirmation dialog → confirm. Toast, back to
the root creator’s freeform form. Re-open the job via its
?jobId=URL: preview renders but no commit/discard bar (terminal status). - Refresh / deep link. After a parse, reload the
?jobId=URL: the same draft renders (GET endpoint), including chips and remainder. Open the same URL as a coach of a different org → 404/not-found behavior, never another org’s draft. - “Convert to structured workout” flow (entry b). Create a freeform workout with body text, save, reopen it, and press “Convert to structured workout”. Parse + commit. Expect the same workout id in structured mode with sections and the old freeform body cleared. An explicitly supplied coach description must survive; the parse job retains the original body.
- Too-long input (D13). Paste > 10,000 chars. The char counter turns destructive and submit is disabled client-side; forcing the API call returns 422
parse_too_long. - Budget breach. With the org at/over its AI budget (or tier caps lowered in a dev DB): submit → inline 429 error with the localized budget message including the period. No
ai_parse_jobsrow is created (blocked before the LLM call). - Member role. Signed in as a member: no entry points; direct API calls to all four parse routes → 403.
- RTL pass (HE locale). Switch the dashboard to Hebrew and rerun scenarios 4–11 visually: page layout mirrors correctly (logical properties); the paste textarea honors
dir="auto"(English paste stays LTR inside an RTL page); chips, popover, commit bar, dialogs, and toasts are correctly aligned and fully translated (workouts.parse.*— no English fallbacks); prev/next-style arrows are NOT double-flipped. - Observability. In PostHog (production/staging with a real key): a parse run emits
parse.requested→parse.completed; a commit emitsparse.committedwithzeroEdit/editDistance; blocked attempts emitparse.blockedwith the rightreason. No event property contains pasted text, mention text, or titles — counts/enums only. Events carry$groups.organization.
Negative / robustness
- Submit with an empty textarea → button disabled.
- Kill the API mid-parse → generic
parse_failedinline error; the job row (dev DB) endsfailedwitherror_code. - Double-click commit → single workout created; second attempt (or a stale tab) surfaces the generic failure toast (409 server-side).
- e2e suite:
make test-e2e-web(e2e must target thetaikan_testDB on 55432).
Performance expectations
- Parse round-trip (paste → preview) under ~20s for a typical WOD;
stage_timingson the job row breaks down extraction vs resolution. - Preview interactions (chip popovers, drag, prescription edits) are the builder’s own — no added latency budget.