Skip to content

feat: framework-free token consumption - #24

Merged
jkrumm merged 9 commits into
masterfrom
feat/framework-free-tokens
Jul 27, 2026
Merged

feat: framework-free token consumption#24
jkrumm merged 9 commits into
masterfrom
feat/framework-free-tokens

Conversation

@jkrumm

@jkrumm jkrumm commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Closes the packaging and ergonomics gap that made jkrumm.com hand-port basalt's hexes into its own global.css instead of installing the package. The capability was already there — buildPaletteCss ran framework-free under Node and Bun — but four things stood in the way: required React peers, a hardcoded Mantine selector, 95 component-named spacing variables, and two styles.css rules that reach past basalt's own chrome.

Five additive changes, each defaulting to today's exact behaviour. No majors.

What changed

Commit Change
test: buildPaletteCss() pinned byte-for-byte against a committed fixture — 9718 bytes, 248 lines, 197 variables. The gate everything else lands against.
feat: scheme / defaultScheme / mediaFallback on BuildPaletteOpts. Any of them moves the per-scheme selector to :root[…]; passing none keeps the legacy html[…] literal.
feat: All five remaining required peers marked optional.
feat: dist/tokens.css published as basalt-ui/tokens.css, plus a basalt-ui tokens:css subcommand.
feat: only: 'core' drops the 95 component-named spacing variables — 197 → 102.
fix: styles.css print rule scoped to AppShell class hooks and moved into @layer basalt; heading font-stretch reads a knob.
docs: docs/FRAMEWORK-FREE.md, STATUS, README, llms.txt.

The specificity detail

The custom-selector path emits :root[attr='value'] (0-2-0), not html[…] (0-1-1) and definitely not a bare [attr='value'] (0-1-0 — the same as a light-default site's own :root block, so source order decides and dark mode silently does nothing). The mediaFallback block is a bare :root inside @media, so an explicit attribute outranks it on specificity rather than order.

The no-options path keeps html[data-mantine-color-scheme='…'] verbatim: a consumer may already override --vx-* under that exact selector, and raising basalt's own specificity would silently win over them.

The one real trade

@tanstack/react-query was pinned as non-optional by tests/required-peers.test.ts on the grounds that BasaltProvider hard-requires it. npm expresses peer optionality per package, never per subpath, so that guarantee and "a tokens-only install costs no React" cannot both hold. This PR picks the latter: a root-entry consumer missing a peer now learns from their bundler instead of their package manager. The test flips to pinning what still matters — every peer optional, none dropped from peerDependencies, so version-mismatch warnings survive. Both READMEs say so explicitly.

Follow-ups deliberately not folded in

Recorded in docs/STATUS.md:

  • Expose buildPaletteData / PaletteData — a framework-free consumer can retune radius and density but not the accent.
  • A plain-class dist/content.css.
  • The accent drift between DESIGN-SPEC.md (#0077bd) and the emitter (#4374a6). The only item here that would move existing consumers' pixels.
  • --basalt-font-head-stretch as a createBasaltTheme({ fonts }) option.

Verification

  • bun test — 968 pass / 54 files
  • bun run pre — fmt, lint, typecheck, check-theme green
  • bun run pack-test — full gate green, including basalt-ui/tokens.css resolving from a scratch install, the no-Mantine charts/tokens render, and tarball parity (now also asserting every file-valued export ships)
  • End-to-end: node bin/basalt-ui.mjs tokens:css --selector-attribute data-theme --default-scheme light --media-fallback --only core → 201 lines, correct cascade order, under plain Node with nothing installed

Summary by CodeRabbit

  • New Features
    • Added a prebuilt tokens.css export for using design tokens without React, Mantine, or a bundler.
    • Added the tokens:css CLI command with options for output location, color-scheme selectors, fallbacks, and core token sets.
    • Added configurable color-scheme targeting and reduced “core” token output.
  • Bug Fixes
    • Improved print styling to hide only application shell navigation and chrome.
    • Added configurable heading font stretching.
  • Documentation
    • Added framework-free token consumption guidance and updated installation and package documentation.

jkrumm added 9 commits July 27, 2026 17:07
The fixture is a byte-for-byte snapshot of buildPaletteCss() output. Formatting
it rewrites the exact bytes it exists to pin, so the formatter and the emitter
disagree and the test goes red against code it just proved correct. Same class as
the CHANGELOG entry above it: a generated file is owned by its generator.
buildPaletteCss() is the framework's widest contract — the Mantine theme, every
CSS module, every chart and any non-React consumer read the same emitted --vx-*
set. Per-group unit tests each see one slice, so a refactor that shifts a
declaration, a selector or a byte of whitespace passes them all and still changes
behaviour everywhere downstream.

Pin the whole string against a committed fixture instead: 9718 bytes, 248 lines,
197 unique variables. The fixture lives under tests/ rather than src/ because
copy-assets.mjs mirrors every src CSS file into dist, which would publish it as a
real stylesheet. Regenerate deliberately via tests/fixtures/regen.ts and review
the diff.

This is the regression gate the optional emission modes land against — each one
must leave the no-argument output identical.
The emitted per-scheme blocks were hardcoded to Mantine's own toggle
(html[data-mantine-color-scheme='…']), so a non-Mantine consumer — a static site
on data-theme, say — could use the palette data but not the emitted stylesheet.
Add three options that retarget it: `scheme` (attribute + values), `defaultScheme`
(which scheme rides the bare :root, or 'none'), and `mediaFallback` (an
@media (prefers-color-scheme) block for every non-default scheme).

Setting any of them moves the selector to `:root[attr='value']`, not `html[…]`.
That is load-bearing: a bare `[attr='dark']` is 0-1-0, the same as a light-default
site's own `:root` block, so source order decides and dark mode silently loses.
`:root[…]` is 0-2-0 — above both a bare `:root` and `html[…]` — and carries no
assumption about which element the attribute lands on. The OS-fallback block is a
bare `:root`, so an explicit attribute outranks it on specificity rather than
order: the preference is a fallback, never an override.

Setting none of them keeps the legacy `html[…]` literal, byte for byte — a
consumer may already override --vx-* under that exact selector, and raising
basalt's own specificity would silently win over them. Pinned by the golden
fixture.
./tokens, ./charts, ./state and ./guard resolve and render with no React and no
Mantine in the graph — enforced by the token-layer boundary rule, by the dist
graph walk, and by the no-Mantine step in the pack test. But npm expresses peer
optionality per package, never per subpath, so `bun add basalt-ui` still pulled
~79 packages on a consumer that only ever imports basalt-ui/tokens.

Mark all five remaining required peers optional: react, react-dom,
@mantine/core, @mantine/hooks, @tanstack/react-query. They stay listed in
peerDependencies — `optional` suppresses only the MISSING-peer install, so a
consumer running against an incompatible Mantine or React major still warns, and
that signal exists only while the entry is declared.

The cost is real and worth naming: a root-entry consumer missing one now learns
from their bundler rather than from their package manager. required-peers.test.ts
flips from pinning "react-query must not be optional" to pinning the invariant
that actually matters — every peer optional, none dropped from
peerDependencies.
A consumer with no bundler and no React had no way to consume the token system
short of hand-porting the hexes — ./styles.css is the framework's base layer and
assumes Mantine underneath it.

Two additions, one emitter. dist/tokens.css is buildPaletteCss() with no options,
generated at build time and published as basalt-ui/tokens.css: link it and read
var(--vx-*) from hand-written CSS. `basalt-ui tokens:css` re-emits the same thing
with the scheme selector retargeted, for a consumer who shouldn't have to install
the package just to change an attribute name — bunx it once, commit the output,
carry no dependency at all.

The subcommand parses flags and calls buildPaletteCss. It holds no emission logic
and no post-processing, so a `bunx tokens:css` consumer and a `basalt-ui/tokens`
consumer can never be looking at two different design systems; the artifact is
byte-identical to the golden fixture by construction.

check-tarball-parity now also asserts every file-valued export ships. publint
validates the map rather than the artifact, so ./styles.css, ./tokens.css and
./llms.txt could each have resolved to nothing at install time with every other
gate green.
104 of the 197 emitted variables are --vx-space-*, and 95 of those are named for
a specific basalt React component — agent-transcript-inset, toc-sub-indent,
sidebar-child-row-indent. Nothing outside this framework reads them, so a
framework-free consumer was carrying half the stylesheet for nothing.

`only: 'core'` keeps the 9 generic anchors (stack rhythm, the two control
heights, the row insets) and drops the rest, taking the set from 197 to 102.
Default stays 'all' and the output is unchanged. It is a SPACING filter only —
color, radius, shadow, type and status are identical in both modes.

The partition is derived from the SPACE key set, not maintained as a drop list, so
a new SPACE_STEP one-off is excluded the day it is added and a renamed anchor
follows automatically. --vx-space-row-line-height is in neither set by design: it
is the NavLink row's line-height, not a general anchor.

Exposed as `basalt-ui tokens:css --only core`, which is the flag that makes the
install-free recipe worth running.
Two rules in the shipped global stylesheet matched elements basalt does not own.

The print rule hid bare nav/header/footer, unlayered and with !important. A
consumer page with a real landmark and no BasaltShell printed it blank, and the
combination of no layer and !important left them nothing to override it with.
Scope it to AppShell's class hooks and move it inside @layer basalt, where layer
order already beats @layer mantine without !important — restoring the "one
legitimate !important in this framework" claim the iOS input floor makes, now
pinned by a test.

The heading rule matches bare h1-h6 by design (basalt owns the page's type) but
hardcoded 88% font-stretch, so a consumer layering its own base type had to
out-specify the framework to keep its own heading width. Read the value from
--basalt-font-head-stretch instead; setting it to `normal` opts out in one
declaration. Named --basalt-* rather than --vx-*: it is declared by styles.css
alongside the font stacks, not emitted by the token layer, and a --vx- name that
tokens.css does not carry would be a lie. Reaching it from
createBasaltTheme({ fonts }) is the obvious follow-up, deliberately not folded in
here.
jkrumm.com evaluated 1.2.0 and hand-ported the hexes rather than installing the
package. The capability was already there; nothing told a non-React consumer it
was, or how to avoid the traps.

FRAMEWORK-FREE.md covers the three routes in (bunx the CLI, link tokens.css,
import buildPaletteCss), the selector specificity arithmetic that decides whether
dark mode works at all, `only: 'core'`, the color-mix opacity law, and the two
things a consumer most reliably gets wrong: mapping a lone hairline variable onto
--vx-surface-hairline (the card ring, invisible on its own) instead of
--vx-divider, and treating elevation as a border when the ring lives inside the
shadow value and flips to inset on dark.

STATUS records the five changes and, explicitly, the four follow-ups they did not
fold in — chief among them that a framework-free consumer can retune radius and
density but not the accent, and the accent drift between DESIGN-SPEC and the
emitter.
The install section still told consumers Bun would auto-install the peers. It
won't any more — every peer is optional now, so all five are the consumer's job
and a missing one shows up as an unresolved import at build time rather than a
warning at install time. Say that plainly; it is the one place the optionality
trade is visible to someone who did nothing wrong.

Both the README and llms.txt now name the no-React path, which is the whole point
of the change: an agent reading llms.txt or a developer skimming the install
block should learn that basalt-ui/tokens costs no framework, without having to
infer it from a layering table.
@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 27, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR adds framework-free palette token consumption through configurable CSS generation, a tokens:css CLI command, a published tokens.css asset, optional peer metadata, documentation, fixture-pinning tests, and scoped global stylesheet updates.

Changes

Framework-free token consumption

Layer / File(s) Summary
Palette CSS options and fixture coverage
packages/basalt-ui/src/tokens/index.ts, packages/basalt-ui/tests/palette-css.test.ts, packages/basalt-ui/tests/fixtures/*, .oxfmtignore
buildPaletteCss supports selector customization, media fallback, and only: 'core' spacing output, with generated fixture and compatibility tests.
Token CSS CLI and published asset
packages/basalt-ui/src/cli/*, packages/basalt-ui/package.json, packages/basalt-ui/scripts/*, packages/basalt-ui/src/surfaces.ts
Adds tokens:css, generates dist/tokens.css, publishes ./tokens.css, and validates the asset in tarball checks.
Framework-free documentation and peer metadata
README.md, docs/FRAMEWORK-FREE.md, docs/STATUS.md, packages/basalt-ui/{README.md,CLAUDE.md,llms.txt}, packages/basalt-ui/package.json, packages/basalt-ui/tests/required-peers.test.ts
Documents framework-free token routes and limitations, records the release status, describes the new asset and CLI, and marks package peers optional with invariant tests.
Scoped stylesheet behavior and validation
packages/basalt-ui/src/styles.css, packages/basalt-ui/tests/styles-css.test.ts
Adds configurable heading stretch and scopes print hiding to Mantine shell elements without !important, with CSS assertions.
Estimated code review effort: 3 (Moderate) ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Consumer
  participant TokensCssCLI
  participant BuildPaletteCss
  participant TokensCssAsset
  Consumer->>TokensCssCLI: request token CSS
  TokensCssCLI->>BuildPaletteCss: pass selector and output options
  BuildPaletteCss-->>TokensCssCLI: return generated CSS
  TokensCssCLI->>TokensCssAsset: write or print CSS
  TokensCssAsset-->>Consumer: provide token stylesheet
Loading

Possibly related PRs

  • jkrumm/basalt-ui#18: Both changes modify the SURFACES registry and its published surface boundaries.
  • jkrumm/basalt-ui#20: Both changes modify the token CSS generation path around buildPaletteCss and derived data.
  • jkrumm/basalt-ui#23: Both changes extend token-system CSS emission and spacing-related output.

Poem

A bunny found tokens, bright and light,
And printed their colors just right.
With schemes that can shift,
And CSS as a gift,
Framework-free hops into night!

🚥 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 clearly matches the main change: adding framework-free token consumption support and related docs/CLI/assets.
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: 4

🧹 Nitpick comments (1)
packages/basalt-ui/src/styles.css (1)

27-31: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use one compliant namespace for the new public styling knob.

The stylesheet and its test introduce --basalt-font-head-stretch, while the package guideline requires new styling variables to use --vx-*.

  • packages/basalt-ui/src/styles.css#L27-L31: rename the custom-property declaration.
  • packages/basalt-ui/src/styles.css#L83-L93: update the var(...) reference.
  • packages/basalt-ui/tests/styles-css.test.ts#L51-L55: update the expected property name.
🤖 Prompt for 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.

In `@packages/basalt-ui/src/styles.css` around lines 27 - 31, Rename the public
custom property declaration in packages/basalt-ui/src/styles.css lines 27-31
from the basalt-prefixed name to the required --vx-* namespace, then update its
var(...) usage in packages/basalt-ui/src/styles.css lines 83-93 and the expected
property name in packages/basalt-ui/tests/styles-css.test.ts lines 51-55. Keep
the existing font-stretch behavior unchanged.

Source: Coding guidelines

🤖 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/CLAUDE.md`:
- Around line 351-356: Update the `tokens:css` flag documentation to include the
supported `--only <core|all>` option alongside the existing flags, without
changing the command’s behavior or other documented details.

In `@packages/basalt-ui/package.json`:
- Around line 199-207: Restore the required Mantine peer contract: in
packages/basalt-ui/package.json lines 199-207, remove optional metadata from
`@mantine/core` and `@mantine/hooks`. Update docs/FRAMEWORK-FREE.md lines 19-26 to
remove the every-peer-optional and no-peer-install claims, revise docs/STATUS.md
lines 282-284 to mention only genuinely optional peers, and update
packages/basalt-ui/CLAUDE.md lines 112-122 to document the required Mantine
peers and missing-peer behavior.

In `@packages/basalt-ui/src/styles.css`:
- Around line 164-175: Keep the scoped AppShell print selectors in the `@media`
print block effective against unlayered Mantine imports by retaining a scoped
!important override or adding an equivalent unlayered print rule. In
packages/basalt-ui/tests/styles-css.test.ts at lines 46-48, remove the assertion
that the print block contains no !important; at lines 58-63, replace the global
!important count check with an assertion scoped to the AppShell print selectors.

In `@packages/basalt-ui/tests/styles-css.test.ts`:
- Around line 40-44: Update the test around the layerStart assertion in the
`sits inside `@layer` basalt` case to verify that `@media print` is structurally
nested within the `@layer basalt` block, using brace-aware parsing or a scoped
pattern rather than only comparing source positions.

---

Nitpick comments:
In `@packages/basalt-ui/src/styles.css`:
- Around line 27-31: Rename the public custom property declaration in
packages/basalt-ui/src/styles.css lines 27-31 from the basalt-prefixed name to
the required --vx-* namespace, then update its var(...) usage in
packages/basalt-ui/src/styles.css lines 83-93 and the expected property name in
packages/basalt-ui/tests/styles-css.test.ts lines 51-55. Keep the existing
font-stretch behavior unchanged.
🪄 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 Plus

Run ID: 53400df9-a8b1-4c4a-9521-e45e514e9175

📥 Commits

Reviewing files that changed from the base of the PR and between 6b3a75f and 9366600.

📒 Files selected for processing (23)
  • .oxfmtignore
  • README.md
  • docs/FRAMEWORK-FREE.md
  • docs/STATUS.md
  • packages/basalt-ui/CLAUDE.md
  • packages/basalt-ui/README.md
  • packages/basalt-ui/llms.txt
  • packages/basalt-ui/package.json
  • packages/basalt-ui/scripts/check-tarball-parity.mjs
  • packages/basalt-ui/scripts/export-surface.mjs
  • packages/basalt-ui/scripts/gen-llms.ts
  • packages/basalt-ui/scripts/gen-tokens-css.mjs
  • packages/basalt-ui/scripts/pack-test.sh
  • packages/basalt-ui/src/cli/index.ts
  • packages/basalt-ui/src/cli/tokens-css.test.ts
  • packages/basalt-ui/src/styles.css
  • packages/basalt-ui/src/surfaces.ts
  • packages/basalt-ui/src/tokens/index.ts
  • packages/basalt-ui/tests/fixtures/palette-default.css
  • packages/basalt-ui/tests/fixtures/regen.ts
  • packages/basalt-ui/tests/palette-css.test.ts
  • packages/basalt-ui/tests/required-peers.test.ts
  • packages/basalt-ui/tests/styles-css.test.ts

Comment on lines +351 to +356
- `tokens:css` — emit the `--vx-*` stylesheet (stdout, or `--out <path>`). Flags:
`--selector-attribute` / `--dark-value` / `--light-value` / `--default-scheme <dark|light|none>` /
`--media-fallback`. No flags → byte-identical to the shipped `basalt-ui/tokens.css`. It is a flag
parser over `buildPaletteCss` and holds NO emission logic of its own — the CLI and the API must
not be able to disagree about what basalt's tokens are (`src/cli/tokens-css.test.ts`). This is the
command that removes the install entirely for a static, non-React consumer.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document --only <core|all>.

The CLI supports --only, but this public flag list omits it.

🤖 Prompt for 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.

In `@packages/basalt-ui/CLAUDE.md` around lines 351 - 356, Update the `tokens:css`
flag documentation to include the supported `--only <core|all>` option alongside
the existing flags, without changing the command’s behavior or other documented
details.

Comment on lines +199 to +207
"@mantine/core": {
"optional": true
},
"@mantine/form": {
"optional": true
},
"@mantine/hooks": {
"optional": true
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Restore the required Mantine peer contract. Marking @mantine/core and @mantine/hooks optional violates the package policy and makes the related framework-free documentation inaccurate. As per coding guidelines, both must remain non-optional peer dependencies.

  • packages/basalt-ui/package.json#L199-L207: remove optional metadata for @mantine/core and @mantine/hooks.
  • docs/FRAMEWORK-FREE.md#L19-L26: revise the “every peer is optional” and no-peer-install claims.
  • docs/STATUS.md#L282-L284: describe only the peers that are actually optional.
  • packages/basalt-ui/CLAUDE.md#L112-L122: update the peer dependency contract and missing-peer behavior documentation.
📍 Affects 4 files
  • packages/basalt-ui/package.json#L199-L207 (this comment)
  • docs/FRAMEWORK-FREE.md#L19-L26
  • docs/STATUS.md#L282-L284
  • packages/basalt-ui/CLAUDE.md#L112-L122
🤖 Prompt for 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.

In `@packages/basalt-ui/package.json` around lines 199 - 207, Restore the required
Mantine peer contract: in packages/basalt-ui/package.json lines 199-207, remove
optional metadata from `@mantine/core` and `@mantine/hooks`. Update
docs/FRAMEWORK-FREE.md lines 19-26 to remove the every-peer-optional and
no-peer-install claims, revise docs/STATUS.md lines 282-284 to mention only
genuinely optional peers, and update packages/basalt-ui/CLAUDE.md lines 112-122
to document the required Mantine peers and missing-peer behavior.

Source: Coding guidelines

Comment on lines +164 to +175
/* Print: hide the BasaltShell chrome so only page content prints.
Scoped to AppShell's own class hooks, NOT to bare nav/header/footer. The element form matched
any landmark on the page — a consumer with a real <nav> and no BasaltShell silently printed a
blank one, and being unlayered with `!important` there was nothing they could do about it
either. `@layer basalt` already outranks `@layer mantine`, so no `!important` is needed to win
against the AppShell's own display rules. */
@media print {
.mantine-AppShell-navbar,
.mantine-AppShell-header,
.mantine-AppShell-footer {
display: none;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep the scoped print rule effective for unlayered Mantine imports.

Removing !important from the layered rule makes AppShell chrome printable when Mantine CSS is imported unlayered; the tests currently enforce that broken behavior.

  • packages/basalt-ui/src/styles.css#L164-L175: retain a scoped important override or provide an equivalent unlayered print rule.
  • packages/basalt-ui/tests/styles-css.test.ts#L46-L48: stop requiring the print block to contain no !important.
  • packages/basalt-ui/tests/styles-css.test.ts#L58-L63: replace the global count assertion with a selector-scoped assertion.
📍 Affects 2 files
  • packages/basalt-ui/src/styles.css#L164-L175 (this comment)
  • packages/basalt-ui/tests/styles-css.test.ts#L46-L48
  • packages/basalt-ui/tests/styles-css.test.ts#L58-L63
🤖 Prompt for 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.

In `@packages/basalt-ui/src/styles.css` around lines 164 - 175, Keep the scoped
AppShell print selectors in the `@media` print block effective against unlayered
Mantine imports by retaining a scoped !important override or adding an
equivalent unlayered print rule. In packages/basalt-ui/tests/styles-css.test.ts
at lines 46-48, remove the assertion that the print block contains no
!important; at lines 58-63, replace the global !important count check with an
assertion scoped to the AppShell print selectors.

Comment on lines +40 to +44
it('sits inside @layer basalt, so a consumer can override it', () => {
const layerStart = css.indexOf('@layer basalt {')
expect(layerStart).toBeGreaterThanOrEqual(0)
expect(css.indexOf('@media print')).toBeGreaterThan(layerStart)
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert actual layer nesting, not merely source order.

css.indexOf('@media print') > layerStart also passes if the media block appears after @layer basalt has already closed. Use a brace-aware check or a scoped pattern that proves the print rule is contained by the @layer basalt block.

🤖 Prompt for 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.

In `@packages/basalt-ui/tests/styles-css.test.ts` around lines 40 - 44, Update the
test around the layerStart assertion in the `sits inside `@layer` basalt` case to
verify that `@media print` is structurally nested within the `@layer basalt`
block, using brace-aware parsing or a scoped pattern rather than only comparing
source positions.

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