Git commit and pull request guidelines using conventional commits. Use when creating commits, writing commit messages, creating PRs, or reviewing PR descriptions.
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
feat: New features (correlates with MINOR in semantic versioning)fix: Bug fixes (correlates with PATCH in semantic versioning)docs: Documentation only changesrefactor: Code changes that neither fix bugs nor add featuresperf: Performance improvementstest: Adding or modifying testschore: Maintenance tasks, dependency updates, etc.style: Code style changes (formatting, missing semicolons, etc.)build: Changes to build system or dependenciesci: Changes to CI configuration files and scriptsfeat(transcription):EditRecordingDialog), feature areas (transcription, sound)ui or backend unless truly appropriate! after type/scope, before colon: feat(api)!: change endpoint structureBREAKING CHANGE: in the footer with detailsfeat(transcription): add model selection for OpenAI providersfix(sound): resolve audio import paths in assets modulerefactor(EditRecordingDialog): implement working copy patterndocs(README): clarify cost comparison sectionchore: update dependencies to latest versionsfix!: change default transcription API endpointThe commit message subject line describes WHAT changed. The commit body explains WHY.
Good commit (explains motivation):
fix(auth): prevent session timeout during file upload
Users were getting logged out mid-upload on large files because the
session refresh only triggered on navigation, not background activity.
Bad commit (only describes what):
fix(auth): add keepalive call to upload handler
The first commit tells future developers WHY the code exists. The second makes them dig through the code to understand the purpose.
Every PR description MUST open with a crisp one-sentence summary of WHAT changed, then immediately explain WHY. The WHAT grounds the reader; the WHY gives them the motivation.
Good PR opening:
Redesigns the
createTaggedErrorbuilder: flat.withFields()API replaces nested.withContext()/.withCause(),.withMessage()is optional and seals the message.Analysis of 321 error call sites revealed every error is always all-or-nothing on message ownership. The old API allowed overriding
.withMessage()at the call site, which masked design problems rather than solving them.
Bad opening (why without what):
Users were getting logged out mid-upload on large files. The session refresh only triggered on navigation, not during background activity like uploads.
Bad opening (what without why):
This PR adds a keepalive call to the upload handler and updates the session refresh logic.
The reader should understand WHAT changed before they understand WHY — but they need both.
If the PR introduces or modifies APIs, you MUST include code examples showing how to use them. No exceptions.
What requires code examples:
Good API PR (shows the actual usage):
// Define actions once
const actions = {
posts: {
create: defineMutation({
input: type({ title: 'string' }),
handler: ({ title }) => client.tables.posts.create({ title }),
}),
},
};
// Pass to adapters - they generate CLI commands and HTTP routes
const cli = createCLI(client, { actions });
const server = createServer(client, { actions });
Bad API PR (only describes without showing):
This PR adds an action system that generates CLI commands and HTTP routes from action definitions.
The first version lets reviewers understand the API at a glance. The second forces them to dig through the code to understand the call sites.
Code examples aren't just for API changes. For internal refactors that change how code is structured without changing the public API, before/after code snippets show reviewers the improvement concretely:
// BEFORE: direct YKeyValueLww usage with manual scanning
const ykv = new YKeyValueLww<unknown>(yarray);
function reconstructRow(rowId) { // O(n) - scan every cell
for (const [key, entry] of ykv.map) {
if (key.startsWith(prefix)) { ... }
}
}
// AFTER: composed storage layers
const cellStore = createCellStore<unknown>(ydoc, TableKey(tableId));
const rowStore = createRowStore(cellStore);
rowStore.has(id) // O(1)
rowStore.get(id) // O(m) where m = fields per row
rowStore.count() // O(1)
Use before/after snippets when:
Use ASCII diagrams liberally to communicate complex ideas. They're more scannable than prose and show relationships at a glance.
For PRs that iterate on previous work, show the evolution:
┌─────────────────────────────────────────────────────────────────────────┐
│ PR #1217 (Jan 7) │
│ "Add YKeyValue for 1935x storage improvement" │
│ │
│ Y.Map (524,985 bytes) ──→ YKeyValue (271 bytes) │
│ │
└───────────────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ PR #1226 (Jan 8) │
│ "Remove YKeyValue, use native Y.Map + epoch compaction" │
│ │
│ Reasoning: "Unpredictable LWW behavior" ← ⚠️ (misleading!) │
│ │
└───────────────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ This PR │
│ "Restore YKeyValue with LWW timestamps" │
│ │
│ Why: Timestamp-based resolution gives intuitive "latest wins" │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Show how components stack:
┌─────────────────────────────────────────────────────────────┐
│ defineWorkspace() + workspace.create() │ ← High-level
│ Creates Y.Doc internally, binds tables/kv/capabilities │
├─────────────────────────────────────────────────────────────┤
│ createTables(ydoc, {...}) / createKv(ydoc, {...}) │ ← Mid-level
│ Binds to existing Y.Doc │
├─────────────────────────────────────────────────────────────┤
│ defineTable() / defineKv() │ ← Low-level
│ Pure schema definitions │
└─────────────────────────────────────────────────────────────┘
For showing trade-offs between approaches:
┌────────────────────────────────────────────────────────────────┐
│ Use Case │ Recommendation │
├───────────────────────────────────┼────────────────────────────┤
│ Real-time collab, simple cases │ YKeyValue (positional) │
│ Offline-first, multi-device │ YKeyValueLww (timestamp) │
│ Clock sync unreliable │ YKeyValue (no clock dep) │
└────────────────────────────────────────────────────────────────┘
For showing data/control flow:
┌────────────────────────────────────────────────────────────────┐
│ Conflict Resolution │
├────────────────────────────────────────────────────────────────┤
│ │
│ Client A (2:00pm) ──┐ │
│ │──→ Sync ──→ Winner? │
│ Client B (3:00pm) ──┘ │
│ │ │
│ ┌────────────────┴────────────────┐ │
│ ▼ ▼ │
│ YKeyValue YKeyValueLww │
│ (clientID wins) (timestamp wins) │
│ ~50% correct 100% correct │
│ │
└────────────────────────────────────────────────────────────────┘
For refactors that change how modules compose, use lightweight indented tree notation instead of heavy box-drawing. This shows the dependency/composition hierarchy at a glance:
Before — one module doing everything:
TableHelper (schema + CRUD + row reconstruction + observers)
└── YKeyValueLww ← Map<"rowId:colId", entry>
├── reconstructRow() O(n) scan all keys for prefix
├── collectRows() O(n) group all cells by rowId
└── deleteRowCells() O(n) filter + delete
After — each layer has a single responsibility:
TableHelper (schema validation, typed CRUD, branded Id types)
└── RowStore (in-memory row index → O(1) has/count, O(m) get/delete)
└── CellStore (cell semantics: key parsing, typed change events)
└── YKeyValueLww (generic LWW conflict resolution primitive)
Key properties of composition trees:
└── for single children, ├── when siblings existWhen a refactor physically moves files and that relocation IS the architectural statement, show the move pattern as a tree. This is not "listing files changed" (which the skill forbids) — it's showing the structural reorganization:
packages/epicenter/src/
├── shared/
│ ├── y-cell-store.ts → dynamic/tables/y-cell-store.ts
│ └── y-row-store.ts → dynamic/tables/y-row-store.ts
└── dynamic/tables/
└── table-helper.ts (refactored to compose over the above)
Use file relocation trees when:
Do NOT use when:
ASCII art characters to use: ┌ ┐ └ ┘ ─ │ ├ ┤ ┬ ┴ ┼ ▼ ▲ ◀ ▶ ──→ ←── ⚠️ ✅ ❌
Never let prose run for more than a short paragraph without a visual break. The rhythm should be: context → visual → explanation → visual → ...
Each visual (code snippet, ASCII diagram, before/after block) should be preceded by 1-3 sentences of context and optionally followed by a sentence explaining the subtle detail. If you're writing more than 4-5 sentences of prose in a row, you're missing an opportunity for a diagram or code block.
Good rhythm — prose and visuals alternate naturally:
[1-2 sentences: what the problem is and why it matters]
\`\`\`typescript
// code example showing the new API
workspace.extensions.sync.reconnect(directAuth('https://cloud.example.com'));
\`\`\`
\`\`\`
┌────────────────────────────┐
│ Flow diagram showing how │──► what happens step by step
│ the pieces connect │
└────────────────────────────┘
\`\`\`
[1-2 sentences: explain a subtle implementation detail]
\`\`\`typescript
// before/after showing the fix
\`\`\`
[1 sentence: why the before was broken]
\`\`\`
┌──────────────────────────────────┐
│ Architecture diagram showing │
│ which parts are affected │
└──────────────────────────────────┘
\`\`\`
Bad rhythm — wall of text with visuals tacked on at the end:
[Paragraph explaining the problem]
[Paragraph explaining the solution]
[Paragraph explaining the implementation detail]
[Paragraph explaining another detail]
\`\`\`
[single diagram at the bottom]
\`\`\`
The reader's eye should bounce between prose and visuals. Prose provides the "why," visuals provide the "what" and "how." Neither should dominate for long stretches.
Before drafting a PR description, run a cursory search of open GitHub issues to identify any that the PR's changes may fix, partially address, or lay groundwork for:
# List open issues (scan titles for keywords matching the PR's scope)
gh issue list --state open --limit 100 --json number,title,labels
# Read a specific issue to check if the PR addresses it
gh issue view <NUMBER> --json title,body,labels,comments
What to look for:
How to reference in the PR description:
Closes #123 — only if the PR fully resolves the issuePartially addresses #123 — if the PR improves the situation but doesn't fully fix itLays groundwork for #123 — if the PR creates infrastructure that a future PR will use to fix the issueBe honest: Don't claim a fix unless the changes directly address the root cause. Improved error messages or internal refactors that happen to touch related code do not count as fixes.
CRITICAL: When mentioning GitHub users with @username in PR descriptions, issue comments, or any GitHub content, NEVER guess or assume usernames. Always verify programmatically using the GitHub CLI:
# Get the author of a PR
gh pr view <PR_NUMBER> --json author
# Get the author of an issue
gh issue view <ISSUE_NUMBER> --json author
This prevents embarrassing mistakes where you credit the wrong person. Always run the verification command before writing the @mention.
When merging PRs, use regular merge commits (NOT squash):
gh pr merge --merge # Correct: preserves commit history
# NOT: gh pr merge --squash
# NOT: gh pr merge --rebase
# Use --admin flag if needed to bypass branch protections
gh pr merge --merge --admin
Preserve individual commits; they tell the story of how the work evolved.
Use clean paragraph format:
First Paragraph: Explain what the change does and what problem it solves.
Subsequent Paragraphs: Explain how the implementation works.
Example:
This change enables proper vertical scrolling for drawer components when content exceeds the available drawer height. Previously, drawers with long content could overflow without proper scrolling behavior, making it difficult for users to access all content and resulting in poor mobile UX.
To accomplish this, I wrapped the `{@render children?.()}` in a `<div class="flex-1 overflow-y-auto">` container. The `flex-1` class ensures the content area takes up all remaining space after the fixed drag handle at the top, while `overflow-y-auto` enables vertical scrolling when the content height exceeds the available space.
For PRs that change APIs, storage structures, or architectural patterns, use this section order:
Opening sentence: A single crisp sentence summarizing WHAT changed — what was added, removed, or redesigned. This grounds the reader before the motivation.
Why paragraph: WHY this change exists. What problem does analysis reveal? What was the old design masking? End with a one-sentence decision summary if applicable: "We chose X over Y because Z."
Change bullets: A short bullet list of the specific changes — new APIs, removed patterns, behavioral differences. Bullets complement the prose here; they don't replace it.
API Migration: Before/after code examples showing the new usage. Mandatory for any API change.
Storage/Data Structure: ASCII diagrams showing before/after layouts for any structural changes.
Technical Details: Extension points, type definitions, configuration formats — with code examples.
"Why X?" sections: Use a named ### Why X? heading for each significant design decision that needs justification. Write as direct statements, not hedged observations.
Future Work: What could be re-added later, what's intentionally deferred.
(Optional) Changes Summary / Test Plan: If included, keep minimal and put at the very end. Most PRs don't need one.
Key principles:
When to use Architectural format:
When to use Simple format:
This fixes the long-standing issue with nested reactivity in state management.
First, some context: users have consistently found it cumbersome to create deeply reactive state. The current approach requires manual get/set properties, which doesn't feel sufficiently Svelte-like. Meanwhile, we want to move away from object mutation for future performance optimizations, but `obj = { ...obj, x: obj.x + 1 }` is ugly and creates overhead.
This PR introduces proxy-based reactivity that lets you write idiomatic JavaScript:
```javascript
let todos = $state([]);
todos.push({ done: false, text: 'Learn Svelte' }); // just works
```
Under the hood, we're using Proxies to lazily create signals as necessary. This gives us the ergonomics of mutation with the performance benefits of immutability.
Still TODO:
- Performance optimizations for large arrays
- Documentation updates
- Migration guide for existing codebases
This doubles down on Svelte's philosophy of writing less, more intuitive code while setting us up for the fine-grained reactivity improvements planned for v6.
Generated with [Claude Code](https://claude.ai/code)Co-Authored-By: Claude <[email protected]>Generated with [opencode](https://opencode.ai)Co-Authored-By: opencode <[email protected]>