diff --git a/src/content/docs/vizably/account-storage.md b/src/content/docs/vizably/account-storage.md index 1ba9de1..f2bc109 100644 --- a/src/content/docs/vizably/account-storage.md +++ b/src/content/docs/vizably/account-storage.md @@ -2,7 +2,7 @@ title: Account storage description: Vizably keeps no database — a user's account lives in a GitHub repo or Drive folder they already own. sidebar: - order: 3 + order: 4 --- Vizably runs **no database of its own**. A signed-in user's entire account — @@ -106,13 +106,39 @@ expected rather than exceptional. store can load the account. The storage ACL *is* the account ACL. That is a deliberate trade and it needs saying out loud in the UI, not just in docs. -Two other things must be disclosed in the interface rather than buried: - -- GitHub's OAuth `repo` scope is all-or-nothing — it cannot be narrowed to a - single repository. -- Deleting a scan removes the file and refreshes the caches, but **GitHub - history may still contain the deleted blob** unless history is rewritten. Do - not claim permanence you cannot deliver. +One other thing must be disclosed in the interface rather than buried: +deleting a scan removes the file and refreshes the caches, but **GitHub +history may still contain the deleted blob** unless history is rewritten. Do +not claim permanence you cannot deliver. + +## GitHub access is a GitHub App, not a plain OAuth scope + +GitHub storage is authorized through a **GitHub App** (`GITHUB_APP_ID` + +`GITHUB_APP_PRIVATE_KEY`, installed per-account) rather than a classic OAuth +App with a `repo` scope. That is a deliberate choice: a classic `repo` scope +is all-or-nothing across every repository the user owns, while a GitHub App +installation can be scoped to the one repository Vizably actually needs — +narrower access, disclosed as such. The backend resolves the installation for +a given `owner/repo` via the Apps API before writing +(`backend/services/authService.js`). + +## Endpoints and current gaps + +The auth/storage API lives under `/api/auth/*`. `backend/README.md` in the +repository keeps the endpoint table current — read that rather than this page +for the exact routes, since this is the part of Vizably still changing +fastest. Two things worth knowing going in: + +- **Google is not implemented yet.** `/api/auth/google` and its callback + return `501` today, and + [issue #111](https://github.com/codrlabs/vizably/issues/111) tracks + dropping Google sign-in from the near-term plan rather than finishing it — + treat the Drive side of this page as the target design, not current + behavior. +- **GitHub repository creation exists** (`POST /api/auth/storage/create`, + plus a name-availability check) in addition to the browse/validate/load + flow described above — the connect UI can create a new private repository + for a user who doesn't have one yet, not just pick from existing ones. ## Implementer checklist diff --git a/src/content/docs/vizably/architecture.md b/src/content/docs/vizably/architecture.md index 6665297..a92bbee 100644 --- a/src/content/docs/vizably/architecture.md +++ b/src/content/docs/vizably/architecture.md @@ -2,7 +2,7 @@ title: Architecture description: How Vizably is put together — the layers, the folders, and the contract between the two halves. sidebar: - order: 2 + order: 3 --- Vizably is two halves and one wire contract. The backend knows nothing about diff --git a/src/content/docs/vizably/getting-started.md b/src/content/docs/vizably/getting-started.md new file mode 100644 index 0000000..08c0e67 --- /dev/null +++ b/src/content/docs/vizably/getting-started.md @@ -0,0 +1,140 @@ +--- +title: Getting started +description: Clone, run and test Vizably locally — Docker or plain Node, in under 15 minutes. +sidebar: + order: 2 +--- + +This gets you from `git clone` to a running app with a green test suite. It +assumes nothing beyond `git` and a browser. + +## Prerequisites + +One of: + +- **Docker Desktop**, recommended — one install, no Node version juggling. +- **Node.js 24** and a recent npm. Check with `node -v`. CI and + `backend/package.json`'s `engines` field both pin 24; older versions are + not tested against. + +You do not need Postgres or any cloud account to run the app locally. Puppeteer +and axe-core install with `npm install` in `backend/` (Puppeteer downloads its +own Chromium; the Docker image installs Alpine's system Chromium instead). + +## Clone the repo + +```bash +git clone https://github.com/codrlabs/vizably.git +cd vizably +``` + +The [Architecture](/vizably/architecture/) page covers the folder layout; the +repository's own `README.md` has the up-to-date directory tree. + +## Set the two required secrets + +The server **refuses to start** without `SESSION_SECRET` and `ENCRYPTION_KEY` +set — `backend/index.js` throws on boot if either is missing, even for local +development with no OAuth configured. This is a real requirement, not a +Phase-1 placeholder: sessions are a signed cookie +(`cookie-session`, not a server-side store), and that cookie has to be signed +with something. + +```bash +cd backend +cp .env.example .env +openssl rand -base64 32 # run twice, paste one value each into + # SESSION_SECRET and ENCRYPTION_KEY in .env +``` + +Everything else in `.env.example` (the GitHub App credentials) is only needed +to exercise sign-in and saved scans — see +[Account storage](/vizably/account-storage/). Scanning a URL works without +them. + +:::note[Docker users] +`docker-compose.yml` does not currently inject `SESSION_SECRET` or +`ENCRYPTION_KEY` into the backend container, so `docker compose up` fails at +boot on a fresh clone until you either add them to the compose file's +`environment:` block or otherwise get them into the container's environment. +Local Node picks up `backend/.env` automatically through `dotenv`. +::: + +## Run the app + +### Option A — Docker + +```bash +docker compose up --build +``` + +First run takes a couple of minutes (pulling `node:22-alpine`, installing +dependencies in both containers). Once you see the frontend and backend both +report they're listening, open . + +### Option B — local Node + +Two terminals: + +```bash +# Terminal 1 — backend (Express on :3000) +cd backend +npm install +npm run dev # nodemon, reloads on save + +# Terminal 2 — frontend (Vite on :5173) +cd frontend +npm install +npm run dev # hot-reloads on save +``` + +Open . + +## Smoke-test it + +Every submission runs a real Puppeteer + axe-core scan — there is no mock +mode in the running app. + +1. Open . Type a URL — bare domains work too + (`example.com`), see [URL normalization](/vizably/url-normalization/) — + and submit. +2. The scan takes a few seconds against the live page. You land on + `/results?url=...`: a score, severity badges, and findings grouped into + Visual Accessibility, Structure & Semantics and Multimedia, plus a "what's + good" list. +3. Click a finding to go to `/problem/:id` — root cause, offending markup, + fix steps and a WCAG reference. + +Or verify the API directly: + +```bash +curl http://localhost:3000/health + +curl "http://localhost:3000/api/scan-results?url=https://example.com" +# real scan — expect several seconds + +curl "http://localhost:3000/api/scan-results?url=http://127.0.0.1" +# 400 {"error":"Private/loopback hosts are not allowed"} — the SSRF guard +``` + +## Run the tests + +```bash +cd backend && npm test # node:test + supertest +cd frontend && npm test:run # Vitest, single run +cd frontend && npm run lint +cd frontend && npm run build +``` + +These are what CI runs on every pull request. Run them once on a clean clone +so you know what green looks like before you make your first edit. + +## Where to look next + +1. [Architecture](/vizably/architecture/) — the layers and the folders. +2. [Account storage](/vizably/account-storage/) — the portable-account model + behind sign-in and saved scans. +3. [Scanning](/vizably/scanning/) — how a submitted URL turns into a report. +4. The repository's own `README.md` and `backend/README.md` for the + authoritative, always-current directory layout and environment variable + reference. diff --git a/src/content/docs/vizably/overview.md b/src/content/docs/vizably/overview.md index def86b2..34757a7 100644 --- a/src/content/docs/vizably/overview.md +++ b/src/content/docs/vizably/overview.md @@ -48,14 +48,12 @@ hosting, no lock-in, and an account that is portable across devices. ## Read next +- [Getting started](/vizably/getting-started/) — clone it, run it, test it. - [Architecture](/vizably/architecture/) — the layers, the folders, and the contract between the two halves. - [Account storage](/vizably/account-storage/) — the portable account: on-disk layout, the fit-check, and the concurrency rules. - -:::note[Still to write] -The scan pipeline — Puppeteer, axe-core, and the transformer that turns rule -violations into readable findings — does not have a page here yet. Until it -does, the detail lives in the -[Vizably repository](https://github.com/codrlabs/vizably). -::: +- [URL normalization](/vizably/url-normalization/) — accepting a bare domain + on the landing page without weakening the backend's validation. +- [Scanning](/vizably/scanning/) — Puppeteer, axe-core, and the transform + that turns rule violations into readable findings. diff --git a/src/content/docs/vizably/scanning.md b/src/content/docs/vizably/scanning.md new file mode 100644 index 0000000..acec87d --- /dev/null +++ b/src/content/docs/vizably/scanning.md @@ -0,0 +1,118 @@ +--- +title: Scanning +description: How a submitted URL becomes a categorized WCAG report — Puppeteer, axe-core, and the transform between them. +sidebar: + order: 6 +--- + +Every scan is real: there is no mock mode in the running app. A submitted URL +gets a headless browser, a live axe-core run against the rendered page, and a +pure transform into Vizably's report shape. + +## The pipeline + +``` +POST /api/scan { url } + │ + ▼ +routes/scan.js → controllers/scanController.js + │ ssrfGuard.validate(url) — reject non-http, private/loopback hosts + ▼ +services/scanRunner.js — ScanRunner.run(url) + │ launch headless Chromium + │ page.goto(url, { waitUntil: 'domcontentloaded' }) + │ inject axe-core into the page context + │ page.evaluate(() => axe.run()) + ▼ +services/axeTransformer.js — transform(axeResults) + │ bucket violations into visualAccessibility / + │ structureAndSemantics / multimedia + ▼ +res.json(ScanResult) → /results?url=... renders it +``` + +## `ScanRunner` (`backend/services/scanRunner.js`) + +`run(url)` does the whole lifecycle: validate, launch, navigate, inject, +evaluate, transform, close. A few decisions worth knowing if you're touching +this file: + +- **Waits for DOM ready, not network idle.** Busy sites (ad-heavy pages, + chat widgets) never reach `networkidle0` and would time out waiting for + it. The runner waits for `domcontentloaded`, then gives the page a short, + best-effort idle window (`waitForNetworkIdle`, 500ms idle / 5s cap, + swallowed on timeout) so late content has a chance to settle without + blocking the scan on it. +- **Bypasses CSP before navigating.** Many sites ship a strict + `Content-Security-Policy` that would otherwise block the injected + `