# Recommended kickoff plan ## 0) Toolchain (lock these down) * **Python** 3.11 (for wheels availability + perf) * **Node** 20.19+ and **npm** 10+ * **UV** for deps * **Playwright** (later for UI smoke tests) * **Git** repo from day 1 ## 1) Make a tiny, opinionated repo scaffold Create a new repo (you can copy/paste this tree into your README so the LLM follows it): ``` gspro-remote/ backend/ app/ __init__.py main.py # FastAPI app factory + router mounts api/ __init__.py actions.py # /api/actions/* vision.py # /api/vision/* (keep but V2-gated) core/ config.py # AppConfig + load/save input_ctrl.py # pydirectinput helpers screen.py # mss capture utils (non-vision bits) pkg.json # (optional) for uvicorn dev script via npm - or just use Makefile pyproject.toml README.md frontend/ src/ main.tsx App.tsx pages/DynamicGolfUI.tsx components/ AimPad.tsx StatBar.tsx MapPanel.tsx styles/ index.html package.json vite.config.ts README.md scripts/ dev.ps1 # start both servers on Windows .editorconfig .gitignore README.md ``` ## 2) Bootstrap bare bones projects ### Backend (FastAPI) ```bash cd backend uv venv && uv pip install fastapi uvicorn pydantic-settings pydirectinput pywin32 mss opencv-python pillow zeroconf # Or with pip: # python -m venv .venv && .venv\Scripts\activate && pip install fastapi uvicorn pydirectinput pywin32 mss opencv-python pillow zeroconf ``` `app/main.py` (minimal) ```python from fastapi import FastAPI from .api.actions import router as actions_router def create_app() -> FastAPI: app = FastAPI(title="GSPro Remote", version="0.1.0") app.include_router(actions_router, prefix="/api/actions", tags=["actions"]) return app app = create_app() ``` `app/api/actions.py` (just enough to prove the vertical slice) ```python from fastapi import APIRouter, HTTPException from pydantic import BaseModel from ..core.input_ctrl import press_keys, focus_window router = APIRouter() class KeyReq(BaseModel): keys: str @router.post("/key") def post_key(req: KeyReq): if not focus_window("GSPro"): raise HTTPException(409, "GSPro window not found/active") press_keys(req.keys) return {"ok": True} ``` Run: ```bash uvicorn app.main:app --reload --host 0.0.0.0 --port 5005 ``` ### Frontend (Vite + React + TS) ```bash cd ../frontend npm create vite@latest . -- --template react-ts npm i npm run dev ``` Add a `.env` in frontend later if you want a configurable API base URL. ## 3) Migrate your existing code **incrementally** You already have solid pieces. Bring them in slice by slice: * Move `config.py` → `backend/app/core/config.py` (keep AppConfig + persistence). * Move `input_ctrl.py` → `backend/app/core/input_ctrl.py` (unchanged). * Create `backend/app/api/vision.py` and paste **only** the streaming endpoint you’ll use first (WebSocket or SSE). Keep OCR endpoints behind a `VISION_ENABLED` flag for V2. * Defer `screen_watch.py`, `capture.py`, `streaming.py` until the “MapPanel” slice (below). It’s okay if V1 ships with **no** OCR. ## 4) Implement one **vertical slice** end-to-end (MVP proof) Start with the **Aim Pad + Mulligan** because it touches everything: * Frontend: * `components/AimPad.tsx`: buttons call `POST /api/actions/key` with `"left"`, `"right"`, `"up"`, `"down"`, and `"a"` (Reset). * Add Mulligan button calling `"ctrl+m"`. * Backend: * The `post_key` route already exists. * Make sure `focus_window("GSPro")` works on your machine. * Test on Windows: you should see GSPro react from the tablet/phone. Now you have a working remote! ## 5) Add the **MapPanel** as the second slice * Backend: * Introduce a simple `/api/vision/ws/stream` that returns a **downscaled JPEG** buffer of a fixed region; reuse your `mss` capture and JPEG base64 helpers. Keep the code minimal. * Frontend: * `components/MapPanel.tsx` opens a WS to `/api/vision/ws/stream`, paints frames onto a ``, supports expand/collapse (no click mapping yet). * Aim for **720p @ 30fps, \~75–85 JPEG quality**. Measure latency, then optimize resize **before** JPEG encode. ## 6) Wire the rest of the “Essentials” * Tee left/right (`c`/`v`) * Scorecard (`t`) and Range finder (`r`) as secondary buttons under a kebab menu * Stat row at bottom (hard-coded 0.0° up/right for now—wire later) ## 7) Keep OCR as **V2** but leave hooks * Add a feature flag `VISION_ENABLED = False` in `app/core/config.py`. * Keep `vision.py` imported but routes gated: if disabled, return 404 with a friendly message. * This lets you merge OCR work later without reshaping the app. ## 8) Dev ergonomics on Windows Create `scripts/dev.ps1` to run both servers: ```powershell Start-Job { Set-Location backend; uvicorn app.main:app --reload --port 5005 } Start-Sleep -Seconds 1 Start-Process powershell -ArgumentList 'cd frontend; npm run dev' ``` ## 9) Packaging (when MVP is stable) * **Backend**: PyInstaller or Nuitka into a single EXE, then wrap with **Inno Setup** to produce an installer that: * Installs the EXE + config files to `C:\ProgramData\GSPro Remote\` * Creates a Start Menu entry and an **auto-start shortcut** for the Windows user * Opens firewall rule for your port * **Frontend**: `npm run build` → copy `dist/` into `backend/ui/` and serve using FastAPI `StaticFiles`. (You already had `build-ui.py`; keep that idea.) ## 10) How to use the LLM effectively (so it helps, not hurts) * **Give it the scaffold** and the **PRD** (and the Section-12 matrix). * Ask for **one file at a time**: “Implement `components/AimPad.tsx` against `/api/actions/key`” with explicit props and return types. * Paste back **real compiler/server errors** verbatim; ask it to fix **only those**. * Freeze the public contracts early (API request/response shapes). LLMs drift if the interface is fuzzy. ---