Dnd-Kit Drag and Drop#
Blinko uses @dnd-kit/core for manual note reordering on the home feed. Dragging a card updates its sortOrder in the database; the two-level sort (pinned notes first via isTop, then by sortOrder ascending) is maintained both client- and server-side .
Drag-and-drop is disabled for the todo view β useDragCard receives undefined for notes in that branch .
Key Files#
| File | Role |
|---|---|
app/src/hooks/useDragCard.tsx | Core hook + DraggableBlinkoCard component |
app/src/pages/index.tsx | Mounts DndContext, wires sensors and handlers |
server/routerTrpc/note.ts | updateNotesOrder tRPC mutation |
prisma/schema.prisma | sortOrder Int @default(0) field on notes model |
app/src/store/blinkoStore.tsx | fullscreenEditorNoteId: number | null state |
useDragCard Hook#
useDragCard is the central abstraction. It returns localNotes, sensors, and three handlers consumed by DndContext in the home page.
Sensor configuration β Both MouseSensor and TouchSensor use an activationConstraint of { delay: 250, tolerance: 5 } under normal conditions, requiring a 250 ms hold before a drag begins . When the fullscreen editor is open, the constraints are set to { delay: 999999, distance: 999999 } β effectively impossible to satisfy β to suppress drag activation .
Drag guard in handleDragStart β Even with the sensor constraints in place, handleDragStart contains an explicit early return if blinko.fullscreenEditorNoteId !== null .
Drag end / reorder logic β On DragEndEvent, the hook:
- Finds old and new indices in
localNotes. - Prevents cross-boundary drops between pinned (
isTop: true) and unpinned notes . - Splices the moved note into its new position and remaps
sortOrderto each note's array index . - Calls
api.notes.updateNotesOrder.mutateto persist the new order β a fire-and-forget call with no error handling shown in the hook itself.
Local state freeze during drag β A isDraggingRef ref prevents the useEffect that syncs localNotes from overwriting in-progress drag state when the parent's notes prop updates .
DraggableBlinkoCard Component#
DraggableBlinkoCard composes useDroppable and useDraggable on the same element. Each card registers:
- A droppable zone with id
drop-{note.id}β always active . - A draggable handle on the inner
divβ drag listeners are stripped whenblinkoItem.isExpandis true .
A color-coded insert line (blue = allowed, red = forbidden) appears above the drop target , and a prohibition icon overlay renders when hovering over a cross-boundary target .
The DragOverlay in index.tsx renders the dragged card at 3Β° rotation and 105% scale while the original card shows a dashed placeholder .
Server-Side: updateNotesOrder#
The updateNotesOrder tRPC mutation accepts { updates: { id: number; sortOrder: number }[] } and bulk-writes sortOrder via Prisma updateMany, gated by accountId ownership.
Fullscreen Editor Interaction & Known Edge Case#
fullscreenEditorNoteId is set in BlinkoCard when a card is expanded and cleared in FullscreenEditor on close.
Known edge case: The activation-constraint approach disables drag initiation, but does not guard against stray pointer/touch events that may fire during the fullscreen modal's mount animation. During that brief window β before fullscreenEditorNoteId is set in the store β the sensors are still configured with normal constraints, so a fast interaction could theoretically start a drag. The explicit handleDragStart guard is a second line of defense but also reads from the store, so a race during mounting is still possible.