type TipTapNode = { type?: string text?: string content?: TipTapNode[] } /** True when TipTap JSON has no meaningful text (blank doc / empty paragraph). */ export function isEmptyTipTapDoc(content: Record | null | undefined): boolean { if (!content) return true return !tipTapNodeHasText(content as TipTapNode) } /** Ensure TipTap JSON is a valid editable doc (at least one block node). */ export function ensureMinimalTipTapDoc( content: Record | null | undefined ): Record { if ( content && content.type === "doc" && Array.isArray(content.content) && content.content.length > 0 ) { return content } return { type: "doc", content: [{ type: "paragraph" }] } } function tipTapNodeHasText(node: TipTapNode | TipTapNode[] | null | undefined): boolean { if (!node) return false if (Array.isArray(node)) { return node.some((child) => tipTapNodeHasText(child)) } if (typeof node.text === "string" && node.text.trim() !== "") { return true } if (Array.isArray(node.content)) { return node.content.some((child) => tipTapNodeHasText(child)) } return false }