M2 drag-reorder: notes.position + reorder API + native DnD
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 5s
CI & Build / Python tests (push) Successful in 7s
CI & Build / Build & push image (push) Successful in 30s

- Migration 0008: notes.position (int). Board orders pinned -> position ->
  updated_at; new notes created at top (max position + 1). POST /api/notes/reorder
  assigns positions from the given order (owner-scoped).
- notes store: position on Note, position-aware sort, optimistic reorder().
- NoteCard reorderable (native HTML5 draggable + dragstart/drop); BoardView moves
  the dragged note before the drop target and persists.

Note: drag on a CSS-columns masonry has imperfect during-drag visuals (columns
reflow); order persists correctly. Candidate for a polish pass / layout tweak.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
This commit is contained in:
2026-07-19 22:28:43 -04:00
co-authored by Claude Opus 4.8
parent e4c898cd1b
commit 339cc5c2d2
7 changed files with 135 additions and 7 deletions
+15
View File
@@ -30,6 +30,7 @@ export interface Note {
body: string;
color: NoteColor;
kind: NoteKind;
position: number;
pinned: boolean;
archived: boolean;
trashed: boolean;
@@ -49,6 +50,7 @@ export const useNotesStore = defineStore("notes", () => {
function sortItems(): void {
items.value.sort((a, b) => {
if (a.pinned !== b.pinned) return a.pinned ? -1 : 1;
if (a.position !== b.position) return b.position - a.position;
return (b.updated_at ?? "").localeCompare(a.updated_at ?? "");
});
}
@@ -139,6 +141,18 @@ export const useNotesStore = defineStore("notes", () => {
reconcile(await api.del<Note>(`/api/notes/${id}/attachments/${attId}`));
}
async function reorder(orderedIds: string[]): Promise<void> {
// Optimistically assign positions matching the backend (total - index), sort,
// then persist.
const total = orderedIds.length;
orderedIds.forEach((id, index) => {
const n = items.value.find((x) => x.id === id);
if (n) n.position = total - index;
});
sortItems();
await api.post("/api/notes/reorder", { ids: orderedIds });
}
async function trash(id: string): Promise<void> {
reconcile(await api.post<Note>(`/api/notes/${id}/trash`));
}
@@ -171,6 +185,7 @@ export const useNotesStore = defineStore("notes", () => {
deleteItem,
uploadAttachment,
deleteAttachment,
reorder,
trash,
restore,
deleteForever,