Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .agents/skills/webjs/references/styling.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,19 @@ The default stack is a static compiled Tailwind stylesheet (`css:build` compiles

**Two halves.** (1) `public/input.css` MAPS token names into Tailwind with `@theme inline` (`--color-background: var(--background)`), so `bg-background` resolves to `var(--background)`. That is infrastructure; leave it. (2) The root layout (`app/layout.ts`) DEFINES the values as plain CSS custom properties in a `<style>` block. That is your palette; make it your own. A freshly cleared app (after `npm run gallery:clear`) ships only the OS system-colour base (`Canvas` / `CanvasText`) with NO tokens, so building this palette is your first styling step.

**`@theme` and `@theme inline` differ in whether the token reaches `:root`, and the difference is silent.** Measured on `tailwindcss@4.3.0`, a token mapped in a theme block is emitted as a real `:root` custom property when:
Comment thread
vivek7405 marked this conversation as resolved.

| block | token used only through a utility (`border-border`) | token written as a raw `var(--color-x)` in any SCANNED file | token unused |
|---|---|---|---|
| `@theme` | emitted | emitted | not emitted |
| `@theme inline` | NOT emitted (the value is substituted into the utility) | emitted | not emitted |

The one cell that bites is `inline` plus utility-only usage. Nothing on the page can then inherit `--color-x`, so a raw `var(--color-x)` written somewhere Tailwind never scanned resolves to nothing and the declaration falls back to its initial value (a border or outline silently becomes `currentColor`).

"Scanned" is wider than it looks, and this is the part worth knowing: Tailwind scans source files as raw text, so a `var(--color-ring)` inside a component's `static styles` template DOES count and forces emission, exactly like one in the stylesheet. That is why the `@webjsdev/ui` kit theme works despite using `inline`. So the rule is not "shadow components need a plain `@theme`". It is: **if a token is only ever used through utilities, and something outside the scanned source needs to inherit it, map that token with a plain `@theme`.** Anything under a configured `@source` is scanned and needs no special handling.

Whichever form you use, a token nothing references is dropped in both, so an unused mapping is dead configuration rather than a safety net.

**Light and dark, defined once (DRY).** Write each colour token ONE time with the native CSS `light-dark(LIGHT, DARK)` function and let `color-scheme` pick the side. The default `color-scheme: light dark` follows the OS; a `[data-theme]` attribute forces one. No duplicated light/dark blocks:

```html
Expand Down
2 changes: 1 addition & 1 deletion framework-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ gates that bind every agent are `.hooks/pre-commit` and CI (#1372).

The scaffold is webjs's primary teaching surface for AI agents, so a new framework feature must ship a runnable gallery demo, not just a doc bullet.

**The gallery lives ONCE, at the repo root `gallery/`,** as a live workspace app you boot (`npm run dev:gallery`, port 5005) and test (`npm test --workspace=@webjsdev/gallery`) like `website` and `examples/blog`, so a framework change is validated against the demos instead of rotting in un-executed template files. `packages/cli`'s `prepack` bundles it into `packages/cli/templates/gallery/` for the npm tarball (`scripts/sync-scaffold-gallery.mjs`) and `postpack` deletes that copy, which is gitignored: never edit or commit it. Four files in `gallery/` exist only because it is a runnable app (`app/layout.ts`, `app/page.ts`, `components/theme-toggle.ts`, `lib/utils/cn.ts`); the generator writes its own, so both the copy and the bundle filter them through `packages/cli/lib/gallery-shell-files.js`.
**The gallery lives ONCE, at the repo root `gallery/`,** as a live workspace app you boot (`npm run dev:gallery`, port 5005) and test (`npm test --workspace=@webjsdev/gallery`) like `website` and `examples/blog`, so a framework change is validated against the demos instead of rotting in un-executed template files. `packages/cli`'s `prepack` bundles it into `packages/cli/templates/gallery/` for the npm tarball (`scripts/sync-scaffold-gallery.mjs`) and `postpack` deletes that copy, which is gitignored: never edit or commit it. Four files in `gallery/` exist only because it is a runnable app (`app/layout.ts`, `app/page.ts`, `components/theme-toggle.ts`, `lib/utils/cn.ts`); the generator writes its own, so both the copy and the bundle filter them through `packages/cli/lib/gallery-shell-files.js`. All of `gallery/public/` is gallery-only as well, since both copiers iterate only `app`, `modules`, `test`, `components` and `lib`, which is why the gallery's brand assets and its hand-written `input.css` live there. `test/repo-health/gallery-payload-boundary.test.mjs` guards the whole boundary, and it asserts the mechanism that actually holds it: the generator writes its own shell files AFTER `copyGallery()` runs, so they overwrite the copy, and the filter is defence-in-depth rather than the load-bearing part.

Enforcement is two tiers, mirroring how tests are enforced:

Expand Down
21 changes: 21 additions & 0 deletions gallery/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,27 @@ scaffolded app.

Everything else in those five directories IS payload and ships verbatim.

**`public/` is gallery-only too, and is the other half of the boundary.** Both
copiers iterate `app`, `modules`, `test`, `components` and `lib`, so nothing
under `public/` is ever read. That is where this app's brand assets live (the
lockup SVGs in `public/brand/`, the self-hosted woff2 files in `public/fonts/`,
and the hand-written `public/input.css`), and it is why they can exist at all
without reaching a generated app.

**A brand helper belongs INLINE in a shell file, never in a new module.** The
reflex is to mirror the website, which keeps its logo in
`website/lib/design/brand.ts` and its footer in `website/lib/ui/site-footer.ts`.
Both of those paths are payload here, so either one would ship the WebJs mark
into every generated app. The header lockup and the footer are written inline in
`app/layout.ts` for exactly that reason.

The boundary is now GUARDED rather than only documented, by
`test/repo-health/gallery-payload-boundary.test.mjs`. Note what actually holds
it: the generator writes its own `app/layout.ts`, `app/page.ts` and
`components/theme-toggle.ts` AFTER `copyGallery()` runs, so those overwrite
whatever the copy left. The shell-file filter is defence-in-depth on top of
that write order, not the mechanism itself.

## Rules for a demo

- **One concept per card.** A demo under `app/features/<name>/` shows a single
Expand Down
Loading
Loading