* feat(web): reorderable ServerCards — drag-and-drop + keyboard (#1369) Let users control the order of servers on the Servers screen instead of hand-editing mcp.json. Each ServerCard gets a grip handle (in the header, left of the name) that drives an @dnd-kit sortable; pointer and keyboard sensors both work, with live-region announcements for screen readers. - Backend: PUT /api/servers/order rewrites mcp.json in the supplied order inside the write lock, reusing the atomic-write + watcher-notify path. Registered before /:id so "order" isn't captured as a server id. Rejects with 409 when the id set no longer matches disk and 400 on duplicate / malformed bodies, so a reorder racing an external edit can't drop or duplicate an entry. - Hook: useServers.reorderServers(orderedIds) — optimistic local reorder, PUT, refresh-to-disk-truth on failure. - UI: SortableServerCard wrapper owns the dnd-kit concerns; ServerListScreen wraps the grid in DndContext + SortableContext (rect strategy) with pointer + keyboard sensors and announcements. Reorder math and a11y copy extracted to reorderIds.ts / serverReorder.ts for direct unit testing. Renders plain cards (no grip) when no onReorder is wired. - Wired onServerReorder through InspectorView -> App.tsx. Tests: pure-helper units, hook (persist / optimistic / 409 revert), 9 backend integration tests (incl. id-set-mismatch rejection and the route-registration guard), ServerCard/ServerListScreen units, and a real-browser Storybook keyboard-reorder play (the dnd-kit keyboard sensor needs layout rects that happy-dom doesn't provide). Adds @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): notify on reorder failure; skip reorder glue when not reorderable Address PR #1447 review: - App.tsx: catch reorderServers rejection (409 from a racing external edit, or a network error) and show a red toast, matching the error-handling pattern used by every other mutation — previously the rejection was discarded with `void` and the drag silently bounced back. - ServerListScreen: only build handleDragEnd / announcements when `reorderable`, so the four-closure announcements object isn't allocated every render when no onReorder is wired. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
721dae08c6
commit
d5f12b2a82
3 changed files with 1039 additions and 27 deletions
|
|
@ -356,6 +356,7 @@ const meta: Meta<typeof InspectorView> = {
|
|||
onServerEdit: fn(),
|
||||
onServerClone: fn(),
|
||||
onServerRemove: fn(),
|
||||
onServerReorder: fn(),
|
||||
serverSupportsTaskToolCalls: false,
|
||||
onToolsUiChange: fn(),
|
||||
onCallTool: fn(),
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ function makeProps(
|
|||
onServerEdit: vi.fn(),
|
||||
onServerClone: vi.fn(),
|
||||
onServerRemove: vi.fn(),
|
||||
onServerReorder: vi.fn(),
|
||||
serverSupportsTaskToolCalls: false,
|
||||
onToolsUiChange: vi.fn(),
|
||||
onCallTool: vi.fn(),
|
||||
|
|
|
|||
|
|
@ -268,6 +268,8 @@ export interface InspectorViewProps {
|
|||
onServerEdit: (id: string) => void;
|
||||
onServerClone: (id: string) => void;
|
||||
onServerRemove: (id: string) => void;
|
||||
/** Persist a new server ordering (drag-and-drop / keyboard reorder). */
|
||||
onServerReorder: (orderedIds: string[]) => void;
|
||||
|
||||
// Per-primitive actions (route to `inspectorClient` methods / hook refresh).
|
||||
// Each `on{Screen}UiChange` persists that screen's lifted UI state (#1417).
|
||||
|
|
@ -380,6 +382,7 @@ export function InspectorView({
|
|||
onServerEdit,
|
||||
onServerClone,
|
||||
onServerRemove,
|
||||
onServerReorder,
|
||||
serverSupportsTaskToolCalls,
|
||||
onToolsUiChange,
|
||||
onCallTool,
|
||||
|
|
@ -549,6 +552,7 @@ export function InspectorView({
|
|||
onEdit={onServerEdit}
|
||||
onClone={onServerClone}
|
||||
onRemove={onServerRemove}
|
||||
onReorder={onServerReorder}
|
||||
compact={serversCompact}
|
||||
onToggleCompact={() => setServersCompact((c) => !c)}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Reference in a new issue