← All apps
📺

whats-on

TV show tracker - What's On tonight?

README

# What's On? 📺

A simple, mobile-first TV show tracker. No automatic syncing, no clutter from shared accounts — just your shows.

## Features

- 🟢 **Ready to Watch** — Shows you're current on with new episodes
- ⏳ **Catching Up** — Shows you're behind on
- 📱 **Mobile-first** — Designed for phone/iPad
- 🦉 **Henry Integration** — Tell Henry what you watched, he updates the tracker

## Tech Stack

- FastAPI + Jinja2
- SQLite
- Tailwind CSS (CDN)
- Deployed on Render

## API Endpoints

- `GET /` — Main dashboard
- `GET /api/shows` — List all shows
- `POST /api/shows` — Add a show
- `PUT /api/shows/{id}` — Update a show
- `DELETE /api/shows/{id}` — Delete a show
- `POST /api/shows/{id}/caught-up` — Mark show as caught up

## Local Development

```bash
pip install -r requirements.txt
python main.py
```

Then open http://localhost:8005
# Trigger redeploy

STATUS

---
type: project-status
project: What's On
last_updated: 2026-06-11
---

# STATUS — What's On

*The freshest file. Answers "where am I on this project?" Updated at the end of every substantive session.*

---

## Current state

**Live on Coolify at https://whatson.kenlill.com (2026-06-11)** — migrated off
Render with all production data (19 shows, 21 dismissed recs, 40 cached
recommendations). SQLite on a persistent volume (`/data/whats_on.db`), verified
to survive container restarts. Render (https://whats-on.onrender.com) is still
running in parallel as the fallback; **Ken suspends it himself** after a few
days of the new deploy behaving.

Also fixed this session: the home-page 500 that had been live since the 06-05
recommender deploy. Root cause was a Postgres migration bug (`ALTER TABLE ADD
COLUMN` on an existing column aborts the transaction; the swallowed exception
silently rolled back the new columns + `show_tags` every startup), plus a Jinja
guard that let `Undefined` through. Fixed in `a56fc3a`; Render healed on deploy.
Migration playbook: `V:\dev\sherpa-server\MIGRATION-RENDER-TO-COOLIFY.md`
(D:\dev\sherpa-server on the main machine).

## Ken's post-migration checklist (CC can't do these)

- [ ] **GitHub webhook for auto-deploy** (until then, deploys are manual via
      Coolify API/dashboard): repo Settings → Webhooks → Add webhook —
      url `https://coolify.kenlill.com/webhooks/source/github/events/manual`,
      content type `application/json`, secret = contents of
      `C:\temp\whatson-repro\webhook_secret.txt` (on the second computer; also
      already PATCHed into the Coolify app), events: just push.
- [ ] **Add `TMDB_API_KEY` to the Coolify app** (copy the value from the Render
      dashboard env vars) — without it, new recommendation refreshes lose
      poster + streaming-service data (the imported cache still displays fine).
- [ ] After the TMDB key is in: **POST `https://whatson.kenlill.com/api/admin/retag`**
      once to regenerate taste t

…(truncated for upload size)

DECISIONS

---
type: project-decisions
project: What's On
last_updated: 2026-06-11
---

# DECISIONS — What's On

*Architectural and scope choices. Append-only log. Each entry is a decision that shouldn't be re-litigated without new information. If you find yourself reopening a decision, either add a new entry that overrides the old (and say why) or leave both so the history is visible.*

---

## How to use this file

Each decision gets a dated entry with: what was decided, why, what was considered instead, and what would change our mind. Never delete entries — if a decision is reversed, add a new one that supersedes it.

---

## 2026-06-11 — Hosting moves to Coolify (kenlill.com); SQLite on a volume

**Decision:** Primary hosting is Coolify on Ken's kenlill.com server
(https://whatson.kenlill.com), built from the repo's Dockerfile, with **SQLite
on a persistent volume** (`DATABASE_PATH=/data/whats_on.db`) as the production
database. Render stays up in parallel until Ken personally suspends it. Data
moved (and moves again if ever needed) via the new `/api/admin/export` /
`/api/admin/import` JSON endpoints.

**Context:** Part of the multi-app campaign off Render onto Ken's own Coolify
box. Render had also drifted from its config: the dashboard's `DATABASE_URL`
meant prod silently ran Postgres while render.yaml described SQLite-on-disk —
that drift is what let the migration bug hide.

**Alternatives considered:** Postgres container on Coolify — rejected (Ken's
call 2026-06-11): more moving parts than a single-user hobby app needs, and the
dual-engine code keeps Postgres available if that ever changes.

**Reasoning:** One container, one volume, no separate DB service to operate.
SQLite matches the app's actual scale (one user, ~20 rows of shows) and the
original render.yaml intent.

**Would reconsider if:** Multi-user ever happens (that's the Supabase V2
conversation), or volume-backed SQLite proves fragile under Coolify redeploys
(verified surviving restarts on day one).

---

##

…(truncated for upload size)

MEMORY

---
type: project-memory
project: What's On
last_updated: 2026-06-11
---

# MEMORY — What's On

*Standing facts, preferences, and accumulated context. Long-lived — not "what I did yesterday" (that's STATUS.md). Update when you learn something worth keeping.*

---

## Purpose and scope

A personal TV-show tracker for Ken — single user, no login. Answers "what's on / what
should I watch next?" on a phone or iPad. Tracks watch progress, surfaces taste-aware
recommendations, and pulls air days/posters from external APIs. Standalone personal
app, not part of the tax suite.

## Domain knowledge

- **Show categories on the home page** (computed in `main.py:home`):
  - `between_seasons` — `status == 'hiatus'`
  - `backup_shows` — `priority == 3` ("watch if nothing else is on")
  - `catching_up` — `current_episode != 99` and `status == 'watching'`
  - `priority_shows` — everything else (the "ready to watch" hero list)
- **`current_episode = 99` is a sentinel meaning "caught up"** — not a real episode
  number. Lots of logic keys off this; don't treat 99 as data.
- **Statuses:** `watching`, `current`, `hiatus`, `dropped` (`VALID_STATUSES`).
  `dropped` shows are hidden from `get_all_shows()`.
- **Priority** 1–5 (1 = highest). **Rating** 1–5 (drives recommendation weighting;
  unrated defaults to 2).
- **Services** Ken uses (`USER_SERVICES`): Max, Apple TV+, Hulu, Peacock, Paramount+,
  Prime Video, Netflix. Plus Disney+/Other allowed as `VALID_SERVICES`.

## User preferences discovered

- Wants the standard four project files (CLAUDE/MEMORY/STATUS/DECISIONS) maintained and
  kept current as the repo changes — established 2026-06-04 when cloning the repo local.
- Mobile-first, low-clutter UI. Brand-colored service badges, "yeti/Henry" mascot.

## Hosting (since 2026-06-11)

- **Primary: Coolify on kenlill.com** — https://whatson.kenlill.com, app uuid
  `jpv73ok9hboffxm5np0kpu2v`, Dockerfile build, SQLite at `/data/whats_on.db`
  on persistent volume `pzor8tj1k0h4meyp5h9ffv3h`.

…(truncated for upload size)

CLAUDE.md

# CLAUDE.md — What's On

*Project-specific rules. User-level conventions come from `~/.claude/CLAUDE.md` — do not duplicate here.*

---

## What this is

"What's On?" — a personal, mobile-first TV show tracker for Ken. Tracks what he's
watching, what he's caught up on, what's on hiatus, and surfaces taste-aware
recommendations. Single-user, no auth, no clutter. **This is a standalone personal
app — NOT part of the Sherpa tax suite.**

## Tech stack

- **Backend:** FastAPI (Python) — `main.py` (routes) + `database.py` (data layer)
- **Frontend:** Server-rendered Jinja2, single template `templates/index.html`; Tailwind CSS via CDN
- **Database:** Dual-mode — SQLite when no `DATABASE_URL` (local + Coolify prod), PostgreSQL (psycopg3) when set (legacy Render prod)
- **Hosting:** Coolify on kenlill.com — https://whatson.kenlill.com (Dockerfile build,
  SQLite on a `/data` volume). Render (`render.yaml`) is the legacy deploy, running in
  parallel until Ken suspends it; it runs Postgres via a dashboard `DATABASE_URL`.
  Migration playbook: `dev\sherpa-server\MIGRATION-RENDER-TO-COOLIFY.md`
- **Dependencies:** pip (`requirements.txt`) — not Poetry

## Session startup

Read all four root files (CLAUDE.md, MEMORY.md, STATUS.md, DECISIONS.md) at the
start of substantive work. No other required reading.

## Conventions

- This app does **not** inherit the Sherpa-suite database/color rules from
  `~/.claude/CLAUDE.md`. Specifically:
  - **No shared Supabase DB.** It owns its own SQLite/Postgres database. No `firm_id`,
    no central `clients` table.
  - **No tax data-entry color system** (red/yellow/green). UI colors here are
    service-brand colors (Max, Hulu, etc.) in `index.html`.
- Tests, if added, go in `tests/` as `test_{module}.py` (pytest) — per global rules.
- Keep the SQLite/Postgres dual-path working: `database.py` branches on `DATABASE_URL`.
  Any new query must use the `_ph()` placeholder helper so it works on both engines.

## Do not redesign without asking

- Th

…(truncated for upload size)

Diary mentions

No recent diary mentions.

Render

  • Service: whats-on
  • Status: live
  • Last deploy: 2026-06-11T21:39:45.139650Z