S1: frontend infra — AsyncState/EmptyState + useNoteEditor, adopted in BoardView
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 8s
CI & Build / Python tests (push) Successful in 11s
CI & Build / Build & push image (push) Successful in 30s

M9 section S1, commit 4 (frontend). Introduce the shared UI primitives the 7 data
views + 5 editor hosts were hand-rolling:

- components/AsyncState.vue: the loading / error(+Retry) wrapper (emits `retry`).
- components/EmptyState.vue: the centered title/subtitle "nothing here" block.
- composables/useNoteEditor.ts: the editing/open/close/navigate glue every editor
  host duplicated, as one controller (onClose hook + local-list resolution for
  [[wiki-link]] navigation).

BoardView adopts all three: its three hand-rolled loading/error/empty blocks
collapse into <AsyncState> + <EmptyState>, and its editor glue into useNoteEditor.
The other views + editor hosts adopt these in the Organize (S3) and Auth (S5)
sections. Behavior-preserving.

Frontend has no local typecheck (rule 10); CI's vue-tsc is the gate.

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-23 19:40:37 -04:00
co-authored by Claude Opus 4.8
parent 61f7c40db7
commit 18ca4d4db4
4 changed files with 93 additions and 41 deletions
+33
View File
@@ -0,0 +1,33 @@
import { ref } from "vue";
import { useNotesStore, type Note } from "../stores/notes";
// Shared controller for hosting the NoteEditor modal. Every view that opens the
// editor used to re-declare the same editing/open/close/navigate glue; this is the
// single implementation.
//
// - `onClose` lets a host clean up its own state (e.g. a board's compose flag).
// - `list` is the host's local note array, tried first when resolving a navigated
// [[wiki-link]] target before falling back to the store, then a fetch — so views
// that keep their own list (reminders, timeline, search, graph) still resolve
// locally without duplicating the lookup.
export function useNoteEditor(options: { onClose?: () => void; list?: () => Note[] } = {}) {
const notes = useNotesStore();
const editing = ref<Note | null>(null);
function open(note: Note): void {
editing.value = note;
}
function close(): void {
editing.value = null;
options.onClose?.();
}
async function navigate(id: string): Promise<void> {
const local = options.list?.() ?? notes.items;
const found = local.find((n) => n.id === id) ?? notes.items.find((n) => n.id === id) ?? (await notes.fetchOne(id));
if (found) editing.value = found;
}
return { editing, open, close, navigate };
}