Skip to content

Commit 06271e5

Browse files
committed
feat: update frontend styles, fix alert calls, support step crop screenshots, and add plans
1 parent fcf2d67 commit 06271e5

9 files changed

Lines changed: 806 additions & 14 deletions

File tree

APP_BUILD_STEPS.md

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
# 🧱 ScrapeWizard — Application Build Steps (Backend Foundation → Frontend)
2+
3+
> The execution sequence for building the GUI app. Feature detail lives in
4+
> [FRONTEND_PLAN.md](FRONTEND_PLAN.md); the *order of operations* lives here.
5+
>
6+
> **Golden rule:** the backend API is built **first** because every screen needs it. Then
7+
> screens are added as **vertical slices** (one screen + the endpoints it uses), each shippable.
8+
>
9+
> **Locked:** local web app (`scrapewizard start`) · launch-alongside recorder · Stage 2 engine.
10+
11+
---
12+
13+
# PART A — Backend API Skeleton + SQLite (the foundation)
14+
15+
The backend is a **thin HTTP layer over the working Stage 2 engine**. It does three jobs:
16+
expose the engine over REST/WS, persist tests & runs in SQLite, serve the built frontend.
17+
18+
**Engine functions it wraps (all exist, verified working):**
19+
- `InteractiveRecorder(output_path, screenshots_dir, headless=False).start(url)` → writes `flow.json`
20+
- `TestGenerator(flow_path).generate()``{url, steps:[...]}` · `.export_pytest(path)` → file
21+
- `SandboxRunner(artifacts_dir, baselines_dir, headless).run(test_def)``RunResult`
22+
- `LLMClient(provider, api_key, model)` → for Settings test-connection
23+
- keyring via `scrapewizard/utils/security.py` → for storing the API key
24+
25+
### Step A1 — Dependencies & structure
26+
- **Do:** add `sqlmodel` (and confirm `fastapi`, `uvicorn`, `websockets`) to `requirements.txt`
27+
+ `pyproject.toml`. Create the backend module layout under `studio/backend/`:
28+
```
29+
studio/backend/
30+
main.py # FastAPI app, CORS, static mount, router registration (exists — extend)
31+
db.py # SQLite engine + session, create_all, schema_version
32+
models.py # SQLModel tables
33+
routes_settings.py
34+
routes_tests.py
35+
routes_runs.py
36+
run_executor.py # background run worker + WS hub
37+
deps.py # shared dependencies (DB session, paths)
38+
```
39+
- **Done-when:** `uvicorn studio.backend.main:app` boots; `GET /health` returns ok.
40+
41+
### Step A2 — Database setup (`db.py`)
42+
- **Do:**
43+
1. SQLite at `~/.scrapewizard/studio.db` (create dir if missing).
44+
2. SQLModel `engine` + a `get_session()` dependency.
45+
3. `init_db()``SQLModel.metadata.create_all()`; store a `schema_version` row.
46+
4. Artifacts root at `~/.scrapewizard/artifacts/`, baselines at `~/.scrapewizard/baselines/`
47+
(matches the sandbox default — keep them consistent).
48+
- **Done-when:** first boot creates the `.db` file; `GET /health` reports `schema_version`.
49+
50+
### Step A3 — Models (`models.py`) *(minimal set for v1 — expand later per PLATFORM_PLAN §12)*
51+
```
52+
Setting key (PK), value # provider, model, ai_mode, thresholds (NOT the key)
53+
Test id, name, url, created_at, updated_at
54+
Step id, test_id(FK), order, action, value, selectors(JSON), assertions(JSON), fingerprint(JSON)
55+
Run id, test_id(FK), status, started_at, finished_at, duration_ms, ai_calls, ai_cost_usd
56+
StepResult id, run_id(FK), step_name, status, duration_ms, screenshot_path,
57+
visual_diff_score, console_errors(JSON), network_errors(JSON),
58+
a11y_violations(JSON), healed, error_message
59+
```
60+
- **Note:** `Step.selectors/assertions/fingerprint` mirror `TestGenerator.generate()` output, and
61+
`StepResult` mirrors the sandbox dataclass → storage = direct dump, no translation.
62+
- The **API key is NOT a DB column** — it lives in keyring (`Setting` only records `has_key`).
63+
- **Done-when:** tables create cleanly; a manual insert/select round-trips.
64+
65+
### Step A4 — Settings router (`routes_settings.py`) → unblocks the Settings screen
66+
- **Endpoints:**
67+
- `GET /settings``{provider, model, ai_mode, has_key, visual_threshold, retention}`
68+
(read from `Setting` rows; `has_key` = does keyring hold one).
69+
- `PUT /settings` → upsert `Setting` rows; if `api_key` present, store it in keyring
70+
(`security.py`) and **never store/echo it in the DB or response**.
71+
- `POST /settings/test-connection` → build `LLMClient(provider, key, model)`, do a tiny
72+
probe call; return `{ok, message}`.
73+
- **Done-when:** `curl PUT /settings` saves; `GET` shows `has_key:true`; bad key → test fails clearly.
74+
75+
### Step A5 — Tests router (`routes_tests.py`) → unblocks New Test + Step Manager
76+
- **Endpoints:**
77+
- `POST /tests {url, name?}` → create `Test`, return `{id}`.
78+
- `GET /tests` → list with `step_count` + `last_run`.
79+
- `GET /tests/{id}` → test + ordered `Step`s.
80+
- `PUT /tests/{id}` → update name / steps (edits from Step Manager).
81+
- `DELETE /tests/{id}`.
82+
- `POST /tests/{id}/record`**launch the alongside recorder as a background task**
83+
(see A6 note); returns `{status:"started"}`.
84+
- `GET /tests/{id}/record/status``{recording: bool, step_count}`.
85+
- `POST /tests/{id}/generate {mode:"local"|"ai"}` → run `TestGenerator` (local) or AI path
86+
(Stage 5); persist resulting `Step`s.
87+
- `POST /tests/{id}/export``TestGenerator.export_pytest(...)` → return file download.
88+
- `POST /tests/{id}/run` → enqueue a run (A7), return `{run_id}`.
89+
- **Done-when:** create a test, record against it, `GET /tests/{id}` shows captured steps.
90+
91+
### Step A6 — Recording as a background task (the one tricky bit)
92+
- **Why:** `InteractiveRecorder.start()` opens a **headed browser and blocks until the user
93+
closes it** — you cannot await it inside a request handler.
94+
- **Do:**
95+
1. `POST /tests/{id}/record` spawns an `asyncio` task running the recorder, writing
96+
`flow.json` into the test's working dir; set an in-memory `recording[test_id]=True`.
97+
2. Frontend polls `record/status` (or subscribe via WS `/tests/{id}/record/live`).
98+
3. When the browser closes → recorder finishes → backend reads `flow.json`, runs
99+
`TestGenerator.generate()`, and **persists the steps** to the `Step` table; flips status off.
100+
- **Done-when:** click record (via curl trigger), perform actions, close browser → steps land in DB.
101+
102+
### Step A7 — Runs router + executor (`routes_runs.py`, `run_executor.py`) → unblocks Run view
103+
- **Do:**
104+
1. `POST /tests/{id}/run` → create `Run(status="queued")`, spawn a background task.
105+
2. Executor: build `test_def` from the test's `Step`s → `SandboxRunner(...).run(test_def)`
106+
→ write `StepResult` rows → update `Run` to passed/failed + duration; emit progress over WS.
107+
3. `GET /runs/{run_id}``Run` + its `StepResult`s (= the sandbox `RunResult` shape).
108+
4. `GET /runs?test_id=&status=` → history.
109+
5. `WS /runs/{run_id}/live` → push each `StepResult` as it completes.
110+
- **Simpler v1 fallback:** if WS is too much at first, make `POST /run` block and return the
111+
full `RunResult`; add live WS later. (Frontend can poll `GET /runs/{id}` meanwhile.)
112+
- **Done-when:** trigger a run → `GET /runs/{id}` returns per-step results + screenshots paths.
113+
114+
### Step A8 — Artifacts static mount
115+
- **Do:** mount `~/.scrapewizard/artifacts/` at `GET /artifacts/{run_id}/{file}` so the frontend
116+
can load screenshots and diff images by URL.
117+
- **Done-when:** a screenshot path from a run loads in the browser.
118+
119+
### Step A9 — `scrapewizard start` command
120+
- **Do:** new CLI command that (1) `init_db()`, (2) runs uvicorn on `127.0.0.1`, (3) serves the
121+
**built** frontend (`studio/frontend/dist`) as static files at `/`, (4) opens the browser.
122+
Bind localhost only (security, already set). Keep CORS for the Vite dev origin during dev.
123+
- **Done-when:** `scrapewizard start` opens the app shell in the browser.
124+
125+
### Step A10 — Verify the backend before touching the frontend
126+
- **Do:** a smoke script (or REST client) that walks: `PUT /settings``POST /tests`
127+
(`record` manually) → `GET /tests/{id}``POST /run``GET /runs/{id}`.
128+
- **Done-when:** the whole loop works over HTTP with **no frontend** — the API is real and trusted.
129+
130+
> **End of Part A:** the engine is fully drivable over HTTP + persisted in SQLite. Now the
131+
> frontend is "just" a client of a working API — exactly the position you want to build a UI from.
132+
133+
---
134+
135+
# PART B — Frontend, step by step
136+
137+
Build as vertical slices; each slice ends with something you can click. Detail per screen is in
138+
[FRONTEND_PLAN.md](FRONTEND_PLAN.md) §5 — this is the sequence + setup.
139+
140+
### Slice 0 — Scaffold & plumbing
141+
- **Do (in `studio/frontend/`):**
142+
1. Confirm Vite + React; add TypeScript if not present.
143+
2. Install: `react-router-dom`, `@tanstack/react-query`, `tailwindcss`, `shadcn/ui` (init),
144+
`react-hook-form`, `zod`, `lucide-react`.
145+
3. Set up Tailwind + design tokens + **dark mode** (semantic colors: passed/failed/healed/
146+
running/queued).
147+
4. Create `src/lib/api.ts` — a typed fetch client pointing at `http://127.0.0.1:8000`
148+
(env var for prod vs dev). One place for all API calls.
149+
5. Create `QueryClientProvider`, `RouterProvider`.
150+
- **Done-when:** `npm run dev` shows a themed blank app; dark mode toggles.
151+
152+
### Slice 1 — App shell + the four state components
153+
- **Do:**
154+
1. Layout: sidebar (Dashboard / Tests / Runs / Settings) + top bar (logo, **AI status pill**).
155+
2. Define routes (FRONTEND_PLAN §4) with placeholder pages.
156+
3. Build the reusable primitives **now** (everything depends on them): `EmptyState`,
157+
`LoadingSkeleton`, `ErrorState` (with "copy details"), `StatusPill`.
158+
- **Done-when:** navigation works; every placeholder renders an EmptyState.
159+
160+
### Slice 2 — Settings screen *(simplest real screen; uses A4)*
161+
- **Do:** provider select (OpenAI/Anthropic/OpenRouter/Local), API key field, model field,
162+
AI mode radio (Off/Creation/Full), threshold slider. "Test connection" button →
163+
`POST /settings/test-connection`. Save → `PUT /settings`. Show `has_key` as "•••• set".
164+
- **Done-when:** save a key, test connection passes, AI pill updates.
165+
166+
### Slice 3 — The spine: New Test → Record → Step Manager *(uses A5 + A6)*
167+
- **Do:**
168+
1. **New Test** (`/tests/new`): URL form (Zod), name optional → `POST /tests` → then
169+
`POST /tests/{id}/record`.
170+
2. **Record state:** waiting panel polling `record/status` (or WS), showing live step count +
171+
⚠ warnings; on finish → navigate to `/tests/{id}`.
172+
3. **Step Manager** (`/tests/{id}`): render ordered `StepCard`s (action, **editable selector
173+
ladder**, value, assertions, screenshot thumb). Edit/reorder/delete → `PUT /tests/{id}`.
174+
Buttons: **Run ▶** (`POST /run`), **Generate** (local/AI), **Export pytest**.
175+
- **Done-when:** enter URL → record 3 actions → see them as editable steps → edit one → save.
176+
177+
### Slice 4 — Run view (live + result) *(uses A7 + A8)*
178+
- **Do:** on Run ▶ → navigate to `/runs/{run_id}`; subscribe to WS (or poll `GET /runs/{id}`);
179+
render `RunTimeline` (steps green/red) + an evidence panel per step: screenshot (from
180+
`/artifacts/...`), `VisualDiffViewer`, `A11yViolationList`, `ConsoleNetworkList`, error msg.
181+
Status banner deep-links to the first failing step.
182+
- **Done-when:** run a test, watch steps update, open evidence; failed run highlights the failure.
183+
184+
### Slice 5 — Export button polish
185+
- **Do:** wire **Export pytest** in Step Manager to `POST /tests/{id}/export` → download the
186+
standalone file. (Engine already produces it.)
187+
- **Done-when:** downloaded file runs as a standalone `pytest` outside the app.
188+
189+
### Slice 6 — Dashboard + Run history *(uses /stats, /runs)*
190+
- **Do:** Dashboard stat cards + recent runs + empty state ("Record your first test" / "Try the
191+
demo"). Run history table with filters → click → Run view.
192+
- **Done-when:** dashboard shows real numbers; history filters to a test's failed runs.
193+
194+
### Slice 7 — Polish & a11y
195+
- **Do:** ensure **all four states** on every screen; keyboard shortcuts (`r`, `/`, `Esc`);
196+
run the portal through axe-core in CI (we ship an a11y checker — it must pass its own).
197+
- **Done-when:** clean empty/loading/error states everywhere; portal passes its own a11y scan.
198+
199+
---
200+
201+
## First demo-able product = end of Slice 4
202+
Enter a URL → record the task → see/edit the generated steps → run → watch it pass/fail live →
203+
read the report. That is the exact application you described, working end to end, no API key
204+
required (AI optional via Settings).
205+
206+
## Suggested commit checkpoints
207+
- After **A10** (backend drivable over HTTP) — a clean foundation commit.
208+
- After each frontend slice (0→7) — each is independently shippable.
209+
210+
## Dependency note
211+
Part B Slice N assumes the Part A endpoints it lists are done. If building solo, do
212+
**A1–A5 → Slice 0–2 → A6/A7 → Slice 3–4 → rest** so backend and frontend interleave naturally.

0 commit comments

Comments
 (0)