---
title: Paired Review and Feedback - Plan
type: feat
date: 2026-09-14
deepened: 2026-09-14
artifact_contract: ce-unified-plan/v1
product_contract_source: ce-plan-bootstrap
execution: code
---

# Paired Review and Feedback - Plan

## Goal Capsule

- **Objective:** James can compare his position with Damien's, identify supported priorities to discuss with his coach, and see those priorities stay consistent with his joint corrections.
- **Means:** An editable paired view and a local, deterministic evidence-to-feedback pipeline (KTD1–KTD6).
- **Authority:** This is a bounded follow-on to `../docs/plans/2026-09-14-0936-feat-track-day-body-position-plan.md`. Its feasibility, privacy, and coaching-support gates remain binding. This document's IDs are local to this feature.
- **Execution:** Extend the existing macOS application. Implement geometry and persistent-data behavior test-first; verify canvas interaction in the native app.
- **Stop conditions:** Missing evidence suppresses affected claims. A successful skeleton or synthetic test never establishes coaching validity. If no real pair supports useful feedback after review, report that unmet outcome instead of declaring the complete objective achieved.
- **Delivery:** Build and verify locally. James and his coach assess utility and validate coaching; private source images and derived artifacts remain local.

---

## Product Contract

### Summary

Add a Compare mode showing the subject and reference photographs together, editable markers on both, and up to three prioritized focus points. Refresh the report when committed joint edits or comparison context change.

### Problem Frame

The reviewer currently shows one rider at a time. James must remember one posture while inspecting another, and saved corrections do not yet drive measurements or explanatory text. The current application is useful for repairing detections, but lacks the comparison and interpretation step.

### Key Decisions

- **Extend the existing editor.** Governs R1–R3. Keep the correction workflow available within comparison.
- **Evidence-linked local feedback.** Governs R4–R8. Rules make each statement inspectable and reproducible.
- **Contextual reference.** Governs R6–R7. Damien's posture provides comparison evidence, not a universal target.

### Requirements

**Paired review**

- R1. Offer Single and Compare modes. Compare groups frames by explicit pair ID and role, with subject left and reference right regardless of manifest order or original panel position.
- R2. Both panes support the existing correction, placement, visibility, undo, detection-source, and zoom controls. The active rider is clear, and every edit targets the frame in the pane where it began.
- R3. Unpaired photographs retain Single mode and explain that no reference exists. Loading failures never display a previous pair's photograph or feedback as the new selection.

**Analysis and explanation**

- R4. Display supported 2D observations and up to three prioritized focus bullets beside or below the photographs. Each focus names the observation, its evidence, a conditional suggested change or coach-discussion point, and its limitation.
- R5. Use only confirmed or corrected visible joints for measurements. Unreviewed detections, uncertain joints, and obscured joints cannot satisfy measurement prerequisites.
- R6. Require explicit review of rider identity and view comparability before cross-photo conclusions. Record corner direction separately; unknown direction blocks inside/outside suggestions but allows anatomical left/right observations.
- R7. Distinguish visible differences, provisional focus suggestions, and coach-validated advice. An angular difference alone is not a fault, a causal explanation, or a prescribed target.
- R8. Explain missing evidence without filling the three-bullet quota. Missing prerequisites for motorcycle contact, hidden depth, or camera comparability must not become assumed findings.

**Revision and ownership**

- R9. Refresh after drag release, placement, nudge, visibility changes, undo, and comparison-context changes on either rider. While dragging, mark the report as awaiting completion of the edit; selection, Fit, zoom, and detection-source display changes do not alter analysis evidence.
- R10. Feedback must identify the input revision it describes. Save failure leaves current edits and matching analysis visible as unsaved, preserves the last saved document, and retains existing navigation protection.
- R11. Reopening restores corrections and comparison context and regenerates feedback locally. Preserve original photos, watermarks, raw detections, retries, and edit history; existing review documents must remain recoverable.

### Acceptance Examples

- AE1. P01 opens with James left and Damien right; reversing source-panel order or frame order does not reverse their roles. Covers R1.
- AE2. Dragging Damien's elbow changes only Damien's correction. A subsequent Undo restores that correction and the previous feedback, even if James was selected in between. Covers R2, R9.
- AE3. Marking a required knee obscured removes the dependent comparison and focus, showing the missing evidence instead. Covers R5, R8–R9.
- AE4. A pair with no detections still opens both photographs and permits manual placement. No analysis is fabricated while its required joints remain unreviewed. Covers R1–R2, R5.
- AE5. A changed marker followed by a failed save shows updated, explicitly unsaved analysis. Reopening later derives its report from the actual saved revision. Covers R10–R11.
- AE6. A 2025 single-subject photo never borrows a 2026 reference with a similar identifier. Covers R3.

### Scope Boundaries

This feature covers paired visual review, a bounded 2D measurement catalog, evidence-linked focus suggestions, and revision-aware refresh. It does not finish the entire parent plan. In particular, this two-photo view is separate from the parent plan's R6 reference-only composite; it does not change that composite's contract.

#### Deferred to Follow-Up Work

Bike-anchor alignment, reference-only overlay export, manual motorcycle-contact annotations, session-wide recurring themes, report export, pair creation/import UI, and single-photo coaching remain follow-up work. Free-form language-model narration, calibrated 3D reconstruction, model training, and video analysis remain outside this implementation.

### Success Criteria

James can identify which visible difference each focus refers to and observe it change after correcting either rider. Record utility review on at least one eligible real pair, including withheld topics. Functional completion and coaching validation are reported separately; zero emitted suggestions cannot pass the utility check by default.

---

## Planning Contract

### Existing Evidence and Constraints

`SessionManifest.swift` already supports one subject with an optional reference, including separate rider files. `ReviewFrame` stores stable IDs, pair ID, role, rider name, panel geometry, raw evidence, corrections, and retries. Pair membership needs no filename heuristic.

`ReviewerModel.swift` owns one active index and image. `JointCanvas.swift` reads that mutable selection during mouse events, so simply rendering it twice would couple both panes to the same photo. Preserve its coordinate conversion and mouse-up commit behavior while removing implicit edit-target selection.

`ReviewFrame.reviewedPoint` already enforces the evidence boundary required by R5. `displayPoint` can return unreviewed retry results and must never be used by the analyzer. `ReviewDocument.downstreamEvidenceStale` is only `revision > 0`; it cannot represent whether a new report is current.

`docs/larger-source-feasibility.md` documents missing detections and wrong-rider risk; `docs/highres-feasibility.md` documents remaining errors in larger single-subject photos. These are evidence for a manually reviewed 2D path, not passage of an automatic coaching gate. Start implementation by checking the parent plan's U1 evidence against the intended measurement prerequisites. Existing original-source labels remain evaluation evidence and are not rewritten to match results.

### Key Technical Decisions

- KTD1. **One document, explicit pane identities.** Keep `ReviewerModel` as the sole owner of mutable review data. Build pair descriptors from frame IDs and roles, retain a selected pair and active frame ID, and pass each canvas its own frame ID, image, and presentation settings. An edit callback includes its captured frame ID and joint. Reject callbacks from an unloaded session or replaced canvas. This fixes R1–R3 without independent copies of the review document.

- KTD2. **Persist minimal comparison context with the review.** Add per-pair identity confirmation, corner direction (`unknown`, `left`, `right`), and per-feature comparability (`unreviewed`, `comparable`, `notComparable`). Feature-level decisions allow one visible limb to be assessed while another is hidden or foreshortened. Bind context to the exact frame IDs and source evidence. Use review schema 2, reading schema 1 with unreviewed defaults. Under the existing file lock, create an exclusive byte-for-byte schema-1 backup before the first schema-2 save; backup failure aborts replacement. A retry may reuse an existing regular-file backup only after verifying its bytes against the still-current schema-1 source; reject a mismatch or symbolic link without overwriting either file. Loading alone never rewrites the source. Preserve revision conflict checks and rejection of unknown future schemas. Old app versions will reject schema 2; their rollback input is the preserved pre-upgrade file. Implements R6, R10–R11.

- KTD3. **Small pure 2D feature catalog.** Start with left/right elbow included angles (shoulder–elbow–wrist) and knee included angles (hip–knee–ankle). Calculate source-coordinate angles in degrees from reviewed points, rejecting nonfinite, coincident, and numerically degenerate segments. Display rounded whole degrees, explicitly labeled 2D included angle. No segment heading, inferred torso twist, or corrected 3D measurement is implied. Comparison eligibility additionally requires KTD2 context for that feature. Independent display scaling does not enter the math. Implements R4–R6, R8.

- KTD4. **Versioned rules with explicit support.** Keep measurement extraction separate from a small compiled rule catalog. A rule declares its feature, viewpoint conditions, meaningful-difference threshold, priority tier, approved wording, suppression conditions, evidence references, and review provenance. Deduplicate by feature and direction; sort by declared priority, then stable rule ID. Do not rank different anatomical concerns by the largest raw angle delta. A missing reviewed threshold or wording leaves that rule inactive and shows observations only. Synthetic rules prove engine behavior but never become production coaching rules. Initial rules focus on visible elbow/knee relationships; they cannot infer foot pressure, peg contact, pelvis/seat contact, grip force, or a dependency between those topics. Implements R4, R7–R8 and the parent plan's coaching-support gate.

- KTD5. **Synchronous derived report, separate persistence status.** These few measurements need no asynchronous generator or network. Build an immutable report from a document snapshot on load and after each committed mutation, stamped with session evidence, pair/frame IDs, document revision, context, analyzer version, and rule-catalog version. Compute before publishing the updated document/report together on the existing main-actor model. Autosave then updates a separate saved/unsaved indicator. A drag preview temporarily labels the last committed report as awaiting the edit; cancellation restores its current label. No persisted report cache is needed. Ignore the legacy stale boolean for this feature. Implements R9–R11. Apple's [model-data guidance](https://developer.apple.com/documentation/swiftui/model-data) supports retaining one observable source of truth; migration to another observation framework adds no value here.

- KTD6. **Keep raw inference outside reviewed feedback.** Retry completion may update unreviewed display seeds and the document revision, but it cannot promote seeds into KTD3 evidence. Rebuild the report stamp after retries while preserving the same findings unless reviewed evidence changes. Keep the existing isolated retry worker. Apple documents [3D recognized body points](https://developer.apple.com/documentation/vision/vnhumanbodyrecognizedpoint3d) as 3D geometry; retain the project's explicit prohibition on manufactured per-joint 3D confidence. Implements R5, R9, R11.

### User Interface

Use a pair sidebar in Compare mode, with Single/Compare switching above the content. Show subject and reference names and roles on their respective panes. Each pane independently fits its complete panel, preserving aspect ratio and watermarks; label differing views as independent photo views, not physical alignment. Keep separate zoom and detection-source settings. A common “Show markers” control permits clean visual comparison without changing evidence.

Clicking either pane selects its rider for the existing joint inspector. Highlight that pane and repeat its rider name in the inspector. Placement is confined to the active pane and cancels on pane or pair changes. Keyboard nudges follow actual canvas focus. A drag captures its original target; navigation cancels the preview before replacing views.

At the normal 1400 × 900 window size, put both photos across the central area and a scrollable feedback section below them. Keep the joint inspector collapsible so the two photographs remain useful at the existing minimum window size. Do not silently stack the photos vertically when the user selected Compare. Native smoke testing determines final split proportions.

The feedback section shows “Focus,” followed by a collapsible measurement/evidence detail area and limitations. A focus can select and highlight its named joints on both panes. Missing identity or comparability review offers the corresponding controls. Unknown corner direction is an explicit option, never inferred from the session's CCW name. No enabled coaching rules shows “Visible differences — coaching review pending,” not an empty success state.

### High-Level Technical Design

```mermaid
flowchart TB
  Panes[Two frame-bound canvases] --> Model[One review model]
  Context[Pair review controls] --> Model
  Model --> Snapshot[Reviewed document snapshot]
  Snapshot --> Features[Eligible 2D measurements]
  Features --> Rules[Versioned supported rules]
  Rules --> Report[Revision-stamped pair report]
  Report --> UI[Focus bullets and evidence]
  Model --> Store[Atomic local review save]
  Store --> Persistence[Saved or unsaved indicator]
```

```mermaid
stateDiagram-v2
  [*] --> NeedsReview: open pair
  NeedsReview --> Current: prerequisites reviewed
  Current --> Preview: begin drag
  Preview --> Current: cancel
  Preview --> Recompute: commit edit
  Current --> Recompute: edit or undo or context change
  Recompute --> Current: eligible evidence
  Recompute --> NeedsReview: evidence withdrawn
```

Persistence status is orthogonal to the report states: Current means current for in-memory evidence, not necessarily saved.

```mermaid
flowchart TB
  Pair[Selected pair] --> Identity{Both riders confirmed?}
  Identity -->|No| Review[Request review]
  Identity -->|Yes| Points{Feature points reviewed?}
  Points -->|No| Missing[Explain missing evidence]
  Points -->|Yes| View{Feature views comparable?}
  View -->|No| Separate[Individual measurements only]
  View -->|Yes| Measure[Compute visible difference]
  Measure --> Rule{Supported rule applies?}
  Rule -->|No| Observe[Observation only]
  Rule -->|Yes| Focus[Prioritized conditional focus]
```

### Risks and Delivery Dependencies

The initial production rule thresholds and wording are not established by the current repository. Collect coach review of that bounded catalog and assess its outputs during U3/U5. Until then, the interface can deliver visual comparison and supported observations, but must report coaching as pending. The implementation must not invent thresholds to turn on suggestions.

Manual comparability approval is a review decision, not calibration. Different bikes, proportions, camera perspectives, and the tiny source panels remain limitations. Real-pair eligibility and utility are execution evidence to collect; failure cannot be replaced with a synthetic demonstration.

Schema conversion affects valuable manual corrections. The backup and lock checks in KTD2 are delivery prerequisites, including preservation of retries and history. The current repository has no initial commit, so a later executor must establish an appropriate review baseline without including ignored private data.

---

## Implementation Units

### U1. Pair identity and durable comparison context

**Goal:** Resolve correct pairs and safely retain the review decisions required for comparison.

**Requirements:** R1, R3, R6, R11. **Dependencies:** Existing feasibility evidence reviewed as described above.

**Files:** Modify `Sources/PoseCore/ReviewSession.swift`; add `Sources/PoseCore/PairReview.swift` and `Tests/PoseCoreTests/PairReviewTests.swift`; extend `Tests/PoseCoreTests/ReviewSessionTests.swift`.

**Approach:** Implement KTD1 pair resolution and KTD2 context persistence. Use the manifest-backed frame IDs, not display names or array adjacency. Validate saved frame role/pair identity against the baseline at app load. Reject duplicate, missing-subject, or mismatched context records rather than guessing.

**Patterns to follow:** `SessionManifest.load`, `ReviewStore.load/save`, and existing atomic-save tests.

**Execution note:** Write failing synthetic pairing and migration tests before changing the document model.

**Test scenarios:**

1. Covers AE1. Shuffled frames, reversed source panels, and separate image files resolve the same subject/reference pair.
2. Covers AE6. A subject-only entry returns no reference; a similarly named pair from another document is never joined.
3. A legacy review loads with unreviewed comparison context and retains edits, raw data, retries, history, and revision.
4. First upgraded save preserves an exact legacy backup; backup failure, stale writer, malformed context, or future schema preserves the existing destination.
5. A saved frame's rider role or pair identity differing from the baseline is rejected before images or context are attached.
6. Failure after backup creation but before replacement allows retry with the matching backup; a mismatching or symbolic-link backup blocks replacement and preserves both files.
7. Schema-2 context round-trips all review states and unknown corner direction. Changed source evidence cannot inherit a previous pair's comparability or identity approval.

**Verification:** Pair descriptors are deterministic, both supported schemas round-trip, and migration cannot discard prior evidence.

### U2. Frame-bound paired editing

**Goal:** Show and edit the correct two photographs through one shared document.

**Requirements:** R1–R3, R9–R11. **Dependencies:** U1.

**Files:** Modify `Sources/PoseReview/ReviewerModel.swift`, `Sources/PoseReview/JointCanvas.swift`, `Sources/PoseReview/PoseReviewApp.swift`; add `Sources/PoseReview/PairComparisonView.swift`; extend `Sources/PoseCore/PairReview.swift`, `Tests/PoseCoreTests/PairReviewTests.swift`; add `docs/paired-review-verification.md` for native evidence.

**Approach:** Apply KTD1 and the User Interface contract. Change mutations and undo records to use frame IDs. Resolve and load both images before publishing a new pair; retain the prior selection on failure. Keep keyboard focus, placement, and preview cancellation explicit. Exercise domain target resolution in core tests and AppKit event routing in native smoke checks.

**Patterns to follow:** Existing `ReviewCoordinates`, image hash verification, `selectFrame` save protection, and canvas mouse-up semantics.

**Test scenarios:**

1. Covers AE2. Editing and undo targeting remain correct after pane selection changes; a stale frame/session callback is rejected.
2. Nonzero panel origin, differing image sizes, and independent zoom map each drag within one source pixel.
3. Covers AE4. No-pose frames accept placement in the chosen pane; the opposite pane does not change.
4. Missing or changed reference image leaves the prior complete pair selected with an error; no mixed pair appears.
5. Native: switch panes during placement, cancel a drag, nudge with keyboard, hide markers, resize, and switch Single/Compare without altering unrelated evidence.

**Verification:** Native interaction proves both panes edit independently and retain all existing correction controls.

### U3. Reviewed measurements and supported rule evaluation

**Goal:** Turn eligible reviewed joints into inspectable observations and prioritized focus candidates.

**Requirements:** R4–R8. **Dependencies:** U1.

**Files:** Add `Sources/PoseCore/PairAnalysis.swift`, `Sources/PoseCore/FeedbackRules.swift`, `Tests/PoseCoreTests/PairAnalysisTests.swift`, `Tests/PoseCoreTests/FeedbackRulesTests.swift`, and `docs/feedback-rule-catalog.md`.

**Approach:** Implement KTD3/KTD4 as pure core logic. Keep a structured unavailable reason for each excluded feature. The rule-catalog document records wording, thresholds, review provenance, and disabled rules; private pair-specific reviews remain in ignored local artifacts. Gather review for real rules, while keeping engine tests independent of private images and coach availability.

**Patterns to follow:** `ReviewFrame.reviewedPoint`, `Point2`, and the explicit missing-evidence results in `Geometry.swift`.

**Execution note:** Start with failing known-angle and evidence-exclusion fixtures.

**Test scenarios:**

1. Known 0°, 90°, and 180° included-angle cases behave correctly; coincident/nonfinite points return unavailable. Translation, uniform scaling, and rotation preserve included angles.
2. Covers AE3. Withdrawing any one required joint excludes exactly its dependent feature and rule; unrelated measurements remain.
3. Raw-only and retry-only points never qualify, even when detection confidence is high or the display source changes.
4. Unreviewed identity or noncomparable views suppress cross-photo claims; unknown turn direction suppresses inside/outside language.
5. Synthetic rules exercise threshold boundaries, stable priority order, deduplication, three-item cap, and missing review provenance. No ineligible rule emits a focus.
6. Each emitted focus resolves to the exact pair, anatomical joint IDs, measurements, and rule version; no wording implies corrected depth or guaranteed benefit.

**Verification:** Deterministic tests prove the engine. Production rule activation separately requires recorded support review; no test fixture implicitly enables coaching.

### U4. Automatic report refresh and evidence navigation

**Goal:** Keep visible feedback matched to every committed edit and make its evidence easy to inspect.

**Requirements:** R4, R8–R11. **Dependencies:** U2, U3.

**Files:** Modify `Sources/PoseReview/ReviewerModel.swift`, `Sources/PoseReview/PairComparisonView.swift`, `Sources/PoseReview/JointCanvas.swift`, `Sources/PoseCore/PairAnalysis.swift`; add `Sources/PoseReview/PairFeedbackView.swift`, `Tests/PoseCoreTests/PairAnalysisRevisionTests.swift`; extend `docs/paired-review-verification.md`.

**Approach:** Implement KTD5/KTD6, routing all committed mutation paths through snapshot-to-report publication. Expose analysis eligibility and persistence as separate state. Evidence clicks select the intended frame/joint and highlight related points without mutating review state.

**Patterns to follow:** Existing main-actor model publication, autosave errors, and retry preservation.

**Test scenarios:**

1. Each committed edit, undo, and context change produces a report with the new input revision and expected changed evidence; rebuilding the same snapshot is deterministic.
2. Retry seeds, zoom, and raw display changes do not create new reviewed measurements or alter priorities.
3. Covers AE5. A failed save leaves analysis matched to current in-memory edits with an unsaved label; a subsequent reopen derives from disk.
4. Native: a drag marks feedback pending without repeated recomputation; release updates it and Escape restores the committed report.
5. Native: rapidly navigate pairs and activate evidence links; no old-pair text or highlights appear in the new pair.
6. Reopening after a rule-version change regenerates output rather than restoring obsolete feedback. Repeated analysis does not increment document revision or trigger save loops.

**Verification:** All edit entry points update the report, and each visible report can be tied to its current evidence and persistence status.

### U5. Real-session verification and local handoff

**Goal:** Demonstrate the integrated workflow and record its actual utility and limits.

**Requirements:** R1–R11 and Success Criteria. **Dependencies:** U4.

**Files:** Update `README.md`, `docs/paired-review-verification.md`, `docs/feedback-rule-catalog.md`; add `Tests/PoseCoreTests/PairWorkflowTests.swift`.

**Approach:** Cover the full synthetic save/reopen workflow, then exercise the native app on a copied private session with its matching results. Prepare any new original-source evaluation annotations before examining model outputs. Check existing reviewed decisions rather than relabeling them to make the feature pass. Record all withheld topics and distinguish UI success from coaching validation.

**Test scenarios:**

1. Covers AE1–AE5. A synthetic pair can load, receive corrections on both riders, generate eligible output, lose eligibility, undo, save, and reopen without raw-evidence changes.
2. Native: P01, a reversed-panel pair, a no-pose pair, and a 2025 single image behave according to their roles and evidence.
3. Native: missing reference and forced save failure display actionable errors without losing edits or replacing the wrong pair.
4. On an eligible private pair, James can identify the evidence behind a focus and see it update after an edit. Record coach support/actionability review or explicitly record that it remains pending.

**Verification:** Package checks pass, native evidence is recorded, existing user corrections are preserved, and the handoff states any unmet coaching/utility gate.

---

## Verification Contract

- Use the project's `scripts/swift.sh` test wrapper with the full Xcode toolchain for synthetic PoseCore tests. Its established invocation is `scripts/swift.sh test --disable-sandbox`.
- Build the native bundle through `scripts/build-review-app.sh` and perform the U2/U4/U5 smoke scenarios at normal and minimum window sizes.
- Private Vision tests remain opt-in through `POSE_PRIVATE_MANIFEST` and explicitly skip when absent. This feature needs no fresh Vision run merely to verify paired editing or feedback recomputation.
- Compare original hashes and semantic raw-evidence/current-edit snapshots before and after private smoke testing. Keep private copies, backup documents, screenshots, and report artifacts ignored.
- Review the final change for frame identity, schema preservation, all mutation paths, and evidence eligibility. There is no configured standalone linter or release-validation command.
- Report synthetic correctness, native behavior, real-pair coverage, rule support review, and coaching utility as separate evidence. Do not replace one with another.

---

## Definition of Done

- U1–U5 verification outcomes are met, and all feature-bearing test scenarios are covered by synthetic tests or documented native checks as specified.
- Both photos remain editable in Compare mode; feedback corresponds to the displayed evidence after edits, undo, retry, save failure, and reopen.
- Production focus rules have recorded review provenance. Inactive rules and unavailable measurements have clear explanations; utility/coach validation remains an explicit unmet gate if its review has not occurred.
- README explains Compare mode, evidence eligibility, automatic refresh, migration backup behavior, and the distinction between observations and coaching.
- Source photos, watermarks, raw detections, and existing corrections are preserved. Abandoned implementation experiments and unused abstractions are removed.
