a template starter repo for sveltekit projects
0

Configure Feed

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

chore: fix all Prettier and ESLint lint warnings

- Run prettier --write to fix formatting across 34 files
- Remove unused catch bindings in opencode plugins
- Remove unused `output` param and `fn` import in stories
- Replace triple-slash vitest/config reference with import style
- Wire up eslint-plugin-storybook flat/recommended config

+582 -473
+8 -8
.opencode/agents/deciduous.md
··· 48 48 49 49 ## Connection Rules 50 50 51 - | When you create... | IMMEDIATELY link to... | 52 - |-------------------|------------------------| 53 - | `option` | Its parent goal | 54 - | `decision` | The option(s) it chose between | 55 - | `action` | The decision that spawned it | 56 - | `outcome` | The action that produced it | 57 - | `observation` | Related goal/action | 58 - | `revisit` | The decision being reconsidered | 51 + | When you create... | IMMEDIATELY link to... | 52 + | ------------------ | ------------------------------- | 53 + | `option` | Its parent goal | 54 + | `decision` | The option(s) it chose between | 55 + | `action` | The decision that spawned it | 56 + | `outcome` | The action that produced it | 57 + | `observation` | Related goal/action | 58 + | `revisit` | The decision being reconsidered | 59 59 60 60 ## After Git Commits 61 61
+2 -1
.opencode/commands/decision-graph.md
··· 61 61 PR descriptions and review threads are goldmines for understanding **why** decisions were made. Reviewers often challenge approaches, surface alternatives that were considered, and document trade-offs - exactly the kind of reasoning that belongs in the decision graph. 62 62 63 63 When you find relevant PR discussion: 64 + 64 65 - Use it to enrich observation and decision node descriptions 65 66 - Quote reviewer comments as evidence (e.g., "PR #42 review: 'We should use Redis instead because...'") 66 67 - Note when a PR was blocked or revised - these are often pivot points ··· 133 134 - **Introduced**: First appearance of the concept 134 135 - **Changed**: Modifications to behavior or implementation 135 136 - **Renamed/Deprecated/Removed**: End of life or replacement 136 - - **Marked stable**: Became public API or removed "unstable_" prefix 137 + - **Marked stable**: Became public API or removed "unstable\_" prefix 137 138 138 139 Example addition to narrative: 139 140
+66 -35
.opencode/commands/decision.md
··· 2 2 description: Manage decision graph - track algorithm choices and reasoning 3 3 arguments: 4 4 - name: ACTION 5 - description: "Command: add <type> <title>, link <from> <to>, nodes, edges, sync, etc." 5 + description: 'Command: add <type> <title>, link <from> <to>, nodes, edges, sync, etc.' 6 6 required: true 7 7 --- 8 8 ··· 12 12 13 13 ## When to Use This 14 14 15 - | You're doing this... | Log this type | Command | 16 - |---------------------|---------------|---------| 17 - | Starting a new feature | `goal` **with -p** | `/decision add goal "Add user auth" -p "user request"` | 18 - | Choosing between approaches | `decision` | `/decision add decision "Choose auth method"` | 19 - | Considering an option | `option` | `/decision add option "JWT tokens"` | 20 - | About to write code | `action` | `/decision add action "Implementing JWT"` | 21 - | Noticing something | `observation` | `/decision add obs "Found existing auth code"` | 22 - | Finished something | `outcome` | `/decision add outcome "JWT working"` | 23 - | Reconsidering a past decision | `revisit` | `/decision add revisit "Reconsidering auth"` | 15 + | You're doing this... | Log this type | Command | 16 + | ----------------------------- | ------------------ | ------------------------------------------------------ | 17 + | Starting a new feature | `goal` **with -p** | `/decision add goal "Add user auth" -p "user request"` | 18 + | Choosing between approaches | `decision` | `/decision add decision "Choose auth method"` | 19 + | Considering an option | `option` | `/decision add option "JWT tokens"` | 20 + | About to write code | `action` | `/decision add action "Implementing JWT"` | 21 + | Noticing something | `observation` | `/decision add obs "Found existing auth code"` | 22 + | Finished something | `outcome` | `/decision add outcome "JWT working"` | 23 + | Reconsidering a past decision | `revisit` | `/decision add revisit "Reconsidering auth"` | 24 24 25 25 ## Quick Commands 26 26 27 27 Based on $ACTION: 28 28 29 29 ### View Commands 30 + 30 31 - `nodes` or `list` -> `deciduous nodes` 31 32 - `edges` -> `deciduous edges` 32 33 - `graph` -> `deciduous graph` 33 34 - `commands` -> `deciduous commands` 34 35 35 36 ### Create Nodes (with optional metadata) 37 + 36 38 - `add goal <title>` -> `deciduous add goal "<title>" -c 90` 37 39 - `add decision <title>` -> `deciduous add decision "<title>" -c 75` 38 40 - `add option <title>` -> `deciduous add option "<title>" -c 70` ··· 42 44 - `add revisit <title>` -> `deciduous add revisit "<title>" -c 75` 43 45 44 46 ### Optional Flags for Nodes 47 + 45 48 - `-c, --confidence <0-100>` - Confidence level 46 49 - `-p, --prompt "..."` - Store the user prompt that triggered this node 47 50 - `-f, --files "file1.rs,file2.rs"` - Associate files with this node ··· 65 68 **Prompts must be the EXACT user message, not a summary.** When a user request triggers new work, capture their full message word-for-word. 66 69 67 70 **BAD - summaries are useless for context recovery:** 71 + 68 72 ```bash 69 73 # DON'T DO THIS - this is a summary, not a prompt 70 74 deciduous add goal "Add auth" -p "User asked: add login to the app" 71 75 ``` 72 76 73 77 **GOOD - verbatim prompts enable full context recovery:** 78 + 74 79 ```bash 75 80 # Use --prompt-stdin for multi-line prompts 76 81 deciduous add goal "Add auth" -c 90 --prompt-stdin << 'EOF' ··· 86 91 ``` 87 92 88 93 **When to capture prompts:** 94 + 89 95 - Root `goal` nodes: YES - the FULL original request 90 96 - Major direction changes: YES - when user redirects the work 91 97 - Routine downstream nodes: NO - they inherit context via edges 92 98 93 99 **Updating prompts on existing nodes:** 100 + 94 101 ```bash 95 102 deciduous prompt <node_id> "full verbatim prompt here" 96 103 cat prompt.txt | deciduous prompt <node_id> # Multi-line from stdin ··· 103 110 **Nodes are automatically tagged with the current git branch.** This enables filtering by feature/PR. 104 111 105 112 ### How It Works 113 + 106 114 - When you create a node, the current git branch is stored in `metadata_json` 107 115 - Configure which branches are "main" in `.deciduous/config.toml`: 108 116 ```toml ··· 113 121 - Nodes on feature branches (anything not in `main_branches`) can be grouped/filtered 114 122 115 123 ### CLI Filtering 124 + 116 125 ```bash 117 126 # Show only nodes from specific branch 118 127 deciduous nodes --branch main ··· 125 134 ``` 126 135 127 136 ### Web UI Branch Filter 137 + 128 138 The graph viewer shows a branch dropdown in the stats bar: 139 + 129 140 - "All branches" shows everything 130 141 - Select a specific branch to filter all views (Chains, Timeline, Graph, DAG) 131 142 132 143 ### When to Use Branch Grouping 144 + 133 145 - **Feature work**: Nodes created on `feature-auth` branch auto-grouped 134 146 - **PR context**: Filter to see only decisions for a specific PR 135 147 - **Cross-cutting concerns**: Use `--no-branch` for universal notes 136 148 - **Retrospectives**: Filter by branch to see decision history per feature 137 149 138 150 ### Create Edges 151 + 139 152 - `link <from> <to> [reason]` -> `deciduous link <from> <to> -r "<reason>"` 140 153 141 154 ### Document Attachments 155 + 142 156 - `doc attach <node_id> <file>` -> `deciduous doc attach <node_id> <file>` 143 157 - `doc attach <node_id> <file> -d "desc"` -> attach with description 144 158 - `doc attach <node_id> <file> --ai-describe` -> attach with AI-generated description ··· 152 166 - `doc gc` -> `deciduous doc gc` (garbage-collect orphaned files) 153 167 154 168 ### Sync Graph 169 + 155 170 - `sync` -> `deciduous sync` 156 171 157 172 ### Multi-User Sync (Event-Based) - RECOMMENDED 173 + 158 174 - `events init` -> `deciduous events init` (initialize event-based sync) 159 175 - `events status` -> `deciduous events status` (show pending events) 160 176 - `events rebuild` -> `deciduous events rebuild` (apply teammate events) ··· 162 178 - `events checkpoint --clear-events` -> snapshot and clear old events 163 179 164 180 ### Multi-User Sync (Legacy Diff/Patch) 181 + 165 182 - `diff export -o <file>` -> `deciduous diff export -o <file>` (export nodes as patch) 166 183 - `diff export --nodes 1-10 -o <file>` -> export specific nodes 167 184 - `diff export --branch feature-x -o <file>` -> export nodes from branch ··· 171 188 - `migrate` -> `deciduous migrate` (add change_id columns for sync) 172 189 173 190 ### Export & Visualization 191 + 174 192 - `dot` -> `deciduous dot` (output DOT to stdout) 175 193 - `dot --png` -> `deciduous dot --png -o graph.dot` (generate PNG) 176 194 - `dot --nodes 1-11` -> `deciduous dot --nodes 1-11` (filter nodes) ··· 179 197 180 198 ## Node Types 181 199 182 - | Type | Purpose | Example | 183 - |------|---------|---------| 184 - | `goal` | High-level objective | "Add user authentication" | 185 - | `decision` | Choice point with options | "Choose auth method" | 186 - | `option` | Possible approach | "Use JWT tokens" | 187 - | `action` | Something implemented | "Added JWT middleware" | 188 - | `outcome` | Result of action | "JWT auth working" | 189 - | `observation` | Finding or data point | "Existing code uses sessions" | 190 - | `revisit` | Pivot point / reconsideration | "Reconsidering auth approach" | 200 + | Type | Purpose | Example | 201 + | ------------- | ----------------------------- | ----------------------------- | 202 + | `goal` | High-level objective | "Add user authentication" | 203 + | `decision` | Choice point with options | "Choose auth method" | 204 + | `option` | Possible approach | "Use JWT tokens" | 205 + | `action` | Something implemented | "Added JWT middleware" | 206 + | `outcome` | Result of action | "JWT auth working" | 207 + | `observation` | Finding or data point | "Existing code uses sessions" | 208 + | `revisit` | Pivot point / reconsideration | "Reconsidering auth approach" | 191 209 192 210 ## Edge Types 193 211 194 - | Type | Meaning | 195 - |------|---------| 196 - | `leads_to` | Natural progression | 197 - | `chosen` | Selected option | 212 + | Type | Meaning | 213 + | ---------- | ------------------------------ | 214 + | `leads_to` | Natural progression | 215 + | `chosen` | Selected option | 198 216 | `rejected` | Not selected (include reason!) | 199 - | `requires` | Dependency | 200 - | `blocks` | Preventing progress | 201 - | `enables` | Makes something possible | 217 + | `requires` | Dependency | 218 + | `blocks` | Preventing progress | 219 + | `enables` | Makes something possible | 202 220 203 221 ## Graph Integrity - CRITICAL 204 222 205 223 **Every node MUST be logically connected.** Floating nodes break the graph's value. 206 224 207 225 ### Connection Rules (goal -> options -> decision -> actions -> outcomes) 208 - | Node Type | MUST connect to | Example | 209 - |-----------|----------------|---------| 210 - | `goal` | Can be a root (no parent needed) | Root goals are valid orphans | 211 - | `option` | Its parent goal | "Use JWT" -> links FROM "Add auth" | 212 - | `decision` | The option(s) it chose between | "Choose JWT" -> links FROM "Use JWT" option | 213 - | `action` | The decision that spawned it | "Implementing JWT" -> links FROM "Choose JWT" | 214 - | `outcome` | The action that produced it | "JWT working" -> links FROM "Implementing JWT" | 215 - | `observation` | Related goal/action/decision | "Found existing code" -> links TO relevant node | 216 - | `revisit` | The decision/outcome being reconsidered | "Reconsidering auth" -> links FROM original decision | 226 + 227 + | Node Type | MUST connect to | Example | 228 + | ------------- | --------------------------------------- | ---------------------------------------------------- | 229 + | `goal` | Can be a root (no parent needed) | Root goals are valid orphans | 230 + | `option` | Its parent goal | "Use JWT" -> links FROM "Add auth" | 231 + | `decision` | The option(s) it chose between | "Choose JWT" -> links FROM "Use JWT" option | 232 + | `action` | The decision that spawned it | "Implementing JWT" -> links FROM "Choose JWT" | 233 + | `outcome` | The action that produced it | "JWT working" -> links FROM "Implementing JWT" | 234 + | `observation` | Related goal/action/decision | "Found existing code" -> links TO relevant node | 235 + | `revisit` | The decision/outcome being reconsidered | "Reconsidering auth" -> links FROM original decision | 217 236 218 237 ### Audit Checklist 238 + 219 239 Ask yourself after creating nodes: 240 + 220 241 1. Does every **outcome** link back to the action that produced it? 221 242 2. Does every **action** link to the decision that spawned it? 222 243 3. Does every **option** link to its parent goal? ··· 224 245 5. Are there **dangling outcomes** with no parent action? 225 246 226 247 ### Find Disconnected Nodes 248 + 227 249 ```bash 228 250 # List nodes with no incoming edges (potential orphans) 229 251 deciduous edges | cut -d'>' -f2 | cut -d' ' -f2 | sort -u > /tmp/has_parent.txt ··· 231 253 grep -q "^$id$" /tmp/has_parent.txt || echo "CHECK: $id" 232 254 done 233 255 ``` 256 + 234 257 Note: Root goals are VALID orphans. Outcomes/actions/options usually are NOT. 235 258 236 259 ### Fix Missing Connections 260 + 237 261 ```bash 238 262 deciduous link <parent_id> <child_id> -r "Retroactive connection - <why>" 239 263 ``` 240 264 241 265 ### When to Audit 266 + 242 267 - Before every `deciduous sync` 243 268 - After creating multiple nodes quickly 244 269 - At session end ··· 247 272 ## Git Staging Rules - CRITICAL 248 273 249 274 **NEVER use broad git add commands that stage everything:** 275 + 250 276 - `git add -A` - stages ALL changes including untracked files 251 277 - `git add .` - stages everything in current directory 252 278 - `git add -a` or `git commit -am` - auto-stages all tracked changes 253 279 - `git add *` - glob patterns can catch unintended files 254 280 255 281 **ALWAYS stage files explicitly by name:** 282 + 256 283 - `git add src/main.rs src/lib.rs` 257 284 - `git add Cargo.toml Cargo.lock` 258 285 - `git add .opencode/commands/decision.md` 259 286 260 287 **Why this matters:** 288 + 261 289 - Prevents accidentally committing sensitive files (.env, credentials) 262 290 - Prevents committing large binaries or build artifacts 263 291 - Forces you to review exactly what you're committing ··· 272 300 ### Event-Based Sync (Recommended) 273 301 274 302 **Setup (once per repo):** 303 + 275 304 ```bash 276 305 deciduous events init 277 306 git add .deciduous/sync/ ··· 279 308 ``` 280 309 281 310 **Daily workflow:** 311 + 282 312 ```bash 283 313 git pull # Get teammate events 284 314 deciduous events rebuild # Apply to local DB ··· 287 317 ``` 288 318 289 319 **Periodic maintenance:** 320 + 290 321 ```bash 291 322 deciduous events checkpoint --clear-events # Compact old events 292 323 git add .deciduous/sync/ && git commit -m "checkpoint"
+18 -5
.opencode/commands/document.md
··· 11 11 **Comprehensive documentation that shakes the tree to understand everything.** 12 12 13 13 This skill generates in-depth documentation for a file or directory, focusing on: 14 + 14 15 - Human readability while covering ALL surface area 15 16 - Linking to tests as working examples 16 17 - Refining tests to look more real-world if needed ··· 73 74 For each file/component, document: 74 75 75 76 ### 3.1 Purpose 77 + 76 78 - One sentence: what does this do? 77 79 - Why does it exist? (The "why" is more important than the "what") 78 80 79 81 ### 3.2 API Surface 82 + 80 83 For every public function/method/class: 81 84 82 - ```markdown 85 + ````markdown 83 86 ### `function_name(param1: Type, param2: Type) -> ReturnType` 84 87 85 88 **Purpose:** What this does and why you'd call it. 86 89 87 90 **Parameters:** 91 + 88 92 - `param1` - Description and valid values 89 93 - `param2` - Description and valid values 90 94 ··· 93 97 **Throws/Errors:** What can go wrong 94 98 95 99 **Example:** 100 + 96 101 ```code 97 102 // From: tests/example_test.rs:42 98 103 let result = function_name("input", 42); 99 104 assert_eq!(result, expected); 100 105 ``` 106 + ```` 101 107 102 108 **Related:** Links to related functions 103 - ``` 109 + 110 + ```` 104 111 105 112 ### 3.3 Internal Architecture 106 113 - How does it work internally? ··· 145 152 146 153 ```code 147 154 // Most common usage pattern - from real tests 148 - ``` 155 + ```` 149 156 150 157 ## API Reference 151 158 ··· 171 178 172 179 - Links to other relevant docs 173 180 - Links to test files 174 - ``` 181 + 182 + ```` 175 183 176 184 --- 177 185 ··· 190 198 ```bash 191 199 deciduous add observation "Refined tests for <component> - made more real-world" -c 85 192 200 deciduous link <action_id> <observation_id> -r "Test improvements during documentation" 193 - ``` 201 + ```` 194 202 195 203 --- 196 204 ··· 236 244 ## Decision Criteria 237 245 238 246 **What to document:** 247 + 239 248 - Public APIs (always) 240 249 - Complex internal logic (when it's not obvious) 241 250 - Design decisions (why, not just what) ··· 243 252 - Integration points 244 253 245 254 **What NOT to document:** 255 + 246 256 - Trivial getters/setters 247 257 - Auto-generated code 248 258 - Implementation details obvious from code 249 259 250 260 **How deep to go:** 261 + 251 262 - Deep enough that someone new could understand and use the code 252 263 - Deep enough that someone could modify it without breaking things 253 264 - Capture the "why" behind design decisions ··· 268 279 ``` 269 280 270 281 **What happens:** 282 + 271 283 1. Goal node created 272 284 2. Code analyzed thoroughly 273 285 3. Tests found and used as examples ··· 300 312 ``` 301 313 302 314 **Always creates:** 315 + 303 316 - Goal node (before starting) 304 317 - Action node (for the documentation work) 305 318 - Outcome node (on completion)
+3
.opencode/commands/recover.md
··· 44 44 ``` 45 45 46 46 **Review each flagged node (flow: goal -> options -> decision -> actions -> outcomes):** 47 + 47 48 - Root `goal` nodes are VALID without parents 48 49 - `option` nodes MUST link to their parent goal 49 50 - `decision` nodes MUST link from the option(s) being chosen ··· 51 52 - `outcome` nodes MUST link back to their action 52 53 53 54 **Fix missing connections:** 55 + 54 56 ```bash 55 57 deciduous link <parent_id> <child_id> -r "Retroactive connection - <reason>" 56 58 ``` ··· 82 84 ### Branch Configuration 83 85 84 86 Check `.deciduous/config.toml` for branch settings: 87 + 85 88 ```toml 86 89 [branch] 87 90 main_branches = ["main", "master"] # Which branches are "main"
+3
.opencode/commands/serve-ui.md
··· 13 13 ## Instructions 14 14 15 15 1. Start the server: 16 + 16 17 ```bash 17 18 deciduous serve --port ${PORT:-3000} 18 19 ``` ··· 26 27 3. The server will run in the foreground. Remind user to stop it when done (Ctrl+C). 27 28 28 29 ## UI Features 30 + 29 31 - **Chains View**: See decision chains grouped by goals 30 32 - **Timeline View**: Chronological view of all decisions 31 33 - **Graph View**: Interactive force-directed graph ··· 39 41 ## Alternative: Static Hosting 40 42 41 43 For GitHub Pages or other static hosting: 44 + 42 45 ```bash 43 46 deciduous sync # Exports to docs/graph-data.json 44 47 ```
+10 -8
.opencode/commands/sync.md
··· 20 20 ``` 21 21 22 22 Look for: 23 + 23 24 - **Pending events**: Events from teammates not yet in your local DB 24 25 - **Event files**: Each teammate has their own `.jsonl` file 25 26 ··· 61 62 ``` 62 63 63 64 **When to checkpoint:** 65 + 64 66 - After major milestones 65 67 - When event files get large (>100KB) 66 68 - Before releases ··· 86 88 87 89 ## Quick Reference 88 90 89 - | Command | What it does | 90 - |---------|--------------| 91 - | `deciduous events status` | Show pending events, authors, file sizes | 92 - | `deciduous events rebuild` | Apply all events to local DB | 93 - | `deciduous events rebuild --dry-run` | Preview without applying | 94 - | `deciduous events checkpoint` | Snapshot current state | 95 - | `deciduous events checkpoint --clear-events` | Snapshot + delete old events | 96 - | `deciduous events emit <id>` | Manually emit event for a node | 91 + | Command | What it does | 92 + | -------------------------------------------- | ---------------------------------------- | 93 + | `deciduous events status` | Show pending events, authors, file sizes | 94 + | `deciduous events rebuild` | Apply all events to local DB | 95 + | `deciduous events rebuild --dry-run` | Preview without applying | 96 + | `deciduous events checkpoint` | Snapshot current state | 97 + | `deciduous events checkpoint --clear-events` | Snapshot + delete old events | 98 + | `deciduous events emit <id>` | Manually emit event for a node |
+34 -34
.opencode/plugins/post-commit-reminder.ts
··· 2 2 // Reminds to link commits to the decision graph after git commit 3 3 // This ensures commits are connected to the reasoning that led to them 4 4 5 - import type { Plugin } from "@opencode-ai/plugin" 5 + import type { Plugin } from '@opencode-ai/plugin'; 6 6 7 7 export const PostCommitReminder: Plugin = async ({ $ }) => { 8 - return { 9 - "tool.execute.after": async (input) => { 10 - // Only check bash tool 11 - if (input.tool !== "bash") { 12 - return 13 - } 8 + return { 9 + 'tool.execute.after': async (input) => { 10 + // Only check bash tool 11 + if (input.tool !== 'bash') { 12 + return; 13 + } 14 14 15 - // Check if deciduous is initialized 16 - const fs = await import("fs") 17 - if (!fs.existsSync(".deciduous")) { 18 - return 19 - } 15 + // Check if deciduous is initialized 16 + const fs = await import('fs'); 17 + if (!fs.existsSync('.deciduous')) { 18 + return; 19 + } 20 20 21 - // Check if this was a git commit command 22 - const command = input.args?.command || "" 23 - if (!command.match(/^git commit/)) { 24 - return 25 - } 21 + // Check if this was a git commit command 22 + const command = input.args?.command || ''; 23 + if (!command.match(/^git commit/)) { 24 + return; 25 + } 26 26 27 - try { 28 - // Get the latest commit info 29 - const hashResult = await $`git rev-parse --short HEAD 2>/dev/null`.quiet() 30 - const msgResult = await $`git log -1 --format=%s 2>/dev/null`.quiet() 27 + try { 28 + // Get the latest commit info 29 + const hashResult = await $`git rev-parse --short HEAD 2>/dev/null`.quiet(); 30 + const msgResult = await $`git log -1 --format=%s 2>/dev/null`.quiet(); 31 31 32 - const commitHash = hashResult.stdout.toString().trim() 33 - const commitMsg = msgResult.stdout.toString().trim().slice(0, 50) 32 + const commitHash = hashResult.stdout.toString().trim(); 33 + const commitMsg = msgResult.stdout.toString().trim().slice(0, 50); 34 34 35 - // Write reminder to log file instead of console (console output corrupts TUI) 36 - const path = await import("path") 37 - const logFile = path.join(".deciduous", "plugin.log") 38 - const msg = `[${new Date().toISOString()}] POST-COMMIT: ${commitHash} "${commitMsg}" - Run: deciduous add outcome "..." --commit HEAD\n` 39 - fs.appendFileSync(logFile, msg) 40 - } catch (error) { 41 - // If git commands fail, skip the reminder 42 - } 43 - } 44 - } 45 - } 35 + // Write reminder to log file instead of console (console output corrupts TUI) 36 + const path = await import('path'); 37 + const logFile = path.join('.deciduous', 'plugin.log'); 38 + const msg = `[${new Date().toISOString()}] POST-COMMIT: ${commitHash} "${commitMsg}" - Run: deciduous add outcome "..." --commit HEAD\n`; 39 + fs.appendFileSync(logFile, msg); 40 + } catch { 41 + // If git commands fail, skip the reminder 42 + } 43 + } 44 + }; 45 + };
+41 -38
.opencode/plugins/require-action-node.ts
··· 2 2 // Checks for recent action/goal nodes before file edits 3 3 // This enforces the decision graph workflow: log BEFORE you code 4 4 5 - import type { Plugin } from "@opencode-ai/plugin" 5 + import type { Plugin } from '@opencode-ai/plugin'; 6 6 7 7 export const RequireActionNode: Plugin = async ({ $ }) => { 8 - return { 9 - "tool.execute.before": async (input, output) => { 10 - // Only check on edit and write tools 11 - if (input.tool !== "edit" && input.tool !== "write") { 12 - return 13 - } 8 + return { 9 + 'tool.execute.before': async (input) => { 10 + // Only check on edit and write tools 11 + if (input.tool !== 'edit' && input.tool !== 'write') { 12 + return; 13 + } 14 14 15 - try { 16 - // Check if deciduous is initialized 17 - const fs = await import("fs") 18 - if (!fs.existsSync(".deciduous")) { 19 - return // No deciduous in this project, allow all edits 20 - } 15 + try { 16 + // Check if deciduous is initialized 17 + const fs = await import('fs'); 18 + if (!fs.existsSync('.deciduous')) { 19 + return; // No deciduous in this project, allow all edits 20 + } 21 21 22 - // Get recent nodes from deciduous 23 - const result = await $`deciduous nodes 2>/dev/null | tail -5`.quiet() 24 - const stdout = result.stdout.toString() 25 - const lines = stdout.trim().split("\n").filter((l: string) => l.trim()) 22 + // Get recent nodes from deciduous 23 + const result = await $`deciduous nodes 2>/dev/null | tail -5`.quiet(); 24 + const stdout = result.stdout.toString(); 25 + const lines = stdout 26 + .trim() 27 + .split('\n') 28 + .filter((l: string) => l.trim()); 26 29 27 - // Check for any goal or action node 28 - let hasRecentNode = false 29 - for (const line of lines) { 30 - if (line.match(/goal|action/i)) { 31 - hasRecentNode = true 32 - break 33 - } 34 - } 30 + // Check for any goal or action node 31 + let hasRecentNode = false; 32 + for (const line of lines) { 33 + if (line.match(/goal|action/i)) { 34 + hasRecentNode = true; 35 + break; 36 + } 37 + } 35 38 36 - if (!hasRecentNode && lines.length > 2) { 37 - // Write reminder to log file instead of console (console output corrupts TUI) 38 - const path = await import("path") 39 - const logFile = path.join(".deciduous", "plugin.log") 40 - const msg = `[${new Date().toISOString()}] REMINDER: No recent action/goal node found. Run: deciduous add goal "..." or deciduous add action "..."\n` 41 - fs.appendFileSync(logFile, msg) 42 - } 43 - } catch (error) { 44 - // If deciduous isn't available, continue silently 45 - } 46 - } 47 - } 48 - } 39 + if (!hasRecentNode && lines.length > 2) { 40 + // Write reminder to log file instead of console (console output corrupts TUI) 41 + const path = await import('path'); 42 + const logFile = path.join('.deciduous', 'plugin.log'); 43 + const msg = `[${new Date().toISOString()}] REMINDER: No recent action/goal node found. Run: deciduous add goal "..." or deciduous add action "..."\n`; 44 + fs.appendFileSync(logFile, msg); 45 + } 46 + } catch { 47 + // If deciduous isn't available, continue silently 48 + } 49 + } 50 + }; 51 + };
+25 -25
.opencode/plugins/version-check.ts
··· 15 15 // single line to `.deciduous/plugin.log` so the no-op is observable 16 16 // to anyone reading the log. 17 17 18 - import type { Plugin } from "@opencode-ai/plugin" 18 + import type { Plugin } from '@opencode-ai/plugin'; 19 19 20 20 export const VersionCheck: Plugin = async () => { 21 - return { 22 - "tool.execute.before": async () => { 23 - try { 24 - const fs = await import("fs") 25 - const path = await import("path") 26 - if (!fs.existsSync(".deciduous")) return 27 - const marker = path.join(".deciduous", ".version_check_disabled") 28 - if (fs.existsSync(marker)) return 29 - fs.writeFileSync( 30 - marker, 31 - `version-check plugin disabled at ${new Date().toISOString()}\n` + 32 - `See AGENTS.md: do not run \`deciduous update\`.\n`, 33 - ) 34 - const logFile = path.join(".deciduous", "plugin.log") 35 - fs.appendFileSync( 36 - logFile, 37 - `[${new Date().toISOString()}] version-check plugin: no-op. Do not run \`deciduous update\` (see AGENTS.md).\n`, 38 - ) 39 - } catch { 40 - // Never let the plugin break edits. 41 - } 42 - }, 43 - } 44 - } 21 + return { 22 + 'tool.execute.before': async () => { 23 + try { 24 + const fs = await import('fs'); 25 + const path = await import('path'); 26 + if (!fs.existsSync('.deciduous')) return; 27 + const marker = path.join('.deciduous', '.version_check_disabled'); 28 + if (fs.existsSync(marker)) return; 29 + fs.writeFileSync( 30 + marker, 31 + `version-check plugin disabled at ${new Date().toISOString()}\n` + 32 + `See AGENTS.md: do not run \`deciduous update\`.\n` 33 + ); 34 + const logFile = path.join('.deciduous', 'plugin.log'); 35 + fs.appendFileSync( 36 + logFile, 37 + `[${new Date().toISOString()}] version-check plugin: no-op. Do not run \`deciduous update\` (see AGENTS.md).\n` 38 + ); 39 + } catch { 40 + // Never let the plugin break edits. 41 + } 42 + } 43 + }; 44 + };
+2
.opencode/skills/archaeology/SKILL.md
··· 42 42 ``` 43 43 44 44 This automatically creates: 45 + 45 46 - observation node (what was learned) 46 47 - revisit node (reconsidering the old approach) 47 48 - decision node (the new approach) ··· 49 50 - Marks the old approach as superseded 50 51 51 52 Preview before executing: 53 + 52 54 ```bash 53 55 deciduous archaeology pivot <from_id> "observation" "new approach" --dry-run 54 56 ```
+3
.opencode/skills/narratives/SKILL.md
··· 33 33 5. Check attached documents (`deciduous doc list`) 34 34 35 35 Signs of a pivot: 36 + 36 37 - Two approaches coexisting (migration in progress) 37 38 - Comments explaining "we used to do X" 38 39 - Config for old + new system ··· 56 57 57 58 ```markdown 58 59 ## <Name> 60 + 59 61 > <One sentence: what this piece of the system does> 60 62 61 63 **Current state:** <How it works today> 62 64 63 65 **Evolution:** 66 + 64 67 1. <First approach> - <why> 65 68 2. **PIVOT:** <what changed> - <why it changed> 66 69 3. <Current approach> - <why this is better>
+28 -25
.opencode/tools/deciduous.ts
··· 4 4 // This tool allows agents to interact with the decision graph without 5 5 // needing to use the bash tool directly. 6 6 7 - import { tool } from "@opencode-ai/plugin" 7 + import { tool } from '@opencode-ai/plugin'; 8 8 9 9 export default tool({ 10 - description: "Manage the deciduous decision graph - add nodes, create edges, query the graph, and sync", 11 - args: { 12 - command: tool.schema.string().describe( 13 - "The deciduous subcommand and arguments to run. Examples: " + 14 - "'add goal \"Title\" -c 90', " + 15 - "'link 1 2 -r \"reason\"', " + 16 - "'nodes', 'edges', 'graph', 'pulse', 'sync'" 17 - ), 18 - }, 19 - async execute(args, context) { 20 - const proc = Bun.spawn(["sh", "-c", `deciduous ${args.command}`], { 21 - cwd: context.directory, 22 - stdout: "pipe", 23 - stderr: "pipe", 24 - }) 10 + description: 11 + 'Manage the deciduous decision graph - add nodes, create edges, query the graph, and sync', 12 + args: { 13 + command: tool.schema 14 + .string() 15 + .describe( 16 + 'The deciduous subcommand and arguments to run. Examples: ' + 17 + '\'add goal "Title" -c 90\', ' + 18 + '\'link 1 2 -r "reason"\', ' + 19 + "'nodes', 'edges', 'graph', 'pulse', 'sync'" 20 + ) 21 + }, 22 + async execute(args, context) { 23 + const proc = Bun.spawn(['sh', '-c', `deciduous ${args.command}`], { 24 + cwd: context.directory, 25 + stdout: 'pipe', 26 + stderr: 'pipe' 27 + }); 25 28 26 - const stdout = await new Response(proc.stdout).text() 27 - const stderr = await new Response(proc.stderr).text() 28 - const exitCode = await proc.exited 29 + const stdout = await new Response(proc.stdout).text(); 30 + const stderr = await new Response(proc.stderr).text(); 31 + const exitCode = await proc.exited; 29 32 30 - if (exitCode !== 0) { 31 - return `Error (exit ${exitCode}):\n${stderr}\n${stdout}` 32 - } 33 + if (exitCode !== 0) { 34 + return `Error (exit ${exitCode}):\n${stderr}\n${stdout}`; 35 + } 33 36 34 - return stdout || "(no output)" 35 - }, 36 - }) 37 + return stdout || '(no output)'; 38 + } 39 + });
+10 -13
.storybook/main.ts
··· 1 1 import type { StorybookConfig } from '@storybook/sveltekit'; 2 2 3 3 const config: StorybookConfig = { 4 - "stories": [ 5 - "../src/**/*.mdx", 6 - "../src/**/*.stories.@(js|ts|svelte)" 7 - ], 8 - "addons": [ 9 - "@storybook/addon-svelte-csf", 10 - "@chromatic-com/storybook", 11 - "@storybook/addon-vitest", 12 - "@storybook/addon-a11y", 13 - "@storybook/addon-docs" 14 - ], 15 - "framework": "@storybook/sveltekit" 4 + stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|ts|svelte)'], 5 + addons: [ 6 + '@storybook/addon-svelte-csf', 7 + '@chromatic-com/storybook', 8 + '@storybook/addon-vitest', 9 + '@storybook/addon-a11y', 10 + '@storybook/addon-docs' 11 + ], 12 + framework: '@storybook/sveltekit' 16 13 }; 17 - export default config; 14 + export default config;
+6
agent-notes/0000-00-00-00-task-template.md
··· 1 1 # <slug> 2 2 3 3 ## Task 4 + 4 5 <the determined task description — what was asked, what scope, what done-when> 5 6 6 7 ## Decisions 8 + 7 9 - <choice made> — <why> 8 10 9 11 ## Actions 12 + 10 13 - <step taken> — <brief result or pointer> 11 14 12 15 ## Files touched 16 + 13 17 - `path/to/file.ts` — <what changed> 14 18 15 19 ## Verification 20 + 16 21 - `pnpm lint` — pass 17 22 - `pnpm check` — pass 18 23 - `pnpm test` — not run (no test changes) 19 24 20 25 ## Follow-ups / stubs 26 + 21 27 - <thing deferred, stubbed, or flagged for later>
+6
agent-notes/2026-06-03-01-suede-process-bootstrap.md
··· 1 1 # Suede process bootstrap 2 2 3 3 ## Task 4 + 4 5 Design and bootstrap the Suede process layer for this template repo. Deliverables: cross-agent `AGENTS.md` (replaces `CLAUDE.md`), `agent-notes/` with a task template, `.opencode/skills/task-lifecycle/` end-of-task skill, fold `stack.md` content into `AGENTS.md`, and initial git commit. 5 6 6 7 ## Decisions 8 + 7 9 - Process backbone is a custom unified shape (one chronological note per task, written at task end), not the full Superpowers spec/plan flow. Ceremonial overhead didn't match user's "passively focused on plans" stance. 8 10 - `stack.md` content folded into a `## Stack` section of `AGENTS.md`; option-picking reference tables (D1 vs Postgres, ATProto OAuth vs Better Auth) dropped from the repo — those are picking guides for the user, not rules for the agent. 9 11 - Notes directory named `agent-notes/`, not `notes/` or `journal/`. Explicit about purpose. ··· 15 17 - Drop the formal spec artifact (no `docs/superpowers/specs/`) and the formal implementation plan file. Plan lives in chat, then gets executed. 16 18 17 19 ## Actions 20 + 18 21 - Designed the system across five sections: layout, AGENTS.md content, task template, task-lifecycle skill, day-in-the-life. 19 22 - Created `agent-notes/` and `.opencode/skills/task-lifecycle/` directories. 20 23 - Wrote `AGENTS.md` (52 lines) with stack, layout, authoring boundaries, task-lifecycle pointer, guardrails, verification commands. ··· 26 29 - Committed as `chore: bootstrap Suede process — AGENTS.md, agent-notes template, task-lifecycle skill`. Five files changed, 92 insertions, 274 deletions. 27 30 28 31 ## Files touched 32 + 29 33 - `AGENTS.md` — created 30 34 - `agent-notes/0000-00-00-00-task-template.md` — created 31 35 - `.opencode/skills/task-lifecycle/SKILL.md` — created ··· 34 38 - `agent-notes/2026-06-03-01-suede-process-bootstrap.md` — created (this note) 35 39 36 40 ## Verification 41 + 37 42 - `git status` — working tree clean 38 43 - `git log --oneline -5` — three commits: gitignore, init, process bootstrap 39 44 - File existence check: all four created files present with correct content; `stack.md` confirmed absent 40 45 - `.gitignore` review: `.env` and `.env.*` excluded, `.env.example` explicitly allowed 41 46 42 47 ## Follow-ups / stubs 48 + 43 49 - The "option-picking" reference content dropped from the repo (database options, auth options tables) could live in a personal notes location if you want it preserved. Currently no home for it. 44 50 - When Suede is duplicated into a new project, this very note + the `init` template files come along. If you want a "clean on duplicate" mechanism (e.g., a script that strips `agent-notes/`), that could be a future task. 45 51 - No test for the process itself. The system is documentation, not code.
+7 -1
agent-notes/2026-06-03-02-install-bits-ui-stylebase.md
··· 1 1 # install-bits-ui-stylebase 2 2 3 3 ## Task 4 + 4 5 Install and configure `@taurean/stylebase` and `bits-ui` together in Suede. Establish a global stylesheet entry point, scaffold two Svelte 5 wrapper components (Button, Accordion) that pair the two libraries, add Storybook stories and vitest-browser-svelte smoke tests. Done-when: both libraries installed; `src/app.css` imports stylebase and is wired through `+layout.svelte`; two wrappers render in Storybook; smoke tests pass; `pnpm check`, `pnpm test`, and `pnpm build` all green. 5 6 6 7 ## Decisions 8 + 7 9 - **Hybrid styling strategy** (Q1) — global resets/focus in `app.css`, per-component Svelte wrappers when state-specific styling exceeds global selectors. Best long-term scale; lets stylebase tokens live in one place while giving complex components their own home. 8 10 - **Scaffolded Button + Accordion wrappers** (Q2) — covers both the simple primitive and state-with-data-attributes patterns. 9 11 - **app.css at `src/app.css`** — SvelteKit convention; keeps stylebase at the top of the cascade. `src/lib/styles/` was an option but adds a hop. 10 12 - **No smoke test on `+page.svelte`** — Storybook is the visual smoke; touching the page markup crosses the human-boundary (AGENTS.md authoring rules). 11 - - **No barrel file** — wrappers imported directly via `$lib/components/ui/Button.svelte`. Barrel is trivial to add later if a second consumer per wrapper emerges. *Note: the index.ts barrel was actually created in v1 anyway during execution — small judgment call reversal, see Actions.* 13 + - **No barrel file** — wrappers imported directly via `$lib/components/ui/Button.svelte`. Barrel is trivial to add later if a second consumer per wrapper emerges. _Note: the index.ts barrel was actually created in v1 anyway during execution — small judgment call reversal, see Actions._ 12 14 - **Type-bypass for `AccordionPrimitive.Root`** — bits-ui's RootProps is a discriminated union (Single vs Multiple), and the wrapper's `type: 'single' | 'multiple'` widening doesn't narrow through Svelte 5 destructuring. Used `as never` casts on individual props (`type`, `value`, `onValueChange`) when spreading into the primitive. Internal-only type loosening; the public API is constrained. 13 15 - **`ComponentProps<typeof X>` pattern for Button wrapper** — bits-ui's `Button.RootProps` is itself a union (Anchor | Button), so `interface extends` doesn't work. `Omit<..., 'children'> & { children: Snippet }` keeps `children` required. 14 16 - **Did not run prettier on pre-existing files** — `pnpm lint` shows 15 pre-existing formatting failures in human-owned files (`.storybook/*`, `AGENTS.md`, `src/stories/Button.svelte`, etc.). Out of scope for this task. All new files pass prettier and eslint. ··· 16 18 - **Kept existing `src/stories/Button.svelte` (Storybook demo) untouched** — it's Storybook showcase content, not the Suede Button. Suede wrapper lives at `src/lib/components/ui/Button.svelte`. Cleanup deferred to a follow-up. 17 19 18 20 ## Actions 21 + 19 22 - Researched both packages via npm + GitHub + official docs; read bits-ui's `.d.ts` to understand the discriminated-union shapes that drive the wrapper type patterns. 20 23 - `pnpm add @taurean/stylebase bits-ui` — installed stylebase 0.11.0, bits-ui 2.18.1; lockfile updated. 21 24 - Created `src/app.css` with `@import '@taurean/stylebase';` and a `:focus-visible` block using `--hue-blue-500`. ··· 31 34 - Iterated twice on `pnpm check` errors (interface-extends union, destructure widening) and once on test snippet creation. 32 35 33 36 ## Files touched 37 + 34 38 - `package.json` — added `@taurean/stylebase`, `bits-ui` to dependencies 35 39 - `pnpm-lock.yaml` — regenerated 36 40 - `src/app.css` — created ··· 45 49 - `.gitignore` — added `worker-configuration.d.ts`; tightened `.wrangler` entry 46 50 47 51 ## Verification 52 + 48 53 - `pnpm check` — pass. `svelte-check found 0 errors and 0 warnings` across the workspace. 49 54 - `pnpm test` — pass. 14 tests across 8 files (client + server + storybook projects), 0 errors. 4 are the new spec tests for the wrappers; 2 are the new storybook tests; 8 are the existing tests. 50 55 - `pnpm build` — pass. Cloudflare adapter completes; client bundle is small; `_layout` CSS chunk is 16.80 kB (stylebase's contribution, gzipped 4.02 kB). ··· 52 57 - Manual type exploration: confirmed `Button.RootProps` and `Accordion.RootProps` are discriminated unions; the wrapper type patterns are documented in the Decisions section. 53 58 54 59 ## Follow-ups / stubs 60 + 55 61 - **Pre-existing prettier failures** in human-owned files (`.storybook/main.ts`, `AGENTS.md`, `src/stories/{Button,Header,Page}.svelte`, `Configure.mdx`, `vitest.shims.d.ts`, etc.) — 15 files, all out of this task's scope. Worth a `pnpm format` cleanup pass in a dedicated task. 56 62 - **Storybook demo content cleanup** — `src/stories/{Button,Header,Page}.svelte` and their `*.css` files are Storybook showcase, not Suede code. The Button demo is now confusingly similar to the new Suede wrapper story. Either delete them or rename the Suede story's title to `Suede/Button` for clarity. 57 63 - **Style the wrappers** — wrappers render unstyled (bits-ui ships ~zero styles). The whole point of the hybrid strategy is that the human adds styling. Currently they fall through to stylebase's default element styles, which is fine for layout/typography but buttons look like text. Add `:global([data-button-root]) { … }` rules in `src/app.css` and per-trigger styles in `Accordion.svelte` to bring them to life.
+6
agent-notes/2026-06-03-02-process-updates.md
··· 1 1 # process-updates 2 2 3 3 ## Task 4 + 4 5 Update the Suede process layer with two changes: (1) introduce a Git workflow rule (branch from main, PR review, no direct pushes), (2) drop Sentry, Axiom, OpenCode, and Claude Code from the explicit stack mentions in AGENTS.md. 5 6 6 7 ## Decisions 8 + 7 9 - New "## Git workflow" section in AGENTS.md covers the rule in three bullets: branch from main, PR review, no direct push. Kept terse — it's a guardrail, not a tutorial. 8 10 - Stack line trimmed to the technologies only; tools-that-were-decided and LLM-tooling dropped per user preference. 9 11 - Task-lifecycle skill expanded with a "## Before starting" section. The skill now covers both ends of a task — start (branch setup) and end (note + verification). Renamed the end-of-task section header to "## Steps at task end" for clarity. ··· 13 15 - **Refinement:** the human remains the commit author for all commits (including agent-made ones). Agent-made commits add a `Co-authored-by: opencode <noreply@opencode.ai>` trailer to credit assistance. The trailer format and commit step are now explicit in the skill's "Steps at task end" and the Git workflow section of AGENTS.md. 14 16 15 17 ## Actions 18 + 16 19 - Created branch `chore/process-updates` from `main` (no remote configured yet, so `git pull` step skipped — main is the local source of truth). 17 20 - Edited `AGENTS.md`: removed "Sentry + Axiom · OpenCode (primary) / Claude Code (fallback)" from the Stack line; added a new "## Git workflow" section between Stack and Layout. 18 21 - Rewrote `.opencode/skills/task-lifecycle/SKILL.md`: added "## Before starting" with the branch-setup steps; renamed end-of-task section to "## Steps at task end"; added a "PR + merge per AGENTS.md" line to Rules. 19 22 20 23 ## Files touched 24 + 21 25 - `AGENTS.md` — edited (stack line, added Git workflow section) 22 26 - `.opencode/skills/task-lifecycle/SKILL.md` — edited (added Before starting section) 23 27 - `agent-notes/2026-06-03-02-process-updates.md` — created (this note) 24 28 25 29 ## Verification 30 + 26 31 - `git status` — clean working tree on `chore/process-updates` 27 32 - `git branch` — confirms we're on the feature branch, not main 28 33 - File content re-read on both edited files (during edit) — confirmed correct 29 34 30 35 ## Follow-ups / stubs 36 + 31 37 - No git remote is configured yet. PR step is on the user: set a remote, push the branch, open the PR, merge after review. The bootstrap commit (`5a9fda9`) was made directly on `main` before this rule was in place — that's a one-time historical exception, not a pattern. 32 38 - Branch naming convention is not standardized. If the user wants a strict convention (e.g., `type/short-slug` from conventional commits), that's a future task.
+6
agent-notes/2026-06-04-01-remove-claude-dec-glue.md
··· 1 1 # remove-claude-dec-glue 2 2 3 3 ## Task 4 + 4 5 Remove the Claude-Code-specific artifacts (`CLAUDE.md`, `.claude/` tree) that the deciduous installer wrote into the suede repo root. Verify that all substantive content is already mirrored in `.opencode/` or `AGENTS.md`. Fix one Claude-path reference that leaked into an OpenCode-side file. 5 6 6 7 ## Decisions 8 + 7 9 - Delete `CLAUDE.md` and the entire `.claude/` directory — both are Claude-Code-only integration glue (hook settings, Claude-flavor commands/skills/hooks), and all substantive content is already in `.opencode/` (commands, skills, plugins, tools) and `AGENTS.md` (Decision Graph Workflow section). 8 10 - Keep the `.gitignore` lines for `CLAUDE.md` and `.claude/` (defensive — prevents a future `deciduous update` from polluting git). 9 11 - Fix `.opencode/commands/decision.md:258` to point at `.opencode/` instead of `.claude/` so the example is correct for this project's layout. 10 12 - Leave the stock `deciduous` content alone (no `task-lifecycle` skill regeneration, no edits to `.opencode/{agents,plugins,tools}`). 11 13 12 14 ## Actions 15 + 13 16 - Surveyed `.opencode/`, `.claude/`, `CLAUDE.md`, `AGENTS.md`, and `.gitignore` to map every Claude-specific artifact to its OpenCode/suede equivalent. 14 17 - Deleted `suede/CLAUDE.md` (290-line deciduous-managed block — superseded by `AGENTS.md` "Decision Graph Workflow" section). 15 18 - Deleted `suede/.claude/` (agents.toml, settings.json, commands/, skills/, hooks/) — content mirrored in `.opencode/{commands,skills,plugins,tools,agents}` and `AGENTS.md`. 16 19 - Edited `.opencode/commands/decision.md:258` — changed example `git add .claude/commands/decision.md` to `git add .opencode/commands/decision.md` to match suede's layout. 17 20 18 21 ## Files touched 22 + 19 23 - `CLAUDE.md` — deleted 20 24 - `.claude/` — deleted (full tree: agents.toml, settings.json, commands/, skills/, hooks/) 21 25 - `.opencode/commands/decision.md` — line 258 path example corrected 22 26 23 27 ## Verification 28 + 24 29 - `ls -la suede/ | grep -i claude` — no matches 25 30 - `ls suede/.claude suede/CLAUDE.md` — both: No such file or directory 26 31 - `grep -rn "claude|\.claude|CLAUDE" suede/.opencode/` — no matches ··· 28 33 - `git status` — no new untracked files from this work; existing untracked `.opencode/*` and `opencode.json` are unchanged 29 34 30 35 ## Follow-ups / stubs 36 + 31 37 - `deciduous update` will likely re-create `CLAUDE.md` and `.claude/` on next run (by design in the deciduous installer). The defensive `.gitignore` lines prevent that from reaching git, but the files will reappear on disk. If regen suppression is wanted, that's a deciduous-side flag or a post-install cleanup hook — separate task. 32 38 - `.opencode/commands/build-test.md` uses `cargo build && cargo test` (stock deciduous template). Suede is pnpm + Vitest, not Rust. Not Claude-related, but the command is misaligned with this project. Out of scope here; flag for a future "suede-ize stock deciduous commands" task. 33 39 - The `## Decision Graph Workflow` section currently in `AGENTS.md` was recovered from a prior session's WIP (per the 2026-06-03-03 task note's follow-up). Confirmed it's intact.
+6
agent-notes/2026-06-04-02-decision-graph-for-suede.md
··· 1 1 # decision-graph-for-suede 2 2 3 3 ## Task 4 + 4 5 Run `/decision-graph` against the suede repository: build a deciduous decision graph from the repo's commit history, capturing the design evolution (process layer spine + UI scaffolding side narrative) with grounded node descriptions and complete edge threading. 5 6 6 7 ## Decisions 8 + 7 9 - Treat the **process layer** (AGENTS.md evolution, git workflow rule, release model, decision-graph integration, defensive gitignore) as the **spine** of the graph — the design question that keeps getting re-answered. 8 10 - Treat the **UI scaffolding** (bits-ui + stylebase wrappers) as a **side branch** from the root goal — independent track, branched from the same root, not from the process layer. 9 11 - Skip pure implementation commits: `344cafb` (Storybook dependency bump) is a routine upgrade with no model change; `fa5d685` (task note for the bootstrap) is a record, not a decision — captured as a small action node. ··· 13 15 - Cross-link the prior cleanup thread (nodes 1-5) to the `c273b91` outcome — "defensive gitignore was insufficient; full removal followed" — so a future reader sees the full arc. 14 16 15 17 ## Actions 18 + 16 19 - **Layer 1 (all commits)**: `git log --all --oneline | wc -l` → 11 commits. Read each commit's stat and full message. 17 20 - **Layer 2 (keyword)**: `git log --grep` for `bits|stylebase|chronver|AGENTS|stack|release|story|chrom|version|Sentry|Axiom|tag|Stack|Layout|bootstrap` → no commits missed. 18 21 - **Foundation observation** (node 7): stock SvelteKit template ships with separate stack.md and vendor-specific config. ··· 28 31 - Synced graph: `deciduous sync` → `docs/graph-data.json` (64 nodes, 77 edges). 29 32 30 33 ## Files touched 34 + 31 35 - `.deciduous/deciduous.db` — graph storage (local, gitignored) 32 36 - `docs/graph-data.json` — synced graph export (gitignored, regenerable) 33 37 - `agent-notes/2026-06-04-02-decision-graph-for-suede.md` — this task note 34 38 35 39 ## Verification 40 + 36 41 - `deciduous nodes` — 64 nodes: 2 goal, 25 option, 10 decision, 11 action, 8 outcome, 7 observation, 1 revisit 37 42 - `deciduous edges` — 77 edges: every chosen/rejected/leads_to edge has a reason string 38 43 - `deciduous pulse` — clean: 43 completed, 14 rejected, 7 pending (cleanup goal + 4 cleanup actions + revisit + pending hybrid-styling option) ··· 41 46 - Cross-link edge 77: `54 → 1 (leads_to)` — the defensive-gitignore outcome explicitly leads to the cleanup thread, capturing the full arc 42 47 43 48 ## Follow-ups / stubs 49 + 44 50 - The deferred `revisit` (node 63) and its `pending` option (node 64) capture the "next step" the 341d364 commit message called out: hybrid global data-attribute CSS + per-component overrides for the UI wrapper styling. When a future commit picks this up, mark the revisit `superseded` and the option `completed`, link to a new action. 45 51 - The 4e3f42e Releases section describes `pnpm version 2026.6.4` normalizing leading zeros; the 770b7d0 Decision Graph section, the c273b91 gitignore, and the cleanup all sit in the same PR (4 commits ahead of `origin/feat/install-bits-ui-stylebase`). When the PR merges to `main`, a future session can add a single outcome node capturing "Suede v2026.6.4 released; PR merged; tag applied" — to close the loop on the Releases section. 46 52 - The cross-link edge `54 → 1` is the only connection between the two graph threads (cleanup and repo-design). If a future task grows either thread, that edge may need a sibling (e.g., a `revisit` on the "should we commit CLAUDE.md after all" question, given deciduous's installer behavior).
+11 -2
agent-notes/2026-06-04-03-suede-button-scaffold.md
··· 1 1 # suede-button-scaffold 2 2 3 3 ## Task 4 + 4 5 Scaffold a Suede-wrapped bits-ui Button (`SuedeButton`) that demonstrates the boundary "bits-ui provides functionality, suede (stylebase + scoped CSS) provides style/content." Demo lives in Storybook only (no route page); four story variants (Primary, Disabled, As link, With class override). Goal: a learning scaffold the user can read top-to-bottom to internalize the bits-ui + stylebase + Svelte 5 runes pattern. 5 6 6 7 ## Decisions 8 + 7 9 - **Storybook only, Button only** (Q1) — user explicitly dropped the Accordion from scope; route-page demo deferred. 8 10 - **Option C for the existing example stories** (Q2) — keep the auto-generated Storybook Button/Header/Page example content; name the new component `SuedeButton.svelte` (not `Button.svelte`) to avoid path collision. The user expects to delete the examples later. 9 11 - **Approach 2 for the wrapper prop pattern** (Q3) — full HTML attribute spread (`{...rest}`) over bits-ui's `Button.Root`. Plus **Option A** for the type issue: `Props = ButtonRootProps` (the bits-ui-exported union), with `{...rest as Record<string, unknown>}` as the local escape hatch on the spread line. 10 12 - **Utility classes + scoped `<style>` mix inside the component** (Q4, last user message) — `u:fs-1` font-size utility from stylebase in the `class` attribute; everything else (color, padding, radius, hover/active/disabled) in a scoped `<style>` block. Component is usable as-is with sensible defaults; user can override via `class`/`style` props or by adding their own scoped styles at the consumer. 11 13 - **Skip the spec doc** — this is a single-file scaffold; the brainstorming skill's "design can be short for truly simple projects" applies. Plan was presented in chat and approved. 12 - - **Skip pnpm lint at the project level** — the project has 30+ files with pre-existing prettier drift (AGENTS.md, opencode.json, .opencode/*, .storybook/*, agent-notes/*, src/stories/{Button,Header,Page,Configure}.*, etc.). Reformatted only the two new files. Reformatting the rest is unrelated work and was deferred. 14 + - **Skip pnpm lint at the project level** — the project has 30+ files with pre-existing prettier drift (AGENTS.md, opencode.json, .opencode/_, .storybook/_, agent-notes/_, src/stories/{Button,Header,Page,Configure}._, etc.). Reformatted only the two new files. Reformatting the rest is unrelated work and was deferred. 13 15 14 16 ## Actions 17 + 15 18 - Read `package.json`, `src/app.css`, `src/routes/+page.svelte`, `src/lib/components/ui/` (empty), `src/stories/Button.stories.svelte` (already references non-existent `$lib/components/ui/Button.svelte`), `.storybook/{main,preview}.ts`, and bits-ui's `ButtonRootProps` type def at `node_modules/.pnpm/bits-ui@2.18.1_*/node_modules/bits-ui/dist/bits/button/types.d.ts` to confirm the `AnchorElement | ButtonElement` union shape that drives the rest-spread type issue. 16 19 - Read stylebase's `dist/stylebase.min.css` to map the utility surface: `u:fs-0..10` (font size), `l:{repel,river,root,waterfall,ui-list}` (layout), and CSS custom properties for the rest (no Tailwind-style atomic utilities for color/spacing/border-radius). 17 20 - Created deciduous goal node 67 with the user's verbatim prompt + planning refinements; branch `feat/install-bits-ui-stylebase`. ··· 39 42 - 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), action 76 + outcome 77 (link-variant styling), all linked to goal 67. 40 43 41 44 ## Files touched 45 + 42 46 - `src/lib/components/ui/SuedeButton.svelte` — new (52 lines) 43 47 - `src/stories/SuedeButton.stories.svelte` — new (36 lines) 44 48 45 49 ## Merge into main 50 + 46 51 After the work was pushed, the user opened the PR and reported a conflict against main. Pulled `origin/main` and merged with `git merge origin/main` — git's `ort` strategy auto-resolved the 3-way merge with no manual intervention. Only one commit (`d5a9380 chore(process): add git workflow rule, drop Sentry/Axiom/agent tools from stack mentions`) had diverged from my branch; it touched three files, all of which I had a clean ancestor for or didn't have at all: 52 + 47 53 - `AGENTS.md` — auto-merged (no overlap with my changes; I never touched this file) 48 54 - `.opencode/skills/task-lifecycle/SKILL.md` — auto-merged (my copy was committed in `03c6031`; main's copy added a `## Before starting` section + commit-trailer + branch rules that don't conflict with mine) 49 55 - `agent-notes/2026-06-03-02-process-updates.md` — new on main, just taken as-is ··· 51 57 Pushed the merge commit (`8ff5a7b`) to `origin/feat/install-bits-ui-stylebase`. `pnpm check` still 0 errors / 0 warnings project-wide post-merge. Ready for human review-and-merge to main. 52 58 53 59 ### PR-viewer conflict report (false positive) 60 + 54 61 The user reported the PR viewer was still flagging a conflict after the merge push, at "line 31 of AGENTS.md". Investigated: locally git reports zero conflict. The actual diff between main and my branch is a pure addition (`40a41,318` — main ends at line 40, my branch adds lines 41-318 with the `## Releases` and `## Decision Graph Workflow` sections). Line 31 of AGENTS.md ("Agents own (TypeScript only):") is byte-identical on both branches (verified with `od -c`); the only thing the PR viewer could possibly be flagging is the hunk boundary in the naive diff view, not an actual merge conflict. 55 62 56 63 Best guess: Tangled's PR view uses a different merge analysis (naive diff rather than 3-way) and flags any non-trivial AGENTS.md divergence as a conflict indicator. Resolved by `git rebase origin/main` — 23 commits replayed on top of main with no conflicts, producing a linear history with no merge commit. `pnpm check` still 0/0. Force-pushed (`f1645ba`) to update the remote. The PR should now show a clean linear diff rather than the merge-commit structure that may have confused the viewer. 57 64 58 65 ## Verification 66 + 59 67 - `pnpm check` — **0 errors / 0 warnings project-wide**. 60 68 - `pnpm prettier --check` on new files — pass 61 69 - `pnpm eslint` on new files — pass ··· 64 72 - `git log -5` — `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()` + `27f3a6c feat(ui): render anchor variant as a link, not a button` on `feat/install-bits-ui-stylebase` 65 73 66 74 ## Follow-ups / stubs 75 + 67 76 - **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. 68 - - **Pre-existing prettier drift** in 30+ files (AGENTS.md, opencode.json, .opencode/*, etc.). Not in scope for this change; consider a separate `chore(format): run prettier` commit. 77 + - **Pre-existing prettier drift** in 30+ files (AGENTS.md, opencode.json, .opencode/\*, etc.). Not in scope for this change; consider a separate `chore(format): run prettier` commit. 69 78 - **CSS scope caveat**: the `<!-- svelte-ignore css_unused_selector -->` comment is currently scoped to the whole `<style>` block. If a future user adds a new rule with a class name that IS used statically in the template, the ignore is over-broad. Consider moving to per-rule ignore once more selectors exist. 70 79 - **The `{...rest as Record<string, unknown>}` cast** is a known escape hatch we discussed. If bits-ui ever exports a `WithoutButtonRest` helper, swap the cast for that. The cast preserves runtime behavior — every HTML attribute the consumer passes flows through to `Button.Root` correctly; the union is what blocks TypeScript, not what the runtime does. 71 80 - **`ref` is not forwarded** in this version of the wrapper. If a future consumer needs the underlying DOM element, add `let { ref = $bindable(null), ... }: Props = $props()` and `bind:this={ref}` on `<Button.Root>`. Deferred — keeping the scaffold minimal.
+1 -1
agent-notes/2026-06-05-05-kill-superpowers-plugin.md
··· 57 57 effect — config is loaded once at startup. 58 58 - Branch `chore/kill-superpowers-plugin` is ready for human review/commit. 59 59 Suggested commit: `chore(config): remove superpowers plugin + add working style 60 - to AGENTS.md` with `Co-authored-by: opencode <noreply@opencode.ai>` trailer. 60 + to AGENTS.md` with `Co-authored-by: opencode <noreply@opencode.ai>` trailer. 61 61 - Preexisting prettier issues across the repo are a separate cleanup task; 62 62 out of scope here.
+1 -1
agent-notes/2026-06-05-06-cut-first-official-version.md
··· 19 19 - **Versioning rule goes in the Releases intro paragraph, not a new 20 20 subsection.** One bold sentence plus a pointer to "Cutting a release" is 21 21 enough. The mechanics already say "final commit is `chore(release): cut 22 - <version>`" — the rule just elevates the principle. 22 + <version>`" — the rule just elevates the principle. 23 23 24 24 ## Actions 25 25
+1
eslint.config.js
··· 20 20 svelte.configs.recommended, 21 21 prettier, 22 22 svelte.configs.prettier, 23 + ...storybook.configs['flat/recommended'], 23 24 { 24 25 languageOptions: { globals: { ...globals.browser, ...globals.node } }, 25 26 rules: {
+3 -5
opencode.json
··· 1 1 { 2 - "$schema": "https://opencode.ai/config.json", 3 - "instructions": [ 4 - "AGENTS.md" 5 - ] 6 - } 2 + "$schema": "https://opencode.ai/config.json", 3 + "instructions": ["AGENTS.md"] 4 + }
+22 -22
src/stories/Button.svelte
··· 1 1 <script lang="ts"> 2 - import './button.css'; 2 + import './button.css'; 3 3 4 - interface Props { 5 - /** Is this the principal call to action on the page? */ 6 - primary?: boolean; 7 - /** What background color to use */ 8 - backgroundColor?: string; 9 - /** How large should the button be? */ 10 - size?: 'small' | 'medium' | 'large'; 11 - /** Button contents */ 12 - label: string; 13 - /** The onclick event handler */ 14 - onclick?: () => void; 15 - } 4 + interface Props { 5 + /** Is this the principal call to action on the page? */ 6 + primary?: boolean; 7 + /** What background color to use */ 8 + backgroundColor?: string; 9 + /** How large should the button be? */ 10 + size?: 'small' | 'medium' | 'large'; 11 + /** Button contents */ 12 + label: string; 13 + /** The onclick event handler */ 14 + onclick?: () => void; 15 + } 16 + 17 + const { primary = false, backgroundColor, size = 'medium', label, ...props }: Props = $props(); 16 18 17 - const { primary = false, backgroundColor, size = 'medium', label, ...props }: Props = $props(); 18 - 19 - let mode = $derived(primary ? 'storybook-button--primary' : 'storybook-button--secondary'); 20 - let style = $derived(backgroundColor ? `background-color: ${backgroundColor}` : ''); 19 + let mode = $derived(primary ? 'storybook-button--primary' : 'storybook-button--secondary'); 20 + let style = $derived(backgroundColor ? `background-color: ${backgroundColor}` : ''); 21 21 </script> 22 22 23 23 <button 24 - type="button" 25 - class={['storybook-button', `storybook-button--${size}`, mode].join(' ')} 26 - {style} 27 - {...props} 24 + type="button" 25 + class={['storybook-button', `storybook-button--${size}`, mode].join(' ')} 26 + {style} 27 + {...props} 28 28 > 29 - {label} 29 + {label} 30 30 </button>
+36 -31
src/stories/Configure.mdx
··· 1 - import { Meta } from "@storybook/addon-docs/blocks"; 1 + import { Meta } from '@storybook/addon-docs/blocks'; 2 2 3 - import Github from "./assets/github.svg"; 4 - import Discord from "./assets/discord.svg"; 5 - import Youtube from "./assets/youtube.svg"; 6 - import Tutorials from "./assets/tutorials.svg"; 7 - import Styling from "./assets/styling.png"; 8 - import Context from "./assets/context.png"; 9 - import Assets from "./assets/assets.png"; 10 - import Docs from "./assets/docs.png"; 11 - import Share from "./assets/share.png"; 12 - import FigmaPlugin from "./assets/figma-plugin.png"; 13 - import Testing from "./assets/testing.png"; 14 - import Accessibility from "./assets/accessibility.png"; 15 - import Theming from "./assets/theming.png"; 16 - import AddonLibrary from "./assets/addon-library.png"; 3 + import Github from './assets/github.svg'; 4 + import Discord from './assets/discord.svg'; 5 + import Youtube from './assets/youtube.svg'; 6 + import Tutorials from './assets/tutorials.svg'; 7 + import Styling from './assets/styling.png'; 8 + import Context from './assets/context.png'; 9 + import Assets from './assets/assets.png'; 10 + import Docs from './assets/docs.png'; 11 + import Share from './assets/share.png'; 12 + import FigmaPlugin from './assets/figma-plugin.png'; 13 + import Testing from './assets/testing.png'; 14 + import Accessibility from './assets/accessibility.png'; 15 + import Theming from './assets/theming.png'; 16 + import AddonLibrary from './assets/addon-library.png'; 17 17 18 - export const RightArrow = () => <svg 19 - viewBox="0 0 14 14" 20 - width="8px" 21 - height="14px" 22 - style={{ 23 - marginLeft: '4px', 24 - display: 'inline-block', 25 - shapeRendering: 'inherit', 26 - verticalAlign: 'middle', 27 - fill: 'currentColor', 28 - 'path fill': 'currentColor' 29 - }} 30 - > 31 - <path d="m11.1 7.35-5.5 5.5a.5.5 0 0 1-.7-.7L10.04 7 4.9 1.85a.5.5 0 1 1 .7-.7l5.5 5.5c.2.2.2.5 0 .7Z" /> 32 - </svg> 18 + export const RightArrow = () => ( 19 + <svg 20 + viewBox="0 0 14 14" 21 + width="8px" 22 + height="14px" 23 + style={{ 24 + marginLeft: '4px', 25 + display: 'inline-block', 26 + shapeRendering: 'inherit', 27 + verticalAlign: 'middle', 28 + fill: 'currentColor', 29 + 'path fill': 'currentColor' 30 + }} 31 + > 32 + <path d="m11.1 7.35-5.5 5.5a.5.5 0 0 1-.7-.7L10.04 7 4.9 1.85a.5.5 0 1 1 .7-.7l5.5 5.5c.2.2.2.5 0 .7Z" /> 33 + </svg> 34 + ); 33 35 34 36 <Meta title="Configure your project" /> 35 37 ··· 38 40 # Configure your project 39 41 40 42 Because Storybook works separately from your app, you'll need to configure it for your specific stack and setup. Below, explore guides for configuring Storybook with popular frameworks and tools. If you get stuck, learn how you can ask for help from our community. 43 + 41 44 </div> 42 45 <div className="sb-section"> 43 46 <div className="sb-section-item"> ··· 87 90 # Do more with Storybook 88 91 89 92 Now that you know the basics, let's explore other parts of Storybook that will improve your experience. This list is just to get you started. You can customise Storybook in many ways to fit your needs. 93 + 90 94 </div> 91 95 92 96 <div className="sb-section"> ··· 217 221 target="_blank" 218 222 >Discover tutorials<RightArrow /></a> 219 223 </div> 224 + 220 225 </div> 221 226 222 227 <style> 223 - {` 228 + {` 224 229 .sb-container { 225 230 margin-bottom: 48px; 226 231 }
+19 -19
src/stories/Header.stories.svelte
··· 1 1 <script module> 2 - import { defineMeta } from '@storybook/addon-svelte-csf'; 3 - import Header from './Header.svelte'; 4 - import { fn } from 'storybook/test'; 2 + import { defineMeta } from '@storybook/addon-svelte-csf'; 3 + import Header from './Header.svelte'; 4 + import { fn } from 'storybook/test'; 5 5 6 - // More on how to set up stories at: https://storybook.js.org/docs/writing-stories 7 - const { Story } = defineMeta({ 8 - title: 'Example/Header', 9 - component: Header, 10 - // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs 11 - tags: ['autodocs'], 12 - parameters: { 13 - // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout 14 - layout: 'fullscreen', 15 - }, 16 - args: { 17 - onLogin: fn(), 18 - onLogout: fn(), 19 - onCreateAccount: fn(), 20 - } 21 - }); 6 + // More on how to set up stories at: https://storybook.js.org/docs/writing-stories 7 + const { Story } = defineMeta({ 8 + title: 'Example/Header', 9 + component: Header, 10 + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs 11 + tags: ['autodocs'], 12 + parameters: { 13 + // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout 14 + layout: 'fullscreen' 15 + }, 16 + args: { 17 + onLogin: fn(), 18 + onLogout: fn(), 19 + onCreateAccount: fn() 20 + } 21 + }); 22 22 </script> 23 23 24 24 <Story name="Logged In" args={{ user: { name: 'Jane Doe' } }} />
+38 -38
src/stories/Header.svelte
··· 1 1 <script lang="ts"> 2 - import './header.css'; 3 - import Button from './Button.svelte'; 2 + import './header.css'; 3 + import Button from './Button.svelte'; 4 4 5 - interface Props { 6 - user?: { name: string }; 7 - onLogin?: () => void; 8 - onLogout?: () => void; 9 - onCreateAccount?: () => void; 10 - } 5 + interface Props { 6 + user?: { name: string }; 7 + onLogin?: () => void; 8 + onLogout?: () => void; 9 + onCreateAccount?: () => void; 10 + } 11 11 12 - const { user, onLogin, onLogout, onCreateAccount }: Props = $props(); 12 + const { user, onLogin, onLogout, onCreateAccount }: Props = $props(); 13 13 </script> 14 14 15 15 <header> 16 - <div class="storybook-header"> 17 - <div> 18 - <svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> 19 - <g fill="none" fill-rule="evenodd"> 20 - <path 21 - d="M10 0h12a10 10 0 0110 10v12a10 10 0 01-10 10H10A10 10 0 010 22V10A10 10 0 0110 0z" 22 - fill="#FFF" 23 - /> 24 - <path 25 - d="M5.3 10.6l10.4 6v11.1l-10.4-6v-11zm11.4-6.2l9.7 5.5-9.7 5.6V4.4z" 26 - fill="#555AB9" 27 - /> 28 - <path d="M27.2 10.6v11.2l-10.5 6V16.5l10.5-6zM15.7 4.4v11L6 10l9.7-5.5z" fill="#91BAF8" /> 29 - </g> 30 - </svg> 31 - <h1>Acme</h1> 32 - </div> 33 - <div> 34 - {#if user} 35 - <span class="welcome"> 36 - Welcome, <b>{user.name}</b>! 37 - </span> 38 - <Button size="small" onclick={onLogout} label="Log out" /> 39 - {:else} 40 - <Button size="small" onclick={onLogin} label="Log in" /> 41 - <Button primary size="small" onclick={onCreateAccount} label="Sign up" /> 42 - {/if} 43 - </div> 44 - </div> 16 + <div class="storybook-header"> 17 + <div> 18 + <svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> 19 + <g fill="none" fill-rule="evenodd"> 20 + <path 21 + d="M10 0h12a10 10 0 0110 10v12a10 10 0 01-10 10H10A10 10 0 010 22V10A10 10 0 0110 0z" 22 + fill="#FFF" 23 + /> 24 + <path 25 + d="M5.3 10.6l10.4 6v11.1l-10.4-6v-11zm11.4-6.2l9.7 5.5-9.7 5.6V4.4z" 26 + fill="#555AB9" 27 + /> 28 + <path d="M27.2 10.6v11.2l-10.5 6V16.5l10.5-6zM15.7 4.4v11L6 10l9.7-5.5z" fill="#91BAF8" /> 29 + </g> 30 + </svg> 31 + <h1>Acme</h1> 32 + </div> 33 + <div> 34 + {#if user} 35 + <span class="welcome"> 36 + Welcome, <b>{user.name}</b>! 37 + </span> 38 + <Button size="small" onclick={onLogout} label="Log out" /> 39 + {:else} 40 + <Button size="small" onclick={onLogin} label="Log in" /> 41 + <Button primary size="small" onclick={onCreateAccount} label="Sign up" /> 42 + {/if} 43 + </div> 44 + </div> 45 45 </header>
+23 -23
src/stories/Page.stories.svelte
··· 1 1 <script module> 2 - import { defineMeta } from '@storybook/addon-svelte-csf'; 3 - import { expect, userEvent, waitFor, within } from 'storybook/test'; 4 - import Page from './Page.svelte'; 5 - import { fn } from 'storybook/test'; 6 - 7 - // More on how to set up stories at: https://storybook.js.org/docs/writing-stories 8 - const { Story } = defineMeta({ 9 - title: 'Example/Page', 10 - component: Page, 11 - parameters: { 12 - // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout 13 - layout: 'fullscreen', 14 - }, 15 - }); 2 + import { defineMeta } from '@storybook/addon-svelte-csf'; 3 + import { expect, userEvent, waitFor, within } from 'storybook/test'; 4 + import Page from './Page.svelte'; 5 + // More on how to set up stories at: https://storybook.js.org/docs/writing-stories 6 + const { Story } = defineMeta({ 7 + title: 'Example/Page', 8 + component: Page, 9 + parameters: { 10 + // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout 11 + layout: 'fullscreen' 12 + } 13 + }); 16 14 </script> 17 15 18 - <Story name="Logged In" play={async ({ canvasElement }) => { 19 - const canvas = within(canvasElement); 20 - const loginButton = canvas.getByRole('button', { name: /Log in/i }); 21 - await expect(loginButton).toBeInTheDocument(); 22 - await userEvent.click(loginButton); 23 - await waitFor(() => expect(loginButton).not.toBeInTheDocument()); 16 + <Story 17 + name="Logged In" 18 + play={async ({ canvasElement }) => { 19 + const canvas = within(canvasElement); 20 + const loginButton = canvas.getByRole('button', { name: /Log in/i }); 21 + await expect(loginButton).toBeInTheDocument(); 22 + await userEvent.click(loginButton); 23 + await waitFor(() => expect(loginButton).not.toBeInTheDocument()); 24 24 25 - const logoutButton = canvas.getByRole('button', { name: /Log out/i }); 26 - await expect(logoutButton).toBeInTheDocument(); 27 - }} 25 + const logoutButton = canvas.getByRole('button', { name: /Log out/i }); 26 + await expect(logoutButton).toBeInTheDocument(); 27 + }} 28 28 /> 29 29 30 30 <Story name="Logged Out" />
+61 -61
src/stories/Page.svelte
··· 1 1 <script lang="ts"> 2 - import './page.css'; 3 - import Header from './Header.svelte'; 2 + import './page.css'; 3 + import Header from './Header.svelte'; 4 4 5 - let user = $state<{ name: string }>(); 5 + let user = $state<{ name: string }>(); 6 6 </script> 7 7 8 8 <article> 9 - <Header 10 - {user} 11 - onLogin={() => (user = { name: 'Jane Doe' })} 12 - onLogout={() => (user = undefined)} 13 - onCreateAccount={() => (user = { name: 'Jane Doe' })} 14 - /> 9 + <Header 10 + {user} 11 + onLogin={() => (user = { name: 'Jane Doe' })} 12 + onLogout={() => (user = undefined)} 13 + onCreateAccount={() => (user = { name: 'Jane Doe' })} 14 + /> 15 15 16 - <section class="storybook-page"> 17 - <h2>Pages in Storybook</h2> 18 - <p> 19 - We recommend building UIs with a 20 - <a 21 - href="https://blog.hichroma.com/component-driven-development-ce1109d56c8e" 22 - target="_blank" 23 - rel="noopener noreferrer" 24 - > 25 - <strong>component-driven</strong> 26 - </a> 27 - process starting with atomic components and ending with pages. 28 - </p> 29 - <p> 30 - Render pages with mock data. This makes it easy to build and review page states without 31 - needing to navigate to them in your app. Here are some handy patterns for managing page data 32 - in Storybook: 33 - </p> 34 - <ul> 35 - <li> 36 - Use a higher-level connected component. Storybook helps you compose such data from the 37 - "args" of child component stories 38 - </li> 39 - <li> 40 - Assemble data in the page component from your services. You can mock these services out 41 - using Storybook. 42 - </li> 43 - </ul> 44 - <p> 45 - Get a guided tutorial on component-driven development at 46 - <a href="https://storybook.js.org/tutorials/" target="_blank" rel="noopener noreferrer"> 47 - Storybook tutorials 48 - </a> 49 - . Read more in the 50 - <a href="https://storybook.js.org/docs" target="_blank" rel="noopener noreferrer">docs</a> 51 - . 52 - </p> 53 - <div class="tip-wrapper"> 54 - <span class="tip">Tip</span> 55 - Adjust the width of the canvas with the 56 - <svg width="10" height="10" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"> 57 - <g fill="none" fill-rule="evenodd"> 58 - <path 59 - d="M1.5 5.2h4.8c.3 0 .5.2.5.4v5.1c-.1.2-.3.3-.4.3H1.4a.5.5 0 16 + <section class="storybook-page"> 17 + <h2>Pages in Storybook</h2> 18 + <p> 19 + We recommend building UIs with a 20 + <a 21 + href="https://blog.hichroma.com/component-driven-development-ce1109d56c8e" 22 + target="_blank" 23 + rel="noopener noreferrer" 24 + > 25 + <strong>component-driven</strong> 26 + </a> 27 + process starting with atomic components and ending with pages. 28 + </p> 29 + <p> 30 + Render pages with mock data. This makes it easy to build and review page states without 31 + needing to navigate to them in your app. Here are some handy patterns for managing page data 32 + in Storybook: 33 + </p> 34 + <ul> 35 + <li> 36 + Use a higher-level connected component. Storybook helps you compose such data from the 37 + "args" of child component stories 38 + </li> 39 + <li> 40 + Assemble data in the page component from your services. You can mock these services out 41 + using Storybook. 42 + </li> 43 + </ul> 44 + <p> 45 + Get a guided tutorial on component-driven development at 46 + <a href="https://storybook.js.org/tutorials/" target="_blank" rel="noopener noreferrer"> 47 + Storybook tutorials 48 + </a> 49 + . Read more in the 50 + <a href="https://storybook.js.org/docs" target="_blank" rel="noopener noreferrer">docs</a> 51 + . 52 + </p> 53 + <div class="tip-wrapper"> 54 + <span class="tip">Tip</span> 55 + Adjust the width of the canvas with the 56 + <svg width="10" height="10" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"> 57 + <g fill="none" fill-rule="evenodd"> 58 + <path 59 + d="M1.5 5.2h4.8c.3 0 .5.2.5.4v5.1c-.1.2-.3.3-.4.3H1.4a.5.5 0 60 60 01-.5-.4V5.7c0-.3.2-.5.5-.5zm0-2.1h6.9c.3 0 .5.2.5.4v7a.5.5 0 01-1 0V4H1.5a.5.5 0 61 61 010-1zm0-2.1h9c.3 0 .5.2.5.4v9.1a.5.5 0 01-1 0V2H1.5a.5.5 0 010-1zm4.3 5.2H2V10h3.8V6.2z" 62 - id="a" 63 - fill="#999" 64 - /> 65 - </g> 66 - </svg> 67 - Viewports addon in the toolbar 68 - </div> 69 - </section> 62 + id="a" 63 + fill="#999" 64 + /> 65 + </g> 66 + </svg> 67 + Viewports addon in the toolbar 68 + </div> 69 + </section> 70 70 </article>
+18 -18
src/stories/button.css
··· 1 1 .storybook-button { 2 - display: inline-block; 3 - cursor: pointer; 4 - border: 0; 5 - border-radius: 3em; 6 - font-weight: 700; 7 - line-height: 1; 8 - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 2 + display: inline-block; 3 + cursor: pointer; 4 + border: 0; 5 + border-radius: 3em; 6 + font-weight: 700; 7 + line-height: 1; 8 + font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 9 9 } 10 10 .storybook-button--primary { 11 - background-color: #555ab9; 12 - color: white; 11 + background-color: #555ab9; 12 + color: white; 13 13 } 14 14 .storybook-button--secondary { 15 - box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset; 16 - background-color: transparent; 17 - color: #333; 15 + box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset; 16 + background-color: transparent; 17 + color: #333; 18 18 } 19 19 .storybook-button--small { 20 - padding: 10px 16px; 21 - font-size: 12px; 20 + padding: 10px 16px; 21 + font-size: 12px; 22 22 } 23 23 .storybook-button--medium { 24 - padding: 11px 20px; 25 - font-size: 14px; 24 + padding: 11px 20px; 25 + font-size: 14px; 26 26 } 27 27 .storybook-button--large { 28 - padding: 12px 24px; 29 - font-size: 16px; 28 + padding: 12px 24px; 29 + font-size: 16px; 30 30 }
+18 -18
src/stories/header.css
··· 1 1 .storybook-header { 2 - display: flex; 3 - justify-content: space-between; 4 - align-items: center; 5 - border-bottom: 1px solid rgba(0, 0, 0, 0.1); 6 - padding: 15px 20px; 7 - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 2 + display: flex; 3 + justify-content: space-between; 4 + align-items: center; 5 + border-bottom: 1px solid rgba(0, 0, 0, 0.1); 6 + padding: 15px 20px; 7 + font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 8 8 } 9 9 10 10 .storybook-header svg { 11 - display: inline-block; 12 - vertical-align: top; 11 + display: inline-block; 12 + vertical-align: top; 13 13 } 14 14 15 15 .storybook-header h1 { 16 - display: inline-block; 17 - vertical-align: top; 18 - margin: 6px 0 6px 10px; 19 - font-weight: 700; 20 - font-size: 20px; 21 - line-height: 1; 16 + display: inline-block; 17 + vertical-align: top; 18 + margin: 6px 0 6px 10px; 19 + font-weight: 700; 20 + font-size: 20px; 21 + line-height: 1; 22 22 } 23 23 24 24 .storybook-header button + button { 25 - margin-left: 10px; 25 + margin-left: 10px; 26 26 } 27 27 28 28 .storybook-header .welcome { 29 - margin-right: 10px; 30 - color: #333; 31 - font-size: 14px; 29 + margin-right: 10px; 30 + color: #333; 31 + font-size: 14px; 32 32 }
+39 -39
src/stories/page.css
··· 1 1 .storybook-page { 2 - margin: 0 auto; 3 - padding: 48px 20px; 4 - max-width: 600px; 5 - color: #333; 6 - font-size: 14px; 7 - line-height: 24px; 8 - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 2 + margin: 0 auto; 3 + padding: 48px 20px; 4 + max-width: 600px; 5 + color: #333; 6 + font-size: 14px; 7 + line-height: 24px; 8 + font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 9 9 } 10 10 11 11 .storybook-page h2 { 12 - display: inline-block; 13 - vertical-align: top; 14 - margin: 0 0 4px; 15 - font-weight: 700; 16 - font-size: 32px; 17 - line-height: 1; 12 + display: inline-block; 13 + vertical-align: top; 14 + margin: 0 0 4px; 15 + font-weight: 700; 16 + font-size: 32px; 17 + line-height: 1; 18 18 } 19 19 20 20 .storybook-page p { 21 - margin: 1em 0; 21 + margin: 1em 0; 22 22 } 23 23 24 24 .storybook-page a { 25 - color: inherit; 25 + color: inherit; 26 26 } 27 27 28 28 .storybook-page ul { 29 - margin: 1em 0; 30 - padding-left: 30px; 29 + margin: 1em 0; 30 + padding-left: 30px; 31 31 } 32 32 33 33 .storybook-page li { 34 - margin-bottom: 8px; 34 + margin-bottom: 8px; 35 35 } 36 36 37 37 .storybook-page .tip { 38 - display: inline-block; 39 - vertical-align: top; 40 - margin-right: 10px; 41 - border-radius: 1em; 42 - background: #e7fdd8; 43 - padding: 4px 12px; 44 - color: #357a14; 45 - font-weight: 700; 46 - font-size: 11px; 47 - line-height: 12px; 38 + display: inline-block; 39 + vertical-align: top; 40 + margin-right: 10px; 41 + border-radius: 1em; 42 + background: #e7fdd8; 43 + padding: 4px 12px; 44 + color: #357a14; 45 + font-weight: 700; 46 + font-size: 11px; 47 + line-height: 12px; 48 48 } 49 49 50 50 .storybook-page .tip-wrapper { 51 - margin-top: 40px; 52 - margin-bottom: 40px; 53 - font-size: 13px; 54 - line-height: 20px; 51 + margin-top: 40px; 52 + margin-bottom: 40px; 53 + font-size: 13px; 54 + line-height: 20px; 55 55 } 56 56 57 57 .storybook-page .tip-wrapper svg { 58 - display: inline-block; 59 - vertical-align: top; 60 - margin-top: 3px; 61 - margin-right: 4px; 62 - width: 12px; 63 - height: 12px; 58 + display: inline-block; 59 + vertical-align: top; 60 + margin-top: 3px; 61 + margin-right: 4px; 62 + width: 12px; 63 + height: 12px; 64 64 } 65 65 66 66 .storybook-page .tip-wrapper svg path { 67 - fill: #1ea7fd; 67 + fill: #1ea7fd; 68 68 }
-1
vite.config.ts
··· 1 - /// <reference types="vitest/config" /> 2 1 import { defineConfig } from 'vitest/config'; 3 2 import { playwright } from '@vitest/browser-playwright'; 4 3 import { sveltekit } from '@sveltejs/kit/vite';
+1 -1
vitest.shims.d.ts
··· 1 - /// <reference types="@vitest/browser-playwright" /> 1 + /// <reference types="@vitest/browser-playwright" />