useDocumentCreation.tsx3.0 KBView on GitHub 'use client';
import { useCallback, useRef, useState } from 'react';
import type { Editor } from '@tiptap/core';
import {
PlaybookDocumentCreateDialog,
type CreatedPlaybookDocument,
} from './PlaybookDocumentCreateDialog';
import type { PlaybookDocumentKind } from './HashMentionList';
import { insertDocumentRef, scopeAtCursor } from './playbookExtensions';
interface UseDocumentCreationOptions {
/** User AOP id, for the resource-document path. */
aopId?: string;
/** Scope used when the cursor is not inside a scope card (single-scope editors). */
baseScope?: 'user' | 'org';
/** The playbook's owner, forwarded to the create mutation. */
ownerUserId?: string | null;
/** Persist the playbook before navigating away, so the inserted ref survives. */
save?: () => Promise<unknown>;
}
/**
* Wires the playbook editor's Board / Table / Document options to the create flow.
*
* The twin of `useSubagentCreation`, and separate from it for the reason
* `insertDocumentRef` is separate from `insertSubagentRef`: a subagent's ref has to land
* inside a `<trigger>` to ever fire, and a board's must not, because a trigger around a
* board is a resource the playbook would try to run. Merging the two hooks would put that
* difference behind a conditional in a shared `onCreated`, which is the sort of thing that
* gets "simplified" back into one path by someone who does not know why it is two.
*
* It does NOT navigate away the way the subagent flow does. A subagent doc arrives empty
* and unusable until you write its instructions, so leaving for it is the next step; a board
* created from a template is complete on arrival, and the next step is finishing the
* sentence you were writing in the playbook.
*/
export function useDocumentCreation({
aopId,
baseScope = 'user',
ownerUserId,
save,
}: UseDocumentCreationOptions) {
const [open, setOpen] = useState(false);
const [kind, setKind] = useState<PlaybookDocumentKind>('board');
const [scope, setScope] = useState<'user' | 'org'>(baseScope);
const editorRef = useRef<Editor | null>(null);
const onCreateDocument = useCallback(
(nextKind: PlaybookDocumentKind, editor: Editor) => {
editorRef.current = editor;
setKind(nextKind);
setScope(scopeAtCursor(editor) ?? baseScope);
setOpen(true);
},
[baseScope],
);
const onCreated = useCallback(
async (result: CreatedPlaybookDocument) => {
if (editorRef.current) insertDocumentRef(editorRef.current, result.documentId);
try {
await save?.();
} catch {
// Best-effort: the document exists either way, and the ref is in the editor. A failed
// save is recoverable by typing anything; a swallowed create would not be.
}
setOpen(false);
},
[save],
);
const documentDialog = (
<PlaybookDocumentCreateDialog
open={open}
onOpenChange={setOpen}
kind={kind}
scope={scope}
aopId={aopId}
targetUserId={ownerUserId ?? undefined}
onCreated={onCreated}
/>
);
return { onCreateDocument, documentDialog };
}