feat: Image and Editable slides side by side #98

Merged
casper-stevens merged 39 commits from development_pptx_mockup into development 2026-06-04 09:16:54 +00:00
Member

What

Brings the image (PNG) slide-generation pipeline back alongside the new
HTML (Editable) pipeline so both coexist — the user picks a style when
creating a deck and can switch it per slide or for the whole deck. The two
pipelines live side by side; generation dispatches to the right one by each
slide's resolved render mode.

User-facing names: Image (AI-rendered picture) and Editable (WYSIWYG
HTML). Internally RenderMode { Image, Html }.

Highlights

  • Render mode model (hashing.rs): per-slide render_mode override + per-deck
    default_render_mode (defaults to Html, so existing decks are unchanged —
    guarded by a backward-compat test). resolve_render_mode() resolves slide → deck default.
  • Image pipeline restored (generator.rs, deck.rs): generate_slide /
    fix_slide_image, slide_generate_image / deck_generate_image, spell-check.
  • Dispatch: slide_generate_auto / deck_generate_auto route each slide to
    the image or HTML pipeline by mode (mixed decks supported, parallel with a
    shared metadata lock). CLI generate slide|deck auto-dispatch; server submits
    these mode-agnostically. slide.generateAsync / deck.generateAsync added;
    the *Html* RPC names kept as aliases.
  • Shared version history: snapshots already span .png/.html/.md per
    slide. Restoring a version now syncs the slide's mode to the restored artifact;
    the UI warns when rolling back to a different style.
  • Mixed-deck export: PDF embeds image slides directly + renders HTML slides;
    PPTX wraps image slides as a full-bleed picture through the same dom-to-pptx path.
  • Admin UI: deck-create style picker, per-slide style selector + whole-deck
    switch, restored image/thumbnail routes, image thumbnails in the grid, and the
    rollback warning.
  • Rhai: slide_generate / deck_generate / *_set_render_mode bindings.

Also includes a preceding commit: single image-slot regeneration from the WYSIWYG editor.

Backward compatibility

Existing decks (metadata without the new keys) resolve to Editable exactly as
before. Render mode is deliberately excluded from the staleness input hash;
mode-change setters clear generation markers so a flipped slide re-renders.

Testing

  • cargo test -p hero_slides_lib — 111 pass, incl. test_render_mode_backward_compat.
  • Deployed locally via lab build --release --restart; services running, new
    admin JS served, RPC dispatch verified.

🤖 Generated with Claude Code

## What Brings the **image (PNG) slide-generation pipeline** back alongside the new **HTML (Editable)** pipeline so both coexist — the user picks a style when creating a deck and can switch it per slide or for the whole deck. The two pipelines live side by side; generation dispatches to the right one by each slide's resolved render mode. User-facing names: **Image** (AI-rendered picture) and **Editable** (WYSIWYG HTML). Internally `RenderMode { Image, Html }`. ## Highlights - **Render mode model** (`hashing.rs`): per-slide `render_mode` override + per-deck `default_render_mode` (defaults to `Html`, so existing decks are unchanged — guarded by a backward-compat test). `resolve_render_mode()` resolves slide → deck default. - **Image pipeline restored** (`generator.rs`, `deck.rs`): `generate_slide` / `fix_slide_image`, `slide_generate_image` / `deck_generate_image`, spell-check. - **Dispatch**: `slide_generate_auto` / `deck_generate_auto` route each slide to the image or HTML pipeline by mode (mixed decks supported, parallel with a shared metadata lock). CLI `generate slide|deck` auto-dispatch; server submits these mode-agnostically. `slide.generateAsync` / `deck.generateAsync` added; the `*Html*` RPC names kept as aliases. - **Shared version history**: snapshots already span `.png`/`.html`/`.md` per slide. Restoring a version now syncs the slide's mode to the restored artifact; the UI warns when rolling back to a different style. - **Mixed-deck export**: PDF embeds image slides directly + renders HTML slides; PPTX wraps image slides as a full-bleed picture through the same dom-to-pptx path. - **Admin UI**: deck-create style picker, per-slide style selector + whole-deck switch, restored image/thumbnail routes, image thumbnails in the grid, and the rollback warning. - **Rhai**: `slide_generate` / `deck_generate` / `*_set_render_mode` bindings. Also includes a preceding commit: single image-slot regeneration from the WYSIWYG editor. ## Backward compatibility Existing decks (metadata without the new keys) resolve to **Editable** exactly as before. Render mode is deliberately excluded from the staleness input hash; mode-change setters clear generation markers so a flipped slide re-renders. ## Testing - `cargo test -p hero_slides_lib` — 111 pass, incl. `test_render_mode_backward_compat`. - Deployed locally via `lab build --release --restart`; services running, new admin JS served, RPC dispatch verified. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Adds `hero_slides pptx export collection/deck` which:
1. Parses slide markdown into structured JSON (title, bullets, speaker
   notes, layout) via a single AI text-model call.
2. Assembles a valid OpenXML PPTX ZIP from those elements — two layouts:
   title_slide for the cover, title_and_content for all other slides.
3. Writes slides.pptx to the deck root alongside the existing slides.pdf.

New surface: hero_slides_lib::pptx module, generate_pptx_slides(),
create_pptx(), deck_export_pptx(); zip v2 added as a dependency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a parallel HTML generation path alongside the existing PNG pipeline:

- generator.rs: generate_slide_html() — calls text model, returns a
  self-contained 1920×1080 HTML document with inline CSS and image-slot
  placeholder divs (data-image-slot="true") for visual regions
- deck.rs: slide_generate_html() and deck_generate_html() public API
- CLI: hero_slides generate html <collection/deck> [--force]
- Server: deck.generateHtmlAsync / JobLogs / JobStatus RPC methods
  backed by the new GenerateHtml job kind
- Routes: GET /api/.../slides/{slide}/html serves the HTML file
- RPC slide.list: has_html field alongside existing has_png
- present.html: viewer now renders HTML slides in a scaled <iframe>
  (1920×1080 with CSS transform scale-to-fit); falls back to PNG when
  only that exists; both formats coexist per-slide

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- UI fully switches to HTML model: Generate All, per-slide Generate,
  viewer, and stats all use has_html instead of has_png
- Slide card thumbnails use scaled iframes with ResizeObserver for
  correct scaling once layout is available
- Per-slide HTML generation via new slide.generateHtmlAsync RPC and
  hero_slides generate html-slide CLI subcommand
- Export dropdown + Generate split button cleaned up to HTML-first
- present.html viewer is HTML-only (no PNG fallback)
- Image slot filling made non-fatal: HTML always writes even if
  Gemini image generation fails for a slot
- HTML generation prompt updated for edge-flush images, gradient
  blends, and bold typography

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Server fetches HERO_SLIDES_JOBS_DIR and HERO_SLIDES_EXAMPLES_DIR from
  hero_proc at startup via fetch_secrets(); falls back to defaults if
  hero_proc is unreachable
- Remove HERO_DB_SOCKET env var override; always use the conventional
  socket path (HERO_SOCKET_DIR/hero_db/resp.sock)
- Remove GROQ_API_KEY and OPENROUTER_API_KEY env var reads from server
  jobs; herolib_ai builders already use HeroProcResolver (default_resolver)
  so the lib reads keys directly from hero_proc
- CLI early-validation check for GROQ_API_KEY now uses
  herolib_ai::providers::default_resolver() instead of std::env::var
- Drop JobManager::spawn() and journal::default_dir(); production code
  always calls spawn_with_dir(secrets.jobs_dir)
- Remove stale tests that asserted uppercase deck names and spaces are
  rejected — both are now valid after the camelCase slug conversion feature

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolved conflicts: rustfmt formatting in main.rs/startup.rs/journal.rs;
dashboard.js preserves pptx background_folders param and integrates
spellCheckSlide + pollSlideGenerateProgress rename from development.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract background color, text color, and font family from each slide's
HTML <style> block and apply them to the PPTX output via DrawingML:
solid background fill, explicit text color on every run, and latin
typeface override. Auto-contrast (Rec. 601 luma) derives text color from
background when no explicit color is set.

Add <a:endParaRPr> with u="none" on every paragraph so Keynote/PowerPoint
use the explicit run properties as the paragraph baseline, preventing
smart-text detection from applying underlines to recognized phrases.

Also tighten the AI PPTX generation prompt to strip markdown syntax and
hyperlinks from text values, and update the sample deck slides.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the AI text-extraction PPTX path with a dom-to-pptx pipeline that
renders each slide HTML in headless Chrome and converts the live DOM to
editable PPTX shapes — preserving gradients, images, fonts, and layout.

- Add browser_pptx.rs: hero_browser JSON-RPC client, per-slide export via
  dom-to-pptx (bundle injected inline to bypass file:// script restrictions),
  and OOXML zip merge for N single-slide PPTXs → one deck
- Pre-process DOM before export: collapse multi-layer CSS gradients to single
  layer (dom-to-pptx silently drops layer 2+), and root export at .slide
  container rather than document.body
- Fix inject_slot_images: detect actual MIME type instead of hardcoding png
- Fix slot image generation: use 16:9 aspect ratio instead of 4:3
- Fix pick_context_image: warn on unmatched label fallback
- Fix replace_fake_logos_with_slots: depth-track matching close tag and
  continue loop after replacement (previously stopped at first match)
- Fix extract_image_slot_descriptions: remove backward 60-char window
- Guard validate_slide_html AI call behind svg presence check

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the broken per-slide export + hand-rolled zip merge with a single
exportToPptx([...]) call over all slides at once, letting PptxGenJS generate
a well-formed multi-slide PPTX natively.

The merge had three bugs that caused PowerPoint's repair dialog:
- patch_content_types used line-by-line filtering but PptxGenJS writes
  minified single-line XML, stripping everything after the XML header
- zip directory entries (ppt/media/) passed the media filter, trimmed to "",
  and replace("", filename) corrupted the rels XML
- presentation rels had duplicate rId conflicts between non-slide rels
  (theme, notesMaster, presProps) and the newly added slide rels

New flow: blank loader page served over local HTTP → inject dom-to-pptx CDN
once → N iframes (one per slide, same origin) → exportToPptx([roots]) →
single PPTX. Gradient pre-processing applied per iframe before export.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add measure_slide_overflow() in browser_pptx.rs: renders each HTML slide
  at 1920×1080 via hero_browser, measures actual max bottom-edge with
  getBoundingClientRect, returns overflow pixels
- Add fix_overflow_html() in generator.rs: when overflow > 0, sends HTML +
  exact pixel count to AI for targeted font/padding reductions
- Wire both as step 3 in generate_slide_html(), falling back silently when
  hero_browser is unavailable
- Clamp PPTX export root element to 1920×1080 with overflow:hidden so
  dom-to-pptx matches the preview (no extra content below the boundary)
- Migrate UI from PNG to HTML thumbnails in version strip and deck cards
- Switch slide staleness tracking from has_png to has_html; expose both
  fields from slide.staleness RPC
- Replace generateSlide/generateAllSlides with generateSlideHtml/generateAllHtml
  throughout dashboard and editor

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add mandatory CSS entrance animations to system_generate_slide.md:
  title fade-up, staggered body text, image slots fade last, all within 2s
- Inject animation-freeze style into PPTX iframe capture so dom-to-pptx
  reads final visible state instead of hidden entrance positions
- Extract inline system prompt from generator.rs into system_generate_slide.md,
  registered in prompts.rs alongside all other templates (user-overridable)
- Add prompts_dir param to generate_slide_html and update all 4 call sites
- Update README, PURPOSE, and agent/wizard prompt files to reference HTML
  generator instead of old image/PNG pipeline

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
generated_count, first_slide, and staleness verdicts all checked for
output/<name>.png, but the generator writes output/<name>.html since
the switch to browser-based rendering — causing every slide to report
Pending / never_generated despite having valid HTML output on disk.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- generate_images_for_slots now returns Vec<Option<_>> — a failing slot
  yields None and logs a warning instead of aborting the whole slide
- validate_slide_html trigger extended to cover data-image-slot presence
  (CSS label fragments around slots are the common validation trigger)
- validate_slide_html prompt extracted to system_validate_slide.md so it
  can be overridden per-deck like the generate prompt
- system_generate_slide.md: stricter diagram/chart rules — ONE image slot
  per diagram, all labels in data-description, slot-in-container layout
  constraints added to prevent clipping

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The LLM was adding data-context-image slots on its own initiative even
when slide content never mentioned a logo. Rule now requires the slide
content to explicitly reference a logo or brand asset before a context
image slot may be placed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Inline string literals for context-image rules removed from generator.rs.
Two new embedded templates replace them:
  ctx_image_rule_with.md    — when context images are provided
  ctx_image_rule_without.md — when no context images are provided

Both are loadable/overridable per-deck via the prompts_dir mechanism
like all other prompt templates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- `needs_validation` now checks `html.contains("<svg")` only; the
  `|| html.contains("data-image-slot")` branch that caused slot stripping
  is removed
- Add `strip_diagram_labels` — string-scanning heuristic that removes
  `position:absolute` elements with <5 words of text within 200px of any
  image slot, without touching slot elements themselves
- Integrate `strip_diagram_labels` into the pipeline after the validation
  pass and before `extract_image_slot_descriptions`
- Add `info!` slot-count logs after each pipeline stage for regression
  visibility
- Remove Violation #3 (DIAGRAM LABEL ANNOTATIONS) from
  `system_validate_slide.md`; renumber remaining violations

#95
Adds in-place visual editing of slides in the preview iframe (?edit=true):
click-to-select, double-click inline text edit, drag-to-move and resize for
absolute-positioned elements, copy/cut/paste/duplicate/delete of elements,
and undo/redo. Edits persist via a new slide.saveHtml RPC which writes the
cleaned outerHTML to output/<slide>.html and marks the slide manually_edited
so AI regeneration skips it unless forced.

#96
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
#96
- Inject layout-safety CSS after generation to prevent text-element height
  clipping and sibling overlap
- Strip inline data: URLs before sending HTML to the overflow fixer to avoid
  provider request-size limits
- Remove rigid type hierarchy, spacing constants, and layout templates from
  system_generate_slide.md — AI chooses font sizes and layout freely; add
  fit-check section to prevent overflow and title/content overlap
- Copy body background onto .slide before dom-to-pptx captures it; the
  library only reads the exported element's own styles and misses the body
  background, causing a white canvas in the exported PPTX
- Collapse multi-layer CSS gradients to the last (opaque base) layer instead
  of the first (semi-transparent overlay), so the PPTX background shows the
  correct solid gradient rather than a near-transparent vignette over white
- Include root itself in the gradient collapse pass (previously only queried
  root's children)
- Add HTML slide/deck generation to CLI (`generate html`, `generate html-slide`)
  and propagate `visual_critique_enabled` setting via env var to the lib
- Wire `slide.generateHtmlAsync`, `deck.generateHtmlAsync`, and all job-status/
  logs variants in the RPC dispatcher
- Add `slide.saveHtml` RPC handler so WYSIWYG edits are persisted back to disk
- Add `slide.critiqueAsync` RPC + `Critique` CLI command; expose as per-slide QA
  action in the dashboard
- Add `visual_critique_enabled` user setting (default on); auto-critique runs
  after generation and can be toggled in settings
- Inject WYSIWYG editor script into served HTML; postMessage save round-trip
  to `slide.saveHtml` in slide_edit.js
- Fix version listing to recognise `.html` snapshots alongside `.png`/`.md`
- Add `system_critique_slide.md` prompt (previously untracked)
- Various HTML generation quality fixes: data-URL stripping, layout safety,
  overflow correction, fake-logo slot replacement, validate pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove all PNG image generation and the associated PNG spell-check path,
leaving HTML as the sole slide output format. The visual-critique pass
(HTML screenshot → AI fix) replaces the old PNG spell-check workflow.

Removed:
- `generate_slide()` and `fix_slide_image()` from generator.rs
- `deck_generate()`, `slide_generate()`, `slide_generate_with_selection()`,
  `spell_check_and_fix()`, `slide_spell_check_fix()` from deck.rs
- `submit_generate_{deck,slide}_job`, `handle_{deck,slide}_generate_async`,
  and all PNG job-status/logs shims from jobs/generate.rs
- `JobKind::GenerateSlide` and `JobKind::GenerateAll`; added `JobKind::Critique`
  so visual-critique gets its own debounce key
- `slide.generate`, `deck.generate`, `*Async`, `*JobStatus`, `*JobLogs` (PNG)
  and `slide.spellCheckAsync` RPC dispatch arms
- PNG image-serving routes (`serve_slide_image`, `serve_slide_thumbnail`,
  `serve_slide_version_{image,thumbnail}`) from admin routes
- `GenerateSub::Deck`, `GenerateSub::Slide`, `SpellCheck` CLI commands from main.rs
- `slide_generate`, `deck_generate` Rhai bindings; updated Rhai tests
- Unused `write_resize_marker` and `image_ref_kind` helpers

Also:
- PPTX export now triggers a browser download via new `serve_deck_pptx_download`
  route (`GET /api/collections/{col}/decks/{dk}/export/pptx`)
- dashboard.js triggers <a download> after successful PPTX job

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove dead spellCheckSlide() (called slide.spellCheckAsync which is gone)
- critiqueSlideVisual: slide.generateJobLogs/Status → generateHtmlJobLogs/Status
- pollGenerateProgress: deck.generateJobLogs/Status → generateHtmlJobLogs/Status
PDF export was broken after PNG generation was removed — pdf.rs still looked
for .png files that no longer exist. Now renders each HTML slide to PNG via
hero_browser (one browser session per deck), then assembles with printpdf as
before. deck_export_pdf now returns an error instead of a ghost success path
when no output is produced.

Also removes the dead text-extraction PPTX path (pptx.rs, generate_pptx_slides)
that was superseded by browser_pptx. Vendors dom-to-pptx@1.1.9 as a static
bundle (include_str!) eliminating the CDN runtime dependency. Drops the
LogBroadcast/stream_logs stub from hero_slides_admin that was never wired up.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add humanizeError() and wire it into rpc() and pollAiJob() so every
toast automatically shows plain language instead of HTTP status codes.
Covers 402 (credits), 401/403 (auth), 404 (model not found), 429
(rate limit), 5xx (provider issues), browser service unreachable,
PDF/slide export before generation, and server connectivity errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds an Image CLI subcommand and GenerateImage job kind that regenerate
one image slot from a description, wired through the WYSIWYG editor and
slide editor UI. Includes the admin build.rs for sccache invalidation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Restores the image (PNG) generation pipeline alongside the HTML
(Editable) pipeline so both coexist. Adds a per-slide render mode and a
per-deck default (RenderMode{Image,Html}, default Html for backward
compat), with generation dispatching to the right pipeline by resolved
mode.

- hashing: RenderMode enum, SlideMetaEntry.render_mode, GlobalMeta
  .default_render_mode, resolve_render_mode + backward-compat test
- generator: restore generate_slide / fix_slide_image / write_resize_marker
- deck: slide_generate_image / deck_generate_image (image pipeline),
  slide_generate_auto / deck_generate_auto (per-slide mode dispatch),
  spell_check restore, render-mode setters; slide_restore_version now
  syncs + returns the restored artifact's mode
- server: slide.generateAsync / deck.generateAsync (Html aliases kept),
  slide.setRenderMode, deck.setDefault/AllRenderMode; deck.get exposes
  render_mode + default_render_mode; deck.create takes default_render_mode;
  jobs submit mode-agnostic 'generate slide/deck' (CLI resolves mode)
- cli: generate deck/slide (auto), spell-check
- export: pdf + pptx handle mixed decks (image slides embed PNG)
- admin: restored image/thumbnail routes; grid image thumbnails;
  deck-create style picker; per-slide + whole-deck style switch;
  rollback warning when restoring a different-style version
- rhai: slide_generate / deck_generate / *_set_render_mode bindings

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When a slide is generated, remove the other mode's stale live artifact
from output/ (image gen drops a leftover .html; HTML gen drops a leftover
.png). archive_version prefers .html when both exist, so an image slide
with a leftover .html would archive the wrong artifact and exports could
pick the stale one. Previous artifacts remain in version history.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
casper-stevens force-pushed development_pptx_mockup from a5a3812621 to 63f98d51f7 2026-06-04 07:54:31 +00:00 Compare
Author
Member

Tested on a real mixed deck (local deploy, build #187)

Duplicated sample_deck → set 2 slides to Image, left 3 as Editable:

  • Mixed generation dispatchgenerate deck rendered the 2 image slides as PNG (Gemini) and skipped the 3 unchanged Editable slides: generated: 2, skipped: 3.
  • Mode-aware statedeck.get reports correct per-slide render_mode; generated_count = 5/5 across both artifact types.
  • Mixed PDF exportslides.pdf = 5 pages, image slides embedded directly (2752×1536) + Editable slides browser-rendered (1920×1080).
  • Cross-mode version history — after switching a slide image↔Editable, history holds both v001.html and v002.png.
  • Cross-mode rollbackslide.restoreVersion of an HTML snapshot on an image slide returns restored_mode: "html" and flips the slide back to Editable (UI warns before this).
  • Backward compat — legacy sample_deck (metadata without render-mode keys) resolves to Editable, unchanged.

Fixed during testing (commit 63f98d5): generation now removes the other mode's stale live artifact so archive_version snapshots the correct one.

⚠️ Pre-existing issue (not from this PR)

PPTX export fails with hero_browser response: error decoding response body — and it fails identically on the pure-HTML sample_deck, so it's a pre-existing problem in the dom-to-pptx / hero_browser path, independent of the Image/Editable work. PDF export is unaffected. Worth a separate look.

### Tested on a real mixed deck (local deploy, build #187) Duplicated `sample_deck` → set 2 slides to **Image**, left 3 as **Editable**: - ✅ **Mixed generation dispatch** — `generate deck` rendered the 2 image slides as PNG (Gemini) and skipped the 3 unchanged Editable slides: `generated: 2, skipped: 3`. - ✅ **Mode-aware state** — `deck.get` reports correct per-slide `render_mode`; `generated_count` = 5/5 across both artifact types. - ✅ **Mixed PDF export** — `slides.pdf` = 5 pages, image slides embedded directly (2752×1536) + Editable slides browser-rendered (1920×1080). - ✅ **Cross-mode version history** — after switching a slide image↔Editable, history holds both `v001.html` and `v002.png`. - ✅ **Cross-mode rollback** — `slide.restoreVersion` of an HTML snapshot on an image slide returns `restored_mode: "html"` and flips the slide back to Editable (UI warns before this). - ✅ **Backward compat** — legacy `sample_deck` (metadata without render-mode keys) resolves to Editable, unchanged. Fixed during testing (commit `63f98d5`): generation now removes the other mode's stale live artifact so `archive_version` snapshots the correct one. ### ⚠️ Pre-existing issue (not from this PR) **PPTX export fails** with `hero_browser response: error decoding response body` — and it fails **identically on the pure-HTML `sample_deck`**, so it's a pre-existing problem in the dom-to-pptx / hero_browser path, independent of the Image/Editable work. PDF export is unaffected. Worth a separate look.
- generate button enable + stale badge now key off the slide's resolved
  mode artifact (slideIsGenerated) instead of has_html, so image slides
  are no longer greyed out as 'up to date' before they're generated
- grid + version-strip thumbnails choose the artifact by render mode first
  (slidePreviewKind), so an image slide with a leftover .html no longer
  shows the stale HTML
- preview/present mode includes image slides and renders each slide's
  current artifact (PNG via a full-bleed wrapper, HTML via iframe) — mixed
  decks now preview correctly

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
JobKind::GenerateHtml serves both Editable and Image generation (dispatch
is by resolved render mode), so its display string was misleading 'generate
html' for image jobs. Use 'generate_slide' — also matches the dashboard's
generate_slide active-job filter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts:
#	crates/hero_slides_admin/static/js/dashboard.js
#	crates/hero_slides_sdk/src/generated/openrpc.openrpc.client.generated.rs
casper-stevens merged commit 5945262a10 into development 2026-06-04 09:16:54 +00:00
casper-stevens deleted branch development_pptx_mockup 2026-06-04 09:16:54 +00:00
Sign in to join this conversation.
No reviewers
No labels
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_slides!98
No description provided.