Files
Bike-Tinder/docs/PLAN.md

9.6 KiB
Raw Blame History

PLAN — Bike-Tinder

Status: Build phase — Phase 2 complete + phone-proven (2026-11-10); Phase 3 built, verified, and live (2026-11-11); B1 photo experience v2 built, verified, and live (2026-11-11); Phase 4 next. Phases are gated: each lands, is verified, then the next begins. Glenn's standing rule applies — "pause" stops all spend immediately.

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

  • Engineering consultation (Torres) → memo
  • Design docs in this repo: SOURCES, DESIGN, DATA-MODEL, RANKING, UI-SPEC
  • Glenn's calls (2026-11-10): v1 source = Kijiji · deck = show all, taste-ranked · new-bike feed = in-app badge only
  • 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
  • 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.)

  • DB schema + migrations (SQLite)
  • Importer: source catalog → upsert into DB (new / changed / gone states)
  • Cold-start scoring wired (existing rubric logic)
  • 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.

  • 2–3 UI mock variants → Glenn picked Variant A "Pure" (approved 2026-11-10 after two fix rounds)
  • 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)
  • Full list view with inline edit; basic filters (decision + sort + search)
  • 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.

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) targeting https://biketinder.tailf35a34.ts.net — awaiting Glenn's one-time tailnet authorization
  • ✅ 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)
  • ⏳ PWA install + first phone swipe (after sidecar auth); full reboot test
  • Exit criteria: survives a reboot; refreshes overnight unattended; installable and used from the phone.

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