Update release notes This skill handles the operational tasks of maintaining release notes in the tldraw/tldraw repo: adding new PR entries to and archiving releases when a new version is published. This skill runs from the tldraw-internal repo but operates on a local clone of tldraw/tldraw. Setup Before starting, clone the tldraw repo: All scripts take the repo path as their first argument. Run script commands from the repository root by replacing with the directory that contains this . All file edits happen in this clone. At the end, commit and push from the clone. Release cycle The SDK rel…

| wc -l | tr -d ' ')\n if [ \"$content_lines\" -gt 0 ]; then\n next_has_content=\"true\"\n fi\nfi\n\n# Determine source based on weeks since the last minor/major release.\n# The SDK releases every 4 weeks. During the first 3 weeks (development),\n# we gather PRs from main. At 3+ weeks (freeze week), we switch to production.\n#\n# Find the last minor or major release (skip patches like v4.3.1, v4.3.2)\nsource=\"main\"\nlast_minor_date=\"\"\nif [ -n \"$latest_tag\" ]; then\n # Get the .0 release for the current minor version (e.g., v4.4.0)\n minor_tag=$(echo \"$latest_tag\" | sed 's/^v//' | cut -d. -f1,2)\n minor_release_tag=\"v${minor_tag}.0\"\n # Get the publish date of that .0 release\n last_minor_date=$(gh release view \"$minor_release_tag\" -R tldraw/tldraw --json publishedAt -q '.publishedAt' 2>/dev/null || echo \"\")\n if [ -n \"$last_minor_date\" ]; then\n # Calculate weeks since the minor release\n release_epoch=$(date -jf \"%Y-%m-%dT%H:%M:%SZ\" \"$last_minor_date\" \"+%s\" 2>/dev/null || date -d \"$last_minor_date\" \"+%s\" 2>/dev/null || echo \"\")\n now_epoch=$(date \"+%s\")\n if [ -n \"$release_epoch\" ]; then\n days_since=$(( (now_epoch - release_epoch) / 86400 ))\n weeks_since=$(( days_since / 7 ))\n if [ \"$weeks_since\" -ge 3 ]; then\n source=\"production\"\n fi\n fi\n fi\nfi\n\n# The last tag is the latest release tag (used by get-new-prs-from-main.sh)\nlast_tag=\"${latest_tag}\"\n\n# Output JSON\ncat \u003c\u003cEOF\n{\n \"last_version\": \"${last_version}\",\n \"latest_release\": {\n \"tag\": \"${latest_tag}\",\n \"published_at\": \"${latest_date}\"\n },\n \"diff_branch\": \"${diff_branch}\",\n \"last_tag\": \"${last_tag}\",\n \"source\": \"${source}\",\n \"needs_archive\": ${needs_archive},\n \"archive_exists\": ${archive_exists},\n \"next_has_content\": ${next_has_content}\n}\nEOF\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":3942,"content_sha256":"cc8ba864081b3d89feaf0510ef3d660aa912d6db2dc470b2d808423b45034e47"},{"filename":"scripts/get-new-prs-from-main.sh","content":"#!/bin/bash\n# Find PR numbers on the main branch since a release tag.\n# Uses `git cherry` to compare patches between origin/main and the\n# release tag, excluding commits that were cherry-picked to production\n# and already shipped in the release.\n#\n# Usage: ./get-new-prs-from-main.sh \u003ctldraw-repo-dir> \u003crelease-tag>\n# Example: ./get-new-prs-from-main.sh /tmp/tldraw v4.4.0\n#\n# Output:\n# PR numbers (one per line) on stdout, then any commits\n# without PR numbers (hash + title) on stderr.\n#\n# Requires a local clone of tldraw/tldraw.\n\nset -e\n\nif [ -z \"$1\" ] || [ -z \"$2\" ]; then\n echo \"Usage: $0 \u003ctldraw-repo-dir> \u003crelease-tag>\" >&2\n echo \"Example: $0 /tmp/tldraw v4.4.0\" >&2\n exit 1\nfi\n\nTLDRAW_REPO=\"$1\"\nRELEASE_TAG=\"$2\"\n\ncd \"$TLDRAW_REPO\"\n\n# Fetch to ensure we have latest refs\ngit fetch origin main --quiet 2>/dev/null || true\n\n# Verify the tag exists\nif ! git rev-parse \"$RELEASE_TAG\" >/dev/null 2>&1; then\n echo \"Error: tag '$RELEASE_TAG' not found\" >&2\n exit 1\nfi\n\n# The release tag lives on the production branch, which has a separate\n# history from main (deploy commits, hotfixes, version bumps). Many\n# commits on main after the branch point were cherry-picked to production\n# and shipped in the release. We use `git cherry` to compare patches and\n# exclude commits that are already in the release — just like\n# get-new-prs.sh does for production.\n#\n# `git cherry \u003crelease-tag> origin/main` marks commits unique to main\n# with '+' and those already in the release with '-'.\ncommit_hashes=$(git cherry \"$RELEASE_TAG\" origin/main 2>/dev/null \\\n | grep '^+' \\\n | cut -d' ' -f2)\n\npr_numbers=()\nunmatched=()\n\nfor hash in $commit_hashes; do\n # Get the first line of the commit message\n title=$(git log -1 --pretty=format:%s \"$hash\" 2>/dev/null)\n\n # Skip [skip ci] commits (version bumps, deploys)\n if echo \"$title\" | grep -q '\\[skip ci\\]'; then\n continue\n fi\n\n # Skip deploy commits\n if echo \"$title\" | grep -q '^Deploy from'; then\n continue\n fi\n\n # Extract PR number from commit message (format: \"... (#1234)\")\n pr=$(echo \"$title\" | grep -oE '#[0-9]+' | head -1 | sed 's/#//')\n\n if [ -n \"$pr\" ]; then\n pr_numbers+=(\"$pr\")\n else\n unmatched+=(\"$hash $title\")\n fi\ndone\n\n# Output unique PR numbers, sorted descending\nprintf '%s\\n' \"${pr_numbers[@]}\" | sort -rn | uniq\n\n# Output unmatched commits to stderr\nif [ ${#unmatched[@]} -gt 0 ]; then\n echo \"\" >&2\n echo \"Commits without PR numbers:\" >&2\n for line in \"${unmatched[@]}\"; do\n echo \" $line\" >&2\n done\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":2503,"content_sha256":"263f3d24bafc2fb0adc3a84df6e9862ab1044c6c37cc8b7e1cba0700641ee642"},{"filename":"scripts/get-new-prs.sh","content":"#!/bin/bash\n# Find PR numbers on the production branch since a release branch.\n# Uses `git cherry` (like extract-draft-changelog.tsx) to correctly\n# handle cherry-picked hotfix commits.\n#\n# Usage: ./get-new-prs.sh \u003ctldraw-repo-dir> \u003crelease-branch>\n# Example: ./get-new-prs.sh /tmp/tldraw v4.2.x\n#\n# Output:\n# PR numbers (one per line), then any commits\n# without PR numbers (hash + title) on stderr.\n#\n# Requires a local clone of tldraw/tldraw.\n\nset -e\n\nif [ -z \"$1\" ] || [ -z \"$2\" ]; then\n echo \"Usage: $0 \u003ctldraw-repo-dir> \u003crelease-branch>\" >&2\n echo \"Example: $0 /tmp/tldraw v4.2.x\" >&2\n exit 1\nfi\n\nTLDRAW_REPO=\"$1\"\nRELEASE_BRANCH=\"$2\"\n\ncd \"$TLDRAW_REPO\"\n\n# Fetch to ensure we have latest refs\ngit fetch origin production \"$RELEASE_BRANCH\" --quiet 2>/dev/null || true\n\n# Use git cherry to find commits on production that aren't on the\n# release branch. git cherry compares patches (not hashes), so it\n# correctly handles cherry-picks and hotfixes.\n# Lines starting with '+' are unique to production.\ncommit_hashes=$(git cherry \"origin/$RELEASE_BRANCH\" origin/production 2>/dev/null \\\n | grep '^+' \\\n | cut -d' ' -f2)\n\npr_numbers=()\nunmatched=()\n\nfor hash in $commit_hashes; do\n # Get the first line of the commit message\n title=$(git log -1 --pretty=format:%s \"$hash\" 2>/dev/null)\n\n # Skip [skip ci] commits (version bumps, deploys)\n if echo \"$title\" | grep -q '\\[skip ci\\]'; then\n continue\n fi\n\n # Skip deploy commits\n if echo \"$title\" | grep -q '^Deploy from'; then\n continue\n fi\n\n # Extract PR number from commit message (format: \"... (#1234)\")\n pr=$(echo \"$title\" | grep -oE '#[0-9]+' | head -1 | sed 's/#//')\n\n if [ -n \"$pr\" ]; then\n pr_numbers+=(\"$pr\")\n else\n unmatched+=(\"$hash $title\")\n fi\ndone\n\n# Output unique PR numbers, sorted descending\nprintf '%s\\n' \"${pr_numbers[@]}\" | sort -rn | uniq\n\n# Output unmatched commits (hotfixes without PR numbers) to stderr\nif [ ${#unmatched[@]} -gt 0 ]; then\n echo \"\" >&2\n echo \"Commits without PR numbers (likely hotfixes):\" >&2\n for line in \"${unmatched[@]}\"; do\n echo \" $line\" >&2\n done\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":2085,"content_sha256":"28c9e38ad8267cd30bc63fe662730529266bf07027092b94bf0c9acc752fd227"},{"filename":"shared/release-notes-guide.md","content":"# Release notes style guide\n\nThis document defines the rules and conventions for tldraw SDK release notes articles in `apps/docs/content/releases/`.\n\n**Prerequisite**: Read the [writing guide](./writing-guide.md) first. This document builds on those foundations with release-notes-specific patterns.\n\n## Editorial guidance\n\n### What to include\n\n- Breaking changes that require user action\n- New features that solve common pain points\n- API additions that unlock new capabilities\n- Changes that affect how developers integrate tldraw\n- Bug fixes for user-reported issues\n\n### What to omit\n\n- Internal performance optimizations (unless user-visible)\n- Fixes for bugs introduced in the same release cycle\n- Implementation details that don't affect public API\n- Pure code quality improvements\n\n### When to create a featured section\n\nPromote changes to the \"What's new\" section when:\n\n- It's a breaking change requiring a migration guide\n- It introduces a major new capability\n- It is an interesting or significant new feature, possibly the result of multiple PRs\n- Users need detailed guidance (migration guides, platform tables)\n\nFeatured sections should include:\n\n- Clear description of what changed and why it matters\n- Code examples where helpful\n- Migration guides in collapsible `\u003cdetails>` blocks for breaking changes\n- Links to relevant documentation\n\n## PR categorization\n\n| Category | Labels | Indicators |\n| ------------ | ---------------------------- | -------------------------------- |\n| API changes | `api`, `feature`, `major` | Adds/removes/modifies public API |\n| Improvements | `improvement`, `enhancement` | Enhances existing functionality |\n| Bug fixes | `bugfix`, `bug`, | Fixes issues |\n\nLook for `### Release notes` and `### API changes` sections in PR bodies. Search for \"breaking\" to identify breaking changes. Search for \"deprecat\" to identify deprecated APIs (mark with 🔜, not 💥).\n\n### PRs to skip\n\nSkip PRs with these labels:\n\n- `other`\n- `skip-release`\n- `chore`\n- `dotcom`\n\nAlso skip:\n\n- Reverts, unless they fix something user-facing\n- Fixes for bugs introduced in the same release cycle (i.e., the bug was caused by a PR that is also in `next.mdx` and was not in the previous release)\n\nWhen a PR is reverted, also remove the original PR's entry from `next.mdx` if it is present.\n\n## Team members (do not credit)\n\nangrycaptain19, AniKrisn, ds300, kostyafarber, max-dra, mimecuvalo, MitjaBezensek, profdl, Siobhantldraw, steveruizok, tldrawdaniel, huppy-bot, github-actions, Somehats, todepond, Taha-Hassan-Git, alex-mckenna-1, max-drake\n\nCredit community contributors with:\n\n```markdown\n(contributed by [@username](https://github.com/username))\n```\n\n## General notes\n\n- Do not include AI attribution\n- Write as if the release has already happened\n- Omit empty sections\n- The release listing is maintained in `apps/docs/content/getting-started/releases.mdx`\n\n---\n\n## Formatting conventions\n\n### Section order\n\nUse these sections in order (omit empty sections):\n\n1. **Introduction paragraph** - 1-2 sentence summary of the release highlights\n2. **What's new** (`## What's new`) - Featured sections (H3s) for major features and breaking changes\n3. **API changes** (`## API changes`) - New methods, properties, options, deprecations, and breaking changes\n4. **Improvements** (`## Improvements`) - Enhancements to existing functionality\n5. **Bug fixes** (`## Bug fixes`) - Fixed issues\n6. **Patch releases** (`## Patch releases`) - Separated by `---`, contains bulleted changes for each patch version\n\n### Introduction paragraph\n\nStart each release with a 1-2 sentence summary highlighting the most significant changes. Lead with concrete features, then mention infrastructure and performance:\n\n```markdown\nThis release introduces several significant changes: a new pattern for defining custom shape/binding typings, pluggable storage for `TLSocketRoom` with a new SQLite option, reactive `editor.inputs`, and optimized draw shape encoding. It also adds various other API improvements, performance optimizations, and bug fixes.\n```\n\n### Entry format\n\nStart entries with a verb: \"Add\", \"Fix\", \"Improve\", \"Remove\". Keep descriptions concise but informative.\n\n```markdown\n- Add `Editor.newMethod()` for doing something useful. ([#7123](https://github.com/tldraw/tldraw/pull/7123))\n```\n\nFor multiple related PRs:\n\n```markdown\n- Improve arrow snapping performance. ([#7145](https://github.com/tldraw/tldraw/pull/7145), [#7150](https://github.com/tldraw/tldraw/pull/7150))\n```\n\nWith code examples:\n\n````markdown\n- Add `localStorageAtom` to `@tldraw/state`. ([#6876](https://github.com/tldraw/tldraw/pull/6876))\n\n ```tsx\n const myAtom = localStorageAtom('my-key', defaultValue)\n ```\n````\n\n### Breaking changes\n\nMark breaking API changes with a 💥 prefix. Mark deprecations (APIs that still work but will be removed in a future release) with a 🔜 prefix. Place breaking changes at the top of the API changes section, followed by deprecations:\n\n```markdown\n## API changes\n\n- 💥 **`ShapeUtil.canEdit()`** signature changed to accept a `TLEditStartInfo` parameter. ([#7361](https://github.com/tldraw/tldraw/pull/7361))\n- 💥 **`oldMethod`** renamed to `newMethod`. ([#7400](https://github.com/tldraw/tldraw/pull/7400))\n- 🔜 **`Editor.legacyMethod()`** is deprecated. Use `Editor.newMethod()` instead. ([#7450](https://github.com/tldraw/tldraw/pull/7450))\n- Add `Editor.newMethod()` for doing something useful. ([#7123](https://github.com/tldraw/tldraw/pull/7123))\n```\n\n### What's new section\n\nThe `## What's new` section contains featured subsections (H3s) for headline features and major breaking changes.\n\n**Basic structure:**\n\n```markdown\n## What's new\n\n### Feature name ([#7320](https://github.com/tldraw/tldraw/pull/7320))\n\nBrief description of what this feature does and why it matters.\n```\n\n**Multiple related PRs:**\n\n```markdown\n### Pluggable storage for TLSocketRoom ([#7320](https://github.com/tldraw/tldraw/pull/7320), [#7123](https://github.com/tldraw/tldraw/pull/7123))\n```\n\n**Deprecation featured sections** - add 🔜 to the heading:\n\n```markdown\n### 🔜 Deprecation of old API ([#0000](https://github.com/tldraw/tldraw/pull/0000))\n\nBrief description of what is deprecated and what to use instead.\n```\n\n**Breaking change featured sections** - add 💥 to the heading and include a migration guide:\n\n```markdown\n### 💥 Feature name ([#0000](https://github.com/tldraw/tldraw/pull/0000))\n\nBrief description of what this feature does and why it matters.\n\n\u003cdetails>\n\u003csummary>Migration guide\u003c/summary>\n\nBefore:\n\\`\\`\\`ts\n// old code\n\\`\\`\\`\n\nAfter:\n\\`\\`\\`ts\n// new code\n\\`\\`\\`\n\n\u003c/details>\n```\n\n**Collapsible explanations** - use for supplementary context:\n\n```markdown\n\u003cdetails>\n\u003csummary>Why SQLite?\u003c/summary>\n\n- **Automatic persistence**: Data survives process restarts\n- **Lower memory usage**: No need to keep entire documents in memory\n\n\u003c/details>\n```\n\n**Platform support tables:**\n\n```markdown\n\u003cdetails>\n\u003csummary>Platform support\u003c/summary>\n\n| Platform | Wrapper | Library |\n| -------------------------- | -------------------------------- | --------------------------------- |\n| Cloudflare Durable Objects | `DurableObjectSqliteSyncWrapper` | Built-in `ctx.storage` |\n| Node.js/Deno | `NodeSqliteWrapper` | `better-sqlite3` or `node:sqlite` |\n\n\u003c/details>\n```\n\n### GitHub release link\n\nAdd a link to the GitHub release at the end of each release section:\n\n- **For minor releases**: Place after the last content section and before the `---` separator\n- **For patch releases**: Place after the bulleted list of changes\n\n```markdown\n[View release on GitHub](https://github.com/tldraw/tldraw/releases/tag/v4.3.0)\n```\n\n### Patch releases\n\nAdd patch releases at the bottom of the minor release file, after a horizontal rule. List in chronological order:\n\n```markdown\n---\n\n## Patch releases\n\n### v4.2.1\n\n- Fix text selection flakiness when clicking into text shapes. ([#3643](https://github.com/tldraw/tldraw/pull/3643))\n\n[View release on GitHub](https://github.com/tldraw/tldraw/releases/tag/v4.2.1)\n\n### v4.2.2\n\n- Fix arrow binding when target shape is rotated. ([#3650](https://github.com/tldraw/tldraw/pull/3650))\n\n[View release on GitHub](https://github.com/tldraw/tldraw/releases/tag/v4.2.2)\n```\n\n### Horizontal rules\n\nUse `---` only before the `## Patch releases` section. Do not use horizontal rules elsewhere.\n\n### Headings\n\nUse sentence case: \"API changes\" not \"API Changes\", \"Bug fixes\" not \"Bug Fixes\".\n\n### Frontmatter\n\n```yaml\n---\ntitle: 'v4.3.0'\ncreated_at: 12/19/2024\nupdated_at: 12/19/2024\nkeywords:\n - changelog\n - release\n - v4.3\n - v4.3.0\n - v4.3.1\n - feature-keyword\n---\n```\n\n- Dates in MM/DD/YYYY format\n- Always include `changelog` and `release` as keywords\n- Include the minor version without patch (e.g., `v4.3`), the `.0` release, and all patch versions\n- Add 2-5 content-relevant keywords (lowercase, hyphens for multi-word)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":9088,"content_sha256":"d42ce7887c7a9e478a6c7e22f6496eb2c7510952c3a0f9f4aa5e675678b43e8a"},{"filename":"shared/writing-guide.md","content":"# Writing style guide\n\nThis document defines the voice and style for all tldraw writing. It applies to documentation, release notes, and any other written content.\n\n## Core identity\n\n**Expert-to-developer guidance**: We write as a knowledgeable colleague explaining a system they helped build. We're confident, practical, and focused on getting developers to working code quickly.\n\nThe overall feeling is: _\"Here's how this works, here's exactly how to use it, and here's working code to prove it.\"_\n\n## Tone characteristics\n\n### What we are\n\n| Trait | Description |\n| ---------------------- | ------------------------------------------------------------------ |\n| **Confident** | We make clear, direct assertions without hedging |\n| **Upfront** | We present solutions early rather than showing what _doesn't_ work |\n| **Pragmatic** | We focus on \"here's how to do it\" rather than theory |\n| **Helpful** | We anticipate developer needs and provide escape hatches |\n| **Honest** | We're transparent about limitations and work-in-progress |\n| **Warm but efficient** | We have personality without being chatty |\n\n### What we're not\n\n- **Not dry or academic** — we have warmth and occasional personality\n- **Not overly chatty** — we respect the reader's time\n- **Not condescending** — we assume intelligence and competence\n- **Not corporate** — we're human, sometimes playful\n\n## Voice examples\n\n### Confidence without hedging\n\n**Do:**\n\n> The Editor class is the main way of controlling tldraw's editor.\n\n> By design, the Editor's surface area is very large.\n\n> Custom shapes are shapes that were created by you or someone you love.\n\n**Don't:**\n\n> The Editor class can be used to control tldraw's editor.\n\n> The Editor's surface area might seem large.\n\n> Custom shapes are shapes that may have been created by developers.\n\n### Pragmatic directness\n\n**Do:**\n\n> Need to create some shapes? Use `Editor#createShapes`. Need to delete them? Use `Editor#deleteShapes`.\n\n> In tldraw, a shape is something that can exist on the page, like an arrow, an image, or some text.\n\n> The sync demo is great for prototyping but you should not use it in production.\n\n**Don't:**\n\n> The following section describes the various methods available for creating and deleting shapes in the editor.\n\n> A shape can be defined as an entity that exists within the canvas space.\n\n> Production usage of the sync demo is discouraged.\n\n### Honesty about limitations\n\n**Do:**\n\n> There are some features that we have not provided and you might want to add yourself.\n\n> While we're working on docs for this part of the project, refer to our examples.\n\n> We don't guarantee server backwards compatibility forever.\n\n**Don't:**\n\n> This comprehensive solution handles most scenarios.\n\n> Documentation is forthcoming.\n\n> Backwards compatibility is maintained between versions.\n\n### Stay concrete\n\nAvoid florid language, extended metaphors, and theoretical examples. We explain with real code and real scenarios, not imagination.\n\n**Do:**\n\n> The store holds all the data for your document.\n\n> Let's create a custom shape for a card with a title and description.\n\n> The editor manages state changes through its store.\n\n**Don't:**\n\n> Think of the store as a river of data, flowing through your application, carrying shapes like leaves on a current.\n\n> Imagine you're building a spaceship dashboard with custom controls...\n\n> The editor orchestrates a symphony of state changes...\n\nShort clarifying comparisons are fine—\"shapes are just records (JSON objects)\"—but don't reach for extended metaphors when plain language works. Avoid distracting hypothetical scenarios in your prose.\n\n## Avoiding AI writing tells\n\nAI-generated text has recognizable patterns. Avoid these to keep our writing sounding human. For a comprehensive catalog, see [Wikipedia: Signs of AI writing](https://en.wikipedia.org/wiki/Wikipedia:Signs_of_AI_writing).\n\n### Hollow importance claims\n\nAI loves to emphasize significance without saying anything concrete. These phrases are red flags:\n\n- \"serves as a testament to\"\n- \"plays a vital/crucial/significant role\"\n- \"underscores its importance\"\n- \"watershed moment,\" \"key turning point,\" \"pivotal moment\"\n- \"deeply rooted,\" \"profound heritage\"\n- \"rich history,\" \"enduring legacy\"\n\n**Don't:**\n\n> The store plays a crucial role in tldraw's architecture, serving as a testament to the power of reactive state management.\n\n**Do:**\n\n> The store holds all shapes, bindings, and other records. The store is reactive: when data changes, the UI updates automatically.\n\n### Trailing gerund phrases\n\n**This is one of the most common AI writing patterns. Actively hunt for and eliminate trailing gerunds.**\n\nAI ends sentences with gerund clauses (-ing phrases) that claim importance without substance:\n\n- \"...emphasizing the significance of X\"\n- \"...reflecting the continued relevance of Y\"\n- \"...highlighting the importance of Z\"\n- \"...ensuring a seamless experience\"\n- \"...underscoring its commitment to quality\"\n\n**Don't:**\n\n> The editor batches updates automatically, ensuring optimal performance while highlighting the importance of reactive state management.\n\n**Do:**\n\n> The editor batches updates automatically. This keeps renders fast even when many shapes change at once.\n\n**Even neutral trailing gerunds are a problem.** They weaken sentences by burying the point at the end, making prose feel monotonous and AI-generated. This isn't just about avoiding hollow importance claims—it's about sentence structure.\n\nCommon neutral trailing gerunds to eliminate:\n\n- \"...allowing you to X\"\n- \"...enabling users to X\"\n- \"...making it easy to X\"\n- \"...giving you X\"\n- \"...providing X\"\n- \"...creating X\"\n- \"...resulting in X\"\n- \"...causing X to Y\"\n\n**Don't:**\n\n> The store is reactive, allowing you to subscribe to changes.\n\n> When `shrink` is greater than zero, the stroke width also decreases during fade-out, creating a smooth disappearance effect.\n\n> The editor exposes methods for shape manipulation, making it easy to create complex diagrams.\n\n**Do:**\n\n> The store is reactive. You can subscribe to changes.\n\n> When `shrink` is greater than zero, the stroke width also decreases during fade-out. This creates a smooth disappearance effect.\n\n> The editor exposes methods for shape manipulation. You can use these to create complex diagrams.\n\nOr lead with what matters:\n\n> Set `shrink` above zero for a smooth disappearance effect—the stroke width decreases during fade-out.\n\n> You can create complex diagrams using the editor's shape manipulation methods.\n\n**The fix is simple**: Split into two sentences, or restructure so the important information comes first. When you see a comma followed by an -ing word near the end of a sentence, that's your signal to rewrite.\n\n### Formulaic transitions\n\nThese transitions are overused by AI and often unnecessary:\n\n- \"Moreover,\" \"Furthermore,\" \"Additionally,\"\n- \"It's important to note that...\"\n- \"It is worth mentioning that...\"\n- \"On the other hand,\"\n- \"In addition to this,\"\n\nUsually you can just delete these and the sentence is stronger. If you need a transition, use a shorter one (\"But,\" \"And,\" \"Also,\") or restructure.\n\n**Don't:**\n\n> The editor manages all state changes. Moreover, it provides a reactive system for updates. Furthermore, it handles undo/redo automatically.\n\n**Do:**\n\n> The editor manages all state changes. It's reactive: when state changes, dependent values update automatically. It also handles undo/redo.\n\n### The rule of three\n\nAI overuses three-part lists. Real writing has lists of two, or four, or seven items. If you find yourself writing exactly three things, ask whether that's actually the right number or just a pattern.\n\n**Don't:**\n\n> The editor is fast, flexible, and powerful.\n\n> This gives you control, clarity, and confidence.\n\n**Do:**\n\n> The editor is fast and flexible.\n\n> This gives you precise control over rendering.\n\n### Promotional language\n\nAI picks up marketing speak from its training data. We're writing technical content, not ad copy:\n\n- \"breathtaking,\" \"stunning,\" \"beautiful\"\n- \"seamless,\" \"frictionless,\" \"effortless\"\n- \"robust,\" \"comprehensive,\" \"cutting-edge\"\n- \"empowers developers to...\"\n- \"unlock the full potential of...\"\n\n**Don't:**\n\n> Tldraw empowers developers to unlock the full potential of infinite canvas experiences with a robust and comprehensive API.\n\n**Do:**\n\n> Tldraw gives you an infinite canvas with a large API surface. You can control almost everything.\n\n### Em dash overuse\n\nAI writing often features multiple em dashes where a comma or period would be more natural. One em dash per paragraph is fine; several is a red flag. Also avoid dramatic formulations that call for an em dash.\n\nLLMs especially use em dashes in formulaic, punched-up ways—often mimicking sales copy by over-emphasizing clauses. They also use em dashes where humans would use commas, parentheses, or colons.\n\n**Don't:**\n\n> It's not just a history manager—it's a way to track changes across time.\n\n> The store is reactive—it notifies subscribers—and it's fully typed—with TypeScript.\n\n**Do:**\n\n> You can also use the history manager to track changes across time.\n\n> The store is reactive: it notifies subscribers when data changes. All records are fully typed.\n\n### Negation parallelism\n\nThe \"It's not X, it's Y\" structure is an AI signature. Real writing just says what something is.\n\n**Don't:**\n\n> It's not just a canvas—it's a complete editing experience.\n\n> The editor isn't simply a state container; it's a reactive system.\n\n**Do:**\n\n> The editor is a reactive system that manages all document state.\n\n### Overused AI vocabulary\n\nCertain words appear disproportionately in LLM output. Avoid these unless they're genuinely the right word:\n\n| Avoid | Use instead |\n| ------------ | -------------------------- |\n| delve (into) | explore, examine, look at |\n| pivotal | important, key, critical |\n| underscore | emphasize, show, highlight |\n| leverage | use |\n| utilize | use |\n| multifaceted | complex, varied |\n| nuanced | subtle, detailed |\n| foster | encourage, create |\n| bolster | strengthen, support |\n| spearhead | lead |\n| paradigm | model, approach |\n| synergy | (usually delete entirely) |\n\nThese words aren't wrong, but their overuse signals AI authorship. If you find yourself reaching for them, consider whether a simpler word works.\n\n### Bullet points with bolded headers\n\nIn prose writing, this format is a ChatGPT signature:\n\n**Don't:**\n\n> - **Reactive updates**: The store automatically notifies subscribers when data changes.\n> - **Type safety**: All records are fully typed with TypeScript.\n> - **Persistence**: Data can be saved to IndexedDB or synced to a server.\n\n**Do:**\n\n> The store is reactive: it automatically notifies subscribers when data changes. All records are fully typed. You can persist data to IndexedDB or sync it to a server.\n\nThis format is fine for reference material (API docs, style guides, changelogs) where scanability matters more than flow. Use a table if you have genuinely parallel information to present.\n\n### Regression to the mean\n\nAI replaces specific, unusual details with generic positive-sounding language. LLMs are trained on text where notable things are described with important-sounding words, so they tend to smooth over unique facts in favor of statistical averages.\n\n**Don't:**\n\n> The arrow tool is a powerful and versatile feature that enables users to create professional-looking diagrams.\n\n> Steve is a visionary leader who has made significant contributions to the field.\n\n**Do:**\n\n> The arrow tool draws arrows between shapes. Arrows can have different heads, labels, and curve styles.\n\n> Steve invented the train-coupling device used in most modern rail systems.\n\nThe fix: preserve specific facts. If you don't know the specifics, research them or omit the claim entirely. Vague importance claims add nothing.\n\n### Uniform sentence structure\n\nAI defaults to sentences of similar length and paragraphs of similar size. Real writing has rhythm—short punchy sentences, then longer ones with more detail. Paragraphs vary based on content, not formula.\n\n**Don't:**\n\n> The editor manages document state. The store holds shape records. Bindings connect related shapes. Tools handle user interactions.\n\n**Do:**\n\n> The editor manages all document state. It holds shapes in a reactive store—when data changes, the UI updates automatically. Tools handle user interaction: each tool is a state machine that responds to pointer and keyboard events.\n\nIf your prose feels monotonous, vary your sentence lengths. Start some sentences with the subject, others with a clause. Let the content dictate structure.\n\n## Grammar and mechanics\n\n### Pronouns\n\n**Use \"you\" for direct address:**\n\n> You can access the editor in two ways.\n\n> You can change the current active tool using `editor.setCurrentTool`.\n\n> You should make sure that there's only ever one `TLSocketRoom` globally.\n\n**Use \"we\" for recommendations and team perspective:**\n\n> We've found it best to create the store, set its data, and then pass the store into the editor.\n\n> We recommend the tldraw sync packages for collaboration.\n\n**Use \"the SDK\" or \"the editor\" when describing what the software does:**\n\n> The SDK has several features to support collaboration.\n\n> The editor provides history methods for undo and redo.\n\n> The editor's history manager handles history. It uses \"stacks\" for undos and redos.\n\nDon't say \"we support X\" when you mean \"the editor supports X\"—it conflates the team with the software.\n\n**Avoid:**\n\n- First-person singular (\"I recommend...\")\n- Passive constructions that obscure the actor (\"It is recommended that...\")\n\n### Voice\n\n**Active voice dominates:**\n\n> The editor holds the raw state of the document in its store property.\n\n> Each node will first handle the event and then pass the event to its active child state.\n\n> Tldraw uses migrations to bring data from old snapshots up to date.\n\n**Passive voice only when the actor genuinely doesn't matter:**\n\n> Data is kept here as a table of JSON serializable records.\n\n> The event is first processed in order to update its inputs.\n\n**Tables still prefer active voice.** When describing states or behaviors in tables, make the subject act rather than be acted upon:\n\n| Don't | Do |\n| ------------------------------------ | ----------------------------------------------------------------- |\n| \"The scribble is temporarily paused\" | \"The manager pauses the scribble\" or \"Drawing pauses temporarily\" |\n| \"The request is being processed\" | \"The server processes the request\" |\n| \"Points are removed from the tail\" | \"The scribble removes points from its tail\" |\n\n### Sentence structure\n\n**Write like a person.** Prefer short, clear sentences, but don't be robotic about it. Natural prose has rhythm—some sentences are short, others flow a bit longer. The goal is readability, not mechanical uniformity.\n\n**Prefer:**\n\n> In tldraw, a shape is something that can exist on the page.\n\n> Shapes are just records (JSON objects) that sit in the store. For example, here's a shape record for a rectangle geo shape.\n\n> When the editor receives an event, it first updates inputs and other state. Then it sends the event to the state chart.\n\n**Avoid complex, nested constructions:**\n\n> When the editor receives an event via its dispatch method, the event is first handled internally to update inputs and other state before being sent into the editor's state chart, where it cascades through the active states.\n\nThe problem isn't sentence length, but rather cognitive load. Break up ideas when a sentence asks the reader to hold too much in their head at once.\n\n### Contractions\n\nUse contractions naturally:\n\n- it's, we've, you'll, won't, don't, can't, shouldn't\n\n**Example:**\n\n> It's our library for fast, fault-tolerant shared document syncing, and it's what we use to power collaboration on our flagship app.\n\n### Headings\n\n**Always use sentence case** (not Title Case):\n\n- \"Custom shapes\" not \"Custom Shapes\"\n- \"Using the editor\" not \"Using the Editor\"\n- \"Camera and coordinates\" not \"Camera and Coordinates\"\n\n**Exception:** Proper nouns and technical names remain capitalized:\n\n- \"PostgreSQL database\"\n- \"WebSocket connections\"\n- \"ShapeUtil implementation\"\n\n## Code examples\n\n### Complete and runnable first\n\nWhen showing code examples, your _first_ snippet should provide a full working example. Following examples can be fragments.\n\n**Do:**\n\n```tsx\nimport { Tldraw } from 'tldraw'\nimport 'tldraw/tldraw.css'\n\nexport default function () {\n\treturn (\n\t\t\u003cdiv style={{ position: 'fixed', inset: 0 }}>\n\t\t\t\u003cTldraw persistenceKey=\"my-persistence-key\" />\n\t\t\u003c/div>\n\t)\n}\n```\n\n**Don't:**\n\n```tsx\n// Add persistenceKey to your Tldraw component\n\u003cTldraw persistenceKey=\"...\" />\n```\n\n### Comments are conversational\n\nUse comments to provide context, not obvious descriptions:\n\n```tsx\neditor.run(\n\t() => {\n\t\teditor.createShapes(myShapes)\n\t},\n\t{ history: 'ignore' } // Changes won't affect undo/redo\n)\n```\n\n### Show realistic data\n\nUse meaningful example data, not placeholders:\n\n**Do:**\n\n```json\n{\n\t\"type\": \"geo\",\n\t\"props\": {\n\t\t\"geo\": \"rectangle\",\n\t\t\"w\": 200,\n\t\t\"h\": 200,\n\t\t\"color\": \"blue\",\n\t\t\"text\": \"diagram\"\n\t}\n}\n```\n\n**Don't:**\n\n```json\n{\n\t\"type\": \"example-type\",\n\t\"props\": {\n\t\t\"prop1\": \"value1\",\n\t\t\"prop2\": \"value2\"\n\t}\n}\n```\n\nFor IDs, use either `\"shape:123\" as TLShapeId` or `createShapeId(\"123\")` to avoid TypeScript errors.\n\n## General notes\n\n- Do not include AI attribution in written content\n- American English spelling\n- Avoid complicated grammar, obscure vocabulary, jokes, or cultural idioms\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":18110,"content_sha256":"e4f3353c96d183d01390940c142ae4d2b0da020226c96ccf8f2f0061fa0025c4"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Update release notes","type":"text"}]},{"type":"paragraph","content":[{"text":"This skill handles the operational tasks of maintaining release notes in the ","type":"text"},{"text":"tldraw/tldraw","type":"text","marks":[{"type":"strong"}]},{"text":" repo: adding new PR entries to ","type":"text"},{"text":"next.mdx","type":"text","marks":[{"type":"code_inline"}]},{"text":" and archiving releases when a new version is published.","type":"text"}]},{"type":"paragraph","content":[{"text":"This skill runs from the tldraw-internal repo but operates on a local clone of tldraw/tldraw.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Setup","type":"text"}]},{"type":"paragraph","content":[{"text":"Before starting, clone the tldraw repo:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"git clone https://github.com/tldraw/tldraw.git /tmp/tldraw","type":"text"}]},{"type":"paragraph","content":[{"text":"All scripts take the repo path as their first argument. Run script commands from the repository root by replacing ","type":"text"},{"text":"\u003cskill-dir>","type":"text","marks":[{"type":"code_inline"}]},{"text":" with the directory that contains this ","type":"text"},{"text":"SKILL.md","type":"text","marks":[{"type":"code_inline"}]},{"text":". All file edits happen in this clone. At the end, commit and push from the clone.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Release cycle","type":"text"}]},{"type":"paragraph","content":[{"text":"The SDK releases every four weeks. One week before launch, the SDK is frozen and a ","type":"text"},{"text":"production","type":"text","marks":[{"type":"code_inline"}]},{"text":" branch is cut from ","type":"text"},{"text":"main","type":"text","marks":[{"type":"code_inline"}]},{"text":". During release week, only hotfixes are cherry-picked to ","type":"text"},{"text":"production","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"The release notes in ","type":"text"},{"text":"next.mdx","type":"text","marks":[{"type":"code_inline"}]},{"text":" must ultimately reflect what ships on the ","type":"text"},{"text":"production","type":"text","marks":[{"type":"code_inline"}]},{"text":" branch. However, during the ~3 development weeks before the freeze, we build up release notes from ","type":"text"},{"text":"main","type":"text","marks":[{"type":"code_inline"}]},{"text":" so they're not empty. The status script detects which phase we're in based on the date of the last minor release:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Development weeks","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"source: \"main\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", \u003c3 weeks since last minor release): PRs are gathered from ","type":"text"},{"text":"main","type":"text","marks":[{"type":"code_inline"}]},{"text":" since the last release tag. These are preliminary — some may not ship if they're reverted or if production cherry-picks differ.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Freeze week","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"source: \"production\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", 3+ weeks since last minor release): PRs are gathered from the ","type":"text"},{"text":"production","type":"text","marks":[{"type":"code_inline"}]},{"text":" branch using ","type":"text"},{"text":"git cherry","type":"text","marks":[{"type":"code_inline"}]},{"text":" against the previous release branch. Only PRs that landed before the freeze or were hotfixed onto production are included. Entries accumulated from ","type":"text"},{"text":"main","type":"text","marks":[{"type":"code_inline"}]},{"text":" that aren't on production will be pruned.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Process","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"1. Check status","type":"text"}]},{"type":"paragraph","content":[{"text":"Run the status script to determine versions and diff branch:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"\u003cskill-dir>/scripts/get-changelog-status.sh /tmp/tldraw","type":"text"}]},{"type":"paragraph","content":[{"text":"This returns:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"source","type":"text","marks":[{"type":"code_inline"}]},{"text":" - either ","type":"text"},{"text":"\"main\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" (development weeks) or ","type":"text"},{"text":"\"production\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" (release week)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"diff_branch","type":"text","marks":[{"type":"code_inline"}]},{"text":" (e.g., ","type":"text"},{"text":"v4.3.x","type":"text","marks":[{"type":"code_inline"}]},{"text":") - the branch to diff against (used when ","type":"text"},{"text":"source","type":"text","marks":[{"type":"code_inline"}]},{"text":" is ","type":"text"},{"text":"\"production\"","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"last_tag","type":"text","marks":[{"type":"code_inline"}]},{"text":" (e.g., ","type":"text"},{"text":"v4.4.0","type":"text","marks":[{"type":"code_inline"}]},{"text":") - the last release tag (used when ","type":"text"},{"text":"source","type":"text","marks":[{"type":"code_inline"}]},{"text":" is ","type":"text"},{"text":"\"main\"","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"latest_release","type":"text","marks":[{"type":"code_inline"}]},{"text":" - the most recent published release","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Whether archival is needed","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"2. Handle archival (if needed)","type":"text"}]},{"type":"paragraph","content":[{"text":"If a new release was published since ","type":"text"},{"text":"last_version","type":"text","marks":[{"type":"code_inline"}]},{"text":" in ","type":"text"},{"text":"next.mdx","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Copy ","type":"text"},{"text":"next.mdx","type":"text","marks":[{"type":"code_inline"}]},{"text":" content to ","type":"text"},{"text":"vX.Y.0.mdx","type":"text","marks":[{"type":"code_inline"}]},{"text":" (e.g., ","type":"text"},{"text":"v4.3.0.mdx","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Update the frontmatter in the new file:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Set ","type":"text"},{"text":"title","type":"text","marks":[{"type":"code_inline"}]},{"text":" to the version","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Update dates","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add the GitHub release link after frontmatter","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add the new version to the releases index at ","type":"text"},{"text":"apps/docs/content/getting-started/releases.mdx","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add a line at the top of the appropriate ","type":"text"},{"text":"## v{major}.x","type":"text","marks":[{"type":"code_inline"}]},{"text":" section","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Format: ","type":"text"},{"text":"- [vX.Y](/releases/vX.Y.0) - Brief description","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The description should be a short summary of the release highlights from the intro paragraph (3-5 key features, comma-separated)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If a new major version section is needed, create it above the previous one","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Reset ","type":"text"},{"text":"next.mdx","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Update ","type":"text"},{"text":"last_version","type":"text","marks":[{"type":"code_inline"}]},{"text":" field to the new release","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Clear the content sections","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Keep the file structure for accumulating new changes","type":"text"}]}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"3. Find new PRs","type":"text"}]},{"type":"paragraph","content":[{"text":"The approach depends on the ","type":"text"},{"text":"source","type":"text","marks":[{"type":"code_inline"}]},{"text":" field from step 1.","type":"text"}]},{"type":"paragraph","content":[{"text":"If ","type":"text","marks":[{"type":"strong"}]},{"text":"source","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" is ","type":"text","marks":[{"type":"strong"}]},{"text":"\"production\"","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" (release week):","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"Get PR numbers on the production branch since the previous release:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"\u003cskill-dir>/scripts/get-new-prs.sh /tmp/tldraw \u003cdiff_branch>","type":"text"}]},{"type":"paragraph","content":[{"text":"This uses ","type":"text"},{"text":"git cherry","type":"text","marks":[{"type":"code_inline"}]},{"text":" (same approach as ","type":"text"},{"text":"extract-draft-changelog.tsx","type":"text","marks":[{"type":"code_inline"}]},{"text":") to compare patches between ","type":"text"},{"text":"origin/production","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"origin/\u003cdiff_branch>","type":"text","marks":[{"type":"code_inline"}]},{"text":". Unlike ","type":"text"},{"text":"git log","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"git cherry","type":"text","marks":[{"type":"code_inline"}]},{"text":" correctly handles cherry-picked hotfix commits by comparing patch content rather than commit hashes.","type":"text"}]},{"type":"paragraph","content":[{"text":"If ","type":"text","marks":[{"type":"strong"}]},{"text":"source","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" is ","type":"text","marks":[{"type":"strong"}]},{"text":"\"main\"","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" (development weeks):","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"Get PR numbers on main since the last release tag:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"\u003cskill-dir>/scripts/get-new-prs-from-main.sh /tmp/tldraw \u003clast_tag>","type":"text"}]},{"type":"paragraph","content":[{"text":"This uses ","type":"text"},{"text":"git cherry","type":"text","marks":[{"type":"code_inline"}]},{"text":" to compare patches between ","type":"text"},{"text":"origin/main","type":"text","marks":[{"type":"code_inline"}]},{"text":" and the release tag, excluding commits that were cherry-picked to production and already shipped in the release.","type":"text"}]},{"type":"paragraph","content":[{"text":"Both scripts output:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"stdout","type":"text","marks":[{"type":"strong"}]},{"text":": PR numbers (one per line) extracted from commit messages","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"stderr","type":"text","marks":[{"type":"strong"}]},{"text":": Commits without PR numbers (typically hotfixes or direct commits)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"4. Resolve hotfix commits","type":"text"}]},{"type":"paragraph","content":[{"text":"Check stderr from step 3 for any ","type":"text"},{"text":"[HOTFIX]","type":"text","marks":[{"type":"code_inline"}]},{"text":" commits without PR numbers. For each SDK-relevant one (skip dotcom-only), find the corresponding PR:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"gh pr list -R tldraw/tldraw --state merged --search \"\u003chotfix title keywords>\" --json number,title --jq '.[]'","type":"text"}]},{"type":"paragraph","content":[{"text":"Also check ","type":"text"},{"text":"sdk-hotfix-please","type":"text","marks":[{"type":"code_inline"}]},{"text":" labeled PRs, but verify they actually landed on production — this label is also used for patch releases on the previous release branch (e.g., v4.3.x):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"gh pr list -R tldraw/tldraw --label \"sdk-hotfix-please\" --state merged --limit 20 --json number,title,mergedAt","type":"text"}]},{"type":"paragraph","content":[{"text":"Add any matched PR numbers to the production PR list from step 3.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"5. Fetch PR details","type":"text"}]},{"type":"paragraph","content":[{"text":"Batch fetch all PR information:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"\u003cskill-dir>/scripts/fetch-pr-batch.sh \u003cpr1> \u003cpr2> ...","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"6. Remove stale entries","type":"text"}]},{"type":"paragraph","content":[{"text":"Compare the full PR list (from steps 3 + 4) against the PR numbers already referenced in ","type":"text"},{"text":"next.mdx","type":"text","marks":[{"type":"code_inline"}]},{"text":". Remove any entries from ","type":"text"},{"text":"next.mdx","type":"text","marks":[{"type":"code_inline"}]},{"text":" whose PRs are ","type":"text"},{"text":"not","type":"text","marks":[{"type":"strong"}]},{"text":" in the current set.","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"When ","type":"text","marks":[{"type":"strong"}]},{"text":"source","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" is ","type":"text","marks":[{"type":"strong"}]},{"text":"\"production\"","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":": This prunes entries that were accumulated from ","type":"text"},{"text":"main","type":"text","marks":[{"type":"code_inline"}]},{"text":" during development but didn't make it to production. These are PRs that landed on ","type":"text"},{"text":"main","type":"text","marks":[{"type":"code_inline"}]},{"text":" after the production branch was cut and will not ship in this release.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"When ","type":"text","marks":[{"type":"strong"}]},{"text":"source","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" is ","type":"text","marks":[{"type":"strong"}]},{"text":"\"main\"","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":": This removes entries for PRs that were reverted or otherwise no longer on main. Pruning is less aggressive since the full set isn't finalized until production is cut.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"When removing an entry, also check whether it was referenced in a featured \"What's new\" section and remove that section too. Update the intro paragraph if it mentions a removed feature.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"7. Add new entries","type":"text"}]},{"type":"paragraph","content":[{"text":"For each PR not already in ","type":"text"},{"text":"next.mdx","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check PR labels and body against the style guide's categorization rules","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Skip PRs that should be omitted (see style guide)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Skip PRs that fix bugs introduced in the same release cycle (i.e., bugs caused by other PRs already in ","type":"text"},{"text":"next.mdx","type":"text","marks":[{"type":"code_inline"}]},{"text":" that were not in the previous release)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If a PR is a revert, also remove the original reverted PR's entry from ","type":"text"},{"text":"next.mdx","type":"text","marks":[{"type":"code_inline"}]},{"text":" if present","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add entries to the appropriate section","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Promote significant changes to featured sections when warranted","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"8. Verify","type":"text"}]},{"type":"paragraph","content":[{"text":"Check that:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Every PR referenced in ","type":"text"},{"text":"next.mdx","type":"text","marks":[{"type":"code_inline"}]},{"text":" is in the current PR set (from ","type":"text"},{"text":"main","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"production","type":"text","marks":[{"type":"code_inline"}]},{"text":" depending on ","type":"text"},{"text":"source","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"PR links are correct","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Community contributors are credited","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Sections are in the correct order","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Breaking changes are marked with 💥","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Deprecations are marked with 🔜","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Note","type":"text","marks":[{"type":"strong"}]},{"text":": When ","type":"text"},{"text":"source","type":"text","marks":[{"type":"code_inline"}]},{"text":" is ","type":"text"},{"text":"\"main\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", the entries are preliminary. Some may be pruned later when the production branch is cut and the source switches to ","type":"text"},{"text":"\"production\"","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"9. Push changes","type":"text"}]},{"type":"paragraph","content":[{"text":"After editing ","type":"text"},{"text":"next.mdx","type":"text","marks":[{"type":"code_inline"}]},{"text":" (and any archive files), commit and push from the tldraw clone:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"cd /tmp/tldraw\nBRANCH=\"update-release-notes-$(date -u +%Y%m%d-%H%M)\"\ngit checkout -b \"$BRANCH\"\ngit add apps/docs/content/releases/\ngit commit -m \"docs: update release notes\"\ngit push -u origin \"$BRANCH\"","type":"text"}]},{"type":"paragraph","content":[{"text":"Then create the PR using the ","type":"text"},{"text":"../pr/SKILL.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" workflow and the standards in ","type":"text"},{"text":"../write-pr/SKILL.md","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Title","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"docs(releases): update release notes","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Change type","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"other","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test plan","type":"text","marks":[{"type":"strong"}]},{"text":": Remove the numbered list (no manual testing steps). Untick both unit tests and end to end tests.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Release notes","type":"text","marks":[{"type":"strong"}]},{"text":": Omit this section (internal docs work with no user-facing impact)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Description paragraph","type":"text","marks":[{"type":"strong"}]},{"text":": Start with \"In order to...\" and mention the source (","type":"text"},{"text":"main","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"production","type":"text","marks":[{"type":"code_inline"}]},{"text":") and what was updated (new entries added, stale entries pruned, archival performed, etc.)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Include a ","type":"text"},{"text":"Code changes","type":"text","marks":[{"type":"strong"}]},{"text":" table with a ","type":"text"},{"text":"Documentation","type":"text","marks":[{"type":"code_inline"}]},{"text":" row","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"The ","type":"text"},{"text":"last_version","type":"text","marks":[{"type":"code_inline"}]},{"text":" field","type":"text"}]},{"type":"paragraph","content":[{"text":"The ","type":"text"},{"text":"next.mdx","type":"text","marks":[{"type":"code_inline"}]},{"text":" frontmatter includes a ","type":"text"},{"text":"last_version","type":"text","marks":[{"type":"code_inline"}]},{"text":" field that tracks the most recent published release. This determines which PRs are \"new\" and need to be added.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"SDK release workflow","type":"text"}]},{"type":"paragraph","content":[{"text":"During an SDK release, this skill is run ","type":"text"},{"text":"twice","type":"text","marks":[{"type":"strong"}]},{"text":" to get the docs site into its post-release state:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"First run","type":"text","marks":[{"type":"strong"}]},{"text":" — update ","type":"text"},{"text":"next.mdx","type":"text","marks":[{"type":"code_inline"}]},{"text":" with all PRs that will ship in the release (source is ","type":"text"},{"text":"\"production\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" during freeze week). This ensures the release notes are complete before publishing.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Publish the release to NPM","type":"text","marks":[{"type":"strong"}]},{"text":" — this happens outside of this skill.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Second run","type":"text","marks":[{"type":"strong"}]},{"text":" — now that the release is published, the status script will detect ","type":"text"},{"text":"needs_archive: true","type":"text","marks":[{"type":"code_inline"}]},{"text":". This run:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Archives ","type":"text"},{"text":"next.mdx","type":"text","marks":[{"type":"code_inline"}]},{"text":" to a versioned file (e.g., ","type":"text"},{"text":"v4.5.0.mdx","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Adds the new version to the releases index (","type":"text"},{"text":"releases.mdx","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Resets ","type":"text"},{"text":"next.mdx","type":"text","marks":[{"type":"code_inline"}]},{"text":" with the new ","type":"text"},{"text":"last_version","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Finds PRs that landed on ","type":"text"},{"text":"main","type":"text","marks":[{"type":"code_inline"}]},{"text":" during the freeze (since the release tag) and adds them to the fresh ","type":"text"},{"text":"next.mdx","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]}]},{"type":"paragraph","content":[{"text":"After the second run, the docs site reflects the published release and ","type":"text"},{"text":"next.mdx","type":"text","marks":[{"type":"code_inline"}]},{"text":" already has a head start on the next cycle's changes.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"References","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Style guide","type":"text","marks":[{"type":"strong"}]},{"text":": See ","type":"text"},{"text":"shared/release-notes-guide.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for guidance on what a release notes article should contain and how to format it.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Writing guide","type":"text","marks":[{"type":"strong"}]},{"text":": See ","type":"text"},{"text":"shared/writing-guide.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for voice and style conventions.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Scripts","type":"text","marks":[{"type":"strong"}]},{"text":": See ","type":"text"},{"text":"scripts/","type":"text","marks":[{"type":"code_inline"}]},{"text":" for automation helpers","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"update-release-notes","author":"@skillopedia","source":{"stars":47550,"repo_name":"tldraw","origin_url":"https://github.com/tldraw/tldraw/blob/HEAD/skills/update-release-notes/SKILL.md","repo_owner":"tldraw","body_sha256":"898108312cea417974a8523f387afe54d9f849dd3b6e2586c3abe7626a88d068","cluster_key":"598dfa1aa4665924e87cfa8307e860ae61a075fec96648a1a3402804fc62ccc5","clean_bundle":{"format":"clean-skill-bundle-v1","source":"tldraw/tldraw/skills/update-release-notes/SKILL.md","attachments":[{"id":"73dc4fe4-8e55-5369-ab51-7147f227df6c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/73dc4fe4-8e55-5369-ab51-7147f227df6c/attachment.sh","path":"scripts/fetch-pr-batch.sh","size":1333,"sha256":"b88e220e7204e66763fe7a59f0eb73e79e3cc415b15c3d573f5bc84cbe6f5d9e","contentType":"application/x-sh; charset=utf-8"},{"id":"be74f52a-7d3e-5242-b539-98bce3fc6937","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/be74f52a-7d3e-5242-b539-98bce3fc6937/attachment.sh","path":"scripts/get-changelog-status.sh","size":3942,"sha256":"cc8ba864081b3d89feaf0510ef3d660aa912d6db2dc470b2d808423b45034e47","contentType":"application/x-sh; charset=utf-8"},{"id":"e64c8635-22f4-59e7-9921-ffe4ece83142","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e64c8635-22f4-59e7-9921-ffe4ece83142/attachment.sh","path":"scripts/get-new-prs-from-main.sh","size":2503,"sha256":"263f3d24bafc2fb0adc3a84df6e9862ab1044c6c37cc8b7e1cba0700641ee642","contentType":"application/x-sh; charset=utf-8"},{"id":"5872f8bf-8289-5d74-8105-d0102c15228a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5872f8bf-8289-5d74-8105-d0102c15228a/attachment.sh","path":"scripts/get-new-prs.sh","size":2085,"sha256":"28c9e38ad8267cd30bc63fe662730529266bf07027092b94bf0c9acc752fd227","contentType":"application/x-sh; charset=utf-8"},{"id":"e812f194-0f08-599a-8f5d-b5c0f175cbb7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e812f194-0f08-599a-8f5d-b5c0f175cbb7/attachment.md","path":"shared/release-notes-guide.md","size":9088,"sha256":"d42ce7887c7a9e478a6c7e22f6496eb2c7510952c3a0f9f4aa5e675678b43e8a","contentType":"text/markdown; charset=utf-8"},{"id":"28128df7-8895-598e-8ca6-7391d3a0583f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/28128df7-8895-598e-8ca6-7391d3a0583f/attachment.md","path":"shared/writing-guide.md","size":18110,"sha256":"e4f3353c96d183d01390940c142ae4d2b0da020226c96ccf8f2f0061fa0025c4","contentType":"text/markdown; charset=utf-8"}],"bundle_sha256":"03a8f857f501f2eb01188201ca9cc43ef1327ebf2c8394a52c00fa14a74fc2e5","attachment_count":6,"text_attachments":6,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/update-release-notes/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"media-content","category_label":"Media"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"media-content","import_tag":"clean-skills-v1","description":"Update the release notes file at `apps/docs/content/releases/next.mdx` in the tldraw/tldraw repo based on PRs since the previous release, or archive `next.mdx` to a versioned file when a new version is published.","allowed-tools":"Bash(git:*), Bash(gh:*)"}},"renderedAt":1782979543286}

Update release notes This skill handles the operational tasks of maintaining release notes in the tldraw/tldraw repo: adding new PR entries to and archiving releases when a new version is published. This skill runs from the tldraw-internal repo but operates on a local clone of tldraw/tldraw. Setup Before starting, clone the tldraw repo: All scripts take the repo path as their first argument. Run script commands from the repository root by replacing with the directory that contains this . All file edits happen in this clone. At the end, commit and push from the clone. Release cycle The SDK rel…