Drag-and-drop and keyboard swapping (Feature 14) #120

Member

Task

Maestro task 5c2842a8-5ac1-4141-9575-c0f461d90c3e — "Drag-and-drop and keyboard swapping" (Feature 14, Weekly Plan Builder). Last implementation task in the feature.

Done-condition

On tablet/desktop, dinners can be dragged (pointer events) from the desktop right panel or the tablet chip strip onto a day: the source row becomes a dashed ghost at 55% opacity, the dragged card floats with shadow-lg rotated −3°, the hovered day shows a dashed 2.5 px grape border on grape-100 with "Drop to swap / Replaces {current dinner}", and dropping calls the swap API optimistically with rollback on error; dropping outside a day cancels. Keyboard alternative: focus a panel row, press Enter/Space to pick it up, arrow keys to choose a day, Enter to drop, Escape to cancel, with live-region announcements. Vitest tests cover pointer drop, cancel, rollback and the full keyboard flow. Typecheck, test and build pass.

Scope

Tablet/desktop only (≥ 768 px); mobile is unchanged. No new dependency, no HTML5 drag-and-drop, and no second swap path: drops go through WeekBoard.dropIn → the existing useSwapDay mutate (which owns the optimistic update, rollback and the one-swap-per-week guard). No backend, migration, skill or API-contract change.

Notes for review

  • New web/src/plan/drag.tsx: a page-level provider, DropBoard (the grid registers itself only while shown, so there are no targets while a draft runs or no plan exists), useDropTarget, useDraggable (pointer events with capture and a 4 px threshold, plus the keyboard handling) and a polite live region. The panel/strip sit outside the per-week board, hence the provider.
  • One existing assertion changed: the 6-dot grip is now read inside the row's button.
  • Two additions beyond the literal spec: a dinner already this week cannot be picked up (matching the swap sheet, with the reason announced), and moving focus off a keyboard drag cancels it.
  • Space picks up but only Enter drops, as the done-condition states. The live region is aria-live="polite" rather than role="status" so it does not collide with the app's existing status queries in tests.
  • The stylesheet declares the 2.5 px dashed border; headless Chrome reports 2 px at 1× and 2× because it rounds border widths.

Evidence (orchestrator-run pre-review gate at 3d6c1109c8)

  • gofmt -l . — empty (no Go file changed)
  • cd web && npm run typecheck — PASS
  • cd web && npm test — PASS, 45 files / 759 tests (+12), colour-literal lint clean
  • cd web && npm run build — PASS (includes lint:colors and check:dist-fonts)

Developer-run headless-Chrome (Playwright) verification against the Vite dev server with a mocked API at 1180 and 1440 px: pointer drop (one ghost at 0.55 opacity, floating copy at −3° with the large shadow, "Drop to swap / Replaces Pizza night" on grape-100, exactly one PUT on release, dinner removed from the Quest log); cancel over the header (no PUT, announced); rollback on a 409 (day restored with the error and Retry, rows aria-disabled while pending); and the full keyboard flow (pick up, arrow to Wednesday, Escape cancel keeping focus, Space + arrows + Enter dropping and moving focus to that day's swap button). Screenshots under /tmp/opencode/f14t7/, outside the repository.

Implementation commit: 3d6c1109c812fa468e2a2ea39ac39da591ba1b60

## Task Maestro task `5c2842a8-5ac1-4141-9575-c0f461d90c3e` — "Drag-and-drop and keyboard swapping" (Feature 14, Weekly Plan Builder). Last implementation task in the feature. ## Done-condition On tablet/desktop, dinners can be dragged (pointer events) from the desktop right panel or the tablet chip strip onto a day: the source row becomes a dashed ghost at 55% opacity, the dragged card floats with shadow-lg rotated −3°, the hovered day shows a dashed 2.5 px grape border on grape-100 with "Drop to swap / Replaces {current dinner}", and dropping calls the swap API optimistically with rollback on error; dropping outside a day cancels. Keyboard alternative: focus a panel row, press Enter/Space to pick it up, arrow keys to choose a day, Enter to drop, Escape to cancel, with live-region announcements. Vitest tests cover pointer drop, cancel, rollback and the full keyboard flow. Typecheck, test and build pass. ## Scope Tablet/desktop only (≥ 768 px); mobile is unchanged. No new dependency, no HTML5 drag-and-drop, and no second swap path: drops go through `WeekBoard.dropIn` → the existing `useSwapDay` mutate (which owns the optimistic update, rollback and the one-swap-per-week guard). No backend, migration, skill or API-contract change. ## Notes for review - New `web/src/plan/drag.tsx`: a page-level provider, `DropBoard` (the grid registers itself only while shown, so there are no targets while a draft runs or no plan exists), `useDropTarget`, `useDraggable` (pointer events with capture and a 4 px threshold, plus the keyboard handling) and a polite live region. The panel/strip sit outside the per-week board, hence the provider. - One existing assertion changed: the 6-dot grip is now read inside the row's button. - Two additions beyond the literal spec: a dinner already this week cannot be picked up (matching the swap sheet, with the reason announced), and moving focus off a keyboard drag cancels it. - Space picks up but only Enter drops, as the done-condition states. The live region is `aria-live="polite"` rather than `role="status"` so it does not collide with the app's existing `status` queries in tests. - The stylesheet declares the 2.5 px dashed border; headless Chrome reports 2 px at 1× and 2× because it rounds border widths. ## Evidence (orchestrator-run pre-review gate at 3d6c1109c812fa468e2a2ea39ac39da591ba1b60) - `gofmt -l .` — empty (no Go file changed) - `cd web && npm run typecheck` — PASS - `cd web && npm test` — PASS, 45 files / 759 tests (+12), colour-literal lint clean - `cd web && npm run build` — PASS (includes `lint:colors` and `check:dist-fonts`) Developer-run headless-Chrome (Playwright) verification against the Vite dev server with a mocked API at 1180 and 1440 px: pointer drop (one ghost at 0.55 opacity, floating copy at −3° with the large shadow, "Drop to swap / Replaces Pizza night" on grape-100, exactly one PUT on release, dinner removed from the Quest log); cancel over the header (no PUT, announced); rollback on a 409 (day restored with the error and Retry, rows aria-disabled while pending); and the full keyboard flow (pick up, arrow to Wednesday, Escape cancel keeping focus, Space + arrows + Enter dropping and moving focus to that day's swap button). Screenshots under `/tmp/opencode/f14t7/`, outside the repository. Implementation commit: `3d6c1109c812fa468e2a2ea39ac39da591ba1b60`
orchestrator-agent added 1 commit 2026-10-07 15:11:39 -04:00
Drag dinners onto days, by pointer or keyboard (Feature 14)
CI / ci (push) Successful in 3m26s
CI / ci (pull_request) Successful in 3m19s
3d6c1109c8
From tablet up, a Quest-log or Regular dinner is dragged from desktop's
right panel or tablet's Quest-log strip onto a day of the week to swap it
in. Pointer events with capture: the source becomes a dashed ghost at 55%
opacity, a copy floats with shadow-lg at -3deg, and the day under the
pointer shows a dashed 2.5 px grape border on grape-100 with "Drop to swap /
Replaces {dinner}". Letting go over a day calls useSwapDay's mutate, so the
swap is optimistic and rolls back with the existing error; anywhere else, a
pointer cancel or Escape cancels with no request.

Keyboard alternative: Enter or Space picks a row up onto Monday, the arrow
keys choose the day, Enter drops (moving focus to that day's swap button),
Escape or leaving the row cancels. Each step is announced in a polite live
region. No drag starts while a swap of the week is pending, nor for a
dinner already this week, matching the swap sheet. Mobile is unchanged.

Task: 5c2842a8-5ac1-4141-9575-c0f461d90c3e

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
review_agent approved these changes 2026-10-07 15:15:00 -04:00
Dismissed
review_agent left a comment
Member

Independent Codex gpt-6-sol review: APPROVED for this exact commit.

Independent Codex gpt-6-sol review: APPROVED for this exact commit.
overseer added 1 commit 2026-10-07 15:27:52 -04:00
Offset the dragged copy below right of the pointer (Feature 14)
CI / ci (push) Successful in 3m30s
CI / ci (pull_request) Successful in 3m20s
621fdef42b
The floating copy sat where it was grabbed, over the hovered day's
"Drop to swap / Replaces ..." text. It now floats 48 px below and to
the right of the pointer, flipping left or up where it would leave the
viewport, so the day's current dinner stays readable during a drag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
overseer added 1 commit 2026-10-07 15:28:03 -04:00
Reflow the floating copy offset comment
CI / ci (push) Successful in 3m16s
CI / ci (pull_request) Successful in 3m22s
9e0c20ab5e
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
review_agent left a comment
Member

Independent Codex gpt-6-sol review: APPROVED for this exact commit.

Independent Codex gpt-6-sol review: APPROVED for this exact commit.
approver_agent merged commit 955613941f into feature/ca2a118d-0beb-49f9-b2ef-d5ed0ac1fa2a-weekly-plan-builder 2026-10-07 15:45:12 -04:00
Sign in to join this conversation.
No Reviewers
No labels
3 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Maniacs_Mansion/MealQuest#120