"use client" // Creates a note, or saves a change to one. The same form for both: a note being edited carries the // version it was read at, so a second editor's save is a 409 shown here and never a silent overwrite. import { emitAppEvent, type ProjectSlugs, useTransport } from "@conusai-kit/ui" import { type FormEvent, type ReactElement, useId, useState } from "react" export interface NoteFormLabels { readonly heading: string readonly title: string readonly body: string readonly submit: string readonly busy: string readonly saved: string /** The app's `app.failed` message, with `{detail}` still in it. */ readonly failed: string } export type NoteFormTarget = | { readonly mode: "create"; readonly listHref: string } | { readonly mode: "edit" readonly noteId: string readonly version: number readonly title: string readonly body: string } export function NoteForm({ slugs, target, labels, }: { slugs: ProjectSlugs target: NoteFormTarget labels: NoteFormLabels }): ReactElement { const { call, describe } = useTransport() const titleId = useId() const bodyId = useId() const [state, setState] = useState<{ kind: "idle" | "busy" | "saved" } | { kind: "failed"; detail: string }>({ kind: "idle", }) const submit = async (event: FormEvent): Promise => { event.preventDefault() const form = new FormData(event.currentTarget) const title = String(form.get("title") ?? "").trim() const body = String(form.get("body") ?? "") if (title.length === 0) return setState({ kind: "busy" }) try { if (target.mode === "create") { const created = (await call("notes.note.create", { ...slugs, title, body })) as { noteId: string } // For whatever listens in the browser (an in-app guide's checklist task); it waits for a // listener that asked to store the event first, because the next line leaves the page. await emitAppEvent("note_created") window.location.assign(`${target.listHref}/${created.noteId}`) return } await call("notes.note.update", { ...slugs, noteId: target.noteId, title, body, expectedVersion: target.version, }) setState({ kind: "saved" }) // The page is rendered on the server: a reload shows the new version, which the next save needs. window.location.reload() } catch (error) { setState({ kind: "failed", detail: describe(error) }) } } return (

{labels.heading}