a template starter repo for sveltekit projects
0

Configure Feed

Select the types of activity you want to include in your feed.

docs(agent-notes): record stylebase + :global() fix and the two non-obvious Svelte/Storybook traps it surfaced

+13 -3
+13 -3
agent-notes/2026-06-04-03-suede-button-scaffold.md
··· 27 27 - Committed `c7c153b` — `feat(ui): scaffold SuedeButton (bits-ui + stylebase wrapper)`. Unstaged the pre-existing staged files (`.github/workflows/cleanup-decision-graphs.yml`, `.opencode/commands/serve-ui.md`) so the commit diff is exactly the two new files; the user can commit those separately. 28 28 - Committed `6299973` — `fix(storybook): use asChild to prevent button-in-button nesting`. 29 29 - **Follow-up unblock: removed `src/stories/Button.stories.svelte` and `src/stories/Accordion.stories.svelte`** (committed `bbdd827`). The user's working tree already had `src/lib/components/ui/{Button,Accordion,index}.{svelte,spec.ts}` deleted locally, which made the two stories' `$lib/components/ui/{Button,Accordion}.svelte` imports unresolvable. Vite's import-analysis plugin then failed at build time on those files, blocking Storybook entirely. The same `asChild` issue from the previous fix would have hit these stories the moment the broken imports were repaired (their `component: Button` + children-in-Story pattern had the same button-in-button structure). `src/stories/Page.stories.svelte` and `src/stories/Header.stories.svelte` were kept — their imports are local (`./Page.svelte`, `./Header.svelte`) and still resolve. **Verified via Playwright across all 6 surviving stories (4 SuedeButton + 2 examples): 0 console errors, 0 vite import-analysis errors, 0 nested buttons.** `pnpm check` is now **fully clean** (0 errors, 0 warnings project-wide — first time in the history of this branch). 30 - - Created deciduous outcome node 70 with `--commit HEAD`; linked to action nodes 68 (wrapper), 69 (story), 71 (asChild fix). Action node 72 + outcome 73 added for the story-file cleanup, all linked to goal 67. 30 + - Committed `b718ad0` — `fix(ui): import stylebase globally; wrap SuedeButton styles in :global()`. Two-part fix for the user's "global stylebase is not being imported" report. 31 + - **Part 1: `.storybook/preview.ts`** — added `import '../src/app.css'`. The SvelteKit app loaded stylebase via `+layout.svelte` → `app.css`, but Storybook's preview iframe is a separate document; that import chain didn't reach it. Importing `app.css` in `preview.ts` brings stylebase (and the `:focus-visible` rule) into both surfaces. **Caught the bug by reading `--hue-blue-500` at `:root` in the iframe before the fix — empty string.** 32 + - **Part 2: `src/lib/components/ui/SuedeButton.svelte`** — wrapped every `.suede-button` selector in `:global(...)`. The class is set as a prop on the child `<Button.Root>` (not as a class on SuedeButton's own template element). Svelte 5's per-component CSS scoping only applies to elements rendered by that component, so the rule was being **pruned from the bundle entirely** (0 selectors named `suede-button` in any stylesheet, even with the `<!-- svelte-ignore css_unused_selector -->` comment — svelte-ignore suppresses the warning but doesn't prevent the build-time strip). `:global()` escapes the scoping. Class name is unique enough that global leakage is safe. 33 + - **Verified via Playwright** on all 4 SuedeButton stories with computed-style + stylesheet introspection: 34 + - **Primary**: bg=oklch(.7188 .15 240) [stylebase blue], white text, 6.75/17.67px padding [stylebase scale], 6px radius, system-ui font [var(--ff-ui)], 600 weight, inline-flex, pointer cursor. 35 + - **Disabled**: same blue, but `cursor: not-allowed` — the `:disabled`/`:[aria-disabled]` rule fired. 36 + - **As link**: renders as `<a>` (polymorphic), all the same styles. 37 + - **With class override**: bg=oklch(.7109 .15 140) [emerald — different hue, the inline `style=` override worked], font-size=20.1px (the `u:fs-3` override beat `u:fs-1`), class string = `suede-button u:fs-1 u:fs-3` (merge order correct). 38 + - Created deciduous outcome node 70 with `--commit HEAD`; linked to action nodes 68 (wrapper), 69 (story), 71 (asChild fix), 72 + outcome 73 (story-file cleanup), action 74 + outcome 75 (stylebase + :global fix), all linked to goal 67. 31 39 32 40 ## Files touched 33 41 - `src/lib/components/ui/SuedeButton.svelte` — new (52 lines) 34 42 - `src/stories/SuedeButton.stories.svelte` — new (36 lines) 35 43 36 44 ## Verification 37 - - `pnpm check` — **0 errors / 0 warnings project-wide** (after the story-file cleanup). 45 + - `pnpm check` — **0 errors / 0 warnings project-wide**. 38 46 - `pnpm prettier --check` on new files — pass 39 47 - `pnpm eslint` on new files — pass 40 48 - `pnpm storybook --port 6006` + Playwright DOM inspection — all 4 SuedeButton stories render a single root-level button (or anchor, for "As link"); no nested-button-in-button or anchor-in-button anywhere in the story canvas. Both remaining example stories (`example-header`, `example-page`) compile cleanly. Zero console errors across all 6 URLs. 41 - - `git log -3` — `c7c153b feat(ui): scaffold SuedeButton (bits-ui + stylebase wrapper)` + `6299973 fix(storybook): use asChild to prevent button-in-button nesting` + `bbdd827 fix(storybook): remove broken Button/Accordion story files` on `feat/install-bits-ui-stylebase` 49 + - `pnpm storybook --port 6006` + Playwright computed-style + stylesheet introspection — `--hue-blue-500` resolves to `oklch(71.88% 0.15 240)` at `:root` in the preview iframe; the four `.suede-button` rules are present in the bundle; the Primary button computes to the expected blue/white/6px-radius/system-ui styling; Disabled correctly shows `cursor: not-allowed`; As link renders as `<a>`; With-class-override shows the consumer's emerald background and `u:fs-3` font-size override. 50 + - `git log -4` — `c7c153b feat(ui): scaffold SuedeButton (bits-ui + stylebase wrapper)` + `6299973 fix(storybook): use asChild to prevent button-in-button nesting` + `bbdd827 fix(storybook): remove broken Button/Accordion story files` + `b718ad0 fix(ui): import stylebase globally; wrap SuedeButton styles in :global()` on `feat/install-bits-ui-stylebase` 42 51 43 52 ## Follow-ups / stubs 44 53 - **Pre-existing deleted files in working tree (not committed)**: `src/lib/components/ui/{Accordion,Button,index}.{svelte,spec.ts}` and the matching `spec.ts` files are deleted locally but uncommitted. The user can `git rm` them in a separate commit when ready; they were not staged in the current cleanup commit because the user had not asked for that scope. The story-file removal (`bbdd827`) is the only cleanup committed here. ··· 49 58 - **The `u:fs-1` utility is the only stylebase utility class used in the component** — the rest of the visual styling is in scoped CSS. If a future "Hybrid: global data-attribute CSS + per-component overrides" option (see deciduous node 64, the deferred revisit from the 341d364 commit) gets picked up, the mix here is the proof of concept. 50 59 - **Story variants are intentionally minimal** — no `play` interaction tests, no `decorators` for surrounding layout. Add those when the user wants to exercise click handlers / hover states / responsive behavior. 51 60 - **`asChild` is a real storybook pattern, not a workaround for a bug in our code** — it's the addon's intended mode for static stories. Worth knowing for future stories: any time the story's children IS the component instance (rather than the children being a separate prop-driven template), `asChild` is the right choice. If a future story needs args-driven controls to update the canvas live, switch to a `template` snippet instead. 61 + - **Two non-obvious traps for any future Suede wrapper component**: (1) Storybook's preview iframe doesn't share the SvelteKit app's `+layout.svelte` import chain, so any global stylesheet must be re-imported in `.storybook/preview.ts`. (2) Svelte 5's CSS scoping is strictly per-component — a `.foo` rule only applies if the class is on an element rendered by THIS component's template, not on a class prop passed to a child component. If a future wrapper passes its styling class to a child primitive (the common pattern with bits-ui), the rules must be wrapped in `:global(...)`. The `<!-- svelte-ignore css_unused_selector -->` comment does **not** prevent the build-time strip; it only suppresses the warning.