/** * Annotation Manager — lists all annotations in the space, grouped by status. * * Wishes for #annotation to collect every annotation piece, then renders * them with quick-action resolve/dismiss buttons and links to both the * annotation itself and the piece it targets. * * Keywords: annotation, manager, todo, wish, overview, agent, graph */ import { computed, handler, NAME, pattern, UI, wish, Writable, } from "commonfabric"; import type { AnnotationKind, AnnotationPiece, AnnotationStatus, } from "./annotation.tsx"; // ===== Handlers ===== const resolveAnnotation = handler< unknown, { status: Writable } >((_event, { status }) => { status.set("resolved"); }); const dismissAnnotation = handler< unknown, { status: Writable } >((_event, { status }) => { status.set("dismissed"); }); const reopenAnnotation = handler< unknown, { status: Writable } >((_event, { status }) => { status.set("open"); }); const setFilter = handler; value: string }>( (_event, { filter, value }) => { filter.set(value); }, ); // ===== Helpers ===== const KIND_ICON: Record = { note: "📌", todo: "☐", wish: "✨", }; const STATUS_STYLE: Record = { open: "background:#dbeafe;color:#1d4ed8", "in-progress": "background:#fef9c3;color:#854d0e", resolved: "background:#dcfce7;color:#166534", dismissed: "background:#f3f4f6;color:#6b7280", }; // ===== The Pattern ===== export default pattern>((_) => { const filter = new Writable("open"); const { candidates: annotations } = wish({ query: "#annotation", scope: [".", "~"], }); const filtered = computed(() => { const f = filter.get(); const all = (annotations ?? []).filter((a) => !!a) as AnnotationPiece[]; if (f === "all") return all; return all.filter((a) => (a?.status ?? "open") === f); }); const counts = computed(() => { const all = (annotations ?? []).filter((a) => !!a) as AnnotationPiece[]; return { all: all.length, open: all.filter((a) => (a?.status ?? "open") === "open").length, "in-progress": all.filter((a) => a?.status === "in-progress").length, resolved: all.filter((a) => a?.status === "resolved").length, dismissed: all.filter((a) => a?.status === "dismissed").length, }; }); const filterValue = computed(() => filter.get()); const TAB_LABELS: Array<{ key: string; label: string }> = [ { key: "open", label: "Open" }, { key: "in-progress", label: "In progress" }, { key: "resolved", label: "Resolved" }, { key: "dismissed", label: "Dismissed" }, { key: "all", label: "All" }, ]; return { [NAME]: computed(() => { const openCount = counts.open ?? 0; return `🗂 Annotations (${openCount} open)`; }), [UI]: ( {/* ── Filter tabs ── */} {TAB_LABELS.map(({ key, label }) => ( ))} {/* ── Annotation list ── */} {filtered.map( (ann: AnnotationPiece) => ann && ( {/* Row 1: kind icon + link to annotation + status badge */} {KIND_ICON[ann?.kind ?? "note"] ?? "📌"} s.split(":").map((p) => p.trim())), ) : {}), }} > {ann?.status ?? "open"} {/* Row 2: target piece link (if set) */} {ann?.targetPiece && ( on: )} {/* Row 3: action buttons */} {(ann?.status === "open" || ann?.status === "in-progress") && ( Resolve )} {(ann?.status === "open" || ann?.status === "in-progress") && ( Dismiss )} {(ann?.status === "resolved" || ann?.status === "dismissed") && ( Reopen )} ), )} {filtered.length === 0 && ( No {filterValue === "all" ? "" : filterValue} annotations. )} ), }; });