Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
phase: design
title: Agent Console Markdown Preview Design
description: A safe Markdown-token-to-styled-terminal-row pipeline with memoized active-preview layout and visible-row slicing.
---

# Agent Console Markdown Preview Design

## Architecture Overview

```mermaid
flowchart LR
M[20-message conversation tail] --> S[Sanitize terminal controls]
S --> P[Parse supported Markdown tokens]
P --> L[Lay out styled physical rows at preview width]
L --> C[Component-local memoized rows]
C --> V[Clamp and slice viewport by scrollOffset]
V --> I[Render visible Ink rows only]
```

`PreviewPane` retains the current state selection and scroll-offset adjustment. Message content and the explicit content width are the only inputs to the memoized parse/layout stage. `scrollOffset` is consumed only by the cheap viewport stage. `PreviewSection` passes the current right-pane width; narrow mode remains unchanged because the preview is not newly exposed there.

## Data Models

```ts
interface PreviewSpan {
text: string;
bold?: boolean;
italic?: boolean;
dimColor?: boolean;
color?: string;
backgroundColor?: string;
}

interface PreviewViewportRow {
kind: 'header' | 'content' | 'separator' | 'indicator';
spans: PreviewSpan[];
role: ConversationMessage['role'] | null;
timestamp?: string;
}
```

- Header/separator/indicator rows preserve existing semantics.
- Content rows contain already wrapped styled spans. Their plain display width never exceeds the supplied content width except where a single terminal grapheme itself cannot be split.
- Layout data exists only inside the mounted active `PreviewPane` memo for the current message array and width.

## API Design

- `PreviewSectionProps.contentWidth: number` carries `max(1, inputInnerWidth - 2)` from `ConsoleApp`: panel borders/padding are already removed by `inputInnerWidth`, and two columns are reserved for the existing message-body indent.
- `PreviewPaneProps.contentWidth?: number` defaults conservatively for direct tests/callers.
- Pure helpers parse and lay out messages, build a viewport from stable rows, and render row spans.
- No external API, persistence, schema, polling, conversation-tail, or focus-key contract changes.

## Component Breakdown

- `PreviewPane.tsx`: state preservation, message-row memoization, viewport slicing, and visible-row Ink rendering.
- `markdownPreview.ts` (or equivalently small render module): sanitization, Marked token traversal, inline style flattening, safe fallbacks, terminal-width wrapping, and message row construction.
- `PreviewSection.tsx` / `ConsoleApp.tsx`: width plumbing only.
- Focused test modules: pure Markdown/layout coverage plus `PreviewPane` render and offset-only rerender regression coverage.

## Rendering Rules

- Headings: bold accent text; heading markers are not displayed.
- Bold/emphasis: Ink bold/italic attributes.
- Inline code: distinct existing palette color; fenced code uses a dim fence label when present and indented literal lines, without highlighting.
- Lists: `•` or parser-provided ordered numbers with hanging indentation.
- Blockquotes: dim `│ ` prefix with recursively rendered body.
- Links: styled label followed by a dim plain ` (URL)` when the destination differs from the label.
- Raw HTML: sanitized literal source text, never passed to an HTML/ANSI interpreter.
- Images: safe text fallback containing alt text and destination, never fetched or rendered.
- Unsupported/malformed constructs: sanitized literal or parser text fallback; parsing exceptions fall back to sanitized source lines.
- Control handling: strip ANSI/OSC/C0 control sequences except source newlines/tabs before parsing and layout; rendered spans never contain terminal control characters.

## Design Decisions

- Add `marked` as a direct CLI dependency because it already has repository precedent, exposes block/inline tokens, and avoids a bespoke grammar.
- Add or directly declare a terminal display-width utility if required; do not rely on an undeclared transitive dependency.
- Keep parsing and layout in one `useMemo([messages, width])`. This is the smallest cache satisfying offset-only optimization and is bounded by the active 20-message tail/current width.
- Slice the viewport before mapping rows to Ink elements. No binary search or transcript virtualization is needed for twenty messages.
- Preserve numeric bottom-relative offsets and current appended-row adjustment. Frozen updates and semantic anchors remain explicit non-goals.

## Non-Functional Requirements

- **Performance:** offset-only rerenders reuse stable laid-out rows; viewport work is O(visible rows) for React element creation and O(1) bounds plus array slice for selection.
- **Memory:** one active message array's parsed/laid-out result at one width; released when messages, width, selection, or component lifetime changes.
- **Security:** no raw HTML, image retrieval, OSC hyperlinks, ANSI, or terminal controls; URLs remain inert displayed text.
- **Reliability:** parser exceptions and malformed tokens fall back to sanitized text without affecting preview state handling.
- **Compatibility:** preserve current props with optional width defaults where useful, and minimize overlap with open cached-preview metadata work.
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
---
phase: implementation
title: Agent Console Markdown Preview Implementation
description: Current implementation structure, decisions, integration points, safety, and performance notes.
---

# Agent Console Markdown Preview Implementation

## Development Setup

- Worktree: `.worktrees/feature-agent-console-markdown-preview`
- Branch: `feature-agent-console-markdown-preview`
- Bootstrap: `npm ci` (completed; Husky could not update the shared sandboxed git config, while package installation completed).
- Runtime dependencies added: `marked` aligned with the repository's existing channel-connector major version and `string-width` for terminal display columns.

## Code Structure

- `packages/cli/src/tui/console/render/markdownPreview.ts`: pure Markdown token-to-styled-span/row renderer (in progress).
- `packages/cli/src/__tests__/tui/console/render/markdownPreview.test.ts`: focused TDD specifications.
- `PreviewPane.tsx`: memoized Markdown row construction, viewport slicing, and visible styled-span rendering.
- `PreviewSection.tsx` / `ConsoleApp.tsx`: explicit preview content-width plumbing from terminal layout.

## Implementation Notes

### Completed

- Tasks 1.1–1.2: Marked lexer traversal for headings, paragraphs/text, bold, emphasis, inline code, fenced code, ordered/unordered lists, blockquotes, and links.
- Code fences produce optional dim language labels and literal warning-colored, indented lines without highlighting.
- Lists use deterministic bullets/numbers and continuation indentation; blockquotes use a dim terminal bar; links display styled labels plus inert plain destinations.
- Styled spans reuse existing `TUI_COLORS`; no ANSI or terminal hyperlink output is generated.
- TDD evidence: five focused behaviors each failed before their minimum implementation and now pass together.
- Tasks 2.1–2.2: raw HTML/table source stays inert, images become dim label/destination fallbacks, VT/C0 controls are removed before parsing, empty content retains one row, and styled rows wrap by grapheme display width with hanging prefixes.
- Markdown soft/source line breaks are split before viewport accounting so one row never hides multiple physical terminal lines.
- Current focused evidence: 11 tests passed.

### In progress / next

- Formal implementation alignment, coverage, broader regression checks, and review.

### Patterns & Best Practices

- Pure token traversal and immutable span objects.
- Unsupported block tokens retain sanitized raw lines; images have an explicit inert fallback.
- Production behavior is added only after its focused test fails for the intended reason.

## Integration Points

- Marked is a direct CLI runtime dependency; no channel-connector internals are imported.
- Conversation acquisition, polling, 20-message tailing, focus routing, and channel state are unchanged.
- `computeLayout` derives `previewContentWidth` from `inputInnerWidth - 2`; `PreviewSection` passes it directly to `PreviewPane`.

## Error Handling

- Parser errors fall back to sanitized literal rows; Marked also safely tokenizes incomplete Markdown without executing content.

## Performance Considerations

- The pure renderer wraps by Unicode grapheme display width, prefers word boundaries, and repeats or hangs list/quote/code prefixes where space permits.
- `PreviewPane` memoizes complete parse/layout rows only on `[messages, contentWidth]`; scroll offset is excluded.
- The viewport is computed from stable rows and sliced before Ink elements are mapped.
- No global cache exists; memory is bounded by the mounted active preview's current 20-message array and width.

## Security Notes

- Current output is data-only span objects.
- ANSI/OSC/VT controls and unsafe C0/C1 characters are removed before parsing; raw HTML remains literal, images are never fetched, and links remain inert display text.

## Design Deviations

None known. The implementation follows the selected direct-parser/pure-row architecture without adding excluded cache, anchoring, history, toggle, highlighting, or virtualization scope.

## Changed Files

- `packages/cli/package.json`, `package-lock.json`: direct `marked` and `string-width` runtime dependencies.
- `packages/cli/src/tui/console/render/markdownPreview.ts`: pure safe parser, styling, fallback, and display-width layout.
- `packages/cli/src/tui/console/PreviewPane.tsx`: styled row model, memoized parse/layout, viewport-first rendering.
- `packages/cli/src/tui/console/PreviewSection.tsx`, `ConsoleApp.tsx`: explicit content-width flow.
- `packages/cli/src/__tests__/tui/console/render/markdownPreview.test.ts`: syntax, safety, malformed, Unicode, and wrapping tests.
- `packages/cli/src/__tests__/tui/console/PreviewPane.test.ts`, `computeLayout.test.ts`: render/state/viewport/memoization/width integration tests.

## Verification to Date

- `npm test --workspace ai-devkit -- markdownPreview.test.ts PreviewPane.test.ts computeLayout.test.ts`: 40 tests passed before the final Phase 8 additions.
- `npm run build --workspace ai-devkit`: 196 files compiled; declaration typecheck passed.
- `npm run lint --workspace ai-devkit`: exit 0 with five pre-existing warnings and no errors.
- `npm run test:coverage --workspace ai-devkit`: 82 files / 984 tests passed; global coverage thresholds passed.
- Targeted changed-file coverage: `markdownPreview.ts` 95.9% lines and `PreviewPane.tsx` 98.2% lines.

## Phase 9 Review

- **Blocking findings:** none.
- **Important findings:** none.
- Design, requirements, and non-goals align with the final code.
- Export/caller tracing found only internal preview helper contracts; all in-repo callers and tests were updated.
- `marked@15.0.12` is deduplicated with the channel connector and `string-width@8.2.2` is a direct CLI dependency.
- No migration, persisted state, external API, irreversible operation, or rollback hazard exists.
- Entity-encoded controls stay literal, raw HTML stays text, images are never fetched, and links are plain displayed destinations.

## Conscious Limitations

- Markdown remains the explicit MVP subset; tables and other unsupported blocks display sanitized source text.
- Extremely narrow widths may place an indivisible wide grapheme beyond the requested single column because terminal graphemes cannot be split safely.
- Component-local layout is rebuilt on message-array or width changes; only offset-only rerenders are guaranteed to do zero parse/layout work.
- Numeric bottom-relative scroll offsets remain; frozen updates and semantic anchors are excluded by design.
105 changes: 105 additions & 0 deletions docs/ai/planning/2026-08-15-feature-agent-console-markdown-preview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
---
phase: planning
title: Agent Console Markdown Preview Plan
description: Ordered TDD implementation, integration, safety, responsiveness, documentation, and verification tasks.
---

# Agent Console Markdown Preview Plan

## Milestones

- [x] Milestone 1: Pure safe Markdown-to-terminal-row renderer is complete.
- [x] Milestone 2: Width-aware preview integration preserves scrolling and visible-row-only rendering.
- [x] Milestone 3: Documentation, coverage, verification, and review gates pass.

## Task Breakdown

### Phase 1: Markdown row foundation

- [x] **Task 1.1 — Establish direct dependencies and core inline rendering.**
- Outcome: CLI directly declares the parser/display-width dependencies; headings, paragraphs, bold, emphasis, and inline code produce styled spans.
- Dependencies: approved design; repository Marked precedent.
- TDD: add a focused failing `markdownPreview.test.ts`, run red, install dependencies, implement the minimum parser/token mapping, run green/refactor.
- Validation: targeted unit test, CLI lint/typecheck as useful.
- Testing scenarios: Markdown parsing/layout items 1 and 2 (inline portion).
- [x] **Task 1.2 — Add fenced code, lists, blockquotes, and links.**
- Outcome: remaining MVP constructs render as deterministic prefixed rows and inert label-plus-URL spans.
- Dependencies: Task 1.1 token/span model.
- TDD: one failing behavior at a time for code, lists, quotes, and links, followed by minimal implementation and refactor.
- Validation: targeted renderer tests.
- Testing scenarios: Markdown parsing/layout items 2–4.

### Phase 2: Safety and physical terminal layout

- [x] **Task 2.1 — Add safe unsupported/malformed fallbacks and control sanitization.**
- Outcome: HTML/images/tables/malformed source remains readable and inert; ANSI, OSC, and unsafe C0 characters cannot affect the terminal.
- Dependencies: Tasks 1.1–1.2.
- TDD: failing fixtures for each unsafe/fallback class before production changes.
- Validation: targeted safety tests and coverage branches.
- Testing scenarios: Markdown parsing/layout items 5–7 and 9.
- [x] **Task 2.2 — Add display-width wrapping with styled-span preservation.**
- Outcome: content rows reflect physical terminal width, including prefixes, hanging indentation, Unicode, long URLs, and narrow widths.
- Dependencies: stable span/block output from Task 2.1.
- TDD: failing narrow/Unicode/long-token fixtures, then minimum wrapping implementation.
- Validation: targeted layout tests at narrow and normal widths.
- Testing scenarios: Markdown parsing/layout item 8; viewport narrow-width scenario.

### Phase 3: Preview integration and optimization

- [x] **Task 3.1 — Integrate styled rows and explicit content width.**
- Outcome: `ConsoleApp` passes `inputInnerWidth - 2`; `PreviewPane` memoizes parse/layout by messages and width, preserves all existing states/headers, and renders styled spans.
- Dependencies: Task 2.2.
- TDD: failing Ink render/width tests before component edits.
- Validation: existing and new `PreviewPane` render tests.
- Testing scenarios: all integration tests plus viewport clamp/indicator behavior.
- [x] **Task 3.2 — Prove viewport-only scroll work.**
- Outcome: viewport slicing occurs before Ink row construction; offset-only rerenders read/lay out source once and reuse stable row objects; append/bottom behavior remains unchanged.
- Dependencies: Task 3.1.
- TDD: failing instrumentation and off-viewport render tests before optimization edits.
- Validation: component rerender, row identity, visible-content, clamp, indicator, and appended-row tests.
- Testing scenarios: all viewport/responsiveness and performance scenarios.

### Phase 4: Documentation and quality gates

- [x] **Task 4.1 — Reconcile implementation/testing documents.**
- Outcome: changed files, decisions, deviations, edge cases, test links, checkboxes, and results are current.
- Dependencies: implementation tasks complete.
- Validation: feature lint.
- [x] **Task 4.2 — Run testing, coverage, build, lifecycle lint, and holistic review.**
- Outcome: fresh evidence supports readiness; no blocking design, security, performance, integration, or test finding remains.
- Dependencies: Task 4.1.
- Validation: targeted tests/coverage, CLI lint/build, feature-doc lint, relevant broader tests, git diff review.

## Dependencies

- Task order is intentional: tokens/spans → remaining syntax → safety → wrapping → integration → optimization proof → docs/quality gates.
- `marked` and `string-width` must be direct CLI dependencies, not undeclared transitive imports.
- The existing 20-message conversation hook, polling hooks, focus routing, and narrow-mode visibility are not modified.
- Rebase against current `origin/main` before push; if open PR #162 lands, preserve its cached-preview props/metadata during conflict resolution.

## Timeline & Estimates

- Foundation and syntax: small-to-medium.
- Safety/wrapping: medium and highest correctness risk.
- Integration/optimization tests: medium.
- Documentation/verification/review: small-to-medium.

## Risks & Mitigation

- **Styled wrapping corrupts formatting or widths:** keep pure span/row helpers and test Unicode, prefixes, long tokens, and multiple widths.
- **Malformed tokens crash traversal:** exhaustive safe defaults plus top-level parser fallback.
- **Terminal injection:** sanitize before parsing and again at emitted span boundaries; never emit terminal hyperlinks.
- **Offset rerenders regress CPU:** make memo dependencies explicit and test invocation/object identity.
- **Open console PR conflict:** minimize surface, inspect PR #162, fetch/rebase immediately before submission, and rerun validation.
- **Dependency churn:** align Marked major with existing repository usage and declare only required runtime packages.

## Resources Needed

- One isolated feature worktree on `feature-agent-console-markdown-preview`.
- Existing Ink/Vitest test helpers and console responsiveness tests.
- Marked token patterns already used by `packages/channel-connector`.
- AI DevKit task tracing and lifecycle documentation.

## Progress Summary

All planned tasks are complete. Forty-two focused tests plus the full 984-test CLI coverage suite cover the renderer, malformed fallback, preview integration, exact terminal width, preserved states, current viewport behavior, offset-only zero parse/layout calls, and visible-row-only Ink output. Phase 9 found no blocking or important issues. No blockers or scope changes are known.
Loading
Loading