From 051775337251b0c7036959901eacb58471100862 Mon Sep 17 00:00:00 2001 From: Tyler Hoang Date: Wed, 6 May 2026 14:52:34 -0700 Subject: fixed star behavior and claude init --- AGENTS.md | 3 --- CLAUDE.md | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++ static/app.js | 60 +++++++++++++++++++++++++++-------------------------------- 3 files changed, 78 insertions(+), 36 deletions(-) create mode 100644 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md index bf95f64..a84dcce 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,8 +15,5 @@ Use 4-space indentation and keep Python code straightforward and type-friendly. ## Testing Guidelines There is no committed `tests/` package yet, so add targeted tests alongside any non-trivial feature work. Use `pytest` if you introduce automated tests, with filenames like `test_imports.py` and test names such as `test_watchlist_import_sets_queue_shelf`. For now, every change should at least pass the `py_compile` check and a local smoke test of the affected route in the browser. -## Commit & Pull Request Guidelines -Git history is not readable from this workspace, so use short imperative commit subjects such as `Add queue watchlist import`. Keep commits focused on one behavior change. Pull requests should explain the user-facing change, note any schema or import implications, list verification steps, and include screenshots for template or CSS updates. - ## Security & Configuration Keep secrets in `.env`, especially `TMDB_API_KEY`, and never hardcode credentials in Python, templates, or JavaScript. Treat `lumiere.db` and backup database files as local artifacts unless a task explicitly requires fixture data. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..a646370 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,51 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What This Is +Lumiere is a personal cinema diary — a server-rendered FastAPI web app backed by SQLite. Users track films across three shelves: `diary`, `queue`, and `abandoned`. TMDB enriches film metadata (posters, directors, runtime, countries). + +## Commands + +```bash +# Setup +python -m venv .venv +.venv/bin/pip install -r requirements.txt + +# Run dev server +.venv/bin/uvicorn main:app --reload + +# Syntax check (run before any PR) +.venv/bin/python -m py_compile main.py database.py models.py routers/*.py services/*.py + +# Tests (no suite yet — use pytest when adding tests) +.venv/bin/pytest +``` + +Requires a `.env` file with `TMDB_API_KEY=...`. App runs at `http://127.0.0.1:8000`. + +## Architecture + +**Request flow:** Browser → FastAPI router → SQLAlchemy (SQLite) → Jinja2 template → HTML response. `static/app.js` handles star ratings (JSON PATCH) and TMDB search autocomplete without a build step. + +**Key files:** +- `main.py` — app entry, router registration +- `database.py` — SQLite engine, sessions, self-healing schema migration (no Alembic; renames old table on column mismatch and migrates data) +- `models.py` — single `Film` ORM model with a `shelf` column +- `routers/films.py` — shelf listing, CRUD, director page, queue random pick, star rating API +- `routers/imports.py` — Letterboxd and watchlist CSV import, deduplication, enrichment +- `routers/stats.py` — all-time stats and year-in-review (pure Python aggregation, no SQL GROUP BY) +- `routers/tmdb.py` — `/tmdb/search?q=` proxy for JS autocomplete +- `services/tmdb.py` — TMDB API client: search, detail fetch, metadata application +- `services/film_people.py` — director name normalization and URL slug helpers + +**Patterns:** +- Route logic stays in `routers/`, shared service logic in `services/` +- Stats computed in-memory from full query results (appropriate for personal-scale SQLite) +- TMDB fields are additive/optional; all core film fields work without enrichment +- Import pipeline dedupes by TMDB ID first, then normalized title+year+date + +## Coding Conventions +- 4-space indentation; `snake_case` for functions/variables, `PascalCase` for ORM models, uppercase for shelf/status constants +- Jinja templates stay minimal; defer behavior to `static/app.js` +- `lumiere.db` and `*.db.bak-*` files are local artifacts — do not drive code changes from them diff --git a/static/app.js b/static/app.js index e2213f3..b942d7e 100644 --- a/static/app.js +++ b/static/app.js @@ -161,39 +161,6 @@ document.querySelectorAll("form[data-confirm]").forEach((form) => { }); }); -document.addEventListener("click", async (event) => { - const button = event.target.closest(".star-button"); - if (!button) return; - - const control = button.closest(".star-control"); - if (!control || !control.dataset.filmId) return; - - const stars = Number(button.dataset.stars || 0); - const filmId = control.dataset.filmId; - - button.disabled = true; - try { - const response = await fetch(`/films/${filmId}/stars`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ stars }), - }); - - const data = await response.json(); - if (!response.ok) { - return; - } - - syncStarControl(control, Number(data.stars || 0)); - } catch (error) { - console.error("Failed to update stars", error); - } finally { - button.disabled = false; - } -}); - document.querySelectorAll(".star-control").forEach((control) => { syncStarControl(control, Number(control.dataset.currentStars || 0)); control.addEventListener("pointerleave", () => clearStarPreview(control)); @@ -205,5 +172,32 @@ document.querySelectorAll(".star-control").forEach((control) => { control.querySelectorAll(".star-button").forEach((button) => { button.addEventListener("pointerenter", () => previewStarControl(control, Number(button.dataset.stars || 0))); button.addEventListener("focus", () => previewStarControl(control, Number(button.dataset.stars || 0))); + button.addEventListener("click", async () => { + const currentStars = Number(control.dataset.currentStars || 0); + const selectedStars = Number(button.dataset.stars || 0); + const nextStars = currentStars === selectedStars ? 0 : selectedStars; + + button.disabled = true; + try { + const response = await fetch(`/films/${control.dataset.filmId}/stars`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ stars: nextStars }), + }); + + const data = await response.json(); + if (!response.ok) { + return; + } + + syncStarControl(control, Number(data.stars || 0)); + } catch (error) { + console.error("Failed to update stars", error); + } finally { + button.disabled = false; + } + }); }); }); -- cgit v1.3-2-g0d8e