docs: What's New release numbers and commit IDs instead of dates
The What's New popup shows the date each release went out, and the version strings are dates too. That gives the office a way to ask "what did you do on the 30th?". Design: each release is identified by a release number and the short commit SHA it shipped with. The date stays in the data file as an authoring reference but is never rendered, and the date formatter is deleted so no date can reach the screen. Five existing entries are renumbered 1-5, each anchored to the last commit that landed in it. A guard test fails the build if a date ever comes back into the popup.
This commit is contained in:
parent
3ca66ef19a
commit
ebfee9eeaa
1 changed files with 213 additions and 0 deletions
|
|
@ -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
|
||||
<span className="...pill...">{release.version}</span>
|
||||
<span className="text-xs text-muted-foreground">{formatDate(release.date)}</span>
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```tsx
|
||||
<span className="...pill...">Release {release.release}</span>
|
||||
<span className="font-mono text-xs text-muted-foreground">{release.commit}</span>
|
||||
```
|
||||
|
||||
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.
|
||||
Loading…
Reference in a new issue