06aca6bb-9fee-4fa9-bcc4-7ce527f71121
This commit is contained in:
parent
04233da343
commit
9e90f7ff8a
5 changed files with 419 additions and 2 deletions
|
|
@ -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() {
|
|||
<SourceControls />
|
||||
</div>
|
||||
|
||||
{/* theme switch + BPM readout + beat indicator + Set Beat — always visible */}
|
||||
{/* what's new + theme switch + BPM readout + beat indicator + Set Beat — always visible */}
|
||||
<div className="order-2 ml-auto flex items-center gap-2.5 md:order-3 sm:gap-3">
|
||||
<WhatsNew />
|
||||
<ThemeToggle />
|
||||
<BpmReadout />
|
||||
<BeatDot size={44} />
|
||||
|
|
|
|||
159
src/components/ava/whats-new-dialog.tsx
Normal file
159
src/components/ava/whats-new-dialog.tsx
Normal file
|
|
@ -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<ChangeKind, { label: string; cls: string }> = {
|
||||
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 (
|
||||
<section aria-label={`Version ${note.version} release notes`}>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
'rounded border px-1.5 py-0.5 font-mono text-[11px] font-bold leading-none',
|
||||
current
|
||||
? 'border-emerald-400/50 bg-emerald-400/15 text-emerald-400'
|
||||
: 'border-zinc-700 bg-zinc-800/60 text-zinc-300',
|
||||
)}
|
||||
>
|
||||
v{note.version}
|
||||
</span>
|
||||
{note.name && <span className="text-sm font-semibold leading-none">{note.name}</span>}
|
||||
{current && (
|
||||
<span className="rounded-full bg-emerald-400/15 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-emerald-400">
|
||||
current
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<ul className="mt-2 space-y-1.5">
|
||||
{note.changes.map((c, i) => {
|
||||
const meta = KIND_META[c.kind]
|
||||
return (
|
||||
<li key={i} className="flex items-start gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
'mt-px shrink-0 rounded border px-1.5 py-px font-mono text-[9px] font-semibold uppercase tracking-wider leading-relaxed',
|
||||
meta.cls,
|
||||
)}
|
||||
>
|
||||
{meta.label}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 text-xs leading-relaxed text-zinc-300">{c.text}</span>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleOpenChange(true)}
|
||||
aria-label="What's New! — release notes"
|
||||
title="What's New! — everything shipped to OVE, newest first"
|
||||
className="relative flex h-11 items-center gap-1.5 rounded-lg border border-zinc-800 bg-zinc-900/60 px-3 text-xs font-medium text-zinc-300 transition-colors hover:border-emerald-500/40 hover:text-zinc-100"
|
||||
>
|
||||
<Sparkles className="size-3.5 text-emerald-400" aria-hidden />
|
||||
<span className="hidden sm:inline">What’s New!</span>
|
||||
<span className="sm:hidden">New</span>
|
||||
{unseen && (
|
||||
<span
|
||||
className="absolute -right-1 -top-1 size-2.5 rounded-full border-2 border-zinc-950 bg-emerald-400"
|
||||
aria-hidden
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
|
||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||
<DialogContent className="max-h-[85vh] sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Sparkles className="size-4 text-emerald-400" aria-hidden />
|
||||
What’s New!
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Every update shipped to {APP_SHORT} — {RELEASE_NOTES.length} releases and
|
||||
counting, newest first.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div
|
||||
className={cn('max-h-[min(60vh,520px)] space-y-5 overflow-y-auto pr-2', thinScrollbar)}
|
||||
role="region"
|
||||
aria-label="Release notes by version"
|
||||
>
|
||||
{RELEASE_NOTES.map((note) => (
|
||||
<ReleaseSection key={note.version} note={note} current={note.version === APP_VERSION} />
|
||||
))}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
219
src/lib/release-notes.ts
Normal file
219
src/lib/release-notes.ts
Normal file
|
|
@ -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.`,
|
||||
)
|
||||
}
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
22
worklog.md
22
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 <WhatsNew /> — 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: <WhatsNew /> 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).
|
||||
|
|
|
|||
Loading…
Reference in a new issue