5.9 KiB
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)
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)
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)
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:
uvicorn app.main:app --reload --host 0.0.0.0 --port 5005
Frontend (Vite + React + TS)
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.pyand paste only the streaming endpoint you’ll use first (WebSocket or SSE). Keep OCR endpoints behind aVISION_ENABLEDflag for V2. - Defer
screen_watch.py,capture.py,streaming.pyuntil 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 callPOST /api/actions/keywith"left","right","up","down", and"a"(Reset).- Add Mulligan button calling
"ctrl+m".
-
Backend:
- The
post_keyroute already exists. - Make sure
focus_window("GSPro")works on your machine.
- The
-
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/streamthat returns a downscaled JPEG buffer of a fixed region; reuse yourmsscapture and JPEG base64 helpers. Keep the code minimal.
- Introduce a simple
-
Frontend:
components/MapPanel.tsxopens a WS to/api/vision/ws/stream, paints frames onto a<canvas>, 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 = Falseinapp/core/config.py. - Keep
vision.pyimported 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:
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
- Installs the EXE + config files to
-
Frontend:
npm run build→ copydist/intobackend/ui/and serve using FastAPIStaticFiles. (You already hadbuild-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.tsxagainst/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.