diff --git a/db/custom.db.seed b/db/custom.db.seed index 0bfb762..922425b 100755 Binary files a/db/custom.db.seed and b/db/custom.db.seed differ diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a1d82af..577d22c 100755 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -8,7 +8,7 @@ datasource db { } // A preset script file living somewhere in the folder tree. -// folderPath is the full path from the virtual root, e.g. "root/embedded/Synth-C". +// folderPath is the full path from the virtual root, e.g. "root/embedded". // Names are unique per folder (files in a directory). // approx = true marks embedded presets that are generated stand-ins rather // than ports of the original's code (the original relies on effects the @@ -45,7 +45,7 @@ model Settings { bandConfig String? beatConfig String? // Version stamp of the embedded preset library; when it changes, the - // root/embedded subtree is re-seeded from BUILTIN + EMBEDDED_LIBRARY. + // root/embedded subtree is re-seeded from EMBEDDED_LIBRARY. embeddedSeed String? updatedAt DateTime @updatedAt } diff --git a/src/app/api/presets/route.ts b/src/app/api/presets/route.ts index f6e9e24..ab5f1bf 100755 --- a/src/app/api/presets/route.ts +++ b/src/app/api/presets/route.ts @@ -1,23 +1,17 @@ import { NextRequest, NextResponse } from 'next/server' import { db } from '@/lib/db' -import { BUILTIN_PRESETS } from '@/lib/ava/presets' import { EMBEDDED_LIBRARY, EMBEDDED_SEED_VERSION } from '@/lib/ava/embedded-presets' import { ensureFolderRows } from '@/lib/server/preset-tree' import { EMBEDDED_ROOT, ROOT, USER_ROOT } from '@/lib/preset-paths' export const dynamic = 'force-dynamic' -/** The structural backbone of the tree, created on first run. */ -const BASE_FOLDERS = [ - ROOT, - EMBEDDED_ROOT, - USER_ROOT, - 'root/embedded/Synth-C', - 'root/embedded/Amp Picks', - 'root/embedded/Dynamics', - 'root/embedded/Envision', - 'root/embedded/Zai-Magic', -] +/** + * The structural backbone of the tree, created on first run. The embedded + * branch has no subfolders (v7.19.0 curation) — "Beat Matrix" lives directly + * under root/embedded. + */ +const BASE_FOLDERS = [ROOT, EMBEDDED_ROOT, USER_ROOT] /** * Version-gated seed + INTEGRITY SELF-HEAL for the embedded library. @@ -26,18 +20,13 @@ const BASE_FOLDERS = [ * sees, so GET verifies it on every request: * 1. version gate — EMBEDDED_SEED_VERSION changes trigger a full re-seed * (library was regenerated); - * 2. fingerprint — the embedded subtree must exactly match - * BUILTIN + EMBEDDED (folder + name). Any drift — a preset renamed or - * deleted through pre-hardening endpoints, a row injected straight into - * the DB — re-seeds the subtree automatically. + * 2. fingerprint — the embedded subtree must exactly match EMBEDDED + * (folder + name). Any drift — a preset renamed or deleted through + * pre-hardening endpoints, a row injected straight into the DB — + * re-seeds the subtree automatically. */ function libraryFingerprint(): string { - const items = [ - ...BUILTIN_PRESETS.map((p) => `root/embedded/Synth-C/${p.name}`), - ...EMBEDDED_LIBRARY.map( - (p) => `${p.folder.replace('root/embedded/root/embedded/', 'root/embedded/')}/${p.name}`, - ), - ] + const items = EMBEDDED_LIBRARY.map((p) => `${p.folder}/${p.name}`) return items.sort().join('|') } @@ -72,30 +61,20 @@ async function ensureSeeded(): Promise { ], }, }) + // the curated library has NO subfolders — drop any stale category + // folder rows (Synth-C / Amp Picks / Dynamics / Envision / Zai-Magic) + await db.folder.deleteMany({ where: { path: { startsWith: 'root/embedded/' } } }) await db.preset.createMany({ - data: [ - ...BUILTIN_PRESETS.map((p) => ({ - name: p.name, - author: p.author, - description: p.description, - content: p.content, - originalFile: p.originalFile ?? null, - isBuiltIn: true, - approx: false, - folderPath: 'root/embedded/Synth-C', - })), - ...EMBEDDED_LIBRARY.map((p) => ({ - name: p.name, - author: p.author, - description: p.description, - content: p.content, - originalFile: p.originalFile ?? null, - isBuiltIn: true, - approx: p.approx ?? false, - // tolerate old converter output that recorded the download nesting - folderPath: p.folder.replace('root/embedded/root/embedded/', 'root/embedded/'), - })), - ], + data: EMBEDDED_LIBRARY.map((p) => ({ + name: p.name, + author: p.author, + description: p.description, + content: p.content, + originalFile: p.originalFile ?? null, + isBuiltIn: true, + approx: p.approx ?? false, + folderPath: p.folder, + })), }) await db.settings.upsert({ where: { id: 'main' }, @@ -114,6 +93,12 @@ async function ensureSeeded(): Promise { * the embedded library, folders outside the structural backbone — is purged * here so no visitor can ever alter what other visitors see. Runs on every * GET; it's two indexed deleteMany calls on a small SQLite table. + * + * v7.19.0: the embedded branch ships NO subfolders, so the folder purge now + * keeps only the three backbone rows and deletes everything else (stale + * category folders included). User folders never exist server-side — the + * "root/user created" tree lives in each visitor's browser — so nothing of + * any visitor's is touched. */ async function purgeVisitorJunk(): Promise { await db.preset.deleteMany({ @@ -126,14 +111,7 @@ async function purgeVisitorJunk(): Promise { }, }) await db.folder.deleteMany({ - where: { - NOT: [ - { path: ROOT }, - { path: EMBEDDED_ROOT }, - { path: USER_ROOT }, - { path: { startsWith: 'root/embedded/' } }, - ], - }, + where: { path: { notIn: [ROOT, EMBEDDED_ROOT, USER_ROOT] } }, }) } diff --git a/src/app/page.tsx b/src/app/page.tsx index 48c9767..67f9e14 100755 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -102,19 +102,17 @@ function useInitialLoad() { const beat = readStoredBeatConfig() if (beat) store.applyBeatConfig(beat) - // startup script: saved default (this browser) → v7.2 default preset - // (Zai-Magic "Bass Bloom" — the megabuf particle bloom that remembers - // every beat's hue) → any Zai-Magic preset → template + // startup script: saved default (this browser) → the embedded default + // preset (v7.19.0: "Beat Matrix" — the 16-band beat-reactive spectrum + // wall, the single preset of the curated embedded library) → template let text: string | null = lsGetString('ava.defaultScript') const usedSavedDefault = text !== null && text.trim() !== '' let startupPreset: (typeof presetsList)[number] | undefined if (!text) { startupPreset = presetsList.find( - (p) => p.folderPath === 'root/embedded/Zai-Magic' && p.name === 'Bass Bloom', - ) ?? - presetsList.find((p) => p.folderPath === 'root/embedded/Zai-Magic') ?? - presetsList[0] + (p) => p.folderPath === 'root/embedded' && p.name === 'Beat Matrix', + ) ?? presetsList[0] text = startupPreset?.content ?? null } if (!text) text = STARTER_TEMPLATE diff --git a/src/components/ava/audio-source.ts b/src/components/ava/audio-source.ts index bbc87cc..93669a8 100755 --- a/src/components/ava/audio-source.ts +++ b/src/components/ava/audio-source.ts @@ -320,17 +320,18 @@ function fileErrorMessage(err: unknown): string { } // --------------------------------------------------------------------------- -// v7.2 showcase defaults — Bass Bloom + the Zai-Magic club mix +// Showcase defaults (v7.19.0) — "Beat Matrix" over the demo track // --------------------------------------------------------------------------- -/** The read-only folder that ships the hand-authored Zai-Magic presets. */ -const ZAI_MAGIC_FOLDER = 'root/embedded/Zai-Magic' +/** The read-only folder of the curated embedded library (one preset: + * "Beat Matrix" — the single shipped preset, directly under root/embedded). */ +const EMBEDDED_FOLDER = 'root/embedded' // --------------------------------------------------------------------------- // v7.11 — the visitor's own selection vs. the welcome showcase // --------------------------------------------------------------------------- -/** The script text this visit booted with (saved default / Bass Bloom / +/** The script text this visit booted with (saved default / Beat Matrix / * template). Any later difference = the visitor's own material. */ let bootScriptSnapshot = '' @@ -366,18 +367,18 @@ export function visitorHasOwnSelection(): boolean { /** * Music just started but no preset is active (the visitor never picked one — * e.g. they are running their own untitled draft): load the default preset, - * Zai-Magic "Bass Bloom", so something beat-reactive is on screen the moment - * audio arrives. Engine-only — the editor's unsaved text is never touched - * (the same courtesy random switching pays). v7.11: a visitor with their OWN - * material (custom draft / saved default) is left completely alone — the - * forced default used to replace their running script. + * the embedded "Beat Matrix", so something beat-reactive is on screen the + * moment audio arrives. Engine-only — the editor's unsaved text is never + * touched (the same courtesy random switching pays). v7.11: a visitor with + * their OWN material (custom draft / saved default) is left completely + * alone — the forced default used to replace their running script. */ function ensureDefaultPreset(): void { const s = useAvaStore.getState() if (s.activePresetId !== null) return if (visitorHasOwnSelection()) return const preset = s.presets.find( - (p) => p.folderPath === ZAI_MAGIC_FOLDER && p.name === 'Bass Bloom', + (p) => p.folderPath === EMBEDDED_FOLDER && p.name === 'Beat Matrix', ) if (!preset) return const v = validateScript(preset.content) @@ -390,30 +391,31 @@ function ensureDefaultPreset(): void { } /** - * The Demo button is the showcase: point the preset tree at Zai-Magic (and - * expand it so the visitor sees what is playing), then start the sequence — - * v7.4: every Zai-Magic preset IN ORDER, one every 20 seconds, wrapping - * around, for the one play-through (Sequence, not Random — the demo pass - * should walk the whole family, not repeat picks). v7.11: only ever called - * for a visit that made no choice of their own (see visitorHasOwnSelection). + * The Demo button is the showcase: point the preset tree at the embedded + * library (and expand it so the visitor sees what is playing). v7.19.0: the + * curated library is a single preset ("Beat Matrix"), so there is nothing + * to cycle — no mix timer is engaged; the track simply plays under the + * beat-reactive wall, and any mix the visitor starts later is scoped to + * the folder by the selection. v7.11: only ever called for a visit that + * made no choice of their own (see visitorHasOwnSelection). */ function startShowcaseMix(): void { const s = useAvaStore.getState() - if (s.folders.includes(ZAI_MAGIC_FOLDER)) { - s.selectFolder(ZAI_MAGIC_FOLDER) // also scopes the mix to the folder - s.toggleFolder(ZAI_MAGIC_FOLDER, true) + if (s.folders.includes(EMBEDDED_FOLDER)) { + s.selectFolder(EMBEDDED_FOLDER) // also scopes any later mix to the folder + s.toggleFolder(EMBEDDED_FOLDER, true) } - s.setRandomIntervalSec(20) - s.setSequenceMode(true) // mutual exclusion unticks Random if it was on } /** * v7.3/v7.4: the demo is a single pass — when it ends (natural play-through, * Stop button, or switching to another source) unwind the showcase state: - * the Demo flow force-checked the mix mode (Sequence), so it gets unchecked - * again. The visitor's own selection (the last preset that was running) - * stays put. v7.11: only called when the showcase actually engaged — a - * visitor whose own Random/Sequence mix was already running keeps it. + * the Demo flow used to force-check the mix mode (Sequence), so it gets + * unchecked again. v7.19.0 no longer engages a timer, but the unwind stays + * as a safety net for any flags that did get set. The visitor's own + * selection (the last preset that was running) stays put. v7.11: only + * called when the showcase actually engaged — a visitor whose own + * Random/Sequence mix was already running keeps it. */ function retireDemoShowcase(): void { const s = useAvaStore.getState() @@ -473,7 +475,7 @@ export function useAudioSource() { const index = readDemoNext() try { await engine.audio.startDemo(index) - // v7.11: the welcome showcase (Bass Bloom + Zai-Magic Sequence) only + // v7.11: the welcome showcase (Beat Matrix over the demo track) only // engages for a visit that made NO choice of their own — no preset // loaded (double-click / Load / a copy), no custom script material in // the editor, no own mix running. Everyone else keeps their selection: @@ -492,17 +494,17 @@ export function useAudioSource() { if (reason === 'ended' || reason === 'stop') writeDemoNext(index + 1) } syncSource() - // showcase flow (v7.2/v7.4/v7.11): nothing active and nothing of the - // visitor's own → Bass Bloom, then the Zai-Magic sequence takes over — - // every preset in order, 20 s each, for this one play-through. With - // the visitor's own selection, BOTH are skipped (ensureDefaultPreset - // holds its own guard as well). + // showcase flow (v7.2/v7.11, reworked v7.19.0): nothing active and + // nothing of the visitor's own → Beat Matrix on screen; the tree points + // at the embedded library for the rest of the pass. With the visitor's + // own selection, the showcase is skipped (ensureDefaultPreset holds its + // own guard as well). ensureDefaultPreset() if (showcase) startShowcaseMix() toast({ title: 'Demo track playing', description: showcase - ? `${demoTrackTitle(index)} (${index + 1} of ${DEMO_TRACK_COUNT}) · Zai-Magic preset sequence -- every preset in order, 20 s each, one play-through.` + ? `${demoTrackTitle(index)} (${index + 1} of ${DEMO_TRACK_COUNT}) · "Beat Matrix" is running -- the built-in beat-reactive wall, straight from the embedded library.` : `${demoTrackTitle(index)} (${index + 1} of ${DEMO_TRACK_COUNT}) · your selection stays active -- the demo never touches your preset or the editor.`, }) } catch (err) { diff --git a/src/components/ava/visual-stage.tsx b/src/components/ava/visual-stage.tsx index d1b575b..8728144 100755 --- a/src/components/ava/visual-stage.tsx +++ b/src/components/ava/visual-stage.tsx @@ -110,8 +110,8 @@ export function VisualStage({ className }: { className?: string }) {
{/* v7.2 showcase entry point -- the shimmering Demo Track - button: one click starts the music AND the Zai-Magic - club mix (random preset every 20 s) */} + button: one click starts the music AND the embedded + "Beat Matrix" showcase (v7.19.0: single-preset library) */}