/** * Reduces an agent-written HTML `
` to what a terminal form draws. * * The model is deliberately small: titled sections (one per top-level * `
`, plus untitled runs of loose content between them), context * blocks (paragraphs, headings, lists, preformatted text, tables) and fields. * Radios and checkboxes join one {@link ChoiceField} per `type` + `name` * wherever they appear, so a group split across table cells or list items is * still one question. Layout-only markup (`
` between labels, wrapper * `
`s, `style`, buttons) is dropped: the renderer owns layout, and the * UI owns Submit/Cancel. * * @example * const form = parseHtmlForm(`
Database * * *
`); * form.sections[0].title; // [{ text: "Database" }] * form.fields[0]; // { kind: "choice", name: "db", multi: false, options: [sqlite ✓, pg] } */ import { Comment, type Document, Element, type Node, parseHTML, Text } from "@oh-my-pi/pi-utils/dom"; /** Inline emphasis a span keeps from ``, ``/`` and ``/``. */ export type FormSpanStyle = "code" | "strong" | "em"; /** A run of inline text; `"\n"` inside `text` is a hard line break (`
`). */ export interface FormSpan { text: string; style?: FormSpanStyle; } /** One answer of a {@link ChoiceField}. */ export interface ChoiceOption { value: string; label: FormSpan[]; checked: boolean; disabled: boolean; } /** A radio group (`multi: false`), checkbox group (`multi: true`) or ``: a single select draws as a one-row dropdown instead of option rows. */ select: boolean; /** * The UI adds a free-text "Other" answer: set for radio and checkbox groups of * two or more options that don't already offer one of their own. */ other: boolean; /** The control's own label; empty for radio/checkbox groups, whose question is the surrounding text. */ label: FormSpan[]; options: ChoiceOption[]; required: boolean; } /** A single-line `` (`multiline: false`) or a `