Address #135 review: ref with the refs, primary button only, reset on ground
Build image / build-and-push (push) Successful in 12s

lastPress sits with the component's other refs and its thresholds at module
scope with the other tuning constants; only a primary-button (or finger)
press counts, like a native dblclick; a press on empty ground clears the
pending half, so bed → ground → bed within 400 ms is not a double-click.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
2026-08-23 03:25:54 -04:00
co-authored by Claude Fable 5
parent a19fc2e7fc
commit b5e97d4144
+15 -12
View File
@@ -35,6 +35,10 @@ import {
import { useEditorStore, type Viewport } from './store'
import type { EditorGarden, EditorObject } from './types'
// A double press: same object, this close in time and space.
const DOUBLE_PRESS_MS = 400
const DOUBLE_PRESS_PX = 12
const WHEEL_SENSITIVITY = 0.0016
const ANIM_MS = 520
const REFIT_THRESHOLD_PX = 60
@@ -95,6 +99,13 @@ export const Canvas = forwardRef<
const drag = useRef<Drag | null>(null)
const pinch = useRef<Pinch | null>(null)
const dropPlant = useRef<{ plant: Plant; lotId: number | null } | null>(null)
// The last primary-button press on an object, for double-click detection.
// Detected in objDown rather than with onDoubleClick on the object's <g>:
// track() captures the pointer on the SVG root, and capture retargets the
// compatibility click and dblclick events to the root, so a dblclick
// handler on the object never fires — which is why "double-click a bed to
// plant it" did nothing from the Organic rebuild until 2026-08-23.
const lastPress = useRef<{ id: number; at: number; x: number; y: number } | null>(null)
const vp = useEditorStore((s) => s.vp)
const anim = useEditorStore((s) => s.anim)
@@ -316,6 +327,7 @@ export const Canvas = forwardRef<
// ── pointer handlers ────────────────────────────────────────────────────
const onCanvasDown = (e: ReactPointerEvent) => {
lastPress.current = null // a press on empty ground is not half of a double-click on a bed
track(e)
if (pts.current.size === 2) return pinchStart()
const st = useEditorStore.getState()
@@ -329,16 +341,6 @@ export const Canvas = forwardRef<
if (!drag.current) drag.current = { t: 'pan', sx: e.clientX, sy: e.clientY, tx: st.vp.tx, ty: st.vp.ty, moved: false, th: thresh(e) }
}
// The last press on an object, for double-click detection. Detected here
// rather than with onDoubleClick on the object's <g>: track() captures the
// pointer on the SVG root, and capture retargets the compatibility click
// and dblclick events to the root, so a dblclick handler on the object
// never fires — which is why "double-click a bed to plant it" did nothing
// from the Organic rebuild until this was noticed (2026-08-23).
const lastPress = useRef<{ id: number; at: number; x: number; y: number } | null>(null)
const DOUBLE_PRESS_MS = 400
const DOUBLE_PRESS_PX = 12
const objDown = (o: EditorObject) => (e: ReactPointerEvent) => {
e.stopPropagation()
track(e)
@@ -354,8 +356,9 @@ export const Canvas = forwardRef<
if (st.focusId != null && o.id !== st.focusId) return
const prev = lastPress.current
const now = performance.now()
lastPress.current = { id: o.id, at: now, x: e.clientX, y: e.clientY }
if (prev && prev.id === o.id && now - prev.at < DOUBLE_PRESS_MS && Math.hypot(e.clientX - prev.x, e.clientY - prev.y) < DOUBLE_PRESS_PX) {
// Only the primary button (or a finger) counts, like a native dblclick.
lastPress.current = e.button === 0 && e.isPrimary ? { id: o.id, at: now, x: e.clientX, y: e.clientY } : null
if (prev && lastPress.current && prev.id === o.id && now - prev.at < DOUBLE_PRESS_MS && Math.hypot(e.clientX - prev.x, e.clientY - prev.y) < DOUBLE_PRESS_PX) {
// Second press of a double-click: focus the bed to plant it. No drag
// starts, so the matching pointerup has nothing to select into Plot.
lastPress.current = null