Repository navigation
chore: integration check for the follow-up stack (do not merge) - #339
Draft
leoafarias wants to merge 94 commits into
Draft
leoafarias wants to merge 94 commits into
leoafarias wants to merge 94 commits into
Conversation
A NakedDisclosure with an explicit semanticLabel wrapped the trigger in Semantics(excludeSemantics: true), which also dropped the trigger's focus action and focused state from the semantics tree. Exclude only the visual trigger content instead, so the label still replaces it while the surrounding focus semantics survive.
…hanges The Vanilla and Fortal theme scopes checked MediaQuery.maybeOf(context) to decide whether to supply a MediaQuery from the view. maybeOf subscribes to every MediaQuery field, so the scope and everything it builds rebuilt on unrelated changes such as keyboard insets, once per frame while the keyboard moves. Use getElementForInheritedWidgetOfExactType, which does not subscribe. System mode still follows platformBrightness through its own aspect lookup. Regenerates the registry templates and the installed copies in the workspace consumer apps.
RemixToast laid the message, action, and close button in one row, so a narrow toast or large text squeezed the message into a sliver. Measure the width the toast gets (parent constraints enforced by its style constraints, scaled by the text scale) and, below the breakpoint, move the action and close button to their own row under the message. Wider toasts keep the single row.
Fortal defines focusRingWidth and focusRingOffset, but the checkbox, radio, switch, card and link outlines and the shared fortalFocusOutline hard-coded width 2 and offset 2, so overriding the tokens changed nothing for them. Outward rings (checkbox, radio, switch, link, classic and solid buttons) now read both tokens and every outline reads the width token; inset rings keep their -1 offset. Default token values are 2 and 2, so rendering is unchanged. The registry templates and the demo and dashboard installed copies are regenerated to match.
The first pass moved the Fortal outlines onto focusRingWidth and focusRingOffset, but the select trigger and the slider thumb still drew their rings from hard-coded box shadows. The select trigger now uses fortalFocusOutline with Radix's inset -1px offset (`outline: 2px solid focus-8; outline-offset: -1px`), replacing the outer plus inset spread-1 shadow pair that painted the same band. The slider thumb keeps Radix's 3px accent-3 halo and draws the ring as a token-width outline starting where the halo ends (`0 0 0 3px accent-3, 0 0 0 5px focus-8`). The halo stays a 3px literal: it is not the 2px outline-offset of the other outward rings. Default tokens give the same bands as before. Tests now cover every ring: outlines (including select), the slider thumb, and the inside-border rings (toggle, toggle group item, tabs, disclosure, accordion trigger). Registry templates and the demo and dashboard installed copies are regenerated to match.
The branch that supplies a MediaQuery when a scope sits above any had no test, because View always provides one in apps and widget tests. Pump each scope under a bare RawView and check it supplies a MediaQuery and follows the view's brightness. Also tell preset authors which lookup avoids the rebuild this PR fixes.
Add RemixToastActionPlacement (inline, below) as ToastSpec.actionPlacement and the generated ToastStyler.actionPlacement. Being a spec field, a style can switch it with a Mix variant such as onBreakpoint. `below` puts the action under the title and description, aligned with their start, and top-aligns the icon, message, and close button so the close button stays at the trailing top corner. An inline toast that is too narrow for one row at the current text scale uses the same arrangement; the fallback keeps the existing width measurement and no longer moves the close button, or a toast without an action, anywhere. The stacked arrangement groups the message and action into one semantics node and keeps widget-order focus traversal, so assistive technology and Tab still reach the title, description, action, then close button.
Give the focus contract its own test that performs the semantics focus action, keep the label test about replacement, wait a frame before reading the controlled panel so the test also holds on the web, and drop the activation test that could not detect this change. Clarify the comment.
An errored Fortal text field keeps an error-8 outline whether or not it is focused, merged after the focus ring, so it is also the only ring drawn while the field has focus. Its width was a literal 2, so overriding focusRingWidth did nothing for errored fields. It now reads the token, keeping the -1 offset and the inset shadow, so default rendering is unchanged. The token test now also covers the segmented control item, the solid and soft icon buttons, the disabled field's gray-8 ring and the errored field (idle and focused), and checks that the inside-border rings use the focus colour. The slider assertion that restated the offset and width checks is removed, and the fortalFocusOutline doc comment now says which offsets follow the offset token. Registry templates and the demo and dashboard installed copies are regenerated.
RemixToast is now a StatefulWidget with the same constructor. It keeps the action and close button under GlobalKeys, so their elements, and the keyboard focus on them, survive a switch between the inline and stacked arrangements. Before, a resize, rotation, or text-scale change that crossed the breakpoint or the narrow cutoff re-created both buttons; focus fell to the route, the scope's focus-within state never cleared, the countdown stayed paused, and Escape no longer reached the toast. The narrow fallback now subtracts the container margin from the parent constraints before applying the style constraints, so a toast whose box is below the cutoff stacks even when its parent is wide enough. Reword the placement docs to what holds: the close button stays on the first line after the message, `inline` keeps the action in the row only while the message has room, and a toast with an inline action measures its width and so does not support intrinsic sizing. The docs example uses Breakpoint.maxWidth(768).
Author the HeroUI v3.2.6 look as a first-party preset that styles existing Remix components instead of reimplementing them. Each component is a top-level `@MixWidget(target: Remix<X>.new)` recipe returning a styler, or a thin composition where Remix has no widget (Kbd, Surface, Typography, groups, Breadcrumbs). Behavior, focus, keyboard, and semantics stay in Remix and Naked UI; the preset supplies tokens, variants, and sizes only. - theme: typed light/dark palette, tokens, effects, and a Vanilla-shaped `HeroUiTheme` - 42 components covering the display, input, selection, overlay, and navigation families, plus tooltip, modal, accordion, disclosure, slider, toast, alert, progress, and table - one worksheet per component under specs/heroui, derived from the pinned upstream CSS and measurements - ADR 0001/0002 recording the reference-over-Remix doctrine, the approximations where stylers cannot express an upstream visual, and the general Remix API candidates proposed as separate changes - recipe tests that assert measured upstream geometry and tokens in both color modes
Pin the upstream evidence the HeroUI recipes are measured against, and the checker that keeps it honest. - reference/heroui_3_2_6: tarball URLs and integrity, the compiled CSS and theme variables for the mapped families, browser-resolved tokens in both modes, rendered measurements (375 and 1024 px, hover/pressed/focus), the family map, and the upstream Apache-2.0 text - tool/heroui_parity: `check.dart` verifies the source hashes, the token inventory, and the item mapping (`melos run heroui:parity:check`); `slim_measurements.dart` derives measurements.json from raw captures; the `chromium/` harness regenerates them - raw Chromium captures stay out of the repository (.gitignore); only the derived measurements and tokens are committed - `crypto` becomes a managed dev dependency of registry_source for the SHA-256 pins, and the workspace constraint is shared through melos - melos scripts `heroui:parity:check` and `ci:open-code:heroui`, wired into `ci`, `ci:checks`, and the open-code check aggregates
Derive the HeroUI preset into `registry/heroui` and wire it through the build, the consumer check, and CI like Vanilla and Fortal. - build_registry: a `heroui` preset spec (`HeroUi` type word, `heroUi` value word) deriving the theme plus 42 component items into 51 templates - registry/index.yaml lists `heroui`, so the registry workflow promotes it to registry-stable once the hosted smoke check passes; registry/icons.yaml gains `chevronRight` for the select and dropdown indicators - check_open_code: `--preset heroui` with its own item list, dependency set, and import allow-list; the contract now carries per-preset foundation, independent, spec-styler, and dev-dependency sets, and rejects an `--item` the preset does not install. HeroUI installs need no consumer build.yaml and no consumer codegen - open_code/heroui_fixture: the installed-app gallery, behavior tests, and the capture script used for side-by-side review - CI: `heroui` joins the open-code matrix and the hosted smoke in the registry workflow; the fixture is excluded from root analysis - validate_docs: Dart snippets that name `HeroUi*`/`heroUi*` compile against `package:registry_source/heroui.dart` - tests: build_registry and check_open_code cover the third preset
Teach consumers and preset authors what the HeroUI preset is and the doctrine it follows: a preset adds design decisions to Remix, never behavior. - skills/using-remix: a HeroUI reference (installed API, variants, sizes, approximations, motion) beside Vanilla and Fortal, plus evals - skills/building-remix-design-system: the reference-parity guide (appearance-only fidelity from pinned CSS, resolved tokens, and measured geometry, with the extension ladder style, composition, general Remix API by ADR, defer), and the doctrine folded into the component, token, and registry references - open_code: ADDING_PRESETS.md for authoring a first-party preset beside Fortal, and the PRESETS, README, and RELEASING updates - docs/open-code.mdx, README.md, AGENTS.md, and the remix_cli README list the third preset and its catalog
Bump next to 16.4.1 and zod to 4.6.6 in apps/docs. The lockfile now resolves sharp 0.35.5 (next's optional dependency) and source-map-js 1.2.2 (postcss, @tailwindcss/node), and no longer contains the vulnerable versions.
The preset's remix floor follows the Vanilla catalog (^1.0.0-beta.13), and the derived theme scope template picks up the non-subscribing MediaQuery lookup already present in the authoring source.
Move flutter_test_config.dart into test/ so `flutter test --platform chrome` can compile the suite; at the package root the web compiler cannot resolve it. Fix the two web-only failures: - integration_tests_test.dart launches a child process through dart:io, so mark it @teston('vm'). - Split the radio space and enter test. On the web Enter maps to ButtonActivateIntent, which RawRadio does not handle, so the enter half is skipped there.
Fortal's shadow tokens are lists, and a plain List equals only itself. MixScope compares the old and new token maps value by value to decide whether to notify its dependents, so every FortalTheme rebuild, even with identical settings, notified every widget that reads a Fortal token. Vanilla's tokens are all value-equal and were never affected. buildFortalScopeTokens now returns its shadow lists as unmodifiable lists that compare by value, so an equal theme builds an equal token map. The registry template and the demo and dashboard installed copies are regenerated.
…des above it Every theme scope installs a complete token set instead of merging with the scope above it, in Vanilla and in Fortal alike, so a MixScope.inherit override made above a nested theme does not apply below it. Say so next to the description of nested scopes, and where to place the override instead.
RemixSelect and RemixMenu now wrap their overlay content in an internal RemixAnchorScope that carries the trigger size. The new remixAnchorWidth context token resolves to that width inside those overlays and to 0 elsewhere, so a style can size a panel from its trigger with SelectContentStyler().minWidth(remixAnchorWidth()). Panels stay content-sized unless a style opts in.
showNakedDialog, showNakedAlertDialog, and NakedTooltip now show and hide at once when MediaQuery.disableAnimations is set or the platform reports AccessibilityFeatures.reduceMotion, which iOS Reduce Motion sets without setting disableAnimations. A dialog reads the setting when it opens and when it is dismissed; a tooltip follows changes while mounted.
The open-code jobs are the pipeline's long pole, and the longest stretch in them is the independent-item loop: eighteen throwaway apps, each doing its own `pub get`, `remix init`, and `remix add` with a generator run, one after another. The items share nothing, so they now go through a pool of at most four workers (one per core below that, never fewer than two). `--jobs N` or `REMIX_OPEN_CODE_JOBS=N` overrides the default; `--jobs` is rejected with `--item`, which never reaches the pool. Each item buffers its output and the logs are written in input order, so the log does not depend on which install finished first. After a failure no more items start, the running ones are awaited, and the run still fails with the earliest failing item's message and every started item's log. `_runProcess` takes an optional log, keeps the registry-harness redirect, and drains both pipes while waiting for the exit code. The pub cache key now also hashes `registry/*/registry.yaml`: the throwaway apps resolve the dependencies those manifests declare, so a registry-only change used to restore a cache that lacked them.
Add RemixReducedMotion, which reports whether MediaQuery.disableAnimations or the platform's accessibility reduceMotion is set and rebuilds when either changes. iOS Reduce Motion only sets reduceMotion, so reading MediaQuery.disableAnimations alone missed it. The select menu and accordion panel now open and close without a transition, and RemixSpinner holds a static frame. Skeleton and sidebar, which already followed MediaQuery.disableAnimations, now follow iOS Reduce Motion too. Style animations declared with .animate(...) are driven by Mix and are not affected.
…uery changes RemixSidebar read MediaQuery.maybeOf(context)?.disableAnimations, which subscribes the sidebar to every MediaQuery field, so it rebuilt on each keyboard inset frame. Read only disableAnimations through MediaQuery.maybeDisableAnimationsOf. The Agent loading glyph read the same field the same way. Its preset source, registry templates, and installed dashboard and playground copies take the same one-line change. The Vanilla and Fortal SidebarLayout reads stay: MediaQuery.removePadding in the same widget subscribes it to the whole MediaQuery regardless.
Checked against the pinned @radix-ui/themes 3.3.0 CSS, five Fortal focus treatments differed from Radix. - The soft radio rings in accent-a8 (base-radio.css:165-168); the other variants keep focus-8. - The soft select rings in accent-8 (select.css:430-433). Fortal resolves both tokens to the same step, so the rendering is unchanged unless the focus color is overridden. - The slider thumb's own 1px edge stays above the focus halo (slider.css:88-93). The halo moves from the foreground layer to behind the thumb, which is where the thumb's decoration shadows sit above it. - The dashboard accent swatch rings with Radix's ThemePanel outline (theme-panel.css:36-47): 2px off the edge, gray-12 once selected, and the panel's accent-9 while focused, so focus stays visible on the selected swatch. The ring is a foreground border and takes no layout space; the panel gains 4px of horizontal padding so the scroll views that host it do not clip the first column. - A focused sortable data table header draws a focus-8 ring inside its cell, as a foreground border like the row divider, through the existing headerCell slot. The errored text field keeps its current focus treatment, which needs a design decision. Registry templates and the demo and dashboard installed copies are regenerated.
Add NakedMenu.openOnTap (default true). When false the menu draws no NakedButton and adds no tap, keyboard activation, or focus stop: the child or builder is the caller's own control, and opening and closing go through the controller. triggerFocusNode is passed only to RawMenuAnchor .childFocusNode, and Semantics(expanded:) still merges onto the control's node. Using it inside a NakedMenubar asserts.
Mirror RemixCheckbox.label on RemixRadio and RemixSwitch. The label is drawn inside the control's pointer, hover, press, and focus target, excluded from semantics, and used as the accessible name unless semanticLabel overrides it. RadioSpec and SwitchSpec gain label and labelSpacing (default 8) with the generated label styler helpers, and both widgets gain minimumTapTargetSize, which defaults to Size.zero so existing layouts are unchanged. semanticLabel becomes optional: one of label and semanticLabel is still required, now enforced by an assertion. The label layout moves into one unexported RemixLabeledControl shared by checkbox, radio, and switch. A radio or switch with no label and no minimum target keeps its widget tree.
Add containerEffects to the Tab, Toggle, ToggleGroup item, SelectMenuItem, MenuItem, and MenuTrigger specs, and triggerEffects to Accordion and Disclosure, whose containerEffects already covers the whole panel. Each widget renders through RemixBoxAdapter or RemixFlexBoxAdapter, so an outline paints outside the box without taking layout space.
…mer check Each item a preset installs alone starts a fresh `remix init`, and an init that resolves the registry from a branch makes one unauthenticated GitHub API request (60 an hour). Marking all 42 HeroUI items independent raised the hosted release check from 38 to 81 requests, so the next remix_cli release verification could fail on the rate limit, and added a second pass of 42 throwaway installs to the HeroUI CI job. HeroUI now installs alone only the items whose registry dependencies name something besides the theme (14 of 42), derived from registry.yaml, which includes every item that uses an icon. The combined install of every item is unchanged, and Vanilla and Fortal keep their lists. Also restores the unconditional build_runner step (every preset has generated parts), names the shared source-selection test for every preset, and braces a multi-line `if` in the test helper.
RemixSidebar published `disabled` over its whole panel, so a widget in `header` or `footer` with an `onDisabled` style lit when the sidebar was disabled, contradicting the `enabled` documentation. Header and footer now get an empty scope; the sidebar's own regions still resolve `onDisabled`. Also cover disabled accordion and disclosure content, document that the content of a non-sortable data-table header resolves widget-state variants against an empty state, and reword the changelog entries.
Open
4 of 5 tasks
# Conflicts: # packages/remix/CHANGELOG.md
Scroll notifications from the option list bubbled to the host page, so a Material 3 AppBar elevated while the list scrolled and stayed elevated after the select closed. The notifications now stop at the options, including the scroll metrics notifications that hosts also treat as scrolling. The list also follows Flutter's own menus: no overscroll stretch, no bounce on iOS and macOS, clamping at both ends, and the desktop scrollbar stays. Document the scrolling and the maxHeight that caps it, and note in the changelog that menuContainer's main-axis alignment and maximal main-axis size no longer distribute free vertical space.
…lays Home and End in an anchored overlay (NakedSelect, NakedMenu) requested focus directly on the first or last item, which skipped the traversal policy's requestFocusCallback. When the overlay content scrolled, End focused the last item without revealing it, so End then Enter selected an item the user could not see. Request focus through the policy, as the arrow keys already do.
4 of 5 tasks
# Conflicts: # docs/components/select.mdx # packages/remix/CHANGELOG.md
… accordion triggers RemixSidebar drew destinations with Mix RowBox, so the containerEffects slot that SidebarDestinationStyler (a ToggleStyler) gained never painted: a preset that moves its toggle focus ring there lost the sidebar destination ring. Both destination row layouts (settled and collapsing or expanding) now render through RemixFlexBoxAdapter. RemixAccordion painted triggerEffects only on its default trigger, while RemixDisclosure also wraps a custom triggerBuilder widget. A custom RemixAccordion.builder widget is now wrapped in triggerEffects too, without applying the trigger box, so a preset ring shows around a custom trigger.
# Conflicts: # packages/remix/CHANGELOG.md # packages/remix/lib/src/components/accordion/accordion_widget.dart # packages/remix/lib/src/components/sidebar/sidebar.dart
…e groups, and menubars NakedTabBar Home and End, NakedToggleGroup arrow keys, Home, and End, and NakedMenubar Home and End requested focus directly on the target, which skipped the traversal policy's requestFocusCallback and left a target inside a scrollable off screen. Request focus through the policy's callback, as the arrow keys in tabs and the overlay fix already do.
4 of 5 tasks
…ic sizing The shared label layout chose between a flexible and a plain label inside a LayoutBuilder, which cannot report intrinsic dimensions. A labeled control asserted in debug and collapsed in release under IntrinsicWidth, IntrinsicHeight, and Material AlertDialog content. A Row already shrink-wraps a loose Flexible child when its width is unbounded, so the layout is now a plain Row with a Flexible label. The label still wraps in bounded width and takes its natural width otherwise. Drop the intrinsic-sizing warning from the dartdoc, docs, and skill, and add a Fixes entry for the released RemixCheckbox label.
4 of 5 tasks
A RemixMenu panel, and every submenu panel, rendered its items in a column with no scroll view, so a list taller than the overlay or than a maxHeight on the overlay style overflowed. RemixFlexBoxAdapter gains a scrollable option that puts a contained vertical scroll view between the box and its column. The box's padding, decoration, constraints, and effects frame the scroll view. The scroll view does not attach to the host's primary scroll controller, does not notify the host page, and clamps at the ends. RemixMenu and RemixSelect both use it, which replaces the select's private scroll structure.
Focusing through the traversal policy's default callback aligns the target to the trailing edge of every ancestor scrollable, even when it is already visible, which scrolled the page and shifted fully visible strip items. requestFocusInView runs the policy callback with keepVisibleAtEnd and then keepVisibleAtStart so a target is revealed from either side and left alone when visible. The anchored overlay, tabs, toggle group, and menubar use it, and MenubarTriggers.move now reveals the next trigger as well.
…n stack # Conflicts: # packages/naked_ui/CHANGELOG.md
4 of 5 tasks
# Conflicts: # docs/components/menu.mdx
4 of 5 tasks
This branch has not been deployed
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.
Do not merge. This branch checks that the open follow-up PRs work together. Each one still merges through its own PR.
It merges every PR head below into
main(a3a8ad5) in the recommended merge order, then runs CI on the combined tree. When a component PR changes, this branch is rebuilt.Merge order
naked_uifloor to^1.3.0.Conflicts resolved here
Each conflict will come up again when the later PR is rebased onto
main.tool/check_open_code.dart: keep ci: check independent open-code items in parallel #329's pooledcheckItem, and take the item list from feat(registry): add the HeroUI preset #324 (preset.independentInstalls()).sidebar_widget.dart: keep fix(remix): honor reduced motion in animated components #327 (remixReducedMotionOfand the accessibility observer). It readsMediaQuery.maybeDisableAnimationsOf, so fix(remix,registry): stop RemixSidebar rebuilding on unrelated MediaQuery changes #323's rebuild test still holds. Both PRs' tests are kept.docs/components/menu.mdxandmenu_widget_test.darttake both sides. The test importsRemixBoxAdapterandRemixFlexBoxAdapter.accordion_widget.dart: keep fix(remix): keep outside widget state out of table headers, panel content, and sidebar slots #340's empty scope around the panel content and feat(remix): add containerEffects to focusable parts #331'striggerEffectswrapper for a custombuilder.sidebar.dart: keep both imports.docs/components/select.mdx: keep both sections. A follow-up commit reconciles feat(remix): add containerEffects to focusable parts #331's ring note with fix(remix): scroll a long RemixSelect list #341: a scrolling list crops an outside row ring.docs/components/menu.mdx: keep all sections. A follow-up commit says a scrolling menu list crops an outside row ring, in the menu docs and the design-system skill.## UnreleasedCHANGELOG conflict keeps all bullets. The last commit rebuilds each package's Unreleased section from the PRs' own sections, grouped as Breaking changes, Features, and Fixes.Cross-PR issues found here
These merge without a textual conflict but fail together:
RadioStyler/SwitchStylersemanticLabelnullable, so HeroUI's generatedradio.g.dartandswitch.g.dartgo stale. This branch regenerates them. feat(remix)!: add label to RemixRadio and RemixSwitch #332 must regenerate after rebasing on amainthat has feat(registry): add the HeroUI preset #324 (noted in feat(remix)!: add label to RemixRadio and RemixSwitch #332).RemixBoxAdapter, so feat(remix): expose the anchor width to select and menu overlays #326'spanelRecttest finder measured a row instead of the panel. Fixed in feat(remix): expose the anchor width to select and menu overlays #326, which now measures the outermost box.Validation
Local, on 58c004b (Flutter 3.44.0):
melos bootstrap, thenopen-code:registry:build: no registry drift.melos run ci:checks,ci:codegen,ci:cli,ci:flutter: pass. Test counts: remix 3038, registry_source 2282, naked_ui 1040, dashboard 84, demo 64, naked_ui_example 56, widgetbook_vanilla 38, playground 11, remix_ui_fonts 9, remix_ui_icons 4.GitHub CI on this PR runs the open-code consumer legs (Vanilla, Fortal, HeroUI).