Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesWorkout ParseWorkout Parse — QA Plan

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.
  • PostHog flag workout-parse-transformer enabled for the org (or dev env override FEATURE_FLAGS=workout-parse-transformer:true on the API + NEXT_PUBLIC_FEATURE_FLAGS=workout-parse-transformer:true on the web).
  • Canonical exercise library seeded (pnpm seed:movements).
  • ANTHROPIC_API_KEY set (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 Run

Scenarios

  1. Flag off — feature invisible. With the flag disabled: the workout-type chooser (/dashboard/workouts/new) shows no “Paste & structure” card; the freeform form shows no “Structure this” button; deep-linking to /dashboard/workouts/new/paste renders the “not available” state (parse-unavailable), not the paste UI; POST /organizations/:orgId/workouts/parse via API returns 403 parse_feature_disabled.
  2. Flag on — entry points appear. Enable the flag. Chooser shows the paste card first; freeform form shows “Structure this”. No other behavior changed anywhere else in workouts.
  3. Tier gate. On the Lite org (flag on): the chooser card renders dimmed with a lock icon; clicking it lands on the paste route showing the upgrade card (FeatureGate), never the textarea. Direct API POST /parse → 403 (tier).
  4. Golden path — paste, preview, commit. Paste the sample text, submit. Expect: spinner ≤ ~20s, then a side-by-side preview — left pane shows the original text with movement spans highlighted; right pane shows 2 sections (a linear/rep-scheme strength section, an AMRAP section with durationMinutes: 12 in its config). Movements matched with green (auto) chips where the library has clear matches. Hovering a movement row highlights its source span and vice-versa. Commit (resolve any flagged movements first, scenario 7) → success toast → redirected to the workout detail page; the workout is structured, opens in the normal builder, and its description contains the original pasted text.
  5. Non-English rejection (D3). Paste a Hebrew workout (e.g. אימון כוח: סקוואט 5x5). Expect 422 with the localized “English only” inline error — polite, no partial parse rendered. Verify the message in HE and RU locales too.
  6. 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.
  7. Adjustment — pick a candidate. Paste text with an ambiguous movement (e.g. “DB press”). An amber suggested chip 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.
  8. Adjustment — create org-local exercise. Include a made-up movement (e.g. “Bosu burpee slam”). Red unresolved chip → 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).
  9. 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.
  10. 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.
  11. Discard. Click discard → confirmation dialog → confirm. Toast, back at the chooser. Re-open the job via its ?jobId= URL: preview renders but no commit/discard bar (terminal status).
  12. 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.
  13. “Structure this” flow (entry b). Create a freeform workout with body text, save, reopen it, press “Structure this”. The paste page opens prefilled with the description. Parse + commit. Expect the same workout (same id) flipped to structured with sections — not a new workout — and its description preserved.
  14. 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.
  15. 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_jobs row is created (blocked before the LLM call).
  16. Member role. Signed in as a member: no entry points; direct API calls to all four parse routes → 403.
  17. 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.
  18. Observability. In PostHog (production/staging with a real key): a parse run emits parse.requestedparse.completed; a commit emits parse.committed with zeroEdit/editDistance; blocked attempts emit parse.blocked with the right reason. 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_failed inline error; the job row (dev DB) ends failed with error_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 (the script defaults FEATURE_FLAGS/NEXT_PUBLIC_FEATURE_FLAGS to enable the parse flag; e2e must target the taikan_test DB on 55432).

Performance expectations

  • Parse round-trip (paste → preview) under ~20s for a typical WOD; stage_timings on the job row breaks down extraction vs resolution.
  • Preview interactions (chip popovers, drag, prescription edits) are the builder’s own — no added latency budget.