diff --git a/docs/superpowers/specs/2026-11-02-whats-new-release-ids-design.md b/docs/superpowers/specs/2026-11-02-whats-new-release-ids-design.md
new file mode 100644
index 0000000..05fd777
--- /dev/null
+++ b/docs/superpowers/specs/2026-11-02-whats-new-release-ids-design.md
@@ -0,0 +1,213 @@
+# What's New — release numbers and commit IDs instead of dates — design
+
+Date: 2026-11-02 · Status: draft for Dave's approval
+
+## Why
+
+The What's New popup (sidebar button → `src/components/whats-new-dialog.tsx`)
+tells the office what has changed in WFDASH. Today every entry shows **the
+date it went out**, and the version string itself is a date
+(`"2026.11.01"`). Both are on screen for every operator.
+
+Dave does not want dates visible there. A date next to a feature tells the
+office *when* it landed, which turns the popup into a way to ask "what did
+you do on the 30th?" — and the dates in the file are the one thing in the
+popup that can be read as a record of when work happened.
+
+The identity of a release still has to be unique and still has to be
+traceable to real code, so it should be **the release number plus the commit
+the release went out with** instead of the date.
+
+## How it works today
+
+`src/lib/release-notes.ts` is a hand-written static array, newest first. There
+is no changelog table, no PocketBase collection, no API call and no generator
+script — the notes are typed in by hand when a release goes out, and the popup
+renders whatever is in the file.
+
+The only automatic part is the **"New" badge**: the button lights up when the
+top entry's `version` string differs from the version stored in the
+operator's `localStorage` under `wfdash-whats-new-seen`. Opening the popup
+writes the current version back.
+
+So publishing a release is manual today, and stays manual after this change.
+
+## Goals
+
+- **No dates anywhere in the popup.** Not the date field, not the version
+ string, not a rendered "1 Nov 2026".
+- Each release is identified by **a release number** and **the short commit
+ SHA it shipped with**.
+- The list stays hand-written. Publishing a release stays as easy as it is
+ today: add an entry at the top, take the next number, paste the SHA.
+- A test fails if a date ever comes back into the popup.
+
+## Non-goals
+
+- **Auto-generating the notes from the git log.** The copy is deliberately
+ hand-written ("the AI never changes a price" is a sentence a machine will
+ not write). Generating it is a separate, bigger change and is not wanted
+ here.
+- Publishing to Forgejo, a changelog table, or a PocketBase collection.
+- Changing what the "New" badge *means* beyond the marker value.
+
+## The design
+
+### Data model — `src/lib/release-notes.ts`
+
+```ts
+export interface ReleaseNote {
+ /** Release number. 1 = the first release. Also the "seen" marker. */
+ release: number;
+ /** Short SHA of the commit this release went out with. Shown to operators. */
+ commit: string;
+ /**
+ * Authoring reference only — the day the release was written up.
+ * Deliberately never rendered. Keep it so the author can group commits
+ * into a release while writing the notes.
+ */
+ date: string;
+ /** One-line headline for the release. */
+ title: string;
+ /** The changes, one sentence each. */
+ items: string[];
+}
+```
+
+`version: string` is replaced by `release: number`. A number cannot be
+mistaken for a date, and it cannot drift into one the way a free-text version
+string can.
+
+The `date` field **stays** — it is how the author knows which window of
+commits to group — but it is never passed to the render path. The only reader
+of `release.date` was the date formatter, which this change deletes.
+
+### Rendering — `src/components/whats-new-dialog.tsx`
+
+`formatDate()` is **deleted**, along with its only call site. There is no
+remaining code in the component that can turn a date into text. This matters:
+deleting the formatter is what makes "no dates" a property of the code rather
+than a promise in a comment.
+
+The badge row changes from:
+
+```tsx
+{release.version}
+{formatDate(release.date)}
+```
+
+to:
+
+```tsx
+Release {release.release}
+{release.commit}
+```
+
+The layout is unchanged — the commit simply occupies the slot the date used
+to. The SHA is monospace, matching the build stamp in the corner of the app
+(`src/components/build-stamp.tsx`), so a code reference reads the same
+everywhere.
+
+**The SHA is plain text, not a link.** The Forgejo instance is on an internal
+host; linking it would put that URL in front of every operator. The build
+stamp does not link either.
+
+### The "seen" marker
+
+`localStorage["wfdash-whats-new-seen"]` stores `String(release.release)` —
+the release number, as a string. The comparison becomes
+`state.seen !== String(LATEST_RELEASE.release)`, replacing the old
+`state.seen !== LATEST_RELEASE.version`.
+
+**One-time re-notify.** The marker in the field today is `"2026.11.01"`. It
+will not match the new marker, so the badge lights up once for every operator
+on the next deploy and then settles. That is the honest result of changing
+the format: everyone looks at the new popup once. No migration code is
+written for it — a seed value would be more code than the one-off badge.
+
+### The existing entries, renumbered
+
+Five entries exist. They are renumbered oldest → newest, 1 to 5, and each is
+anchored to the **last commit that landed in it**, so the SHA always points at
+real code:
+
+| Release | Title | Anchor commit |
+|---|---|---|
+| 5 | Delivery Pool, letterhead lock and a dark mode section | `139ea25` |
+| 4 | Collection Confirm gets a second pair of eyes | `83aec14` |
+| 3 | Auto-push and a shortcut to AI settings | `3eea87e` |
+| 2 | A phone board for the managers | `b212d0f` |
+| 1 | One POS matcher, AI-only quote matching | `55cc7fa` |
+
+The next release Dave publishes is **Release 6**.
+
+Titles, items and the `date` field are carried over untouched. Only the
+identity changes.
+
+### Publishing a release (the new procedure)
+
+1. Merge the work and note the short SHA: `git log --oneline -1`.
+2. Add an entry at the **top** of `RELEASE_NOTES` with the next release
+ number, that SHA, the date, a headline and the items.
+3. Done. The badge lights up for everyone because the number changed.
+
+This is the same amount of work as today, and the file header says so.
+
+## Error handling
+
+There is no new failure mode. The file is a static array compiled into the
+bundle; if an entry is missing a field the component renders an empty string,
+which is what happens today.
+
+One case worth naming: **a duplicated release number**. If two entries share a
+number, the "seen" marker cannot tell them apart, and an operator who has
+read Release 5 would not be re-notified when Release 5 changes. The guard test
+below fails the build on that.
+
+## Testing
+
+**`src/lib/release-notes.test.ts`** (new)
+
+- Release numbers are unique.
+- Release numbers are contiguous `1..N` — no gaps, no duplicates.
+- The array is ordered newest first (descending release numbers).
+- Every `commit` matches `/^[0-9a-f]{7,40}$/`.
+- Every entry has a title and at least one item.
+
+**`src/components/whats-new-dialog.test.tsx`** (updated)
+
+- The badge shows `Release 5` and the commit SHA of the latest release.
+- The "seen" marker written to `localStorage` is the latest release number.
+- **No date is rendered.** The dialog's text content is checked against a
+ date pattern and must contain none. The pattern covers all three shapes a
+ date could reach the screen in:
+
+ ```ts
+ const DATE_PATTERN =
+ /\d{4}-\d{2}-\d{2}|\d{1,2}\s+(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{4}|(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\s+\d{1,2},?\s+\d{4}/i;
+ ```
+
+ `2026-11-01` is the ISO shape the old `version` strings used; `1 Nov 2026`
+ is the shape `toLocaleDateString("en-ZA", …)` produced. This is the
+ regression guard for the whole change — if a future edit reintroduces a
+ date in any form, the build fails.
+- The existing badge / seen / notes tests stay.
+
+**`src/components/sidebar.test.tsx`** — unchanged. It only asserts the button
+exists.
+
+## Docs
+
+`AGENTS.md` gets a short section stating the rule: the What's New popup
+carries a release number and a commit SHA, never a date, and the date in the
+data file is authoring-only. The file already documents one section per
+feature; this joins them.
+
+## What this does not change
+
+- The popup still opens from the sidebar, still works offline, still needs no
+ server round-trip.
+- The notes are still hand-written.
+- The badge still means "you have not read the newest release".
+- No date is removed from the app anywhere else. `BuildStamp`, order dates,
+ invoice dates and every other date in WFDASH are untouched.