11 KiB
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
- 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· dedicatedbike-tinder-importcron (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 inSOURCES.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.
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 unitpodman-stack-bike-tinder.service(Delegate=yes; verified to survive gateway/desktop restarts and re-raise aftersystemctl --user restart) + in-stack Tailscale sidecar (userspace mode) servinghttps://biketinder.tailf35a34.ts.net— AUTHORIZED + serving 2026-11-12 (nodebiketinder,100.114.195.22; first phone swipes logged through the sidecar) - ✅
bike-tinder-importHermes 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):
requestswas 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
mainhead; 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