Refresh docs with current schema and guidance
This commit is contained in:
parent
3374b76838
commit
6e438d049d
2 changed files with 219 additions and 243 deletions
335
AGENTS.md
335
AGENTS.md
|
@ -1,71 +1,49 @@
|
||||||
This repository contains a static web app for searching a large music metadata database (no server/API calls). It uses:
|
# Agent Guide
|
||||||
- Bulma (`bulma.min.css`) for UI (https://bulma.io/documentation/)
|
|
||||||
- A single HTML file (`index.html`)
|
|
||||||
- A single JS file (`script.js`)
|
|
||||||
- A small site stylesheet (`site.css`) for project‑specific tweaks (no frameworks)
|
|
||||||
- A client-side SQLite database (WASM) fetched as a static asset (paged with HTTP Range if available)
|
|
||||||
|
|
||||||
Keep implementation details minimal; this file orients agents to project structure, constraints, and the data model.
|
This document orients automation agents and contributors to the mp3-com-meta-browser project. The app is a static, single-page experience for browsing a large mp3.com metadata dataset entirely in the browser.
|
||||||
|
|
||||||
---
|
## Mission
|
||||||
|
- Deliver a fast, predictable search and browse experience over the bundled metadata database.
|
||||||
## Goals (for agents)
|
- Keep the implementation lightweight: no server-side code, no build system, minimal dependencies.
|
||||||
- Provide a responsive search UI over a large SQLite metadata DB entirely in-browser.
|
- Preserve accessibility, keyboard navigation, and responsive layout from mobile through desktop.
|
||||||
- No server-side code; all queries run client-side.
|
|
||||||
- Fast startup, predictable UX, minimal dependencies.
|
|
||||||
|
|
||||||
## Non-Goals
|
## Non-Goals
|
||||||
- Hosting or streaming audio files.
|
- Hosting or streaming audio files.
|
||||||
- Write access to the DB.
|
- Mutating the SQLite database (it's read-only at runtime).
|
||||||
- Complex build systems or frameworks.
|
- Introducing new frameworks, bundlers, or complex build steps.
|
||||||
|
|
||||||
---
|
## Runtime & Architecture
|
||||||
|
- `index.html` is the only HTML page and contains `<template>` definitions for base views (`tpl-*`) and overlay dialogs.
|
||||||
|
- `script.js` bootstraps the app, loads the WASM SQLite runtime, manages view transitions via `UX.replace(...)`, and handles overlays with `UX.openOverlay(...)` / `UX.closeTop()`.
|
||||||
|
- Bulma (`bulma.min.css`) supplies the UI vocabulary; project-specific tweaks live in `site.css`.
|
||||||
|
- The SQLite database (`assets/mp3com-meta.sqlite`) is fetched lazily using the HTTP virtual file system so only required pages are read.
|
||||||
|
- `sql.js` (bundled `sql-wasm.js` / `sql-wasm.wasm`) provides SQLite + FTS5 inside the browser.
|
||||||
|
|
||||||
## Repository Layout (expected)
|
## Key Assets
|
||||||
|
- `index.html` – SPA shell, templates, base structure.
|
||||||
|
- `script.js` – Application state, data layer, helpers, and view constructors.
|
||||||
|
- `site.css` – Minimal overrides, transitions, and utility classes.
|
||||||
|
- `assets/mp3com-meta.sqlite` – Read-only metadata database.
|
||||||
|
- `bulma.min.css` – Upstream Bulma build; keep pristine.
|
||||||
|
|
||||||
/
|
## Data Access Notes
|
||||||
- index.html # Single-page app shell
|
- Expect to run entirely client-side; the database is opened with read-only mode via `sql.js`.
|
||||||
- bulma.min.css # Bulma CSS (pinned)
|
- Always page results with `LIMIT ? OFFSET ?`; avoid `SELECT *` unless fetching a single record by ID.
|
||||||
- site.css # Site-specific styles (tiny utilities like transitions)
|
- Prefer prepared statements or cached query strings when querying repeatedly.
|
||||||
- script.js # All app logic
|
- HTTP servers used in development or deployment must send `Accept-Ranges: bytes` so the VFS can issue range requests.
|
||||||
- /assets/
|
|
||||||
- mp3com-meta.sqlite # Metadata DB (read-only)
|
|
||||||
- AGENTS.md
|
|
||||||
|
|
||||||
- If subfolders are added later (icons, fonts), prefer `/assets/...`.
|
## Database Schema
|
||||||
- Do not introduce bundlers unless requested.
|
Use the current schema when writing queries or documentation:
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## UI/UX Conventions
|
|
||||||
- Use Bulma components (containers, navbar, form controls, tables, pagination).
|
|
||||||
- Default to semantic HTML + Bulma classes; avoid inline styles.
|
|
||||||
- Place any site-specific CSS (e.g., small transitions like fade utilities) in `site.css`.
|
|
||||||
- Accessibility: ensure focus states, label–input associations, and ARIA for dynamic content.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Data Access (Client-Side)
|
|
||||||
- Use SQLite compiled to WebAssembly in the browser.
|
|
||||||
- Prefer an HTTP VFS (range requests) to avoid fetching the entire DB up front.
|
|
||||||
- Queries should be read-only; no schema migrations/run-time writes.
|
|
||||||
|
|
||||||
Minimal runtime expectations
|
|
||||||
- One network fetch for the WASM runtime.
|
|
||||||
- The DB file (`/assets/mp3com-meta.sqlite`) fetched lazily by page(s) as needed.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Database Schema (authoritative)
|
|
||||||
|
|
||||||
|
```sql
|
||||||
PRAGMA foreign_keys=ON;
|
PRAGMA foreign_keys=ON;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS artists(
|
CREATE TABLE artists(
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
name TEXT NOT NULL COLLATE NOCASE UNIQUE
|
name TEXT NOT NULL COLLATE NOCASE UNIQUE
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS albums(
|
CREATE TABLE albums(
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
artist_id INTEGER NOT NULL REFERENCES artists(id),
|
artist_id INTEGER NOT NULL REFERENCES artists(id),
|
||||||
title TEXT COLLATE NOCASE,
|
title TEXT COLLATE NOCASE,
|
||||||
|
@ -73,7 +51,7 @@ CREATE TABLE IF NOT EXISTS albums(
|
||||||
UNIQUE(artist_id, title, year)
|
UNIQUE(artist_id, title, year)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS tracks(
|
CREATE TABLE tracks(
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
artist_id INTEGER NOT NULL REFERENCES artists(id),
|
artist_id INTEGER NOT NULL REFERENCES artists(id),
|
||||||
album_id INTEGER REFERENCES albums(id),
|
album_id INTEGER REFERENCES albums(id),
|
||||||
|
@ -86,204 +64,81 @@ CREATE TABLE IF NOT EXISTS tracks(
|
||||||
samplerate_hz INTEGER,
|
samplerate_hz INTEGER,
|
||||||
channels INTEGER,
|
channels INTEGER,
|
||||||
filesize_bytes INTEGER,
|
filesize_bytes INTEGER,
|
||||||
sha1 TEXT, -- optional; may be NULL
|
sha1 TEXT,
|
||||||
relpath TEXT NOT NULL
|
relpath TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
-- External-content FTS5 (search over key text fields)
|
CREATE TABLE site_stats (
|
||||||
CREATE VIRTUAL TABLE IF NOT EXISTS fts_tracks USING fts5(
|
name TEXT PRIMARY KEY,
|
||||||
title, artist, album, genre,
|
value TEXT NOT NULL
|
||||||
content='tracks', content_rowid='id',
|
);
|
||||||
tokenize = 'unicode61 remove_diacritics 2',
|
|
||||||
|
CREATE VIRTUAL TABLE fts_tracks USING fts5(
|
||||||
|
title,
|
||||||
|
artist,
|
||||||
|
album,
|
||||||
|
genre,
|
||||||
|
content='tracks',
|
||||||
|
content_rowid='id',
|
||||||
|
tokenize='unicode61 remove_diacritics 2',
|
||||||
prefix='2 3 4'
|
prefix='2 3 4'
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Keep FTS index in sync
|
CREATE TABLE 'fts_tracks_data'(id INTEGER PRIMARY KEY, block BLOB);
|
||||||
CREATE TRIGGER IF NOT EXISTS tracks_ai AFTER INSERT ON tracks BEGIN
|
CREATE TABLE 'fts_tracks_idx'(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWID;
|
||||||
INSERT INTO fts_tracks(rowid,title,artist,album,genre)
|
CREATE TABLE 'fts_tracks_docsize'(id INTEGER PRIMARY KEY, sz BLOB);
|
||||||
VALUES (new.id,
|
CREATE TABLE 'fts_tracks_config'(k PRIMARY KEY, v) WITHOUT ROWID;
|
||||||
new.title,
|
|
||||||
(SELECT name FROM artists WHERE id=new.artist_id),
|
|
||||||
(SELECT title FROM albums WHERE id=new.album_id),
|
|
||||||
new.genre);
|
|
||||||
END;
|
|
||||||
|
|
||||||
CREATE TRIGGER IF NOT EXISTS tracks_ad AFTER DELETE ON tracks BEGIN
|
CREATE INDEX idx_tracks_artist_id ON tracks(artist_id);
|
||||||
INSERT INTO fts_tracks(fts_tracks, rowid) VALUES('delete', old.id);
|
CREATE INDEX idx_tracks_album_id ON tracks(album_id);
|
||||||
END;
|
CREATE INDEX idx_tracks_year ON tracks(year);
|
||||||
|
CREATE INDEX idx_tracks_genre ON tracks(genre);
|
||||||
|
CREATE INDEX idx_artists_name ON artists(name COLLATE NOCASE);
|
||||||
|
CREATE INDEX idx_artists_name_nocase ON artists(name COLLATE NOCASE);
|
||||||
|
```
|
||||||
|
|
||||||
CREATE TRIGGER IF NOT EXISTS tracks_au AFTER UPDATE ON tracks BEGIN
|
`site_stats` stores pre-computed metrics that power the stats dashboard. The FTS data tables are SQLite internals; do not query them directly.
|
||||||
INSERT INTO fts_tracks(fts_tracks, rowid) VALUES('delete', old.id);
|
|
||||||
INSERT INTO fts_tracks(rowid,title,artist,album,genre)
|
|
||||||
VALUES (new.id,
|
|
||||||
new.title,
|
|
||||||
(SELECT name FROM artists WHERE id=new.artist_id),
|
|
||||||
(SELECT title FROM albums WHERE id=new.album_id),
|
|
||||||
new.genre);
|
|
||||||
END;
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_tracks_artist_id ON tracks(artist_id);
|
## UI/UX Conventions
|
||||||
CREATE INDEX IF NOT EXISTS idx_tracks_album_id ON tracks(album_id);
|
- Stick to semantic HTML with Bulma classes; avoid inline styles.
|
||||||
CREATE INDEX IF NOT EXISTS idx_tracks_year ON tracks(year);
|
- Maintain clear focus states and keyboard navigation. `/` focuses search, `Esc` closes the active overlay, and list views support arrow + Enter activation.
|
||||||
|
- Use polite `aria-live` regions for async loading states (see `createAsyncListState`).
|
||||||
|
- Keep DOM renders incremental—do not preload entire datasets or render long tables without pagination.
|
||||||
|
|
||||||
Notes
|
## Shared Utilities (already implemented in `script.js`)
|
||||||
- Text search should use `fts_tracks MATCH ?` and join back to `tracks` for details.
|
- `createTableRenderer` – Safely renders tabular data with built-in HTML escaping.
|
||||||
- Sorting for display can use `ORDER BY rank` (FTS) or `artist,title,year`.
|
- `createPagination` – Standard pagination controls with page size selector.
|
||||||
|
- `Keyboard` helper – Normalizes Enter/Esc handling for list rows and overlays.
|
||||||
|
- `createAsyncListState` – Manages loading, error, and empty states for async lists.
|
||||||
|
|
||||||
---
|
## View Status
|
||||||
|
- [x] Navigation hub (`tpl-nav`, `createNavView`).
|
||||||
|
- [x] Search (`tpl-search`).
|
||||||
|
- [x] Browse Artists (`tpl-browse-artists`).
|
||||||
|
- [x] Browse Albums (`tpl-browse-albums`).
|
||||||
|
- [x] Browse Years (`tpl-browse-years`).
|
||||||
|
- [x] Browse Genres (`tpl-browse-genres`).
|
||||||
|
- [x] Stats (`tpl-stats`).
|
||||||
|
- [x] Artist overlay (`tpl-artist`).
|
||||||
|
- [x] Album overlay (`tpl-album`).
|
||||||
|
- [x] Track overlay (`tpl-track`).
|
||||||
|
- [ ] Filters overlay (`tpl-filters`).
|
||||||
|
- [ ] Keyboard shortcuts overlay (`tpl-keyboard-shortcuts`).
|
||||||
|
- [ ] About / help overlay (`tpl-about`).
|
||||||
|
- [ ] Error overlay (`tpl-error`).
|
||||||
|
|
||||||
## Example Queries (for agents)
|
## Implementation Guardrails
|
||||||
|
- Keep JavaScript modular inside `script.js`; annotate non-obvious sections with short explanatory comments when necessary.
|
||||||
|
- Reuse existing helpers before adding new ones.
|
||||||
|
- Store new assets under `assets/` and prefer lazy loading.
|
||||||
|
- Do not modify `bulma.min.css`; override in `site.css` if required.
|
||||||
|
- Maintain ASCII-only sources unless a file already contains UTF-8 text that requires preservation.
|
||||||
|
|
||||||
Free text:
|
## QA Checklist Before Delivery
|
||||||
|
- Serve the site via a local static server and load `http://localhost:8000/`.
|
||||||
|
- Confirm the console is clean (no errors or unhandled promise rejections).
|
||||||
|
- Verify keyboard navigation (Tab, arrow keys, Enter, Esc) across base views and overlays.
|
||||||
|
- Exercise search and pagination to ensure queries remain performant (< ~300 ms after warm-up).
|
||||||
|
- Validate layout on both narrow and wide viewports.
|
||||||
|
|
||||||
SELECT t.id, a.name AS artist, t.title, IFNULL(al.title,'') AS album, t.year, t.genre
|
Stay within these constraints to keep the project fast, accessible, and easy to host.
|
||||||
FROM fts_tracks f
|
|
||||||
JOIN tracks t ON t.id=f.rowid
|
|
||||||
JOIN artists a ON a.id=t.artist_id
|
|
||||||
LEFT JOIN albums al ON al.id=t.album_id
|
|
||||||
WHERE f MATCH ? -- e.g., 'queen "bohemian rhapsody"'
|
|
||||||
ORDER BY rank LIMIT 50;
|
|
||||||
|
|
||||||
By artist prefix (fast via FTS `prefix`):
|
|
||||||
|
|
||||||
WHERE f MATCH 'artist:beatl*'
|
|
||||||
|
|
||||||
Count by year:
|
|
||||||
|
|
||||||
SELECT year, COUNT(*) FROM tracks GROUP BY year ORDER BY year;
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Local Development
|
|
||||||
|
|
||||||
- Serve statically (to enable range requests and avoid file:// issues):
|
|
||||||
- Python: `python3 -m http.server 8000`
|
|
||||||
- Node: `npx http-server -p 8000`
|
|
||||||
- Open `http://localhost:8000/`
|
|
||||||
- Ensure the server sends `Accept-Ranges: bytes` for `/assets/mp3com-meta.sqlite`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Performance Guidance
|
|
||||||
|
|
||||||
- Keep initial DOM minimal; render results incrementally (virtualized list if needed).
|
|
||||||
- Debounce search input (e.g., 200–300 ms).
|
|
||||||
- Use `LIMIT`/pagination; avoid `SELECT *` on large result sets.
|
|
||||||
- Cache prepared statements in JS if the WASM wrapper allows.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quality Bar
|
|
||||||
|
|
||||||
- No console errors.
|
|
||||||
- Basic keyboard navigation works.
|
|
||||||
- Layout adapts from mobile → desktop via Bulma columns.
|
|
||||||
- Reasonable query latency for common searches (<300ms after warm-up).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Security & Privacy
|
|
||||||
|
|
||||||
- No third-party trackers.
|
|
||||||
- Only static file loads; no credentials.
|
|
||||||
- If adding analytics, prefer privacy-preserving, self-hosted options and document them.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Agent Etiquette
|
|
||||||
|
|
||||||
- Do not introduce new build steps or frameworks unless explicitly asked.
|
|
||||||
- Keep diffs small and focused.
|
|
||||||
- When editing `index.html`/`script.js`, include inline comments explaining assumptions.
|
|
||||||
- Verify changes against the schema above.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## TODOs / UX Elements Roadmap
|
|
||||||
|
|
||||||
Overview
|
|
||||||
- Views are "base" (replace main content) or "overlay" (stacked dialog) via `UX.replace(...)` and `UX.openOverlay(...)` in `script.js`.
|
|
||||||
- Each view pairs an HTML template in `index.html` (e.g., `tpl-...`) with a creator in `script.js` (e.g., `create...View(db)`), returning `{ kind, el, onShow?, destroy? }`.
|
|
||||||
- Use Bulma form/table/pagination patterns. Keep DOM small; paginate and debounce queries.
|
|
||||||
|
|
||||||
Shared Tasks
|
|
||||||
- [x] Add small table/list renderer util in `script.js` to build rows safely (uses `escapeHtml`). Implemented via `createTableRenderer`.
|
|
||||||
- [x] Add shared pagination component (Prev/Next, page size select). Propagate `LIMIT/OFFSET`. Implemented via `createPagination`.
|
|
||||||
- [x] Add common keyboard handlers: Enter to open selection; Esc to close overlays (already wired globally). Implemented via shared `Keyboard` helper in `script.js`.
|
|
||||||
- [x] Add loading/empty-state helpers for lists. Implemented via `createAsyncListState` utility.
|
|
||||||
|
|
||||||
Primary Navigation (Hub)
|
|
||||||
- [x] `tpl-nav` (base): Landing hub to choose "Search", "Browse Artists", "Browse Albums", "Browse Years", "Browse Genres", "Stats".
|
|
||||||
- [x] `createNavView(db)`: Buttons/cards trigger `UX.replace(...)` to corresponding base views.
|
|
||||||
- [x] Accessibility: initial focus on first action; arrow-key navigation across items; visible focus states.
|
|
||||||
|
|
||||||
Search (Existing)
|
|
||||||
- [x] `tpl-search` (base): Input is focusable; shows results area.
|
|
||||||
- [x] Implement query execution with FTS join; debounce 250 ms; paginate results. Wired into the new table + pagination helpers.
|
|
||||||
- SQL: `SELECT t.id, a.name AS artist, t.title, IFNULL(al.title,'') AS album, t.year, t.genre FROM fts_tracks f JOIN tracks t ON t.id=f.rowid JOIN artists a ON a.id=t.artist_id LEFT JOIN albums al ON al.id=t.album_id WHERE f MATCH ? ORDER BY rank LIMIT ? OFFSET ?`.
|
|
||||||
- [x] Column sorts (toggle rank vs artist,title,year).
|
|
||||||
- [x] Row activation opens Track overlay.
|
|
||||||
|
|
||||||
Browse Artists
|
|
||||||
- [x] `tpl-browse-artists` (base): Alphabetical list, A–Z quick jump, mini filter box; paginated.
|
|
||||||
- [x] `createBrowseArtistsView(db)`: Loads pages; clicking row opens Artist overlay.
|
|
||||||
- SQL: `SELECT id, name FROM artists ORDER BY name LIMIT ? OFFSET ?`.
|
|
||||||
- [x] Optional prefix search using FTS prefix (`WHERE f MATCH 'artist:abc*'`) to accelerate filter. Browse Artists view now favors FTS when filters include ≥2 characters.
|
|
||||||
|
|
||||||
Browse Albums
|
|
||||||
- [x] `tpl-browse-albums` (base): List or grid with title, artist badge, year; paginated and sortable by artist/year/title.
|
|
||||||
- [x] `createBrowseAlbumsView(db)`: Clicking item opens Album overlay.
|
|
||||||
- SQL: `SELECT al.id, al.title, al.year, a.name AS artist FROM albums al JOIN artists a ON a.id=al.artist_id ORDER BY a.name, al.year, al.title LIMIT ? OFFSET ?`.
|
|
||||||
|
|
||||||
Browse Years
|
|
||||||
- [x] `tpl-browse-years` (base): Year histogram (counts) with list of tracks/albums when a year is selected; paginated.
|
|
||||||
- [x] `createBrowseYearsView(db)`: Selecting a year shows tracks; rows open Album/Track overlays.
|
|
||||||
- SQL (counts): `SELECT year, COUNT(*) AS cnt FROM tracks WHERE year IS NOT NULL GROUP BY year ORDER BY year`.
|
|
||||||
- SQL (tracks by year): `SELECT t.id, t.title, a.name AS artist, IFNULL(al.title,'') AS album, t.genre FROM tracks t JOIN artists a ON a.id=t.artist_id LEFT JOIN albums al ON al.id=t.album_id WHERE t.year=? ORDER BY a.name, t.title LIMIT ? OFFSET ?`.
|
|
||||||
|
|
||||||
Browse Genres
|
|
||||||
- [x] `tpl-browse-genres` (base): Genre chips with counts → selecting shows paginated tracks.
|
|
||||||
- [x] `createBrowseGenresView(db)`: Genre list with counts; selecting lists tracks; rows open Track overlay.
|
|
||||||
- SQL (counts): `SELECT genre, COUNT(*) AS cnt FROM tracks WHERE genre IS NOT NULL AND genre!='' GROUP BY genre ORDER BY cnt DESC, genre`.
|
|
||||||
- SQL (tracks by genre): `SELECT t.id, t.title, a.name AS artist, IFNULL(al.title,'') AS album, t.year FROM tracks t JOIN artists a ON a.id=t.artist_id LEFT JOIN albums al ON al.id=t.album_id WHERE t.genre=? ORDER BY a.name, t.title LIMIT ? OFFSET ?`.
|
|
||||||
|
|
||||||
Stats
|
|
||||||
- [x] `tpl-stats` (base): Lightweight metrics (totals, top artists, year distribution) linking into browse views.
|
|
||||||
- [x] `createStatsView(db)`: Render summary cards; links navigate via `UX.replace(...)` with preselected filters.
|
|
||||||
- SQL (examples): totals from `COUNT(*)` on artists/albums/tracks; top artists via `SELECT a.name, COUNT(*) cnt FROM tracks t JOIN artists a ON a.id=t.artist_id GROUP BY a.id ORDER BY cnt DESC LIMIT 20`.
|
|
||||||
|
|
||||||
Artist Overlay
|
|
||||||
- [x] `tpl-artist` (overlay): Header: name + counts; tabs: Albums | Top Tracks.
|
|
||||||
- [x] `createArtistOverlay(db, artistId)`: Load artist name, counts, then tab content.
|
|
||||||
- SQL (albums): `SELECT id, title, year FROM albums WHERE artist_id=? ORDER BY year, title`.
|
|
||||||
- SQL (top tracks): `SELECT id, title, year, genre FROM tracks WHERE artist_id=? ORDER BY year, title LIMIT 100`.
|
|
||||||
- [x] Actions: clicking album opens Album overlay; clicking track opens Track overlay.
|
|
||||||
|
|
||||||
Album Overlay
|
|
||||||
- [x] `tpl-album` (overlay): Header with album title, artist, year; tracklist table with `track_no`, `title`, `duration_sec`, `bitrate_kbps`.
|
|
||||||
- [x] `createAlbumOverlay(db, albumId)`: Load album+artist header; then tracklist.
|
|
||||||
- SQL (header): `SELECT al.title, al.year, a.name AS artist FROM albums al JOIN artists a ON a.id=al.artist_id WHERE al.id=?`.
|
|
||||||
- SQL (tracks): `SELECT id, track_no, title, duration_sec, bitrate_kbps FROM tracks WHERE album_id=? ORDER BY track_no, title`.
|
|
||||||
- [x] Row activation opens Track overlay.
|
|
||||||
|
|
||||||
Track Overlay
|
|
||||||
- [x] `tpl-track` (overlay): Show title, artist, album, year, genre, duration, bitrate, samplerate, channels, filesize, sha1 (if present), and `relpath` with a Copy button.
|
|
||||||
- [x] `createTrackOverlay(db, trackId)`: Load detail from join; add Copy action for `relpath`.
|
|
||||||
- SQL: `SELECT t.*, a.name AS artist, al.title AS album FROM tracks t JOIN artists a ON a.id=t.artist_id LEFT JOIN albums al ON al.id=t.album_id WHERE t.id=?`.
|
|
||||||
|
|
||||||
Filters (Optional)
|
|
||||||
- [ ] `tpl-filters` (overlay): Advanced filters (year range, min bitrate, genre multi-select) applied to current base view.
|
|
||||||
- [ ] `createFiltersOverlay(db, onApply)`: Applies constraints and refreshes the invoking view.
|
|
||||||
|
|
||||||
Help/Meta Overlays
|
|
||||||
- [ ] `tpl-keyboard-shortcuts` (overlay): " / focus search", "Esc close overlay", "j/k navigate" if list navigation is added.
|
|
||||||
- [ ] `tpl-about` (overlay): About/help, privacy note.
|
|
||||||
- [ ] `tpl-error` (overlay): Friendly error with retry; used by views on failure.
|
|
||||||
|
|
||||||
Implementation Notes
|
|
||||||
- Use template IDs in `index.html` and instantiate via existing `instantiateTemplate` helper.
|
|
||||||
- Overlays must add `ux-view--overlay` class (done by UX manager) and include a close button that calls `UX.closeTop()`.
|
|
||||||
- Keep queries read-only; always `LIMIT ? OFFSET ?` for lists; avoid `SELECT *` except for single-row detail.
|
|
||||||
- Respect accessibility: label–input associations, `aria-live` only for async status, focus returned to opener on overlay close.
|
|
||||||
- Performance: debounce search 200–300 ms; cache prepared statements if beneficial; do not pre-render large lists.
|
|
||||||
- UX manager now guards against race conditions during fades: `UX.replace` updates `currentBase` before the fade-in completes and checks that the previous element is still connected before trying to animate it out.
|
|
||||||
|
|
127
README.md
127
README.md
|
@ -1,7 +1,128 @@
|
||||||
# mp3-com-meta-browser
|
# mp3-com-meta-browser
|
||||||
|
|
||||||
A self contained static website for browsing the collection of salvaged mp3.com files.
|
mp3-com-meta-browser is a static single-page web app for exploring the salvaged mp3.com metadata catalog. All queries run entirely in the browser against a read-only SQLite database compiled to WebAssembly, so the site can be hosted on any static file server.
|
||||||
|
|
||||||
## Runtime Notes
|
## Highlights
|
||||||
|
- Client-side full-text search (FTS5) across track titles, artists, albums, and genres.
|
||||||
|
- Responsive Bulma-based UI with dedicated views for search, browse (artists, albums, years, genres), and dataset stats.
|
||||||
|
- Overlay detail panels for artists, albums, and tracks with keyboard navigation support.
|
||||||
|
- Zero server dependencies beyond serving static assets with HTTP range support.
|
||||||
|
|
||||||
- Uses the `sql.js` WASM build (bundled as `sql-wasm.js`/`sql-wasm.wasm`) so that FTS5 is available client-side.
|
## Tech Stack
|
||||||
|
- Bulma (`bulma.min.css`) for layout and components.
|
||||||
|
- Vanilla JavaScript in `script.js` for SPA state management, view controllers, and data access helpers.
|
||||||
|
- `sql.js` WASM build (`sql-wasm.js` / `sql-wasm.wasm`) to run SQLite with FTS5 support in the browser.
|
||||||
|
- Local styling refinements in `site.css`.
|
||||||
|
- Metadata database delivered as `/assets/mp3com-meta.sqlite`.
|
||||||
|
|
||||||
|
## Directory Layout
|
||||||
|
```
|
||||||
|
/
|
||||||
|
├─ index.html # Single-page app shell and HTML templates
|
||||||
|
├─ bulma.min.css # Bundled Bulma CSS (do not modify upstream file)
|
||||||
|
├─ site.css # Local overrides and micro-utilities
|
||||||
|
├─ script.js # Application logic, helpers, and view controllers
|
||||||
|
├─ README.md # Project overview (this file)
|
||||||
|
├─ AGENTS.md # Orientation and guardrails for automation agents
|
||||||
|
└─ assets/
|
||||||
|
└─ mp3com-meta.sqlite # Read-only metadata database
|
||||||
|
```
|
||||||
|
Add future static assets (icons, fonts, etc.) under `assets/`.
|
||||||
|
|
||||||
|
## Running Locally
|
||||||
|
1. Start a static file server from the repository root. Examples:
|
||||||
|
- `python3 -m http.server 8000`
|
||||||
|
- `npx http-server -p 8000`
|
||||||
|
2. Ensure the server sends `Accept-Ranges: bytes` for `/assets/mp3com-meta.sqlite` so the WASM virtual file system can page data on demand.
|
||||||
|
3. Visit `http://localhost:8000/` in a modern browser.
|
||||||
|
4. Open the developer console to confirm there are no runtime errors.
|
||||||
|
|
||||||
|
## Using the App
|
||||||
|
- **Search:** Free-text search with 250 ms debounce. Toggle between relevance and alphabetical sorting. Keyboard: `/` focuses search, `Enter` activates the highlighted row.
|
||||||
|
- **Browse Artists / Albums / Years / Genres:** Paginated listings with quick filters (artists leverage FTS prefix queries for fast prefix matching). Selecting an entry opens the corresponding overlay.
|
||||||
|
- **Stats:** Summary cards pull from pre-computed counters in `site_stats` and link into browse views with preset filters.
|
||||||
|
- **Overlays:** Artist, album, and track overlays show detailed metadata. `Esc` closes the top overlay and focus returns to the invoker.
|
||||||
|
|
||||||
|
## Keyboard & Accessibility Notes
|
||||||
|
- Global shortcuts: `/` for search, `Esc` to close overlays.
|
||||||
|
- Lists expose arrow navigation with `Enter` activation when focused.
|
||||||
|
- Focus indicators remain visible; async list regions use polite `aria-live` messages.
|
||||||
|
- Ensure new UI follows Bulma semantics and pairs labels with inputs.
|
||||||
|
|
||||||
|
## Database Schema
|
||||||
|
The bundled database enforces foreign keys and ships with an FTS5 index over key text fields. The current schema is:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
|
|
||||||
|
CREATE TABLE artists(
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL COLLATE NOCASE UNIQUE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE albums(
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
artist_id INTEGER NOT NULL REFERENCES artists(id),
|
||||||
|
title TEXT COLLATE NOCASE,
|
||||||
|
year INTEGER,
|
||||||
|
UNIQUE(artist_id, title, year)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE tracks(
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
artist_id INTEGER NOT NULL REFERENCES artists(id),
|
||||||
|
album_id INTEGER REFERENCES albums(id),
|
||||||
|
title TEXT NOT NULL COLLATE NOCASE,
|
||||||
|
track_no INTEGER,
|
||||||
|
year INTEGER,
|
||||||
|
genre TEXT,
|
||||||
|
duration_sec INTEGER,
|
||||||
|
bitrate_kbps INTEGER,
|
||||||
|
samplerate_hz INTEGER,
|
||||||
|
channels INTEGER,
|
||||||
|
filesize_bytes INTEGER,
|
||||||
|
sha1 TEXT,
|
||||||
|
relpath TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE site_stats (
|
||||||
|
name TEXT PRIMARY KEY,
|
||||||
|
value TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE VIRTUAL TABLE fts_tracks USING fts5(
|
||||||
|
title,
|
||||||
|
artist,
|
||||||
|
album,
|
||||||
|
genre,
|
||||||
|
content='tracks',
|
||||||
|
content_rowid='id',
|
||||||
|
tokenize='unicode61 remove_diacritics 2',
|
||||||
|
prefix='2 3 4'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE 'fts_tracks_data'(id INTEGER PRIMARY KEY, block BLOB);
|
||||||
|
CREATE TABLE 'fts_tracks_idx'(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWID;
|
||||||
|
CREATE TABLE 'fts_tracks_docsize'(id INTEGER PRIMARY KEY, sz BLOB);
|
||||||
|
CREATE TABLE 'fts_tracks_config'(k PRIMARY KEY, v) WITHOUT ROWID;
|
||||||
|
|
||||||
|
CREATE INDEX idx_tracks_artist_id ON tracks(artist_id);
|
||||||
|
CREATE INDEX idx_tracks_album_id ON tracks(album_id);
|
||||||
|
CREATE INDEX idx_tracks_year ON tracks(year);
|
||||||
|
CREATE INDEX idx_tracks_genre ON tracks(genre);
|
||||||
|
CREATE INDEX idx_artists_name ON artists(name COLLATE NOCASE);
|
||||||
|
CREATE INDEX idx_artists_name_nocase ON artists(name COLLATE NOCASE);
|
||||||
|
```
|
||||||
|
|
||||||
|
The `fts_tracks_*` tables are managed by SQLite to back the FTS5 virtual table. `site_stats` stores pre-computed counters that power the stats view. The application issues read-only queries and never mutates the database.
|
||||||
|
|
||||||
|
## Development Notes
|
||||||
|
- Keep the app framework-free; do not introduce bundlers or additional dependencies without discussion.
|
||||||
|
- Reuse HTML templates in `index.html` through the `instantiateTemplate` helper instead of building large DOM fragments manually.
|
||||||
|
- Paginate every query (`LIMIT ? OFFSET ?`) and prefer streaming or incremental rendering for large result sets.
|
||||||
|
- Consolidate shared UI logic (pagination, keyboard handlers, async states) in `script.js` utilities.
|
||||||
|
- Restrict new CSS to scoped utility classes in `site.css`; otherwise rely on Bulma components.
|
||||||
|
- Before submitting changes, verify there are no console warnings, layout adapts from mobile to desktop, and keyboard navigation continues to work.
|
||||||
|
|
||||||
|
## License
|
||||||
|
Add licensing information here when available.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue