Refresh docs with current schema and guidance

This commit is contained in:
Jordan Wages 2025-09-24 00:31:03 -05:00
commit 6e438d049d
2 changed files with 219 additions and 243 deletions

335
AGENTS.md
View file

@ -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 projectspecific 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, labelinput 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., 200300 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, AZ 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: labelinput associations, `aria-live` only for async status, focus returned to opener on overlay close.
- Performance: debounce search 200300 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
View file

@ -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.