Co-Study4Grid / scripts /PARITY_README.md
github-actions[bot]
Deploy 7688ef1
13d4e44
|
Raw
History Blame Contribute Delete
7.08 kB

Parity conformity checks

Scripts in this directory verify that standalone_interface.html faithfully mirrors the React frontend in frontend/. The React app is the source of truth; when the two diverge, the standalone is brought up — not the other way around.

See the root CLAUDE.md § "Standalone Interface Parity Audit" for the gap list these scripts feed; docs/features/interaction-logging.md is the canonical replay-contract spec they check against.

Layers

Layer Script Runs in Gates CI What it catches
1. Static inventory check_standalone_parity.py <5 s, no backend yes Event-type coverage, details schema drift (three-way diff vs spec), missing API paths, SettingsState fields
2. Session fidelity check_session_fidelity.py <2 s, no backend yes Fields saved to session.json that are silently dropped on reload (e.g. the PR #83 lines_overloaded_after regression)
3a. Gesture sequence (static proxy) check_gesture_sequence.py <2 s, no backend yes Canonical 15-step gesture sequence: each gesture's handler emits the required event types in the documented order. Covers the full loop through Display Prioritized → Overview → pin-click → pin-double-click → detach → deselect.
3b. Behavioural E2E (runtime) parity_e2e/e2e_parity.spec.ts 60–90 s, needs Playwright browser nightly / on-label Same gesture sequence driven through real DOM against BOTH UIs; diffs resulting interaction_log.json + session.json at runtime
4. User-observable invariants (static) check_invariants.py + frontend/src/utils/userObservableInvariants.test.ts <2 s Python + ~1 s Vitest, no backend yes Six bug classes Layers 1-3 can't catch by construction: visual thresholds (severity palette ↔ monitoringFactor), conditional rendering (dashed curves only for simulated pairs), field semantics (topology target precedes max_rho_line), auto-effects (tab auto-switch, deselect-stay), loading-state release timing, memoisation / performance guards

Layers 1, 2, and 3a need only Python and finish in under 10 s total — wire them into a GitHub Action on every PR. Layer 3b needs a browser installed via npx playwright install and is designed for nightly CI or on-label runs (see cost discussion below).

Why both a 3a and a 3b

Layer-3b (real Playwright) is the strongest check — it exercises both UIs through actual DOM events and captures the runtime interaction log. But it requires a browser download that isn't always available (sandboxed CI, restricted network environments) and takes ~90 s per run.

Layer-3a (static) walks the source of both codebases, locates the handler body for each canonical gesture, and verifies the expected interactionLogger.record(...) / recordCompletion(...) calls appear in the right order. It can't catch runtime ordering races — if two code paths within one handler fire events in different orders depending on state, 3a sees both paths and signals pass. That's a genuine limitation, but it's strictly cheaper than 3b and it catches the most common regression class: "gesture G should emit event E but no code path emits E from its handler", in a sequence-aware way that Layer-1's set-based diff misses.

Keep both: 3a is the always-on fast proxy, 3b is the nightly authoritative check.

Running the checks

# Layer 1 — static parity (events, API paths, settings, spec diff)
python scripts/check_standalone_parity.py                # human text
python scripts/check_standalone_parity.py --json         # machine
python scripts/check_standalone_parity.py --emit-markdown  # paste into CLAUDE.md

# Layer 2 — session-reload fidelity (save-vs-restore symmetry)
python scripts/check_session_fidelity.py                 # human text
python scripts/check_session_fidelity.py --json          # machine

# Layer 3a — gesture-sequence static proxy
python scripts/check_gesture_sequence.py                 # human text
python scripts/check_gesture_sequence.py --json          # machine

# Layer 4 — user-observable invariants (static Python)
python scripts/check_invariants.py                       # human text
python scripts/check_invariants.py --json                # machine

# Layer 4 — runtime Vitest companion (React-side behaviour)
cd frontend && npx vitest run src/utils/userObservableInvariants.test.ts

# Layer 3b — behavioural E2E with Playwright
cd scripts/parity_e2e
npm install
npx playwright install chromium     # one-off browser download
cd ../../frontend && npm run build  # prereq: React /dist produced
cd ../scripts/parity_e2e
npx playwright test

Each script exits 1 on any FAIL; suitable as a CI gate. They share no state; run them in any order.

Keeping CLAUDE.md in sync

The "Machine-grounded findings" section of the root CLAUDE.md is meant to be regenerated, not hand-edited:

python scripts/check_standalone_parity.py --emit-markdown \
  > /tmp/parity.md
# ...paste /tmp/parity.md into the designated section of CLAUDE.md.

(Automating this with a pre-commit hook is a possible follow-up.)

Spec encoder

check_standalone_parity.py contains a SPEC_DETAILS dict that encodes the replay contract from docs/features/interaction-logging.md § Replay Contract. Each InteractionType maps to (required_keys, optional_keys). When the spec changes, update this table in the same PR — the script's three-way diff (spec vs FE, spec vs SA) relies on it to attribute each finding to the side that owns the fix.

Layer-3b design notes

The spec in parity_e2e/e2e_parity.spec.ts drives the canonical 11-step gesture sequence against both UIs. It is written to be backend-free — all /api/* calls are intercepted by page.route() and fulfilled with canned JSON. That means the run only needs:

  1. A built React app (frontend/dist/) served via vite preview (playwright.config.ts does this automatically).
  2. standalone_interface.html loaded via file://.
  3. A Playwright-compatible browser (Chromium).

The spec captures three artefacts from each run:

  • Ordered list of interactionLogger.record(...) events.
  • details keys per event (order-insensitive).
  • session.json field paths (the payload the Save Results button POSTs to /api/save-session).

It asserts equality across all three between the React run and the standalone run. Divergence → test fail with a per-event diff.

Why Layer 3b is not on every PR

  • Cost: ~90 s including browser launch + both UI runs on a small grid.
  • Flakiness: ordering races between async XHRs and React re-renders are real; timeouts need tuning per gesture.
  • Maintenance: Playwright selectors drift faster than Python regex patterns as the UI changes.

Recommended cadence: run 1, 2, 3a per-PR (fast, deterministic); run 3b nightly or behind an e2e label on PRs that touch standalone_interface.html, frontend/src/hooks/useAnalysis.ts, frontend/src/utils/sessionUtils.ts, or frontend/src/utils/interactionLogger.ts.