···2727- 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.
2828- Committed `6299973` — `fix(storybook): use asChild to prevent button-in-button nesting`.
2929- **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).
3030-- 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.
3030+- 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.
3131+ - **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.**
3232+ - **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.
3333+ - **Verified via Playwright** on all 4 SuedeButton stories with computed-style + stylesheet introspection:
3434+ - **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.
3535+ - **Disabled**: same blue, but `cursor: not-allowed` — the `:disabled`/`:[aria-disabled]` rule fired.
3636+ - **As link**: renders as `<a>` (polymorphic), all the same styles.
3737+ - **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).
3838+- 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.
31393240## Files touched
3341- `src/lib/components/ui/SuedeButton.svelte` — new (52 lines)
3442- `src/stories/SuedeButton.stories.svelte` — new (36 lines)
35433644## Verification
3737-- `pnpm check` — **0 errors / 0 warnings project-wide** (after the story-file cleanup).
4545+- `pnpm check` — **0 errors / 0 warnings project-wide**.
3846- `pnpm prettier --check` on new files — pass
3947- `pnpm eslint` on new files — pass
4048- `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.
4141-- `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`
4949+- `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.
5050+- `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`
42514352## Follow-ups / stubs
4453- **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.
···4958- **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.
5059- **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.
5160- **`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.
6161+- **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.