learning_ai_notes/web/src/components/NoteVersionsPanel.tsx
Saravana Achu Mac a697752d15 feat: implement WEB_AI_FAST_ROADMAP (web + backend + docs)
Phase 1: Command palette (⌘K), editor autosave with quiet auto-saves, dashboard
saved views from API + quick links + onboarding seed CTA, explicit task scan panel.

Phase 2: Context pack formatter with YAML frontmatter, copy on note + workspace .md export.

Phase 3: ADR for hybrid search without embeddings; POST /notes/search (lexical +
ranked hybrid); search UI mode toggle.

Phase 4: POST copilot + suggest-title; in-editor copilot actions; /chat retrieval
answers with citations (backend chat.rag_enabled).

Phase 5: Settings MCP snippet, offline queue note, API token deferral; DEEP_LINKS.md.

Phase 6: Note shares + public GET; share page; POST onboarding-seed.

Phase 7: note_versions on PATCH; version panel; create-note templates; PWA manifest.

Flags: search.hybrid_enabled, copilot.enabled, chat.rag_enabled, onboarding.seed_enabled.
Made-with: Cursor
2026-03-31 13:00:36 -07:00

81 lines
2.7 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { listNoteVersions, type NoteVersionRow } from "@/lib/notes-client";
export function NoteVersionsPanel({ noteId, workspaceId }: { noteId: string; workspaceId: string }) {
const [items, setItems] = useState<NoteVersionRow[]>([]);
const [openId, setOpenId] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
void (async () => {
try {
const res = await listNoteVersions(noteId, workspaceId);
setItems(res.items);
setError(null);
} catch (e) {
setError(e instanceof Error ? e.message : "Could not load versions");
}
})();
}, [noteId, workspaceId]);
if (error) {
return (
<section className="surface-card" style={{ padding: "var(--nl-space-4)", color: "var(--nl-text-secondary)" }}>
{error}
</section>
);
}
if (items.length === 0) {
return (
<section className="surface-card" style={{ padding: "var(--nl-space-4)", color: "var(--nl-text-secondary)" }}>
No saved versions yet. Versions are created when you edit title or body.
</section>
);
}
return (
<section className="surface-card" style={{ padding: "var(--nl-space-5)", display: "grid", gap: "var(--nl-space-3)" }}>
<div style={{ fontWeight: 700 }}>Version history</div>
<ul style={{ listStyle: "none", margin: 0, padding: 0, display: "grid", gap: "var(--nl-space-2)" }}>
{items.map((v) => (
<li key={v.id} className="surface-muted" style={{ padding: "var(--nl-space-3)" }}>
<button
type="button"
onClick={() => setOpenId((o) => (o === v.id ? null : v.id))}
style={{
width: "100%",
textAlign: "left",
background: "none",
border: "none",
color: "var(--nl-text-primary)",
cursor: "pointer",
fontWeight: 600,
}}
>
{new Date(v.savedAt).toLocaleString()} · {v.source} · {v.title.slice(0, 48)}
{v.title.length > 48 ? "…" : ""}
</button>
{openId === v.id ? (
<pre
style={{
marginTop: 8,
whiteSpace: "pre-wrap",
fontSize: "var(--nl-fs-sm)",
color: "var(--nl-text-secondary)",
maxHeight: 200,
overflow: "auto",
}}
>
{v.body.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim()}
</pre>
) : null}
</li>
))}
</ul>
</section>
);
}