Skip to content

feat(remix_cli): add the open-code component workflow - #177

Open
leoafarias wants to merge 45 commits into
mainfrom
feat/open-code
Open

feat(remix_cli): add the open-code component workflow#177
leoafarias wants to merge 45 commits into
mainfrom
feat/open-code

Conversation

@leoafarias

@leoafarias leoafarias commented Aug 30, 2026

Copy link
Copy Markdown
Member

Description

Add the project-local remix_cli for installing editable Remix theme and component source.
The application owns its recipes and generated adapters. Remix supplies component behavior.

The installer supports initialization, dependency installation, prefix rendering, previews, diffs, and explicit item overwrite.
Normal installation preserves application edits. The catalog includes core recipes, icons, and charts.
The playground uses the installed source, and fresh applications verify hosted and checkout Remix.
The registry requires published Remix beta.8 and includes the release changes from #182.

This is the base of the PR stack: #177, then Fortal #180, then Sidebar #178.
The Fortal and Sidebar changes are separate from this PR.
CI now runs for child PRs. Documentation identifies the pending first CLI publication.

The adversarial review fixed seven CLI defects: Windows SDK paths, setup output, dependency constraints, reserved prefixes, workspace discovery, duplicate help, and generation filters.
The installer now uses Pub package configuration and literal generation filters.
A real SDK test verifies installation and application edit preservation on Windows.

Related Issues

Related to #180 and #178.


Checklist

  • My PR includes unit or integration tests for all changed behaviors.
  • I have updated the relevant documentation.
  • I am prepared to follow up on review comments.

Validation

  • 76 CLI tests passed after the update to current main.
  • Version alignment passed for Remix, Fortal, and the default registry.
  • Fresh default consumers passed 371 tests with hosted Remix beta.8 and 371 tests with checkout Remix.
  • GitHub CI passed at a56caca3b, including the real Windows SDK test.

Breaking Change

  • Yes.
  • No. Existing package consumers keep their current APIs.

The first CLI publication still requires package bootstrap. Complete the following stack before preparing the combined release.

Distribute editable Theme and Button source through a first-party CLI modeled
on shadcn. `remix init` writes remix.yaml and a managed UI barrel; `remix add
<item>` renders bundled registry templates into the consumer's lib/, resolves
dependencies, runs Mix generation, and analyzes the result. Remix keeps
behavioral ownership; the consumer owns the authored and generated files.

Includes the clean-sheet review remediation:

- lib/remix_cli.dart is a marker library with no exports. The supported
  surface is the `remix` executable, so lib/src/ can be reshaped without a
  breaking change.
- Runtime requirements (remix, mix_annotations) are satisfied only by
  `dependencies`. A dev_dependencies-only declaration, or the same package in
  both sections, fails preflight before any process runs or file is written,
  and names the section to move it to instead of rewriting pubspec.yaml.
- init and add report the filesystem transition that actually happened:
  single-file init repairs are named individually, and `--overwrite` on a
  missing item reports "Added" rather than "Updated".
- Installed Button source documents its own hit-target tradeoff instead of
  referencing repository-only CLEAN_SHEET.md.

remix_cli is versioned independently: it is excluded from the lockstep
Remix/Fortal release scope in version.yml and absent from publish.yml pending
a separate release gate.

Verified with `melos run ci`: analyzer, 57 remix_cli tests, and the open-code
checker's hosted and current-checkout phases (89 behavior tests each) against
a freshly assembled consumer app.
Walk through init, dry-run/diff preview, add, the three levels of
customization, the overwrite contract, and the dependency-section rules.
Registered under Introduction in the sidebar.
The tool's own usage output calls itself `remix`, but every example showed
`dart run remix_cli:remix`. Add a "Running the CLI" section that states the
binary name and both invocation forms once, then use `remix <command>`
throughout.

Add the two sections the guide was missing:

- Configuration: the full remix.yaml schema-1 surface, validation rules for
  prefix and paths.ui, and the fact that neither can be changed after install
  (state is tracked by file path, so editing the prefix by hand leaves
  installed source untouched while new source renders differently).
- Updating: the pub upgrade -> --diff -> --overwrite review loop, a
  state/flag matrix for reinstall behavior, and manual removal.

Every command, output string, and error message in the guide is transcribed
from a real run against a scratch Flutter package.
…lp output

The Running-the-CLI block showed bare 'remix <command>' as the
project-local form, which only works after global activation. Show the
real 'dart run remix_cli:remix' invocation, fix the note's example to
match, and include the trailing line of the actual --help output the
block claims to reproduce. All three verified against the real CLI.
Registry expansion Phase 1, the architecture test: one simple form control and
one compound component, chosen to prove the item schema before batching the
rest of the catalog.

Both fit `registryDependencies / dependencies / devDependencies / files /
generated / exports` unchanged, with no edit to registry.dart or installer.dart.
`checkbox` ships two adapters (Checkbox, CheckboxGroupItem) and `tabs` three
(TabBar, Tab, TabView), each from one authored file and one generated part, so
the one-file/one-part shape already covers compound components.

One discovery the phase was designed to surface: `RemixTab.builder` is typed
`ValueWidgetBuilder<NakedTabState>`, and `NakedTabState` is not among the four
symbols `remix` re-exports from `naked_ui`. Forwarding it would drag
`package:naked_ui` into the consumer's generated file and break the installed
import boundary. Curated out with `@MixWidget(widgetParameters: .only({...}))`,
already available in the pinned mix_annotations, so no schema field was needed.

Recipe notes:

- Checkbox clamps `Tokens.radius` to 4px. That token is authored for 32-40px
  controls; applied unclamped to a 16px box a pill radius draws a circle, which
  reads as a radio button. Clamping keeps a theme asking for less rounding in
  charge.
- Checkbox nests its hover fragment inside the checked fragment, because a
  top-level hover fragment cannot know which fill it is dimming. Tabs does the
  opposite and deliberately sets no fill when selected, so the hover fill and
  the selected edge coexist.
- Tabs rings focus with a foreground decoration: `TabSpec` has no
  containerEffects layer, and a real border would inset the label.

The checker now installs all three items, and keeps byte-exact snapshots for
two representative generated shapes: button (single adapter) and tabs (multi
adapter). Per-component behavior tests are the per-component guard.

Verified with the full open-code check: 58 remix_cli tests, 10 checker tests,
146 fixture behavior tests against hosted and current-checkout Remix, and the
playground dogfood, where a customized checkbox.dart survived `add` and showed
up in `add --diff`.
Registry expansion Phase 2: avatar, badge, callout, card, divider, icon_button,
link, progress, skeleton, spinner, and toggle. Fourteen components now install
as application-owned source, all on the item schema Phase 1 proved and the same
fifteen theme tokens the MVP shipped.

The planned `surface`/`surfaceForeground` token pair was **not** added. Card was
the test for it and did not confirm the need: `background` plus a `border`
hairline reads correctly in both shipped themes, which is what shadcn's own
defaults do — its `--card` and `--popover` resolve to `--background` unless a
theme chooses otherwise. Two tokens whose values equal two existing tokens would
be ceremony, and every existing install would have to port them. Revisit at the
overlay tier, where a floating surface can demonstrate a concrete failure.

Two bugs the new tests caught, both worth stating:

- The callout's destructive tone first set its sentence in `destructive`, which
  measures 4.1:1 on the dark theme's page — under the 4.5:1 WCAG floor for body
  copy. `destructive` is a fill color chosen to sit under
  `destructiveForeground`, not a text color. The tone now shows in the border
  and the glyph, where the floor is 3:1, and the sentence stays `foreground`.
- Resolving a recipe is not the same as laying it out. The fixture now pumps the
  whole gallery at two viewport sizes, which caught a tab strip overflowing its
  container and a progress bar that shrink-wrapped to zero width. Neither was
  visible to a recipe-level assertion.

Recipe notes:

- Progress spans its parent and clips to its own radius, because progress is
  measured against the width of its container.
- Divider takes a plain `Axis` rather than a local enum, and stretches along it
  with a FractionallySizedBox rather than offering length presets.
- Link pins no font size: it is inline text and must take the scale of the
  paragraph around it. Its color is `foreground` with an underline, because
  this theme's `primary` is a neutral fill rather than a link hue.
- Avatar clips rather than shapes, because Remix renders `backgroundImage` as a
  child of the container and not as part of its decoration.
- `RemixTabBar` will not accept a scroll view as its child: Flutter's tab-bar
  semantics role requires every direct semantics child to be a tab. The strip
  now spans its container, and the recipe documents where the scroll view goes.

The checker installs every item and derives its expected inventory from one
item list, so a new item cannot be added without the boundary check covering it.
The formatter-drift test now runs over every template at the long `Playground`
prefix, not just Button.

Verified with the full open-code check: 58 remix_cli tests, 10 checker tests,
217 fixture behavior tests against hosted and current-checkout Remix, both
generated snapshots byte-exact, and the playground dogfood, where a customized
card.dart survived `add` and showed up in `add --diff`.
…st gaps

Applies the findings from a simplification pass and an adversarial review of
the two preceding commits.

The one real defect: avatar initials were set in `mutedForeground` on a `muted`
surface, which measures 4.35:1 in the shipped light theme — under the 4.5:1
WCAG floor for text that size. It is the same defect that was caught and fixed
in the callout one commit earlier, and avatar escaped only because nothing
asserted its contrast. The fallback now takes `foreground`: initials name a
person, so they are content, not a dim marker.

So the fixture now measures the pairings instead of trusting the token names.
A new group resolves each recipe's real spec across both themes and every
state, composites the layers the way a reader sees them, and holds text to
4.5:1 and glyphs to 3:1. Reverting the avatar to `mutedForeground` fails it at
exactly 4.35 with `reason: initials`, which is the check the previous commit
was missing.

Three assertions that read as proof and were not:

- `a link with no callback is disabled by Remix` incremented a counter that the
  widget under test had no way to increment, so it restated its own setup. It
  now asserts the resolved disabled fragment is present on a callback-less link
  and absent on an enabled one, which is the recipe's actual claim.
- The toggle's `focus-visible rings without moving the label` compared a
  padding that the focus fragment never touches. It now asserts the ring does
  not arrive as a box border, which is the thing that would move the label.
- `every component item resolves theme first` checked only that each pinned
  name exists in the catalog. It now checks both directions.

And the coverage gap behind them: three hand-maintained item lists, none of
which asserted completeness against `registry.yaml`. An item added to the
registry but forgotten in `tool/check_open_code.dart` was never installed,
never rendered into the fixture, never behavior-tested, and never boundary
checked — with every suite green. The checker now fails on that drift in
either direction, before it creates the temporary app.

De-duplication from the simplification pass: `_resolveStyle` and
`_resolvedSpec` now delegate to the generic `_resolve` and `_resolvedSpecOf`
they were copied from, and the single-use `_resolveCheckbox` wrapper is gone.

One finding is deliberately *not* fixed here: `border` measures 1.26:1 against
`background` in light and 1.91:1 in dark, which is below the 3:1 non-text floor
for an unchecked checkbox, whose hairline is the only thing identifying it.
That is inherited from the MVP theme, shadcn ships the same tradeoff, and every
option is a theme decision with consequences for preserved installs. The
options are written up in the expansion plan rather than settled by letting one
component diverge.

Verified with the full open-code check: 58 remix_cli tests, 12 checker tests,
229 fixture behavior tests against hosted and current-checkout Remix.
…ift seams

Registry expansion Phase 3 — radio, segmented_control, slider, switch,
textfield, and toggle_group — plus the drift work that reviewing the catalog
turned up. Twenty items now install as application-owned source.

## The `name:` argument was ceremony

Every template passed `@MixWidget(name: '{{typePrefix}}Divider', ...)`,
restating a value the generator already derives. `valuePrefix` lowercases only
the first character (project_config.dart:64), and the generator drops a
trailing `Style` and capitalises the first character — exact inverses, so
`{{valuePrefix}}XStyle` always yields `{{typePrefix}}X`. Regenerating without
it is byte-identical, and Fortal never passed it. Removed from all fourteen:
two names that had to agree, with nothing checking that they did.

That removal exposed a soft assertion. `both prefixes render every configured
public surface` searched the template text for `AcmeCheckboxGroupItem`, which
had been satisfied by the annotation; with the annotation gone it was satisfied
only by doc-comment prose elsewhere. It now pins the recipe *declaration* with
an anchored pattern, which is the same fact as the widget name and the thing a
comment cannot fake.

## The playground could rot silently

`apps/playground` holds a committed, CLI-installed copy of every item. Nothing
renders it, so a stale file compiles; and the ownership contract says a
consumer may edit installed source, so a forgotten resync and a deliberate
customization looked identical in review. Four items had diverged and it took
running fifteen diffs by hand to learn that all four were intentional.

`tool/check_open_code_dogfood.dart` now drives the CLI's own `add --diff` over
every item: each is either clean or named in a list with its reason, and a
named item that turns out to be clean fails too, so the list cannot outlive the
edit it describes. The list is down to one entry — the theme's indigo primary.
Repeating the same lesson in button, card, and checkbox cost three files that
could no longer be resynced and taught nothing the first one did not.

`generate:check` now covers the playground as well, so a `.g.dart` left behind
by a recipe edit fails instead of shipping green. The first thing it caught was
that the templates are *not* byte-identical to their installed form: the CLI
formats after rendering, and how the formatter wraps depends on how long the
prefix is. The dogfood check asks the CLI rather than re-rendering for that
reason.

The generated snapshots are prose-sensitive, because the generator copies the
recipe's doc comment into the adapter. That is now stated where the snapshots
are declared, so a comments-only diff is refreshed rather than investigated.

## Phase 3 findings

- `toggle_group` and `segmented_control` are a third adapter shape: the group's
  spec carries the option's style as a field, so one `@MixWidget` styles the
  container and every option, and options are data rather than widgets. The
  schema did not change.
- `RemixSlider.semanticFormatterCallback` is the second naked_ui leak after
  `RemixTab.builder`, curated out the same way.
- `textfield` generates two adapters through super-parameters, and needs
  `package:flutter/gestures.dart` and `package:flutter/services.dart` for types
  that appear only in the generated constructor.
- A focused text field asserts on an `Overlay` ancestor. The fixture's
  deliberately minimal `WidgetsApp(builder: ...)` host has none, so the gallery
  and the field tests supply one and the recipe documents the requirement.

Two more contrast failures, both caught by the group added last commit:

- The segmented control's unchosen label was `mutedForeground` on the `muted`
  track: 4.35:1 in light. Every segment's label is now `foreground`, and the
  chosen one is marked by its raised surface and a heavier weight — a cue that
  survives where a colour difference would not.
- The text field's error helper was `destructive`: 4.10:1 on the dark page. The
  outline carries the tone, where the floor is 3:1, and the message is
  `foreground` at w500.

That is the third component to want a danger colour it could set text in, which
is the evidence D2 asked for before touching the token vocabulary. The options
are written up in the expansion plan; nothing was added here.

Verified with the full open-code check: 58 remix_cli tests, 12 checker tests,
276 fixture behavior tests against hosted and current-checkout Remix, both
generated snapshots byte-exact, the playground mirroring the registry, and
clean generation reproducing all 20 committed playground adapters.
Registry expansion Phase 4 — accordion, dialog, menu, popover, select, and
tooltip. Twenty-six of the twenty-seven planned components now install as
application-owned source; `disclosure` still postdates hosted 1.0.0-beta.6 and
waits for the constraint bump, as the plan called for.

The nested-spec shape the groups introduced in Phase 3 scales all the way:
`MenuSpec` carries `item`, `checkboxItem`, `radioItem`, `submenuItem`, and
`divider` as fields, and `SelectSpec` carries `trigger`, `content`, and `item`.
A recipe that sets only `item` styles every row kind, which is what keeps a
menu looking like one list rather than four. Still one `@MixWidget` per file,
still no schema change — the item shape has now survived four phases and three
distinct component architectures without gaining a field.

The D2 token question was re-asked here, where the plan expected it to be
answered, and the answer is still no. A floating panel is separated from the
page by its outline and its shadow, and in dark mode the shadow does almost
nothing — so the outline carries it alone at 1.91:1. That is a real problem,
but a `surface` token does not fix it; a `border` that clears 3:1 does, which
is already recorded as an open decision. `muted` as an overlay fill is worse
than `background` on both pages.

Findings:

- `RemixAccordion.builder` is the third naked_ui leak after `RemixTab.builder`
  and `RemixSlider.semanticFormatterCallback`, curated out the same way.
  `RemixAccordionGroup.controller` looked like a fourth, but Remix already
  re-exports it as `RemixAccordionController` — the right fix on their side.
- `AccordionStyler` forwards its box shorthand to `trigger`, not `container`,
  so the first draft's `.border(...)` outlined the clickable row instead of the
  section. Remix documents this in the spec; the recipe now reaches `container`
  by name and says why. The fixture caught it.
- The tooltip is the one floating surface that is not `background`: a transient
  label is not a panel a reader can act in, and inverting it is what makes that
  difference legible without a second token.

Verified with the full open-code check: 58 remix_cli tests, 12 checker tests,
301 fixture behavior tests against hosted and current-checkout Remix, both
generated snapshots byte-exact, the playground mirroring the registry with its
one declared customization, and clean generation reproducing all 26 committed
playground adapters.
Four audits over the finished 26-component expansion: recipe correctness
against the Remix API, test rigour by mutation, design-system consistency and
accessibility, and the tooling and documentation. Everything below was
independently verified before it was acted on; two reported findings did not
survive checking and are called out at the end.

## Four accessibility failures, all recomputed from the token values

- The switch had no outline on either box. `muted` on `background` is 1.09:1 in
  the light theme, so an off switch was a pale shape on a pale page holding an
  invisible thumb. The slider had already solved the identical problem for its
  thumb and the switch never got the same fix. Both boxes now carry a hairline
  in every state, so flipping it does not resize the control.
- The segmented control's chosen segment was a 1.09:1 fill plus a font weight —
  and the weight was the cue I added in the previous commit while fixing a
  different contrast bug. It now carries an outline as well.
- The select trigger tinted its box on hover and left the placeholder at
  `mutedForeground` on `accent`: 3.76:1, in both themes, and only while the
  pointer is on it. The content moves with the surface now.
- The toggle told hover from on with `muted` versus `accent`, which are 1.155:1
  apart — a state distinguished by colour alone, and by a colour difference
  most people cannot see. The on state now carries a `primary` outline. Because
  Flutter insets a container's content by its border widths, every state
  declares that outline and only its colour changes; `ghost` paints its copy in
  nothing.

## Two comments that were wrong about Remix

The tooltip's `showDuration` and `dismissDuration` comments described each
other's mechanism. Remix maps the first onto Naked UI's `touchDelay` — a
long-press hold, nothing to do with hovering — and the second onto
`dismissDelay`, which is the hover-exit grace the first comment claimed. The
slider's doc promised a hover fragment the recipe did not have; it has one now.

## Test gaps, each proven by the mutation that used to pass

- "`[style]` is merged last" is every recipe's headline promise and was
  verified for 6 of 26 components. Reversing badge's merge order passed all 301
  tests. There are now 29 probes, one per published recipe, and that same
  mutation fails.
- The accordion had no focus, disabled, precedence, or forwarding test —
  deleting two whole state fragments and breaking `focusNode` forwarding
  passed. It now has all four.
- `toggle_group` and `segmented_control` never exercised `small` or `large`;
  setting those metrics to 999.0 passed. Both now have size tables.
- Two counters asserted zero in tests that never triggered them.
- "focus-visible rings without moving the label" still measured no position
  after being hardened once. It now measures the label's rect *and* the
  control's size — the label alone still missed a symmetric padding change,
  because a centred label does not move when the box grows on both sides.

## Deliberate absences, now documented rather than "fixed"

The text field has no hover fragment: its affordance is the I-beam cursor Remix
already sets, and a tint would compete with the focus ring a moment later. The
accordion and segmented control carry no focus-ring offset, because their
neighbours are flush and an outward ring would cross into them. Eight size
enums that name a sub-44px control now disclose the hit-target tradeoff that
button and icon_button already did.

## Tooling

`CLEAN_SHEET.md` still said the catalog was fourteen components. Nothing
checked it: `validate_docs.dart` covered three READMEs and none of the
open-code documents. It now covers all three, and cross-references every
registry item against every catalog document — removing one table row from
docs/open-code.mdx now fails the check by name.

## Reported but not real

The recipe audit's sweep for foreign types in generated adapters, mis-forwarded
styler properties, and unwinnable state fragments came back clean, with
`SelectSpec`'s non-obvious forward target checked explicitly. The tooling audit
reported that no workflow runs `melos run ci`; `.github/workflows/ci.yaml` does
exist and does run on PRs — what it delegates to is a pinned external workflow
this checkout cannot read, which is recorded as an open question rather than a
finding. And an estimate that the checker takes 15-20 minutes was out by 5x:
measured, a full run with all 26 items takes 206 seconds, well inside the
plan's budget.

Verified with the full open-code check: 58 remix_cli tests, 12 checker tests,
341 fixture behavior tests against hosted and current-checkout Remix, both
snapshots byte-exact, and the playground mirroring the registry.
Registry expansion Phase 5 — data_list and data_table — plus the design
consistency work the specialist review left open. Twenty-eight of the
twenty-nine catalog entries now ship; `disclosure` still postdates hosted
1.0.0-beta.6 and waits for the constraint bump, which was the plan's own stated
blocker from the start.

Phase 5 was written as demand-driven, and that gate is dropped deliberately:
leaving two of twenty-eight out would have left the plan's Goal — every Remix
component that exists in a hosted release — unmet on a guess about demand,
after the other twenty-six proved the shape works.

## data_table is the one real exception to "templates are self-contained"

`DataTableSpec` takes a `Style<CheckboxSpec>`, a `Style<IconButtonSpec>`, and a
`Style<SelectSpec>`. A table's selection column, pager, and page-size control
literally *are* those components. So `data_table` is the first item with
`registryDependencies` beyond `theme`: it imports three sibling files and
`add data_table` installs four items in dependency order.

The schema already supported this and needed no change — the resolver's
multi-dependency path had a unit test since the MVP but no real item until now.
The alternative, restating three recipes inside a fourth, would produce a table
whose controls drift away from the rest of the application, which is what D3
exists to prevent rather than to cause.

## `mutedForeground` on `muted` is 4.35:1, and this is the third component it bit

Avatar initials, then the segmented control's unchosen label, and now the data
table's column headers. The pairing reads as if it were designed for each other
— the token is literally named for that surface — and it is under the 4.5:1
floor for text in the light theme. Header labels are `foreground` at w500 now,
with the header's own `muted` surface doing the separating.

`mutedForeground`'s own doc comment says so now, so the next person reaching for
it is told before they write the line rather than by a test afterwards.

## The rest of the specialist review's design findings

- The segmented control's type and icon scale were 13/14/14 and 14/16/16 while
  the button, toggle, and tab family used 14/14/16 and 16/16/18. Aligned; only
  the heights still differ, because a segment is inset inside its track and the
  *track* is what lines up with the button beside it.
- The link has no pressed fragment. That is deliberate — a link replaces the
  page and the destination arriving is the feedback — and it now says so,
  because the checkbox's version of that rationale does not transfer.
- `focusRing` and `mutedForeground` resolve to the same value in both shipped
  themes. Deliberate, now stated: a neutral ring reads as the platform talking
  rather than the brand.

## Process note

The scratch consumer app used for iteration had itself drifted from the
templates — a stale `segmented_control` there is why a stale test table reached
the checker instead of being caught locally. Re-rendering every item before
trusting that app is now part of the loop, which is the same lesson the
playground dogfood check already encodes for the committed copy.

Verified with the full open-code check: 58 remix_cli tests, 12 checker tests,
355 fixture behavior tests against hosted and current-checkout Remix, both
snapshots byte-exact, the playground mirroring the registry with its one
declared customization, and clean generation reproducing all 28 committed
playground adapters. The whole check measures 238 seconds.
Two leftovers from the specialist review, both small.

The text field and the select trigger keep a flat 12px horizontal inset across
their sizes while the button scales 12/16/20 — flagged as an inconsistency, and
it is deliberate. A button's padding gives its label room to breathe, so it
grows with the control; a field's is the gutter before the text cursor, and
widening it at a larger size only moves the caret away from the edge the reader
clicked. Both recipes say so now, and the select points at the field.

The dialog's description is `mutedForeground` on the panel, which clears 4.5:1
at 4.74 in the light theme — the thinnest margin of any pairing in the set, and
the only one the contrast group did not measure. It does now, along with the
title.

Verified with the full open-code check: 357 fixture behavior tests against
hosted and current-checkout Remix, the playground mirroring the registry.
A completeness pass over the finished catalog, checked mechanically rather than
by recollection.

The catalog itself is complete: diffing the registry's item set against
`remix`'s own component directory gives 28 = 28, nothing missing and nothing
extra. `disclosure` is not in hosted 1.0.0-beta.6 at all, so it is not a gap in
the catalog — it is the constraint bump the plan named at the start.

Cross-checking every `on…` fragment in every template against the states the
suite actually drives found one real hole: six components declared a `disabled`
fragment that nothing exercised — menu's trigger and item, radio, the segmented
control, select's trigger and item, textfield, and the toggle group. A disabled
fragment is the easiest thing in a recipe to declare and never look at again,
because it is invisible in the gallery and the states around it are the ones a
person exercises by hand. The new group enumerates rather than samples, and
deleting either menu's or select's item fragment fails exactly its own probe.

Two smaller things the same pass turned up:

- The CLI's changelog still described a two-item registry. It now records the
  catalog, and that `add data_table` installs four items.
- `open_code/README.md` was the one document missing the two host notes the
  others carry: the `Overlay` a focused text field needs, and the four
  behavioral Remix widgets that have no registry item because they carry no
  style.

Dead code was checked too and there is none: the fixture's analysis options set
`unused_element: error` and the checker analyzes the installed source with
them, so an unused constant or helper in any of the 28 templates would already
have failed the build.

Verified with the full open-code check: 58 remix_cli tests, 12 checker tests,
365 fixture behavior tests against hosted and current-checkout Remix, both
snapshots byte-exact, and the playground mirroring the registry.
Six hundred lines out, three hundred and thirty in, across 27 templates. The
shape this started from, and what it is now:

    .onFocusVisible(                        .onFocusVisible(
      SelectTriggerStyler().containerEffects(  .containerEffects(
        RemixBoxEffectsMix(                      .outline(
          outline: BorderSideMix(                  .color(
            color: Tokens.focusRing(),               Tokens.focusRing(),
            width: _focusRingWidth,                ).width(_focusRingWidth)
            strokeAlign: .strokeAlignInside,          .strokeAlign(inside),
          ),                                     ).outlineOffset(_offset),
          outlineOffset: _focusRingOffset,     ),
        ),                                   )
      ),
    )

Which forms are available was settled with the analyzer rather than from
memory, because the rule is narrower than it looks: a dot shorthand resolves
only against static members and constructors of the *declared* context type.
Three things therefore cannot be shortened and are left alone —
`BorderSide.strokeAlignInside`, whose parameter is a plain `double`;
`ContextVariant.widgetState`, which lives on a subtype of the `Variant` the
parameter declares; and `wrap`, `thumbColor`, and `trackColor`, which are
instance methods with no static counterpart. That last group is the one worth
remembering: not every fluent method is shorthand-able, so this cannot be
applied by pattern. The analyzer caught all five sites where a sweep had
assumed otherwise.

The border collapse is the largest single win. `BoxBorderMix.color` and
`.width` are defined as `BorderMix.all(...)`, so `.border(.color(x).width(y))`
*is* the all-sides form — proven by resolving both spellings in a real
`BuildContext` and comparing the `Decoration`, not by reading the source. The
seven per-side borders keep their explicit side: the data table's row rules
and footer want one rule, and `.color()` there would draw a box.

`.behindContent(.shadows([...]))` is deliberately not adopted. It needs the
layer statics added in the previous commit, and these templates compile
against hosted `remix ^1.0.0-beta.6`, where the analyzer rejects it. It
arrives with the same release that unblocks `disclosure`.

Templates are taken from the CLI's own formatted output at `Playground`, the
longest prefix the repository dogfoods, so the file a maintainer reads is the
file a consumer gets.

Verified: 58 remix_cli tests including the formatter-drift test that installs
every item and requires `add --diff` to come back clean, 2,727 remix tests,
the full open-code check at 365 fixture tests against hosted and checkout
Remix with both snapshots byte-exact, and the playground still mirroring the
registry with its one declared customization intact.
Driving the installed gallery in a browser turned this up: in the section that
rethemes with `radius: Radius.circular(999)`, the data table's select-all
checkbox is sliced into a wedge and the last row's checkbox is clipped to a
sliver. The control is not just ugly, it is gone.

The table is the only recipe here that both rounds its frame and clips to it.
Only three templates clip at all — the avatar clips an image to a circle and
the progress bar clips a fill to a pill, and neither has anything in the
corner. The table does: the corner arc is carved out of the first and last
rows, and the leading cell of both is the selection column. Past a certain
radius the clip stops trimming the header fill and starts removing a checkbox.

The clip is not optional. The header sits on `muted` and would otherwise
square off the top two corners inside the rounded border. So the frame bounds
its own radius instead, and the bound is geometry rather than taste: an arc of
radius r has finished turning r from the corner, so a radius no larger than
half the header's height is out of the way by the row's own content line.
Rows are taller than the header, so the header binds.

This clamps rather than hardcodes, which keeps the theme in charge in the
other direction — `radius: Radius.zero` still gives a square table, and there
is a test for that. It is the same move the checkbox recipe already makes at a
different scale, for the same reason: a token authored for controls has to be
interpreted by a recipe whose geometry it knows nothing about. With one radius
token in the vocabulary, a consumer who wants pill buttons has no way to ask
for anything else, so the recipe has to be the one that copes.

Deliberately not changed: the menu and select panels, and the card, callout,
dialog, and text area, all become lozenges at that radius and their content
sits outside the arc. None of them clips, so nothing is lost — that is a
theme asking for a shape and getting it, and bounding it would be this layer
overriding a choice rather than surviving one.

Verified with the full open-code check at 367 fixture tests, both snapshots
byte-exact. The new test is discriminating: with the clamp removed the frame
resolves to 999.0 against a bound of 20.0 and the test fails.
…talog with disclosure

`remix 1.0.0-beta.7` is on pub.dev, which was the plan's one stated blocker.
This closes everything that waited on it.

**The constraint moves to `^1.0.0-beta.7`** in the registry, the CLI tests,
the checker test, and the docs. The checker's first phase now resolves beta.7
from the hosted cache, so the guarantee is real rather than assumed. The
scratch verification also exercised the CLI's own guard on the way: an app
whose lockfile still held beta.6 was refused with "remix 1.0.0-beta.6 does not
satisfy ^1.0.0-beta.7" before any file was written.

**`disclosure` is the 29th item**, and the catalog now covers every component
in the hosted release. The recipe is deliberately frameless: the accordion is
this component's stacked sibling and draws a rule because its rows have
neighbours to separate; a lone disclosure has none, so a frame would only box
in whatever the caller placed it inside. The trigger is styled as a
self-contained row target instead — menu-row padding, radius, and hover
treatment, at the accordion's 44px height because it stands alone rather than
in a dense list. The open trigger holds a `muted` fill, distinct from the
`accent` hover, so a reader can tell an open section from a hovered one.
`triggerBuilder` and `transitionBuilder` are curated out of the generated
widget: both are typed by `package:naked_ui`, which this layer does not
depend on.

Its tests resolve through a real widget rather than `_resolve`, and not by
preference: `onExpanded` reads the live `NakedDisclosureState`, so resolving
the style outside a disclosure throws — the same constraint the checkbox's
`onIndeterminate` already imposed, handled with the same `_disclosureSpec`
pattern. The disabled-fade group grows its ninth probe.

**`.behindContent(.shadows([_shadow]))`** replaces the spelled-out
`RemixBoxEffectLayerMix(shadows: [...])` in menu and select — the two sites
that were waiting for the beta.7 API.

Also migrated `test:cli` to melos 8's `exec.command` form, which main's melos
bump made mandatory for this branch's one remaining `run`+`exec` script.

Worth recording: regenerating the playground against beta.7 produced
byte-identical adapters for all 28 existing components — the release changed
no generated output.

Verified with the full open-code check: 58 CLI tests, 12 checker tests, 375
fixture behavior tests against hosted beta.7 and the current checkout, both
snapshots byte-exact, docs validation over 21 consumer-facing Markdown files,
and the playground mirroring the registry with its one declared customization
intact.
Built a Playwright capture harness for the installed layer and reviewed all 29
components specimen by specimen — every variant, every size, rest/hover/focus/
pressed, both themes, 304 captures. The harness lives in `.context/` and is
not part of the repository; what it found is here.

**The select's option rows were inset 12px where the menu's are 8.** Same 32px
row, same 4px panel, same kind of floating list. The recipe's own
documentation claims the opposite — "the panel matches the menu's, so a select
and a menu opened side by side do not read as two systems" — and the panels do
match, the rows did not. One `_paddingX = 12.0` was serving both the trigger
and the rows. 12 is right for the trigger, which is a field styled after the
text field; a row in a dropdown is not a field. The rows now have their own
constant at the menu's 8.

**The accordion's row had no horizontal inset at all**, against 12 for every
other row-like surface in the layer: the table's cells, the select's trigger,
the callout, the disclosure. Upstream shadcn does leave its accordion trigger
flush, but only because the item wrapping it supplies the inset — nothing
wraps this one, so flush put the title hard against whatever contained it
while the rule underneath still spanned the full width. Row and panel now take
the same 12 as their neighbours.

**The disclosure's focus ring wrapped the content as well as the trigger.**
Keyboard focus is on the trigger, but an expanded disclosure's container is
the trigger plus everything it revealed, so the ring claimed the content was
focused and grew with it. It now rings the trigger alone via
`foregroundDecoration`, which paints over the box rather than beside it — so
the ring still takes no layout space and opening the section does not reflow
the page. A plain border would have pushed the trigger's content in by two
pixels on focus.

**The disclosure's revealed content had no bottom inset**, leaving its last
line against the container's own edge. Invisible while the container is
undecorated, obvious under the focus ring, and wrong for any consumer who
gives the container a fill. Both sides now.

Two things the pass confirmed rather than changed: every one of the 29 reads
the theme, with the only literal colors being transparent sentinels, shadow
tints outside the fifteen-token vocabulary, and doc examples; and the
segmented control's odd-looking 26/30/34 item heights are correct — plus twice
the 3px track inset they are exactly the 32/36/40 the rest of the layer uses.

Verified with the full open-code check: 375 fixture tests against hosted
beta.7 and the checkout, both snapshots byte-exact, and the playground
mirroring the registry.
Compared the layer against shadcn/ui numerically rather than by eye: its
values come from the public registry, decoding Tailwind at one unit to four
pixels; ours from resolving the installed specs in a real BuildContext and
reading them back. Seventeen components have a direct counterpart.

The controls already agree, in more detail than expected. Button heights are
32/36/40 against `h-8`/`h-9`/`h-10`; the small and medium insets are 12 and 16
against `px-3` and `px-4`; the text field and select trigger are 36 and 12
against `h-9 px-3`; the menu and select panels inset 4 against `p-1`; badge 8
against `px-2`; avatar 40 against `h-10`.

That comparison also settles the row inset the previous commit changed on
internal-consistency grounds alone. shadcn's `DropdownMenuItem` and its
`SelectItem` both use `py-1.5` with 8px on the text side, differing only in
which side reserves room for the indicator — 6 and 8, exactly where the fix
landed.

One value was genuinely wrong: the tooltip's vertical inset was 5. That is the
only number in the layer off its own four-pixel grid, it carried no comment
explaining the exception, and both shadcn and our own menu and select rows use
6. Now 6.

Everything else that differs, differs consistently and stays. Every surface is
one step tighter than shadcn — tooltip 8 against `px-3`, callout 12/10 against
`px-4 py-3`, popover 12 against `p-4`, card 16 against `p-6`, dialog 20
against `p-6` — while the internal progression holds. That is one decision
rather than five accidents, and it is what makes the layer read denser. Button
large keeps 20 rather than shadcn's `px-8` at 32, which nearly doubles its own
medium; ours steps 12/16/20 so the three sizes read as one family.

The full comparison is written up in `.context/shadcn-compare/`, alongside the
fetched registry items it was derived from.

Verified with the full open-code check: 375 fixture tests against hosted
beta.7 and the checkout, both snapshots byte-exact, and the playground
mirroring the registry.
…hter

The measured comparison found every surface in this layer sitting one step
tighter than its shadcn counterpart. That was internally consistent — the
progression tooltip < callout < popover < card < dialog held — but consistency
was the only thing defending it. Nothing was written down about why a layer
that takes shadcn's token vocabulary, its control heights, and its row insets
should then run denser than it everywhere else, and being systematically
tighter is not a decision anyone made; it is five values that each drifted the
same direction.

    tooltip   8/6  -> 12/6    (px-3 py-1.5)
    callout  12/10 -> 16/12   (px-4 py-3)
    popover  12    -> 16      (p-4)
    card     16    -> 24      (p-6)
    dialog   20    -> 24      (p-6)

The controls needed nothing: heights, button insets, field and trigger
geometry, row insets, and panel padding already matched exactly.

Two apparent mismatches turned out not to be. shadcn ships one checkbox and
one progress bar; this layer ships three sizes of each, and shadcn's single
value lands inside our scale rather than beside it — its 16px checkbox is our
small, its 8px track is our large. Collapsing our medium onto their one value
would flatten a scale with three real steps, so both stay.

Button large also stays at 20 rather than shadcn's `px-8`. That one is an
outlier inside shadcn's own system, nearly doubling its medium, where ours
steps 12/16/20 so the three sizes read as one family.

Verified with the full open-code check: 375 fixture tests against hosted
beta.7 and the checkout, both snapshots byte-exact, and the playground
mirroring the registry.
shadcn ships a size scale on exactly two components: `button` and `toggle`.
This layer had thirteen. The other eleven were three numbers each with nothing
anchoring them to the system they claim to follow, and they were the reason
the checkbox and progress bar could not be aligned in the previous commit —
their single value sat inside our scale rather than on it.

Collapsed, each landing on shadcn's own value: checkbox and radio to 16
(`h-4`), progress to 8 (`h-2`), switch to 20 (`h-5`), avatar to 40 (`h-10`),
the slider rail to 6 (`h-1.5`), and the text field, select trigger and tab to
36 (`h-9`) — which is also the button height they sit beside. The segmented
control keeps 30 because its segment is inset inside the track by three on
both sides, so the track lands on the same 36. The spinner has no shadcn
counterpart and keeps its 20.

`button`, `icon_button`, `toggle` and `toggle_group` keep their scales.
`icon_button` is `button`'s `size=icon` as a component, so it inherits the
same justification.

This is a breaking change to the generated widgets: eleven of them lose their
`size` parameter, and the committed `tabs` adapter snapshot records exactly
that. A call site that needs a different weight now sets it through `style`,
which is one line where it matters rather than an enum every consumer of the
recipe has to read past.

The data table composed `.small` variants of the checkbox and select for
density; both are single-size now, which moves its selection checkbox by two
pixels inside a 44px row and its page-size select by four inside a 40px
footer. Both were checked and neither crowds.

The fixture suite drops from 375 tests to 353 — the removed size permutations,
not removed coverage. Every group that iterated a size map now asserts the one
shipped value, and the four components that kept their scales still iterate
theirs.

Verified with the full open-code check: 353 fixture tests against hosted
beta.7 and the checkout, both snapshots byte-exact, docs validation over 21
consumer-facing Markdown files, and the playground mirroring the registry.
Recaptured all 29 components after the size collapse — every variant, every
state, both themes, 298 captures — and reviewed them one at a time. Three
hover states were not doing anything a reader could see.

**The radio's ring disappeared on hover.** `accent` over the `border`
hairline is 1.09:1 in the shipped light theme, so tinting the disc erased its
outline and left a hovered *empty* radio rendering as a solid grey dot — which
is what a *chosen* one looks like. The hover fragment now darkens the ring to
`mutedForeground` as well as filling, so the circle stays a circle.

**The checkbox had the same bug** for the same reason, and takes the same
fix. It reads less alarmingly — a grey square still looks like a box — but an
unchecked checkbox with no visible edge under the pointer is the same defect.

**The accordion had no perceptible hover at all.** Its fragment moved the two
icons from `mutedForeground` to `foreground`, which at 16px next to a title
already at full strength is invisible; hovered and resting rows were
indistinguishable in the capture. It now underlines the title, which is what
shadcn's own accordion trigger does (`hover:underline`). The title's weight
stays reserved for the open state, so "the pointer is here" and "this section
is open" remain different things.

Two of these were only findable by looking. The specs resolve exactly what the
recipes ask for, so no assertion was wrong — the colours were simply too close
to see, which is the class of defect a test suite cannot report and a capture
sheet shows immediately.

The harness needed two fixes of its own to make that possible: specimens are
now shrink-wrapped on both axes, because several components expand into
whatever they are given and an unconstrained segmented control filled the
viewport and captured as a slice of a stretched track; and the segmented
control and toggle group are additionally width-constrained.

Verified with the full open-code check: 353 fixture tests against hosted
beta.7 and the checkout, both snapshots byte-exact, and the playground
mirroring the registry.
Added specimens for the four components the capture harness had never covered
— slider, tabs, popover and data table — which takes it to all 29, and found
one real defect immediately.

**The gallery's popover never opened.** `RemixPopover` opens on a tap of its
own `child`, and the trigger was an `AcmeButton` with its own `onPressed`,
which consumes that tap before the popover sees it. Nothing failed: the panel
simply never appeared, and the only assertion covering it checked that the
trigger *rendered*. The capture showed a button and no panel, which is what
made it visible.

The gallery now drives it from a `MenuController`, the recipe documents the
trap with the working composition, and two tests cover both halves — a plain
child opening on tap, and a button trigger needing the controller. The second
is the one that would have caught this.

Slider, tabs and the data table are all correct: the slider's range, thumb and
focus ring are distinct; the tab bar's selected underline, hover fill and
disabled state all render; and the table's select-all checkbox is intact in
the corner, which is the radius clamp from an earlier commit still holding.

Two harness fixes were needed to see any of it. The tabs specimen is now the
bar alone rather than bar-plus-panel, because the driver puts the pointer at
the specimen's centre and the centre of the taller composition landed on the
panel — hover and focus captured as identical to rest. The popover specimen
uses a tappable non-button child so the component's own behaviour is what the
capture shows.

Also recorded in the comparison notes: aligning `tabs` to shadcn would be
wrong. shadcn has no segmented control — its `Tabs` *is* the pill-on-a-track
control this layer already ships as `segmented_control`, down to the same 36px
track. Matching it would leave two components rendering identically and
differing only in name.

Verified with the full open-code check: 355 fixture tests against hosted
beta.7 and the checkout, both snapshots byte-exact, workspace analyze clean,
and the playground mirroring the registry.
The registry's `remix: ^1.0.0-beta.7` admits every later beta, but the
templates are only ever tested against one version. A consumer who upgraded
landed on an untested beta with no signal, because lock verification only
checked constraint membership.

`add` now reports when the resolved `remix` is above the constraint's floor.
The install still completes; `--dry-run` and `--diff` stop before
`flutter pub get`, so they never print it.

The floor is the tested version because the floor is now coupled to the
release. `check_version_alignment` asserts the registry constraint floors at
`packages/remix`'s version, `sync_registry_remix` is the writer that moves it,
and `version.yml` runs that writer after `melos version` — melos cannot,
because the registry is data, not a pubspec dependency. `check_open_code`
proves a real consumer resolves that same version from pub.dev.

Test fixtures derive both values from the bundled registry, so the next bump
does not leave a dozen `does not satisfy` failures inside the release pull
request.
The documented `flutter pub add dev:remix_cli` could not work: the package is
not on pub.dev and `publish.yml` excluded it on purpose, behind a post-MVP
release gate.

This closes that gate. `publish.yml` gains a `remix_cli-v<version>` trigger and
a fourth job, mirroring how `remix_ui_icons` was added. remix_cli depends on no
other package here and is versioned independently, so its tag orders against
nothing and cannot race the existing three jobs.

The docs drop the "not yet published" qualifiers and describe the pub.dev
install as the normal path.

The job stays inert until the first `remix_cli-v*` tag exists. Creating the
package on pub.dev and configuring automated publishing are manual bootstrap
steps that follow this merge.
The catalog listed `orientation` in the Axes column for `divider`, next to
`variant` and `size` on the rows that declare real enums. It is not one.

The divider recipe accepts a plain Flutter `Axis` by explicit design — its own
doc comment says so — and the generator emits named constructors only for a
declared axis. `UiBadge` gets four, `UiButton` five; `UiDivider` gets none.
The table implied a surface that is not there.

Verified against the installed source: every row the table marks `variant`
generates named constructors, and `divider` generates zero.
Restructure the standalone guide section by section. The guide now leads
with the ownership model, then the six-step path, then the reference.

- Replace the headline. The reader owns the recipe; Remix keeps the
  behavior, the accessibility, and the interaction states.
- Show the render before the source in the hero, and use the real recipe
  entry point instead of a private helper.
- Add four principles, an ownership table, an upstream-update answer, a
  six-card path overview, a three-scope customization ladder, a six-state
  sheet, and a provenance section.
- Collapse five reference sections into one, and disclose the complete
  installed button.dart.

Correct three inaccuracies:

- theme_data.dart shows the real primary and focusRing values.
- The --diff excerpt shows the real header a/proposed for a new file.
- The one-call-site snippet matches the render beside it.

Capture 12 new renders at a device pixel ratio of 2, from a specimen app
built from the installed source. The 36 and 44 pixel pair measures 72 and
88 device pixels.

Verify every command in a fresh Flutter 3.44.0 app, and check the guide at
four viewport widths for overflow, broken images, contrast, and console
errors.
The guide is about the command the reader types, so its title and brand now
read `remix`. The header pills name both packages and their versions, because
the guide contrasts them in the Update section.

Correct the type system:

- A table row header is prose, so it uses the text face. It keeps the
  monospace face only when it names an identifier. This fixes 34 headers.
- The `.mono` class had no font-family rule, so the CLI status words rendered
  in the text face. It now applies the monospace face.

Correct the mobile layout:

- Every table stacks below 560 pixels. Six tables scrolled sideways before,
  which hid the last column.
- A stacked cell puts the column name above the value, so a long column name
  no longer squeezes the value.
- Code inside a stacked cell wraps. The two upgrade commands sit on two lines.

Reduce the container styles:

- The four principles use the hairline grid the guide already uses, not cards.
- The scope rail is a 1 pixel hairline, like every other rule.

Rewrite three result lines that shared one sentence template, and the Update
subtitle. Match each contents entry to its heading.

Verify at four viewport widths: no overflow, no broken or upscaled images, no
contrast failure, and no console error.
A recipe is a declaration in source, not a rendered result, so a generator can
read it. This section explains what that makes possible.

- Map what the recipe already holds to what a generator can derive from it:
  the axes, the recipe matrix, the resolved styles, and the theme documents.
- Show the Muse A2UI catalog entry a generator would emit, with the variant
  and size vocabulary marked, because it comes from the recipe enums.
- Show the component capture document, with all six states as data.
- Name the boundary: a generator derives mechanics, never meaning. Slot names,
  semantic roles, nested boundaries, and behavioral geometry stay authored.

The section opens with a notice that it is a design preview. Provenance now
scopes the evidence claim to the rest of the page, so the guide does not
present an unbuilt generator as verified behavior. The two JSON shapes follow
contracts that exist today.
…ject Dart

Three defects a review found, each verified by running the CLI.

A CRLF barrel was rejected. validateManagedBarrel and updateManagedBarrel
split on the line feed and compared the marker exactly, so a barrel that Git
checked out with core.autocrlf=true carried a trailing carriage return and
matched nothing. A Windows consumer then saw every remix add fail, reporting a
missing marker pair while both markers sat in the file. Both functions now
parse without the terminator, and the rewrite keeps the terminator the file
already used.

The diff formatted the proposal with whichever Dart ran the CLI, while add
formats with the project's Flutter SDK. A globally activated CLI on a
different Dart could therefore report formatter-version differences that no
install would produce, which breaks the promise that current is your file and
proposed is the template. The diff now resolves the same toolchain as add.
This makes --diff require Flutter 3.44 as add already does; a diff that
predicts an install which cannot run has nothing to say.

Bare `remix help` printed the usage twice. CommandRunner's help command
printed it and returned null, and the null-result branch printed it again.
`help` now takes the same path as --help and -h.

The dogfood checker recorded only an exit code, which cannot be acted on from
a CI log. It now carries stderr, where a missing Git or a refused preflight is
named.

Tests: LF and CRLF barrels, both parse and rewrite; bare help prints usage
once; the diff test asserts the toolchain Dart instead of the host Dart, which
is the assertion that would have caught the second defect.
- The step 3 card promised the proposed source from --dry-run. Only --diff
  shows source, so the card now names both modes.
- The preview subtitle said both modes stop before any process. --diff runs a
  formatter and Git in a temporary directory. The section now states what each
  mode runs, and that neither writes to the project.
- The update loop did not repeat the scope warning. One diff covers one item,
  so add button --diff can come back clean while the theme template moved.
- The exit-code table did not record that a partial registry dependency blocks
  both previews, while a partially installed requested item still previews.
- open-code.mdx read an empty diff as the only sign of being current. An
  edited recipe keeps the diff non-empty whether or not the template moved.

CLEAN_SHEET.md said generation targets only the parts declared by resolved
items. The installer unions every already-installed adapter, so adding one
item cannot delete another item's generated part.
Use the Windows Dart executable and resolved Pub workspace membership. Reject reserved prefixes before writes. Print command help once. Use literal package filters for source generation. Verify installation and edit preservation with the real SDK.
@leoafarias
leoafarias marked this pull request as ready for review September 4, 2026 21:25
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.

1 participant