diff --git a/README.md b/README.md
index 201e37a..204accb 100644
--- a/README.md
+++ b/README.md
@@ -91,7 +91,7 @@ GenieACS Twilio Traccar modem-bridge
**Frontend:** Vue 3, Quasar v2, Pinia, Vite, Mapbox GL JS
**Backend:** ERPNext v16 / Frappe (Python), PostgreSQL, Node.js (targo-hub)
**Infra:** Docker, Traefik v2.11, Authentik SSO, Proxmox
-**Integrations:** Twilio (SMS), Mailjet (email), Stripe (payments), Traccar (GPS), GenieACS (TR-069), Gemini 2.5 Flash via targo-hub (vision/OCR — see [docs/VISION_AND_OCR.md](docs/VISION_AND_OCR.md))
+**Integrations:** Twilio (SMS), Mailjet (email), Stripe (payments), Traccar (GPS), GenieACS (TR-069), Gemini 2.5 Flash via targo-hub (vision/OCR — see [docs/features/vision-ocr.md](docs/features/vision-ocr.md))
## Data Volumes (migrated from legacy)
@@ -128,11 +128,18 @@ Authentik SSO protects staff apps via Traefik `forwardAuth`. The ops app reads `
## Documentation
-| Document | Content |
-|----------|---------|
-| [ARCHITECTURE.md](docs/ARCHITECTURE.md) | Ecosystem overview, remote Docker infrastructure, platform strategy |
-| [DATA_AND_FLOWS.md](docs/DATA_AND_FLOWS.md) | ERPNext data models, atomic order creation, customer flows |
-| [CPE_MANAGEMENT.md](docs/CPE_MANAGEMENT.md) | Hardware management, XX230v diagnostics, TR-069/TR-369 |
-| [APP_DESIGN_GUIDELINES.md](docs/APP_DESIGN_GUIDELINES.md) | Frontend framework architecture rules, UI/UX Wizard guidelines |
-| [ROADMAP.md](docs/ROADMAP.md) | Implementation phases and current remote transition tasks |
-| [archive/](docs/archive/) | Completed legacy migration analyses and accounting audits |
+Start at **[docs/README.md](docs/README.md)** — it indexes every doc with a
+"I want to…" intent table. Quick map:
+
+| Area | Entry point |
+|---|---|
+| Plan & live module URLs | [docs/roadmap.md](docs/roadmap.md) |
+| System architecture (services, Docker, SSO) | [docs/architecture/overview.md](docs/architecture/overview.md) |
+| ERPNext data model + customer flows | [docs/architecture/data-model.md](docs/architecture/data-model.md) |
+| Frontend patterns (Vue/Quasar/Pinia) | [docs/architecture/app-design.md](docs/architecture/app-design.md) |
+| Billing, Stripe, invoices | [docs/features/billing-payments.md](docs/features/billing-payments.md) |
+| CPE / modems / ONTs / TR-069 | [docs/features/cpe-management.md](docs/features/cpe-management.md) |
+| Scanner / OCR / Gemini pipeline | [docs/features/vision-ocr.md](docs/features/vision-ocr.md) |
+| Agent flows (Flow Template) | [docs/features/flow-editor.md](docs/features/flow-editor.md) |
+| Wizard SKU vs legacy audit | [docs/reference/erpnext-item-diff.md](docs/reference/erpnext-item-diff.md) |
+| Historical snapshots & migration logs | [docs/archive/](docs/archive/) |
diff --git a/apps/field/infra/nginx.conf b/apps/field/infra/nginx.conf
index e7d17d7..37b4d40 100644
--- a/apps/field/infra/nginx.conf
+++ b/apps/field/infra/nginx.conf
@@ -18,7 +18,7 @@ server {
# NOTE: Ollama Vision proxy removed 2026-04-22 — all invoice OCR and
# barcode/equipment scans now go directly to targo-hub (Gemini 2.5 Flash).
- # See docs/VISION_AND_OCR.md.
+ # See docs/features/vision-ocr.md.
# Targo Hub API proxy — vision, devices, etc.
location /hub/ {
diff --git a/apps/field/src/api/ocr.js b/apps/field/src/api/ocr.js
index 5f90e7a..0e9734a 100644
--- a/apps/field/src/api/ocr.js
+++ b/apps/field/src/api/ocr.js
@@ -7,7 +7,7 @@
* vision model behind the hub.
*
* NOTE: apps/field is being folded into apps/ops under /j (see
- * docs/ARCHITECTURE.md §"Legacy Retirement Plan"). During the transition
+ * docs/architecture/overview.md §"Legacy Retirement Plan"). During the transition
* we keep this file in sync with apps/ops/src/api/ocr.js so no surprises
* when code moves over.
*/
diff --git a/apps/ops/infra/nginx.conf b/apps/ops/infra/nginx.conf
index 52b04e3..75258fd 100644
--- a/apps/ops/infra/nginx.conf
+++ b/apps/ops/infra/nginx.conf
@@ -21,7 +21,7 @@ server {
# NOTE: Ollama Vision proxy removed 2026-04-22 — invoice OCR and all
# barcode/equipment scans now go directly to targo-hub (Gemini 2.5 Flash).
- # See docs/VISION_AND_OCR.md. The hub handles CORS + rate-limit, so no
+ # See docs/features/vision-ocr.md. The hub handles CORS + rate-limit, so no
# nginx pass-through is needed here.
# SPA fallback — all routes serve index.html
diff --git a/apps/ops/src/components/flow-editor/index.js b/apps/ops/src/components/flow-editor/index.js
index 1732aa6..ad3d308 100644
--- a/apps/ops/src/components/flow-editor/index.js
+++ b/apps/ops/src/components/flow-editor/index.js
@@ -6,8 +6,8 @@
*
*
*
- * See FLOW_EDITOR_ARCHITECTURE.md in /docs/ for the full data model and
- * runtime contract.
+ * See docs/features/flow-editor.md for the full data model and runtime
+ * contract.
*/
export { default as FlowEditor } from './FlowEditor.vue'
diff --git a/apps/ops/src/composables/useScanner.js b/apps/ops/src/composables/useScanner.js
index e1beb6b..aa34408 100644
--- a/apps/ops/src/composables/useScanner.js
+++ b/apps/ops/src/composables/useScanner.js
@@ -25,7 +25,7 @@
* Merged from apps/ops/src/composables/useScanner.js (which had the
* equipment-label branch) and apps/field/src/composables/useScanner.js
* (which had the resilient timeout + offline queue). See
- * docs/ARCHITECTURE.md §"Legacy Retirement Plan" — field is being folded
+ * docs/architecture/overview.md §"Legacy Retirement Plan" — field is being folded
* into ops at /j and must not lose offline capability in the process.
*
* @param {object} options
diff --git a/apps/ops/src/data/wizard-constants.js b/apps/ops/src/data/wizard-constants.js
index 1a640aa..be0888b 100644
--- a/apps/ops/src/data/wizard-constants.js
+++ b/apps/ops/src/data/wizard-constants.js
@@ -183,7 +183,7 @@ export const RESIDENTIAL_PRESETS = [
// Legacy reuse: gestionclient.product.sku = TELEPMENS
// "Téléphonie IP, options toutes incluses, Canada et É-U illimité".
// Bundle discount (to reach ~10$/mo) is applied manually by the sales
- // rep, not auto-generated. See docs/ERPNEXT_ITEM_DIFF_VS_LEGACY.md.
+ // rep, not auto-generated. See docs/reference/erpnext-item-diff.md.
{ item_code: 'TELEPMENS', item_name: 'Téléphonie IP illimitée CA/US', rate: 28.95, billing: 'recurring', billing_interval: 'Month', contract_months: 24 },
],
},
@@ -320,7 +320,7 @@ export const REFERRAL_CREDIT_AMOUNT = 50.00
// Combo rebates are applied MANUALLY by the sales rep from the catalog
// (legacy RAB2X / RAB3X / RAB4X SKUs, -5 / -10 / -15 $). We intentionally
// do NOT auto-insert a combo rebate line — the rep picks the right SKU
-// based on the actual bundle. See docs/ERPNEXT_ITEM_DIFF_VS_LEGACY.md.
+// based on the actual bundle. See docs/reference/erpnext-item-diff.md.
const CAT_ICON_MAP = { Internet: 'wifi', Téléphonie: 'phone', Bundle: 'inventory_2', Équipement: 'router', Frais: 'receipt', Autre: 'category' }
export function catIcon (cat) {
diff --git a/apps/ops/src/modules/tech/pages/TechDevicePage.vue b/apps/ops/src/modules/tech/pages/TechDevicePage.vue
index 0287bc1..eb62047 100644
--- a/apps/ops/src/modules/tech/pages/TechDevicePage.vue
+++ b/apps/ops/src/modules/tech/pages/TechDevicePage.vue
@@ -15,7 +15,7 @@
├ Interventions ─────┤ upcoming Dispatch Jobs at this Service Location
└ Info OLT ──────────┘ frame/slot/port/ontid if this is an ONT
- See docs/VISION_AND_OCR.md §10 for the full relationship map. The data
+ See docs/features/vision-ocr.md §10 for the full relationship map. The data
loads lazily (equipment first, then location + related entities in
parallel) — if the network drops halfway, the first card still renders.
diff --git a/apps/ops/src/modules/tech/pages/TechScanPage.vue b/apps/ops/src/modules/tech/pages/TechScanPage.vue
index a81ac92..7d50e4a 100644
--- a/apps/ops/src/modules/tech/pages/TechScanPage.vue
+++ b/apps/ops/src/modules/tech/pages/TechScanPage.vue
@@ -5,7 +5,7 @@
label, Gemini reads the serial, and we look it up in ERPNext, optionally
auto-linking the equipment to the tech's current Dispatch Job.
- ERPNext relationships touched (see docs/VISION_AND_OCR.md §10 for the full
+ ERPNext relationships touched (see docs/features/vision-ocr.md §10 for the full
data-model diagram):
Dispatch Job ─► Customer ─► Service Location ◄── Service Equipment
@@ -22,7 +22,7 @@
naturally for any downstream ticket view.
Ported from apps/field/src/pages/ScanPage.vue during the field→ops
- unification (see docs/ARCHITECTURE.md §"Legacy Retirement Plan"). Adapted
+ unification (see docs/architecture/overview.md §"Legacy Retirement Plan"). Adapted
for the ops router:
- device-detail route name: 'tech-device' (was 'device' in field)
- same query-param contract from TechJobDetailPage.goScan()
diff --git a/apps/ops/src/stores/offline.js b/apps/ops/src/stores/offline.js
index ba874a9..0462449 100644
--- a/apps/ops/src/stores/offline.js
+++ b/apps/ops/src/stores/offline.js
@@ -33,7 +33,7 @@
* - `cache-{key}` → generic read cache (used for read-through patterns)
*
* Ported from apps/field/src/stores/offline.js as part of the field→ops
- * unification (see docs/ARCHITECTURE.md §"Legacy Retirement Plan").
+ * unification (see docs/architecture/overview.md §"Legacy Retirement Plan").
*/
import { defineStore } from 'pinia'
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..c730c5f
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,76 @@
+# Gigafibre FSM — Documentation
+
+> **Start here.** Everything you need to understand the system is one or two
+> clicks away. Pick the row that matches why you're here.
+
+---
+
+## Quick nav by intent
+
+| I want to… | Open |
+|---|---|
+| **See the plan** — what's shipped, what's queued, with clickable links to every live module | [roadmap.md](roadmap.md) |
+| **Understand the system end-to-end** — services, containers, networks, SSO, Traefik routes | [architecture/overview.md](architecture/overview.md) |
+| **Build a new feature** — UI patterns, folder layout, router conventions, Pinia, feature-sliced design | [architecture/app-design.md](architecture/app-design.md) |
+| **Touch ERPNext data** — doctypes, customer → subscription → equipment → invoice, "Lead to Live" flow | [architecture/data-model.md](architecture/data-model.md) |
+| **Work on billing, Stripe, payments** — subscription lifecycle, invoice OCR, payment reconciliation, PPA | [features/billing-payments.md](features/billing-payments.md) |
+| **Touch CPE / modems / ONTs** — GenieACS, Oktopus, TR-069 → TR-369 migration, TP-Link XX230v diagnostics | [features/cpe-management.md](features/cpe-management.md) |
+| **Build or debug the scanner / OCR** — Gemini vision pipeline, barcode/equipment/invoice endpoints, offline queue, AI_API_KEY rotation | [features/vision-ocr.md](features/vision-ocr.md) |
+| **Work on agent flows / automation** — Flow Template model, trigger wiring, step editor, runtime contract | [features/flow-editor.md](features/flow-editor.md) |
+| **Audit the wizard's SKU mapping vs legacy** | [reference/erpnext-item-diff.md](reference/erpnext-item-diff.md) |
+| **Inspect the legacy PHP wizard** | [reference/legacy-wizard/](reference/legacy-wizard/) |
+| **Read a historical status snapshot** | [archive/status-snapshots/](archive/status-snapshots/) |
+
+---
+
+## Folder map
+
+```text
+docs/
+├── README.md ← you are here
+├── roadmap.md ← phase tracker + live module URLs
+├── architecture/ ← the system itself
+│ ├── overview.md (services, Docker, Traefik, SSO, retirement plan)
+│ ├── data-model.md (ERPNext doctypes + "Lead to Live")
+│ └── app-design.md (Vue/Quasar patterns, feature-sliced layout)
+├── features/ ← one doc per business capability
+│ ├── billing-payments.md (Stripe, invoices, subscriptions, PPA)
+│ ├── cpe-management.md (GenieACS, Oktopus, XX230v)
+│ ├── vision-ocr.md (Gemini, scan pipeline, offline queue, keys)
+│ └── flow-editor.md (Flow Template, triggers, runtime)
+├── reference/ ← lookup material
+│ ├── erpnext-item-diff.md (new SKUs vs legacy gestionclient)
+│ └── legacy-wizard/ (read-only snapshot of the PHP wizard)
+├── assets/ ← PPTX decks, screenshots, generated diagrams
+└── archive/ ← frozen docs, do not edit
+ ├── HANDOFF-2026-04-18.md
+ ├── MIGRATION.md
+ ├── LEGACY-ACCOUNTING-ANALYSIS.md
+ └── status-snapshots/ (dated session reports)
+```
+
+---
+
+## Conventions
+
+- **Edit in place.** Don't copy-paste a section into a new file — link to it instead.
+- **Dated snapshots go to `archive/status-snapshots/`.** Don't create `STATUS_YYYY-MM-DD.md` at the root anymore.
+- **Code comments that reference a doc** use the full repo-relative path, e.g.
+ `// See docs/features/vision-ocr.md §10` — so `grep docs/` still surfaces them if we reorganize again.
+- **The root `README.md`** (one level up) is the repo introduction for
+ non-technical readers; `docs/README.md` (this file) is the engineering index.
+
+---
+
+## What changed on 2026-04-22
+
+The docs were flattened out of a single folder into `architecture/`,
+`features/`, `reference/`, `archive/`. Every file move used `git mv` so
+`git log --follow` still works. The old paths
+(`docs/ARCHITECTURE.md`, `docs/VISION_AND_OCR.md`, etc.) no longer exist — if
+you land on one of those links from an external bookmark, use the table above
+to find the new location.
+
+The only place old names still appear is inside
+`archive/HANDOFF-2026-04-18.md` and `archive/status-snapshots/*.md`, where the
+links are intentionally preserved as a frozen historical record.
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
deleted file mode 100644
index d2b1ec2..0000000
--- a/docs/ROADMAP.md
+++ /dev/null
@@ -1,111 +0,0 @@
-# Gigafibre FSM — Roadmap
-
-> See [STATUS_2026-04-18.md](STATUS_2026-04-18.md) for a full state snapshot and [HANDOFF.md](HANDOFF.md) for the reader's guide.
-
-
-## Phase 1 — Foundation (Done, March 2026)
-- [x] ERPNext v16 + PostgreSQL
-- [x] Custom FSM doctypes (Service Location, Equipment, Subscription)
-- [x] Dispatch doctypes (Job, Technician, Tag with skill levels)
-- [x] Dispatch PWA with timeline, drag-drop, Mapbox map
-- [x] GPS tracking (Traccar hybrid REST + WebSocket)
-- [x] Authentik SSO (forwardAuth)
-- [x] ERPNext API proxy (nginx same-origin)
-- [x] Legacy migration (6,667 customers, 21K subs, 115K invoices, 242K tickets)
-
-## Phase 2 — Ops App (Done, March 2026)
-- [x] Unified ops PWA (erp.gigafibre.ca/ops/)
-- [x] Client list/detail with inline editing (Odoo-style)
-- [x] Dispatch module + ticket management
-- [x] Equipment tracking with OLT/SNMP diagnostics
-- [x] SMS/Email notifications (Twilio + Mailjet)
-- [x] Invoice OCR — originally Ollama Vision, migrated to Gemini 2.5 Flash via targo-hub (2026-04-22, no GPU on ops VM). See [VISION_AND_OCR.md](VISION_AND_OCR.md).
-- [x] Field tech mobile (/j/, unified into ops app — see Phase 2.7)
-- [x] Authentik federation (staff → client SSO)
-- [x] Modem-bridge (Playwright headless for TP-Link ONU diagnostics)
-- [x] WiFi diagnostic panel (mesh topology, client signal, packet loss)
-
-## Phase 2.5 — Remote Architecture Transition (Current Focus)
-- [x] Deprecate local `frappe_docker` development dependencies
-- [x] Consolidate architecture and ecosystem documentation
-- [ ] Decouple API/Auth (Token-based auth instead of session for frontend apps)
-- [ ] Set up dev proxy (Vite) to bridge local env to remote ERPNext API (bypassing CORS)
-- [ ] Establish secure PostgreSQL tunnel for `infra-map-vue` development
-- [ ] **Sandboxed outbound comms** (required before any scheduler/webhook/Twilio/Mailjet E2E test) — prevents test runs from reaching real customers while legacy still bills
-- [ ] Subscription → Sales Invoice scheduler: keep `pause_scheduler=1` until cutover event. Legacy PHP is authoritative until then.
-
-## Phase 2.6 — Quotation + DocuSeal (Shipped 2026-04-18)
-- [x] DocuSeal container at sign.gigafibre.ca (Docker + Mailjet SMTP)
-- [x] Hub routes: `/accept/generate`, `/accept/docuseal-webhook`, `/accept/confirm`
-- [x] Quotation custom fields: `custom_docuseal_signing_url`, `custom_docuseal_envelope_id`, `custom_quote_type`
-- [x] Billing Frequency Custom Field on Item + Quotation/Sales Invoice/Sales Order Item (fetch_from item_code)
-- [x] Print Format "Soumission TARGO" with split Recurring / One-time sections and QR → signing URL
-- [x] Wizard flow: ProjectWizard → `/accept/generate` → DocuSeal submission → signed webhook → `acceptQuotation()`
-- [ ] Register DocuSeal webhook in UI (Settings → Webhooks, `form.completed` → hub endpoint) — **manual**
-- [ ] First end-to-end signed acceptance on a real customer quote
-
-## Phase 2.7 — Field ↔ Ops unification at /j (In Progress, started 2026-04-22)
-
-Collapse `apps/field` into `apps/ops/src/modules/tech` so there is one
-PWA, one deploy, one auth surface. See [VISION_AND_OCR.md](VISION_AND_OCR.md)
-for the scan pipeline this depends on.
-
-**Phase 1 — scan + device (Shipped 2026-04-22, commit `e50ea88`)**
-- [x] Invoice OCR on Gemini 2.5 Flash via hub `/vision/invoice` — ops VM no longer needs a GPU
-- [x] Ollama proxy blocks removed from ops + field nginx configs
-- [x] Offline store (`apps/ops/src/stores/offline.js`) — mutation queue + vision queue, time-driven retries, idb-keyval persistence
-- [x] Unified scanner composable (`useScanner.js`) with Mode A (barcodes, 8s timeout + queue) and Mode B (equipment label, sync)
-- [x] TechScanPage at `/j/scan` — camera, 3-tier lookup (serial → barcode → MAC), auto-link to Dispatch Job context, create/link dialogs
-- [x] TechDevicePage at `/j/device/:serial` — 7 cards surfacing full ERPNext relationship graph (Equipment, Customer, Location, Subscription, Issues, Dispatch Jobs, OLT)
-- [x] Documentation: `docs/VISION_AND_OCR.md` (pipeline, §10 relationship graph, §8.1 secrets/rotation)
-
-**Phase 2 — PWA hardening**
-- [ ] Quasar service worker runtime caching scoped to `/j/*` (stale-while-revalidate for reads, network-first for mutations)
-- [ ] Precache the tech route manifest so a cold install with no signal still boots `/j/`
-
-**Phase 4 — Auth unification**
-- [ ] Collapse logout URL to `id.gigafibre.ca` (currently ops points to `auth.targo.ca`)
-- [ ] Decide whether `/j/*` stays behind Authentik forwardAuth or moves to magic-link only
-
-**Phase 5 — Magic-link tech access**
-- [ ] Traefik skip Authentik on `/j/{jwt-token}` route
-- [ ] targo-hub `/otp/tech-link` — mint short-lived JWT bound to technician + job
-- [ ] JWT validation in TechTasksPage → populate tech context without an SSO session
-- [ ] SMS delivery of the link (reuse existing Twilio path)
-
-**Phase 6 — Flow runtime integration**
-- [ ] Wire `flow-runtime` to persist pending steps through `offline.queue` so a tech mid-flow survives a dead zone
-- [ ] Surface queued flow state in TechTasksPage ("3 actions en attente de sync")
-
-**Phase 7 — Remove apps/field**
-- [ ] `git rm -r apps/field` once `/j/*` has parity and has run in production for ≥2 weeks
-- [ ] Remove field build + deploy from CI
-- [ ] Redirect `*.field.gigafibre.ca` (if any) → `erp.gigafibre.ca/ops/#/j/`
-- [ ] Update `docs/ARCHITECTURE.md` service table (drop field row)
-
-## Phase 3 — Workflows & Automation (In Progress)
-- [ ] Tag technicians with skills (46 techs to tag)
-- [ ] Wire auto-dispatch (cost-optimization matching)
-- [ ] Issue → Dispatch Job creation
-- [ ] Job completion → equipment status + close ticket
-- [ ] Equipment swap → inventory log
-- [ ] n8n escalation workflows
-- [ ] Twilio 10DLC production upgrade
-- [ ] SLA tracking
-
-## Phase 4 — Customer Portal
-- [ ] Self-service app (invoices, tickets, equipment)
-- [ ] Stripe payments
-- [ ] Online appointment booking
-- [ ] Real-time tech tracking SMS
-- [ ] Legacy password migration (MD5 → PBKDF2)
-- [ ] QR code modem → subscriber dashboard
-
-## Phase 5 — Advanced Features
-- [ ] Van stock inventory per tech
-- [ ] Revenue analytics (MRR, churn, ARPU)
-- [ ] Proactive monitoring (auto-ticketing)
-- [ ] Online checkout (e-commerce signup)
-- [ ] Marketing segmentation + campaigns
-- [ ] Tech performance dashboards
-- [ ] Preventive maintenance scheduling
diff --git a/docs/architecture/README.md b/docs/architecture/README.md
new file mode 100644
index 0000000..5ed4507
--- /dev/null
+++ b/docs/architecture/README.md
@@ -0,0 +1,12 @@
+# Architecture
+
+How the pieces fit together. Read [overview.md](overview.md) first unless you
+have a specific reason not to.
+
+| Doc | Read when… |
+|---|---|
+| [overview.md](overview.md) | You need the full service map — ERPNext, Ops PWA, targo-hub, DocuSeal, Authentik, Traefik, Docker networks, the legacy retirement plan |
+| [data-model.md](data-model.md) | You're about to touch ERPNext: Customer, Service Location, Service Subscription, Service Equipment, Sales Invoice, Payment Entry. Also covers the "Lead to Live" customer flow |
+| [app-design.md](app-design.md) | You're building frontend. Feature-sliced layout, Vue/Quasar patterns, Pinia, router, theming, Storybook conventions |
+
+Back to [docs/README.md](../README.md) · [roadmap.md](../roadmap.md)
diff --git a/docs/APP_DESIGN_GUIDELINES.md b/docs/architecture/app-design.md
similarity index 100%
rename from docs/APP_DESIGN_GUIDELINES.md
rename to docs/architecture/app-design.md
diff --git a/docs/DATA_AND_FLOWS.md b/docs/architecture/data-model.md
similarity index 100%
rename from docs/DATA_AND_FLOWS.md
rename to docs/architecture/data-model.md
diff --git a/docs/ARCHITECTURE.md b/docs/architecture/overview.md
similarity index 98%
rename from docs/ARCHITECTURE.md
rename to docs/architecture/overview.md
index 9e43a52..2364694 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/architecture/overview.md
@@ -77,7 +77,7 @@ Internet
- **Model:** Gemini 2.5 Flash (Google) — no local GPU, all inference remote.
- **Endpoints (hub):** `/vision/barcodes`, `/vision/equipment`, `/vision/invoice`.
- **Why centralized:** ops VM has no GPU, so the legacy Ollama `llama3.2-vision` install was retired. All three frontends (ops, field-as-ops `/j`, future client portal) hit the hub, which enforces JSON `responseSchema` per endpoint.
-- **Client-side resilience:** barcode scans use an 8s timeout + IndexedDB retry queue so techs in weak-LTE zones don't lose data. See [VISION_AND_OCR.md](VISION_AND_OCR.md) for the full pipeline.
+- **Client-side resilience:** barcode scans use an 8s timeout + IndexedDB retry queue so techs in weak-LTE zones don't lose data. See [../features/vision-ocr.md](../features/vision-ocr.md) for the full pipeline.
---
diff --git a/docs/HANDOFF.md b/docs/archive/HANDOFF-2026-04-18.md
similarity index 92%
rename from docs/HANDOFF.md
rename to docs/archive/HANDOFF-2026-04-18.md
index 4b5838c..5ed5a0d 100644
--- a/docs/HANDOFF.md
+++ b/docs/archive/HANDOFF-2026-04-18.md
@@ -1,6 +1,8 @@
-# Gigafibre FSM — Handoff Index
+# Gigafibre FSM — Handoff Index (ARCHIVED 2026-04-18)
-> **Purpose:** one-page map of every doc in this repo. Pick the path for your role, read in order, stop when you have enough.
+> **⚠️ ARCHIVED.** This was the single-page index before the docs were reorganized into folders. Doc paths below are **stale** — use [../README.md](../README.md) for the current index. Kept for historical reference only.
+
+> **Purpose (at time of writing):** one-page map of every doc in this repo. Pick the path for your role, read in order, stop when you have enough.
**Last refreshed:** 2026-04-18
diff --git a/docs/archive/README.md b/docs/archive/README.md
new file mode 100644
index 0000000..3d8b6c0
--- /dev/null
+++ b/docs/archive/README.md
@@ -0,0 +1,26 @@
+# Archive
+
+> **Do not edit these files.** They're frozen snapshots kept for historical
+> context. Internal links inside archived docs still point at the
+> pre-reorganization paths (e.g. `ARCHITECTURE.md`) — that's intentional so
+> they read as they did at the time.
+
+For the current state of the system, go back to [docs/README.md](../README.md)
+or [roadmap.md](../roadmap.md).
+
+## Contents
+
+| File | What it is |
+|---|---|
+| [HANDOFF-2026-04-18.md](HANDOFF-2026-04-18.md) | The single-page doc index as it stood before the 2026-04-22 reorganization |
+| [MIGRATION.md](MIGRATION.md) | Legacy `gestionclient` (MariaDB) → ERPNext migration: table-by-table mapping, completion log |
+| [LEGACY-ACCOUNTING-ANALYSIS.md](LEGACY-ACCOUNTING-ANALYSIS.md) | April 2026 audit of the legacy PHP/MariaDB accounting system, informed the migration plan |
+| [status-snapshots/](status-snapshots/) | Dated "state of the system" write-ups from individual work sessions |
+
+## Why these still exist
+
+Old snapshots capture context that doesn't belong in the evergreen docs:
+what someone was worried about on a specific date, what tradeoffs we
+considered and rejected, what shipped in a given sprint. When reading, assume
+code examples and doc paths have drifted — verify in the current code before
+acting on them.
diff --git a/docs/STATUS_2026-04-18.md b/docs/archive/status-snapshots/2026-04-18.md
similarity index 96%
rename from docs/STATUS_2026-04-18.md
rename to docs/archive/status-snapshots/2026-04-18.md
index 6c1177e..e702cdc 100644
--- a/docs/STATUS_2026-04-18.md
+++ b/docs/archive/status-snapshots/2026-04-18.md
@@ -1,8 +1,9 @@
-# Gigafibre FSM — Status Snapshot (2026-04-18)
+# Gigafibre FSM — Status Snapshot (2026-04-18) — ARCHIVED
-> **Audience:** new contributor, stakeholder, or future-me opening this cold.
-> **Goal:** be able to hold a coherent conversation about the system in 10 minutes.
-> **Companion docs:** [README](../README.md) · [ARCHITECTURE](ARCHITECTURE.md) · [ROADMAP](ROADMAP.md) · [DATA_AND_FLOWS](DATA_AND_FLOWS.md) · [BILLING_AND_PAYMENTS](BILLING_AND_PAYMENTS.md) · [CPE_MANAGEMENT](CPE_MANAGEMENT.md) · [APP_DESIGN_GUIDELINES](APP_DESIGN_GUIDELINES.md)
+> **⚠️ ARCHIVED snapshot.** Links below point to the pre-reorganization layout and are stale. For the current state, see [../../roadmap.md](../../roadmap.md). For the live index, see [../../README.md](../../README.md).
+
+> **Audience (original):** new contributor, stakeholder, or future-me opening this cold.
+> **Goal (original):** be able to hold a coherent conversation about the system in 10 minutes.
---
diff --git a/docs/STATUS_2026-04-18b.md b/docs/archive/status-snapshots/2026-04-18b.md
similarity index 96%
rename from docs/STATUS_2026-04-18b.md
rename to docs/archive/status-snapshots/2026-04-18b.md
index 4d653a6..7ce4573 100644
--- a/docs/STATUS_2026-04-18b.md
+++ b/docs/archive/status-snapshots/2026-04-18b.md
@@ -1,6 +1,8 @@
-# Status — 2026-04-18 (session 2)
+# Status — 2026-04-18 (session 2) — ARCHIVED
-> Follow-up to STATUS_2026-04-18.md. This session was the sales-flow sprint.
+> **⚠️ ARCHIVED snapshot.** For current state, see [../../roadmap.md](../../roadmap.md).
+
+> Follow-up to [2026-04-18.md](2026-04-18.md). This session was the sales-flow sprint.
## What changed this session
diff --git a/docs/STATUS_2026-04-19.md b/docs/archive/status-snapshots/2026-04-19.md
similarity index 94%
rename from docs/STATUS_2026-04-19.md
rename to docs/archive/status-snapshots/2026-04-19.md
index a9e5265..f5fe25f 100644
--- a/docs/STATUS_2026-04-19.md
+++ b/docs/archive/status-snapshots/2026-04-19.md
@@ -1,6 +1,8 @@
-# Status — 2026-04-19 (wizard legacy-alignment refinement)
+# Status — 2026-04-19 (wizard legacy-alignment refinement) — ARCHIVED
-> Follow-up to STATUS_2026-04-18b.md. Small session: reviewed the legacy
+> **⚠️ ARCHIVED snapshot.** For current state, see [../../roadmap.md](../../roadmap.md).
+
+> Follow-up to [2026-04-18b.md](2026-04-18b.md). Small session: reviewed the legacy
> wizard (`facturation.targo.ca/.../client_wizard`), realized the initial
> preset model fought the real workflow, refactored.
diff --git a/docs/archive/status-snapshots/README.md b/docs/archive/status-snapshots/README.md
new file mode 100644
index 0000000..d1d17bb
--- /dev/null
+++ b/docs/archive/status-snapshots/README.md
@@ -0,0 +1,19 @@
+# Status snapshots
+
+Dated "state of the system" write-ups. Each captures what shipped, what
+changed, and what was queued on that specific date. **Frozen** — do not edit
+past snapshots.
+
+| Date | File | Theme |
+|---|---|---|
+| 2026-04-18 | [2026-04-18.md](2026-04-18.md) | Foundational 10-min snapshot, TL;DR + architecture refresher |
+| 2026-04-18 (session 2) | [2026-04-18b.md](2026-04-18b.md) | Sales-flow sprint |
+| 2026-04-19 | [2026-04-19.md](2026-04-19.md) | Wizard legacy-alignment refinement |
+
+For the live state see [../../roadmap.md](../../roadmap.md).
+
+## Creating a new snapshot
+
+- File name: `YYYY-MM-DD.md` (append `b`, `c`, … for multiple sessions per day).
+- Link it from the table above when you commit.
+- Don't cross-link from evergreen docs — those should point at `roadmap.md` instead.
diff --git a/docs/Gigafibre-Billing-Handoff.pptx b/docs/assets/Gigafibre-Billing-Handoff.pptx
similarity index 100%
rename from docs/Gigafibre-Billing-Handoff.pptx
rename to docs/assets/Gigafibre-Billing-Handoff.pptx
diff --git a/docs/Gigafibre-FSM-Features.pptx b/docs/assets/Gigafibre-FSM-Features.pptx
similarity index 100%
rename from docs/Gigafibre-FSM-Features.pptx
rename to docs/assets/Gigafibre-FSM-Features.pptx
diff --git a/docs/build-billing-pptx.js b/docs/assets/build-billing-pptx.js
similarity index 100%
rename from docs/build-billing-pptx.js
rename to docs/assets/build-billing-pptx.js
diff --git a/docs/build-pptx.js b/docs/assets/build-pptx.js
similarity index 99%
rename from docs/build-pptx.js
rename to docs/assets/build-pptx.js
index a6b6c05..cb0bce5 100644
--- a/docs/build-pptx.js
+++ b/docs/assets/build-pptx.js
@@ -482,7 +482,8 @@ async function build() {
s.addText("Targo — Avril 2026", { x: 0.8, y: 5.1, w: 9, h: 0.3, fontSize: 10, fontFace: "Calibri", color: C.muted, margin: 0 });
// Write file
- await pres.writeFile({ fileName: "/Users/louispaul/Documents/testap/gigafibre-fsm/docs/Gigafibre-FSM-Features.pptx" });
+ const path = require("path");
+ await pres.writeFile({ fileName: path.join(__dirname, "Gigafibre-FSM-Features.pptx") });
console.log("Presentation created!");
}
diff --git a/docs/package-lock.json b/docs/assets/package-lock.json
similarity index 100%
rename from docs/package-lock.json
rename to docs/assets/package-lock.json
diff --git a/docs/package.json b/docs/assets/package.json
similarity index 100%
rename from docs/package.json
rename to docs/assets/package.json
diff --git a/docs/features/README.md b/docs/features/README.md
new file mode 100644
index 0000000..6804a07
--- /dev/null
+++ b/docs/features/README.md
@@ -0,0 +1,14 @@
+# Features
+
+One doc per business capability. Each describes the user-facing behaviour,
+the ERPNext doctypes touched, the API endpoints involved, and the failure
+modes. Open the one that matches the feature you're changing.
+
+| Doc | Owns |
+|---|---|
+| [billing-payments.md](billing-payments.md) | Stripe integration (Checkout, Billing Portal, webhook), subscription lifecycle, invoice generation, payment reconciliation, PPA (Plan de paiement automatique), Klarna BNPL |
+| [cpe-management.md](cpe-management.md) | CPE fleet: GenieACS (TR-069), Oktopus (TR-369), provisioning, diagnostics, TP-Link XX230v / Deco deep probe, migration plan |
+| [vision-ocr.md](vision-ocr.md) | Camera-based scanning via Gemini 2.5 Flash — barcode, equipment label, invoice OCR. Hub endpoints `/vision/*`, `useScanner` composable, offline queue, AI_API_KEY rotation policy, ERPNext relationships triggered by a scan |
+| [flow-editor.md](flow-editor.md) | Agent-flows module: Flow Template + Flow Run doctypes, trigger catalogue, step types, runtime contract, UI editor at `/ops/#/agent-flows` |
+
+Back to [docs/README.md](../README.md) · [roadmap.md](../roadmap.md)
diff --git a/docs/BILLING_AND_PAYMENTS.md b/docs/features/billing-payments.md
similarity index 99%
rename from docs/BILLING_AND_PAYMENTS.md
rename to docs/features/billing-payments.md
index 049537a..e6f2c78 100644
--- a/docs/BILLING_AND_PAYMENTS.md
+++ b/docs/features/billing-payments.md
@@ -1,8 +1,9 @@
# Facturation & Paiements — Handoff dev
> Référence unique pour toutes les fonctionnalités facture / paiement construites sur
-> la stack `erp.gigafibre.ca` + `client.gigafibre.ca` + `ops`. Lisez `ARCHITECTURE.md`
-> d'abord pour le contexte réseau/services.
+> la stack `erp.gigafibre.ca` + `client.gigafibre.ca` + `ops`. Lisez
+> [../architecture/overview.md](../architecture/overview.md) d'abord pour le contexte
+> réseau/services.
Dernière MàJ : 2026-04-17
diff --git a/docs/CPE_MANAGEMENT.md b/docs/features/cpe-management.md
similarity index 100%
rename from docs/CPE_MANAGEMENT.md
rename to docs/features/cpe-management.md
diff --git a/docs/FLOW_EDITOR_ARCHITECTURE.md b/docs/features/flow-editor.md
similarity index 99%
rename from docs/FLOW_EDITOR_ARCHITECTURE.md
rename to docs/features/flow-editor.md
index 46a7870..7c7f4e8 100644
--- a/docs/FLOW_EDITOR_ARCHITECTURE.md
+++ b/docs/features/flow-editor.md
@@ -3,7 +3,8 @@
> Authoritative reference for the Flow Editor subsystem: visual builder in the
> Ops PWA, JSON flow-definition language, runtime engine in `targo-hub`, and
> the Frappe scheduler that wakes delayed steps. Companion document to
-> `ARCHITECTURE.md` and `DATA_AND_FLOWS.md`.
+> [../architecture/overview.md](../architecture/overview.md) and
+> [../architecture/data-model.md](../architecture/data-model.md).
---
@@ -674,5 +675,6 @@ Deploy order (important):
---
*Last updated: 2026-04-21. Owners: Targo Platform team
-(). See `ROADMAP.md` for upcoming Flow Editor v2 items
-(JSONLogic conditions, sub-flows, parallel branches, retry policies).*
+(). See [../roadmap.md](../roadmap.md) for upcoming Flow
+Editor v2 items (JSONLogic conditions, sub-flows, parallel branches,
+retry policies).*
diff --git a/docs/VISION_AND_OCR.md b/docs/features/vision-ocr.md
similarity index 98%
rename from docs/VISION_AND_OCR.md
rename to docs/features/vision-ocr.md
index d5f7057..23f9feb 100644
--- a/docs/VISION_AND_OCR.md
+++ b/docs/features/vision-ocr.md
@@ -323,11 +323,13 @@ DevTools, no key in the browser's `Network` tab.
## 9. Related
-- [ARCHITECTURE.md](ARCHITECTURE.md) — the full service map this lives in.
-- [CPE_MANAGEMENT.md](CPE_MANAGEMENT.md) — how scanned serials flow into
+- [../architecture/overview.md](../architecture/overview.md) — the full service map this lives in.
+- [cpe-management.md](cpe-management.md) — how scanned serials flow into
the TR-069/TR-369 device management plane.
-- [APP_DESIGN_GUIDELINES.md](APP_DESIGN_GUIDELINES.md) — frontend
+- [../architecture/app-design.md](../architecture/app-design.md) — frontend
conventions (Vue 3 Composition API, feature folders).
+- [../roadmap.md](../roadmap.md) — Phase 2.7 tracks the ongoing /j tech
+ unification this pipeline depends on.
---
diff --git a/docs/reference/README.md b/docs/reference/README.md
new file mode 100644
index 0000000..07cc5ae
--- /dev/null
+++ b/docs/reference/README.md
@@ -0,0 +1,11 @@
+# Reference
+
+Lookup material. These aren't narratives — open them when you need a specific
+fact.
+
+| Doc | Use when… |
+|---|---|
+| [erpnext-item-diff.md](erpnext-item-diff.md) | Auditing why a wizard SKU doesn't match a legacy `gestionclient.product` row. Lists every new / reused / semantic-reuse item with rationale |
+| [legacy-wizard/](legacy-wizard/) | You need to check what the old PHP wizard at `facturation.targo.ca/.../client_wizard` actually did for a given step. These are read-only snapshots — never edit |
+
+Back to [docs/README.md](../README.md) · [roadmap.md](../roadmap.md)
diff --git a/docs/ERPNEXT_ITEM_DIFF_VS_LEGACY.md b/docs/reference/erpnext-item-diff.md
similarity index 100%
rename from docs/ERPNEXT_ITEM_DIFF_VS_LEGACY.md
rename to docs/reference/erpnext-item-diff.md
diff --git a/docs/legacy-wizard/account_wizard.php b/docs/reference/legacy-wizard/account_wizard.php
similarity index 100%
rename from docs/legacy-wizard/account_wizard.php
rename to docs/reference/legacy-wizard/account_wizard.php
diff --git a/docs/legacy-wizard/account_wizard_ajax.php b/docs/reference/legacy-wizard/account_wizard_ajax.php
similarity index 100%
rename from docs/legacy-wizard/account_wizard_ajax.php
rename to docs/reference/legacy-wizard/account_wizard_ajax.php
diff --git a/docs/legacy-wizard/tele_wizard_package.php b/docs/reference/legacy-wizard/tele_wizard_package.php
similarity index 100%
rename from docs/legacy-wizard/tele_wizard_package.php
rename to docs/reference/legacy-wizard/tele_wizard_package.php
diff --git a/docs/legacy-wizard/tele_wizard_subs.php b/docs/reference/legacy-wizard/tele_wizard_subs.php
similarity index 100%
rename from docs/legacy-wizard/tele_wizard_subs.php
rename to docs/reference/legacy-wizard/tele_wizard_subs.php
diff --git a/docs/roadmap.md b/docs/roadmap.md
new file mode 100644
index 0000000..cdb355b
--- /dev/null
+++ b/docs/roadmap.md
@@ -0,0 +1,182 @@
+# Gigafibre FSM — Roadmap
+
+> Live phase tracker. For onboarding or architecture context see
+> [README.md](README.md). Historical status snapshots live under
+> [archive/status-snapshots/](archive/status-snapshots/).
+
+**Last refreshed:** 2026-04-22
+
+---
+
+## Modules in production — quick links
+
+Everything in this table is reachable from a browser today (Authentik
+SSO for staff surfaces, Stripe Checkout for customer ones).
+
+### Ops app — `https://erp.gigafibre.ca/ops/`
+
+| Module | URL | Purpose |
+|---|---|---|
+| Dashboard | [/ops/#/](https://erp.gigafibre.ca/ops/#/) | Home |
+| Clients | [/ops/#/clients](https://erp.gigafibre.ca/ops/#/clients) | Customer list + detail |
+| Dispatch | [/ops/#/dispatch](https://erp.gigafibre.ca/ops/#/dispatch) | Timeline, drag-drop, map |
+| Tickets | [/ops/#/tickets](https://erp.gigafibre.ca/ops/#/tickets) | Issue management |
+| Équipe | [/ops/#/equipe](https://erp.gigafibre.ca/ops/#/equipe) | Technician directory |
+| Rapports | [/ops/#/rapports](https://erp.gigafibre.ca/ops/#/rapports) | Revenu / Ventes / Taxes / AR |
+| OCR | [/ops/#/ocr](https://erp.gigafibre.ca/ops/#/ocr) | Invoice OCR (Gemini) |
+| Téléphonie | [/ops/#/telephony](https://erp.gigafibre.ca/ops/#/telephony) | PBX + SIP |
+| Agent flows | [/ops/#/agent-flows](https://erp.gigafibre.ca/ops/#/agent-flows) | Flow editor |
+| Réseau | [/ops/#/network](https://erp.gigafibre.ca/ops/#/network) | GenieACS / OLT / TR-069 |
+| Settings | [/ops/#/settings](https://erp.gigafibre.ca/ops/#/settings) | Config |
+
+### Tech mobile (same app, mobile layout) — `/j/*`
+
+| Page | URL | Purpose |
+|---|---|---|
+| Tasks | [/ops/#/j](https://erp.gigafibre.ca/ops/#/j) | Tech's assigned jobs |
+| Scan | [/ops/#/j/scan](https://erp.gigafibre.ca/ops/#/j/scan) | Camera scanner, auto-link |
+| Device | `/ops/#/j/device/:serial` | Equipment detail + relationships |
+| Diagnostic | [/ops/#/j/diagnostic](https://erp.gigafibre.ca/ops/#/j/diagnostic) | ONT/OLT/SNMP probe |
+
+### Customer portal — `https://portal.gigafibre.ca`
+
+| Page | URL | Purpose |
+|---|---|---|
+| Dashboard | [/](https://portal.gigafibre.ca) | Balance + quick actions |
+| Account | [/#/me](https://portal.gigafibre.ca/#/me) | Profile + cards (Stripe Billing Portal) |
+| Invoices | [/#/invoices](https://portal.gigafibre.ca/#/invoices) | History + pay (Stripe Checkout + Klarna) |
+| Tickets | [/#/tickets](https://portal.gigafibre.ca/#/tickets) | Open tickets |
+| Messages | [/#/messages](https://portal.gigafibre.ca/#/messages) | SMS / email thread with support |
+| Catalog | [/#/catalog](https://portal.gigafibre.ca/#/catalog) | Add-on purchases |
+
+### Admin surfaces (non-ops)
+
+| Service | URL | Stack |
+|---|---|---|
+| ERPNext | [erp.gigafibre.ca](https://erp.gigafibre.ca) | Frappe v16 / PostgreSQL |
+| Authentik SSO (staff) | [auth.targo.ca](https://auth.targo.ca) | migrating → id.gigafibre.ca |
+| Authentik SSO (clients) | [id.gigafibre.ca](https://id.gigafibre.ca) | Federated from auth.targo.ca |
+| DocuSeal | [sign.gigafibre.ca](https://sign.gigafibre.ca) | Contract signing |
+| n8n | [n8n.gigafibre.ca](https://n8n.gigafibre.ca) | Workflow automation |
+| Oktopus | [oss.gigafibre.ca](https://oss.gigafibre.ca) | TR-369 CPE controller |
+| GenieACS | internal `10.5.2.115:7557` | TR-069 NBI |
+| Traccar | [tracker.targointernet.com](https://tracker.targointernet.com) | GPS tracking |
+| Website | [www.gigafibre.ca](https://www.gigafibre.ca) | Marketing + address lookup |
+
+---
+
+## Phase 1 — Foundation (Done, March 2026)
+- [x] ERPNext v16 + PostgreSQL
+- [x] Custom FSM doctypes (Service Location, Equipment, Subscription) → see [architecture/data-model.md](architecture/data-model.md)
+- [x] Dispatch doctypes (Job, Technician, Tag with skill levels)
+- [x] Dispatch PWA with timeline, drag-drop, Mapbox map → [/ops/#/dispatch](https://erp.gigafibre.ca/ops/#/dispatch)
+- [x] GPS tracking (Traccar hybrid REST + WebSocket)
+- [x] Authentik SSO (forwardAuth)
+- [x] ERPNext API proxy (nginx same-origin)
+- [x] Legacy migration (6,667 customers, 21K subs, 115K invoices, 242K tickets)
+
+## Phase 2 — Ops App (Done, March 2026)
+- [x] Unified ops PWA → [erp.gigafibre.ca/ops/](https://erp.gigafibre.ca/ops/)
+- [x] Client list/detail with inline editing → [/ops/#/clients](https://erp.gigafibre.ca/ops/#/clients)
+- [x] Dispatch module + ticket management → [/ops/#/dispatch](https://erp.gigafibre.ca/ops/#/dispatch), [/ops/#/tickets](https://erp.gigafibre.ca/ops/#/tickets)
+- [x] Equipment tracking with OLT/SNMP diagnostics → [/ops/#/network](https://erp.gigafibre.ca/ops/#/network)
+- [x] SMS/Email notifications (Twilio + Mailjet)
+- [x] Invoice OCR — migrated from Ollama Vision to Gemini 2.5 Flash via targo-hub (2026-04-22, no GPU on ops VM). See [features/vision-ocr.md](features/vision-ocr.md) → [/ops/#/ocr](https://erp.gigafibre.ca/ops/#/ocr)
+- [x] Field tech mobile → [/ops/#/j](https://erp.gigafibre.ca/ops/#/j) (unified into ops app, see Phase 2.7)
+- [x] Authentik federation (staff → client SSO)
+- [x] Modem-bridge (Playwright headless for TP-Link ONU diagnostics)
+- [x] WiFi diagnostic panel (mesh topology, client signal, packet loss)
+
+## Phase 2.5 — Remote Architecture Transition (In Progress)
+- [x] Deprecate local `frappe_docker` development dependencies
+- [x] Consolidate architecture and ecosystem documentation (this reorg, 2026-04-22)
+- [ ] Decouple API/Auth (Token-based auth instead of session for frontend apps)
+- [ ] Set up dev proxy (Vite) to bridge local env to remote ERPNext API (bypassing CORS)
+- [ ] Establish secure PostgreSQL tunnel for `infra-map-vue` development
+- [ ] **Sandboxed outbound comms** (required before any scheduler/webhook/Twilio/Mailjet E2E test) — prevents test runs from reaching real customers while legacy still bills
+- [ ] Subscription → Sales Invoice scheduler: keep `pause_scheduler=1` until cutover event. Legacy PHP is authoritative until then.
+
+## Phase 2.6 — Quotation + DocuSeal (Shipped 2026-04-18)
+- [x] DocuSeal container → [sign.gigafibre.ca](https://sign.gigafibre.ca)
+- [x] Hub routes: `/accept/generate`, `/accept/docuseal-webhook`, `/accept/confirm`
+- [x] Quotation custom fields: `custom_docuseal_signing_url`, `custom_docuseal_envelope_id`, `custom_quote_type`
+- [x] Billing Frequency Custom Field on Item + Quotation/Sales Invoice/Sales Order Item (fetch_from item_code)
+- [x] Print Format "Soumission TARGO" with split Recurring / One-time sections and QR → signing URL
+- [x] Wizard flow: ProjectWizard → `/accept/generate` → DocuSeal submission → signed webhook → `acceptQuotation()`
+- [ ] Register DocuSeal webhook in UI (Settings → Webhooks, `form.completed` → hub endpoint) — **manual**
+- [ ] First end-to-end signed acceptance on a real customer quote
+
+## Phase 2.7 — Field ↔ Ops unification at /j (In Progress, started 2026-04-22)
+
+Collapse `apps/field` into `apps/ops/src/modules/tech` so there is one
+PWA, one deploy, one auth surface. See [features/vision-ocr.md](features/vision-ocr.md)
+for the scan pipeline this depends on.
+
+**Step 1 — scan + device (Shipped 2026-04-22, commit `e50ea88`)**
+- [x] Invoice OCR on Gemini 2.5 Flash via hub `/vision/invoice` — ops VM no longer needs a GPU
+- [x] Ollama proxy blocks removed from ops + field nginx configs
+- [x] Offline store (`apps/ops/src/stores/offline.js`) — mutation queue + vision queue, time-driven retries, idb-keyval persistence
+- [x] Unified scanner composable (`useScanner.js`) with Mode A (barcodes, 8s timeout + queue) and Mode B (equipment label, sync)
+- [x] TechScanPage → [/ops/#/j/scan](https://erp.gigafibre.ca/ops/#/j/scan) — camera, 3-tier lookup (serial → barcode → MAC), auto-link to Dispatch Job context, create/link dialogs
+- [x] TechDevicePage at `/j/device/:serial` — 7 cards surfacing full ERPNext relationship graph (Equipment, Customer, Location, Subscription, Issues, Dispatch Jobs, OLT)
+- [x] Documentation: [features/vision-ocr.md](features/vision-ocr.md) (pipeline, §10 relationship graph, §8.1 secrets/rotation)
+
+**Step 2 — PWA hardening**
+- [ ] Quasar service worker runtime caching scoped to `/j/*` (stale-while-revalidate for reads, network-first for mutations)
+- [ ] Precache the tech route manifest so a cold install with no signal still boots `/j/`
+
+**Step 3 — Auth unification**
+- [ ] Collapse logout URL to `id.gigafibre.ca` (currently ops points to `auth.targo.ca`)
+- [ ] Decide whether `/j/*` stays behind Authentik forwardAuth or moves to magic-link only
+
+**Step 4 — Magic-link tech access**
+- [ ] Traefik skip Authentik on `/j/{jwt-token}` route
+- [ ] targo-hub `/otp/tech-link` — mint short-lived JWT bound to technician + job
+- [ ] JWT validation in TechTasksPage → populate tech context without an SSO session
+- [ ] SMS delivery of the link (reuse existing Twilio path)
+
+**Step 5 — Flow runtime integration**
+- [ ] Wire `flow-runtime` to persist pending steps through `offline.queue` so a tech mid-flow survives a dead zone
+- [ ] Surface queued flow state in TechTasksPage ("3 actions en attente de sync")
+
+**Step 6 — Remove apps/field**
+- [ ] `git rm -r apps/field` once `/j/*` has parity and has run in production for ≥2 weeks
+- [ ] Remove field build + deploy from CI
+- [ ] Redirect `*.field.gigafibre.ca` (if any) → `erp.gigafibre.ca/ops/#/j/`
+- [ ] Update [architecture/overview.md](architecture/overview.md) service table (drop field row)
+
+## Phase 3 — Workflows & Automation (In Progress)
+- [ ] Tag technicians with skills (46 techs to tag) → [/ops/#/equipe](https://erp.gigafibre.ca/ops/#/equipe)
+- [ ] Wire auto-dispatch (cost-optimization matching)
+- [ ] Issue → Dispatch Job creation (triggered from [/ops/#/tickets](https://erp.gigafibre.ca/ops/#/tickets))
+- [ ] Job completion → equipment status + close ticket
+- [ ] Equipment swap → inventory log
+- [x] Flow editor (v1 shipped) → [/ops/#/agent-flows](https://erp.gigafibre.ca/ops/#/agent-flows) — see [features/flow-editor.md](features/flow-editor.md)
+- [ ] n8n escalation workflows → [n8n.gigafibre.ca](https://n8n.gigafibre.ca)
+- [ ] Twilio 10DLC production upgrade
+- [ ] SLA tracking
+
+## Phase 4 — Customer Portal (Largely Shipped)
+
+Portal Vue SPA lives at [portal.gigafibre.ca](https://portal.gigafibre.ca).
+All 16 `/payments/*` hub endpoints ship; see
+[features/billing-payments.md](features/billing-payments.md) for the full flow.
+
+- [x] Self-service app → [portal.gigafibre.ca](https://portal.gigafibre.ca) (13 pages: Dashboard, Invoices, Account, Tickets, Messages, Catalog, Cart, …)
+- [x] Stripe payments → pay-balance, pay-invoice, save-card, Billing Portal, refund, PPA auto-pay cron (daily 06:00 EST). Klarna BNPL supported on invoice payments.
+- [x] Webhook handler with signature verification (5-min tolerance) → `/webhook/stripe`
+- [x] Payment links via SMS + email (`/payments/send-link`)
+- [x] Magic-link redirect after Stripe return (`/payments/return` → portal)
+- [ ] Online appointment booking
+- [ ] Real-time tech tracking SMS
+- [ ] Legacy password migration (MD5 → PBKDF2) — MD5-hashed passwords in legacy need forced reset via OTP email/SMS
+- [ ] QR code on modem → subscriber dashboard (`msg.gigafibre.ca/q/{mac}`)
+
+## Phase 5 — Advanced Features
+- [ ] Van stock inventory per tech
+- [ ] Revenue analytics (MRR, churn, ARPU) → extend [/ops/#/rapports](https://erp.gigafibre.ca/ops/#/rapports)
+- [ ] Proactive monitoring (auto-ticketing)
+- [ ] Online checkout (e-commerce signup via [www.gigafibre.ca](https://www.gigafibre.ca))
+- [ ] Marketing segmentation + campaigns
+- [ ] Tech performance dashboards
+- [ ] Preventive maintenance scheduling
diff --git a/services/targo-hub/lib/flow-runtime.js b/services/targo-hub/lib/flow-runtime.js
index 658e7c9..f336af8 100644
--- a/services/targo-hub/lib/flow-runtime.js
+++ b/services/targo-hub/lib/flow-runtime.js
@@ -26,7 +26,7 @@
* { [stepId]: { status: 'pending|running|done|failed|scheduled|skipped',
* started_at, completed_at, result, error, retry_count } }
*
- * See docs/FLOW_EDITOR_ARCHITECTURE.md for full data model + trigger wiring.
+ * See docs/features/flow-editor.md for full data model + trigger wiring.
*/
const { log, erpFetch } = require('./helpers')