Files
2026-11-14 09:56:45 +00:00

47 lines
2.0 KiB
TypeScript

"use client"
// The project's notes, live (umbrella TS6.4 and TS6.6): the page's view renders the first page and hands it
// over as the seed, so the page arrives complete; this keeps it current through the organization's live stream
// -- a note another member adds, renames or deletes appears without a reload. The rows come from
// `notes.note.getMany`, under the same permission as the list, so the stream itself never carries a note's
// contents.
import type { ProjectSlugs } from "@conusai-kit/ui"
import { liveCollection, useDataClient, useLive } from "@conusai-kit/ui/data"
// Its live sync stops when no live query reads it any more (TanStack DB's collection GC).
import { useState } from "react"
import { NOTE_COLLECTION, type NoteView } from "../contract/index.js"
export function useLiveNotes(slugs: ProjectSlugs, seed: readonly NoteView[]): readonly NoteView[] {
const client = useDataClient()
// One collection for the life of the page: a page of another project mounts its own, and the seed is the
// first render's.
const [notes] = useState(() =>
liveCollection<NoteView>({
client,
org: slugs.org,
topic: { ws: slugs.workspace, project: slugs.project },
entity: NOTE_COLLECTION.entity,
key: NOTE_COLLECTION.key,
version: "version",
seed,
list: {
procedure: NOTE_COLLECTION.list,
input: { ...slugs },
rows: (out) => (out as { notes: NoteView[] }).notes,
},
getMany: {
procedure: NOTE_COLLECTION.getMany,
input: { ...slugs },
rows: (out) => (out as { items: NoteView[] }).items,
},
}),
)
// Newest first, as the list procedure orders them, through the query operators (umbrella §7.2).
const { data, isReady } = useLive(notes, (q, rows) =>
q.from({ note: rows }).orderBy(({ note }: { note: NoteView }) => note.createdAt, "desc"),
)
// Until the collection is ready the server's rows are the answer, so the first paint matches it.
return isReady ? data : seed
}