component-audit is the first mechanical guardrail for the MoonUI component
refactor.
It is not a replacement for visual PNG snapshots. It is the baseline layer that records the current component architecture and catches regressions before a human opens the gallery.
Create or refresh the current baseline:
cargo xtask component-audit --update-baselineCheck current code against the baseline:
cargo xtask component-audit --check-baselinePrint the current full report:
cargo xtask component-audit --jsonThe baseline lives at:
docs/component-audit-baseline.json
The audit currently records:
- component manifest classification:
Mirror,TrackedFork,Forged,Domain,Pending,Forbidden; - source hygiene counters such as
MoonSkinPalette,moon_color, public facade slurp, raw runtime hex and no-op API markers; - critical semantic contracts that must not regress;
- guardrail contracts that prove the checking machinery is wired;
- the verifier class for each contract:
StructuralSource,BehavioralTest, orVisualGolden.
Example critical visual contract:
window_frame.visual_types
This is backed by committed gallery golden PNGs. If the window frame's visual chrome changes, the snapshot diff must fail.
Example behavioral contract:
checkbox.click_toggles
This does not grep implementation text. It requires a concrete Rust test name
to exist and not be #[ignore]; cargo test is the execution side of the
contract.
Forged controls that own interaction logic must also have behavioral
contracts. Examples: toggle.click_behavior, radio.click_behavior,
stepper.value_behavior, collapsible.click_behavior,
dropdown.select_behavior, rating.click_behavior, kbd.keystroke_format,
and skeleton.longbridge_capabilities. A committed PNG proves the control is
visible; these tests prove the important user-facing semantics did not turn
into a static drawing.
Example structural contract:
legacy_dock.internal_only
This is intentionally source/manifest based because the invariant is an API boundary: the retained Longbridge dock source must stay internal and must not be exported through the public facade.
Example mirror-truth contract:
mirror.class_matches_donor_drift
This joins component-audit with component-mirror. A component may remain
classified as Mirror only while its relation to the pinned Longbridge donor is
truthful and clean: donor_changed_files must be empty. If the component keeps
Longbridge behavior but has reviewed local base-file edits, it must be
classified as TrackedFork, must explain the local drift in fork_reason, and
must set a positive donor_drift_budget. If the drift grows past that budget,
the audit fails. A green audit without the donor side is not a valid proof of
mirror health.
The baseline is a measured state, not permission to leave problems alone.
Refactors are allowed to improve measured architecture and behavior. They are not allowed to make the measured state worse.
For example:
moon_skin_palette_usages: 29 -> 0is good;moon_skin_palette_usages: 29 -> 30is a regression and must fail;checkbox.checked_glyph.asset: Pass -> Failis always a hard failure.
This audit catches architecture and semantic regressions. It does not prove pixel-perfect visual identity.
Visual snapshots are handled by the gallery runner:
powershell -ExecutionPolicy Bypass -File tools\capture-gallery-snapshots.ps1 -CompareThe gallery switches pages internally, waits for frames to settle, clears stale PNG files, writes current screenshots, and the wrapper compares them against the committed golden baseline in:
crates/moon-ui-gallery/snapshots/baseline
component-audit verifies that all golden files exist and that the gallery
snapshot flow is still wired. The actual pixel comparison is the PowerShell
snapshot command above.
On Windows the current snapshot path falls back to Win32 GDI client capture
because the GPUI Windows backend still reports render_to_image as
unimplemented. This is good enough to catch visual regressions in normal local
developer runs, but backend render_to_image remains the cleaner final target.
MoonSize::control_metrics() returns the shared MoonControlMetrics table in
moon/foundation.rs.
Its doc comment defines every tier's design-reference values, the WCAG target-size
floor (Sm is the intended terminal default; Xs needs the spacing exception),
and zoom-only scaling for tiers, including text; custom sizes retain text scaling.
MoonSize::nearest(&supported) snaps by tier steps, with ties going down, and
requires a non-empty list. Component migrations adopt this table separately.
The second guardrail records the Moon-facing public API surface.
Create or refresh the API baseline:
cargo xtask component-api --update-baselineCheck current API against the baseline:
cargo xtask component-api --check-baselineThe baseline lives at:
docs/component-api-baseline.json
This guard catches accidental removals or signature changes in public component
builders, events, public structs, enums, types, constants and facade exports.
Removing or changing existing API must be an approved migration, not an
accidental side effect of refactoring; a removal is declared in
docs/component-api-removals.json with its reason.
Adding API is allowed, and the addition is recorded: the check fails on a public
item the baseline does not know, so an addition carries
--update-baseline in the same change. Without that the file describes only the
part of the surface somebody once wrote down — items it never learned about are
unguarded against a later removal, and the next legitimate signature change has
to sweep the accumulated backlog into its own diff, where a reviewer can no
longer tell the two apart. That is exactly how it fell 87 items behind.
The third guardrail tracks components that are intentionally classified as
Mirror or TrackedFork in
crates/moon-ui-components/component-manifest.json.
Run it against the pinned Longbridge donor tree:
cargo xtask component-mirror --donor-root vendor\longbridge-gpui-component-ui-src --check-baselineRefresh the mirror baseline only after reviewing the diff:
cargo xtask component-mirror --donor-root vendor\longbridge-gpui-component-ui-src --update-baselineThe baseline lives at:
docs/component-mirror-baseline.json
The donor source is vendored at:
vendor/longbridge-gpui-component-ui-src
The manifest pins each upstream reference as Longbridge::<component>@<sha>.
The current donor SHA is recorded in the vendored UPSTREAM.md. A donor-based
baseline must not be checked without --donor-root; that is a false green.
This guard does not prove Moon visual quality. It proves that every Mirror or
TrackedFork component is still tied to a known Longbridge source path.
Mirror must have zero donor drift. TrackedFork may have reviewed donor
drift, but that drift must stay within its manifest donor_drift_budget. If a
component no longer treats Longbridge as the behavior source, reclassify it as
Forged.
component-audit also reads this mirror baseline. That makes donor drift part
of the main semantic audit instead of a disconnected report:
Mirrormeans zero donor drift.TrackedForkmeans Longbridge behavior is still source-owned, but reviewed Moon edits live in base files and are capped bydonor_drift_budget.Forgedmeans Moon owns the implementation and the donor is no longer the behavioral source.
Run the full local non-visual gate with:
powershell -ExecutionPolicy Bypass -File tools\run-component-guardrails.ps1Run the local gate with visual checks:
powershell -ExecutionPolicy Bypass -File tools\run-component-guardrails.ps1 `
-WithSnapshotsThe local gate uses the vendored Longbridge donor by default. Passing
-DonorRoot is only for intentional donor refresh work.
The snapshot gate captures both Dark and Light themes. A component that only works in the terminal dark theme is not considered verified.
The repository also contains .github/workflows/moonui-guardrails.yml, which
runs the non-visual guardrails on push and pull request, including the vendored
donor mirror check.
-
Before a refactor, run:
powershell -ExecutionPolicy Bypass -File tools\run-component-guardrails.ps1
-
Make the refactor.
-
Run the audit again.
-
If the audit fails:
- fix the regression; or
- if the change is intentional, update the manifest/contract first and then refresh the baseline in the same reviewed change.
-
Never refresh the baseline just to hide a regression.