Implementation Plan: Public Grid Viewer + Live Selection Branch: `001-public-grid-viewer` | Date: 2026-01-03 | Spec: specs/001-public-grid-viewer/spec.md Summary Deliver a vertical slice that proves the core UI/value: a public page that loads the master image, supports pan/zoom, a performant canvas-based grid, rectangle selection, a sidebar showing cell count and server-provided `price_per_cell`, and a modal for client-side upload preview. No checkout, orders, or rendering Jobs are created in this slice. Technical Context Language/Version: PHP ^8.2 (Laravel 12) Primary Dependencies: laravel/framework ^12, inertiajs/inertia-laravel ^2, vue 3, @inertiajs/vue3 Storage: Laravel Storage (local for dev, S3-ready via Storage driver) Testing: Pest (pestphp/pest) Target Platform: Web (Linux production), local dev via Sail / macOS Project Type: Single Laravel web application (Inertia SPA) Performance Goals: Public page loads master image + metadata within ~2000ms on 4G for MVP Constraints: Avoid DOM explosion — use canvas/Konva for grid rendering Constitution Check (REQUIRED) Impact area(s): ☑ Public UX ☐ Payments ☐ Locking/Reservations ☐ Rendering/Compositing ☐ Upload/Security Compliance Checklist - [x] Inertia-first: no API-only split — UI served via Inertia page - [x] Server is source of truth for `price_per_cell` (endpoint provides value) - [ ] No double booking enforced at DB level: DEVIATION for this slice — reservations are out-of-scope; availability API is a stub. DB locking will be implemented in a follow-up feature. (Mitigation: UI shows availability overlay only; spec documents DB requirements.) - [x] Webhook + job flow prepared but out-of-scope for this slice (idempotency planned in later features) - [x] Image compositing will run in queued jobs in later features (not done in this slice) - [x] Cache busting: server returns `master_version` in metadata for image URL `?v=` token - [x] Upload validation rules: modal is client-only preview in this slice; server-side validation planned later Decision log: - Deviation: DB-level prevention of double booking is deferred. - Rationale: vertical slice focuses on UI and public performance; locking requires order lifecycle and payment flow. - Mitigation: availability endpoint will be implemented as a stub returning occupied cells later; tasks call out DB-level locking for follow-up. Project Structure (selected) Use the existing Laravel app layout. New/modified files for this feature: - Backend - `routes/web.php` — add route for public grid page - `app/Http/Controllers/PublicGridController.php` — serves Inertia page and endpoints - `config/pixel_grid.php` — `cell_size`, `price_per_cell`, `master_path` - `database/migrations/*_create_master_images_table.php` (optional seed for master image) - `app/Models/MasterImage.php` (lightweight model) - `routes/api.php` (or small GET endpoints under web with `->name('api.*')`): - `GET /api/grid/meta` → returns `master_image_url`, `master_version`, `cell_size` - `GET /api/grid/price` → returns `price_per_cell` - `GET /api/grid/availability` → returns list of occupied `{cell_x,cell_y}` (MVP: empty) - Frontend - `resources/js/Pages/PublicGrid/Index.vue` (Inertia page) - `resources/js/components/GridCanvas.vue` (Konva/canvas wrapper + hit-testing) - `resources/js/components/SelectionSidebar.vue` (cell count, price, CTA) - `resources/js/components/UploadModal.vue` (client preview only) - Tests - `tests/Feature/PublicGridMetaTest.php` (meta endpoints) - `tests/Feature/PriceCalculationTest.php` Structure Decision: Keep everything inside the single Laravel app to use Inertia and Wayfinder where helpful; frontend code goes into `resources/js/Pages` and `resources/js/components` per project conventions. Phase Plan & Tasks Phase 1 — Setup (1-2 days) - T001 Add `config/pixel_grid.php` with `cell_size` and `price_per_cell`. - T002 Add sample master image to `storage/app/public/master/master.png` and a migration/seed to populate `master_images` if desired. - T003 Add routes and `PublicGridController` skeleton. Phase 2 — Foundational API (1 day) - T010 Implement `GET /api/grid/meta` returning `{ master_image_url, master_version, cell_size }`. - T011 Implement `GET /api/grid/price` returning `{ price_per_cell }`. - T012 Implement `GET /api/grid/availability` returning empty set (stub) and wire rate-limiting middleware. - T013 Add Pest feature tests for these endpoints (`PublicGridMetaTest`, `PriceCalculationTest`). Phase 3 — Frontend Vertical Slice (3-5 days) - T020 Create Inertia page `PublicGrid/Index.vue` and route. - T021 Implement `GridCanvas.vue` using Konva or native canvas for grid overlay, pan/zoom, and rectangle selection. Ensure hit-testing uses cell units. - T022 Implement `SelectionSidebar.vue` that queries `/api/grid/price` and computes `cell_count × price_per_cell` for display. - T023 Implement `UploadModal.vue` for client-side image selection and preview composited into selection rectangle. - T024 Wire mobile touch handlers (pinch to zoom, drag to pan, drag to select) and test on iPhone viewport. Phase 4 — Tests & QA (1-2 days) - T030 Add feature tests verifying price endpoint, meta endpoint, and selection cell math (server-provided `cell_size`). - T031 Manual QA checklist for mobile pinch/drag and selection precision. Phase 5 — Polish & Docs (1 day) - T040 Add `specs/001-public-grid-viewer/quickstart.md` with local dev steps. - T041 Add a short `/docs/decisions/001-public-grid-viewer.md` noting the canvas choice and DB-locking deferral. Complexity Tracking No constitution violations required for this slice except the deliberate deferral of DB-level locking. This is documented above and in the spec. Deliverables - Inertia page: `resources/js/Pages/PublicGrid/Index.vue` - Canvas component: `resources/js/components/GridCanvas.vue` - API endpoints: `GET /api/grid/meta`, `/api/grid/price`, `/api/grid/availability` - Tests: `tests/Feature/PublicGridMetaTest.php`, `tests/Feature/PriceCalculationTest.php` - Spec updates: `specs/001-public-grid-viewer/spec.md` (done) Next Steps (how I can help) - I can implement Phase 1+2 now: add config, controller and the three endpoints plus tests. - Or I can scaffold the frontend Inertia page and the `GridCanvas.vue` component next. Local run commands to verify: ```bash composer install cp .env.example .env php artisan key:generate php artisan migrate --force npm install npm run dev php artisan serve ``` Estimated total: 6–10 developer days (split: backend 2 days, frontend 4–7 days, QA 1 day). Adjust based on review/iterations. ***