fix(admin): drag-and-drop application reorder, fix broken swap (BUNYIP-473) #468
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/BUNYIP-473-app-reorder-dnd"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes BUNYIP-473: the admin Applications list reorder does nothing and the list jumps. Replaces the broken up/down control with drag-and-drop plus a keyboard path.
Root cause (confirmed, as the reporter suspected both bugs share one)
sort_order, so they all take the column default0(ApplicationRepository::create; migration20260316000022only ranked the rows that existed then). The list orders by(sort_order, display_name), and the control calledswap_sort_order, which swaps the two rows'sort_ordervalues. Swapping0with0writes the same value to both rows, so nothing moves and thedisplay_nametiebreaker keeps the order identical./admin/applications, reloading at the top. The web handler also discarded the API result (let _ = ...), so a failure was silent.Fix: explicit positions + one drag handle
ApplicationRepository::set_ordersets each id'ssort_orderfrom its index in an ordered list (oneunnest ... WITH ORDINALITYstatement), so positions stay distinct and the equal-value no-op cannot recur.create()appends new apps atMAX(sort_order)+1, and a new migration densifies existing rows to distinct sequential positions preserving their current order.swap_sort_order/SwapApplicationOrderRequestare removed.PUT /v1/admin/applications/reorder(registered before the/{app_id}routes so "reorder" is never parsed as an id) and the bunyip-webPOST /admin/applications/reorderproxy, which returns a bare status (no redirect) because it is called byfetch.draggable.assets/js/app-reorder.jsmoves rows live on drag (the moving row is its own drop indicator) and, for keyboard users, moves the focused row on ArrowUp/ArrowDown; on drop or key-move it POSTs the new id order. Nothing navigates, so the list never scroll-jumps. Ships as a first-partydata-*-wired module, soscript-srcstays'self'(BUNYIP-424).Acceptance criteria
set_order+ fetch).createMAX+1 + backfill migration).Tests / verification
data-reorder-*markup, noswap-order/chevron controls, and keeps the toggles + Edit link; the reorder request DTO parses its ordered id list.just check-containergreen (fmt, clippy-D warnings, 178 web tests) in the pinned image. Stylesheet rebuilt for the newcursor-grab/focus:ring-*utilities. Screenshot of the new list below..sqlxregen (runtime queries only).Note: native HTML5 drag-and-drop is desktop-pointer; the ArrowUp/ArrowDown handle path is the keyboard-accessible equivalent.
🤖 Generated with Claude Code
https://claude.ai/code/session_018TXaT3P192nDsZzbzHETb9
The admin Applications list reorder was broken two ways, with one root cause plus a UI-mechanism problem. Root cause of "the arrows do nothing": applications created through the admin form never set sort_order, so they all took the column default 0 (ApplicationRepository::create; the 20260316000022 migration only ranked the rows that existed then). The list orders by (sort_order, display_name) and the up/down control called swap_sort_order, which swapped the two rows' sort_order values. Swapping 0 with 0 writes the same value to both rows, so nothing moved and the display_name tiebreaker kept the order identical. Root cause of "the list jumps": the control was a full-page form POST that redirected to /admin/applications, reloading at the top; the web handler also discarded the API result, so a failure was silent. Both are fixed by ordering on explicit positions instead of swapping values, and by replacing the two stacked chevrons with one drag handle: - ApplicationRepository::set_order assigns each id's sort_order from its index in a supplied ordered list (one unnest WITH ORDINALITY statement), so positions stay distinct and the equal-value no-op cannot recur. create() now appends new apps at MAX(sort_order)+1, and a new migration densifies existing rows to distinct sequential positions preserving their current order. swap_sort_order and SwapApplicationOrderRequest are removed. - New endpoints replace the per-row swap: PUT /v1/admin/applications/reorder (registered before the /{app_id} routes so "reorder" is never parsed as an id) and the bunyip-web POST /admin/applications/reorder proxy, which returns a bare status (no redirect) since it is called by fetch. - Each row now carries a single grab-cursor drag handle (grip icon) and is draggable. assets/js/app-reorder.js moves rows live on drag (the moving row is its own drop indicator) and, for keyboard users, moves the focused row on ArrowUp/ArrowDown; on drop or key-move it POSTs the new id order. Nothing navigates, so the list never scroll-jumps. The behaviour ships as a first-party data-* wired module, so script-src stays 'self' (BUNYIP-424). Tests: the row renders one drag handle and no swap-order/chevron markup while keeping the toggles and Edit link; the reorder request DTO parses its ordered id list. Verified with just check-container (fmt, clippy -D warnings, workspace tests) and a screenshot of the new list. The tracked stylesheet is rebuilt for the new cursor-grab / focus-ring utilities. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018TXaT3P192nDsZzbzHETb9f5d6a035427b8736d132