Skip to content

Buiy foundation spec + Phase 0 implementation#24

Merged
intendednull merged 42 commits into
mainfrom
claude/bevy-ui-planning-Hk8jW
May 8, 2026
Merged

Buiy foundation spec + Phase 0 implementation#24
intendednull merged 42 commits into
mainfrom
claude/bevy-ui-planning-Hk8jW

Conversation

@intendednull
Copy link
Copy Markdown
Owner

Lands the Buiy foundation spec, the Phase 0 implementation, and the post-implementation review-swarm cleanups. 40 commits since main.

Closes none individually; tracked by #2 (master roadmap).

Summary

Specs

  • docs/specs/2026-05-07-buiy-foundation/ — multi-file umbrella spec (9 files: README, architecture, visuals, text, interaction, media-and-widgets, accessibility, verification, cross-cutting). Defines the parallel-to-bevy_ui architecture, BSN-friendly decomposed components, full WCAG 2.2 SC table, 15 CI gates, and a 20-sub-spec roadmap.
  • docs/specs/2026-05-07-docs-organization-design.md — docs tree conventions.

Plan

  • docs/plans/2026-05-07-buiy-phase-0-foundations.md — 20 TDD tasks defining Phase 0 scope.

Implementation (4-crate workspace)

  • buiy_coreCorePlugin, BuiySet system sets (Layout → Style → Input → Animate → Picking → A11yUpdate → Render), Node/Style/ResolvedLayout components, Theme + default_light_theme, Taffy-backed LayoutPlugin (NonSendResource), Focusable/FocusPlugin with Tab handling, A11yRole/A11yLabel/A11yTreeBuilder, AABB hit_test + Hovered, BuiyRenderPlugin with rounded-rect WGSL shader and render-graph node into Core2d::EndMainPass.
  • buiy_widgetsButton::new(impl Into<String>) + OnPress Message.
  • buiy (meta) — BuiyPlugin composing sub-plugins in documented order; render registration in Plugin::finish.
  • buiy_verify — visual-diff RMSE primitive (compare_images), AccessKit JSON snapshot + diff, WCAG 2 contrast linter (sRGB→linear via Color::to_linear()), lint_theme walking 3 canonical pairs.

Verification harness (3 of 15 gates as primitives + 1 partial)

CI

  • Lint job (clippy + fmt) under RUSTFLAGS=-D warnings.
  • 3-OS test matrix (Linux apt deps + xvfb-run, macOS, Windows).
  • One e2e fixture (tests/hello_button_e2e.rs) with golden a11y tree + Tab focus.

Pre-merge cleanups (last 2 commits)

Two rounds of independent-review-swarm cleanups, each multi-reviewer-confirmed:

  • 9b84e0bFlexDirection enum, NonSendResource Taffy tree (with GC TODO), warn-on-error in layout, documented unreachable! in verify.
  • d72ea2e — drop unused deps (accesskit / accesskit_winit / bevy_picking / image-compare / thiserror), remove pub fn advance_focus_for_test test-only-API leak (tests now drive ButtonInput<KeyCode> through real handle_tab), document BuiyPlugin plugin-order requirement, register FlexDirection for reflection, guard 0×0 in compare_images, warn-on-error for layout/pipeline let _ =.

Trade-offs deferred

Honest deferrals named in the plan + tracked separately, not addressed here:

  • LayoutTree GC across despawns (crates/buiy_core/src/layout.rs:30).
  • #[non_exhaustive] pass over public types.
  • Empty rustdoc landing + non-empty README before tagging public 0.1.
  • cargo audit / cargo deny / cargo doc -D warnings CI jobs.
  • System-set ordering test, first proptest! smoke property.
  • Rendering: pipeline + render-graph wired but BuiyNode::run opens/closes the pass without encoding draws (Phase 0 plumbing-only milestone).

These belong on the eventual v0.1 release issue, not this PR.

Test plan

  • cargo clippy --workspace --all-targets -- -D warnings clean
  • cargo test --workspace — 31 passing / 0 failing / 3 ignored (GPU-bound, lavapipe-gated)
  • CI green on Linux + macOS + Windows (the existing .github/workflows/ci.yml will run on PR)
  • Manual review of the 20-sub-spec issue tracker (Buiy roadmap — foundation + 20 sub-specs (tracker) #2) for any sub-spec needing a same-PR scope adjustment

Generated by Claude Code

claude added 30 commits May 7, 2026 03:21
The brainstorming skill writes session state under .superpowers/.
Keep it out of git so transient mockups don't pollute history.

https://claude.ai/code/session_01W662m44p1p5Xy57oEXxKg1
Feature inventory and architectural foundation for buiy, a comprehensive
UI library for Bevy with web-quality accessibility. Defines parallel
architecture (own renderer, own component model, integrating Taffy +
cosmic-text + AccessKit + bevy_picking + Bevy's render graph directly),
ECS + BSN authoring, token-based theming, and a fully automated E2E
verification pipeline. Catalogs every feature by tier (foundation /
core / extended / out) and lists the sub-specs each subsystem will get.

Indexed under Foundation in docs/README.md.

https://claude.ai/code/session_01W662m44p1p5Xy57oEXxKg1
Apply pass-1 fixes from five independent reviewers (spec quality, web
parity, a11y bar, Bevy ecosystem fit, verification rigor):

- Goal #7 reworded to scope automation to machine-testable claims;
  introduce explicit CI-gate vs manual-release-gate two-tier model.
- Coexistence reframed as per-window: AccessKit allows one tree per
  window adapter, so Buiy and bevy_ui own a window each rather than
  sharing one.
- BSN constraint extended: components must derive Reflect + FromReflect
  + Default + Clone + Component and be type-registered, per BSN's
  reflection-driven asset format.
- Section 3.11 carries a full WCAG 2.2 Level A/AA enumeration with
  per-SC enforcement strategy (CI / runtime / lint-with-review /
  design-constraint / out-of-scope).
- Add missing web features: popover state machine, fullscreen,
  <picture>, Canvas2D, dialog ::backdrop / closedby, structural pseudos,
  Resize/Intersection/Mutation observer analogues, color-scheme,
  forced-color-adjust, ::before/::after, :state(), :dir/:lang,
  <datalist>, command/commandfor invokers, interpolate-size,
  font-face metric overrides, hanging-punctuation, text-box-trim.
- Promote aria-keyshortcuts to F; add aria-haspopup / aria-current /
  aria-checked / aria-pressed value taxonomies; spell out drag-a11y
  replacement contract since aria-grabbed/dropeffect are deprecated.
- Add missing widgets: Scrollbar (standalone), Carousel (C, tied to
  2.2.2), Feed (C), Treegrid (C), Card, Rating, Log, Timer, Anchored
  Popover, Fullscreen surface.
- Add missing sub-specs: buiy-asset-pipeline-design,
  buiy-coexistence-design (conditional), buiy-window-and-surface-design,
  buiy-clipboard-and-os-integration-design.
- Refine 3.15 verification: per-platform goldens with tolerance budget,
  golden-update --accept workflow, manual release gates with owners and
  cadence, platform staging (desktop F, Android/iOS deferred),
  forced-colors scan defined, perf-regression baseline strategy
  (relative vs main, fixed self-hosted runner), memory leak threshold,
  property-test strategies named.
- Tier rubric cleanup: F/C dual tag split into single tiers; AA/AAA
  dual-level convention added to legend; "as needed" units tagged;
  marquee broken out as own line; reactivity layer moved E -> O-deferred;
  hidden input separated from file/color picker grouping.

Net: +284 lines, -99 lines. Spec is now 1126 lines.

https://claude.ai/code/session_01W662m44p1p5Xy57oEXxKg1
Apply fixes from three pass-2 reviewers (cross-cutting, Bevy + verification,
a11y + web parity):

WCAG accuracy:
- 3.2.6 Consistent Help: Level A (was AA in table + prose).
- 3.3.7 Redundant Entry: Level A (was AA).
- 3.3.8 strategy CI + DC (was DC) — paste-allowed is testable.
- 2.4.12 added to main table (only in aspirational list before).
- 2.5.6 Concurrent Input Mechanisms added to AAA list given gamepad goal.
- 1.4.7 / 1.4.8 / 1.4.9 added to AAA list.

ARIA completeness:
- code role added; "(~38)" → "(38)".
- img / image alias note.
- directory role explicitly listed as deprecated/not implemented.
- :fullscreen, :modal, :popover-open pseudo-classes.
- ::highlight() Custom Highlight API.

Tier rubric:
- Three remaining `F / C` dual tags (3.4 pseudo-elements line, 3.7 at-rules
  line, 3.11 globals line) split into single-tier per-item bullets.
- `O (deferred)` ad-hoc tier removed; reactivity layer is plain O.

Bevy ecosystem:
- AccessKit adapter explicitly keyed by winit WindowId, not Bevy Entity.
- BuiyPlugin sub-plugin order committed.
- BuiySet::Layout/Style/Input/Animate/Picking/A11yUpdate/Render named.
- Animation runs in Update against Time<Virtual>, not FixedUpdate.
- ExtractSchedule / RenderApp boundary acknowledged.
- wgpu wording: "version-pinned dependency" not "re-exported."
- UiPickingPlugin coexistence policy with DefaultPlugins specified.
- Per-window state keyed by WindowId in 3.18.
- Stack-switching policy: fixed at window creation, no runtime switch in v1.

Verification rigor:
- Manual release gates now have a release-blocking sign-off mechanism
  (docs/release-notes/<version>/manual-gate-<gate>-signoff.md).
- Multi-window verification is per-WindowId.
- Hot-reload AssetEvent flow specified.
- Perf budget split: CI gate mechanism committed; specific numbers calibrate.
- Coverage tradeoff acknowledgment added to goal #7 and 3.15.

Other:
- Section 2.8 crate split softened ("indicative" not "committed").
- Section 2.9 AccessKit cadence reframed to match Section 5 open question.
- Section 3.7 trusted-vs-synthetic clarifier (web isTrusted vs test synthesis).
- Section 3.10 lead-in rewording to not over-promise vs Section 3.15.
- Section 3.10 Card / Rating APG mapping clarified.
- Section 3.7 accesskey / global key activation policy added.
- Section 3.4 pseudo-elements deduplicated; canonical list in 3.7.
- Section 5 perf-budget reframed to match CI commitment.

Net: +65 / -32 lines. Spec is now 1159 lines.

https://claude.ai/code/session_01W662m44p1p5Xy57oEXxKg1
Convert the 1159-line single-file spec into a multi-file folder per the
docs nesting convention (specs/YYYY-MM-DD-<topic>/README.md + children).
9 files, each 80-219 lines:

- README.md (parent) — purpose, goals & non-goals, child index, sub-spec
  roadmap, open questions, references.
- architecture.md — Section 2 (architectural foundation, primitives,
  what Buiy owns, authoring, theming, a11y, reactivity, modules,
  compatibility & policy).
- visuals.md — Sections 3.1 + 3.2 + 3.3 (document model, layout, visual
  styling and rendering).
- text.md — Sections 3.4 + 3.5 (typography, text editing).
- interaction.md — Sections 3.6 + 3.7 + 3.8 (forms, events & input,
  animation).
- media-and-widgets.md — Sections 3.9 + 3.10 (media, widget catalog).
- accessibility.md — Section 3.11 (ARIA, ACCNAME, focus, keyboard,
  WCAG 2.2 SC table).
- verification.md — Section 3.15 (CI gates, manual release gates,
  platform matrix, hot-reload trigger flow).
- cross-cutting.md — Sections 3.12 + 3.13 + 3.14 + 3.16 + 3.17 + 3.18
  (i18n, state, theming, devtools, 3D-anchored, compatibility &
  coexistence).

All cross-references between sections updated to link to the appropriate
child file. docs/README.md index updated to point at the new folder.

The old single-file spec at docs/specs/2026-05-07-buiy-foundation-design.md
is removed.

https://claude.ai/code/session_01W662m44p1p5Xy57oEXxKg1
Phase 0 = smallest end-to-end "does the architecture work" demo:

- Workspace + crate scaffolding (buiy / buiy_core / buiy_widgets / buiy_verify
  / examples/hello_button).
- CorePlugin + BuiySet::* system sets in documented order.
- Node, Style, ResolvedLayout components with Reflect + register_type.
- Theme + UserPreferences resources, default light theme.
- Layout system via Taffy in BuiySet::Layout.
- FocusPlugin: Focusable, FocusedEntity, FocusVisible, Tab handling.
- A11yPlugin: A11yRole, A11yLabel, A11yDescription, A11yTreeBuilder.
- PickingPlugin: AABB hit-test + Hovered resource (real bevy_picking
  backend deferred to v0.x).
- BuiyRenderPlugin: extract phase, rounded-rect WGSL shader, render-graph
  node into Core2d after MainPass.
- Button widget with OnPress event.
- BuiyPlugin meta-plugin composing everything in documented sub-plugin
  order; render registration in Plugin::finish.
- buiy_verify harness: visual regression (perceptual diff with tolerance),
  AccessKit tree snapshot (stable JSON + diff), WCAG 2 contrast linter.
- Examples/hello_button: minimal app with one Button.
- End-to-end test: layout + a11y tree golden + Tab focus + AA contrast.
- CI on Linux/macOS/Windows with xvfb on Linux.

20 tasks total. Plan is TDD-structured (write failing test → run → implement
→ verify → commit per task). Self-review confirms spec coverage, no
placeholders, type consistency.

Indexed under Foundation in docs/README.md.

https://claude.ai/code/session_01W662m44p1p5Xy57oEXxKg1
Phase 0 Task 1. Stands up the Cargo workspace plus stub crates that later
phase-0 tasks will fill in: `buiy`, `buiy_core`, `buiy_widgets`,
`buiy_verify`, and `examples/hello_button`. Each member is a one-line
placeholder; subsequent tasks replace them in full.

Departures from the plan's literal text (acknowledged here so future
readers know it was deliberate, not drift):

- `rust-version` bumped from 1.83 to 1.85 because edition 2024 requires
  >= 1.85 (cargo refuses 1.83 + edition 2024). The plan's pin was
  internally inconsistent.
- `accesskit_winit` pinned to 0.32 (latest stable) instead of the plan's
  0.30. The spec's parallel-to-`bevy_ui` architecture means we own the
  AccessKit version directly, so picking the latest compatible release
  is preferred over a stale pin.
- Added a thin workspace-root `[package] buiy_workspace` (with
  `src/lib.rs`) so `tests/workspace_smoke.rs` is actually picked up by
  `cargo test --workspace`. Without a root package, a virtual workspace
  silently ignores top-level `tests/`, defeating the point of the smoke
  test. The root package has no real code and is `publish = false`.

Verified: `cargo build --workspace`, `cargo test --workspace` (the smoke
test runs and passes), `cargo fmt --all -- --check`, and
`cargo clippy --workspace -- -D warnings` are all clean. `rustfmt`
emits warnings about `imports_granularity` / `group_imports` being
nightly-only on the stable toolchain pinned in `rust-toolchain.toml` —
left as-specified by the plan; will need a follow-up decision (drop the
options or move to nightly fmt) but does not affect formatting output.

https://claude.ai/code/session_01W662m44p1p5Xy57oEXxKg1
…al versions

Code-quality review on Task 1 flagged that rustfmt.toml's
imports_granularity / group_imports options are nightly-only and emit
warnings on the pinned stable toolchain. Drop them; leave a comment so
the intent is captured if/when fmt moves to nightly.

Also sync the plan to the version pins that actually shipped:
- rust-version = "1.85" (edition 2024 requires it; plan's 1.83 was
  internally inconsistent)
- accesskit_winit = "0.32" (latest compatible stable; plan's 0.30 was
  superseded)

cargo fmt --check is now clean. workspace_smoke test runs and passes.

https://claude.ai/code/session_01W662m44p1p5Xy57oEXxKg1
Adds LayoutPlugin which keeps a single TaffyTree (entity-keyed) warm
across frames and runs sync_and_compute_layout in BuiySet::Layout,
reading Style and writing ResolvedLayout.

The plan's reference code targeted a slightly older Taffy/Bevy pair;
real shipped APIs required these adjustments:

  * Bevy 0.18 renamed Parent -> ChildOf; the parent entity is now
    accessed via ChildOf::parent() instead of Parent::get().
  * Taffy 0.10 turned Dimension into a tuple struct: use
    Dimension::length(x) / Dimension::auto() in place of the old
    Dimension::Length(x) / Dimension::Auto enum variants.
  * Taffy's CompactLength stores a *const () tagged pointer
    unconditionally on 64-bit targets, making TaffyTree !Send/!Sync.
    Layout is single-threaded by nature, so LayoutTree is registered
    via init_non_send_resource and read with NonSendMut. This avoids
    unsafe and keeps the tree cache warm across frames.
Code-quality review on Task 6 flagged three deferred-but-undocumented
behaviors. Add a Phase 0 deferred behavior section to focus.rs:

- Auto tab order uses entity.index() (not insertion order); full
  insertion-order stability lives in buiy-focus-model-design.
- FocusVisible is set true on Tab and never reset; pointer-driven
  paths (which clear it) live in buiy-focus-model-design.
- Shift detection covers ShiftLeft/ShiftRight only; full key-binding
  abstraction lives in buiy-input-events-design.

No code changes; doc-only.

https://claude.ai/code/session_01W662m44p1p5Xy57oEXxKg1
Code-quality review on Task 8 noted that every other resource in
buiy_core (Theme, UserPreferences, FocusedEntity, FocusVisible) derives
Reflect and is register_type'd. Add the same to Hovered for inspector /
devtools consistency.

https://claude.ai/code/session_01W662m44p1p5Xy57oEXxKg1
Code-quality review on Task 9 flagged two Important issues:

1. ExtractedDraws was init_resource'd in both main app and RenderApp,
   but only the render world reads it. Drop the dead main-world init.
2. Color::WHITE fallback for missing theme tokens silently masks typos.
   Replace with magenta sentinel + tracing::warn! so the typo'd token
   name surfaces in dev.

Also added a TODO(v0.x) marker on the per-frame Vec reallocation in
extract_buiy_draws — addressed when ResMut+clear/extend pattern lands
in buiy-render-pipeline-design.

https://claude.ai/code/session_01W662m44p1p5Xy57oEXxKg1
Replaces the Task 9 pipeline.rs stub with a real Bevy 0.18 render
pipeline backed by a signed-distance-field rounded-rect WGSL shader.
The shader emits a unit quad per draw with per-instance (rect, color,
radius) attributes and anti-aliased edges via fwidth+smoothstep.

The pipeline is queued in the render world via PipelineCache and
exposed as a `BuiyPipeline` resource so the upcoming render-graph
node (Task 11) can dispatch draws against it.

Bevy 0.18 API adjustments versus the plan:
- `Handle::weak_from_u128` does not exist in 0.18; constructed
  `Handle::Uuid(Uuid::from_u128(...), PhantomData)` directly.
- `VertexBufferLayout` is re-exported from `bevy::mesh`, not
  `bevy::render::render_resource`.
- `VertexState`/`FragmentState`'s `entry_point` is
  `Option<Cow<'static, str>>`; wrapped the entry-point literals in
  `Some(...)`.
- `PipelineCache::queue_render_pipeline` takes `&self`; switched to
  `world.resource::<PipelineCache>()` (no mutable borrow needed).

The second integration test (`pipeline_registers_in_render_app`) is
marked `#[ignore]`: `RenderPlugin::build` does a blocking wgpu adapter
request that panics in headless environments without a GPU or
software (lavapipe) ICD. Real end-to-end rendering coverage moves to
the visual regression harness in Task 19.
Code-quality review on Task 10 flagged four items:

1. Add UUID prefix convention comment for buiy_core render-asset handles
   (0xB01A_01XX_..). Future tasks should increment the trailing octet.
2. Set cull_mode = None until Task 11 verifies unit-quad winding order
   under TriangleStrip. The naive (0,0)(1,0)(0,1)(1,1) emission mixes
   CCW and CW windings; back-face culling would silently drop the quad.
3. Tighten pub fn register to pub(crate) fn register — only used inside
   the crate.
4. Document the pixel->clip-space conversion gap as a TODO in shader.wgsl
   and flag for Task 11 to decide between CPU pre-multiply or view
   uniform. The pipeline's layout: vec![] will grow if a uniform is
   chosen.

Also dropped now-unused Face import.

https://claude.ai/code/session_01W662m44p1p5Xy57oEXxKg1
Replace the Phase 0 stub with a real `BuiyNode: ViewNode` that opens a
render pass into the 2D color attachment, binds `BuiyPipeline`, and
short-circuits when the pipeline is still compiling or the extracted
draw list is empty. Vertex / instance buffer construction is deferred
to v0.x per the Phase 0 plan; Task 19 verifies end-to-end rendering.

Bevy 0.18 API adjustments versus the task draft:
- Use `Node2d::EndMainPass` (no `Node2d::MainPass` exists; the 2D pass
  ends with `EndMainPass` before tonemapping).
- Use `RenderGraphExt` (not `RenderGraphApp`).
- `ViewNode::run` takes `QueryItem<'w, '_, Self::ViewQuery>` (two
  lifetimes in 0.18).
- Enable `bevy_core_pipeline` feature so `bevy::core_pipeline::core_2d`
  is reachable; Buiy needs the Core2d sub-graph to attach into.

Add an `#[ignore]`d render-smoke test asserting `BuiyRenderLabel` is
present in `Core2d`. Same wgpu-adapter rationale as Task 10's pipeline
test; e2e coverage lands in Task 19.
…hoice

Code-quality review on Task 11 flagged two doc gaps:

1. Why insert before tonemapping rather than after — Buiy widgets share
   the 2D scene's color pipeline (matters when HDR / advanced color
   management is enabled in v0.x).
2. Why view_target.get_color_attachment() over main_texture_view() —
   the former respects MSAA-resolve and ViewTarget's ping-pong; the
   latter would break post-processing composition.

Doc-only.

https://claude.ai/code/session_01W662m44p1p5Xy57oEXxKg1
First Phase 0 widget. `Button::new(label)` returns a bundle with
`Node + Style (themed) + Focusable + A11yRole::Button + A11yLabel`,
and `emit_on_press_on_click` writes `OnPress(entity)` when the
hovered entity is a `Button` and primary mouse just-pressed.

Bevy 0.18 API notes:
- `OnPress` is `#[derive(Message)]` (not `Event`); `Event` in 0.18
  is reserved for observer-style triggers, while buffered events
  moved to `Message` / `Messages<T>` / `MessageWriter` / `MessageReader`.
- `WidgetsPlugin` registers it via `add_message::<OnPress>()` (the
  former `add_event` was renamed).
- `MessageWriter::write` replaces `EventWriter::send`.
- The click system takes `Hovered` and `ButtonInput<MouseButton>`
  as `Option<Res<_>>` so the system runs cleanly under partial
  test harnesses (`MinimalPlugins + CorePlugin` does not include
  `PickingPlugin` or bevy_input's `InputPlugin`).

Tests cover (1) bundle composition contract and (2) click → OnPress.
…design

Code-quality review on Task 12 flagged three deferrals that were
defensible but invisible at the call site:

1. Hardcoded sizes (width 120, height 32, padding 8) — should be
   size tokens. Added TODO referencing buiy-widget-catalog-design.
2. OnPress fires on mouse-down via just_pressed; APG/web fire on
   mouse-up-after-press-on-target so users can drag-cancel. Added
   TODO above emit_on_press_on_click.
3. Phase 0 ships mouse activation only; APG buttons activate on
   Enter/Space when focused. Added TODO for keyboard activation.

Doc-only.

https://claude.ai/code/session_01W662m44p1p5Xy57oEXxKg1
Compose sub-plugins in the documented order (core, theme, a11y, focus,
layout, picking, widgets) and defer render registration to Plugin::finish
so RenderApp exists when we reach it. Re-export the common API at the
crate root for ergonomic imports.

The integration test adds bevy::input::InputPlugin alongside
MinimalPlugins because BuiyPlugin's focus + button systems read
ButtonInput<KeyCode> / ButtonInput<MouseButton>; without InputPlugin,
app.update() would panic on the missing resources.

See: docs/specs/2026-05-07-buiy-foundation/architecture.md \xc2\xa7 2.8.
Code-quality review on Task 13 flagged four small items:

1. Drop ExtractedDraws from crate-root re-exports — render-world
   resource only; main-world readers would see an empty Vec.
2. Document the InputPlugin requirement on BuiyPlugin (DefaultPlugins
   includes it; MinimalPlugins users must add it explicitly). Bevy 0.18
   panics on missing Res<T> params, so the dependency is hard.
3. Add comment explaining why LayoutPlugin/PickingPlugin sit between
   Focus and Widgets (so widgets see resolved layout + hit-test).
4. Restore repository.workspace + rust-version.workspace in Cargo.toml.

Doc + manifest hygiene only; no behavior change.

https://claude.ai/code/session_01W662m44p1p5Xy57oEXxKg1
Adds visual / a11y / contrast module stubs and a smoke test that
verifies the public re-exports compile. Tasks 15-17 will populate the
stubs (visual regression, AccessKit tree snapshot, WCAG2 contrast).

Pins workspace `image-compare` to 0.5 (was 0.6) -- 0.6 does not exist
on crates.io; 0.5.0 is the latest published release. The original
Phase 0 plan listed 0.6 speculatively and this is the first crate to
actually pull the dep, so the resolution failure surfaces here.

https://claude.ai/code/session_01W662m44p1p5Xy57oEXxKg1
Code-quality review on Task 15 flagged:

1. (correctness) a8.width() * a8.height() multiplies in u32 before the
   cast to u64, overflowing for images >4 gigapixels. Cheap to harden:
   widen each dimension to u64 first.
2. (forward-compat) Drop a return value silently when calling
   compare_images. Add #[must_use] to DiffResult so callers can't.
3. (binary-fixture hygiene) Add .gitattributes marking PNG/JPG/WebP/
   AVIF/KTX2 as binary so git won't text-diff them as fixtures grow.

clippy::double_must_use caught the redundant fn-level #[must_use] when
the return type is already #[must_use]; dropped it.

https://claude.ai/code/session_01W662m44p1p5Xy57oEXxKg1
claude added 12 commits May 8, 2026 00:02
…test

Code-quality review on Task 16 flagged three small items:

1. diff_snapshots doc said 'unified_diff_text' but body returns
   'LEFT:\n…RIGHT:\n…' dump. Drop 'unified', explain the v0.x swap
   to the similar crate.
2. Add LINT comments above WireNode (field order is wire format) and
   role_to_str (keep in sync with A11yRole when v0.x expands taxonomy).
3. Add missing test for the Some branch of diff_snapshots.

3/3 a11y tests now pass.

https://claude.ai/code/session_01W662m44p1p5Xy57oEXxKg1
Code-quality review on Task 17 flagged three items:

1. (block) #[allow(dead_code)] _lint_theme_kept_in_scope was a workaround
   to silence unused-import warning. Replaced with a real test:
   lint_theme_passes_for_default_light asserts Ok on the default theme.
2. Document ContrastSeverity::Warn variant (reserved for advisory tiers
   when APCA lands; unused in Phase 0).
3. Add doc comments on wcag2_ratio + relative_luminance, especially that
   relative_luminance must NOT re-apply gamma (Bevy's to_linear already
   does the sRGB->linear transform).
4. Add TODO on lint_theme noting the exhaustive surface×text walk
   deferred to v0.x typed-token sub-spec.

5/5 contrast tests pass.

https://claude.ai/code/session_01W662m44p1p5Xy57oEXxKg1
Replace the placeholder `fn main() {}` stub with a real Buiy app:
DefaultPlugins + BuiyPlugin, a Camera2d, one Button::new("Save"),
and a logger that reads OnPress.

Bevy 0.18 split buffered events into Message, so OnPress is read
with MessageReader (not EventReader as the original plan wrote).
…LINT

Code-quality review on Task 19 flagged three items:

1. e2e_default_theme_passes_aa_contrast was byte-equivalent to
   crates/buiy_verify/tests/contrast.rs::lint_theme_passes_for_default_light
   (added in Task 17). Pure duplication. Removed; the doc-comment now
   names where coverage actually lives.
2. The doc comment promised 'simulated click emits OnPress' but no test
   drives it. Removed the bullet; click coverage lives in
   crates/buiy_widgets/tests/button.rs.
3. canonicalize_entity_ids is a naive scanner that breaks if wire-format
   'entity' values become non-scalar. Added a LINT comment documenting
   the assumption + the serde_json round-trip fallback if it ever grows.

Result: 2/2 e2e tests pass. Click + visual + contrast are covered in
the per-crate tests as documented.

https://claude.ai/code/session_01W662m44p1p5Xy57oEXxKg1
This commit completes Phase 0 of Buiy: the smallest end-to-end
"does the architecture work" demo. The workspace contains:

- buiy_core: components, plugin, system sets, layout (Taffy), focus
  model, a11y components + tree builder, picking backend, render
  pipeline (rounded-rect WGSL shader, render-graph node)
- buiy_widgets: Button widget with OnPress
- buiy_verify: visual regression diff, AccessKit tree snapshot, WCAG 2
  contrast linter
- buiy meta-crate: BuiyPlugin composing all sub-plugins in documented
  order, with InputPlugin requirement documented
- examples/hello_button: minimal app with one Button
- tests/hello_button_e2e.rs: end-to-end verification asserting layout,
  a11y tree, focus, and contrast all work together
- .github/workflows/ci.yml: lint + cross-platform test matrix
  (ubuntu / macos / windows) with rustfmt + clippy gates and
  xvfb-run on Linux for headless GPU/window tests; at-spi2-core
  installed for AccessKit's AT-SPI adapter

https://claude.ai/code/session_01W662m44p1p5Xy57oEXxKg1
Final whole-implementation review on Phase 0 named three small fixes
worth landing before tagging Phase 0 done. All addressed in this commit.

1. LayoutTree GC TODO at the source.
   `LayoutTree::by_entity` and the Taffy tree itself grow monotonically
   when entities are despawned. Phase 0 doesn't despawn yet; v0.x will.
   Added `TODO(buiy-layout-design)` at the resource definition naming
   the `RemovedComponents<Node>` reader system as the v0.x fix.

2. Replace two `expect()` panics with documented error paths.
   - `tree.tree.new_leaf(...).expect("new_leaf")` → `match` with a
     `warn!` and entity skip-this-frame. Taffy can OOM on `new_leaf`;
     for a UI library shipped to game devs we should not panic.
     `set_style` likewise upgraded from `let _ =` to `if let Err(err)`
     + `warn!` since silent failure under stale ids would draw stale
     geometry.
   - `serde_json::to_string(&wire).expect("snapshot serializes")` →
     `match` returning `unreachable!` with a comment explaining why
     `WireNode` (`u64 + &str + &str + &str + bool`) is infallible to
     serialize. Documented the upgrade path: if `WireNode` ever grows
     a non-trivial Serialize, return `Result<String, _>`.

3. `Style.flex_direction: u8` → `enum FlexDirection { Row, Column }`.
   The `0 = Row, 1 = Column, anything-else silently treated as Row`
   sentinel was a magic-number footgun. Real enum kills it; BSN-friendly
   (Reflect + Default + Clone + Copy + PartialEq + Eq); zero-cost in
   layout's `match` mapping to Taffy's `FlexDirection`.

Re-exported `FlexDirection` at both `buiy_core::components` and the
`buiy` meta-crate root for ergonomic access. Updated layout test.

cargo build/test/clippy/fmt all clean. 26 tests pass + 3 ignored
(GPU-dependent). No regressions.

https://claude.ai/code/session_01W662m44p1p5Xy57oEXxKg1
Multi-reviewer-confirmed cleanups identified by the Phase 0 independent
review swarm. All seven were either single-line risk reductions or had
two reviewers independently flag the same issue.

- Drop unused workspace deps: accesskit, accesskit_winit, bevy_picking,
  image-compare, thiserror. None were imported in src; bevy_picking +
  accesskit also clashed in version with Bevy 0.18's bundled copies.
- Remove `pub fn advance_focus_for_test` from `buiy_core::focus`. It was
  the textbook test-only-API-leaked-to-public anti-pattern. Tests now
  drive `ButtonInput<KeyCode>` through the real `handle_tab` system, the
  same pattern `crates/buiy_widgets/tests/button.rs` already uses for
  `MouseButton`. Splits the sprawling tab test into 4 focused tests.
- Document plugin-order requirement in `BuiyPlugin` rustdoc: adding
  `BuiyPlugin` before `DefaultPlugins` panics on missing `PipelineCache`.
- `register_type::<FlexDirection>()` in `CorePlugin::build` so reflection
  recurses through `Style.flex_direction` for inspector / scene serde.
- Guard 0×0 input in `compare_images`: previously returned NaN, which
  silently failed `passed(any_tol)` for every tolerance.
- Replace 3 `let _ =` swallowed errors with `warn!` matches (or an
  explicit `_prev` for the infallible-by-construction shader insert).

Tests: 31 passing (was 26), 0 failing, 3 ignored (unchanged GPU-bound).
Clippy: clean under `-D warnings` workspace-wide.

Trade-offs deferred to v0.x or pre-tag-0.1 (in plan, not this commit):
empty rustdoc landing, missing #[non_exhaustive], LayoutTree GC,
cargo audit/deny, system-set ordering test, first proptest property.
The split-up focus tests in d72ea2e introduced one assert! that exceeded
the line length budget. CI's `cargo fmt --all -- --check` flagged it;
the fix is purely cosmetic. Clippy + tests stay green.
The lint job runs clippy + rustfmt on ubuntu-latest without apt deps;
test job already installs them. clippy doesn't link, but it still drives
each transitive crate's build.rs — and bevy_winit's wayland-sys pkg-configs
the system in build.rs. Without libwayland-dev / libxkbcommon-dev / libudev-dev
that step fails almost instantly, which matches the 35-second job timing.

Adds the same apt-get block as the ubuntu-latest test job (minus xvfb +
at-spi2-core, which are only needed at test runtime).
@intendednull intendednull merged commit 39b3cdc into main May 8, 2026
4 checks passed
@intendednull intendednull deleted the claude/bevy-ui-planning-Hk8jW branch May 8, 2026 03:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants