130 lines
6.5 KiB
Markdown
130 lines
6.5 KiB
Markdown
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.
|
||
|
||
***
|
||
|