Buiy foundation spec + Phase 0 implementation#24
Merged
Conversation
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
…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).
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.
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_uiarchitecture, 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_core—CorePlugin,BuiySetsystem sets (Layout → Style → Input → Animate → Picking → A11yUpdate → Render),Node/Style/ResolvedLayoutcomponents,Theme+default_light_theme, Taffy-backedLayoutPlugin(NonSendResource),Focusable/FocusPluginwith Tab handling,A11yRole/A11yLabel/A11yTreeBuilder, AABBhit_test+Hovered,BuiyRenderPluginwith rounded-rect WGSL shader and render-graph node intoCore2d::EndMainPass.buiy_widgets—Button::new(impl Into<String>)+OnPressMessage.buiy(meta) —BuiyPlugincomposing sub-plugins in documented order; render registration inPlugin::finish.buiy_verify— visual-diff RMSE primitive (compare_images), AccessKit JSON snapshot + diff, WCAG 2 contrast linter (sRGB→linear viaColor::to_linear()),lint_themewalking 3 canonical pairs.Verification harness (3 of 15 gates as primitives + 1 partial)
2026-05-07-buiy-foundationto [landed] #3 (a11y snapshots): in-house tree builder + JSON wire format with stability LINT.buiy-accessibility-design#9 (contrast linter): WCAG 2 ratio with the black-on-white = 21.0 sanity test.buiy-render-pipeline-design#4–spec:buiy-focus-model-design#8, spec:buiy-theme-tokens-design#10–spec:buiy-i18n-design#15: explicit deferrals named in the plan.CI
RUSTFLAGS=-D warnings.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:
9b84e0b—FlexDirectionenum, NonSendResource Taffy tree (with GC TODO), warn-on-error in layout, documentedunreachable!in verify.d72ea2e— drop unused deps (accesskit / accesskit_winit / bevy_picking / image-compare / thiserror), removepub fn advance_focus_for_testtest-only-API leak (tests now driveButtonInput<KeyCode>through realhandle_tab), documentBuiyPluginplugin-order requirement, registerFlexDirectionfor reflection, guard 0×0 incompare_images, warn-on-error for layout/pipelinelet _ =.Trade-offs deferred
Honest deferrals named in the plan + tracked separately, not addressed here:
LayoutTreeGC across despawns (crates/buiy_core/src/layout.rs:30).#[non_exhaustive]pass over public types.cargo audit/cargo deny/cargo doc -D warningsCI jobs.proptest!smoke property.BuiyNode::runopens/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 warningscleancargo test --workspace— 31 passing / 0 failing / 3 ignored (GPU-bound, lavapipe-gated).github/workflows/ci.ymlwill run on PR)Generated by Claude Code