6.5 KiB
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
- Inertia-first: no API-only split — UI served via Inertia page
- 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.)
- Webhook + job flow prepared but out-of-scope for this slice (idempotency planned in later features)
- Image compositing will run in queued jobs in later features (not done in this slice)
- Cache busting: server returns
master_versionin metadata for image URL?v=token - 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 pageapp/Http/Controllers/PublicGridController.php— serves Inertia page and endpointsconfig/pixel_grid.php—cell_size,price_per_cell,master_pathdatabase/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→ returnsmaster_image_url,master_version,cell_sizeGET /api/grid/price→ returnsprice_per_cellGET /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.phpwithcell_sizeandprice_per_cell. - T002 Add sample master image to
storage/app/public/master/master.pngand a migration/seed to populatemaster_imagesif desired. - T003 Add routes and
PublicGridControllerskeleton.
Phase 2 — Foundational API (1 day)
- T010 Implement
GET /api/grid/metareturning{ master_image_url, master_version, cell_size }. - T011 Implement
GET /api/grid/pricereturning{ price_per_cell }. - T012 Implement
GET /api/grid/availabilityreturning 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.vueand route. - T021 Implement
GridCanvas.vueusing Konva or native canvas for grid overlay, pan/zoom, and rectangle selection. Ensure hit-testing uses cell units. - T022 Implement
SelectionSidebar.vuethat queries/api/grid/priceand computescell_count × price_per_cellfor display. - T023 Implement
UploadModal.vuefor 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.mdwith local dev steps. - T041 Add a short
/docs/decisions/001-public-grid-viewer.mdnoting 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.vuecomponent next.
Local run commands to verify:
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.