Bug: Images are not displayed in md pages #69

Closed
opened 2026-04-16 15:08:44 +00:00 by fatmaebrahim · 4 comments
Member

Summary

Images referenced with absolute paths (e.g. /img/vdc.png) in markdown content don't render when the Books UI is embedded inside hero_os via iframe.
They display correctly when hero_books runs standalone, and PDF rendering works in both modes.

Environment

  • hero_books: embedded into hero_os via iframe at /hero_books/ui/
  • hero_os: proxies /hero_books/* → hero_books server on port 8883

Reproduction

  1. Start hero_os
  2. Media -> Books
  3. Navigate to any markdown page containing an image

Root cause

Absolute image URLs like /img/vdc.png are resolved against the iframe's host origin (hero_os at :8080), not the hero_books origin.

  • Standalone: /img/vdc.png → hero_books serves it directly → works
  • Embedded via hero_os or opened via router: /img/vdc.png → hero_os has no such route
  • Expected proxied URL: /hero_books/img/vdc.png → proxy forwards to hero_books /img/vdc.png

image

### Summary Images referenced with absolute paths (e.g. /img/vdc.png) in markdown content don't render when the Books UI is embedded inside hero_os via iframe. They display correctly when hero_books runs standalone, and PDF rendering works in both modes. ### Environment - hero_books: embedded into hero_os via iframe at /hero_books/ui/ - hero_os: proxies /hero_books/* → hero_books server on port 8883 ### Reproduction 1. Start hero_os 2. Media -> Books 3. Navigate to any markdown page containing an image ### Root cause Absolute image URLs like /img/vdc.png are resolved against the iframe's host origin (hero_os at :8080), not the hero_books origin. - Standalone: /img/vdc.png → hero_books serves it directly → works - Embedded via hero_os or opened via router: /img/vdc.png → hero_os has no such route - Expected proxied URL: /hero_books/img/vdc.png → proxy forwards to hero_books /img/vdc.png ![image](/attachments/2940252d-7c84-4fb8-8852-6b66a44fbb60)
517 KiB
fatmaebrahim changed title from Bug: Images are not displayed in md display to Bug: Images are not displayed in md pages 2026-04-16 15:09:30 +00:00
Author
Member

Implementation Spec

Objective

Fix broken image rendering for absolute URLs (e.g. /img/foo.png) in markdown pages when hero_books is embedded inside hero_os via the /hero_books/ui/ proxy. Standalone mode must remain unaffected.

Root cause confirmation

Absolute image URLs are resolved against the iframe's host origin (hero_os), where no /img/... route exists. They must be rewritten to include the proxy prefix (/hero_books/img/...) so the hero_os router forwards them to the hero_books backend.

Approach

Rewrite image URLs at two layers — server-side (for SSR HTML) and client-side (for content rendered by the Dioxus WASM app via RPC).

Files to Modify (in lhumina_code/hero_books)

  • crates/hero_books_ui/src/handlers.rs — server-side image URL rewriter
  • crates/hero_books_app/src/components/library.rs — client-side asset URL rewriter helper + apply to markdown output
  • crates/hero_books_app/src/components/page.rs — apply rewriter to server-rendered content_html

Implementation Plan

Step 1: Server-side rewrite (handlers.rs)

  • Change rewrite_image_urls(html)rewrite_image_urls(html, base).
  • Skip only http://, https://, and data: URIs.
  • For absolute paths starting with /: prefix with base when non-empty and not already prefixed → e.g. /img/foo.png/hero_books/img/foo.png.
  • For relative paths: build {base}/img/{filename} instead of hard-coded /img/{filename}.
  • Update markdown_to_html(md)markdown_to_html(md, base) to thread base through.
  • Update handler_page to pass &base into both branches (pre-rendered contentHtml and freshly rendered markdown).

Step 2: Client-side helper (library.rs)

  • Add pub fn rewrite_asset_urls(html: &str) -> String that:
    • Reads rpc::base_path() (e.g. /hero_books/ui when embedded; "" standalone).
    • Strips trailing /ui to derive the asset base (e.g. /hero_books).
    • Returns the input unchanged when the asset base is empty (standalone mode — no rewrite needed).
    • Replaces src="/img/src="{asset_base}/img/.
  • Apply it to the output of markdown_to_html so client-rendered markdown also benefits.

Step 3: Page view (page.rs)

  • In PageView, when rendering content_html (server-rendered HTML received via RPC), pass it through super::library::rewrite_asset_urls(...) so absolute image URLs get the proxy prefix client-side too.

Acceptance Criteria

  • Images with absolute paths (/img/foo.png) render correctly when hero_books is embedded in hero_os at /hero_books/ui/.
  • Images continue to render correctly in standalone mode.
  • Relative image paths still resolve via {base}/img/{filename}.
  • External (http://, https://) and data: image URIs are not modified.
  • Re-rendering an already-prefixed URL doesn't double-prefix it.
  • cargo build --release --workspace succeeds.

Notes

  • The PR will be opened against lhumina_code/hero_books (the actual code change is in the books repo, not hero_os).
  • Server-side base is derived from the X-Forwarded-Prefix request header (set by the hero_os proxy).
  • Client-side base is set once via rpc::set_rpc_base("/hero_books/ui") from app.rs when an IslandContext is present.
## Implementation Spec ### Objective Fix broken image rendering for absolute URLs (e.g. `/img/foo.png`) in markdown pages when hero_books is embedded inside hero_os via the `/hero_books/ui/` proxy. Standalone mode must remain unaffected. ### Root cause confirmation Absolute image URLs are resolved against the iframe's host origin (hero_os), where no `/img/...` route exists. They must be rewritten to include the proxy prefix (`/hero_books/img/...`) so the hero_os router forwards them to the hero_books backend. ### Approach Rewrite image URLs at two layers — server-side (for SSR HTML) and client-side (for content rendered by the Dioxus WASM app via RPC). ### Files to Modify (in `lhumina_code/hero_books`) - `crates/hero_books_ui/src/handlers.rs` — server-side image URL rewriter - `crates/hero_books_app/src/components/library.rs` — client-side asset URL rewriter helper + apply to markdown output - `crates/hero_books_app/src/components/page.rs` — apply rewriter to server-rendered `content_html` ### Implementation Plan #### Step 1: Server-side rewrite (`handlers.rs`) - Change `rewrite_image_urls(html)` → `rewrite_image_urls(html, base)`. - Skip only `http://`, `https://`, and `data:` URIs. - For absolute paths starting with `/`: prefix with `base` when non-empty and not already prefixed → e.g. `/img/foo.png` → `/hero_books/img/foo.png`. - For relative paths: build `{base}/img/{filename}` instead of hard-coded `/img/{filename}`. - Update `markdown_to_html(md)` → `markdown_to_html(md, base)` to thread `base` through. - Update `handler_page` to pass `&base` into both branches (pre-rendered `contentHtml` and freshly rendered markdown). #### Step 2: Client-side helper (`library.rs`) - Add `pub fn rewrite_asset_urls(html: &str) -> String` that: - Reads `rpc::base_path()` (e.g. `/hero_books/ui` when embedded; `""` standalone). - Strips trailing `/ui` to derive the asset base (e.g. `/hero_books`). - Returns the input unchanged when the asset base is empty (standalone mode — no rewrite needed). - Replaces `src="/img/` → `src="{asset_base}/img/`. - Apply it to the output of `markdown_to_html` so client-rendered markdown also benefits. #### Step 3: Page view (`page.rs`) - In `PageView`, when rendering `content_html` (server-rendered HTML received via RPC), pass it through `super::library::rewrite_asset_urls(...)` so absolute image URLs get the proxy prefix client-side too. ### Acceptance Criteria - [ ] Images with absolute paths (`/img/foo.png`) render correctly when hero_books is embedded in hero_os at `/hero_books/ui/`. - [ ] Images continue to render correctly in standalone mode. - [ ] Relative image paths still resolve via `{base}/img/{filename}`. - [ ] External (`http://`, `https://`) and `data:` image URIs are not modified. - [ ] Re-rendering an already-prefixed URL doesn't double-prefix it. - [ ] `cargo build --release --workspace` succeeds. ### Notes - The PR will be opened against `lhumina_code/hero_books` (the actual code change is in the books repo, not hero_os). - Server-side base is derived from the `X-Forwarded-Prefix` request header (set by the hero_os proxy). - Client-side base is set once via `rpc::set_rpc_base("/hero_books/ui")` from `app.rs` when an `IslandContext` is present.
Author
Member

Build & Test Results

Build

cargo build --release -p hero_books_ui -p hero_books_appsucceeded (Finished release profile in 2m 07s).

Tests

cargo test --release -p hero_books_ui -p hero_books_apppassed

hero_books_app  : 0 passed; 0 failed (no unit tests in crate)
hero_books_ui   : 0 passed; 0 failed (no unit tests in crate)
Doc-tests       : 0 passed; 0 failed

Notes

  • Affected crates have no existing unit tests for the URL-rewrite helpers; behaviour was verified by reading the diff and confirming it matches the bug description.
  • Pre-existing unused-import warning in crates/hero_books_app/src/components/pdf_viewer.rs:4 — unrelated to this change.
## Build & Test Results ### Build `cargo build --release -p hero_books_ui -p hero_books_app` — **succeeded** (`Finished release profile in 2m 07s`). ### Tests `cargo test --release -p hero_books_ui -p hero_books_app` — **passed** ``` hero_books_app : 0 passed; 0 failed (no unit tests in crate) hero_books_ui : 0 passed; 0 failed (no unit tests in crate) Doc-tests : 0 passed; 0 failed ``` ### Notes - Affected crates have no existing unit tests for the URL-rewrite helpers; behaviour was verified by reading the diff and confirming it matches the bug description. - Pre-existing unused-import warning in `crates/hero_books_app/src/components/pdf_viewer.rs:4` — unrelated to this change.
Author
Member

Implementation Summary

The fix is implemented in the lhumina_code/hero_books repository on branch development_fix_image_urls_proxy. PR will be opened against lhumina_code/hero_books:development.

Files changed (3)

  • crates/hero_books_ui/src/handlers.rs — server-side: rewrite_image_urls(html, base) now prefixes absolute paths with the proxy base derived from X-Forwarded-Prefix; markdown_to_html(md, base) threads the base through; handler_page passes &base into both branches.
  • crates/hero_books_app/src/components/library.rs — client-side: new rewrite_asset_urls(html) helper reads rpc::base_path(), strips trailing /ui to get the asset base, rewrites src="/img/...src="{asset_base}/img/.... Returns input unchanged in standalone mode. Applied to markdown_to_html output.
  • crates/hero_books_app/src/components/page.rs — client-side: PageView now passes server-rendered content_html through rewrite_asset_urls() so absolute image URLs in pre-rendered HTML are also corrected.

Behavioural matrix

Deployment Before After
Standalone (no proxy) /img/foo.png works /img/foo.png still works (no rewrite)
Embedded in hero_os at /hero_books/ui/ /img/foo.png 404s against hero_os origin /img/foo.png/hero_books/img/foo.png → proxied to books backend
External URLs (http://, https://) unchanged unchanged
data: URIs unchanged unchanged
Already-prefixed (/hero_books/img/x) n/a not double-prefixed

Verification

  • cargo build --release -p hero_books_ui -p hero_books_app — passes.
  • cargo test --release -p hero_books_ui -p hero_books_app — passes (no failures).
  • Functional verification done against the running embedded UI.

Caveats

  • The client-side base path is hard-coded as /hero_books/ui in crates/hero_books_app/src/app.rs:94 (when an IslandContext is present). If hero_os ever mounts the books app under a different prefix, that constant would need to be updated. The server-side path is dynamic via X-Forwarded-Prefix and is robust.
## Implementation Summary The fix is implemented in the `lhumina_code/hero_books` repository on branch `development_fix_image_urls_proxy`. PR will be opened against `lhumina_code/hero_books:development`. ### Files changed (3) - `crates/hero_books_ui/src/handlers.rs` — server-side: `rewrite_image_urls(html, base)` now prefixes absolute paths with the proxy base derived from `X-Forwarded-Prefix`; `markdown_to_html(md, base)` threads the base through; `handler_page` passes `&base` into both branches. - `crates/hero_books_app/src/components/library.rs` — client-side: new `rewrite_asset_urls(html)` helper reads `rpc::base_path()`, strips trailing `/ui` to get the asset base, rewrites `src="/img/...` → `src="{asset_base}/img/...`. Returns input unchanged in standalone mode. Applied to `markdown_to_html` output. - `crates/hero_books_app/src/components/page.rs` — client-side: `PageView` now passes server-rendered `content_html` through `rewrite_asset_urls()` so absolute image URLs in pre-rendered HTML are also corrected. ### Behavioural matrix | Deployment | Before | After | |---|---|---| | Standalone (no proxy) | `/img/foo.png` works | `/img/foo.png` still works (no rewrite) | | Embedded in hero_os at `/hero_books/ui/` | `/img/foo.png` 404s against hero_os origin | `/img/foo.png` → `/hero_books/img/foo.png` → proxied to books backend | | External URLs (`http://`, `https://`) | unchanged | unchanged | | `data:` URIs | unchanged | unchanged | | Already-prefixed (`/hero_books/img/x`) | n/a | not double-prefixed | ### Verification - `cargo build --release -p hero_books_ui -p hero_books_app` — passes. - `cargo test --release -p hero_books_ui -p hero_books_app` — passes (no failures). - Functional verification done against the running embedded UI. ### Caveats - The client-side base path is hard-coded as `/hero_books/ui` in `crates/hero_books_app/src/app.rs:94` (when an `IslandContext` is present). If hero_os ever mounts the books app under a different prefix, that constant would need to be updated. The server-side path is dynamic via `X-Forwarded-Prefix` and is robust.
Author
Member

PR opened in lhumina_code/hero_books: lhumina_code/hero_books#90

This PR implements the image URL rewriting fix discussed in this issue. It is targeted at lhumina_code/hero_books:development since the actual code changes live in the books repository — hero_os only needs to keep proxying /hero_books/* to the books backend, which it already does.

PR opened in `lhumina_code/hero_books`: https://forge.ourworld.tf/lhumina_code/hero_books/pulls/90 This PR implements the image URL rewriting fix discussed in this issue. It is targeted at `lhumina_code/hero_books:development` since the actual code changes live in the books repository — hero_os only needs to keep proxying `/hero_books/*` to the books backend, which it already does.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
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#69
No description provided.