# Architecture

[Documentation index](README.md) · [Result contract](result-contract.md) · [Development](development.md)

Pi Rich Questions is one TypeScript Pi extension split into focused modules under `extensions/pi-rich-questions/`. The package manifest points Pi at `extensions/index.ts`, a one-line wrapper that re-exports the implementation. Keeping the package entrypoint directly under `extensions/` removes the redundant `:pi-rich-questions` suffix from Pi's provenance label; npm installs display simply as `pi-rich-questions`, while Git installs retain their Git source label.

## Modules

| Module | Responsibility |
| --- | --- |
| `extensions/index.ts` | Package entrypoint that re-exports the implementation with a concise Pi extension-list label. |
| `pi-rich-questions/index.ts` | Registers `ask_rich_questions` and `/rich-questions`, enforces mode guards, coordinates configuration/extraction/form execution, and returns/sends results. |
| `schema.ts` | Defines the public TypeBox input schema. |
| `types.ts` | Defines public inputs, normalized questions, answers, and form results. |
| `normalize.ts` | Cleans/deduplicates IDs, filters options, supplies labels, and applies `allowOther`/`required` defaults. |
| `form-state.ts` | Owns radio, checkbox, custom, text, response-note, and review-note state; initializes defaults and builds answers. |
| `form.ts` | Connects the state machine to Pi's custom modal and Editor, handles input, navigation, scrolling, cancellation, and submission. |
| `form-view.ts` | Renders question and review screens and reports the active line range. |
| `markdown.ts` | Adapts Pi's active theme to Markdown rendering. |
| `layout.ts` | Wraps/truncates by visible width and calculates the height-aware viewport. |
| `format.ts` | Produces the self-contained model-facing result. |
| `extraction.ts` | Finds source assistant text, builds extraction attempts, calls providers, validates JSON, and creates the broad fallback. |
| `extraction-config.ts` | Validates and atomically persists versioned global extraction preferences. |
| `config-ui.ts` | Provides first-run choices, authenticated-model search, and supported-thinking selection. |

## Direct tool data flow

1. Pi validates tool arguments with `RichAskParams`.
2. `pi-rich-questions/index.ts` rejects non-TUI modes or empty question arrays.
3. `runRichForm` normalizes input and initializes form state/defaults.
4. The user navigates the modal; `form.ts` mutates focused state helpers and asks Pi TUI to render.
5. Submission builds one `RichAnswer` per question plus optional review text.
6. `formatRichAnswers` creates model-facing text; the tool returns it with the structured `RichFormResult` as `details`.

Cancellation follows the same return boundary but sets `cancelled: true` and uses cancellation text instead of formatted partial answers.

## Slash-command data flow

1. `lastAssistantText` scans the session branch backward for completed assistant text.
2. Empty input enters the demo-confirmation path.
3. Configuration is loaded or collected. `resolveExtractionAttempts` selects an authenticated configured/session attempt while avoiding duplicates and preserving lazy fallback authentication.
4. `runExtractionAttempts` sends the source under a preservation-focused system prompt. `parseExtraction` strips an optional JSON fence, parses, and TypeBox-validates the result.
5. A failure may resolve and try the session fallback. Exhaustion creates one broad freeform question with the complete source.
6. Short extracted questions receive the complete source as fallback context; the normal form then runs.
7. Submission is formatted into a displayed `rich-answers` custom message and triggers the next agent turn.

## Form state and transitions

`form.ts` tracks the current question, per-question option cursor, review mode, editor kind (`text`, `other`, `note`, or `additional`), guide visibility, and viewport position. Answer data itself stays in `RichFormState` maps keyed by normalized question ID.

Entering another question saves the active editor. Radio selection advances immediately; checkbox Space toggles without advancing; choosing Other starts an editor. After the last question, the review screen opens with the additional-text editor. Final submission succeeds only when `allRequiredAnswered` is true.

`Esc` first saves and exits an editor, then cancels if pressed outside editing. `Ctrl+C` always cancels. There is no draft persistence.

## Pi Editor and Markdown

A single Pi `Editor` instance is retargeted to the active text/custom/note/review value. Cursor movement, deletion, multiline protocol handling, IME behavior, and editor-specific keys are delegated to Pi Editor. The custom component exposes editor focus and forwards `invalidate()` so theme/render changes invalidate editor state.

Question/form prose is rendered through Pi TUI `Markdown` with colors and styles from the current Pi `Theme`. Editor content is rendered by Editor itself rather than Markdown.

## Width and viewport

`layout.ts` relies on Pi TUI's `visibleWidth`, `wrapTextWithAnsi`, and `truncateToWidth` so ANSI codes and multi-column Unicode are measured as terminal content rather than JavaScript string length. Every final line is width-limited.

The modal uses the full terminal width and height. Rendering computes `terminal.rows - 2`, reserves an overflow status row when needed, follows the active range by default, and clamps manual Page Up/Page Down scrolling. This isolates form scrolling from the underlying transcript.

## TUI-only boundary

Both public entry points check `ctx.mode` before touching custom UI. `runRichForm` also throws on non-TUI use as a defensive internal guard. The tool returns an actionable error object; the command notifies and returns. This boundary prevents unsupported custom-component calls in RPC, JSON, and print modes.
