From 9e90f7ff8a1499b002b2c7984837afa74ecc3fac Mon Sep 17 00:00:00 2001 From: Z User Date: Thu, 29 Oct 2026 08:43:20 +0000 Subject: [PATCH] 06aca6bb-9fee-4fa9-bcc4-7ce527f71121 --- src/components/ava/header.tsx | 4 +- src/components/ava/whats-new-dialog.tsx | 159 +++++++++++++++++ src/lib/release-notes.ts | 219 ++++++++++++++++++++++++ src/lib/version.ts | 17 +- worklog.md | 22 +++ 5 files changed, 419 insertions(+), 2 deletions(-) create mode 100644 src/components/ava/whats-new-dialog.tsx create mode 100644 src/lib/release-notes.ts diff --git a/src/components/ava/header.tsx b/src/components/ava/header.tsx index e7bbe99..29c3c60 100755 --- a/src/components/ava/header.tsx +++ b/src/components/ava/header.tsx @@ -12,6 +12,7 @@ import { Button } from '@/components/ui/button' import { Switch } from '@/components/ui/switch' import { BeatDot } from '@/components/ava/beat-dot' import { LinkDialog } from '@/components/ava/link-dialog' +import { WhatsNew } from '@/components/ava/whats-new-dialog' import { tapBeat, useAudioSource } from '@/components/ava/audio-source' import { useAvaStore } from '@/lib/store' import { getStoredDarkBase } from '@/lib/theme' @@ -191,8 +192,9 @@ export function AppHeader() { - {/* theme switch + BPM readout + beat indicator + Set Beat — always visible */} + {/* what's new + theme switch + BPM readout + beat indicator + Set Beat — always visible */}
+ diff --git a/src/components/ava/whats-new-dialog.tsx b/src/components/ava/whats-new-dialog.tsx new file mode 100644 index 0000000..d60b0c1 --- /dev/null +++ b/src/components/ava/whats-new-dialog.tsx @@ -0,0 +1,159 @@ +// OVE — "What's New!" header button + dialog (v7.6). +// +// Self-contained: the button and its dialog live together (local open state, +// no store wiring needed — nothing else opens this dialog). The list comes +// from src/lib/release-notes.ts, newest release first; the running version +// is badged "current". A small emerald dot on the button marks updates the +// visitor hasn't seen yet — stored per visitor in localStorage and cleared +// the moment the dialog is opened (mount-guarded so SSR never mismatches, +// same pattern as the header's ThemeToggle). + +'use client' + +import { useState, useSyncExternalStore } from 'react' +import { Sparkles } from 'lucide-react' +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { RELEASE_NOTES, type ChangeKind, type ReleaseNote } from '@/lib/release-notes' +import { APP_SHORT, APP_VERSION } from '@/lib/version' +import { thinScrollbar } from '@/components/ava/shared' +import { cn } from '@/lib/utils' + +const SEEN_KEY = 'ove.whatsNew.seen' + +const KIND_META: Record = { + added: { label: 'Added', cls: 'border-emerald-400/40 bg-emerald-400/10 text-emerald-400' }, + improved: { label: 'Improved', cls: 'border-amber-400/40 bg-amber-400/10 text-amber-400' }, + fixed: { label: 'Fixed', cls: 'border-rose-400/40 bg-rose-400/10 text-rose-400' }, + removed: { label: 'Removed', cls: 'border-zinc-500/40 bg-zinc-500/10 text-zinc-400' }, +} + +/** One release: version chip + name + change rows with kind tags. */ +function ReleaseSection({ note, current }: { note: ReleaseNote; current: boolean }) { + return ( +
+
+ + v{note.version} + + {note.name && {note.name}} + {current && ( + + current + + )} +
+
    + {note.changes.map((c, i) => { + const meta = KIND_META[c.kind] + return ( +
  • + + {meta.label} + + {c.text} +
  • + ) + })} +
+
+ ) +} + +const emptySubscribe = () => () => {} +/** Hydration-safe localStorage read: null on the server, the stored value on + * the client after hydration (React re-checks the snapshot on every render, + * so writing the key then re-rendering updates it — no effects needed). */ +function useSeenVersion(): string | null { + return useSyncExternalStore( + emptySubscribe, + () => { + try { + return localStorage.getItem(SEEN_KEY) ?? '' + } catch { + return '' + } + }, + () => null, + ) +} + +export function WhatsNew() { + const [open, setOpen] = useState(false) + const seenVersion = useSeenVersion() + const unseen = seenVersion !== null && seenVersion !== APP_VERSION + + const handleOpenChange = (o: boolean) => { + setOpen(o) + if (o) { + try { + localStorage.setItem(SEEN_KEY, APP_VERSION) + } catch { + /* private mode — the dot just stays for this visit */ + } + } + } + + return ( + <> + + + + + + + + What’s New! + + + Every update shipped to {APP_SHORT} — {RELEASE_NOTES.length} releases and + counting, newest first. + + +
+ {RELEASE_NOTES.map((note) => ( + + ))} +
+
+
+ + ) +} diff --git a/src/lib/release-notes.ts b/src/lib/release-notes.ts new file mode 100644 index 0000000..8c06301 --- /dev/null +++ b/src/lib/release-notes.ts @@ -0,0 +1,219 @@ +// OVE — release notes for the "What's New!" dialog (v7.6). +// +// The visitor-facing changelog, newest release first. This is the ONE place +// to update when shipping a version: every bump of APP_VERSION in version.ts +// must come with a matching entry at the top of RELEASE_NOTES (a dev-mode +// guard below warns in the console when the two drift apart). +// +// Keep entries brief — one line per change, written for visitors (what they +// can now do / what stopped breaking), not for the build log. The verbose +// engineering history still lives in version.ts; this file is the digest. +// +// Change kinds shown in the dialog: +// added — new capability +// improved — made better/faster/ friendlier +// fixed — a bug that stopped reproducing +// removed — taken away (with the reason in the text) + +import { APP_VERSION } from '@/lib/version' + +export type ChangeKind = 'added' | 'improved' | 'fixed' | 'removed' + +export interface ReleaseChange { + kind: ChangeKind + text: string +} + +export interface ReleaseNote { + /** matches APP_VERSION of that release, e.g. '7.5.1' */ + version: string + /** short release name shown next to the version (optional) */ + name?: string + changes: ReleaseChange[] +} + +export const RELEASE_NOTES: ReleaseNote[] = [ + { + version: '7.6', + name: 'What’s New!', + changes: [ + { kind: 'added', text: '“What’s New!” button in the header — every release’s additions, improvements and fixes at a glance.' }, + { kind: 'added', text: 'A small dot on the button marks updates you haven’t seen yet; opening the list clears it.' }, + ], + }, + { + version: '7.5.1', + changes: [ + { kind: 'fixed', text: 'The “Stream a music link” dialog no longer stretches past its own border when a saved link title is long.' }, + { kind: 'improved', text: 'The link dialog is a little wider, and long titles end with an ellipsis instead of overflowing.' }, + ], + }, + { + version: '7.5', + name: 'Music links that just play', + changes: [ + { kind: 'fixed', text: 'Many YouTube links failing with “media resource not suitable” — tracks are now downloaded audio-only and converted to MP3 on the server, instead of streaming expiring, IP-locked URLs.' }, + { kind: 'improved', text: 'The Playing label shows the video’s real title.' }, + { kind: 'improved', text: 'Over-long tracks get an honest “too long” answer, and playlist transitions no longer stall behind conversions.' }, + ], + }, + { + version: '7.4', + name: 'Sequence mode', + changes: [ + { kind: 'added', text: '“Sequence” mix mode — cycles the folder’s presets in order, wrapping back to the first so the folder loops forever.' }, + { kind: 'improved', text: 'Random never picks the same preset twice in a row; the demo showcase now walks all ten Zai-Magic presets in order.' }, + ], + }, + { + version: '7.3', + name: 'Demo-flow refinements', + changes: [ + { kind: 'improved', text: 'The demo track plays once through and returns you to the source picker — a demo, not a radio station.' }, + { kind: 'fixed', text: 'The randomizer no longer switches presets while no music is playing.' }, + { kind: 'removed', text: 'Preset-change popups (“Preset loaded”…) — the footer’s SCRIPT label already echoes every change.' }, + ], + }, + { + version: '7.2.1', + changes: [ + { kind: 'fixed', text: 'Zai-Magic “Supernova” fading to black after its first explosion — every beat now re-detonates the field with fresh geometry.' }, + ], + }, + { + version: '7.2', + name: 'Showcase flow', + changes: [ + { kind: 'added', text: 'Animated shimmer on the Demo Track button — one click starts the full showcase (demo music + the Zai-Magic folder on random mix).' }, + { kind: 'added', text: 'Preset changes blur-dissolve over 0.7 s instead of hard-cutting.' }, + { kind: 'improved', text: 'Fresh visitors start on the Black + Purple Outline theme with Bass Bloom loaded.' }, + ], + }, + { + version: '7.1', + name: 'Real demo track + beat detector rewrite', + changes: [ + { kind: 'added', text: 'The Demo button plays “Neural Mecha Pulse” — a real mastered recording — with live tempo detection and a track chip in the header.' }, + { kind: 'improved', text: 'Beat detection rewritten the way real trackers work (spectral flux → autocorrelation → phase-locked grid): locks the true BPM of real music in about two seconds.' }, + { kind: 'removed', text: 'The demo BPM slider — a real track’s tempo is detected, not dialed in.' }, + ], + }, + { + version: '7.00', + name: 'Zai-Magic', + changes: [ + { kind: 'added', text: 'Ten hand-authored presets built around beat-synced color — the color wheel turns on the kick, palettes swap per beat and per bar.' }, + { kind: 'added', text: 'Script tools hsvr/hsvg/hsvb, smoothstep and the beatage kick-envelope variable, documented in the help panel.' }, + ], + }, + { + version: '6.9.3', + changes: [{ kind: 'fixed', text: 'Share URLs point at the correct domain.' }], + }, + { + version: '6.9.2', + changes: [{ kind: 'fixed', text: 'The share preview image now passes Facebook’s checks; new title-card artwork.' }], + }, + { + version: '6.9.1', + changes: [ + { kind: 'added', text: 'Social share previews — sharing the site shows the big image card on Facebook, X, WhatsApp and Discord.' }, + ], + }, + { + version: '6.9', + name: 'House account', + changes: [ + { kind: 'added', text: 'Anonymous visitors play YouTube links through the shared house account — bot walls stop applying to guests.' }, + ], + }, + { + version: '6.8.2', + changes: [ + { kind: 'improved', text: 'Music-service login cut to about four clicks — drag the exported cookie file straight onto the login box.' }, + ], + }, + { + version: '6.8.1', + changes: [ + { kind: 'improved', text: 'The login box recognizes raw Cookie headers, cookies.txt and Cookie-Editor JSON, and validates them live.' }, + ], + }, + { + version: '6.8', + name: 'Self-healing engine', + changes: [ + { kind: 'added', text: 'When YouTube changes their mechanics, the server updates yt-dlp itself and retries behind an “Updating in Background” notice.' }, + ], + }, + { + version: '6.7', + name: 'Lazy playlist queue', + changes: [ + { kind: 'added', text: 'Playlist links become a queue that streams one song at a time — each track is fetched only right before it plays.' }, + { kind: 'added', text: 'Queue bar with skip, jump and replay.' }, + ], + }, + { + version: '6.6', + name: 'Music service logins', + changes: [ + { kind: 'added', text: 'Settings-tab logins for YouTube, SoundCloud, Spotify and Apple Music, encrypted on your computer.' }, + ], + }, + { + version: '6.5', + name: 'Settings tab & themes', + changes: [{ kind: 'added', text: 'Theme picker — base chrome × highlight color, saved per visitor.' }], + }, + { + version: '6.4', + changes: [ + { kind: 'fixed', text: 'Beat detection suspends when no music is playing — no ghost beats after Stop.' }, + { kind: 'added', text: 'Royal purple dark theme (later joined by more bases).' }, + ], + }, + { + version: '6.3', + changes: [{ kind: 'added', text: 'Light/dark tick switch in the header.' }], + }, + { + version: '6.2', + changes: [ + { kind: 'improved', text: 'The embedded preset library is read-only; your own presets live in your browser.' }, + ], + }, + { + version: '6.1', + changes: [ + { kind: 'improved', text: 'Every embedded preset is a unique generated visual — sixteen families, seeded parameters.' }, + ], + }, + { + version: '6.0', + changes: [ + { kind: 'improved', text: 'Renamed to Open Visualization Engine, with a version stamp in the footer.' }, + ], + }, + { + version: '5.0', + changes: [{ kind: 'added', text: 'Folder-tree Presets page.' }], + }, + { + version: '4.0', + changes: [{ kind: 'added', text: 'Link audio source — paste a URL, hear the music.' }], + }, +] + +// Dev-mode guard: the newest entry must describe the running version. If you +// bumped APP_VERSION without adding its release note, this warns in the +// browser console AND the server log so it gets noticed before shipping. +if ( + process.env.NODE_ENV !== 'production' && + RELEASE_NOTES.length > 0 && + RELEASE_NOTES[0].version !== APP_VERSION +) { + console.warn( + `[release-notes] RELEASE_NOTES[0] (v${RELEASE_NOTES[0].version}) does not match APP_VERSION (v${APP_VERSION}) — add the new release's entry to src/lib/release-notes.ts.`, + ) +} diff --git a/src/lib/version.ts b/src/lib/version.ts index fd1f635..9cec5c8 100755 --- a/src/lib/version.ts +++ b/src/lib/version.ts @@ -6,7 +6,22 @@ // useful both for the z.ai Version History and for naming local backups // (v6.0 = rename to Open Visualization Engine + version indicator). // +// STANDING RULE (since v7.6): every APP_VERSION bump MUST come with a +// matching entry at the top of RELEASE_NOTES in src/lib/release-notes.ts — +// that list powers the header's "What's New!" dialog. A dev-mode guard +// warns in the console when the two drift apart. +// // History: +// 7.6 — "What's New!" dialog: a Sparkles button in the header (left of +// the theme switch) opens the visitor-facing changelog — every +// release from v4.0 on, newest first, each with one-line Added / +// Improved / Fixed / Removed notes (src/lib/release-notes.ts, +// color-coded tags, the running version badged "current", +// scrollable with the thin scrollbar). A small emerald dot on the +// button marks updates the visitor hasn't seen yet — per visitor +// in localStorage, cleared on open (mount-guarded, no hydration +// mismatch). Standing rule added: version bumps ship with their +// release note; a dev-mode guard warns when they drift // 7.5.1 — Link dialog width fix: text no longer escapes the dialog box. // Root cause: Radix dialogs lay their children in an implicit CSS // grid whose single column sizes to the widest child's min-content; @@ -205,4 +220,4 @@ export const APP_NAME = 'Open Visualization Engine' export const APP_SHORT = 'OVE' -export const APP_VERSION = '7.5.1' +export const APP_VERSION = '7.6' diff --git a/worklog.md b/worklog.md index 530e0e7..d3fecc2 100755 --- a/worklog.md +++ b/worklog.md @@ -2139,3 +2139,25 @@ E2E (agent-browser, ALL PASS): Stage Summary: - v7.5.1 shipped: the Link dialog (and every other dialog) can no longer be stretched past its border by long unbreakable content — the shared dialog column is capped at the available width, the Link dialog is comfortably wider (512 px), long recent/playlist titles truncate with ellipsis, and error text with embedded URLs wraps. Deploy note: no server changes, pure client fix. + +--- +Task ID: 34 +Agent: main (Z.ai Code) +Task: v7.6 "What's New!" (user request): a button to the left of the header's dark/light theme switch opens a dialog titled "What's New!" listing every release version with a brief summary of what was added / improved / fixed / removed — populated with the project's full history (v4.0 on), and from now on every version bump must add its entry here too. + +Work Log: +- src/lib/release-notes.ts (new): the visitor-facing changelog data — RELEASE_NOTES newest-first (26 entries, v4.0 → v7.6), each { version, name?, changes[] } with changes typed added/improved/fixed/removed and written as one-line visitor-oriented bullets (digest of the verbose version.ts history; engineering detail stays there). Module-scope dev guard warns in the console + dev.log when RELEASE_NOTES[0].version drifts from APP_VERSION, enforcing the new standing rule. +- src/components/ava/whats-new-dialog.tsx (new): self-contained — Sparkles button (h-11 bordered group styling to match the header, emerald icon, "What's New!" label, icon-only text on xs) + Dialog. Dialog: title "What's New!" with sparkle icon, description "Every update shipped to OVE — N releases and counting, newest first", scrollable list (max-h min(60vh,520px), thinScrollbar), each release = version chip (mono, bordered) + optional name + change rows with color-coded ADDED/IMPROVED/FIXED/REMOVED tags (emerald/amber/rose/zinc — all remapped per theme base & accent, verified in globals.css); the running APP_VERSION gets an emerald "current" badge. Unseen-updates dot: small emerald dot on the button when the visitor's last-opened version (localStorage ove.whatsNew.seen) ≠ APP_VERSION; opening the dialog stores the current version and clears the dot. Read via useSyncExternalStore (server snapshot null → no hydration mismatch, client snapshot reads localStorage, React re-checks the snapshot on the setOpen re-render so the dot clears without effects — the react-hooks/set-state-in-effect lint rule blocked the useEffect version). +- header.tsx: mounted in the always-visible right group, first item (left of ThemeToggle as requested). +- version.ts: APP_VERSION 7.6 + history entry + STANDING RULE comment (every bump ships with its RELEASE_NOTES entry). lint clean; tsc clean (pre-existing examples/skills only). + +E2E (agent-browser + VLM, ALL PASS): +- Button sits left of the theme switch (btn right 952 < switch left 995), unseen dot present on first visit; title now "OVE — Open Visualization Engine (v7.6)". +- Dialog: title "What's New!" + sparkle; 26 releases newest-first; v7.6 first with "current" badge; scrollable (2622 px content in 346 px pane; scrolled to bottom → v5.0/v4.0 visible); ZERO elements outside the dialog box (v7.5.1 minmax(0,1fr) fix covers this dialog too); 512 px wide desktop / 343 px at 375 px viewport, fits. +- VLM (dark): tags colored correctly (ADDED green, IMPROVED amber, FIXED rose), no overflow, no cut-off text, aligned. VLM (light theme via the tick switch): all tags/chips/body text readable on white, no contrast problems. +- Dot lifecycle: fresh visit → dot; open → stored 7.6, dot gone after close; simulated old visitor (seen=7.5) → dot returns after reload, clears on open. +- Header widths: 375/390/414/768 px zero overflow with the new button. ≤360 px the header overflows IDENTICALLY with the button hidden (pre-existing BPM/BeatDot/Set-Beat row issue, not a regression — baseline parity measured). +- No release-notes drift warning in console or dev.log (guard green); zero page errors; dev.log clean. + +Stage Summary: +- v7.6 shipped: the header now carries a "What's New!" button (left of the theme switch) opening the full release-notes dialog — 26 releases from v4.0 to today, tagged Added/Improved/Fixed/Removed, newest first, current version badged, with an unseen-updates dot. The release-notes module is now part of the version-bump workflow (standing rule + dev-mode drift guard).