Commits
The task-note lifecycle (AGENTS.md task-lifecycle skill) requires
an entry per completed task; these were authored at the time of
their respective work but never committed. Closing the loop:
- 2026-06-03-03: chronver release process
- 2026-06-04-01: remove Claude-Code-specific artifacts from
deciduous install
- 2026-06-04-02: build the deciduous decision graph for Suede
from commit history
Plus the plan artifact for the chronver task
(agent-notes/plans/add-suede-release-process-chronver.md) —
plan files are write-only at task end per the skill, so this
is the canonical place for it.
- .opencode/agents/deciduous.md: specialist agent for
deciduous decision-graph operations.
- .opencode/commands/*.md (10 files): slash-command wrappers
for the most common workflows (work, build, document,
decision-graph, decision, recover, serve-ui, sync, sync-graph,
build-test).
- .opencode/plugins/*.ts (3 files): lifecycle hooks —
post-commit-reminder, require-action-node (Edit/Write
guard), version-check.
- .opencode/skills/{archaeology,narratives,pulse}/SKILL.md:
in-repo skills for narrative-driven decision-graph work.
- .opencode/tools/deciduous.ts: thin TS tool wrapper for the
deciduous CLI used by the agents/commands above.
- opencode.json: project-level opencode config (model,
agent list, MCP servers, etc.).
Also includes a stray prettier reformat of .storybook/preview.ts
(the import chain was unchanged; only whitespace/indentation
differed from the version committed in b718ad0).
Excluded: .opencode/.gitignore (workspace-internal config that
self-excludes via its own last line), .opencode/node_modules/,
.opencode/package.json, .opencode/package-lock.json
(gitignored at the repo root by the prior commit).
The .opencode directory is a pnpm-managed workspace (its own
internal .opencode/.gitignore covers node_modules + lockfiles
for the pnpm tooling). Git only reads the root .gitignore for
untracked-detection, so add the corresponding entries here to
keep 'git status' clean for downstream contributors.
The tracked .opencode/ contents (agents, commands, plugins,
skills, tools, and the .opencode/.gitignore itself) are
configuration and ship with the repo.
- .github/workflows/cleanup-decision-graphs.yml: scheduled (and
manual-dispatch) cleanup of stale deciduous event log files.
Companion to the deciduous sync export at docs/graph-data.json.
- .github/workflows/deploy-pages.yml: builds the Storybook
static site (pnpm build-storybook) and deploys storybook-static/
to GitHub Pages. This is what makes the SuedeButton docs
(and any future Storybook) browsable from the public web.
Neither workflow has been exercised against the live repo yet;
add a PR for the deploy workflow to fire on a tag or manual
dispatch only.
One-line content edit; the SvelteKit default 'Welcome to SvelteKit'
page no longer reflects the project's identity. Now reads
'<h1>suede</h1><p>a template repo.</p>'. No functional change;
the +page.svelte component is otherwise unchanged.
Removes the auto-generated example Button/Accordion wrappers and
their Vitest specs that were scaffolded in 341d364
('feat: install bits-ui and stylebase, scaffold UI wrappers') but
never replaced with real components. The matching story files
that referenced them were removed in bbdd827 (broken imports
were blocking Storybook's vite import-analysis).
What's left in src/lib/components/ui/ is the new SuedeButton
wrapper (added in c7c153b) — the only wrapper the project
currently ships. Future wrappers should be added here as
they're built.
The SuedeButton wrapper was applying the same .suede-button styles
to both the <button> and <a> renderings of bits-ui's polymorphic
Button.Root. By design, an anchor acting as a button (e.g. a
'Subscribe' link that navigates to a checkout page) should look
like a button — but a literal <a href> link should look like a
link. Until now the two were visually indistinguishable.
Added a :global(.suede-button[href]) rule that overrides the
button styling when bits-ui renders an anchor (it sets the
href attribute on the underlying element):
background-color: transparent
color: var(--hue-blue-500)
padding-inline: 0
text-decoration: underline
text-decoration-thickness: from-font
text-underline-offset: 0.2em
plus a :hover variant that darkens the color to --hue-blue-600.
The existing :hover and :active rules are now scoped with
:not([href]) so they only apply to the button variant — without
that, their higher-specificity background-color: var(--hue-blue-600)
would have re-painted the link blue on hover, defeating the
transparent background.
The user also renamed the destructured class prop from `klass`
to `className` (React convention) and updated the
'With class override' story to pass `class="u:fs-1"` instead
of `u:fs-3`, so the demo now isolates the inline-style
background override (emerald) without compounding it with a
font-size change.
Verified via Playwright computed-style inspection on all 4 stories:
Primary bg=oklch blue, white text, no underline (button)
Disabled same blue, cursor=not-allowed (button)
As link bg=transparent, blue text, underlined (link!)
With class override bg=oklch emerald, white text, no underline (button, color override)
pnpm check still 0 errors / 0 warnings project-wide.
Two-part fix for the same observable bug: SuedeButton rendered
with browser-default styles in Storybook (gray bg, black text,
0 padding, Arial font) because the stylebase custom properties
(--hue-blue-500, --space-*, --ff-ui, etc.) and utility classes
(u:fs-1) were never present in the Storybook preview iframe.
Part 1 — .storybook/preview.ts:
Import `../src/app.css` so the Storybook preview iframe gets
the same global stylesheet as the SvelteKit app (stylebase +
the :focus-visible rule). Storybook's preview iframe is
independent of the SvelteKit app's +layout.svelte, so
SvelteKit's import chain didn't reach it.
Part 2 — src/lib/components/ui/SuedeButton.svelte:
Wrap every .suede-button selector in :global(...). The class
is set as a prop on the child <Button.Root> component, 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 — the .suede-button rule was being pruned
(0 selectors in the bundle) even with the svelte-ignore
comment, because svelte-ignore suppresses the warning but
doesn't prevent the build-time strip.
:global() escapes the scoping. The `suede-button` class name
is unique enough that global leakage is safe.
Verified via Playwright on all 4 SuedeButton stories + style
introspection (computed styles + styleSheets inspection):
Primary bg=oklch(.7188 .15 240) [blue] cursor=pointer
Disabled same blue, cursor=not-allowed
As link renders <a>, cursor=pointer
With class override bg=oklch(.7109 .15 140) [emerald], font-size=20.1px
(u:fs-3 override of u:fs-1), class= 'suede-button u:fs-1 u:fs-3'
All custom properties now resolve at :root, all .suede-button
rules present in the bundle, pnpm check still 0/0 project-wide.
`src/stories/Button.stories.svelte` and `src/stories/Accordion.stories.svelte`
both imported `$lib/components/ui/{Button,Accordion}.svelte` which no longer
exist on this branch (deleted in working tree). The deletion left the
stories with unresolvable imports, and Storybook's vite import-analysis
fails at build time on those files (the same asChild-related issue
surfaced earlier would have hit if the broken imports were repaired).
`src/stories/Page.stories.svelte` and `src/stories/Header.stories.svelte`
are kept: their imports are local (`./Page.svelte`, `./Header.svelte`)
and still resolve.
Verified via Playwright across 6 story URLs (all 4 SuedeButton + the two
remaining examples): 0 console errors, 0 vite import-analysis errors.
Only warnings are Storybook 11 deprecations on Storybook's internal UI
(`ariaLabel` on its own Button/PopoverProvider), not user code.
`pnpm check` is now fully clean (0 errors, 0 warnings) — no more
pre-existing noise from the deleted stories.
@storybook/addon-svelte-csf's Story.svelte, when given both a
`component` in defineMeta and children in the <Story> block, wraps
the children in another instance of that component
(line 148: `<renderer.storyContext.component {...renderer.args} {children} />`).
In our case `component: SuedeButton` + `<SuedeButton>Click me</SuedeButton>`
as Story children produced `<button class="suede-button"><button class="suede-button">Click me</button></button>`
— an accessibility violation (interactive element nested in interactive element)
and semantically wrong markup.
`asChild` on each <Story> routes the children through the
`{@render children()}` branch (no component wrapping) so the story
renders as static markup. Trade-off: args-driven controls don't
update the canvas for `asChild` stories, which is acceptable for
discrete-variant learning demos.
Verified via Playwright against the running Storybook dev server:
Primary / Disabled / With-class-override each render exactly one
`<button data-button-root="true" class="suede-button ...">` at the
root of the story canvas; As link renders as a single
`<a data-button-root="true">`. `button button` and `a button`
nested-selector counts: 0 in every story.
Adds a Suede-wrapped bits-ui Button as a learning scaffold for the
bits-ui + stylebase + Svelte 5 pattern. Distinguishes functionality
(bits-ui's polymorphic Button.Root via ButtonRootProps) from style
(stylebase utility class + scoped <style> block in the same file).
Story at src/stories/SuedeButton.stories.svelte exercises four
variants: Primary, Disabled, As link, With class override.
TypeScript escape hatch: rest is spread via
`{...rest as Record<string, unknown>}` because bits-ui's
ButtonRootProps is a union (AnchorElement | ButtonElement) that
exceeds TypeScript's type complexity budget when re-spread into
another Svelte component. Cast is local to one line; polymorphic
behavior (button vs anchor via href) still works at runtime.
CSS pruner workaround: `<!-- svelte-ignore css_unused_selector -->`
on the <style> block, because the class name is built from a
template literal in the class attribute and is therefore invisible
to Svelte's static class-extraction pass.
Existing example stories (Button, Accordion, Header, Page) are
left untouched per option C; pre-existing pnpm check errors
(missing /components/ui/Button.svelte and Accordion.svelte
references) are out of scope for this change.
- .deciduous/ : local decision-graph database
- docs/ : generated web viewer export (regenerate via 'deciduous sync')
- CLAUDE.md : explicit Claude-Code instruction file, not part of this project
- .claude/ : explicit Claude-Code config directory, not part of this project
The last two are defensive: if 'deciduous install' re-runs, the Claude
artifacts will be re-created locally but stay untracked.
Cross-agent view of the deciduous decision-graph tool. Source content was
copied from CLAUDE.md's managed block (which we will not commit) and stripped
of tool-specific syntax:
- Removed slash command table (Claude Code only)
- Removed skills table (Claude Code only)
- Removed inline slash-command references
- Replaced the .claude/commands/decision.md git-staging example with
project-relevant paths
A header note at the top of the section explains that this is the
tool-agnostic view; tool-specific commands and skills live in
.opencode/commands/ and .opencode/skills/ (loaded at runtime by OpenCode,
not duplicated here).
First suede release. Bump placeholder 0.0.1 -> 2026.6.4 (chronver, today's date).
Tag 2026.6.4 will be applied to the merge commit on main after this PR merges.
- chronver for apps and templates, semver for packages
- release flow: bump-on-merge, bare chronver tag, --follow-tags
- downstream lineage via suede.from field in project package.json
- pnpm add @taurean/stylebase bits-ui
- src/app.css imports stylebase and provides global focus ring
- +layout.svelte wires app.css
- src/lib/components/ui/Button.svelte and Accordion.svelte are
Svelte 5 + bits-ui wrappers (script-only agent territory)
- Storybook stories for both wrappers
- vitest-browser-svelte smoke tests for both wrappers
- .gitignore: worker-configuration.d.ts (wrangler build artifact)
Wrapper styling is intentionally minimal — human territory per
AGENTS.md. The wrappers render with stylebase defaults; the
hybrid strategy (global data-attribute CSS + per-component
overrides) is the next step.
Refs agent-notes/2026-06-03-02-install-bits-ui-stylebase.md
- AGENTS.md: add Git workflow section (branch from main, PR review, no direct push)
- AGENTS.md: trim stack line to technologies only
- task-lifecycle skill: add Before starting section for branch setup
The task-note lifecycle (AGENTS.md task-lifecycle skill) requires
an entry per completed task; these were authored at the time of
their respective work but never committed. Closing the loop:
- 2026-06-03-03: chronver release process
- 2026-06-04-01: remove Claude-Code-specific artifacts from
deciduous install
- 2026-06-04-02: build the deciduous decision graph for Suede
from commit history
Plus the plan artifact for the chronver task
(agent-notes/plans/add-suede-release-process-chronver.md) —
plan files are write-only at task end per the skill, so this
is the canonical place for it.
- .opencode/agents/deciduous.md: specialist agent for
deciduous decision-graph operations.
- .opencode/commands/*.md (10 files): slash-command wrappers
for the most common workflows (work, build, document,
decision-graph, decision, recover, serve-ui, sync, sync-graph,
build-test).
- .opencode/plugins/*.ts (3 files): lifecycle hooks —
post-commit-reminder, require-action-node (Edit/Write
guard), version-check.
- .opencode/skills/{archaeology,narratives,pulse}/SKILL.md:
in-repo skills for narrative-driven decision-graph work.
- .opencode/tools/deciduous.ts: thin TS tool wrapper for the
deciduous CLI used by the agents/commands above.
- opencode.json: project-level opencode config (model,
agent list, MCP servers, etc.).
Also includes a stray prettier reformat of .storybook/preview.ts
(the import chain was unchanged; only whitespace/indentation
differed from the version committed in b718ad0).
Excluded: .opencode/.gitignore (workspace-internal config that
self-excludes via its own last line), .opencode/node_modules/,
.opencode/package.json, .opencode/package-lock.json
(gitignored at the repo root by the prior commit).
The .opencode directory is a pnpm-managed workspace (its own
internal .opencode/.gitignore covers node_modules + lockfiles
for the pnpm tooling). Git only reads the root .gitignore for
untracked-detection, so add the corresponding entries here to
keep 'git status' clean for downstream contributors.
The tracked .opencode/ contents (agents, commands, plugins,
skills, tools, and the .opencode/.gitignore itself) are
configuration and ship with the repo.
- .github/workflows/cleanup-decision-graphs.yml: scheduled (and
manual-dispatch) cleanup of stale deciduous event log files.
Companion to the deciduous sync export at docs/graph-data.json.
- .github/workflows/deploy-pages.yml: builds the Storybook
static site (pnpm build-storybook) and deploys storybook-static/
to GitHub Pages. This is what makes the SuedeButton docs
(and any future Storybook) browsable from the public web.
Neither workflow has been exercised against the live repo yet;
add a PR for the deploy workflow to fire on a tag or manual
dispatch only.
Removes the auto-generated example Button/Accordion wrappers and
their Vitest specs that were scaffolded in 341d364
('feat: install bits-ui and stylebase, scaffold UI wrappers') but
never replaced with real components. The matching story files
that referenced them were removed in bbdd827 (broken imports
were blocking Storybook's vite import-analysis).
What's left in src/lib/components/ui/ is the new SuedeButton
wrapper (added in c7c153b) — the only wrapper the project
currently ships. Future wrappers should be added here as
they're built.
The SuedeButton wrapper was applying the same .suede-button styles
to both the <button> and <a> renderings of bits-ui's polymorphic
Button.Root. By design, an anchor acting as a button (e.g. a
'Subscribe' link that navigates to a checkout page) should look
like a button — but a literal <a href> link should look like a
link. Until now the two were visually indistinguishable.
Added a :global(.suede-button[href]) rule that overrides the
button styling when bits-ui renders an anchor (it sets the
href attribute on the underlying element):
background-color: transparent
color: var(--hue-blue-500)
padding-inline: 0
text-decoration: underline
text-decoration-thickness: from-font
text-underline-offset: 0.2em
plus a :hover variant that darkens the color to --hue-blue-600.
The existing :hover and :active rules are now scoped with
:not([href]) so they only apply to the button variant — without
that, their higher-specificity background-color: var(--hue-blue-600)
would have re-painted the link blue on hover, defeating the
transparent background.
The user also renamed the destructured class prop from `klass`
to `className` (React convention) and updated the
'With class override' story to pass `class="u:fs-1"` instead
of `u:fs-3`, so the demo now isolates the inline-style
background override (emerald) without compounding it with a
font-size change.
Verified via Playwright computed-style inspection on all 4 stories:
Primary bg=oklch blue, white text, no underline (button)
Disabled same blue, cursor=not-allowed (button)
As link bg=transparent, blue text, underlined (link!)
With class override bg=oklch emerald, white text, no underline (button, color override)
pnpm check still 0 errors / 0 warnings project-wide.
Two-part fix for the same observable bug: SuedeButton rendered
with browser-default styles in Storybook (gray bg, black text,
0 padding, Arial font) because the stylebase custom properties
(--hue-blue-500, --space-*, --ff-ui, etc.) and utility classes
(u:fs-1) were never present in the Storybook preview iframe.
Part 1 — .storybook/preview.ts:
Import `../src/app.css` so the Storybook preview iframe gets
the same global stylesheet as the SvelteKit app (stylebase +
the :focus-visible rule). Storybook's preview iframe is
independent of the SvelteKit app's +layout.svelte, so
SvelteKit's import chain didn't reach it.
Part 2 — src/lib/components/ui/SuedeButton.svelte:
Wrap every .suede-button selector in :global(...). The class
is set as a prop on the child <Button.Root> component, 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 — the .suede-button rule was being pruned
(0 selectors in the bundle) even with the svelte-ignore
comment, because svelte-ignore suppresses the warning but
doesn't prevent the build-time strip.
:global() escapes the scoping. The `suede-button` class name
is unique enough that global leakage is safe.
Verified via Playwright on all 4 SuedeButton stories + style
introspection (computed styles + styleSheets inspection):
Primary bg=oklch(.7188 .15 240) [blue] cursor=pointer
Disabled same blue, cursor=not-allowed
As link renders <a>, cursor=pointer
With class override bg=oklch(.7109 .15 140) [emerald], font-size=20.1px
(u:fs-3 override of u:fs-1), class= 'suede-button u:fs-1 u:fs-3'
All custom properties now resolve at :root, all .suede-button
rules present in the bundle, pnpm check still 0/0 project-wide.
`src/stories/Button.stories.svelte` and `src/stories/Accordion.stories.svelte`
both imported `$lib/components/ui/{Button,Accordion}.svelte` which no longer
exist on this branch (deleted in working tree). The deletion left the
stories with unresolvable imports, and Storybook's vite import-analysis
fails at build time on those files (the same asChild-related issue
surfaced earlier would have hit if the broken imports were repaired).
`src/stories/Page.stories.svelte` and `src/stories/Header.stories.svelte`
are kept: their imports are local (`./Page.svelte`, `./Header.svelte`)
and still resolve.
Verified via Playwright across 6 story URLs (all 4 SuedeButton + the two
remaining examples): 0 console errors, 0 vite import-analysis errors.
Only warnings are Storybook 11 deprecations on Storybook's internal UI
(`ariaLabel` on its own Button/PopoverProvider), not user code.
`pnpm check` is now fully clean (0 errors, 0 warnings) — no more
pre-existing noise from the deleted stories.
@storybook/addon-svelte-csf's Story.svelte, when given both a
`component` in defineMeta and children in the <Story> block, wraps
the children in another instance of that component
(line 148: `<renderer.storyContext.component {...renderer.args} {children} />`).
In our case `component: SuedeButton` + `<SuedeButton>Click me</SuedeButton>`
as Story children produced `<button class="suede-button"><button class="suede-button">Click me</button></button>`
— an accessibility violation (interactive element nested in interactive element)
and semantically wrong markup.
`asChild` on each <Story> routes the children through the
`{@render children()}` branch (no component wrapping) so the story
renders as static markup. Trade-off: args-driven controls don't
update the canvas for `asChild` stories, which is acceptable for
discrete-variant learning demos.
Verified via Playwright against the running Storybook dev server:
Primary / Disabled / With-class-override each render exactly one
`<button data-button-root="true" class="suede-button ...">` at the
root of the story canvas; As link renders as a single
`<a data-button-root="true">`. `button button` and `a button`
nested-selector counts: 0 in every story.
Adds a Suede-wrapped bits-ui Button as a learning scaffold for the
bits-ui + stylebase + Svelte 5 pattern. Distinguishes functionality
(bits-ui's polymorphic Button.Root via ButtonRootProps) from style
(stylebase utility class + scoped <style> block in the same file).
Story at src/stories/SuedeButton.stories.svelte exercises four
variants: Primary, Disabled, As link, With class override.
TypeScript escape hatch: rest is spread via
`{...rest as Record<string, unknown>}` because bits-ui's
ButtonRootProps is a union (AnchorElement | ButtonElement) that
exceeds TypeScript's type complexity budget when re-spread into
another Svelte component. Cast is local to one line; polymorphic
behavior (button vs anchor via href) still works at runtime.
CSS pruner workaround: `<!-- svelte-ignore css_unused_selector -->`
on the <style> block, because the class name is built from a
template literal in the class attribute and is therefore invisible
to Svelte's static class-extraction pass.
Existing example stories (Button, Accordion, Header, Page) are
left untouched per option C; pre-existing pnpm check errors
(missing /components/ui/Button.svelte and Accordion.svelte
references) are out of scope for this change.
- .deciduous/ : local decision-graph database
- docs/ : generated web viewer export (regenerate via 'deciduous sync')
- CLAUDE.md : explicit Claude-Code instruction file, not part of this project
- .claude/ : explicit Claude-Code config directory, not part of this project
The last two are defensive: if 'deciduous install' re-runs, the Claude
artifacts will be re-created locally but stay untracked.
Cross-agent view of the deciduous decision-graph tool. Source content was
copied from CLAUDE.md's managed block (which we will not commit) and stripped
of tool-specific syntax:
- Removed slash command table (Claude Code only)
- Removed skills table (Claude Code only)
- Removed inline slash-command references
- Replaced the .claude/commands/decision.md git-staging example with
project-relevant paths
A header note at the top of the section explains that this is the
tool-agnostic view; tool-specific commands and skills live in
.opencode/commands/ and .opencode/skills/ (loaded at runtime by OpenCode,
not duplicated here).
- pnpm add @taurean/stylebase bits-ui
- src/app.css imports stylebase and provides global focus ring
- +layout.svelte wires app.css
- src/lib/components/ui/Button.svelte and Accordion.svelte are
Svelte 5 + bits-ui wrappers (script-only agent territory)
- Storybook stories for both wrappers
- vitest-browser-svelte smoke tests for both wrappers
- .gitignore: worker-configuration.d.ts (wrangler build artifact)
Wrapper styling is intentionally minimal — human territory per
AGENTS.md. The wrappers render with stylebase defaults; the
hybrid strategy (global data-attribute CSS + per-component
overrides) is the next step.
Refs agent-notes/2026-06-03-02-install-bits-ui-stylebase.md