Skip to content

Harden the shipped consumer preset against a real monorepo consumer - #18

Merged
jkrumm merged 5 commits into
masterfrom
fix/shipped-preset-consumer-gaps
Jul 17, 2026
Merged

Harden the shipped consumer preset against a real monorepo consumer#18
jkrumm merged 5 commits into
masterfrom
fix/shipped-preset-consumer-gaps

Conversation

@jkrumm

@jkrumm jkrumm commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Onboarding a real monorepo consumer exercised the shipped surface end-to-end and found three defects. All three share one root cause: basalt.roots was already the config axis for the guard walk, but the seeds weren't reading it.

Fixes

CI seed matched nothing on a monorepo. check.yml hardcoded oxfmt 'src/**'. A monorepo has no top-level src/, so the glob matched zero files and oxfmt exited 2 — the consumer's very first CI run broke, for a reason that reads like a basalt bug. The seed is now rendered through fillTemplate with a new {{ROOTS_GLOBS}} var instead of being copied raw.

Series path was wrong for anyone who configured roots correctly. It hardcoded src/lib/series.ts, so a consumer that correctly set roots: ['apps/web/src'] still got a DESIGN.md pointing at a path that cannot exist, and a defaultExempt that exempted nothing. It now derives from the first root, keeping argo's <root>/lib/series.ts convention. seriesModulePath still overrides. argo sidestepped this by setting exempt explicitly, which is why it never surfaced before.

eqeqeq fought the nullish idiom. Bare "error" flagged the deliberate x != null check (4 sites in the new consumer). ["error", "smart"] allows it while still catching == 1 — verified both ways against oxlint directly, so the rule doesn't get weaker.

Also drops a dangling dev:plugin script pointing at ./plugins/basalt, removed back in b4cb6cc.

The two pre-existing commits on this branch make apps/playground lint against the shipped preset — the dogfooding that makes this class of gap visible.

Release

The fix: commit touches packages/basalt-ui/, so this releases as a patch. Consumers on 1.1.0 get the corrected seeds on their next init/sync.

Verification

  • 688 tests pass; 5 new tests pin the roots derivation (multi-root globs, the src/** fallback when roots is unset, the seriesModulePath override).
  • fmt:check + lint + typecheck clean.
  • Seed/managed ownership is unchanged: check.yml stays mode: 'seed', so sync never overwrites a consumer's copy.

Do not squash — the per-package changelog needs the granular commits.

Summary by CodeRabbit

  • New Features

    • Playground linting now inherits the shared Basalt UI configuration through its local configuration.
    • Initialization supports custom source roots when generating CI checks and design documentation.
    • Generated formatting checks now cover all configured source roots.
  • Bug Fixes

    • Improved equality checking with smart eqeqeq validation.
    • Corrected generated series-module paths for custom roots and configurable overrides.
  • Documentation

    • Clarified generated configuration and series-module path behavior.
  • Chores

    • Removed the obsolete plugin development command.

jkrumm added 2 commits July 17, 2026 14:14
The playground is the model consumer, but it was linted by the repo-local
config — so it received basalt/token-layer-boundary, which is deliberately
absent from the shipped preset because it guards this repo's internal
layering and no consumer should ever get it.

Extend the shipped preset through the workspace symlink instead, via the
same node_modules-relative path a real consumer documents. oxlint 1.68.0
honours the nested config from the root `bun run lint` invocation (the
nearest config replaces the root one for the subtree), so this needs no
separate lint wiring. The playground now sees exactly what argo sees.

No deltas: the preset alone is what a consumer gets, and dist/ and
routeTree.gen.ts are already gitignored, which oxlint honours by default.
The playground now carries its own nested .oxlintrc.json extending the
shipped preset, and oxlint resolves the nearest config as a replacement
for the subtree — so the `#app` repo glob's apps/playground/src/** entry
no longer matches anything. Narrow it to the package's own src and record
why the omission is deliberate, since the glob is generated from SURFACES
and would otherwise look like an oversight worth "fixing" back.

The shipped preset's projection is unchanged: the repo/shipped glob split
means this never reaches consumers.

Lock the config-level chain that would otherwise rot silently: the repo
glob excludes the playground, the playground extends the preset at the
documented path, and that preset carries the visx boundaries but not
token-layer-boundary — the mirror of surfaces-coverage.test.ts's
assertion against the repo-local config.
@github-actions

Copy link
Copy Markdown

📦 basalt-ui package modified

Trigger the Make Release workflow after merging to publish to npm.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jkrumm, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 52 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f30e12f9-d07f-4935-ad06-6d5ee16927a6

📥 Commits

Reviewing files that changed from the base of the PR and between 6fcfcca and 445f031.

📒 Files selected for processing (9)
  • .oxlintrc.json
  • package.json
  • packages/basalt-ui/agent/skills/basalt-app/SKILL.md
  • packages/basalt-ui/configs/check.yml
  • packages/basalt-ui/configs/oxlint.json
  • packages/basalt-ui/src/cli/index.ts
  • packages/basalt-ui/src/cli/init-sync.test.ts
  • tests/fixtures/eqeqeq-smart-fixture.ts
  • tests/oxlint-preset-sync.test.ts

Walkthrough

The changes scope Basalt lint rules, add a playground Oxlint preset extension, and make init-generated CI and design paths derive from configured roots. Documentation and tests reflect the new behavior, while the root plugin development script is removed.

Changes

Configurable tooling

Layer / File(s) Summary
Lint scope and playground preset
.oxlintrc.json, apps/playground/.oxlintrc.json, packages/basalt-ui/configs/oxlint.json, packages/basalt-ui/src/surfaces.ts, tests/oxlint-preset-sync.test.ts
Basalt lint overrides are scoped to Basalt UI sources, the playground extends the shipped preset, equality checking uses smart mode, and boundary contracts are tested.
Root-aware init scaffolding
packages/basalt-ui/src/cli/index.ts, packages/basalt-ui/configs/check.yml, packages/basalt-ui/src/cli/init-sync.test.ts, packages/basalt-ui/agent/skills/basalt-app/SKILL.md
CI formatting globs and series paths are generated from configured roots, with explicit override support and updated documentation.
Repository command cleanup
package.json
The dev:plugin script is removed.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant BasaltInit
  participant Config
  participant TemplateRenderer
  participant CIWorkflow
  BasaltInit->>Config: read basalt.roots
  Config->>TemplateRenderer: provide ROOTS_GLOBS and SERIES_MODULE_PATH
  TemplateRenderer->>CIWorkflow: render root-specific checks
Loading

Poem

A rabbit hops through roots so wide,
With linting rules kept neatly tied.
Templates bloom and paths align,
Smart checks sparkle line by line.
The playground joins the preset tune—
And plugin scripts hop off the moon.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the PR’s main theme of hardening the shipped consumer preset for a real monorepo consumer.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/basalt-ui/src/cli/index.ts`:
- Around line 145-165: Use the default roots whenever cfg.roots is undefined or
empty, not only when it is undefined. Update resolveSeriesModulePath and
buildTemplateVars so both select the same non-empty roots array before deriving
the series path or ROOTS_GLOBS; apply this in
packages/basalt-ui/src/cli/index.ts lines 145-165 and 471-472.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e1c33b82-01a9-4c85-86a0-23c43ec04a00

📥 Commits

Reviewing files that changed from the base of the PR and between c2130b1 and 6fcfcca.

📒 Files selected for processing (10)
  • .oxlintrc.json
  • apps/playground/.oxlintrc.json
  • package.json
  • packages/basalt-ui/agent/skills/basalt-app/SKILL.md
  • packages/basalt-ui/configs/check.yml
  • packages/basalt-ui/configs/oxlint.json
  • packages/basalt-ui/src/cli/index.ts
  • packages/basalt-ui/src/cli/init-sync.test.ts
  • packages/basalt-ui/src/surfaces.ts
  • tests/oxlint-preset-sync.test.ts
💤 Files with no reviewable changes (1)
  • package.json

Comment thread packages/basalt-ui/src/cli/index.ts
jkrumm added 3 commits July 17, 2026 14:38
Real-consumer onboarding surfaced three defects in the shipped consumer surface:

- The CI seed hardcoded `oxfmt 'src/**'`. A monorepo has no top-level src/, so the
  glob matched nothing and oxfmt exited 2 — the consumer's very first CI run broke
  for a reason that reads like a basalt bug. check.yml now renders {{ROOTS_GLOBS}}
  from basalt.roots instead of being copied raw.
- The series path hardcoded `src/lib/series.ts`, so a consumer that set roots
  CORRECTLY still got a DESIGN.md pointing at a path that cannot exist, and a
  defaultExempt that exempted nothing. It now derives from the first root, keeping
  argo's `<root>/lib/series.ts` convention; seriesModulePath still overrides.
- `eqeqeq: "error"` flagged the deliberate `x != null` nullish check. "smart" allows
  it while still catching `== 1`.

roots was already the config axis for the guard walk; the seeds just weren't reading
it. Same knob, three fewer places that ignore it.

Both roots-derived seeds resolve through one `resolveRoots` helper: an explicit
`roots: []` falls back to the default rather than rendering an empty glob, which
would have reproduced the very "matches zero files" break this fixes. Roots are
POSIX-escaped into the generated CI shell string — consumer-authored config has no
business emitting a workflow that dies on a quoting error they can't trace back here.
@jkrumm
jkrumm force-pushed the fix/shipped-preset-consumer-gaps branch from 6fcfcca to 445f031 Compare July 17, 2026 12:38
@jkrumm
jkrumm merged commit ecff1a7 into master Jul 17, 2026
3 checks passed
@jkrumm
jkrumm deleted the fix/shipped-preset-consumer-gaps branch July 17, 2026 12:39
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