Core API
Core is the whole vocabulary without a screen: it parses Markdown into segments, writes them as plain text, and encodes the answers a reader gives. It touches no DOM and no node API, so it runs wherever JavaScript does.
import { parse, toText, inline, encodeAnswer, checkBlocks } from "pi-better-show";parse(markdown: string): Segment[]Splits a document into {type: "markdown", text}, {type: "block", kind, data, index}, and {type: "invalid", kind, source, error} segments, in order. data is typed per kind (BlockDataMap). index counts the document’s blocks of every kind from 0; invalid blocks take no number. index is the block’s address for answers.
Tells the author what is wrong with the blocks of a file, where parse only keeps going.
checkBlocks(markdown: string, options?: { mermaid?: (source: string) => string | undefined }): BlockProblem[]formatProblems(name: string, problems: BlockProblem[]): string
interface BlockProblem { kind: BlockKind; line: number; // 1-based, in the Markdown file path: string; // e.g. form question "o5" choice 1 label message: string; severity: "error" | "warning";}What is an error, and what is a warning
error: the block is invalid and the page shows its source (a YAML error is reported at the line that caused it; a flow collection broken by an unquoted # points at the line that opened it). warning: the block draws, but part of what was written is lost: a plain value that a # cut short (YAML reads the rest as a comment; the message names the key path and says to quote the value), a key the block does not read at that level (with the known keys, and a “did you mean” for a near miss), or a fence that never closes. Core has no Mermaid parser: pass options.mermaid, which returns what is wrong with a diagram source, to check diagrams too. Pure; it loads no more than parse does.
Text fallback
Section titled “Text fallback”Renders a document for a surface that cannot draw blocks: Discord, LINE, a terminal.
toText(segments: Segment[], profile: TextProfile): string
interface TextProfile { flavor: "discord" | "line" | "plain"; // Discord Markdown, or plain text width?: number; // columns a diagram may take; default 60 mermaid?: (source: string, width: number) => string | undefined; keep?: readonly BlockKind[]; // kinds emitted as their `raw` text instead of converted lang?: "en" | "zh-TW"; // language of the reply instructions; default "en", anything else is "en"}Reply instructions, `raw`, and the diagram hook
The interactive kinds end with a reply instruction (options, form, review, suggest, and checklist): “Reply with a number.”, “Reply in text, one line per question, like 1: b.”, and so on. lang: "zh-TW" writes them in Traditional Chinese; pass the conversation’s language when you know it. Block content is never translated.
Every BlockSegment and InvalidSegment carries raw: the block’s exact original text, fences (or an alert’s whole > quote) included, byte for byte. Kinds listed in keep are emitted as raw verbatim, valid or invalid, for a surface that draws them itself; other kinds convert as usual. DISPLAY_KINDS (alert, cards, mermaid, mindmap, diff, table, steps, stats, tree) is the usual keep list for such a surface; INTERACTIVE_KINDS (options, suggest, form, checklist, review) are the kinds a reader answers, the ones a surface that cannot draw them gets as reply instructions. Without keep, output is unchanged.
Renders a document for a surface that cannot draw blocks. A diagram goes through the mermaid hook; when there is no hook, or it returns undefined or a drawing wider than width, the output is the Mermaid source in a code block plus a pointer to the web view. No block-kind fence is left in the output, except Discord’s native diff.
Answers
Section titled “Answers”An answer is a kind and a list of [name, value] fields; encodeAnswer turns it into the text the agent reads.
inline(text: string): InlineToken[] // restricted inline Markdown, tokenized, never HTMLinlinePlain(text: string): string // the same text without markersdisplayWidth(text: string): number // terminal columns: CJK counts 2reviewItemName(item): string // "B (dark header)"
interface Answer { kind: "form" | "review" | "feedback" | "pick" | "suggest" | "checklist"; title?: string; fields: [name: string, value: string][]; ref?: { doc?: string; block: number };}encodeAnswer(answer: Answer): stringencodeAnswer produces the text the agent reads. For form, review, and feedback it is byte-compatible with pi-herdr-doc-review’s formMessage, reviewMessage, and pageFeedbackMessage given the same fields (the legacy pi-kind field is ignored). ref addresses the block for hosts and does not appear in the text. A pi-note field (see below) is appended as Other comments: ….
| Kind | fields |
Text |
|---|---|---|
form |
one [name, value] per question |
User submitted a form ... then - name: value lines |
review |
title, picked, item/comment pairs, silent |
User reviewed "title": … |
feedback |
page, quote/comment pairs, overall |
User feedback on the page "page": … (a quote is a selected passage, or a diagram element, see Markdown pages) |
pick |
[["picked", option]] |
User picked "option" for "title". |
suggest |
[["text", text]] |
the text itself |
checklist |
one [name, "checked" | "unchecked"] per item |
User updated the checklist "title": then ☑/☐ lines |
Validate answers
Section titled “Validate answers”For a host that receives an Answer from an untrusted client: validateAnswer says whether it fits the block it claims to answer.
validateAnswer(block: BlockSegment, answer: Answer): { ok: true } | { ok: false; reason: string }What is accepted
validateAnswer is for a host that receives an Answer from an untrusted client. It accepts the answer when its kind fits the block (options takes pick, the others their own kind), every field names a question, choice, or item of that block, and there are at most 64 fields of at most 1,024 characters each. Several values for one name are accepted only for a multi form question (as repeated fields, or the joined value the form element sends); a single or text question, a pick, and a suggestion take one. A required question must be answered (an empty value or (no answer) is not an answer). On a form question without an other box, a value must be one of its choices. A checklist field is an item name once with checked or unchecked; a review may carry picked only when the block has pick. On refusal reason says why, for the host’s error reply. Blocks that take no answer (display kinds) are refused.