Skip to content

feat: weekly changelog, What's New dialog, and auto-append pipeline#197

Merged
walterlow merged 5 commits intostagingfrom
develop
Apr 17, 2026
Merged

feat: weekly changelog, What's New dialog, and auto-append pipeline#197
walterlow merged 5 commits intostagingfrom
develop

Conversation

@walterlow
Copy link
Copy Markdown
Owner

@walterlow walterlow commented Apr 17, 2026

Summary

  • Introduces a weekly CalVer changelog (YYYY.MM.DD = Monday of the week) with a rolling current entry that accumulates bullets across the week
  • Backfills 9 historical weekly releases covering Feb–Apr 2026 from 1172 commits, with matching local git tags
  • Adds a What's New dialog (Sparkles button in the toolbar, left of Settings) with an unseen-version indicator
  • Automates append on every push to main via GitHub Action; weekly rollup via npm run changelog:rollup

Architecture

  • src/data/changelog.json — typed source of truth ({ current, releases[] }); imported by UI
  • CHANGELOG.md — human-facing mirror
  • .claude/skills/changelog/SKILL.md — skill for curation/rollup work (backfill/append/rollup modes)
  • scripts/changelog-append.mjs — parses feat/fix/perf subjects, appends deduped bullets to current
  • scripts/changelog-rollup.mjs — promotes current → tagged weekly release, bumps package.json, creates annotated tag
  • .github/workflows/changelog-append.yml — runs append on push-to-main with github.event.before..HEAD range and full-depth checkout so merge-commit side branches are walkable

Tags (local only, 9 total)

v2026.02.02 v2026.02.09 v2026.02.16 v2026.02.23 v2026.03.09 v2026.03.16 v2026.03.23 v2026.03.30 v2026.04.06

Push with git push origin --tags after this lands on main.

Test plan

  • Reload dev and click the new Sparkles button — dialog renders with weekly versions in the sidebar and This Week card at top
  • Click through several weekly versions — highlights, added/fixed/improved groups render correctly
  • Close and reopen — no unseen-dot after first open (localStorage writes freecut:whatsNewLastSeen)
  • After this merges to main, check that the Action appends bullets to current (will include 2 meta-bullets for the changelog feature itself unless the merge message carries [skip changelog])
  • Dry-run npm run changelog:rollup -- --date 2026-04-13 after the current week closes to verify rollup output

Caveats

  • Action uses GITHUB_TOKEN; self-pushes don't trigger further runs (belt-and-suspenders if: guard also filters on commit message)
  • Append script produces raw dev-speak bullets; polish via the changelog skill before rollup
  • .claude/ is now partially tracked: settings.local.json and worktrees/ stay ignored

Summary by CodeRabbit

  • New Features

    • Added a "What's New" dialog in-app to browse recent release notes with highlights, grouped change lists, a notification dot for unseen updates, and a link to the full changelog.
  • Documentation

    • Introduced a formal, versioned weekly changelog (CalVer weeks) with structured entries for Highlights, Added, Fixed, and Improved.

Introduces weekly changelog versioned by Monday date (YYYY.MM.DD) with a
rolling current entry that accumulates bullets across the week. Backfills
nine historical weeks covering Feb-Apr 2026.

- .claude/skills/changelog: skill definition with backfill/append/rollup modes
- scripts/changelog-append.mjs: parses feat/fix/perf subjects into current
- scripts/changelog-rollup.mjs: promotes current to a tagged weekly release
- .github/workflows/changelog-append.yml: runs append on every push to main
- package.json: version bumped to latest released week (2026.04.06)
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 17, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
freecut Ready Ready Preview, Comment Apr 17, 2026 8:39am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
freecut-web Ignored Ignored Preview Apr 17, 2026 8:39am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 17, 2026

📝 Walkthrough

Walkthrough

Adds a weekly CalVer-based changelog system: JSON types/data, append and rollup CLI scripts, a GitHub Actions append workflow, VS Code launch config, package script entries, and a "What's New" UI in the editor with seen-state persistence.

Changes

Cohort / File(s) Summary
VS Code & Gitignore
.claude/launch.json, .gitignore
Adds VS Code launch profiles (dev, test, preview) and stops ignoring the entire .claude/ dir while still ignoring .claude/settings.local.json and .claude/worktrees/.
Changelog Skill & Markdown
.claude/skills/changelog/SKILL.md, CHANGELOG.md
Adds changelog maintenance spec and initial CHANGELOG.md with weekly CalVer entries and formatting/operational rules.
Data & Types
src/data/changelog.json, src/data/changelog-types.ts
Introduces structured changelog JSON data and TypeScript types modeling entries, groups, items, and the file layout.
Editor UI
src/features/editor/components/toolbar.tsx, src/features/editor/components/whats-new-dialog.tsx, src/features/editor/components/whats-new-seen.ts
Adds "What's New" button with unread badge, a modal dialog to browse changelog entries, and localStorage-backed seen/mark logic.
Automation Scripts
scripts/changelog-append.mjs, scripts/changelog-rollup.mjs
New CLI scripts: append parses git commits into current groups with dedupe/filtering; rollup promotes current to a dated release, updates package.json, writes CHANGELOG.md, and creates an annotated local tag.
CI Workflow
.github/workflows/changelog-append.yml
Adds GitHub Actions workflow to run append script on pushes to main (skips when commit message indicates), commit src/data/changelog.json if changed, and retry pushes to handle races.
Package Configuration
package.json
Bumps package version to 2026.04.06 and adds npm scripts changelog:append and changelog:rollup.
Misc
.claude/...
New .claude files added for editor launch configs and skill docs (see cohorts above).

Sequence Diagram(s)

sequenceDiagram
  participant GH as GitHub Actions
  participant Repo as Repository
  participant Node as Node script (append)
  participant Git as Git CLI
  GH->>Repo: push to main (trigger)
  GH->>Repo: checkout full history
  GH->>Git: compute commit range
  GH->>Node: run scripts/changelog-append.mjs with range
  Node->>Repo: read/parse commits, update src/data/changelog.json
  Node->>Repo: write changelog.json (if changed)
  GH->>Git: commit file and push (retry up to 5x with fetch/rebase on failure)
Loading
sequenceDiagram
  participant Client as Editor UI
  participant Data as `src/data/changelog.json`
  participant Storage as localStorage
  Client->>Data: import/read changelog
  Client->>Client: render WhatsNewDialog (list + details)
  Client->>Storage: get `freecut:whatsNewLastSeen`
  alt user opens dialog
    Client->>Storage: markChangelogSeen() -> set latest id
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 I burrowed through commits and found the news,
Weekly CalVer carrots, tidy grouped views,
A sparkle on the toolbar, a gentle nudge to see,
JSON seeds and scripts rolled up so neatly — whee! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changeset: it introduces a weekly changelog system, a What's New dialog, and an auto-append pipeline. All three components are substantial parts of the PR and the title captures them concisely.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread .github/workflows/changelog-append.yml Outdated
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add src/data/changelog.json
git commit -m "chore(changelog): append bullets from $(git rev-parse --short HEAD^) [skip ci]"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Commit message references wrong SHA

The command $(git rev-parse --short HEAD^) resolves to the parent of the commit that triggered the workflow, not the actual commit(s) being processed.

When this workflow runs:

  1. A commit is pushed to main (this becomes HEAD)
  2. The script processes commits in the computed range
  3. The commit message incorrectly references HEAD^ (the grandparent commit)

This means every automated changelog commit will cite the wrong source commit in its message.

Fix:

git commit -m "chore(changelog): append bullets from $(git rev-parse --short HEAD) [skip ci]"

Or if referencing the range start:

git commit -m "chore(changelog): append bullets from ${{ github.event.before }}..$(git rev-parse --short HEAD) [skip ci]"
Suggested change
git commit -m "chore(changelog): append bullets from $(git rev-parse --short HEAD^) [skip ci]"
git commit -m "chore(changelog): append bullets from $(git rev-parse --short HEAD) [skip ci]"

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 17, 2026

Greptile Summary

This PR introduces a weekly CalVer changelog pipeline (changelog-append.mjs, changelog-rollup.mjs), a GitHub Action for automated appending on push-to-main, a What's New dialog in the editor toolbar, and backfills nine historical weekly releases into src/data/changelog.json/CHANGELOG.md. The implementation is clean end-to-end — types are sound, the dedup/filter logic is correct, and the UI wires up properly to localStorage for the unseen-dot indicator.

Confidence Score: 5/5

Safe to merge — all findings are P2 style/best-practice suggestions with no correctness impact.

The changelog pipeline logic is sound: dedup is correct, rollup math is correct, the lastMondayIso formula produces the right date, and the unseen-dot identifier handles the empty-string edge case. No P0/P1 bugs were found.

.github/workflows/changelog-append.yml line 47 (unquoted expression) and scripts/changelog-rollup.mjs line 173 (execSync shell injection) are the only spots worth a second look.

Important Files Changed

Filename Overview
.github/workflows/changelog-append.yml Changelog auto-append CI workflow; range computed from github.event.before but passed unquoted to node script on line 47.
scripts/changelog-append.mjs Parses feat/fix/perf commit subjects and deduplicates bullets into current entry; logic is correct and guards are in place.
scripts/changelog-rollup.mjs Promotes current entry to a tagged weekly release; git tag command uses execSync with shell interpolation and does not escape backticks/$ in tagMessage.
src/features/editor/components/whats-new-dialog.tsx What's New dialog UI; minor issues with hardcoded version check and array-index list keys.
src/features/editor/components/whats-new-seen.ts localStorage-based seen/unseen tracking; correctly guards against empty identifiers and SSR.
src/features/editor/components/toolbar.tsx Adds Sparkles button and WhatsNewDialog wiring; state management for the unseen dot is correct.
src/data/changelog-types.ts Clean type definitions for ChangelogFile/Entry/Item with correctly nullable current field.

Sequence Diagram

sequenceDiagram
    participant Dev as Developer
    participant GH as GitHub (push to main)
    participant CI as changelog-append.yml
    participant Script as changelog-append.mjs
    participant JSON as changelog.json
    participant UI as WhatsNewDialog

    Dev->>GH: git push main
    GH->>CI: trigger (if not chore(changelog))
    CI->>CI: compute range (github.event.before..HEAD)
    CI->>Script: node changelog-append.mjs range
    Script->>Script: git log --no-merges (feat/fix/perf only)
    Script->>JSON: dedup + append bullets to current
    CI->>GH: git push chore(changelog) commit [skip ci]

    Note over Dev,JSON: Weekly rollup (manual)
    Dev->>Script: npm run changelog:rollup -- --date YYYY-MM-DD
    Script->>JSON: promote current to releases[], current = null
    Script->>Dev: CHANGELOG.md + package.json updated, git tag created

    Note over UI,JSON: Runtime (bundled at build time)
    UI->>JSON: import changelog.json (static)
    UI->>UI: getLatestIdentifier() returns current date or version
    UI->>UI: hasUnseenChangelog() via localStorage
    UI-->>UI: show unseen dot if identifier changed
Loading

Fix All in Claude Code Fix All in Codex

Prompt To Fix All With AI
This is a comment left during a code review.
Path: .github/workflows/changelog-append.yml
Line: 47

Comment:
**Quote the range expression**

`${{ steps.range.outputs.range }}` is interpolated by the Actions runner before the shell sees it. While the value is always `<hex>..HEAD` today, leaving it unquoted means any future whitespace or glob char in the output would cause word-splitting or unexpected expansion. Double-quoting is the safe default.

```suggestion
        run: node scripts/changelog-append.mjs "${{ steps.range.outputs.range }}"
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: scripts/changelog-rollup.mjs
Line: 173

Comment:
**Backticks and `$()` in tagMessage not escaped**

`run()` calls `execSync` with the string passed directly to a shell. Only `"` is escaped; if `highlights[0]` contains a backtick or `$(...)`, the shell will interpret it as a subshell. Since the message comes from `changelog.json` (curated by an operator), the practical risk is low, but it's safer to use `execFileSync` with an args array to bypass the shell entirely.

```js
// Safer: use execFileSync to bypass shell entirely
import { execFileSync } from 'node:child_process';
execFileSync('git', ['tag', '-a', tagName, sha, '-m', tagMessage], { stdio: 'inherit' });
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/features/editor/components/whats-new-dialog.tsx
Line: 51

Comment:
**Hardcoded version string for "Initial release" label**

`entry.version.startsWith('2026.02.02')` couples the UI to a specific version string. If the initial date is ever corrected or a new "first" entry is introduced, this silently stops working. Consider adding a `subtitle?: string` field to `ChangelogEntry` so the data drives the label rather than a code-level string match.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(changelog): use github.event.before ..." | Re-trigger Greptile

Comment thread .github/workflows/changelog-append.yml Outdated
node-version: 22

- name: Run append script
run: node scripts/changelog-append.mjs ${{ steps.range.outputs.range }}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Quote the range expression

${{ steps.range.outputs.range }} is interpolated by the Actions runner before the shell sees it. While the value is always <hex>..HEAD today, leaving it unquoted means any future whitespace or glob char in the output would cause word-splitting or unexpected expansion. Double-quoting is the safe default.

Suggested change
run: node scripts/changelog-append.mjs ${{ steps.range.outputs.range }}
run: node scripts/changelog-append.mjs "${{ steps.range.outputs.range }}"
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/changelog-append.yml
Line: 47

Comment:
**Quote the range expression**

`${{ steps.range.outputs.range }}` is interpolated by the Actions runner before the shell sees it. While the value is always `<hex>..HEAD` today, leaving it unquoted means any future whitespace or glob char in the output would cause word-splitting or unexpected expansion. Double-quoting is the safe default.

```suggestion
        run: node scripts/changelog-append.mjs "${{ steps.range.outputs.range }}"
```

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Codex

Comment thread scripts/changelog-rollup.mjs Outdated
const tagMessage = `${tagName} — ${highlightLine}`;

console.log(`\nTagging ${tagName} at ${sha.slice(0, 8)}`);
run(`git tag -a ${tagName} ${sha} -m "${tagMessage.replaceAll('"', '\\"')}"`);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Backticks and $() in tagMessage not escaped

run() calls execSync with the string passed directly to a shell. Only " is escaped; if highlights[0] contains a backtick or $(...), the shell will interpret it as a subshell. Since the message comes from changelog.json (curated by an operator), the practical risk is low, but it's safer to use execFileSync with an args array to bypass the shell entirely.

// Safer: use execFileSync to bypass shell entirely
import { execFileSync } from 'node:child_process';
execFileSync('git', ['tag', '-a', tagName, sha, '-m', tagMessage], { stdio: 'inherit' });
Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/changelog-rollup.mjs
Line: 173

Comment:
**Backticks and `$()` in tagMessage not escaped**

`run()` calls `execSync` with the string passed directly to a shell. Only `"` is escaped; if `highlights[0]` contains a backtick or `$(...)`, the shell will interpret it as a subshell. Since the message comes from `changelog.json` (curated by an operator), the practical risk is low, but it's safer to use `execFileSync` with an args array to bypass the shell entirely.

```js
// Safer: use execFileSync to bypass shell entirely
import { execFileSync } from 'node:child_process';
execFileSync('git', ['tag', '-a', tagName, sha, '-m', tagMessage], { stdio: 'inherit' });
```

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Codex

function formatEntrySubtitle(entry: ChangelogEntry): string {
if (entry.version === 'current') return `As of ${formatSingleDate(entry.date)}`;
if (entry.version.startsWith('2026.02.02')) return 'Initial release';
return `Week of ${formatWeekRange(entry.date)}`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Hardcoded version string for "Initial release" label

entry.version.startsWith('2026.02.02') couples the UI to a specific version string. If the initial date is ever corrected or a new "first" entry is introduced, this silently stops working. Consider adding a subtitle?: string field to ChangelogEntry so the data drives the label rather than a code-level string match.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/features/editor/components/whats-new-dialog.tsx
Line: 51

Comment:
**Hardcoded version string for "Initial release" label**

`entry.version.startsWith('2026.02.02')` couples the UI to a specific version string. If the initial date is ever corrected or a new "first" entry is introduced, this silently stops working. Consider adding a `subtitle?: string` field to `ChangelogEntry` so the data drives the label rather than a code-level string match.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Codex

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (3)
.github/workflows/changelog-append.yml (1)

14-16: Guard also tolerates substring matches in unrelated commit subjects.

contains(..., 'chore(changelog)') will match any commit whose subject happens to include that substring (e.g. a revert: Revert "chore(changelog): ..."). That's almost certainly fine in practice, but the stricter check is a prefix test. Non-blocking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/changelog-append.yml around lines 14 - 16, Replace the
lenient substring check contains(github.event.head_commit.message,
'chore(changelog)') with a prefix test using
startsWith(github.event.head_commit.message, 'chore(changelog)') in the workflow
if condition so only commits whose subject begins with "chore(changelog)" are
matched; update the condition line that currently reads contains(...,
'chore(changelog)') to use startsWith(...) instead.
src/data/changelog.json (1)

91-866: Consider normalizing scope values across releases.

Scopes are free-form strings and use both hyphen-compounds (media-library) and singulars (compositions, effects, analysis, player, keyframes, transitions, settings, export) — plus the earliest 2026.02.02 entry has no scope at all. Since changelog-append.mjs emits raw git trailers, an expected set / normalization map would keep the "What's New" UI's scope chips consistent over time. Non-blocking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/data/changelog.json` around lines 91 - 866, The changelog uses
inconsistent free-form "scope" values inside the "releases" array (many entries
in src/data/changelog.json) and some entries lack a scope; update all "scope"
keys to a canonical set (choose consistent hyphenation/singular/plural forms)
and fill missing scopes (e.g., the 2026.02.02 items) so the UI chips remain
stable, and add a normalization/validation step in changelog-append.mjs that
maps incoming git-trailer scopes to the canonical values (e.g., a small
normalization map and fallback logging) before appending to the "releases"
array.
src/features/editor/components/toolbar.tsx (1)

66-73: Simplify: collapse the two effects into a single mount-time read.

The second effect clears hasUnseenWhatsNew whenever the dialog opens, but WhatsNewDialog already calls markChangelogSeen() on open. Since the indicator is only recomputed on mount anyway, you could instead set hasUnseenWhatsNew to false in the open-click handler and drop the second effect. Minor readability nit.

Proposed diff
-  useEffect(() => {
-    setHasUnseenWhatsNew(hasUnseenChangelog());
-  }, []);
-
-  useEffect(() => {
-    if (!showWhatsNewDialog) return;
-    setHasUnseenWhatsNew(false);
-  }, [showWhatsNewDialog]);
+  useEffect(() => {
+    setHasUnseenWhatsNew(hasUnseenChangelog());
+  }, []);
-          onClick={() => setShowWhatsNewDialog(true)}
+          onClick={() => {
+            setShowWhatsNewDialog(true);
+            setHasUnseenWhatsNew(false);
+          }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/editor/components/toolbar.tsx` around lines 66 - 73, Collapse
the two useEffect hooks by removing the effect that watches showWhatsNewDialog
and instead clear the local state when opening the dialog: keep the mount-time
effect that calls setHasUnseenWhatsNew(hasUnseenChangelog()), delete the second
useEffect tied to [showWhatsNewDialog], and modify the handler that sets
showWhatsNewDialog (the open-click handler) to also call
setHasUnseenWhatsNew(false) (and continue to rely on
WhatsNewDialog.markChangelogSeen() on actual open); update references to
useEffect, setHasUnseenWhatsNew, hasUnseenChangelog, showWhatsNewDialog,
WhatsNewDialog, and markChangelogSeen accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/changelog-append.yml:
- Around line 49-59: The commit-and-push step may fail if another push landed
after checkout; update the block that runs the push (the commands around git
add, git commit -m and git push) to perform a small rebase-and-retry loop:
before git push run git pull --rebase origin main (or the target branch) and if
that fails, retry the sequence a few times (re-attempt git add/commit if needed)
or abort after N attempts with a non-zero exit; ensure the commit message (used
in git commit -m) is preserved across retries and that the retry logic only runs
when there are changes to push.
- Around line 33-39: The current workflow writes range=$BEFORE..HEAD without
verifying $BEFORE exists, which breaks on force-push; update the step that sets
BEFORE to run git cat-file -e "$BEFORE" (or equivalent) and if that check fails
set the output to range=HEAD~1..HEAD, otherwise keep range=$BEFORE..HEAD so
changelog-append.mjs never receives a ref that git log cannot resolve; reference
the BEFORE variable and the range output written to GITHUB_OUTPUT when making
this change.
- Around line 1-10: Add a top-level concurrency key to the workflow to serialize
concurrent runs and prevent racing git pushes: insert a concurrency block (e.g.
concurrency: group: "changelog-append-${{ github.ref }}" cancel-in-progress:
false) at the top-level of the YAML (alongside name/on/permissions) so runs that
target the same ref are executed serially.

In `@scripts/changelog-rollup.mjs`:
- Around line 50-60: The comment in lastMondayIso misstates the Sunday behavior:
the current formula for daysBack = ((dayOfWeek + 6) % 7) + 7 returns 13 for
Sunday (two Mondays back) not 6; update the comment to accurately describe that
on Sunday the function returns the Monday 13 days ago (because we want the
Monday of the previous calendar week that already closed), or if you prefer the
alternative behavior change the formula that computes daysBack
accordingly—locate lastMondayIso and the daysBack calculation and either correct
the explanatory comment to match the implemented logic or replace the formula to
yield 6 for Sunday.
- Around line 168-173: The tag message is still passed through the shell via
string interpolation (the code using run(`git tag -a ${tagName} ${sha} -m
"...")`) and the replaceAll escape is incomplete; change the call to invoke git
with an argv array (e.g., use child_process.execFileSync or update run to accept
an args-array) and pass tagName, sha and tagMessage as separate arguments so the
shell is bypassed; keep the same tagMessage computation (shaArg,
latestMainMergeSha(), highlightLine from releaseEntry.highlights or
formatWeekRange) but remove the replaceAll escaping and call git with a safe
argv array instead.

---

Nitpick comments:
In @.github/workflows/changelog-append.yml:
- Around line 14-16: Replace the lenient substring check
contains(github.event.head_commit.message, 'chore(changelog)') with a prefix
test using startsWith(github.event.head_commit.message, 'chore(changelog)') in
the workflow if condition so only commits whose subject begins with
"chore(changelog)" are matched; update the condition line that currently reads
contains(..., 'chore(changelog)') to use startsWith(...) instead.

In `@src/data/changelog.json`:
- Around line 91-866: The changelog uses inconsistent free-form "scope" values
inside the "releases" array (many entries in src/data/changelog.json) and some
entries lack a scope; update all "scope" keys to a canonical set (choose
consistent hyphenation/singular/plural forms) and fill missing scopes (e.g., the
2026.02.02 items) so the UI chips remain stable, and add a
normalization/validation step in changelog-append.mjs that maps incoming
git-trailer scopes to the canonical values (e.g., a small normalization map and
fallback logging) before appending to the "releases" array.

In `@src/features/editor/components/toolbar.tsx`:
- Around line 66-73: Collapse the two useEffect hooks by removing the effect
that watches showWhatsNewDialog and instead clear the local state when opening
the dialog: keep the mount-time effect that calls
setHasUnseenWhatsNew(hasUnseenChangelog()), delete the second useEffect tied to
[showWhatsNewDialog], and modify the handler that sets showWhatsNewDialog (the
open-click handler) to also call setHasUnseenWhatsNew(false) (and continue to
rely on WhatsNewDialog.markChangelogSeen() on actual open); update references to
useEffect, setHasUnseenWhatsNew, hasUnseenChangelog, showWhatsNewDialog,
WhatsNewDialog, and markChangelogSeen accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2bcb1941-0083-4c4a-a317-18e4b566f641

📥 Commits

Reviewing files that changed from the base of the PR and between b99fad0 and 4545d72.

📒 Files selected for processing (13)
  • .claude/launch.json
  • .claude/skills/changelog/SKILL.md
  • .github/workflows/changelog-append.yml
  • .gitignore
  • CHANGELOG.md
  • package.json
  • scripts/changelog-append.mjs
  • scripts/changelog-rollup.mjs
  • src/data/changelog-types.ts
  • src/data/changelog.json
  • src/features/editor/components/toolbar.tsx
  • src/features/editor/components/whats-new-dialog.tsx
  • src/features/editor/components/whats-new-seen.ts

Comment thread .github/workflows/changelog-append.yml
Comment thread .github/workflows/changelog-append.yml
Comment thread .github/workflows/changelog-append.yml Outdated
Comment thread scripts/changelog-rollup.mjs
Comment thread scripts/changelog-rollup.mjs Outdated
Workflow:
- Add concurrency block to serialize runs on the same ref
- Verify github.event.before exists before using (git cat-file -e),
  fall back to HEAD~1..HEAD when missing or not in history (force-push)
- Quote the range expression in the node invocation
- Use startsWith instead of contains for the chore(changelog) guard
- Replace single git push with a rebase-and-retry loop (5 attempts)

Rollup script:
- Clarify comment in lastMondayIso to accurately describe Sunday behavior
  (13 days back = previous completed week's Monday)
- Use execFileSync with argv array for git tag to bypass shell escaping
- Remove now-unused run() helper

Dialog:
- Add optional subtitle field to ChangelogEntry so data drives the label
- Replace hardcoded 2026.02.02 match with subtitle lookup
- Backfill "Initial release" subtitle on the 2026.02.02 entry

Toolbar:
- Collapse two useEffects into one mount-time effect; clear the
  unseen-dot state inline in the open handler instead of via effect
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
scripts/changelog-rollup.mjs (1)

123-129: latestMainMergeSha hardcodes main — fails on detached HEAD or forked default branches.

git log --first-parent main -1 will error out if the local clone doesn't have a main ref (e.g., CI checkout with a different default branch, or a worktree tracking only the current branch). Consider falling back to HEAD or the upstream of the current branch, and wrapping in a clearer error than the raw git exit.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/changelog-rollup.mjs` around lines 123 - 129, latestMainMergeSha
currently hardcodes the branch name "main" which will fail in detached HEADs or
repos whose default branch is different; modify latestMainMergeSha to call git
in a guarded way: detect a safe ref first (try to resolve the current branch
upstream via "git rev-parse --abbrev-ref --symbolic-full-name @{u}" or fallback
to the local branch name via "git rev-parse --abbrev-ref HEAD"), then use that
resolved ref instead of the literal "main" when running the existing
execSync('git log --first-parent ... --pretty=format:%H'), and wrap all execSync
calls in try/catch to throw a clearer, contextual error if resolution or the git
log command fails; reference the latestMainMergeSha function and its use of
execSync in your changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/changelog-rollup.mjs`:
- Around line 38-47: The CLI parsing loop currently lets `--date` fall through
when the next token is missing or is another flag and silently ignores unknown
`--flag`s; update the parsing in the for-loop that inspects `args`,
`dateOverride`, and `sha` so that when encountering `--date` you check that
`args[i + 1]` exists and does NOT start with '--' (and optionally is a valid
date string) before assigning `dateOverride`, otherwise emit an explicit
error/usage message and exit instead of falling back to lastMondayIso(); also
treat any unknown token that starts with '--' as an error (print message and
exit) rather than silently ignoring it to avoid surprising behavior.
- Around line 162-164: The value assigned to pkg.version should have leading
zeros removed from numeric identifiers so it is valid SemVer (keep the original
zero-padded version for tags/changelog); change the assignment that sets
pkg.version = version to instead derive a package-safe version by splitting the
version on '.', for each identifier that is purely numeric convert to a Number
and back to string to strip any leading zeros (leave non-numeric identifiers
unchanged), then join with '.' and set pkg.version to that cleaned string before
calling writeJson; refer to the variables/version string named "version", the
package object "pkg", and the writeJson call to locate where to apply this
transformation.

---

Nitpick comments:
In `@scripts/changelog-rollup.mjs`:
- Around line 123-129: latestMainMergeSha currently hardcodes the branch name
"main" which will fail in detached HEADs or repos whose default branch is
different; modify latestMainMergeSha to call git in a guarded way: detect a safe
ref first (try to resolve the current branch upstream via "git rev-parse
--abbrev-ref --symbolic-full-name @{u}" or fallback to the local branch name via
"git rev-parse --abbrev-ref HEAD"), then use that resolved ref instead of the
literal "main" when running the existing execSync('git log --first-parent ...
--pretty=format:%H'), and wrap all execSync calls in try/catch to throw a
clearer, contextual error if resolution or the git log command fails; reference
the latestMainMergeSha function and its use of execSync in your changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: db04de4b-ead0-41e5-8157-c02e78ae8abf

📥 Commits

Reviewing files that changed from the base of the PR and between 4545d72 and 1ebcd45.

📒 Files selected for processing (6)
  • .github/workflows/changelog-append.yml
  • scripts/changelog-rollup.mjs
  • src/data/changelog-types.ts
  • src/data/changelog.json
  • src/features/editor/components/toolbar.tsx
  • src/features/editor/components/whats-new-dialog.tsx
✅ Files skipped from review due to trivial changes (3)
  • src/data/changelog.json
  • .github/workflows/changelog-append.yml
  • src/data/changelog-types.ts

Comment on lines +38 to +47
for (let i = 0; i < args.length; i += 1) {
const a = args[i];
if (a === '--date') {
dateOverride = args[i + 1];
i += 1;
} else if (!a.startsWith('--')) {
sha = a;
}
}
return { sha, dateOverride };
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

--date with missing value silently falls through to default.

If the user runs node scripts/changelog-rollup.mjs --date (no value) or --date --other, args[i + 1] is undefined / another flag and lastMondayIso() is used instead, producing a surprising version. Also, an unknown --flag is silently ignored.

🔧 Proposed fix
     if (a === '--date') {
-      dateOverride = args[i + 1];
-      i += 1;
+      const val = args[i + 1];
+      if (!val || val.startsWith('--') || !/^\d{4}-\d{2}-\d{2}$/.test(val)) {
+        console.error('--date requires a YYYY-MM-DD argument');
+        process.exit(1);
+      }
+      dateOverride = val;
+      i += 1;
     } else if (!a.startsWith('--')) {
       sha = a;
+    } else {
+      console.error(`Unknown flag: ${a}`);
+      process.exit(1);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (let i = 0; i < args.length; i += 1) {
const a = args[i];
if (a === '--date') {
dateOverride = args[i + 1];
i += 1;
} else if (!a.startsWith('--')) {
sha = a;
}
}
return { sha, dateOverride };
for (let i = 0; i < args.length; i += 1) {
const a = args[i];
if (a === '--date') {
const val = args[i + 1];
if (!val || val.startsWith('--') || !/^\d{4}-\d{2}-\d{2}$/.test(val)) {
console.error('--date requires a YYYY-MM-DD argument');
process.exit(1);
}
dateOverride = val;
i += 1;
} else if (!a.startsWith('--')) {
sha = a;
} else {
console.error(`Unknown flag: ${a}`);
process.exit(1);
}
}
return { sha, dateOverride };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/changelog-rollup.mjs` around lines 38 - 47, The CLI parsing loop
currently lets `--date` fall through when the next token is missing or is
another flag and silently ignores unknown `--flag`s; update the parsing in the
for-loop that inspects `args`, `dateOverride`, and `sha` so that when
encountering `--date` you check that `args[i + 1]` exists and does NOT start
with '--' (and optionally is a valid date string) before assigning
`dateOverride`, otherwise emit an explicit error/usage message and exit instead
of falling back to lastMondayIso(); also treat any unknown token that starts
with '--' as an error (print message and exit) rather than silently ignoring it
to avoid surprising behavior.

Comment on lines +162 to +164
const pkg = loadJson(PACKAGE_JSON);
pkg.version = version;
writeJson(PACKAGE_JSON, pkg);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does npm semver allow leading zeros in numeric identifiers like 2026.04.06?

💡 Result:

No, npm semver does not allow leading zeros in numeric identifiers like 2026.04.06. The SemVer specification (v2.0.0), which npm's semver implementation follows strictly by default, states that numeric identifiers in the major, minor, and patch versions MUST NOT contain leading zeroes. Section 2 of the spec explicitly says: "A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative integers, and MUST NOT contain leading zeroes." In 2026.04.06, the minor (04) and patch (06) identifiers have leading zeros, making it invalid. npm/node-semver's valid function returns null for such versions in strict mode (default), as confirmed in multiple GitHub issues where examples like '1.04', '0.67.09', and '2018.06.05' fail validation or coercion without loose: true option. While semver.coerce with {loose: true} might strip leading zeros (e.g., '0000.004.02' -> '0.4.2'), this is not standard validation and the output would be '2026.4.6', not preserving the original. For strict SemVer compliance used by npm for package versions, leading zeros are prohibited to ensure proper numeric sorting and comparison.

Citations:


🏁 Script executed:

cat -n scripts/changelog-rollup.mjs | head -180 | tail -40

Repository: walterlow/freecut

Length of output: 1568


🏁 Script executed:

cat -n scripts/changelog-rollup.mjs | head -150

Repository: walterlow/freecut

Length of output: 6123


Strip leading zeros from the version written to package.json.

mondayIso.replaceAll('-', '.') produces 2026.04.06, which violates the SemVer 2.0.0 specification: numeric identifiers MUST NOT include leading zeroes. Writing this to package.json#version will break npm version, npm publish, lockfile resolution, and fail semver.valid().

Keep the zero-padded form for the git tag and changelog display, but strip leading zeros for the package version:

Proposed fix
const version = mondayIso.replaceAll('-', '.');
const tagName = `v${version}`;
+// SemVer forbids leading zeros in numeric identifiers; strip them for package.json.
+const pkgVersion = version
+  .split('.')
+  .map((p) => String(Number(p)))
+  .join('.');

const pkg = loadJson(PACKAGE_JSON);
-pkg.version = version;
+pkg.version = pkgVersion;
writeJson(PACKAGE_JSON, pkg);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/changelog-rollup.mjs` around lines 162 - 164, The value assigned to
pkg.version should have leading zeros removed from numeric identifiers so it is
valid SemVer (keep the original zero-padded version for tags/changelog); change
the assignment that sets pkg.version = version to instead derive a package-safe
version by splitting the version on '.', for each identifier that is purely
numeric convert to a Number and back to string to strip any leading zeros (leave
non-numeric identifiers unchanged), then join with '.' and set pkg.version to
that cleaned string before calling writeJson; refer to the variables/version
string named "version", the package object "pkg", and the writeJson call to
locate where to apply this transformation.

@walterlow walterlow merged commit 0ff0eb8 into staging Apr 17, 2026
6 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Apr 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant