The admin layout guard rendered only a "Redirecting to login..." placeholder for the /admin/login child route, trapping every unauthenticated visitor. Exempt the login route from the auth gate so the form renders correctly. Also wire the new POST /api/auth/refresh endpoint (from the dual-token migration) into both auth.init() and the api request() 401 handler, so sessions survive the 15-minute access-token lifetime without a hard logout. Adds a Playwright regression test asserting the login form is visible in a clean (no-cookie) browser context.
6.2 KiB
Implementation Plan: Room Editor Refactor (Core & Logic)
Objective: Fix the pixel vs. grid-unit mismatch in stored room data, and robustify the editor for professional room planning.
Background:
The editor (RoomCanvas.svelte) already stores and renders in grid units (1 unit = 40 px). However, the demo seed (demo_seed.sql) was written with raw pixel values (e.g. width: 200), causing demo Room A to render broken (200 grid units = 8000 px). Any room created via the editor since launch is correct; any room predating the editor's grid-unit switch (or the demo room) is broken. A one-time data migration is the first priority.
Note on the type/kind field: backend/src/models.rs:81 already bridges this with #[serde(rename = "type")] pub kind: String. The wire format is type and frontend/src/lib/types.ts:33 already uses type. No rename is needed. If the Rust internal name is ever changed to type, a raw identifier (r#type) is required since type is reserved.
Note on backend validation: backend/src/routes/rooms.rs:18–69 already implements validate_layout with empty-layout check, unique IDs, allowed types (seat, table, gap, door), unique seat labels, and non-negative geometry. Tests at lines 184–322 cover all of it. Task 2 below replaces the previously planned duplicate work.
Note on SeatMap.svelte: This plan does not touch SeatMap.svelte. Its retirement and replacement with a dynamic renderer is handled by the sibling visualization plan. Any LayoutElement contract change made here must be cross-checked against backend/src/routes/checkin.rs:53,194 (which deserialises it) and frontend/src/lib/types.ts:86 (CheckinInfo.layout).
1. Data Migration & Seed Fix
Task 1: Pixel → Grid-Unit Migration
Files to Modify:
backend/migrations/003_normalize_room_layout_units.sql(create)backend/demo/demo_seed.sqlbackend/src/routes/rooms.rs(update tests that assert large numeric coordinates)
Changes:
- Write
003_normalize_room_layout_units.sql. For each row inrooms, parselayout_json; if any element hasx,y,width, orheight> 50, divide all four by 40 and update the row. This heuristic is safe because grid-unit values are small integers/half-steps (max ~30), while pixel values are large (typically 80–800). - Update
demo_seed.sql:16–41to use grid units (e.g.width: 200→width: 5). The 24 elements in demo Room A need to be re-measured in grid units. - Update any integration tests in
rooms.rsthat rely on large pixel-scale layout values.
Task 2: Backend Validation (Scope Reduction)
Files to Modify:
backend/src/routes/rooms.rs
Changes (additive only — do not duplicate existing logic):
- Add upper-bound validation:
xandymust be < aMAX_CANVASconstant (e.g. 100 grid units). Reject elements that fall off the canvas. - Add grid-step validation:
x,y,width,heightmust be multiples of 0.5 (i.e.(value * 2) % 1 == 0). Apply post-migration so existing data has already been normalised. - Add a test for each new validator.
2. Editor Core Refactor
Task 3: RoomCanvas State & Behaviour
Files to Modify:
frontend/src/lib/RoomCanvas.svelte
Current state (188 lines):
- Drag:
draggingId / startX / startYonly (lines 26–28). No resize state or handles exist. - Snap: lines 47–48 snap to 0.25 grid units (
Math.round(.../10)*10/40). This is partially correct but the increment should be configurable (0.5 default). - Rendering: already multiplies by
GRID_SIZE = 40(line 85). Unit separation is mostly correct. - Bug:
onmousemove/onmouseupare bound on the SVG only (lines 69–71). Releasing the cursor outside the SVG strands the drag. Move these listeners towindowfor the duration of a drag.
Changes:
- Build resize from scratch. Add resize handles (e.g. bottom-right corner hit area) per element. Track
resizingId,resizeStartX,resizeStartY,resizeStartW,resizeStartHas drag state. Snap resize delta to 0.5 increments. - Fix drag escape. Bind
mousemove/mouseuptowindowwhen dragging begins; remove them on drop. - Snap increment. Change snap to 0.5 grid units (from 0.25). Accept an optional
snapStepprop (default0.5) for the snap-toggle feature below.
Task 4: Editor UI Improvements
Files to Modify:
frontend/src/routes/admin/rooms/[roomId]/+page.svelte
What already exists (do not re-add):
- Width/height inputs with
step="0.5"(lines 90–97) - Label input (line 87)
- Add seat/table/door buttons (lines 64–66)
- Delete button (line 101)
What to add:
- X/Y numeric inputs (with
step="0.5") for precise coordinate editing of the selected element, bound to itsxandyfields. - "+ Gap" button alongside the existing add buttons.
gapis accepted byvalidate_layoutbut is currently unreachable from the UI. - "Snap to Grid" toggle. Bind to a boolean state; pass as
snapStep={snapEnabled ? 0.5 : 0}toRoomCanvas. - "Duplicate element" button. Copies the selected element with a new UUID and offsets it by 1 grid unit.
- Surface save errors.
saveLayout(lines 27–29) currently onlyconsole.error. Display an inline error message in the UI.
3. Verification
Automated Tests:
backend/src/routes/rooms.rs: Tests for the new upper-bound and grid-step validators.backend/migrations/: Verify migration 003 runs cleanly on the test DB (usesqlx migrate run).frontend/tests/rooms.spec.ts(new): Playwright test — create a room, add table and two seats via the UI, drag a seat (verify snap), save and reload, assert coordinates are preserved.
Manual Verification:
make seed-demo— reseed with the fixeddemo_seed.sql.- Open
Admin → Rooms → Room Ain the editor. All elements must appear at sensible grid positions (not far off-screen). - Drag an element: verify it snaps to 0.5-unit increments.
- Resize an element: verify handles appear and snap correctly.
- Add a Gap element and verify it can be placed and saved.
- Inspect the SQLite DB directly:
SELECT layout_json FROM rooms LIMIT 1. All element coordinates must be small numbers (≤ 30), not pixel values (≥ 80). - Save and reload: verify coordinates are exactly preserved (no rounding drift).