Files
Bike-Tinder/docs/PLAN.md

94 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PLAN — Bike-Tinder
**Status:** **v1.0 SHIPPED (2026-11-12)** — Phases 1–4 complete: stack live under its unit, nightly chain armed, tailnet URL in daily use, Gitea release `v1.0.0` cut. **v1.1** (QOL batch) is spec'd and awaits Glenn's build order; **v1.2** (analytics suite + fresh-ledger reset) queued. Phases are gated: each lands, is verified, then the next begins. Glenn's standing rule applies — "pause" stops all spend immediately.
## Version map
- **v1.0** — shipped 2026-11-12: the working loop (Phases 1–4 — catalog, swipe deck, personalization, deployed stack + nightly automation). Gitea release `v1.0.0`.
- **v1.1** — QOL batch, spec'd 2026-11-12 (`BACKLOG.md`): keyboard shortcuts · info-first tap flow · option C photos (fit + blurred backdrop) · minimal rubric + debug "why" toggle · section-counter fix + dry-catalog messaging · fresh counter + loud "No more new bikes!" empty state · batch formula v2 (25-target, second-look fill, ≥2 per batch) + Second Look band pop. No data reset. **Build authorized 2026-11-12** — sign-offs: ≥2-SL default (rich day 21 fresh/2 SL/2 wild) · violet band · Backspace alias; spec = `docs/V1.1-BUILD.md`; build phase in progress.
- **v1.2** — analytics suite (`BACKLOG.md`): full interaction capture + from-scratch ledger reset ("fresh start").
## v1 goal
A single-source, single-user, tailnet-only Tinder-style PWA on the Rotarran: nightly-updated catalog, swipe deck (Like / Nope / Maybe), full editable list, taste-ranked ordering.
## Phase 0 — Planning
- [x] Engineering consultation (Torres) → memo
- [x] Design docs in this repo: SOURCES, DESIGN, DATA-MODEL, RANKING, UI-SPEC
- [x] Glenn's calls (2026-11-10): v1 source = **Kijiji** · deck = show all, taste-ranked · new-bike feed = in-app badge only
- [x] Repo housekeeping (2026-11-10): branches `dev`/`main`/`release` · dedicated `bike-tinder-import` cron (parked for deploy) · stack-owned Tailscale sidecar in the deploy plan
- [x] Build gate opened — Glenn green-lit (2026-11-10)
- **Exit criteria:** docs merged to `main` (done 2026-11-10); source decision recorded in `SOURCES.md`.
## Phase 1 — Data spine
**Status: complete (2026-11-10).** Built by Torres on `dev` (`5bf180c..19e46cd`), independently re-verified by Alex (fresh-DB import x2, deck CLI, 8/8 selfcheck), merged to `main` (`d890dcd`). Live result: 640 rows → 560 active + 80 dup; deck top = Indian Scout family (91) then Vulcan 900 Classic (73).
*(Build work lands on `dev`; verified milestones merge to `main`; deploys track `release`.)*
- [x] DB schema + migrations (SQLite)
- [x] Importer: source catalog → upsert into DB (new / changed / gone states)
- [x] Cold-start scoring wired (existing rubric logic)
- [x] Proof: CLI query shows a ranked, deduped queue from the real live catalog
- **Exit criteria:** importer runs clean twice in a row (done — idempotent); counts reconcile against the source (done — 560+0+80=640); no schema surprises (done).
**Carried into Phase 2 from P1 findings:**
- Dedupe pairing refinement — **DONE (P2, 2026-11-10):** `min(ns,nb)` completeness pairing landed; 640 rows = 563 active + 77 dup, sibling rows stay visible.
- Image backfill — **DONE (P2, 2026-11-10):** hunt backfill filled 619/620 Kijiji listings; app mirror `app/imgfetch.py` → 559 local webp (3 CDN 404s fall back to placeholder).
- Dealer-blast mitigation + Stage 1.5 swipe boost matter immediately — only ~67/560 active rows are family-scored; the rest sort below.
## Phase 2 — The swipe app (core)
**Status: built by Torres + verified by Alex (2026-11-10) on `dev`; phone pass LIVE (swiping over the tailnet).** Contract frozen: `UI-SPEC.md` v2 + `reference/mockup-001-fidelity.html` (Variant A "Pure", approved by Glenn; 5-deep live stack, no-lock commit, dossier sheet). Image backfill + app-side mirror (`app/imgfetch.py`) done: 619/620 listings enriched, 559 local webp.
- [x] 2–3 UI mock variants → Glenn picked **Variant A "Pure"** (approved 2026-11-10 after two fix rounds)
- [x] API (deck cursor, swipe upsert/clear, list + filters, stats, media, import) + PWA frontend (faithful mockup port: stack engine, drag, Like / Nope / Maybe, dossier, empty/undo) — no match overlay (removed by Glenn's call)
- [x] Full list view with inline edit; basic filters (decision + sort + search)
- [x] Proof: runs locally on :9453 (systemd dev unit) + tailnet `http://rotarran.tailf35a34.ts.net:9453`; phone-viewport browser pass green; **phone pass live 2026-11-10** — Glenn swiping the real deck over the tailnet (24 swipes recorded + verified from the DB; list-edit tap still unexercised by Glenn — automation-covered)
- **Exit criteria:** Glenn can swipe a real deck on his phone and edit results in the list — swiping proven; edit-tap pending.
## Phase 3 — Personalization ("Trends")
**Status: BUILT → VERIFIED → LIVE (2026-11-11).** Contract frozen 2026-11-10; built by Torres in ~45 min ($3.80, 128 calls, 25/25 selfcheck; commits `04463ca`, `c205d31`, `b6e1851`); independently verified by Alex on DB copies — selfcheck 25/25 re-run, full section loop (compose → strikes → rounds → report → forge), strict-mode 409/200, forge-on-demand, trends, tank gauges, dossier "Why this rank". **One real defect found + fixed** (`1e4d840`): second-look returns were filtered out of the swipe buffer (`decision===null`), leaving round-2 dead in the UI — fixed and verified end to end (SECOND LOOK band, restrike writes round 2). **Live integration 2026-11-11:** backup at `~/dev/bike-tinder-p3/db-backup-pre-p3-20261111.sqlite3`; service restarted; P3 tables + 66 seeded events; section 1 composed; live calibration pass run (`deepseek/deepseek-v4.1-flash`, $0.0042; `agent_runs` #1) → taste profile v1 (`source=agent`).
Glenn's approved design (2026-11-10):
- **Sections of 25:** the deck is a finite batch; the agent always runs between sections (learning segmentation + anti-doom-scroll). Modes: forge-on-demand (default) / strict (nightly read only). Fallback to the last profile if the agent fails.
- **Section lead:** on open/refresh the best suggestions lead; the 5-card live stack is unchanged.
- **Wildcards (~1-in-10):** labeled "maybe you'll like this one instead"; plausible stretches; never hard-noped families; their answers teach the model.
- **Second looks:** likes + maybes + nopes return (chosen; ≤5/section, ≤2 nopes); Round 2 weighs ×2; flips are the loudest signal; a re-nope rests the item; rounds cap at 2. Returning maybes can bring their variants.
- **Match points:** taste + agent adjustments; drive ordering, the section recipe (family caps, price mix), and returns. Explainable in the dossier; tank gauge (non-numeric) in the List.
- **Hits objective:** order = expected-like; Trends reports the hit rate (likes; likes+maybes).
- **Agent pass:** nightly/on-demand read (mock + live + fallback); **no LLM in the swipe path — ever.**
- **Exit criteria:** after ≥30 swipes the deck order visibly reflects taste; explainability visible (dossier breakdown + Trends); the section loop works end-to-end; the agent kill-switch test passes (app fully functional on the last profile with the pass disabled).
**Carried forward:** service-side OpenRouter key wiring at P4 — until then each forge's agent pass runs in mock mode and supersedes the live profile · wildcard pool hygiene (rare non-bike listings can surface) · early-ramp texture: dense nopes punish broad cells (year 2020+, 12k+, nb) — the Scouts read mid until the agent carves family-level exceptions · minor reload edge: an already-restruck second look can re-cycle on mid-section reload.
**Glenn's call (2026-11-10, late): B1 "Photo experience v2" pulled forward** — built next, *before* Phase 4 ("get that feature baked in before we proceed to phase 4"). Spec frozen in `UI-SPEC.md` §11 (galleries · tap-side photo flip · center-tap fullscreen viewer · fresh lead photo on repeats) + `DATA-MODEL.md` B1 additions.
**Status: BUILT → VERIFIED → LIVE (2026-11-11).** Rail: hunt gallery capture + full-catalog backfill — 639/640 state items carry `imgs[]` (612 with 2+ photos); the 1 remainder is a photo-less ad at source ("no photo found", retried and confirmed) → app shows the placeholder, by design. App: Torres on a fresh clone (`~/dev/bike-tinder-b1-build`, commits `3b64635..036a511`, report `~/dev/bike-tinder-b1/TORRES-B1-REPORT.md`); live tree + live DB untouched during the build. Verification: selfcheck 31/31 both sides; `fe_walk_b1` headless §11 walk 390/390 (Alex re-ran green with a fresh setup). **Live integration (2026-11-11, ~00:40 AST):** pre-B1 backups `~/dev/bike-tinder-b1/preb1-live-*.sqlite3` + `preb1-files/`; 11 files deployed; service restarted; `POST /api/import` idempotent (`new:0`); live DB now 639/640 with galleries, 612 multi-photo (sample id 430 Indian Sport Scout n=8; `/img/430?i=1` → 302). Live gesture proof: side-tap flip `?i=0 ↔ ?i=1`, center-tap viewer with segments, screenshot `~/dev/bike-tinder-b1/b1-live-9453.png`. Glenn's ledger intact (248 swipes / 279 events carried). `dev` + `main` merged (`49466fb`); live tree commit `3a399e1`.
## Phase 4 — Deploy + automate (stack deployed 2026-11-12; phone check pending)
- ✅ Podman compose stack at `/var/mnt/DATA/podman-stacks/bike-tinder/` (release checkout) + systemd unit `podman-stack-bike-tinder.service` (`Delegate=yes`; verified to survive gateway/desktop restarts and re-raise after `systemctl --user restart`) + in-stack Tailscale sidecar (userspace mode) serving `https://biketinder.tailf35a34.ts.net` — **AUTHORIZED + serving 2026-11-12** (node `biketinder`, `100.114.195.22`; first phone swipes logged through the sidecar)
- ✅ `bike-tinder-import` Hermes cron live (08:30 daily, post-hunt): import → live agent read → sqlite backup (14-deep); silent on success (three clean live runs on deploy day)
- ✅ DB + image mirror migrated intact into the stack volume; backup drill produced its first files
- ✅ Deploy fix (found on first in-container agent call): `requests` was missing from requirements — the host venv had it incidentally, the slim image did not
- ✅ Runbook in DESIGN.md (deploy/rollback/logs/import gates/backup-restore/phone checklist)
- ✅ **Release v1.0.0 published 2026-11-12** — Gitea release, tag pinned to `main` head; notes summarize the working loop; source zip/tar.gz auto-generated by Gitea (nothing attached)
- ✅ In use from Glenn's phone (2026-11-12 — first photo flips + swipes logged through the sidecar); PWA "Add to Home Screen" available; full reboot test optional/pending
- **Exit criteria:** survives a reboot (service-level verified; power-level test optional); refreshes overnight unattended (armed, first automatic run 08:30 next day); installable and used from the phone — **achieved 2026-11-12.**
## Non-goals (v1)
- Multi-user accounts, social features, comments
- Additional sources (v2+); Facebook (never — manual by Glenn)
- Notifications beyond what already exists (Conduit is the house channel)
- Buying workflow (contact/inspection tracking) — possible v2
## Open decisions
- v1 source — **resolved 2026-11-10: Kijiji** (see `docs/SOURCES.md`)
- Mockup direction — Glenn picks at Phase 2 start