pdf, image viewer, markdown editor #22

Open
opened 2026-03-10 04:26:32 +00:00 by despiegk · 3 comments
Owner

image

we need an markdown editor (2 pane, edit left, preview right, syntax highlighting)
use same editor from any component where we need it
so we need ability to link back for save to where it was from (Timur is good in these tricks)

e.g. in CRM we want to edit the description as markdown, we have ability to break it out into a separate editor (not the default is option), where we now can work in a nice editor, if we do save it goes back to the right field in the CRM

same for pdf viewer, so we can use same viewer everywhere
same for image viewer (full screen)

![image](/attachments/ee80ae40-87c1-4df9-a3ca-570705fc3f3f) we need an markdown editor (2 pane, edit left, preview right, syntax highlighting) use same editor from any component where we need it so we need ability to link back for save to where it was from (Timur is good in these tricks) e.g. in CRM we want to edit the description as markdown, we have ability to break it out into a separate editor (not the default is option), where we now can work in a nice editor, if we do save it goes back to the right field in the CRM same for pdf viewer, so we can use same viewer everywhere same for image viewer (full screen)
558 KiB
despiegk added this to the ACTIVE project 2026-03-10 04:26:44 +00:00
Owner

Implementation Plan: PDF Viewer, Image Viewer, Markdown Editor

Current State

The hero_archipelagos repo already has:

  • viewer archipelago — read-only views for Image (zoom/rotate), PDF (native iframe), Markdown (custom parser), Text (syntax highlighting). All Panel-only, no Row/Card variants.
  • editor archipelago — text/markdown editing with 2-pane split view (edit + preview), WebDAV save. Panel-only.

Both are monolithic single-island archipelagos. They lack Row/Card size variants, editable toggle, and the "link back" save mechanism.


Architecture: 3 Dedicated Archipelagos + Core Media Components

Create 3 new focused archipelagos, one per media type, each with proper IslandSize handling (Row/Card/Panel) and IslandMode toggle (Read/Write where applicable). Lift reusable rendering components into core/src/components/media/ so any archipelago can embed them.

Phase 1: Core Media Components (core/src/components/media/)

Add reusable, stateless view components to core that any archipelago can import:

Component Description
MarkdownRenderer Renders markdown string → styled HTML (extracted from existing viewer/markdown_view.rs)
MarkdownEditorPane Textarea with line numbers + live preview side-by-side (extracted from editor/split_view.rs)
ImageDisplay Image tag with zoom/rotate controls (extracted from viewer/image_view.rs)
PdfEmbed Native browser PDF viewer iframe (extracted from viewer/pdf_view.rs)
MediaThumbnail Generic thumbnail component: icon + filename + optional badge (new)

Also add to core:

  • MediaCallback type — standardized save-back pattern:
    pub struct MediaContext {
        pub file_path: Option<String>,
        pub content: Option<String>,
        pub mime_type: String,
        pub on_save: Option<EventHandler<String>>,   // Fires with new content
        pub on_close: Option<EventHandler<()>>,       // Fires on editor close
        pub source_id: Option<String>,                // Origin component ID for event routing
    }
    

Phase 2: Markdown Archipelago (archipelagos/markdown/)

archipelagos/markdown/
├── Cargo.toml          # island id="markdown", archipelago id="markdown"
└── src/
    ├── lib.rs          # metadata(), mcp_tools(), re-exports
    ├── archipelago.rs  # MarkdownArchipelagoApp — routing + data
    └── islands/
        ├── mod.rs
        └── markdown_island.rs  # Size-variant rendering

IslandSize variants:

  • RowMediaThumbnail (markdown icon + filename + first line preview)
  • Card → Rendered markdown preview (first ~10 lines, truncated), filename header
  • Panel → Full view based on IslandMode:
    • ReadMarkdownRenderer (full rendered preview)
    • WriteMarkdownEditorPane (2-pane: edit left, preview right, syntax highlighting)

Props:

pub struct MarkdownIslandProps {
    pub context: IslandContext,
    pub mode: IslandMode,           // Read = viewer, Write = editor
    pub size: IslandSize,           // Row, Card, Panel
    pub file_path: Option<String>,  // WebDAV path
    pub content: Option<String>,    // Direct content (for inline/embedded use)
    pub on_save: EventHandler<String>,  // Link-back: fires with content on save
    pub on_close: EventHandler<()>,     // Fires when user closes editor
}

Link-back flow:

  1. CRM component renders MarkdownIsland { content: description, mode: Write, on_save: move |new_content| { update_crm_field(new_content) } }
  2. User edits in 2-pane editor, clicks Save
  3. on_save fires with new content → CRM field updates
  4. For "break out" mode: CRM opens markdown island in a separate hero_os window, with content passed via props and save-back via DOM CustomEvent("media-save", { detail: { source_id, content } })

Phase 3: Image Archipelago (archipelagos/image/)

archipelagos/image/
├── Cargo.toml          # island id="image", archipelago id="image"
└── src/
    ├── lib.rs
    ├── archipelago.rs  # ImageArchipelagoApp
    └── islands/
        ├── mod.rs
        ├── image_island.rs   # Size-variant rendering
        └── fullscreen.rs     # Full-screen overlay modal

IslandSize variants:

  • Row → Small thumbnail (48px) + filename + dimensions badge
  • Card → Medium preview (object-fit: cover) + filename overlay
  • Panel → Full ImageDisplay with zoom, rotate, full-screen button

Full-screen: Overlay modal (position: fixed; inset: 0; z-index: 9999) with the image centered, zoom/rotate controls, and Escape to close.

Read-only (no Write mode for images).

Phase 4: PDF Archipelago (archipelagos/pdf/)

archipelagos/pdf/
├── Cargo.toml          # island id="pdf", archipelago id="pdf"
└── src/
    ├── lib.rs
    ├── archipelago.rs  # PdfArchipelagoApp
    └── islands/
        ├── mod.rs
        └── pdf_island.rs  # Size-variant rendering

IslandSize variants:

  • Row → PDF icon + filename + "PDF" badge
  • Card → First-page preview via <canvas> or placeholder + filename
  • Panel → Full PdfEmbed (native browser PDF viewer iframe)

Read-only (no Write mode for PDFs).

Phase 5: Integration & "Break Out to Editor" Pattern

Add a reusable EditButton component to core that any archipelago can place next to a text field:

// In core/src/components/media/edit_button.rs
EditButton {
    content: field_content,
    label: "Edit as Markdown",
    on_save: move |new_content| { update_field(new_content) },
}

Clicking this either:

  1. Inline: Expands the field into an embedded MarkdownIsland (mode=Write) in place
  2. Windowed: Opens the markdown editor in a new hero_os window with save-back via CustomEvent

The mode (inline vs windowed) can be a user preference or configurable prop.


Registration

Each new archipelago gets:

  1. Added to workspace Cargo.toml members
  2. Added to server/Cargo.toml dependencies
  3. [package.metadata.island] + [package.metadata.archipelago] in its own Cargo.toml
  4. Entry in server/src/archipelagos.rs
  5. Auto-generated registration via build.rs

Existing Viewer/Editor

Keep as-is. They serve as the "file browser opens any file" use case. The new dedicated archipelagos are for embedding per-type media components within other apps (CRM, contacts, etc.) with proper size variants and save-back.


Summary of Deliverables

Deliverable Location Key Feature
Core media components core/src/components/media/ Reusable renderers for any archipelago
Markdown archipelago archipelagos/markdown/ Row/Card/Panel + Read/Write toggle + link-back save
Image archipelago archipelagos/image/ Row/Card/Panel + full-screen overlay
PDF archipelago archipelagos/pdf/ Row/Card/Panel + native viewer
EditButton component core/src/components/media/ "Break out to editor" from any text field
MediaContext type core/src/components/media/ Standardized save-back callback pattern

Order of Implementation

  1. Phase 1 (Core) — extract reusable components, add MediaContext
  2. Phase 2 (Markdown) — highest value, most complex (has editor)
  3. Phase 3 (Image) — full-screen is the key new feature
  4. Phase 4 (PDF) — simplest, mostly wrapping native viewer
  5. Phase 5 (Integration) — EditButton, break-out pattern
## Implementation Plan: PDF Viewer, Image Viewer, Markdown Editor ### Current State The `hero_archipelagos` repo already has: - **`viewer` archipelago** — read-only views for Image (zoom/rotate), PDF (native iframe), Markdown (custom parser), Text (syntax highlighting). All Panel-only, no Row/Card variants. - **`editor` archipelago** — text/markdown editing with 2-pane split view (edit + preview), WebDAV save. Panel-only. Both are monolithic single-island archipelagos. They lack Row/Card size variants, editable toggle, and the "link back" save mechanism. --- ### Architecture: 3 Dedicated Archipelagos + Core Media Components Create **3 new focused archipelagos**, one per media type, each with proper `IslandSize` handling (Row/Card/Panel) and `IslandMode` toggle (Read/Write where applicable). Lift reusable rendering components into `core/src/components/media/` so any archipelago can embed them. #### Phase 1: Core Media Components (`core/src/components/media/`) Add reusable, stateless view components to `core` that any archipelago can import: | Component | Description | |-----------|-------------| | `MarkdownRenderer` | Renders markdown string → styled HTML (extracted from existing `viewer/markdown_view.rs`) | | `MarkdownEditorPane` | Textarea with line numbers + live preview side-by-side (extracted from `editor/split_view.rs`) | | `ImageDisplay` | Image tag with zoom/rotate controls (extracted from `viewer/image_view.rs`) | | `PdfEmbed` | Native browser PDF viewer iframe (extracted from `viewer/pdf_view.rs`) | | `MediaThumbnail` | Generic thumbnail component: icon + filename + optional badge (new) | Also add to core: - **`MediaCallback`** type — standardized save-back pattern: ```rust pub struct MediaContext { pub file_path: Option<String>, pub content: Option<String>, pub mime_type: String, pub on_save: Option<EventHandler<String>>, // Fires with new content pub on_close: Option<EventHandler<()>>, // Fires on editor close pub source_id: Option<String>, // Origin component ID for event routing } ``` #### Phase 2: Markdown Archipelago (`archipelagos/markdown/`) ``` archipelagos/markdown/ ├── Cargo.toml # island id="markdown", archipelago id="markdown" └── src/ ├── lib.rs # metadata(), mcp_tools(), re-exports ├── archipelago.rs # MarkdownArchipelagoApp — routing + data └── islands/ ├── mod.rs └── markdown_island.rs # Size-variant rendering ``` **IslandSize variants:** - **Row** → `MediaThumbnail` (markdown icon + filename + first line preview) - **Card** → Rendered markdown preview (first ~10 lines, truncated), filename header - **Panel** → Full view based on `IslandMode`: - `Read` → `MarkdownRenderer` (full rendered preview) - `Write` → `MarkdownEditorPane` (2-pane: edit left, preview right, syntax highlighting) **Props:** ```rust pub struct MarkdownIslandProps { pub context: IslandContext, pub mode: IslandMode, // Read = viewer, Write = editor pub size: IslandSize, // Row, Card, Panel pub file_path: Option<String>, // WebDAV path pub content: Option<String>, // Direct content (for inline/embedded use) pub on_save: EventHandler<String>, // Link-back: fires with content on save pub on_close: EventHandler<()>, // Fires when user closes editor } ``` **Link-back flow:** 1. CRM component renders `MarkdownIsland { content: description, mode: Write, on_save: move |new_content| { update_crm_field(new_content) } }` 2. User edits in 2-pane editor, clicks Save 3. `on_save` fires with new content → CRM field updates 4. For "break out" mode: CRM opens markdown island in a separate hero_os window, with content passed via props and save-back via DOM `CustomEvent("media-save", { detail: { source_id, content } })` #### Phase 3: Image Archipelago (`archipelagos/image/`) ``` archipelagos/image/ ├── Cargo.toml # island id="image", archipelago id="image" └── src/ ├── lib.rs ├── archipelago.rs # ImageArchipelagoApp └── islands/ ├── mod.rs ├── image_island.rs # Size-variant rendering └── fullscreen.rs # Full-screen overlay modal ``` **IslandSize variants:** - **Row** → Small thumbnail (48px) + filename + dimensions badge - **Card** → Medium preview (object-fit: cover) + filename overlay - **Panel** → Full `ImageDisplay` with zoom, rotate, full-screen button **Full-screen:** Overlay modal (`position: fixed; inset: 0; z-index: 9999`) with the image centered, zoom/rotate controls, and Escape to close. Read-only (no Write mode for images). #### Phase 4: PDF Archipelago (`archipelagos/pdf/`) ``` archipelagos/pdf/ ├── Cargo.toml # island id="pdf", archipelago id="pdf" └── src/ ├── lib.rs ├── archipelago.rs # PdfArchipelagoApp └── islands/ ├── mod.rs └── pdf_island.rs # Size-variant rendering ``` **IslandSize variants:** - **Row** → PDF icon + filename + "PDF" badge - **Card** → First-page preview via `<canvas>` or placeholder + filename - **Panel** → Full `PdfEmbed` (native browser PDF viewer iframe) Read-only (no Write mode for PDFs). #### Phase 5: Integration & "Break Out to Editor" Pattern Add a reusable **`EditButton`** component to core that any archipelago can place next to a text field: ```rust // In core/src/components/media/edit_button.rs EditButton { content: field_content, label: "Edit as Markdown", on_save: move |new_content| { update_field(new_content) }, } ``` Clicking this either: 1. **Inline**: Expands the field into an embedded `MarkdownIsland` (mode=Write) in place 2. **Windowed**: Opens the markdown editor in a new hero_os window with save-back via `CustomEvent` The mode (inline vs windowed) can be a user preference or configurable prop. --- ### Registration Each new archipelago gets: 1. Added to workspace `Cargo.toml` members 2. Added to `server/Cargo.toml` dependencies 3. `[package.metadata.island]` + `[package.metadata.archipelago]` in its own Cargo.toml 4. Entry in `server/src/archipelagos.rs` 5. Auto-generated registration via `build.rs` ### Existing Viewer/Editor Keep as-is. They serve as the "file browser opens any file" use case. The new dedicated archipelagos are for **embedding per-type media components** within other apps (CRM, contacts, etc.) with proper size variants and save-back. --- ### Summary of Deliverables | Deliverable | Location | Key Feature | |-------------|----------|-------------| | Core media components | `core/src/components/media/` | Reusable renderers for any archipelago | | Markdown archipelago | `archipelagos/markdown/` | Row/Card/Panel + Read/Write toggle + link-back save | | Image archipelago | `archipelagos/image/` | Row/Card/Panel + full-screen overlay | | PDF archipelago | `archipelagos/pdf/` | Row/Card/Panel + native viewer | | EditButton component | `core/src/components/media/` | "Break out to editor" from any text field | | MediaContext type | `core/src/components/media/` | Standardized save-back callback pattern | ### Order of Implementation 1. Phase 1 (Core) — extract reusable components, add MediaContext 2. Phase 2 (Markdown) — highest value, most complex (has editor) 3. Phase 3 (Image) — full-screen is the key new feature 4. Phase 4 (PDF) — simplest, mostly wrapping native viewer 5. Phase 5 (Integration) — EditButton, break-out pattern
Owner

Updated Plan: No Core Media Components — Each Archipelago Owns Its Components

Per feedback: no need to put reusable components in core/. Each archipelago owns its rendering components and other archipelagos import directly from them.

Import pattern:

# In contacts/Cargo.toml, if CRM needs a markdown editor:
hero_archipelagos_markdown = { path = "../markdown", default-features = false, features = ["web"] }
// In contacts/src/some_island.rs
use hero_archipelagos_markdown::MarkdownEditor;

Revised Architecture

archipelagos/markdown/ — owns all markdown components

Exports (pub):

  • MarkdownRenderer — renders markdown string → styled HTML
  • MarkdownEditor — 2-pane editor (edit left, preview right, syntax highlighting)
  • MarkdownArchipelagoApp — full island with routing, size variants, mode toggle

Structure:

archipelagos/markdown/
├── Cargo.toml
└── src/
    ├── lib.rs              # pub re-exports of reusable components
    ├── archipelago.rs      # MarkdownArchipelagoApp (routing, data)
    └── islands/
        ├── mod.rs
        ├── markdown_island.rs   # Row/Card/Panel by IslandSize
        ├── renderer.rs          # MarkdownRenderer (read-only preview)
        └── editor.rs            # MarkdownEditor (2-pane edit+preview)

IslandSize → rendering:

  • Row → markdown icon + filename + first-line preview
  • Card → rendered preview (truncated ~10 lines)
  • Panel + Read → full MarkdownRenderer
  • Panel + Write → full MarkdownEditor (2-pane)

Props include on_save: EventHandler<String> for link-back to calling component.

archipelagos/image/ — owns all image components

Exports (pub):

  • ImageDisplay — image with zoom/rotate controls
  • ImageThumbnail — compact thumbnail component
  • ImageFullscreen — full-screen overlay modal
  • ImageArchipelagoApp — full island

Structure:

archipelagos/image/
├── Cargo.toml
└── src/
    ├── lib.rs
    ├── archipelago.rs
    └── islands/
        ├── mod.rs
        ├── image_island.rs    # Row/Card/Panel by IslandSize
        ├── display.rs         # ImageDisplay (zoom, rotate)
        ├── thumbnail.rs       # ImageThumbnail (compact)
        └── fullscreen.rs      # ImageFullscreen overlay

IslandSize → rendering:

  • RowImageThumbnail (48px) + filename
  • Card → medium preview (object-fit: cover) + filename overlay
  • PanelImageDisplay with zoom/rotate + full-screen button

Read-only (no Write mode).

archipelagos/pdf/ — owns all PDF components

Exports (pub):

  • PdfEmbed — native browser PDF viewer iframe
  • PdfArchipelagoApp — full island

Structure:

archipelagos/pdf/
├── Cargo.toml
└── src/
    ├── lib.rs
    ├── archipelago.rs
    └── islands/
        ├── mod.rs
        ├── pdf_island.rs     # Row/Card/Panel by IslandSize
        └── embed.rs          # PdfEmbed (iframe viewer)

IslandSize → rendering:

  • Row → PDF icon + filename + "PDF" badge
  • Card → placeholder preview + filename
  • PanelPdfEmbed (native browser PDF viewer)

Read-only.


Cross-archipelago usage example

CRM contact description field with "break out to editor":

// In contacts archipelago
use hero_archipelagos_markdown::{MarkdownRenderer, MarkdownEditor};

// Read mode: render inline preview
MarkdownRenderer { content: contact.description.clone() }

// Write mode: full editor with save-back
MarkdownEditor {
    content: contact.description.clone(),
    on_save: move |new_content| { update_contact_description(new_content) },
}

Existing viewer/editor archipelagos

Keep as-is — they serve as "open any file" general-purpose tools. The new dedicated archipelagos are the reusable building blocks other apps import.

Order of implementation

  1. Markdown archipelago (highest value — has editor + viewer + link-back)
  2. Image archipelago (full-screen is key new feature)
  3. PDF archipelago (simplest — wraps native viewer)
### Updated Plan: No Core Media Components — Each Archipelago Owns Its Components Per feedback: no need to put reusable components in `core/`. Each archipelago owns its rendering components and other archipelagos import directly from them. **Import pattern:** ```toml # In contacts/Cargo.toml, if CRM needs a markdown editor: hero_archipelagos_markdown = { path = "../markdown", default-features = false, features = ["web"] } ``` ```rust // In contacts/src/some_island.rs use hero_archipelagos_markdown::MarkdownEditor; ``` --- ### Revised Architecture #### `archipelagos/markdown/` — owns all markdown components **Exports (pub):** - `MarkdownRenderer` — renders markdown string → styled HTML - `MarkdownEditor` — 2-pane editor (edit left, preview right, syntax highlighting) - `MarkdownArchipelagoApp` — full island with routing, size variants, mode toggle **Structure:** ``` archipelagos/markdown/ ├── Cargo.toml └── src/ ├── lib.rs # pub re-exports of reusable components ├── archipelago.rs # MarkdownArchipelagoApp (routing, data) └── islands/ ├── mod.rs ├── markdown_island.rs # Row/Card/Panel by IslandSize ├── renderer.rs # MarkdownRenderer (read-only preview) └── editor.rs # MarkdownEditor (2-pane edit+preview) ``` **IslandSize → rendering:** - **Row** → markdown icon + filename + first-line preview - **Card** → rendered preview (truncated ~10 lines) - **Panel + Read** → full `MarkdownRenderer` - **Panel + Write** → full `MarkdownEditor` (2-pane) **Props include `on_save: EventHandler<String>`** for link-back to calling component. #### `archipelagos/image/` — owns all image components **Exports (pub):** - `ImageDisplay` — image with zoom/rotate controls - `ImageThumbnail` — compact thumbnail component - `ImageFullscreen` — full-screen overlay modal - `ImageArchipelagoApp` — full island **Structure:** ``` archipelagos/image/ ├── Cargo.toml └── src/ ├── lib.rs ├── archipelago.rs └── islands/ ├── mod.rs ├── image_island.rs # Row/Card/Panel by IslandSize ├── display.rs # ImageDisplay (zoom, rotate) ├── thumbnail.rs # ImageThumbnail (compact) └── fullscreen.rs # ImageFullscreen overlay ``` **IslandSize → rendering:** - **Row** → `ImageThumbnail` (48px) + filename - **Card** → medium preview (object-fit: cover) + filename overlay - **Panel** → `ImageDisplay` with zoom/rotate + full-screen button Read-only (no Write mode). #### `archipelagos/pdf/` — owns all PDF components **Exports (pub):** - `PdfEmbed` — native browser PDF viewer iframe - `PdfArchipelagoApp` — full island **Structure:** ``` archipelagos/pdf/ ├── Cargo.toml └── src/ ├── lib.rs ├── archipelago.rs └── islands/ ├── mod.rs ├── pdf_island.rs # Row/Card/Panel by IslandSize └── embed.rs # PdfEmbed (iframe viewer) ``` **IslandSize → rendering:** - **Row** → PDF icon + filename + "PDF" badge - **Card** → placeholder preview + filename - **Panel** → `PdfEmbed` (native browser PDF viewer) Read-only. --- ### Cross-archipelago usage example CRM contact description field with "break out to editor": ```rust // In contacts archipelago use hero_archipelagos_markdown::{MarkdownRenderer, MarkdownEditor}; // Read mode: render inline preview MarkdownRenderer { content: contact.description.clone() } // Write mode: full editor with save-back MarkdownEditor { content: contact.description.clone(), on_save: move |new_content| { update_contact_description(new_content) }, } ``` ### Existing viewer/editor archipelagos Keep as-is — they serve as "open any file" general-purpose tools. The new dedicated archipelagos are the reusable building blocks other apps import. ### Order of implementation 1. Markdown archipelago (highest value — has editor + viewer + link-back) 2. Image archipelago (full-screen is key new feature) 3. PDF archipelago (simplest — wraps native viewer)
Owner

Testing & Verification Plan

What's been implemented

3 new archipelagos created and registered:

  • archipelagos/markdown/ — MarkdownRenderer, MarkdownEditor (2-pane), MarkdownIsland (Row/Card/Panel + Read/Write toggle), on_save callback
  • archipelagos/image/ — ImageThumbnail, ImageDisplay (zoom/rotate), ImageFullscreen (overlay), ImageIsland (Row/Card/Panel)
  • archipelagos/pdf/ — PdfEmbed (native iframe), PdfIsland (Row/Card/Panel)

All registered in workspace Cargo.toml, server/Cargo.toml, server/src/registry.rs (props, WASM paths, archipelago entries). Auto-generated into island_content.rs by build.rs.

All compile cleanly (zero warnings), markdown unit tests pass (5/5).


How to verify

1. Showcase Server (manual visual testing)

cd hero_archipelagos
make run   # starts dx serve on port 8200

Open http://localhost:8200 — the 3 new archipelagos (Markdown, Image, PDF) should appear in the Archipelago Directory. Click each to open its playground. Configure file_path and webdav_url props in the left panel to point to actual files.

What to check:

  • Markdown archipelago appears in directory with purple icon
  • Image archipelago appears with cyan icon
  • PDF archipelago appears with red icon
  • Markdown: View mode renders markdown as styled HTML
  • Markdown: Edit button opens 2-pane editor (textarea left, preview right)
  • Markdown: Ctrl+S / Save button fires on_save callback
  • Markdown: Close button returns to view mode
  • Image: Displays image from WebDAV URL
  • Image: Zoom +/- and Rotate controls work
  • Image: Full Screen button opens fixed overlay
  • Image: Escape/click-outside closes fullscreen
  • PDF: Displays PDF in native browser iframe viewer
  • All 3: Breadcrumbs show correct navigation
  • All 3: Viewport size dropdown works

Requires: A running WebDAV server (hero_fossil) with test files, or point webdav_url to a server with accessible markdown/image/pdf files.

2. Unit tests

cargo test -p hero_archipelagos_markdown
# Tests: markdown_to_html headers, lists, code blocks, inline formatting, URL sanitization

Image and PDF are pure view components with no parsing logic — their testing is visual.

3. Integration tests with hero_browser_mcp

hero_browser_mcp provides headless browser automation with MCP support. Integration tests would:

  1. Start the showcase server (dx serve --port 8200)
  2. Use hero_browser_mcp to:
    • Navigate to http://localhost:8200
    • Click through to each new archipelago
    • Verify the island renders (check for expected DOM elements)
    • For markdown: input content in textarea, verify preview updates
    • For image: trigger zoom/rotate, verify transform CSS changes
    • For PDF: verify iframe src is set

Proposed test flow (using hero_browser_mcp REST API):

# 1. Create browser session
curl -X POST http://localhost:3000/mcp -d '{"method": "browser_create", "params": {}}'

# 2. Navigate to showcase
curl -X POST http://localhost:3000/mcp -d '{"method": "browser_navigate", "params": {"url": "http://localhost:8200"}}'

# 3. Click markdown archipelago in directory
curl -X POST http://localhost:3000/mcp -d '{"method": "browser_click", "params": {"selector": "[data-archipelago-id=markdown]"}}'

# 4. Verify content rendered
curl -X POST http://localhost:3000/mcp -d '{"method": "browser_evaluate", "params": {"expression": "document.querySelector('.markdown-preview') !== null"}}'

TODO to make this work:

  • Add a tests/ directory to server/ with a test harness that starts dx serve and hero_browser_mcp
  • Create Rhai scripts or Rust integration tests that automate the above flow
  • Add data-testid attributes to key elements in the new archipelagos for reliable selector targeting
  • Add mock content mode so islands can render without a real WebDAV server (e.g. initial_content prop for markdown, data: URL for image)
  • Add make test-e2e target to the Makefile

4. Quick smoke test (no browser needed)

# Verify all 3 compile
cargo check -p hero_archipelagos_markdown -p hero_archipelagos_image -p hero_archipelagos_pdf

# Verify unit tests pass
cargo test -p hero_archipelagos_markdown

# Verify server generates correct island_content.rs
grep -c "markdown\|image\|pdf" server/src/generated/island_content.rs
# Expected: 6 (2 lines each — import + match arm)

# Verify registry includes all 3
grep -c "Markdown\|Image\|Pdf" server/src/registry.rs
# Expected: references in all_archipelagos(), wasm_path(), island_props()

What's NOT yet verified / needs follow-up

  1. WASM buildcargo check passes but dx build (full WASM compilation) hasn't been tested due to disk space. Need to verify WASM target compiles.
  2. Widget (standalone) mode — The showcase has "Component" vs "Widget" toggle. Widget mode loads standalone WASM bundles. We haven't built standalone WASM for the new islands yet.
  3. Cross-archipelago import — The pub use exports (MarkdownRenderer, MarkdownEditor, ImageDisplay, PdfEmbed) haven't been tested from a consuming crate yet. This would be verified when e.g. CRM imports MarkdownEditor.
  4. hero_browser_mcp integration — Needs the test harness scaffolded.
  5. Real WebDAV files — Testing with actual file loading through hero_fossil.
## Testing & Verification Plan ### What's been implemented 3 new archipelagos created and registered: - `archipelagos/markdown/` — MarkdownRenderer, MarkdownEditor (2-pane), MarkdownIsland (Row/Card/Panel + Read/Write toggle), on_save callback - `archipelagos/image/` — ImageThumbnail, ImageDisplay (zoom/rotate), ImageFullscreen (overlay), ImageIsland (Row/Card/Panel) - `archipelagos/pdf/` — PdfEmbed (native iframe), PdfIsland (Row/Card/Panel) All registered in workspace `Cargo.toml`, `server/Cargo.toml`, `server/src/registry.rs` (props, WASM paths, archipelago entries). Auto-generated into `island_content.rs` by build.rs. All compile cleanly (zero warnings), markdown unit tests pass (5/5). --- ### How to verify #### 1. Showcase Server (manual visual testing) ```bash cd hero_archipelagos make run # starts dx serve on port 8200 ``` Open `http://localhost:8200` — the 3 new archipelagos (Markdown, Image, PDF) should appear in the Archipelago Directory. Click each to open its playground. Configure `file_path` and `webdav_url` props in the left panel to point to actual files. **What to check:** - [ ] Markdown archipelago appears in directory with purple icon - [ ] Image archipelago appears with cyan icon - [ ] PDF archipelago appears with red icon - [ ] Markdown: View mode renders markdown as styled HTML - [ ] Markdown: Edit button opens 2-pane editor (textarea left, preview right) - [ ] Markdown: Ctrl+S / Save button fires on_save callback - [ ] Markdown: Close button returns to view mode - [ ] Image: Displays image from WebDAV URL - [ ] Image: Zoom +/- and Rotate controls work - [ ] Image: Full Screen button opens fixed overlay - [ ] Image: Escape/click-outside closes fullscreen - [ ] PDF: Displays PDF in native browser iframe viewer - [ ] All 3: Breadcrumbs show correct navigation - [ ] All 3: Viewport size dropdown works **Requires:** A running WebDAV server (hero_fossil) with test files, or point `webdav_url` to a server with accessible markdown/image/pdf files. #### 2. Unit tests ```bash cargo test -p hero_archipelagos_markdown # Tests: markdown_to_html headers, lists, code blocks, inline formatting, URL sanitization ``` Image and PDF are pure view components with no parsing logic — their testing is visual. #### 3. Integration tests with hero_browser_mcp `hero_browser_mcp` provides headless browser automation with MCP support. Integration tests would: 1. Start the showcase server (`dx serve --port 8200`) 2. Use hero_browser_mcp to: - Navigate to `http://localhost:8200` - Click through to each new archipelago - Verify the island renders (check for expected DOM elements) - For markdown: input content in textarea, verify preview updates - For image: trigger zoom/rotate, verify transform CSS changes - For PDF: verify iframe src is set **Proposed test flow (using hero_browser_mcp REST API):** ```bash # 1. Create browser session curl -X POST http://localhost:3000/mcp -d '{"method": "browser_create", "params": {}}' # 2. Navigate to showcase curl -X POST http://localhost:3000/mcp -d '{"method": "browser_navigate", "params": {"url": "http://localhost:8200"}}' # 3. Click markdown archipelago in directory curl -X POST http://localhost:3000/mcp -d '{"method": "browser_click", "params": {"selector": "[data-archipelago-id=markdown]"}}' # 4. Verify content rendered curl -X POST http://localhost:3000/mcp -d '{"method": "browser_evaluate", "params": {"expression": "document.querySelector('.markdown-preview') !== null"}}' ``` **TODO to make this work:** - [ ] Add a `tests/` directory to `server/` with a test harness that starts `dx serve` and `hero_browser_mcp` - [ ] Create Rhai scripts or Rust integration tests that automate the above flow - [ ] Add `data-testid` attributes to key elements in the new archipelagos for reliable selector targeting - [ ] Add mock content mode so islands can render without a real WebDAV server (e.g. `initial_content` prop for markdown, `data:` URL for image) - [ ] Add `make test-e2e` target to the Makefile #### 4. Quick smoke test (no browser needed) ```bash # Verify all 3 compile cargo check -p hero_archipelagos_markdown -p hero_archipelagos_image -p hero_archipelagos_pdf # Verify unit tests pass cargo test -p hero_archipelagos_markdown # Verify server generates correct island_content.rs grep -c "markdown\|image\|pdf" server/src/generated/island_content.rs # Expected: 6 (2 lines each — import + match arm) # Verify registry includes all 3 grep -c "Markdown\|Image\|Pdf" server/src/registry.rs # Expected: references in all_archipelagos(), wasm_path(), island_props() ``` --- ### What's NOT yet verified / needs follow-up 1. **WASM build** — `cargo check` passes but `dx build` (full WASM compilation) hasn't been tested due to disk space. Need to verify WASM target compiles. 2. **Widget (standalone) mode** — The showcase has "Component" vs "Widget" toggle. Widget mode loads standalone WASM bundles. We haven't built standalone WASM for the new islands yet. 3. **Cross-archipelago import** — The `pub use` exports (`MarkdownRenderer`, `MarkdownEditor`, `ImageDisplay`, `PdfEmbed`) haven't been tested from a consuming crate yet. This would be verified when e.g. CRM imports `MarkdownEditor`. 4. **hero_browser_mcp integration** — Needs the test harness scaffolded. 5. **Real WebDAV files** — Testing with actual file loading through hero_fossil.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_os#22
No description provided.