From 7bc16da765024c2dbbea1296c45b7a0ba33da771 Mon Sep 17 00:00:00 2001 From: Samuel Olabode Date: Thu, 17 Sep 2026 14:25:04 -0600 Subject: [PATCH] docs: update account storage and architecture documentation; enhance overview links Revised the account storage documentation to clarify GitHub App authorization and the implications of deleting scans. Adjusted the architecture documentation order for better navigation. Updated the overview page to include new links for URL normalization and scanning, while removing outdated notes on the scan pipeline. --- src/content/docs/vizably/account-storage.md | 42 +++++- src/content/docs/vizably/architecture.md | 2 +- src/content/docs/vizably/getting-started.md | 140 ++++++++++++++++++ src/content/docs/vizably/overview.md | 12 +- src/content/docs/vizably/scanning.md | 118 +++++++++++++++ src/content/docs/vizably/url-normalization.md | 84 +++++++++++ 6 files changed, 382 insertions(+), 16 deletions(-) create mode 100644 src/content/docs/vizably/getting-started.md create mode 100644 src/content/docs/vizably/scanning.md create mode 100644 src/content/docs/vizably/url-normalization.md 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 + `