The page was loading with effectively no Tailwind utility classes
because there was no postcss.config.js — Vite didn't know to run
Tailwind's PostCSS plugin. The preflight and @apply references in
src/index.css were partially working (because some Tailwind processing
was happening implicitly), but the JIT content scanner wasn't running,
so all utility classes (flex, grid, p-*, text-fg, bg-bg, …) were
missing from the build. The page rendered as unstyled HTML.
postcss.config.js — newly added. Tells Vite to run tailwindcss +
autoprefixer. Written in plain UTF-8 (the earlier draft saved as
UTF-16 with BOM, which Node couldn't parse, hence the 'Syntax Error'
in the file viewer).
tailwind.config.js — added a safelist for the dynamic
`stagger-${index}` classes used in ResultCard.tsx. The JIT
content scanner can't resolve a template literal, so without the
safelist the four matching rules (stagger-0 … stagger-4) got purged
from the build even though they're used at runtime. Adding them to
the safelist keeps them alive.
src/index.css — dropped three dead rules (card-hover, btn-primary,
gradient-bg-soft) that the modern UI redesign made unused. They
were in the file but never referenced by any .tsx, so the
content-aware purger was stripping them anyway; explicit removal
keeps the source honest.
The home page header already shows a small equalizer + 'MelodyMuse'
wordmark. Having a second brand row inside the InputPanel was
redundant and pushed the textarea down. Removed it; the form now
starts directly with the 'Music idea' label. Bumped the textarea
rows from 4 to 5 to fill the freed space.
Also added: the server-status check on the home page now re-runs
every 30s, so the 'Server offline' hint in the header stays current
without a manual refresh.
Subtle link in the top-right of the home page header pointing at
https://www.youtube.com/@AIWentNonsense. Lucide Youtube icon plus
'YouTube' label, hidden on small screens. Opens in a new tab with
rel=noopener noreferrer. Sits to the left of the (conditional)
'Server offline' indicator.
Bug fix
- EqualizerIcon: the SVG was rendering at the wrong size (filling
its parent) because it had no explicit width/height attributes.
Now it accepts a size prop with a 24px default and the SVG element
carries the size as width/height attributes, with the className
on top. Can't accidentally balloon.
Design
- Stripped the light-mode CSS — the app is dark-only now, and the
theme toggle was a no-op (clicking it just toggled a class that
had no matching rules). ThemeToggle.tsx and lib/theme.ts are
gone; initTheme() call removed from main.tsx.
- Reworked the input form:
• brand row is now small + refined (icon + wordmark, no huge
gradient title)
• Options is a single subtle collapsible card with a chevron and
a count badge showing how many options are non-default
• Vocals is now a segmented pill (single rounded container, two
flat segments) instead of two full-width grid buttons
• Generate button is slightly smaller (py-3.5, text-sm) and
uses a softer shadow (shadow-violet-900/20)
- Empty state in the results panel: dashed-border card, soft
gradient blur behind the music icon, cleaner copy.
- Results header: smaller heading, the 'Regenerate All' and
'Cancel' buttons are now tiny ghost-style pills in the top-right.
- Sticky ZIP bar: softer shadow, more refined copy.
- Result cards: smaller icon, single-line title, the Regenerate
button is now an inline pill that fits next to Revert + Copy.
- Home page header now has a tiny equalizer icon + 'MelodyMuse'
wordmark on the left (was just text). Settings page header has
the back arrow on the left and the title, no theme toggle.
- Global CSS: added modern focus-visible ring, custom selection
color, subtle scrollbar thumb, slightly tweaked the gradient
text to a softer violet-300 → cyan-300 instead of the loud
violet-400 → fuchsia-400 → cyan-400.
Bug fixes / UX
- Footer: the privacy line still claimed 'generation runs in your
browser'. Now correctly says the generation runs on the server and
the key never reaches the browser.
- History panel: when the panel was open while a new generation
completed, the new entry didn't appear until the user closed and
re-opened the panel. The history module now dispatches a custom
'melodymuse-history-updated' event on every write; the panel
listens and re-reads. removeFromHistory/clearHistory emit too.
writeAll is also now try/catch'd against quota errors.
- Auto-save the input form: the input is persisted to localStorage
('melodymuse-draft', debounced 400ms) so a refresh doesn't wipe
what the user was typing. The initial state hydrates from the
draft on mount.
- Cmd/Ctrl+Enter now works in the Music style field as well as
the idea field — shared onKeyDown handler, propagated through the
StyleField sub-component.
- Full regen ('Generate' / 'Regenerate All') now clears the previous
assets before kicking off the new request, so the skeleton state
shows during the round-trip instead of stale cards with a spinner
on top. Per-section regen still leaves the other cards intact.
- Settings → Local data: the in-progress draft is now listed
alongside history and theme, and has its own 'Clear in-progress
draft' button. The 'Clear all local data' button clears it too.
The Dockerfile, docker-compose snippet, Caddy fragment, and
generate-env.sh now live alongside the rest of the Jannik-Cloud
services — that's the one place that already knows how to manage
them. The MelodyMuse repo is now app-only: source, build config,
the Node server, and docs.
The README's 'Docker / Jannik-Cloud' subsection is replaced with a
short note pointing the reader at the Jannik-Cloud service directory
and the project-layout section no longer lists deploy/.
Architectural change
The browser no longer talks to the LLM directly. A new Node server
(server.mjs) sits in the middle:
[Browser] → [Node :3000] → [LLM provider]
(no key) (key in .env)
server.mjs is the production runtime: it serves the built SPA out of
dist/ and exposes three JSON endpoints that proxy to the LLM with
credentials held in process.env. The browser-side llm.ts is now a thin
fetch wrapper.
- server.mjs: single-file Node server, no production deps
- server/prompts.mjs: system + user prompt construction (was client-side)
- src/lib/prompts.ts removed (moved server-side)
- src/lib/llm.ts rewritten — no more direct LLM calls, no more
JSON extraction, no more validation; just fetch the proxy
- src/lib/types.ts: drop SaveConfigPayload/TestConnectionResult, add
style_hint and ServerStatus
- vite.config.ts: proxy /api/* → localhost:3000 in dev
- .env.example: LLM_* and PORT/CORS_ORIGIN instead of Supabase values
New feature: random style buttons
The Options → Music style field now has two AI buttons that fill it
with a fresh Suno style description:
- 'Surprise me' → coherent, production-ready style (max 25 words)
- 'Go crazy' → deliberately clashing genre mashup (max 25 words)
The buttons hit a dedicated /api/style/random endpoint on the server
that uses a small, focused system prompt. Each click overwrites the
field. Both buttons show a spinner and disable while a request is
in flight. Errors surface as toasts. AbortController is used so a
fast second click cancels the first.
When the Music style field is non-empty at generation time, its value
is sent to the model as style_hint and used as the basis for the full
120-word style field (per the updated system prompt).
Other UX
- Settings page is now a server-status page: green/red indicator,
model + endpoint, re-check button. The API key is no longer
configurable in the browser (it never was reachable anyway — now
the UI is honest about that).
- Home page header shows a small 'Server offline' warning when the
server is unreachable.
- Settings has a Local data section: list what's in localStorage
with one-click clear-history and clear-all buttons (with confirm).
- Esc cancels any in-flight generation.
- ZIP filename falls back to 'song' if the title sanitizes to empty.
Deployment
deploy/ holds reference files (Dockerfile, docker-compose example,
Caddy fragment, generate-env.sh, README) for adding the service to a
Jannik-Cloud-style stack. The repo is intentionally not wired into
the Jannik-Cloud repo; copy the four files when ready.
UX
- Add elapsed-time counter and Cancel button to in-flight generations,
wired through AbortController so partial responses are discarded
cleanly. The cancel action is available from both the input panel
and the results header.
- Add a Recent generations panel below the input. The last 6 successful
generations are saved to localStorage; one click reloads both the
input fields and the generated assets.
- Add a Revert button to every editable card. It appears the moment
the current value diverges from the last generated value and restores
the field with a single click.
- Add an Empty state to the right panel with a friendly hint pointing
at the Generate button and the Settings page.
- Add a 'Try an example' button that fills the input with a random
starter idea (idea + mood + vocals).
- Add Cmd/Ctrl+Enter as a keyboard shortcut to generate.
- Add a Footer with project info and a privacy reminder.
- Add a 'Clear saved key' button to the Settings page so the user can
remove the API key without overwriting it.
Bug fix
- Settings > Test Connection used to save the in-progress form values
to localStorage before testing. It now uses the in-memory candidate
config, so failed tests don't pollute the saved config.
Code quality
- Extract the duplicated useAutoHeight hook to src/lib/useAutoHeight.ts.
- Extract a useElapsed hook for the loading timer.
- Move InputValues into src/lib/types.ts (was duplicated in
InputPanel.tsx) and add SECTION_LABELS, replacing the humanizeSection
switch in HomePage.
- Centralize the filename sanitization: HomePage now calls
sanitizeFilename from zip.ts instead of duplicating the regex.
- Add previewConfig / testConnectionWithConfig helpers to llm.ts to
support in-memory connection tests.
Drop the Supabase backend and call the AI provider directly from the
browser. The endpoint URL, API key, and model name are stored in
localStorage and used for direct /chat/completions requests.
- src/lib/llm.ts: config persistence, direct fetch, JSON extraction,
shape validation, connection test
- src/lib/prompts.ts: full + partial-regeneration system prompts and
user-message builder
- src/lib/{api,supabase}.ts removed
- supabase/ directory removed
- @supabase/supabase-js dropped from package.json
- README updated to describe the standalone architecture and CORS caveats
- .gitignore: drop Supabase entries, exclude *.tsbuildinfo