Skip to content

chore: gate formatting and close inert checks in remix_cli and remix_agent - #208

Open
leoafarias wants to merge 14 commits into
mainfrom
chore/registry-agent-review-followups
Open

chore: gate formatting and close inert checks in remix_cli and remix_agent#208
leoafarias wants to merge 14 commits into
mainfrom
chore/registry-agent-review-followups

Conversation

@leoafarias

Copy link
Copy Markdown
Member

Description

Review follow-ups for the recent remix_cli and remix_agent work. Most of this is verification that either did not exist or could not fail.

Formatting was unverified. The shared dart-actions workflow runs an analyzer step but no formatter, and no melos script checked it, so two committed test files had drifted. This adds format:check, plus an analyze script so a local melos run ci reports what a pull request would. Seven default registry templates had drifted too, and nothing could have noticed: dart format never reads .tmpl files, and add formats the tree it writes, so an unformatted template cannot reach a consumer either. A new test renders every default template and compares the result to the formatter.

Three guards could not fail. The remix_agent barrel assertion listed six paths that no longer exist, so it passed unconditionally; it is now a set comparison, which also rejects an added export. check_dependency_constraints.dart could not reach the registry's own pub constraints, leaving 196 declarations that nothing compared against the workspace. The transcript worksheet documented a followThreshold of 56 while the code ships 48.

Two remix_agent defects. A transcript could nest inside itself, and a component disposed objects it did not own. Separately, focus-visible on the transcript viewport had no source at all — Agent's slots resolve above any Naked control, so no ancestor state scope existed — and the transcript now publishes its focus through a WidgetStatesController. The pointer-driven states still do not resolve on that slot and did not before, so nothing there changed.

The default preset had no test coverage. Nothing outside apps/playground/lib/ui/ imported the generated mirror, so the preset that consumers actually receive was only ever analyzed, never run.

Two refactors round it out: the @ui/ target prefix is named rather than spelled as a bare substring(4), add is split into a plan phase and an install phase, and AgentStyleBuilder is replaced by RemixStyleSpecBuilder — the builder all 20-odd Remix components already use — now exported from packages/remix.

Related Issues

None.

Validation

melos run ci — passed. 16 steps: ~4,400 tests across 11 packages, 769 files formatter-clean, both open-code fixture checks, plus the dogfood and Fortal parity gates.


Checklist

Note: Updating the pubspec.yaml and CHANGELOG.md is not required. These are handled automatically during the release process.

  • My PR includes unit or integration tests for all changed/updated/fixed behaviors.
  • I have updated or added relevant documentation (doc comments with ///).
  • I am prepared to follow up on review comments in a timely manner.

Breaking Change

Does this PR require users of the package to manually update their code?

  • Yes, this is a breaking change.
  • No, this is not a breaking change.

AgentStyleBuilder lived under src/style/ and was never exported from the remix_agent barrel, so removing it is not consumer-visible. The one public API change is additive: packages/remix now also exports RemixStyleSpecBuilder.

leoafarias and others added 8 commits September 12, 2026 13:52
Nothing verified Dart formatting. The shared dart-actions workflow runs an
analyzer step but no formatter, and no melos script checked it, so two
committed test files had drifted. Adds `format:check`, plus an `analyze`
script so a local `melos run ci` reports what a pull request would.

Seven default registry templates had drifted too, and are reformatted here.
That gate cannot see them: they are `.tmpl` files, so `dart format` never
reads them, and `add` formats the tree it writes, so an unformatted template
cannot reach a consumer either. Nothing was left that could notice. Fortal's
templates are derived from formatted source and were already clean.

Extends check_dependency_constraints.dart to the registry's own pub
constraints. `melos bootstrap` never rewrites them and the pubspec walk
could not see them, leaving 196 declarations that nothing compared to the
workspace. check_version_alignment.dart keeps sole ownership of the `remix`
floor.

Replaces the remix_agent barrel assertion, which listed six paths that no
longer exist and therefore could not fail, with a set comparison that also
rejects an added export. Pins bundledPresets to the preset trees on disk,
requires a worksheet per component, and corrects the transcript worksheet's
followThreshold to the 48 the code ships.

Co-authored-by: Cursor <cursoragent@cursor.com>
…jects

AgentExecution rendered its single child inside a whole AgentTranscript,
which installs Arrow/Page/Home/End shortcuts and a Semantics container.
Nested in a host transcript the inner list shrink-wraps to a zero scroll
extent, but its action still consumed those intents, so focus landing in an
execution card silently stopped the outer transcript from scrolling; the
inner `busy` value also announced the status a second time. The output now
uses AgentLiveEdgeScrollView, the primitive plan and activity already use.

The composer disposed a superseded owned controller or focus node inside
didUpdateWidget, while the child RemixTextArea still held it, so detaching
touched a disposed object. Both swaps now defer to a post-frame callback,
matching the transcript's scroll-controller swap.

Co-authored-by: Cursor <cursoragent@cursor.com>
…lean

`format:check` covers Dart sources, but registry templates are `.dart.tmpl`
and escape it. No consumer is affected either way, since `add` formats the
tree it writes, so installed source is formatted regardless of what the
template looked like. What this buys is that the committed templates stay
diffable against each other instead of differing by pure whitespace.

Every template was already clean when this was written, so it guards against
future drift rather than fixing anything observed. It is a test rather than a
tool under `tool/` so that it uses the real TemplateRenderer and takes both
prefixes from ProjectConfig, rather than carrying a third private copy of the
substitution rules that could drift from what `add` actually writes. The
barrel stays untouched: it documents that `lib/src` is deliberately
unexported, and `test/registry_test.dart` already reaches internals this way.

Scoped to the hand-authored preset. `fortal` is generated from remix_fortal's
formatted source through a byte-identical round-trip assertion, so it is
formatter-clean by construction and `open-code:fortal:check` is its real gate;
a wider version confirmed all 48 Fortal templates were already clean.

Co-authored-by: Cursor <cursoragent@cursor.com>
Nothing outside `lib/ui/` imports this app's installed mirror -- the
playground's own previews render remix_fortal -- so the source `remix add`
copies into every consumer app was only ever analyzed, never built. It was
also the only workspace app with no test directory, while apps/dashboard has
seven files. melos `test:flutter` selects on `dirExists: test`, so adding one
wires it into ci with no configuration.

Covers the theme contract and the sidebar layout, the one default component
that opens a route, owns a breakpoint, and has a controlled/uncontrolled
contract. The theme test deliberately asserts no token count: the open_code
fixture owns that pin for a freshly generated consumer, and a second copy
would mean two places to edit per added token. It does pin the indigo
customization, because check_open_code_dogfood.dart records that as a
deliberate edit and therefore tolerates any divergence -- it cannot notice a
reinstall reverting it.

The sheet is matched by its barrier label, not by ModalBarrier: MaterialApp's
own home route always has one, so the bare type finder reports a sheet that
is not there. Changing the breakpoint default fails three of the four.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ases

`@ui/` was spelled four times across two files, twice as a bare
`substring(4)` whose 4 had to be read back to the literal to be understood.
One `uiTargetPrefix` constant now carries the spelling and its length, next to
`bundledPresets` where the target format is validated.

`Installer.add` was 247 lines that mixed resolving an install with performing
one, so the preflight ordering -- the property that makes `--dry-run` and
`--diff` trustworthy -- was something a reader had to reconstruct. It is now
`_planAdd`, which reads and validates and writes nothing, and `_install`,
which carries out the resulting `_InstallPlan`. `add` is 42 lines and its
control flow is unchanged: print the plan, return on dry-run, print the diff,
otherwise install.

`_InstallPlan` is deliberately one value handed forward, so a later step
cannot re-read the project and act on a different answer than the one already
printed. `_install` names its fields as locals so the moved steps read as they
did before, which keeps this reviewable as a move rather than a rewrite. All
four load-bearing comments are preserved, including the build-filter note
explaining why already-installed adapters stay in the focused build.

Co-authored-by: Cursor <cursoragent@cursor.com>
…cator

AgentStyleBuilder duplicated RemixStyleSpecBuilder: the same either/or
between a fluent style and a resolved styleSpec, down to forwarding the same
optional controller. Rather than keep the copy in step, it is deleted and
RemixStyleSpecBuilder is exported from packages/remix, which is what all
20-odd Remix components already use.

Separately, the transcript never passed a controller to it, and `focused` has
no other source: Agent's slots resolve above any Naked control, so the
`focus-visible` state the transcript worksheet documents could never
activate. The transcript now publishes its focus through a
WidgetStatesController, and the new test fails without it. The pointer-driven
states still do not resolve on that slot and did not before either -- a
host's `onHovered` on AgentTranscriptSpec.viewport has never had an effect --
so nothing there changed.

Also collapses `_indicator`, which was the same body in five components
differing only by the spec type: 67 lines to 35. The builder parameter stays
per-component on purpose, since a permission card discloses details and an
answer discloses sources. The related latched-flag duplication is deliberately
left alone: those six surfaces expose different public names and activity's
getter is not even the same expression, so sharing it would need an adapter
per component to save nothing.

Co-authored-by: Cursor <cursoragent@cursor.com>
Each of these asserted something the code does not do, which is worse than
saying nothing: a reader trusts them over the code.

The transcript claimed Mix keeps tracking pointer states while it publishes
focus. Mix does the opposite — `StyleBuilder` gates its pointer detector on
`controller == null`, so supplying one suppresses it. But the conclusion that
this cost the viewport its hover is also wrong: a host `onHovered` on
`AgentTranscriptSpec.viewport` resolves no differently before this branch than
after. Checked by running an `onHovered` expectation against the pre-branch
transcript, which passed no controller; it fails there identically. So there is
no regression to fix, and the comment now records what the controller does and
does not carry, plus who would own hover if anything ever asked for it.

The playground theme test claimed `check_open_code_dogfood.dart` tolerates any
divergence and cannot notice a reinstall. It can: `!edited && reason != null`
fails when a declared customization matches the template again. What it cannot
see is *which* edit, since any custom color satisfies it, so what this test
adds is the values themselves and the tie between the ring and the primary.

The transcript focus test repeated the first claim and loses that clause.

Co-authored-by: Cursor <cursoragent@cursor.com>
Extracting `agentDisclosureIndicator` put it between the glyph-set dartdoc and
the enum that dartdoc described, so the paragraph about a set of glyphs became
the documentation for a widget factory and `AgentFunctionalGlyphKind` was left
with none. The function moves above the paragraph, which restores both.

`public_api_test.dart` names the files under `src/style/` that stay out of the
barrel by omission, and still listed `style_builder.dart` after it was deleted.
A pin that names a file the tree no longer has cannot be trusted to be checking
what it says.

`registry_test.dart` held the last `substring(4)` on a registry target, which
`uiTargetPrefix` was introduced to retire; the production sites already use it.

Two comments narrated this branch rather than the code. `_install`'s said its
locals read "as they did when this was one method", which tells a reader
nothing once the branch is merged, so it now states the invariant that earns
them: nothing past that point re-reads the project, so no step can act on a
different answer than the one already printed. `format:check`'s counted the
files that had drifted when it was added, which stops being true immediately.

Co-authored-by: Cursor <cursoragent@cursor.com>
@leoafarias leoafarias changed the title Gate formatting and close inert checks across remix_cli and remix_agent chore: gate formatting and close inert checks in remix_cli and remix_agent Sep 12, 2026
leoafarias and others added 6 commits September 12, 2026 17:02
`theme_data.dart.tmpl` requires twenty tokens: fourteen colors, the five
`chart*` entries the chart item added in #207, and `radius`. Eleven sentences
across the repo still said fifteen, the size before chart. Two of them are in
`card.dart.tmpl` and `textfield.dart.tmpl`, so the registry was shipping the
false claim into every consumer that runs `remix add card` or `remix add
textfield` -- six installed files here carried it. Those mirrors are enforced
copies, so correcting the templates and rerunning `--overwrite` moves them.

Two sentences in CLEAN_SHEET.md needed more than the number. One said the
growing catalog needed "no addition to the fifteen theme tokens", which chart
falsifies; it now names the one time the vocabulary did move. The other said
chart "resolves only the existing fifteen theme tokens", offering that as
proof `remix add chart` stays compatible with a theme installed before chart
existed. The opposite is true: chart resolves `chart1`-`chart5`, an older
theme does not carry them, and authored files are preserved on normal runs,
so such a theme needs them added by hand. That is now what it says.

Nothing compared the prose to the constructor -- grepping `packages/`,
`tool/`, and `test/` for any assertion of a token count returned nothing, so
the claim went stale the moment #207 landed and nothing could notice.
`registry_test.dart` now counts `required this.` in the theme template and
requires both shipping templates to spell that number, and only that number.
Reverted against the old `card.dart.tmpl` it fails with the count it expected.

Also corrects `open-code:dogfood:check`'s description, which named only the
playground; `_consumers` has held two entries since the Agent example joined.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
`specs/components/transcript.yaml` recorded `followThreshold default 56` while
every constructor shipped 48. It was caught by reading, not by CI:
`public_api_test.dart` validates worksheets by filename set only, so their
contents assert whatever they like.

A general validator for worksheet measurements would be a parser built for one
line -- across all eight worksheets this is the only measurement that names a
code identifier with a value; the rest is prose like "stop square 12, fill
inset 3/24". So the constant is pinned instead and the worksheet stays prose.

The plan named two declaration sites; there are five. `AgentTranscript` has it
on both constructors, and it is `AgentTranscript` the worksheet is about, so
pinning only `live_edge.dart` and `activity.dart` would have left the drifting
site unpinned. All five are asserted.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…itself

The check decided divergence with `!stdout.contains(_clean)`, so anything the
CLI printed that was not the exact sentence `No authored-source differences.`
counted as an edited file. Rewording that sentence in `installer.dart` made
every item report "has diverged from the template. Rerun `--overwrite`" --
39 of the 40 here -- which is both false and useless: `--overwrite` cannot fix
a change in the CLI's own output.

`--diff` prints a preamble and then exactly one verdict, the sentinel or a real
`git diff`, so both are recognizable. Matching a line rather than a substring
also stops a diff hunk that happens to contain the sentence from reading as
clean. When neither or both appear, the check now names that instead of
guessing, and points at `installer.dart`.

Verified by rewording the sentinel: 39 items report the new message and none
report divergence, while `apps/playground/theme` -- the one item that really
does diverge -- still classifies correctly against its `_customized` entry.

A shared constant was the obvious fix and is not available. The tool lives in
the root `remix_workspace` package, which declares no dependency on
`remix_cli`; `remix_cli.dart` is a marker library that documents `lib/src/` as
deliberately not re-exported; and `installer_test.dart`'s copy of the literal
is a pin of the output contract, so importing the constant there would make
the assertion tautological.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…g Fortal

The builder derived one bundled registry tree from analyzer-checked Dart
source, and ten separate things in it were the literal word Fortal: the source
and output paths, the `Fortal`/`fortal` substitution pair, the rejected path
segment, the legal source placements, a hardcoded `theme` item keyed off
`theme/theme.dart`, a hardcoded `icons` item copied from the default preset,
the `componentNames.length + 2` collision guard, the directories import
inference recognizes, the six packages it demands floors for, and the
`sidebar_layout -> sidebar` dependency nothing imports.

All ten move into `PresetSpec`, with `fortalPreset` as the single instance and
the entrypoint unchanged. `FortalPresetBuilder` becomes `PresetBuilder` and now
contains no Fortal knowledge: what remains of the word is `main`, which really
is Fortal's entrypoint, the spec itself, doc comments using it as the example,
and the header naming the tool inside generated output.

`theme` and `icons` were two shapes hiding behind one name. A shared item is
derived from a source directory and may be imported by components;
a copied item is taken verbatim from the default preset and derived from
nothing. Making them separate kinds is what lets a source package have several
of the first and none of the second.

Behavior is unchanged, and that is checkable rather than asserted: running the
writer, not just `--check`, leaves `registry/fortal/` byte-identical -- 48
templates and registry.yaml, no diff. A second spec with a different word,
different directories, no copied items, and its own forbidden self-import now
derives in the tests, so "general" is verified rather than claimed; without it
every test here would still be exercising the values the builder used to
hardcode.

The file keeps its name. It is still Fortal's entrypoint, and renaming it is
worth doing when a second caller exists, not before.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ctional glyphs

The Lucide catalog has 27,874 `IconData` constants and no
`@staticIconProvider`, so importing it defeats icon tree shaking; that is what
forced the hand-rolled codepoints in `_LucideGlyphs` and the exact pin on
`lucide_icons_flutter`. `RemixIcons` carries the annotation, a preserved
codepoint lock with per-glyph source hashes, and this repository's own notices
file, so the 17 glyphs become plain `RemixIcons` references and the dependency
is removed outright, with no compatibility shim.

Four marks change shape while keeping their meaning, and are accepted as such:
`error` -> `exclamationTriangle`, `tool` -> `code`, `loading` -> `update`
(circular arrows rotate cleanly under `_LoadingGlyph`), and `permission` ->
`lockClosed`. The other 13 map directly.

The glyph test now asserts `RemixIcons` identities instead of raw codepoints,
so it names the glyph it expects rather than a number that happened to match.

The glyph migration was executed by a delegated Gemini 3.1 Pro agent from a
written task; the README and provenance prose it produced was corrected here.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…writes

Track C derives registry templates from this package by substituting `Agent` ->
`{{typePrefix}}` and `agent` -> `{{valuePrefix}}`, and the builder's round-trip
assertion cannot catch a wrong substitution because it is symmetric: rewriting
`a long-running agent turn` into `a long-running ui turn` round-trips exactly
and still ships nonsense to the consumer.

Two doc comments used `agent` as a domain noun rather than as the family name.
They are reworded so every remaining occurrence in `lib/src` is an identifier,
a `ValueKey` string, or prose naming the component family - each of which is
meant to take the consumer's prefix. This is the authoring discipline
`remix_fortal` already follows.
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