Skip to content

Commit 0ff0eb8

Browse files
authored
Merge pull request #197 from walterlow/develop
feat: weekly changelog, What's New dialog, and auto-append pipeline
2 parents b99fad0 + 1ebcd45 commit 0ff0eb8

13 files changed

Lines changed: 2089 additions & 3 deletions

File tree

.claude/launch.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"version": "0.0.1",
3+
"configurations": [
4+
{
5+
"name": "dev",
6+
"runtimeExecutable": "npm",
7+
"runtimeArgs": ["run", "dev"],
8+
"port": 5173,
9+
"autoPort": true
10+
},
11+
{
12+
"name": "test",
13+
"runtimeExecutable": "npm",
14+
"runtimeArgs": ["run", "test"],
15+
"port": 0
16+
},
17+
{
18+
"name": "preview",
19+
"runtimeExecutable": "npm",
20+
"runtimeArgs": ["run", "preview"],
21+
"port": 4173
22+
}
23+
]
24+
}

.claude/skills/changelog/SKILL.md

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
---
2+
name: changelog
3+
description: Maintain FreeCut's weekly changelog with a rolling current entry. Use when (1) backfilling historical weeks into CHANGELOG.md and src/data/changelog.json (backfill mode), (2) adding new bullets to the rolling current entry as commits land (append mode), or (3) closing the week and promoting current into a tagged weekly release (rollup mode). Handles commit curation, deduplication, version assignment, tag creation, and keeping both markdown and JSON artifacts in sync.
4+
---
5+
6+
# Changelog skill
7+
8+
FreeCut's changelog lives in two synchronized files:
9+
10+
- **`CHANGELOG.md`** — human-facing, Keep-a-Changelog style, read on GitHub
11+
- **`src/data/changelog.json`** — typed, imported by the "What's New" dialog UI
12+
13+
Both are generated from the same data. Always update both or neither.
14+
15+
## Structure: weekly releases + one rolling current entry
16+
17+
The changelog has two tiers:
18+
19+
1. **`current`** — a single rolling entry for the in-progress week. It accumulates bullets as commits land, dedupes when the same feature is touched multiple times, and is shown in the UI as a "This Week" card. **Not tagged, not versioned with a final version number.**
20+
2. **`releases`** — one entry per completed week, newest first. Each has a version, a date, and a git tag.
21+
22+
### Versioning: CalVer, Monday-start weeks
23+
24+
- Format: `YYYY.MM.DD` where the date is the **Monday** that opens the week (Mon–Sun).
25+
- Tag: `v2026.04.13` (leading `v`, always).
26+
- A week "closes" on Monday morning of the following week. Rollup is **manually triggered**.
27+
- If a mid-week hotfix needs its own release, add `.N` suffix (`2026.04.13.2`). Rare.
28+
- `package.json` `version` field mirrors the most recent released version.
29+
30+
Separate packages (future CLI/API) get their own semver and their own changelog under their package directory. This file is for the web app only.
31+
32+
## Modes
33+
34+
### Backfill mode
35+
36+
Given a git range, produce historical weekly entries.
37+
38+
**Process**:
39+
1. Walk PR merges in chronological order: `git log --merges --first-parent main --pretty=format:"%H|%ad|%s" --date=short <range>`.
40+
2. Group commits by week (Monday-start). For each week, union all non-merge commits across all PRs that landed that week.
41+
3. Apply curation rules (below), dedup features revisited within the week.
42+
4. Emit one weekly entry per week that has at least one user-visible change. Skip empty weeks.
43+
44+
Pre-PR era (if any): collapse the entire foundation into a single initial release entry, dated the Monday of the week before first-PR week.
45+
46+
### Append mode
47+
48+
Triggered ad-hoc to update `current` as new commits land.
49+
50+
**Input**: commits since last read of `current` (or `git log v<lastTag>..HEAD` if current is empty).
51+
52+
**Process**:
53+
1. Walk new commits, applying curation rules.
54+
2. Merge with existing `current.groups`**dedupe by title**. If a new commit refines or walks back a bullet already in current, edit the existing bullet rather than adding a duplicate.
55+
3. Update `current.date` to today.
56+
4. Do not create tags. Do not update `package.json`.
57+
58+
### Rollup mode
59+
60+
Triggered manually on Monday to close the previous week.
61+
62+
**Input**: none (reads `current` and today's date).
63+
64+
**Process**:
65+
1. Determine last week's Monday date → new version `vYYYY.MM.DD`.
66+
2. Move `current` into `releases` with that version and the Monday date.
67+
3. Empty `current` (or seed with any commits landed since Monday).
68+
4. Bump `package.json` `version` to the new release.
69+
5. Create annotated tag locally: `git tag -a v<version> <mergeCommitSha> -m "<version> — <highlights>"`. The tag should point at the last PR merge commit of the closed week, not today's HEAD (since dev continues on develop).
70+
6. **Do not `git push --tags`** — print the plan and wait for user confirmation.
71+
72+
## Curation rules
73+
74+
### Drop
75+
76+
- Merge commits (`Merge pull request`, `Merge branch`)
77+
- `chore(...)` — including `chore(release)`
78+
- `ci(...)`
79+
- `test(...)` unless it documents notable test infra changes
80+
- `refactor(...)` when the scope is internal (stores, types, utils, deps adapters, chunk splits)
81+
- `deps` / `deps-dev` bumps unless major-version bumps with user-visible impact
82+
- **Follow-up fixes** — commits whose message matches `/address.*(review|PR|findings|feedback)|code review|follow-?up|fix.*(lint|typecheck|build|pre-existing)/i` AND land in the same week as a parent feature. Roll them into the parent bullet silently.
83+
- Reverts paired with a subsequent re-fix in the same week — skip both the revert and the offending commit; keep only the final correct implementation.
84+
- "Update src/..." auto-subject merges (GitHub web-edit artifacts)
85+
- Revisits: if the same feature is improved multiple times in one week, dedupe to one bullet describing the final state. Never list the same feature twice in one week.
86+
87+
### Keep and rewrite
88+
89+
- `feat(...)` — always, one bullet per distinct user-visible feature
90+
- `fix(...)` — if the bug was user-observable (rendering, playback, data loss, crash). Skip fixes for code that never shipped or was shipped and reverted in the same week.
91+
- `perf(...)` — if the impact is noticeable (measurable time saved, dropped frames recovered)
92+
93+
### Rewrite style
94+
95+
Commit messages are dev-speak. The changelog is user-facing. Rewrite:
96+
97+
| Commit subject | Changelog bullet |
98+
|---|---|
99+
| `feat(timeline): add Alt+C as alternate split-at-playhead shortcut` | Split clips at playhead with Alt+C |
100+
| `perf(filmstrip): fill zoom gaps with cover frame background and full-set fallback` | Smoother filmstrip rendering when zooming the timeline |
101+
| `fix(preview): retry video with fresh blob URL on stale-blob load errors` | Preview no longer fails when media blobs expire |
102+
| `feat(storage): migrate to workspace folder via File System Access API` | Projects now live on disk in a folder you choose, not hidden browser storage |
103+
104+
Rules of thumb:
105+
- Lead with the verb of the user experience, not the code change.
106+
- Drop internal names (stores, modules, workers) unless the user knows them.
107+
- If a bullet is only meaningful to developers, drop it.
108+
- Aim for ≤12 words per bullet.
109+
- For weekly entries, prefer thematic phrasing over commit-level phrasing ("Trim tools with smart zone detection" rather than listing each tool variant).
110+
111+
### Grouping
112+
113+
Within each weekly entry, group into:
114+
- **Added** — new features
115+
- **Fixed** — user-visible bug fixes
116+
- **Improved** — performance, polish, noticeable refactors
117+
118+
Skip any group with zero entries.
119+
120+
### Highlights
121+
122+
Each weekly entry picks 1–3 highlights — the bullets a user would brag about. These appear at the top of the UI card. Skip highlights for weeks that are purely fixes.
123+
124+
## File formats
125+
126+
### `src/data/changelog.json`
127+
128+
Matches types in `src/data/changelog-types.ts`:
129+
130+
```ts
131+
export type ChangelogGroup = 'added' | 'fixed' | 'improved';
132+
133+
export type ChangelogItem = {
134+
title: string; // ≤12 words, user-facing
135+
scope?: string; // optional, e.g. "timeline"
136+
};
137+
138+
export type ChangelogEntry = {
139+
version: string; // "2026.04.13" for releases, "current" for rolling
140+
date: string; // ISO date — Monday for releases, today for current
141+
highlights?: string[]; // 1-3 bullets
142+
groups: Partial<Record<ChangelogGroup, ChangelogItem[]>>;
143+
};
144+
145+
export type ChangelogFile = {
146+
current: ChangelogEntry | null; // in-progress week
147+
releases: ChangelogEntry[]; // completed weeks, newest first
148+
};
149+
```
150+
151+
### `CHANGELOG.md`
152+
153+
```markdown
154+
# Changelog
155+
156+
All notable changes to FreeCut. Versioning follows weekly CalVer: `YYYY.MM.DD` = the Monday of the release week.
157+
158+
## [Current] — week of 2026-04-13
159+
...
160+
## [2026.04.06] — week of 2026-04-06 to 2026-04-12
161+
...
162+
```
163+
164+
Do **not** include PR links in weekly entries. Weekly entries aggregate many PRs; PR links add noise. Optionally link the GitHub compare view at the bottom of each entry:
165+
166+
```markdown
167+
[Compare](https://github.com/walterlow/freecut/compare/v2026.03.30...v2026.04.06)
168+
```
169+
170+
## Git tag discipline
171+
172+
- Always annotated (`git tag -a`), never lightweight.
173+
- Tag message = version + 1-line summary of highlights.
174+
- Tag points at the **last PR merge commit of that week** on `main`, not develop HEAD.
175+
- Print the plan (tag → sha → date) before creating tags; never push without the user asking.
176+
177+
## When in doubt
178+
179+
- Fewer bullets > more bullets. A wall of text is worse than missing a tiny fix.
180+
- If a week is 100 commits but they all revisit the same 3 features, produce 3 bullets.
181+
- If a commit scope is new and you don't know if it's user-visible, check what files it touches. UI components and public APIs = user-visible; stores/utils/tests = usually not.
182+
- A "current" entry that has grown past ~15 bullets is a sign you need a rollup.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
name: Changelog append
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
permissions:
9+
contents: write
10+
11+
concurrency:
12+
# Serialize runs on the same ref so two pushes in quick succession can't
13+
# race each other when committing back to main.
14+
group: changelog-append-${{ github.ref }}
15+
cancel-in-progress: false
16+
17+
jobs:
18+
append:
19+
# Skip the commits we push ourselves from this workflow.
20+
if: |
21+
!contains(github.event.head_commit.message, '[skip changelog]') &&
22+
!startsWith(github.event.head_commit.message, 'chore(changelog)')
23+
runs-on: ubuntu-latest
24+
steps:
25+
- name: Checkout
26+
uses: actions/checkout@v4
27+
with:
28+
# Full history so github.event.before..HEAD can walk side-branch
29+
# commits brought in by merge commits from staging.
30+
fetch-depth: 0
31+
token: ${{ secrets.GITHUB_TOKEN }}
32+
33+
- name: Compute range
34+
id: range
35+
run: |
36+
# github.event.before is main's tip *before* this push. Walk
37+
# everything from that point to HEAD so we capture all commits
38+
# that actually arrived, regardless of merge/fast-forward shape.
39+
# Fall back to HEAD~1..HEAD when before is missing (branch
40+
# creation) or not in local history (force-push).
41+
BEFORE="${{ github.event.before }}"
42+
if [ -z "$BEFORE" ] || [ "$BEFORE" = "0000000000000000000000000000000000000000" ]; then
43+
echo "range=HEAD~1..HEAD" >> "$GITHUB_OUTPUT"
44+
elif ! git cat-file -e "$BEFORE^{commit}" 2>/dev/null; then
45+
echo "Warning: before-sha $BEFORE not in history; falling back to HEAD~1..HEAD"
46+
echo "range=HEAD~1..HEAD" >> "$GITHUB_OUTPUT"
47+
else
48+
echo "range=$BEFORE..HEAD" >> "$GITHUB_OUTPUT"
49+
fi
50+
51+
- name: Setup Node
52+
uses: actions/setup-node@v4
53+
with:
54+
node-version: 22
55+
56+
- name: Run append script
57+
run: node scripts/changelog-append.mjs "${{ steps.range.outputs.range }}"
58+
59+
- name: Commit and push updated changelog
60+
run: |
61+
if git diff --quiet src/data/changelog.json; then
62+
echo "No changelog updates."
63+
exit 0
64+
fi
65+
git config user.name "github-actions[bot]"
66+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
67+
68+
COMMIT_MSG="chore(changelog): append bullets from $(git rev-parse --short HEAD^) [skip ci]"
69+
git add src/data/changelog.json
70+
git commit -m "$COMMIT_MSG"
71+
72+
# Rebase-and-retry: if another push lands between our checkout and
73+
# our push, rebase onto the new tip and retry. Give up after 5
74+
# attempts.
75+
BRANCH="${GITHUB_REF_NAME}"
76+
for attempt in 1 2 3 4 5; do
77+
if git push origin "HEAD:$BRANCH"; then
78+
echo "Pushed on attempt $attempt."
79+
exit 0
80+
fi
81+
echo "Push attempt $attempt failed; rebasing onto origin/$BRANCH and retrying."
82+
git fetch origin "$BRANCH"
83+
git rebase "origin/$BRANCH" || {
84+
echo "Rebase conflict; aborting."
85+
git rebase --abort || true
86+
exit 1
87+
}
88+
done
89+
echo "Exhausted push retries."
90+
exit 1

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ coverage
4343
# TanStack Router auto-generated files
4444
# Note: Some teams commit this, uncomment if you prefer to commit
4545
# src/routeTree.gen.ts
46-
.claude/
46+
.claude/settings.local.json
47+
.claude/worktrees/
4748

4849
.vercel
4950
.env*.local

0 commit comments

Comments
 (0)