# dailysplice — changelog

Newest first. Every code change bumps `VERSION` and adds a line here; the
running version is visible at `/api/version` and in the UI footer, which is how
a deploy is confirmed to have landed (`/srv/projects/standards/coding.md`).

## 0.2.1 — 2026-08-05

Application logging actually emits. Uvicorn configures only its own loggers, so
the root logger had no handler and every `log.info()` in the app fell through to
`logging.lastResort` (WARNING-level) and vanished — the startup shape-only
config report and the whole Spotify connect trail were invisible. Warnings and
errors were getting through, which is what made the gap easy to miss.

## 0.2.0 — 2026-08-05

**Public landing page stripped to a wordmark, "Welcome to DailySplice" and a
Sign in button.** The product idea is not public yet, so the descriptive hero
and the `<meta name="description">` pitch are both gone. Recorded as a rule in
`agents.md` — this is a decision, not an omission to be "fixed" later.

**Spotify connect flow (F2, first slice).** Per-user OAuth 2.0 Authorization
Code against Spotify, `state` anti-CSRF in an HttpOnly cookie, tokens stored
**encrypted at rest** (Fernet, `services/crypto.py`) and refreshed on demand
with a 90s margin. New `spotify_accounts` table (Alembic `0002`), keyed on the
user id, `ON DELETE CASCADE`. Routes: `/spotify/connect`, `/spotify/callback`,
`GET /api/spotify/status`, `POST /api/spotify/disconnect`. Dashboard card covers
all four real states — not configured, not connected, connected, and connected
-but-revoked.

Encryption **fails closed**: with no `TOKEN_ENCRYPTION_KEY`, `encrypt()` raises
rather than writing a refresh token in plaintext.

`/spotify/*` joins `/api/*` behind the default-deny middleware. Gated *browser*
paths now redirect to `/` instead of answering a navigation with raw JSON;
gated `/api/*` paths still return the structured 401 that JS expects.

## 0.1.1 — 2026-08-04

The SPA fallback answers HEAD as well as GET. FastAPI, unlike plain Starlette,
does not add HEAD to a GET route, so the public landing page returned 405 to
every uptime monitor and link checker. Added `main/scripts/gate-f0.sh`, the F0
gate: it asserts the public surface is public, the gated surface is closed,
sign-in is wired with PKCE, the deployed version matches the source, the schema
exists, and the host carries no id-auth gate.

## 0.1.0 — 2026-08-04

F0 foundation. FastAPI + SQLAlchemy 2/Alembic + Postgres 17 sidecar serving a
Vite/React 19 SPA from one Python process, per `react.md`. BW Auth Pattern B in
PUBLIC domain mode: OAuth+PKCE handshake against `auth.bowden.works`, an
app-signed session cookie, client-driven silent SSO probe, full sign-out.
Default-deny `AuthMiddleware` over `/api/*` with an exact-path public allowlist.
Invite-only admission enforced in `services/identity.py` (`superadmin` / `user`
/ `none`), roles mirrored to the BW hub for display. Public landing page,
signed-in shell, not-invited screen. No Spotify integration yet.
