- useClearObject: Promise.allSettled + invalidate in onSettled (not onSuccess),
so a partial failure still reconciles the cache with the server instead of
losing the already-removed rows; reports how many failed.
- Keyboard nudge: the keydown effect is now mounted once and reads live values
from a ref, so a data refetch can't re-subscribe and cancel a pending
debounced commit. The pending move is flushed on unmount instead of dropped,
a fire only commits if its live value is still present (a drag that cleared it
already committed its own PATCH), and nudges are ignored while a pointer drag
is active — closing the nudge/drag race.
- usePageTitle: dropped the restore-on-unmount, which raced and could clobber the
incoming route's title on a fast navigation.
- ClearBedModal disables confirm on an empty plop list.
- Extracted EditorHint (empty-state overlays) and objectDisplayName (harmonized
the name fallback across the toolbar and clear dialog).
Skipped (false positive): "nudge clamp ignores rotation" — the clamp is to the
object's LOCAL bounds (±w/2, ±h/2), exactly what the server enforces, so a plop
can't escape a rotated bed.
tsc --noEmit clean; 24/24 vitest; production build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01JdQpdYYsTgtkJBxbcpAszi
- Clear bed: a "Clear (N)" action on a focused plantable object → confirm modal
(ClearBedModal) → soft-removes every active plop (useClearObject loops PATCHes,
invalidates once); rows kept with removed_at.
- Keyboard nudging (desktop): arrows move the selected object/plop 1cm, Shift
10cm; the PATCH is debounced ~400ms on key-idle. Plops nudge in the object's
local frame and clamp to its bounds; ignored while typing in a field.
- Empty states: hint to place a first bed on an empty field, and to add a plant
in an empty focused bed (both non-interactive overlays).
- Paper cuts: per-route document titles (usePageTitle on every page), an emoji
favicon, and a 404 catch-all route (NotFound) inside the app shell.
- Mobile: plop tap targets stay ≥~44px at low zoom via a transparent hit circle.
- Ownership check hoisted to the authoritative ownerId==me so the nudge handler
can gate on canEdit before the loading early-returns.
Imperial coverage verified across inspectors/forms/cards/picker (all via the
unit-aware helpers); conversion helpers stay in lib/units.ts (their home since #8).
tsc --noEmit clean; 24/24 vitest; production build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01JdQpdYYsTgtkJBxbcpAszi