RavenRS/Docs
GitHub

Quality bar

RavenRS components are meant to be used in production applications, at the level users expect from commercial component vendors. This page defines what "done" means for a component, which checks enforce it, the performance budgets, and where each component stands today.

Definition of done for a component#

A component is ✅ in PARITY.md only when all of these hold:

  1. API. Typed builder methods with defaults that look right without configuration. Values come in through Binding<T>, events go out through closures. Every public item has rustdoc, and the type has a runnable example. Growable enums are #[non_exhaustive].
  2. States. Rest, hover, pressed, focused (keyboard ring), disabled, selected or checked where it applies, empty, loading and error where it applies, and overflow (long text, many items, a tiny viewport).
  3. Screen readers. It sets a role and state with Semantics, and every control has a name: from its text, a label, a FormField or a Tooltip. The gallery audit (examples/component-gallery/tests/accessibility.rs) fails on any unnamed control.
  4. Keyboard. Reachable with Tab. Enter and Space activate. Arrow keys, Home and End follow the WAI-ARIA Authoring Practices pattern for that widget. Escape closes overlays.
  5. Themes. Built only from semantic tokens, so it is correct in Raven Dark, Light and Glass. Text meets WCAG AA contrast, which ravenrs-style tests for every theme.
  6. Edge cases. Empty data, a single item, huge counts, non-ASCII text, out-of-range values (clamped, never panicking) and a zero-size viewport.
  7. Performance. Meets the performance contract and stays inside the budgets below. Large collections are virtualized.
  8. Tests. At least one behavior test that drives it through Ui (pointer and keyboard). Property tests for any pure logic. A golden image for any component whose drawing is non-trivial.
  9. Docs and demo. A section in the component gallery that shows its variants and states.

Enforced checks#

scripts/check.sh --full runs every check below, and nothing ships unless it passes. The project uses no hosted CI, so this script is the gate. Without --full it is the quick check for each pull request: rustfmt, and clippy and the tests of the packages the change reaches (scripts/changed-packages.py).

Check What it catches
cargo fmt --check Formatting drift
cargo clippy -D warnings on native and wasm32 Lints, including missing_docs on every public item
cargo test --workspace Unit, behavior, accessibility, property (proptest) and doc tests
GPU golden-image tests (crates/ravenrs/tests/visual.rs) Visual regressions in 6 scenes × dark and light; tolerance 24/255 per channel on at most 0.4% of pixels
GPU pixel tests (crates/ravenrs-render/tests/offscreen.rs) Renderer correctness: shapes, clipping, images
cargo doc -D warnings Broken intra-doc links
cargo deny check Vulnerable, yanked or unlicensed dependencies, and unknown sources
cargo +1.88 check The declared minimum Rust version (MSRV) still builds
--no-default-features Optional features really are optional
cargo bench --no-run, only with RAVENRS_GATE=1 Benchmarks keep compiling. It runs when asked for, with the performance gate
cargo run --release -p ravenrs-perf -- gate, only with RAVENRS_GATE=1 Performance regressions: the exact work of every scene against perf/baseline.json. It runs when asked for, not on every check
Web builds The examples build and bind for WebAssembly

GPU tests skip themselves, with a message, on machines without a GPU adapter. To accept an intended visual change, regenerate the golden images with RAVENRS_UPDATE_GOLDEN=1 cargo test -p ravenrs --test visual, then review the PNG diff in the commit. A failing run writes *.actual.png next to the golden image.

Release checks, run by hand before each publish:

  • cargo semver-checks against the previous release.
  • cargo llvm-cov for coverage, at 75% line coverage as of 0.1. Coverage is a signal, not a target.

Applications built with RavenRS#

The rules above that a machine can check also bind applications, through ravenrs-lint. It reads an application's syntax tree and denies, like the compiler: literal colors and font sizes, spacing off the 4px grid, button captions without a verb, icon buttons without a name, more than one primary button in a view, State::new and blocking work in body or build, and files over 600 lines. Every rule is an error, and every exception needs a written reason. cargo ravenrs-lint --init adds the strictest rustc and Clippy levels, the test that runs the rules on cargo test, and the instructions coding agents read; cargo ravenrs-lint --gate is the one command that says the work is done.

A rule a machine can check goes into ravenrs-lint, not only into this page or the skill.

Performance method#

Optimizing guarantees nothing; a gate that fails on regressions does. There are two instruments, and the skill is the runbook for both.

The gate counts the work of each scene with a real runtime and a real renderer, offscreen: builds, layouts, placements, paints, shaping, allocations in the UI, draw calls and why they split, instances, bytes uploaded, glyphs rasterized, atlas resets. These numbers are exact, so any difference from perf/baseline.json fails, improvements included: they have to be accepted with gate --bless, which shows up in the diff. Each run repeats itself and fails if the two passes disagree, and it fails without a GPU. On the screens of real applications (app-*), allocations are only reported, since they depend on the app's data and the clock. Ceilings in the scene registration keep repeated blessing from eroding the budget. Outside macOS, only ceilings bind. scripts/perf-plant.py proves it works by planting regressions.

Frame time is measured apart, before each release, with scripts/perf-time.sh: the best p99 of three processes, because noise on a laptop almost only adds time. On battery the result is invalid. ravenrs-perf cold measures what warm numbers hide, the first time a screen opens.

Budgets for the reference machine, a 14" MacBook Pro with M2 Pro and a 120 Hz screen, at p99:

Class CPU GPU
Interaction: hover, scroll, animation 4 ms 3 ms
Rebuild: new data, opening a screen, resizing 16.67 ms 3 ms
Idle no frames, no wakes

Time is guaranteed only for the registered scenes on that machine. Counters reflect the same logic everywhere, but a latency on another machine does not follow from them. The system compositor is outside the measurement, and Metal's clock is coarse: short passes often read as zero.

What was measured#

On the reference machine at 1280x800 at 2x, with nothing else running. CPU time in milliseconds at p99, per frame:

Scene Elements Hover before Hover now Scroll before Scroll now Instances before Instances now Draw calls before Draw calls now
curve-small 105 0.90 0.36 0.05 0.05 684 485 61 44
curve-medium 1,005 5.48 2.71 0.44 0.08 6,804 482 601 43
curve-large 10,005 59.81 30.72 5.70 0.66 68,904 482 6,001 43
data-grid 210 1.18 0.85 1.25 0.85 644 526 71 62
scheduler scroll 711 4.56 1.41 808 696 452 389
app-finance-dashboard 320 1.91 1.24 0.34 0.19 1,041 802 116 101
app-finance-entries 423 1.82 1.11 1.82 1.13 1,293 1,237 118 116

The "before" column was measured with the same scenes before the work on the core. A Lottie playing went from a rebuild of the whole screen per frame to none. The finance dashboard opening for the second time went from 25.4 ms to 1.1 ms in its worst frame.

The calculator scene measures examples/calculator as it is, a small screen that stays the same between versions. A click on it, on the same machine: 0.16 ms on the CPU, 0.13 ms recording the GPU's work, 41 instances in 41 draw calls.

Where things stand:

  • Scrolling and animating no longer depend on the size of the screen. Only what can be seen is painted and sent to the GPU.

  • A rebuild still follows the size of the screen. About 2.7 µs per element, and layout is now 75% of that. The 4 ms budget holds up to about 1,500 elements. The real screens measured have 200 to 420. Beyond that, the next step would be invalidation by subtree, which changes the API of View and BuildCx.

  • The GPU is not the bottleneck. Under 2 ms in every scene.

  • Batching does little on small screens. Text and shapes alternate, so the calculator needs one draw call per instance. It costs nothing visible today, and painting order would have to be kept by any fix.

  • Resizing a window with charts. Bars and gridlines are quads now, with no picture to draw or send; a line with more points than columns draws only what can be told apart; and a picture covers only what it draws. The chart scenes, dragging the window to a new width each frame, p99 of CPU and of recording the GPU's work, before and after:

    Scene CPU before CPU now Recording before Recording now
    chart, a line of 5,000 points 26.0 13.9 7.2 5.7
    chart-bars, three series of 12 bars 5.5 0.1 6.3 0.1
    chart-board, area, bars, doughnut and line 7.2 5.5 4.7 3.2
    chart-sankey, ten flows between ten nodes, as quads and curves 0.04 0.1

    The finance dashboard went from 19.7 ms to 7.4 ms on the same measure. Lines, areas and slices stay pictures: each scene fits the 16.67 ms of a rebuild, and curves would be drawn again into the atlas on every frame, hover included, at a cost of about 1/26 of a frame per window-sized shape (see below). This comparison was reasoned from those limits, not measured.

  • Not measured: the delay from input to the screen in a real window, the system compositor, and other machines.

  • Not done: the gate does not yet fail a public component missing from every scene's covers.

How much the renderer can draw#

ravenrs-perf capacity grows each kind of work until a frame stops fitting, counting until the GPU finishes, at 2560x1600. On the reference machine:

Work Fits at 120 Hz Fits at 60 Hz Limited by
Rounded quads 30,000 60,000 GPU
Quads with a soft shadow 4,600 9,300 GPU, the area of the blur
Text 180,000 glyphs 370,000 glyphs CPU
A quad and a line of 43 glyphs in turn 1,700 3,600 Both; draw calls about double the cost
Quads each with its own clip 18,000 38,000 Both
Small shapes of curves 6,700 13,400 Both
Window-sized shapes of curves 26 56 GPU
Different images 13,700 27,600 CPU
Translucent layers over the whole window 39 81 GPU

The real screens measured use 500 to 1,400 instances, so the renderer has about 20 times the room they need. It is not what limits a screen; a rebuild is.

Shapes of curves share a coverage atlas of at most 8192x8192. Past that, the main pass splits to draw another page, so nothing is left out, at the cost of a pass per page.

Performance contract#

Each component answers for these, through a scene in crates/ravenrs-perf/src/scenes/ listing it in covers:

  1. A scene at its worst realistic case, using only the public API. Simple components can share one.
  2. A class: static, virtualized, animated or picture.
  3. Scale: going from N to 10N items, a virtualized component's counters grow at most 1.2 times.
  4. No useless work: an interaction that does not change text shapes nothing, and an animation builds nothing.
  5. Idle: no wakes without an animation running or when paused.
  6. Cold cost declared for a picture: its cache key and what it does with large data.
  7. Ceilings on draw calls, batch breaks and allocations per interaction.

Performance budgets#

Measured with cargo bench (criterion) on an Apple M-series laptop, release profile. Budgets assume a 60 Hz frame (16.7 ms).

Benchmark Budget Measured
Full frame: form with 40 cards (build + layout + paint) < 8 ms 1.6 ms
Full frame: DataGrid with 100,000 rows < 8 ms 2.0 ms
Animation frame: the same form, paint phase only < 2 ms 0.11 ms
DataGrid query over 100k rows: no sort < 16 ms 1.0 ms
… sort by a number < 16 ms 7.9 ms
… sort by text, then number < 16 ms 12.3 ms
… filter row range 100..200 < 16 ms 0.9 ms
… filter row expression over three conditions < 16 ms 8.6 ms
… search across columns < 16 ms 3.2 ms
… group with summaries < 16 ms 11.3 ms
Shape a label / shape and wrap a paragraph < 0.1 ms 0.003 ms / 0.035 ms
Report: grouped sales over 10,000 rows, 305 pages < 300 ms 80 ms native, about 200 ms in the browser
Report: invoice < 5 ms 0.05 ms

Charts are pictures made on the CPU, so their cost is paid when the data or the size changes, and not on other frames: a rebuild that produces the same shapes keeps the same texture. Measured in WebAssembly on Node 24 with the -Oz profile, without SIMD, on the same laptop:

Chart Median
12 bars, 760x260 at 2x 4.3 ms
Line with 12 points, 760x260 at 2x 6.1 ms
Pie with 8 slices, 360x260 at 2x 5.3 ms
AreaChart, 14 values, 760x200 at 2x 10.8 ms
Line with 12 points, 1600x900 at 2x 22.2 ms
Line with 1000 points, 760x260 at 2x 55.8 ms
QR Code, 160 at 2x 2.1 ms

Dragging a window's edge redraws every visible chart on each frame, so more than a few at once, or a long series, go over 16.7 ms while it lasts.

The size of a WebAssembly build, after wasm-opt -Oz:

Build Bytes Gzipped
hello-world, charts off 2,805,367 1,202,999
hello-world, charts on, not using it 2,806,628 1,203,232
component-gallery before charts used it 3,301,057 1,399,745
component-gallery using every chart and code 3,582,964 1,504,909

Link-time optimization drops what an app does not call, so the feature costs an app without charts about 0.2 KB gzipped. Turning it off saves compile time, not download size. An app that does use charts pays about 105 KB gzipped, its own demos included.

The DataGrid caches the query result and reruns it only when the data or the query changes, so these costs are paid once per user action, not every frame. Version 0.1 broke the budget in three cases (search at 47 ms, filter at 31 ms, text sort at 23 ms). They were fixed by parsing filters once per query, matching text case-insensitively without allocating, computing sort keys only for matching rows, and replacing text comparisons with precomputed ranks.

Component audit#

Status against the definition of done. Behavior names the test suites that drive the component. Visual marks golden-image coverage. Every component sets screen reader semantics, covered by accessibility (widgets) and the gallery audit. Right-to-left layout is missing for every component and is tracked once, in the gaps below.

Component Behavior tests Visual Keyboard Notes
Button, IconButton ui, keyboard_and_basics ✅ ✅ Enter/Space, tested
ToggleButton, ToggleGroup batch_parity, keyboard_and_basics ✅ arrows move focus, Space toggles, tested
TextField components, properties, editing_and_clipboard ✅ ✅ editing, word and line moves, Shift selection, select all, copy/cut/paste, mouse drag and double-click, tested; IME on web Passwords paste but never copy
Select components, keyboard_and_basics ✅ Enter/Space/arrows open on the current option, arrows, Enter, focus returns, tested
Combobox batch_parity ✅ type, arrows, Enter, tested
Checkbox, Toggle components, batch_parity, keyboard_and_basics ✅ ✅ Space, tested
RadioGroup, SegmentedControl keyboard_and_basics ✅ ✅ arrows, Home/End, wrap, tested Keyboard added in this pass
Slider components ✅ ✅ arrows, Home/End, tested
Stepper keyboard_and_basics ✅ arrows, Home/End, tested Keyboard added in this pass
TextArea, NumberField, MaskedField, TimePicker, DateTimePicker editors ✅ typing, arrows, tested
LookupField, FormLayout, FilterBuilder components ✅ type, arrows, Enter, tested
Toolbar, SplitButton, ConfirmDialog components ✅ buttons and menus, tested
TreeView, CardView app_components ✅ one Tab stop; arrows, Home/End, Enter, Space, tested Virtualized
ThemePicker, ThemeEditor components ✅ built from DropdownMenu, ColorPicker and Stepper, which bring their keys and names; tested by name
TagBox, ColorPicker, DateRangePicker app_components ✅ arrows, type, Enter, Escape, tested
Wizard, FileUpload, ImageField app_components ✅ buttons, tested Dropping files has no keyboard way; the chooser is the way
TileLayout app_components ✅ arrows move, Shift with arrows resizes, tested
DataGrid row details and Export menu app_components ✅ Right and Left, menu, tested
Disabled and read-only editors components ✅ out of the tab order, or readable, tested
InputOtp batch_parity ✅ digits and Backspace, tested
FormField, Label batch_parity, keyboard_and_basics n/a
Calendar, DatePicker batch_parity, keyboard_and_basics ✅ ✅ one Tab stop; arrows, Home/End, Page Up/Down, Enter, tested
EventCalendar event_calendar ✅ ✅ one Tab stop, shared with Calendar; arrows, Home/End, Page Up/Down, Enter, tested A day's name and description read out its events, tested
Scheduler scheduler ✅ ✅ events are Tab stops; arrows move, Shift resizes, Enter opens, Delete removes, tested Grid, rows and events named, with dates, tested. Pointer: drag, resize, draw, tested
RecurrenceEditor event_calendar ✅ ✅ made of tested controls Says the rule and its next days in words, tested
Ribbon ribbon ✅ 🟡 Tab + Enter; its menus are fully keyboard-operable Key tips planned
DropdownMenu, ContextMenu, Menubar keyboard_and_basics, components, batch_parity ✅ first item focused on open, arrows, Enter, Escape, focus returns; Menubar Left/Right between titles and open menus, tested
Tabs components, layout_and_content, keyboard_and_basics ✅ arrows open tabs, Home/End, tested
Sidebar sidebar, layout_and_content, components ✅ Cmd/Ctrl+B, Tab + Enter, Up/Down/Home/End between rows, Left/Right fold submenus and groups, Escape closes the sheet, tested navigation landmark; rail keeps names and shows tooltips on hover and on keyboard focus, tested. Width eases and is instant under reduced motion. Sheet goes by the window width, not its container's
Breadcrumb, Pagination, Accordion, Disclosure components, layout_and_content, batch_parity 🟡 Tab + Enter Links and buttons by pattern; untested
CommandPalette batch_parity ✅ ⌘K, type, arrows, Enter, tested
Dialog, Sheet, Popover, HoverCard, Tooltip, Toaster components, batch_parity, keyboard_and_basics ✅ Escape; Dialog and Sheet trap focus and return it, tested
SplashScreen splash ✅ no focus stop: takes no input, focus leaves the page and returns on close, tested Status and progress announced, tested
DataGrid datagrid, properties, editing_and_clipboard ✅ ✅ row navigation, Page Up/Down, Enter edits, Cmd/Ctrl+C copies rows as tab-separated text, Cmd/Ctrl+A, tested See the DataGrid parity table in ARCHITECTURE.md
Table, List, Stat components ✅ partial n/a
Chart, BarChart, AreaChart, Barcode charts, components ✅ a chart reads its values; a code reads its text or alt n/a The layout's own tests are in ravenrs-graphics
Layout: stacks, Grid, ScrollView, Splitter, AspectRatio, Card, Section layout_and_content, components, keyboard_and_basics ✅ partial 🟡 Splitter arrows, untested AspectRatio content now fills its box
Link, Anchor, CountUp, Ticker page_motion ✅ Link is a link with its label, focusable, tested by click ✅ Enter opens it Anchors glide, and jump under reduced motion; tickers stand still, tested
Sticky, ScrollScene, ScrollLinked scroll_scene ✅ pinned content is placed before hit testing, so the tree and clicks see it where it shows, tested n/a, takes no input Scrolling rebuilds nothing, tested. Under reduced motion links only fade, tested
Footer footer ✅ no focus stop of its own; its links are buttons (Tab + Enter), tested contentinfo landmark, tested. Stacks by window width, not by its container's
StatusBar status_bar ✅ no focus stop of its own; its actions are buttons (Tab + Enter), tested contentinfo landmark; the center is a status
Timeline, ToolRail timeline 🟡 blocks, markers and tools work with Tab + Enter; moving and trimming need the pointer, so apps offer them in fields too Blocks and markers are named buttons
ModelPicker model_picker ✅ every action is a button (Tab + Enter), tested through ravenrs-ai Actions are named buttons; the progress bar reads its value
Chat, FloatingPanel chat ✅ field, examples, send and sphere work with Tab + Enter, arrow keys move the sphere between corners and Escape closes the panel, tested Answers are a status, announced as they appear; the panel is a named group
ReleaseNotes, CodeBlock release_notes ✅ versions and buttons work with Tab + Enter, tested Lists its versions as buttons; the copy button says when it copied
MoviePlayer, MovieAssistant, MovieEditor, BugReporter ravenrs-movie ✅ controls, chapters, marks and blocks work with Tab + Enter; dragging on the canvas and the timeline has no keyboard way yet: 🟡 Marks say what they teach; a film is an image with a name
Lottie lottie ✅ decoration unless given alt, then an image with that name n/a Takes no input; shows its last frame still where motion is reduced, tested. crates/ravenrs/tests/lottie_corpus.rs measures real files against lottie-web
SplitPanes, SplitHandle structural_extensions ✅ Arrows, Shift and Home and End move the bar, tested
StepIndicator, StepBadge structural_extensions ✅ Tab and Enter on the steps that are buttons, tested
Dot, OverlayBadge, Rating, IconTile, EmptyState, HeroBanner, MultiProgress, ShimmerText, DaySeparator, ShortcutSheet, ShortcutHintBar, PropertyRow, PageHeader, ZStack, ButtonGroup, FavoriteToggle, SwatchGrid, NinePointAnchor, ColorPair simple_pieces ✅ Rating, SwatchGrid and NinePointAnchor take arrows, the rest Tab and Enter where they act, tested
Media and feedback: Image, Icon, Carousel, Avatar, Badge, Alert, ProgressBar, Spinner, Skeleton, Kbd, Divider layout_and_content, components, batch_parity ✅ partial 🟡 Carousel arrows, untested

✅ means the WAI-ARIA keys for that widget work and a test covers them. 🟡 means it is reachable and activatable but the full pattern is missing or untested.

Defects found and fixed during this audit:

  • RadioGroup, SegmentedControl and Stepper had no keyboard support beyond Tab. Tabs, ToggleGroup, every menu, the Select list and the Calendar grid lacked their arrow-key patterns, and Calendar days could not take focus at all.
  • Tab could leave an open Dialog or Sheet for the page behind it; focus now stays inside and returns to the opener on close.
  • Focus moved by code hid the focus ring even when the user was navigating by keyboard.
  • AspectRatio content with modifiers (for example Spacer::new().background(...)) collapsed to zero height.
  • The DataGrid query exceeded the frame budget on 100k rows (see above).

Known gaps#

These block a "commercial-grade" label and are prioritized ahead of new components of the same family:

  1. Right-to-left layout. Parley shapes bidirectional text, but layouts do not mirror.
  2. Remaining keyboard patterns in the 🟡 rows above: Ribbon key tips.
  3. Visual coverage. Golden images exist for 7 scenes. Overlays and navigation have none yet.
  4. Screen reader depth. Text fields do not expose caret position or selection, and the web mirror is rebuilt whole when the tree changes. Tested with macOS VoiceOver's API (System Events) and the browser's DOM; not yet with NVDA, Narrator or Orca.
RavenRS 27.1.27One UI. Native and web.
RavenRS Docsv27.1.27