389 lines
28 KiB
Markdown
389 lines
28 KiB
Markdown
# Inline Markdown Images in AI Block List — Tech Spec
|
||
Product spec: `specs/zachlloyd/inline-markdown-images-in-blocklist/PRODUCT.md`
|
||
|
||
## Problem
|
||
The AI block list does not currently support the product behavior described in `PRODUCT.md`: rendering supported Markdown images and Mermaid diagrams inline inside AI responses with the approved inline-row, grouped-image, and Mermaid-card treatments.
|
||
|
||
Warp already has the underlying primitives needed to build this behavior:
|
||
|
||
- local image asset loading
|
||
- responsive image sizing
|
||
- Mermaid-to-SVG rendering
|
||
- block-list rendering of other markdown section types like code and tables
|
||
|
||
Today, block-list markdown is split into plain text, code, and table sections. Plain text is rendered with `FormattedTextElement`, which intentionally treats `FormattedTextLine::Image` as a line break rather than a visual block. As a result, Markdown image syntax degrades to raw text in the AI block list, and Mermaid fences continue to render through the existing code-path behavior rather than the product's visual treatment.
|
||
|
||
The implementation should add support for the required file-backed image and Mermaid behaviors while keeping the change narrowly scoped:
|
||
|
||
- no new cross-renderer selection model
|
||
- no broad markdown-renderer refactor
|
||
- no new dependency edge from the block list into a rendering stack it does not already use
|
||
|
||
This should be a surgical extension of the existing section-based block-list renderer, with explicit copy behavior for rendered visuals, a visible file-link label below successfully rendered file-backed images, raw-Markdown fallback when rendering is unavailable, and reuse of Warp’s existing fullscreen lightbox treatment when the user clicks a rendered visual.
|
||
|
||
## Relevant Code
|
||
- `specs/zachlloyd/inline-markdown-images-in-blocklist/PRODUCT.md` — approved product behavior
|
||
- `app/src/ai/agent/util.rs (24-186)` — `parse_markdown_into_text_and_code_sections`; current block-list markdown section splitter
|
||
- `app/src/ai/agent/mod.rs (1281-1361)` — `AgentOutputText`, `AgentOutputTable`, and `AIAgentTextSection`
|
||
- `app/src/ai/agent/mod.rs (405-505)` — `AIAgentOutput::all_text`; current message traversal used by rendering and link detection
|
||
- `app/src/ai/agent/mod.rs (1547-1628)` — `Display for AIAgentOutputMessage`; block/conversation copy formatting for markdown sections
|
||
- `app/src/ai/agent/mod.rs (2722-2779)` — `AIAgentExchange::format_output_for_copy` and `format_for_copy`
|
||
- `app/src/ai/blocklist/block/view_impl/output.rs (246-345)` — message-level block-list output rendering loop
|
||
- `app/src/ai/blocklist/block/view_impl/common.rs (869-952)` — `render_text_sections`; current section-to-UI mapping
|
||
- `app/src/ai/blocklist/block/view_impl/common.rs (986-1084)` — `render_rich_text_output_text_section`; current plain-text markdown renderer
|
||
- `app/src/ai/blocklist/block/view_impl/common.rs (1461-1646)` — `render_code_output_section`; current code-block renderer
|
||
- `app/src/workspace/lightbox_view.rs` — shared fullscreen lightbox view with Escape and left/right navigation
|
||
- `crates/ui_components/src/lightbox.rs` — reusable lightbox component and navigation controls
|
||
- `app/src/workspace/action.rs` — workspace actions for opening and updating the shared lightbox
|
||
- `app/src/workspace/view.rs` — workspace-level lightbox lifecycle and focus handling
|
||
- `app/src/ai/blocklist/block.rs (2230-2246)` — per-section handle allocation for code and tables
|
||
- `app/src/ai/blocklist/block.rs (4313-4476)` — `selected_text`, `clear_all_selections`, and `clear_other_selections`; current selection model
|
||
- `app/src/ai/blocklist/block.rs (4946-5002)` — block-level copy scope
|
||
- `app/src/ai/blocklist/controller.rs (62-86)` — `SessionContext`; current working-directory capture
|
||
- `app/src/ai/blocklist/persistence.rs (24-31)` — persisted exchange `working_directory`
|
||
- `app/src/ai/blocklist/history_model.rs (2102-2154)` — restored query history carries working directory
|
||
- `app/src/ai/blocklist/block.rs (906-919)` — `AIBlock::new` accepts `current_working_directory` and `shell_launch_data`
|
||
- `crates/markdown_parser/src/markdown_parser.rs (286-326)` — `parse_image`; current image recognition behavior
|
||
- `crates/markdown_parser/src/markdown_parser.rs (466-475)` — `parse_inline_markdown`; current inline parser surface
|
||
- `crates/markdown_parser/src/markdown_parser_test.rs (2171-2320)` — current parser coverage for images
|
||
- `crates/warpui_core/src/elements/formatted_text_element.rs (1580-1591, 1661-1671)` — images are currently treated as line breaks in the block-list rich-text path
|
||
- `crates/editor/src/content/text.rs (280-362)` — image markdown round-trip behavior in the editor stack
|
||
- `crates/editor/src/content/text.rs (526-579)` — `CodeBlockType::Mermaid` gating
|
||
- `crates/editor/src/content/edit.rs (56-91)` — native/WASM asset-source resolution rules
|
||
- `crates/editor/src/content/mermaid_diagram.rs (20-67)` — in-memory Mermaid SVG asset generation and sizing
|
||
- `crates/warpui_core/src/image_cache.rs` — supported shared image types (JPEG, PNG, GIF, WebP, SVG)
|
||
- `crates/warp_features/src/lib.rs (525-540, 817-858)` — markdown-related feature flags
|
||
- `app/src/lib.rs (2462-2471)` — app-side feature-flag wiring for markdown tables and Mermaid
|
||
- `app/Cargo.toml (667-746)` — compile-time feature declarations
|
||
- `app/src/ai/agent/util_tests.rs` — block-list markdown parser/unit test pattern
|
||
- `crates/editor/src/content/edit_tests.rs (293-404)` — Mermaid layout test pattern
|
||
- `crates/editor/src/content/text_tests.rs (101-127)` — Mermaid block-type gating tests
|
||
- `crates/integration/tests/INTEGRATION_TESTING.md` — integration test registration flow
|
||
|
||
## Current State
|
||
|
||
### Markdown parsing in the AI block list
|
||
The block list currently parses AI markdown with a line-oriented splitter in `app/src/ai/agent/util.rs (24-186)`. It recognizes:
|
||
|
||
- plain markdown text
|
||
- fenced code blocks, including linked-code metadata
|
||
- GFM tables
|
||
|
||
It does not emit explicit image or Mermaid section types today. Standard Markdown image syntax therefore remains embedded inside `AIAgentTextSection::PlainText`, and Mermaid code fences remain ordinary `Code` sections.
|
||
|
||
### Current Markdown semantics vs current parser behavior
|
||
Standard Markdown/CommonMark uses `` as image syntax. There is not a separate standard syntax that means “this should be shown as a literal file path instead of rendered as an image.” If the author wants literal text, they escape the Markdown or place it in code spans/blocks.
|
||
|
||
Our current `markdown_parser` does not fully implement that inline image semantic yet. `crates/markdown_parser/src/markdown_parser.rs (286-326)` only recognizes images when they occupy a standalone line; inline paragraph images like `text  more text` are currently treated as plain text by this parser. For this redesign, the block list still should not invent a generic “render some arbitrary inline image-looking text” rule. The only extension should be a narrow line parser for runs of parser-compatible image references separated only by whitespace so we can distinguish:
|
||
|
||
- consecutive standalone image lines, which stay block-level
|
||
- multiple image references on the same line, which become an inline image row
|
||
|
||
### Why images disappear today
|
||
Plain-text markdown sections are rendered with `render_rich_text_output_text_section` in `app/src/ai/blocklist/block/view_impl/common.rs (986-1084)`, which delegates to `FormattedTextElement`. In `crates/warpui_core/src/elements/formatted_text_element.rs (1580-1591, 1661-1671)`, `FormattedTextLine::Image(_)`, `FormattedTextLine::Embedded(_)`, and `FormattedTextLine::HorizontalRule` are all treated as line-break-like layout items rather than renderable content. That is the immediate reason that block-list Markdown images never show up.
|
||
|
||
### Asset and Mermaid support already exists elsewhere
|
||
Warp already has the low-level capabilities this feature needs:
|
||
|
||
- shared image format support in `crates/warpui_core/src/image_cache.rs`
|
||
- asset-source resolution, including WASM-safe behavior, in `crates/editor/src/content/edit.rs (56-91)`
|
||
- Mermaid SVG generation and sizing in `crates/editor/src/content/mermaid_diagram.rs (20-67)`
|
||
- Mermaid code-block identification in `crates/editor/src/content/text.rs (526-579)`
|
||
|
||
The app crate already depends on `warp_editor`, and the block list already embeds editor-backed code blocks. Reusing editor Mermaid/image helpers therefore does not introduce a new crate dependency edge.
|
||
|
||
Warp also already has a reusable fullscreen lightbox path at the workspace layer. `WorkspaceAction::OpenLightbox` / `UpdateLightboxImage` drive `LightboxView`, which already supports Escape dismissal, left/right keyboard navigation, and previous/next buttons. The block-list visual renderer should reuse that path rather than inventing a new fullscreen viewer.
|
||
|
||
### Working-directory metadata already exists
|
||
The product requirement for resolving relative paths against the working directory captured when the AI block rendered is already compatible with existing data flow:
|
||
|
||
- session context captures `current_working_directory` in `app/src/ai/blocklist/controller.rs (62-86)`
|
||
- persisted exchanges store `working_directory` in `app/src/ai/blocklist/persistence.rs (24-31)`
|
||
- restored history loads it back in `app/src/ai/blocklist/history_model.rs (2102-2154)`
|
||
- `AIBlock::new` already receives both `current_working_directory` and `shell_launch_data`
|
||
|
||
We do not need a new persistence field for this feature; we need to thread existing metadata into the visual-section renderers.
|
||
|
||
### Selection is intentionally out of scope
|
||
The current selection model in `app/src/ai/blocklist/block.rs (4313-4476)` is fragmented across:
|
||
|
||
- block-level text selection
|
||
- child-view-native selection for code editors and other special renderers
|
||
|
||
That architecture does not support a continuous mixed-content selection model across text, code, tables, images, and Mermaid. This feature should not attempt to fix that. Instead, it should:
|
||
|
||
- preserve existing text/code/table selection behavior
|
||
- add explicit copy affordances for rendered images and Mermaid diagrams
|
||
- ensure block-level copy/export includes the correct raw Markdown for visual sections
|
||
|
||
## Proposed Changes
|
||
|
||
### 1. Add a dedicated block-list image feature flag
|
||
Add a new feature flag dedicated to AI block-list markdown visuals, e.g. `BlocklistMarkdownImages`.
|
||
|
||
Implementation points:
|
||
- add `BlocklistMarkdownImages` to `crates/warp_features/src/lib.rs`
|
||
- add `blocklist_markdown_images = []` to `app/Cargo.toml`
|
||
- wire it in `app/src/lib.rs` alongside the existing markdown table/Mermaid flags
|
||
- enable it in `DOGFOOD_FLAGS`, but leave it out of `PREVIEW_FLAGS` until the surface is stable
|
||
|
||
Mermaid rendering in the block list should require:
|
||
- `FeatureFlag::BlocklistMarkdownImages`
|
||
- `FeatureFlag::MarkdownMermaid`
|
||
|
||
File-backed images in the block list should require only `BlocklistMarkdownImages`.
|
||
|
||
I do not recommend reusing the dormant generic `MarkdownImages` flag for this launch. It is not currently wired through `app/src/lib.rs` or `app/Cargo.toml`, and this feature needs rollout control specific to the AI block list.
|
||
|
||
### 2. Extend the AI markdown section model with visual sections
|
||
Extend `AIAgentTextSection` in `app/src/ai/agent/mod.rs` with explicit visual section variants:
|
||
|
||
- `Image { image: AgentOutputImage }`
|
||
- `MermaidDiagram { diagram: AgentOutputMermaidDiagram }`
|
||
|
||
Proposed payloads:
|
||
- `AgentOutputImage`
|
||
- `alt_text: String`
|
||
- `source: String`
|
||
- `markdown_source: String`
|
||
- `layout: AgentOutputImageLayout`
|
||
- `AgentOutputMermaidDiagram`
|
||
- `source: String`
|
||
- `markdown_source: String`
|
||
|
||
The key design choice is to preserve Markdown source on the section payload rather than reconstructing it later from rendered state. That keeps:
|
||
|
||
- block-level copy/export exact
|
||
- right-click copy trivial
|
||
- fallback rendering simple
|
||
|
||
For images, `markdown_source` can initially be canonicalized to `` because the parser only returns `alt_text` and `source`, not byte-accurate source spans. That is consistent with the editor stack’s current image markdown round-trip behavior.
|
||
|
||
### 3. Teach the section splitter to extract images and Mermaid explicitly
|
||
Update `parse_markdown_into_text_and_code_sections` in `app/src/ai/agent/util.rs` so that it can distinguish inline image runs from block-level image lines instead of treating every extracted image section identically.
|
||
|
||
The change should remain incremental:
|
||
- keep current code-fence boundary detection so linked-code metadata parsing stays unchanged
|
||
- keep current table extraction behavior
|
||
- for plain-text regions, continue scanning line-by-line
|
||
- keep using `markdown_parser` for standalone image-line extraction
|
||
- add a narrow helper that recognizes lines containing two or more parser-compatible image references separated only by whitespace and emits them as `AIAgentTextSection::Image` sections tagged with `AgentOutputImageLayout::Inline`
|
||
- emit standalone image lines as `AIAgentTextSection::Image` tagged with `AgentOutputImageLayout::Block`
|
||
- when a fenced code block language resolves to Mermaid, emit `AIAgentTextSection::MermaidDiagram` instead of `Code`
|
||
- leave everything else in `PlainText`
|
||
|
||
Two important constraints:
|
||
- image extraction should still stay narrow and markdown-driven rather than becoming a generic loose regex over arbitrary prose
|
||
- Mermaid section extraction should not be gated at parse time; the section payload should still preserve Mermaid source even when the runtime flags are off, so the renderer can fall back cleanly to raw Markdown without reparsing restored conversations
|
||
|
||
### 4. Keep the existing section renderer and add visual sections surgically
|
||
Do not replace the current block-list markdown rendering architecture for this feature. Instead, extend `render_text_sections` in `app/src/ai/blocklist/block/view_impl/common.rs (869-952)` with grouped visual renderers:
|
||
|
||
- inline image-row rendering for consecutive `AgentOutputImageLayout::Inline` sections
|
||
- grouped block-image row rendering for consecutive `AgentOutputImageLayout::Block` sections
|
||
- a Mermaid card renderer that uses the new framed treatment
|
||
|
||
These should follow the same overall section-rendering pattern already used for:
|
||
- code sections
|
||
- table sections
|
||
|
||
This keeps the implementation targeted:
|
||
- no unified markdown content view
|
||
- no mixed-content selection refactor
|
||
- no change to `AIBlock::selected_text` semantics beyond whatever is needed for explicit image/Mermaid copy affordances
|
||
|
||
As part of that renderer pass, build a source-ordered collection of the successfully renderable visual sections in the current AI message. That shared collection should include both successfully rendered file-backed images and successfully rendered Mermaid diagrams. Clicking any rendered image or Mermaid diagram should dispatch `WorkspaceAction::OpenLightbox` with that shared collection and the clicked section’s initial index, so previous/next navigation works across the other renderable visuals from the same message.
|
||
|
||
### 5. Reuse existing editor helpers without a new dependency edge
|
||
The new visual-section renderers should reuse existing editor helpers rather than inventing a separate rendering stack.
|
||
|
||
For file-backed images:
|
||
- extract or adapt the existing asset-resolution helper in `crates/editor/src/content/edit.rs (56-91)` so the block list can resolve relative paths against a base directory rather than a document path
|
||
- pass the AI block’s stored working directory as that base directory
|
||
- on native platforms, canonicalize when possible
|
||
- on WASM, preserve the existing no-canonicalization behavior
|
||
|
||
For Mermaid:
|
||
- use `mermaid_asset_source` from `crates/editor/src/content/mermaid_diagram.rs (20-67)`
|
||
- derive the rendered max width from the loaded asset's intrinsic size rather than calling a separate Mermaid layout helper from the block-list renderer
|
||
- honor `FeatureFlag::MarkdownMermaid` in addition to the new block-list image flag
|
||
|
||
For actual rendering:
|
||
- use a block-list-local renderer in the existing section flow
|
||
- use WarpUI image elements and existing block-list spacing/styling conventions
|
||
- preserve the existing right-click-to-copy Markdown behavior on every rendered visual
|
||
- for inline image runs, render medium-height image tiles with per-image labels below
|
||
- for grouped block images, render stacked thumbnail-plus-path rows using the full source path text
|
||
- for Mermaid, render a bordered card shell with a titled header and a white inner canvas that contains the diagram
|
||
- on left click, open the shared workspace lightbox instead of a block-list-specific fullscreen modal for both rendered file-backed images and rendered Mermaid diagrams
|
||
- keep the path labels on the shared link-detection pipeline so hover/click behavior remains unchanged
|
||
- allow the implementation to harden shared image/text primitives where needed for this surface, including guarding non-finite image rects, exposing intrinsic SVG dimensions through shared image metadata, and constraining single-line text hit testing to valid Y bounds
|
||
|
||
This follows the same broad pattern as the block list’s existing special markdown sections: section-specific render helpers living inside the current block-list message renderer, with reusable lower-level helpers coming from crates the app already depends on.
|
||
|
||
### 6. Render fallback content instead of broken visuals
|
||
The renderer should decide per visual section whether it can render as a visual block or must fall back to raw Markdown.
|
||
|
||
For file-backed images:
|
||
- if `BlocklistMarkdownImages` is disabled, render `markdown_source`
|
||
- if the path cannot be resolved, render `markdown_source`
|
||
- if the asset fails to load or is an unsupported image type, render `markdown_source`
|
||
- only include images in an inline row or grouped block row set when that individual image succeeds; unsupported or missing images fall back to raw Markdown in source order
|
||
|
||
For Mermaid:
|
||
- if either `BlocklistMarkdownImages` or `MarkdownMermaid` is disabled, render `markdown_source`
|
||
- if Mermaid SVG generation fails, render `markdown_source`
|
||
|
||
This keeps fallback behavior local to rendering and avoids mutating stored section payloads based on transient failures.
|
||
|
||
### 7. Make block-level copy and export section-aware
|
||
Update the markdown copy/export paths in `app/src/ai/agent/mod.rs (1547-1628, 2722-2779)` so that `Image` and `MermaidDiagram` sections serialize from their preserved Markdown source.
|
||
|
||
Rules:
|
||
- block-level output copy uses `markdown_source` for image and Mermaid sections
|
||
- conversation export continues to be markdown, not image bytes or HTML
|
||
- right-click copy on a rendered image or Mermaid diagram writes that section’s `markdown_source`
|
||
|
||
This gives us correct image/Mermaid copy behavior without changing the existing selection architecture.
|
||
|
||
### 8. Render the file-link label with local link detection
|
||
The new file-link label under rendered file-backed images should reuse the existing text/link rendering helpers rather than introducing a special-case link widget.
|
||
|
||
Implementation shape:
|
||
- keep the current global output link-detection pipeline unchanged for the underlying markdown section data
|
||
- in the successful image-render path, render the link text in the layout-specific position required by the product behavior
|
||
- keep using the shared `DetectedLinksState` keyed by the section index so the path labels get normal file-link highlighting and click behavior without a second link-detection system
|
||
- keep binary-file link opening on the standard file-open path so those labels resolve to the platform default app rather than `$EDITOR`
|
||
- keep find/highlight behavior scoped to the existing global text pipeline rather than refactoring search indexing for this label in the same change
|
||
|
||
This keeps the change narrowly targeted while still giving users a visible, interactive file link under rendered file-backed images.
|
||
|
||
### 9. Preserve existing selection behavior
|
||
Do not add a new drag-selection model for rendered visuals in this change.
|
||
|
||
The expected behavior for this implementation is:
|
||
- existing text/code/table selection behavior remains unchanged
|
||
- rendered images and Mermaid diagrams expose explicit copy behavior through right-click/context-menu affordances
|
||
- block-level copy/export includes the right raw Markdown for visual sections
|
||
|
||
That is the intended “minimal change” scope for this feature.
|
||
|
||
### 10. Keep restored conversations deterministic
|
||
Restored conversations should not depend on reparsing with different metadata than the original render.
|
||
|
||
The renderer should use:
|
||
- the section payload stored on the conversation output
|
||
- the `working_directory` already persisted with the exchange/block
|
||
- current runtime flags to decide whether to render a visual or raw fallback
|
||
|
||
That means restored blocks will:
|
||
- resolve relative file images against the original captured working directory
|
||
- render Mermaid only when both flags are currently enabled
|
||
- still show raw Markdown if the file is gone or the platform cannot load it
|
||
|
||
## End-to-End Flow
|
||
1. The AI backend streams markdown text into an `AIAgentOutputMessageType::Text`.
|
||
2. `parse_markdown_into_text_and_code_sections` in `app/src/ai/agent/util.rs` identifies plain text, code, tables, parser-recognized images, and Mermaid sections.
|
||
3. The parsed output stores explicit `Image` and `MermaidDiagram` sections alongside their raw Markdown source.
|
||
4. `app/src/ai/blocklist/block/view_impl/output.rs` renders the new visual sections through the existing section-based block-list renderer alongside plain text, code, and tables.
|
||
5. The visual-section renderers receive the AI block’s stored `current_working_directory` and `shell_launch_data`.
|
||
6. File-backed image sections resolve their source path relative to that working directory.
|
||
7. Mermaid sections build an in-memory SVG asset source with `mermaid_asset_source`.
|
||
8. The block-list renderer derives a source-ordered lightbox collection from the renderable visual sections in that message.
|
||
9. Each visual section either renders as an image block or falls back to a plain markdown block depending on feature flags and asset/render success.
|
||
10. When a file-backed image renders successfully, the renderer also shows a clickable/highlighted file link beneath or beside it:
|
||
- inline image rows show the basename / final path segment
|
||
- block image rows show the full source path
|
||
11. Clicking a rendered image or Mermaid diagram dispatches `WorkspaceAction::OpenLightbox` with that shared visual collection and the clicked section’s initial index.
|
||
12. Right-click copy on a rendered image or Mermaid diagram writes the section’s `markdown_source`.
|
||
13. Clicking a rendered file link continues to use the shared file-open flow, which means binary file labels open in the platform default app instead of `$EDITOR`.
|
||
14. Block-level copy and conversation export continue to emit markdown, using `markdown_source` for visual sections.
|
||
15. When the conversation is restored later, the same section payload renders again using the stored working-directory metadata and current flags.
|
||
|
||
## Risks and Mitigations
|
||
|
||
### Risk: selection expectations drift beyond the implementation
|
||
The current AI block architecture does not support unified mixed-content selection across renderer types. If the specs or implementation drift back toward that requirement, this feature would expand substantially.
|
||
|
||
Mitigation:
|
||
- keep the product and tech specs explicit that this change does not add a new cross-renderer selection model
|
||
- keep the scope focused on rendering, explicit copy affordances, and block-level copy/export correctness
|
||
|
||
### Risk: parser scope does not match full Markdown image semantics
|
||
Standard Markdown treats images as inline syntax, but our current parser only recognizes standalone-image lines, and this redesign needs a narrow same-line image-row case.
|
||
|
||
Mitigation:
|
||
- keep the new same-line parser limited to lines that consist entirely of parser-compatible image references separated only by whitespace
|
||
- do not treat arbitrary prose containing `![...]` as inline-renderable image content
|
||
- treat fuller CommonMark-style inline image support as follow-up parser work
|
||
|
||
### Risk: relative path resolution diverges between notebooks and AI blocks
|
||
The existing editor helper resolves relative image paths against a document path, while AI blocks need to resolve against the captured session working directory.
|
||
|
||
Mitigation:
|
||
- factor path resolution into a base-directory-oriented helper
|
||
- update notebook and AI-block callers to use it explicitly
|
||
- add native and WASM coverage
|
||
|
||
### Risk: regressions to existing section rendering
|
||
Adding two more section types to the current renderer could affect section indexing, link-detection offsets, or restored-block bookkeeping.
|
||
|
||
Mitigation:
|
||
- keep section ordering explicit in `AIAgentOutput::all_text` and render traversal
|
||
- follow existing per-section bookkeeping patterns for code/table sections
|
||
- add mixed-content coverage that includes ordinary code blocks and tables, not just images
|
||
|
||
## Testing and Validation
|
||
|
||
### Unit tests
|
||
- extend `app/src/ai/agent/util_tests.rs` with:
|
||
- standalone image extraction
|
||
- same-line inline image-run extraction
|
||
- standalone multi-line block-image extraction
|
||
- image + table + text ordering
|
||
- Mermaid section extraction
|
||
- add block-list markdown copy tests covering:
|
||
- image sections serialize to ``
|
||
- Mermaid sections serialize to fenced mermaid markdown
|
||
- add native/WASM unit tests around relative path resolution against a base directory
|
||
- add focused rendering/helper coverage for:
|
||
- section-index preservation across skipped empty text sections
|
||
- lightbox index mapping in source order
|
||
- inline-image basename labeling
|
||
- visual width guard rails for invalid sizes
|
||
- add shared primitive regression tests for:
|
||
- invalid image rect rejection
|
||
- SVG intrinsic image sizing
|
||
- single-line text hit testing respecting Y bounds
|
||
|
||
### Integration tests
|
||
- A reasonable first integration test is a single manual-observation test, `test_restored_ai_block_renders_mermaid_and_local_images`, in `crates/integration/src/test/agent_mode.rs`.
|
||
- That test would restore a synthetic `ConversationData` through `load_conversation_from_tasks`, set `InputContext.directory` to `crates/warpui_core/test_data`, and capture a real-display screenshot of one AI response that contains same-line local image markdown plus a Mermaid fence.
|
||
- It would be intentionally narrow: it would prove the restored AI block list can render both surfaces through the real conversation-hydration path without introducing protobuf fixtures or test-only UI hooks.
|
||
- Recommended follow-up integration coverage remains:
|
||
- an AI response containing plain text, a linked code block, a markdown table, a relative-path image, and a Mermaid diagram
|
||
- block-level copy serialization for mixed content
|
||
- restored-conversation rendering and fallback behavior
|
||
- disabled-flag fallback to raw Markdown
|
||
- shared-lightbox initial selection and previous/next navigation in message source order
|
||
|
||
### Manual validation
|
||
- relative and absolute local image paths for PNG, JPEG, GIF, WebP, and SVG
|
||
- inline same-line image rows with path labels below each image
|
||
- grouped standalone block-image rows with thumbnail-plus-path content
|
||
- missing-file fallback
|
||
- unsupported-format fallback
|
||
- Mermaid render success and Mermaid render failure fallback in the new framed treatment
|
||
- click-to-expand from both rendered images and Mermaid diagrams into the shared lightbox
|
||
- previous/next keyboard and button navigation inside the lightbox
|
||
- right-click copy on both file-backed images and Mermaid diagrams
|
||
- block-level copy on a response containing text, code, table, image, and Mermaid content
|
||
- restored conversation rendering after reopening history
|
||
- WASM sanity pass to verify path handling and fallback behavior do not panic
|
||
|
||
## Follow-ups
|
||
- full CommonMark-style inline image support if we decide to expand `markdown_parser` beyond its current standalone-image behavior
|
||
- unified mixed-content selection across block-list renderer types
|
||
- consolidating `MarkdownImages` and `BlocklistMarkdownImages` if Warp later ships a broader app-wide markdown-image rollout
|
||
- richer image interactions such as open/save/zoom, if product wants them later
|