From 94dae685e2d8df2500b0847a7bacf76e6d86e096 Mon Sep 17 00:00:00 2001 From: Matt Edholm Date: Mon, 27 Apr 2026 21:57:06 -0400 Subject: [PATCH] docs: update architecture to reflect Vue 3 SPA frontend decision Replace AssetMapper + Stimulus + Turbo with Vue 3 SPA (Vite, TypeScript strict, SCSS modules, Konva.js). Authenticated app is now a full SPA served by Symfony catch-all; public flows (provisioning, email approve/decline) remain Symfony + Twig. Add JSON API controllers for SPA, SpaController catch-all, updated directory structure, and revised implementation sequence. Co-Authored-By: Claude Sonnet 4.6 --- .../planning-artifacts/architecture.md | 177 +++++++++++------- 1 file changed, 110 insertions(+), 67 deletions(-) diff --git a/_bmad-output/planning-artifacts/architecture.md b/_bmad-output/planning-artifacts/architecture.md index 7b61d3f..9bd9d09 100644 --- a/_bmad-output/planning-artifacts/architecture.md +++ b/_bmad-output/planning-artifacts/architecture.md @@ -72,7 +72,7 @@ symfony new pictureframe --webapp **Language & Runtime:** PHP 8.4+, Symfony 8.0 -**Templating:** Twig with Stimulus and UX Turbo (Hotwire) — lightweight interactivity without a JS build step +**Templating:** Twig — used only for public flows (provisioning setup, email approve/decline pages). Authenticated app is a Vue 3 SPA; Twig is not used there. **ORM & Database:** Doctrine ORM, PostgreSQL 16 (DDEV local), migrations via `doctrine/migrations` @@ -80,17 +80,19 @@ symfony new pictureframe --webapp **Email:** Symfony Mailer — handles sharing flows and hard-delete confirmations -**Build Tooling:** AssetMapper (no Webpack/Node build step required) +**Build Tooling:** Vite (Vue SPA, outputs to `public/build/`). AssetMapper not used — no Stimulus, no Turbo, no `importmap.php`. **Testing:** PHPUnit via `symfony/test-pack` -**Code Organization:** Standard Symfony structure — `src/Entity`, `src/Controller`, `src/Repository`, `src/Service`, `templates/` +**Code Organization:** Standard Symfony structure — `src/Entity`, `src/Controller`, `src/Repository`, `src/Service`, `templates/` (public flows only) + `frontend/` (Vue SPA source) **Additional packages required:** - `symfony/messenger` + Doctrine transport — async image processing worker - `symfony/scheduler` — rotation engine and scheduled cleanup - Image processing library (TBD step 4: GD vs Imagick) +**Post-scaffold cleanup:** After `symfony new pictureframe --webapp`, remove `symfony/stimulus-bundle`, `symfony/ux-turbo`, and AssetMapper. Initialize the Vue SPA in `frontend/` with `npm create vite@latest frontend -- --template vue-ts`. + **Local Dev:** DDEV configured to mirror aqua-iq (PHP 8.4, Nginx-FPM, PostgreSQL 16) **Note:** Project initialization using this command is the first implementation story. @@ -141,7 +143,9 @@ symfony new pictureframe --webapp **Device API:** Single endpoint — `GET /api/device/{mac}/image` — returns raw binary (`application/octet-stream`). This is the only machine-to-machine API surface. No versioning scheme beyond URL stability guarantee (no breaking changes in V1). -**Web Controllers:** Standard Symfony controllers returning Twig responses. No JSON API for the web application. +**Web Controllers (authenticated app):** Symfony controllers return JSON responses for all authenticated app API calls under the `/api/` prefix. No Twig rendering for authenticated routes — Symfony serves the SPA shell only. Controllers use `JsonResponse` or Symfony Serializer. + +**Web Controllers (public flows):** `/setup/{mac}`, `/token/{uuid}/approve`, `/token/{uuid}/decline` return Twig responses. These are the only controllers that render HTML directly. **Email:** Symfony Mailer. Transactional emails: image share notification (with approve link), hard-delete confirmation. Authorization links embedded as tokenized URLs pointing to Symfony routes. @@ -153,13 +157,26 @@ symfony new pictureframe --webapp ### Frontend Architecture -**Templating:** Twig. Identical pattern to aqua-iq. +**Authenticated App — Vue 3 SPA:** +All authenticated routes are served by a Vue 3 SPA built with Vite + TypeScript strict mode. Vue Router handles client-side navigation; Pinia manages shared state (current user, device list, upload funnel state). SCSS modules scoped per SFC for component styles. Konva.js + Vue-Konva for the sticker canvas editor. No Stimulus, no Turbo, no AssetMapper. -**Interactivity:** Stimulus controllers + Turbo Drive (Hotwire). No SPA, no build step required. +Symfony serves the SPA shell (`public/build/index.html`) via a catch-all route for all authenticated paths. Vue Router takes over client-side navigation from that point. -**Forms:** Symfony Form component. +**Public Flows — Symfony + Twig:** +Three routes remain as Symfony Twig pages with no Vue dependency: +- `/setup/{mac}` — device provisioning setup (post-QR scan) +- `/token/{uuid}/approve` — email approve page (no login required) +- `/token/{uuid}/decline` — email decline page (no login required) -**Assets:** AssetMapper (no Webpack/Node). +These must work with images disabled, CSS disabled, and screen reader only. + +**TypeScript:** Strict mode. `frontend/src/types/` holds interfaces mirroring every Symfony API response shape: `Device`, `Image`, `StickerLayer`, `RenderedAsset`, `Token`. The compiler surfaces API contract drift before it reaches deployed devices. + +**SCSS:** Modules scoped per SFC (`