/**
* Wrap-aware card-row → source-text translation for drag-selection text
* recovery (v1.1 item M4).
*
* v1's assembler indexed the entry's source lines by the card's VISUAL row,
* which equals the source line only when no line wraps. Any wrapped line
* desynchronizes the index and garbles the copied text. This module supplies
* the per-row mapping against the entry's SOURCE text (sanitized, with the
* assistant next-steps block stripped exactly like the renderer).
*
* Exactness boundary: the mapping is EXACT for plain prose and thinking text
* — MarkdownView renders those lines as-is and Ink wraps them with the same
* wrap-ansi call mirrored below. For assistant text containing markdown
* STRUCTURE (headings, lists, tables, fenced code), MarkdownView restyles
* the render (heading styles, bullet glyphs, table layout), so the visual
* rows can diverge from this source-text map. The copy semantics are
* deliberately source-based in that case: a drag recovers what the model
* wrote, not the renderer's decoration. Visual-row alignment for
* heavily-structured markdown is approximate and documented as such.
*
* The render truth this mirrors (verified against the components):
* - `assistant` cards (entry.tsx) render `parseNextSteps(text, true).stripped`
* through AssistantBody → MarkdownView, wrapped by Ink's Text at
* `assistantContentWidth(termWidth)` = termWidth − 2. Ink wraps via
* wrap-ansi with `{ trim: false, hard: true }` (ink/build/wrap-text.js).
* The markdown transform above applies only to structured spans; plain
* prose lines pass through unwrapped-by-markdown and are exact here.
* - `thinking` cards render sanitizeTerminalText(text) at the same width
* with no markdown transform — exact.
* - `user` cards render the `'👤 USER '` label inline before the text in
* the same bordered Text (entry.tsx), so row 0 = label cells then text;
* wrapped continuation rows start at the text. `pasteContent` renders as
* a second block (`' ↳ '` preview) after a newline; the copy base is
* `pasteContent || text` (copy-icon.ts), so a card with BOTH blocks shows
* two text regions while the base holds one — those keep the v1 naive
* mapping (assembler-side residual, documented here).
* - `info` cards are unbordered plain Text: the `'ℹ '` icon inline before
* the text, wrapped at the FULL termWidth (no panel gutter).
*
* Inline-prefix translation: the label/icon cells are chrome, not copyable
* text. buildBodyRowMap wraps `prefix + first line` together — the renderer
* concatenates them into one Text node before Ink wraps it, so the wrap
* boundaries are only correct when the prefix participates in the wrap — and
* records the prefix width in terminal cells (string-width, the project's
* measurer; 👤 is a surrogate pair — 2 code units AND 2 cells — so both
* shipped prefixes happen to have prefixWidth === prefix.length). resolveRowCol
* shifts row-0 columns past it, clamping prefix clicks to the text start:
* the same margin-click semantics M3 gives the card gutter. The clamp works
* in CELLS while segment ranges are in CHARACTERS — exact when the body's
* characters are single-cell, the same standing M4 approximation that
* already governs text content. Narrow panes where the label alone exceeds
* the content width produce label-only rows (zero-width spans) — that
* mirrors Ink's render exactly and is deliberately preserved: any divergence
* between the map and the renderer breaks WYSIWYG slicing.
*
* Origin: card row 0 = body row 0, matching v1's documented origin (one card
* row per source line when nothing wraps). Any chrome-row offset above the
* body is a preexisting v1 approximation outside M4's scope — preserved, not
* altered, so every pinned expectation keeps its exact meaning.
*
* The load-bearing invariant (verified empirically against wrap-ansi@10.0.1,
* the exact version ink 7.1.1 resolves): for each source line,
* `wrapAnsi(line, w, {trim:false, hard:true}).split('\n').join('') === line`.
* Segment prefix sums therefore give EXACT source offsets. For a non-wrapped
* line the single segment spans the whole line with `start = 0`, so every
* resolve here reduces to v1's math (`offset = col`, end = `col + 1`).
*/
import type { HistoryEntry } from './types.js';
/** One wrapped visual row inside a card body: the source line it shows and
* the half-open [start, end) character range of that line it covers. */
export interface BodyRowSpan {
line: number;
start: number;
end: number;
}
export interface BodyRowMap {
/** Per-card-row spans, index 0 = card row 0 (v1 origin). */
rows: readonly BodyRowSpan[];
/** The exact text the map indexes into — the render base (sanitized; for
* assistant entries with the next-steps block stripped exactly like the
* renderer). Slicing this text is WYSIWYG for the wrapped kinds. */
text: string;
/** Terminal CELLS of inline chrome rendered before the first text
* character on row 0 (user label / info icon). 0 for prefix-less kinds. */
prefixWidth: number;
}
/** Inline label the renderer places before a user card's text
* (entry.tsx: `👤 USER ` then the text, one Text node).
* Exported so entry.tsx consumes the single source (p3) — drift between the
* render and this constant silently mis-offsets user-card selection. */
export declare const USER_LABEL = "\uD83D\uDC64 USER ";
/** Inline icon the renderer places before an info card's text
* (entry.tsx: `ℹ ` then the text, one Text node). */
export declare const INFO_PREFIX = "\u2139 ";
/** Kinds whose render path this module mirrors. Everything else uses the
* v1 naive mapping in assembleSelectionText. Declared as a type predicate
* (taking the entry, not the kind) so call sites narrow the HistoryEntry
* union — every member kind carries `text`, the others may not. The
* assembler additionally restricts user cards to the no-pasteContent shape
* (see selection-helpers): a card with a paste block renders two text
* regions but the copy base holds one, so those stay on the v1 path. */
export declare function hasWrapMap(entry: HistoryEntry): entry is Extract;
/**
* Build the card-row → (source line, char range) map for one text card.
* Pure and deterministic from (kind, text, termWidth).
*
* For prefixed kinds (user label, info icon) line 0 is wrapped AS the
* renderer sees it — `prefix + line` in one Text node — so wrap boundaries
* match the visual rows; each segment is then translated back to
* text-local character coordinates (the prefix chars subtracted, clamped).
*/
export declare function buildBodyRowMap(kind: HistoryEntry['kind'], rawText: string, termWidth: number): BodyRowMap;
/**
* Resolve the (source line, char offset) a card row + column points at,
* clamping out-of-range rows to the last body row and columns to the
* wrapped segment.
*
* Column semantics match v1: `col` is a body-local character offset (v1
* sliced the source line by it directly), so a non-wrapped line resolves to
* exactly v1's numbers. `atEnd` is set for an inclusive drag endpoint (end
* of a selection range): the offset then counts one past the column,
* clamped to the segment's end — mirroring v1's `endCol + 1`.
*/
export declare function resolveRowCol(map: BodyRowMap, row: number, col: number, atEnd: boolean): {
line: number;
offset: number;
};
//# sourceMappingURL=wrap-geometry.d.ts.map