fix(check): audit the composition when the motion sidecar will not parse - #2806
Draft
xuanruli wants to merge 1 commit into
Draft
fix(check): audit the composition when the motion sidecar will not parse#2806xuanruli wants to merge 1 commit into
xuanruli wants to merge 1 commit into
Conversation
`runCheckPipeline` returned early on `motion.kind === "invalid"`, handing `buildReport` an `emptyBrowserResult()`. The browser never opened, so Layout, Runtime and Contrast printed an affirmative all-clear for sections nothing had looked at, and `layout.duration` came back 0 for a 6s composition. Measured on one composition under three sidecar states: 8 layout findings with a valid sidecar, 8 with none, 0 with a typo'd one. A sidecar says nothing about the composition — the HTML is fine, a JSON file beside it is not — so the parse error now rides along as a motion finding while the audit runs. Layout output for an invalid sidecar is byte-identical to the no-sidecar path. Nothing downstream reads the unparsed spec: both `motion.spec` accesses sit behind a `kind === "valid"` guard, and `planMotionSampling` returns an empty plan for anything else, so `motion.samples` stays 0 and no motion frame is collected. `enabled`/`specPath` are unchanged from before — the early return already passed the invalid resolution to `buildReport`. The run was already failing (the parse error is `error`-severity), so this only adds findings to a red report and cannot flip a passing check. Cost is one ordinary `check`: the up-to-300-seek motion loop is exactly the part that stays skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
runCheckPipelinereturned early onmotion.kind === "invalid", handingbuildReportanemptyBrowserResult(). The browser never opened, so the report affirmed sections nothing had examined:One composition, three sidecar states:
layout.durationSo the duration was fabricated too, not just the findings. A JSON typo in an optional add-on suppressed the mandatory core of the command — a project that gains a typo'd sidecar became strictly worse off than the same project with no sidecar at all.
Change
The parse error becomes a
specFindingsentry passed tobuildReportasextraMotionFindings; the audit runs as usual. A sidecar says nothing about the composition — the HTML is fine, a JSON file beside it is not.Nothing downstream reads the unparsed spec. Both
motion.specaccesses (planMotionSampling, and therunAuditGridevaluate branch) sit behind akind === "valid"guard, andplanMotionSamplingreturns an empty plan for anything else. Verified independently by a reviewer: layout output for an invalid sidecar is now byte-identical to the no-sidecar path (same 757-byte serialisation,IDENTICAL),motion.samplesstays 0, and no motion frame is collected.enabled/specPathare unchanged frommain— the old early return already passed the invalid resolution tobuildReport, so{enabled: true, specPath: "…", samples: 0, findings: [motion_spec_invalid]}is the same shape as before and now has a test pinning it.Cannot flip a passing run
motion_spec_invalidiserror-severity, sookwas alreadyfalseandcheckExitCodealready returned 1. This only adds findings to an already-red report. Confirmed both ways with and without--strict.Cost
One ordinary
check. The expensive part — the up-to-300-seek motion loop (MOTION_FPS=20,MOTION_MAX_SAMPLES=300) — is produced solely bybuildMotionSampleTimes, whichplanMotionSamplingdeclines to call for a non-valid spec. What runs is the default 9-sample layout grid plus ≤5 contrast measurements: 4.66s measured, the same cost every no-sidecar project already pays. A reviewer specifically checked whether a "skip contrast when the spec is invalid" guard was warranted and argued against it — it would re-create this exact bug one section over.Verification
m-badspec-mask: 0 → 8 layout findings, alongside themotion_spec_invaliderror;duration0 → 6. Deterministic over 3 sequential runs.mainthe page error was not reported at all).catcharoundrunBrowserCheckstill works:specFindingsis computed before the try, so a mid-audit browser failure yields bothcheck_runtime_failureandmotion_spec_invalid.--snapshotswith contrast enabled produces all 10 correctly-named PNGs and no crop for the spec error (findingAtRootusesZERO_BBOX, andselectFindingCropRequestsrequires a real bbox).browser.motionIssuesis[], somotionFindingsis exactly the one parse error.src/telemetry/agent_runtime.test.ts, agent-env detection) fails identically onmain— this sandbox injects ~16CLAUDE_CODE_*vars that drown its assertion. oxlint / oxfmt /tsc --noEmitclean.Two tests added. The first pins the fix (it fails on
main:expected "spy" to be called 1 times, but got 0 times). The second —does not sample motion for an invalid sidecar— passes onmaintoo, so it is not a regression pin for this bug; it guards the new path, where deletingplanMotionSampling'skind !== "valid"guard would now crash onmotion.spec. Its comment says so.Known follow-ups, deliberately not in this PR
The reporting layer has no "not run" state.
printSectionprints◇ 0 errors, 0 warningswith a success glyph wheneverfindingsis empty, regardless of whether the section ran;printContrastSectionbranches only onoptions.contrast, so an un-run pass prints◇ 0/0 text checks pass WCAG AA— an affirmative WCAG claim from zero measurements. A per-sectionran/skippedstate would subsume this bug's path, the lint gate below, the browser-failure path, and--no-contrast's existing◇ skipped. That is a report-schema change touching the JSON envelope and every printer — its own PR, and it would not have fixed this defect (8 real findings beat a correctly-labelled empty section).The lint gate has the same symptom for a different reason.
shouldBlockRendershort-circuits on any lint error with{kind: "none"}hard-coded, so a project shipping a sidecar reportsenabled: false,specPath: undefined, and the invalid sidecar is never mentioned. Left untouched on purpose: a lint error means the composition is structurally invalid, so browser results measured against it are noise rather than untrusted-but-useful input. That gate is deliberate, named, and shares the render gate — flipping it would smuggle a behaviour change with render blast radius into a reporting fix. Its residual dishonesty is thenot-runproblem above, not an early-return problem.Sequencing
Composes with #2805 (ambiguous selector no longer zeroes the motion audit): that PR works inside
planMotionSamplingand the evaluate branch and preserves thekind !== "valid"early return this fix relies on. Onlycheck.test.tsconflicts textually (both insert after the same line). This one is smaller — land it first and let #2805 rebase.The highest-value remaining fix in this family is one wrong predicate: an opaque element parked off-canvas is treated as having "appeared" at t=0, because the visibility test has no viewport intersection. It poisons
appearsBy,beforeandstaysInFrameat once, so any slide-in entrance trips it, and it shares a root cause withmotion_off_frameflagging full-bleed backgrounds — one predicate retires two false-positive classes.🤖 Generated with Claude Code