hero_slides_server: implement 15 undispatched OpenRPC methods (deckjobs.*, wizard.*, slide.setLink/clearLink/setImageModel/getStaleness/revertToLastGenerated/resolveContext, bg.extractTheme, deck.staleness, folder.pick) #51

Closed
opened 2026-05-10 02:41:07 +00:00 by mik-tf · 1 comment
Owner

Context

Surfaced by hero_slides#49's schema audit (scripts/smoke_openrpc.py) — 15 methods are advertised in crates/hero_slides_server/openrpc.json and called by the JS dashboard, but have no match arm in crates/hero_slides_server/src/rpc.rs. Same silent-failure class as the collection.* gap PR #48 closed: each call returns -32000 "Method not found", the JS swallows it in try{…}catch{toast(…)}, and the operator only sees the side-effect (button does nothing).

The 15 methods

Slide Wizard (4) — flagship feature on the Home page CTA

  • wizard.runAsync — kick off async wizard run (params: {collection, deck, mode, intent, ...})
  • wizard.runJobStatus — poll status (params: {key})
  • wizard.runJobLogs — stream logs (params: {key, lines?})
  • wizard.runResult — read final result (params: {output_file, input_file?})

Per-deck job tracking (5) — Slides tab "Jobs" sidebar

  • deckjobs.list (params: {collection, deck})
  • deckjobs.status (params: {job_id})
  • deckjobs.logs (params: {job_id, lines?})
  • deckjobs.cancel (params: {job_id})
  • deckjobs.result (params: {job_id})

Slide linking + per-slide config (6)

  • slide.setLink — link two slides (params: {collection, deck, slide, src_collection, src_deck, src_slide_slug})
  • slide.clearLink (params: {collection, deck, slide})
  • slide.setImageModel — per-slide model override (params: {collection, deck, slide, model?})
  • slide.getStaleness (params: {deck_path, slide_name})
  • slide.revertToLastGenerated (params: {deck_path, slide_name})
  • slide.resolveContext (params: {collection, deck, slide, context_selection?})

Misc (3)

  • bg.extractTheme — derive a theme from a background image (params: {collection, deck, folder?, file?, data?, mime_type?})
  • deck.staleness (params: {path})
  • folder.pick — UI affordance for picking a filesystem folder (no params)

Acceptance

  • Every method above gains a dispatch entry in match req.method.as_str() in crates/hero_slides_server/src/rpc.rs.
  • Each handler is wired against existing helpers in hero_slides_lib where the lib already has the function (e.g. deck.staleness likely just needs to call an existing staleness helper).
  • Wizard handlers use the existing JobManager async-job pattern (see deck.generateAsync for the template).
  • scripts/smoke_openrpc.py post-fix: METHOD_MISSING count drops from 15 to 0.
  • Hero Browser MCP smoke: "Slide Wizard" button on the Slides tab opens its modal and produces a real wizard job; the per-deck Jobs sidebar populates after a generate; Slide Editor's Image Model selector persists choice via slide.setImageModel.

Design notes

  • Some methods declare {deck_path, slide_name} legacy params (e.g. slide.getStaleness, slide.revertToLastGenerated). The legacy_param_shim (PR #50) auto-translates {collection, deck, slide} → these legacy keys, so new handlers can either accept either shape or use the helpers param_deck_path / param_slide directly.
  • folder.pick is a UI affordance — server may want to return a curated set of allowed roots (e.g. ~/hero/var/hero_slides/, ~/Documents) rather than expose a raw filesystem picker.

Refs

  • #49
  • #48 (collection.* dispatch fix — same pattern)
  • #50 (legacy_param_shim — landed)
## Context Surfaced by [hero_slides#49](https://forge.ourworld.tf/lhumina_code/hero_slides/issues/49)'s schema audit (`scripts/smoke_openrpc.py`) — 15 methods are advertised in `crates/hero_slides_server/openrpc.json` and called by the JS dashboard, but have no `match` arm in `crates/hero_slides_server/src/rpc.rs`. Same silent-failure class as the `collection.*` gap [PR #48](https://forge.ourworld.tf/lhumina_code/hero_slides/pulls/48) closed: each call returns `-32000 "Method not found"`, the JS swallows it in `try{…}catch{toast(…)}`, and the operator only sees the side-effect (button does nothing). ## The 15 methods ### Slide Wizard (4) — flagship feature on the Home page CTA - `wizard.runAsync` — kick off async wizard run (params: `{collection, deck, mode, intent, ...}`) - `wizard.runJobStatus` — poll status (params: `{key}`) - `wizard.runJobLogs` — stream logs (params: `{key, lines?}`) - `wizard.runResult` — read final result (params: `{output_file, input_file?}`) ### Per-deck job tracking (5) — Slides tab "Jobs" sidebar - `deckjobs.list` (params: `{collection, deck}`) - `deckjobs.status` (params: `{job_id}`) - `deckjobs.logs` (params: `{job_id, lines?}`) - `deckjobs.cancel` (params: `{job_id}`) - `deckjobs.result` (params: `{job_id}`) ### Slide linking + per-slide config (6) - `slide.setLink` — link two slides (params: `{collection, deck, slide, src_collection, src_deck, src_slide_slug}`) - `slide.clearLink` (params: `{collection, deck, slide}`) - `slide.setImageModel` — per-slide model override (params: `{collection, deck, slide, model?}`) - `slide.getStaleness` (params: `{deck_path, slide_name}`) - `slide.revertToLastGenerated` (params: `{deck_path, slide_name}`) - `slide.resolveContext` (params: `{collection, deck, slide, context_selection?}`) ### Misc (3) - `bg.extractTheme` — derive a theme from a background image (params: `{collection, deck, folder?, file?, data?, mime_type?}`) - `deck.staleness` (params: `{path}`) - `folder.pick` — UI affordance for picking a filesystem folder (no params) ## Acceptance - [ ] Every method above gains a dispatch entry in `match req.method.as_str()` in `crates/hero_slides_server/src/rpc.rs`. - [ ] Each handler is wired against existing helpers in `hero_slides_lib` where the lib already has the function (e.g. `deck.staleness` likely just needs to call an existing staleness helper). - [ ] Wizard handlers use the existing `JobManager` async-job pattern (see `deck.generateAsync` for the template). - [ ] `scripts/smoke_openrpc.py` post-fix: METHOD_MISSING count drops from 15 to 0. - [ ] Hero Browser MCP smoke: "Slide Wizard" button on the Slides tab opens its modal and produces a real wizard job; the per-deck Jobs sidebar populates after a generate; Slide Editor's Image Model selector persists choice via `slide.setImageModel`. ## Design notes - Some methods declare `{deck_path, slide_name}` legacy params (e.g. `slide.getStaleness`, `slide.revertToLastGenerated`). The `legacy_param_shim` (PR #50) auto-translates `{collection, deck, slide}` → these legacy keys, so new handlers can either accept either shape or use the helpers `param_deck_path` / `param_slide` directly. - `folder.pick` is a UI affordance — server may want to return a curated set of allowed roots (e.g. `~/hero/var/hero_slides/`, `~/Documents`) rather than expose a raw filesystem picker. ## Refs - https://forge.ourworld.tf/lhumina_code/hero_slides/issues/49 - https://forge.ourworld.tf/lhumina_code/hero_slides/pulls/48 (collection.* dispatch fix — same pattern) - https://forge.ourworld.tf/lhumina_code/hero_slides/pulls/50 (legacy_param_shim — landed)
Author
Owner

Closing — bucket A + B verified end-to-end. Bucket C lives in #54.

Buckets shipped

Bucket Methods PR Commit LOC
A — wizard. + deckjobs.** (9 methods) wizard.runAsync, wizard.runJobStatus, wizard.runJobLogs, wizard.runResult, deckjobs.list, deckjobs.status, deckjobs.logs, deckjobs.cancel, deckjobs.result #52 1c4391e +23 / -5
B — slide. + bg.extractTheme* (4 methods) slide.setImageModel, slide.revertToLastGenerated, slide.resolveContext, bg.extractTheme #53 c897938 +183
C — blocked on missing lib code (5 methods) slide.setLink, slide.clearLink, slide.getStaleness, deck.staleness, folder.pick tracked in #54

Note: bucket A turned out to be pure dispatch glue — every handler already existed in the codebase (wizard.* in generate_job.rs:1127/1590/1602/1693, deckjobs.* in jobs/rpc.rs:46/68/75/86/102) but no match arm pointed at them. The jobs/mod.rs comment literally said the re-exports "become load-bearing when RPC handlers are wired in A.5 / A.6" — bucket A wired them.

Bucket B added 4 new handler functions (~150 LOC) wrapping existing lib helpers (slide_set_image_model, the slide_list_versions + slide_restore_version + slide_get_content composition for revert, resolve_context, extract_theme_from_image / extract_theme_from_pdf).

Verification (2026-05-10, live workstation)

Deployed the bucket-B binary to ~/hero/bin/hero_slides_server and drove the dashboard at http://127.0.0.1:9988/hero_slides/admin/ via Hero Browser MCP. Every one of the 13 newly-dispatched methods reached a real handler — zero -32601 / "Method not found" in the response set:

Method Result
wizard.runJobStatus/Logs/Result proper "no job found" / "no such file" — real handler errors
deckjobs.list returned a real JobSnapshot[]
deckjobs.status/logs/cancel/result returned "unknown job id" for the bogus id (proves JobManager dispatch)
slide.setImageModel({…model: ''}) {ok: true} — actually cleared the per-slide override
slide.resolveContext({…}) {entries: [], fingerprint: "e3b0c4..."} — resolver ran
slide.revertToLastGenerated({…}) {content: "# Write in markdown..."} — restored snapshot
bg.extractTheme({…}) proper validation error from the new handler

Dashboard loaded clean, console: [] (zero messages).

Smoke harness delta

scripts/smoke_openrpc.py METHOD_MISSING: 15 → 5 (only bucket C left, all tracked in #54).

Sister issues closed in the same arc

  • hero_aibroker#66 — Gemini image models registered (closes the Generate All produces images end-to-end gap from #49 acceptance bullet 7).

Signed-off-by: mik-tf

Closing — bucket A + B verified end-to-end. Bucket C lives in [#54](https://forge.ourworld.tf/lhumina_code/hero_slides/issues/54). ## Buckets shipped | Bucket | Methods | PR | Commit | LOC | |---|---|---|---|---| | **A — wizard.* + deckjobs.*** (9 methods) | `wizard.runAsync`, `wizard.runJobStatus`, `wizard.runJobLogs`, `wizard.runResult`, `deckjobs.list`, `deckjobs.status`, `deckjobs.logs`, `deckjobs.cancel`, `deckjobs.result` | [#52](https://forge.ourworld.tf/lhumina_code/hero_slides/pulls/52) | `1c4391e` | +23 / -5 | | **B — slide.* + bg.extractTheme** (4 methods) | `slide.setImageModel`, `slide.revertToLastGenerated`, `slide.resolveContext`, `bg.extractTheme` | [#53](https://forge.ourworld.tf/lhumina_code/hero_slides/pulls/53) | `c897938` | +183 | | **C — blocked on missing lib code** (5 methods) | `slide.setLink`, `slide.clearLink`, `slide.getStaleness`, `deck.staleness`, `folder.pick` | tracked in [#54](https://forge.ourworld.tf/lhumina_code/hero_slides/issues/54) | — | — | Note: bucket A turned out to be pure dispatch glue — every handler already existed in the codebase (wizard.* in `generate_job.rs:1127/1590/1602/1693`, deckjobs.* in `jobs/rpc.rs:46/68/75/86/102`) but no `match` arm pointed at them. The `jobs/mod.rs` comment literally said the re-exports "become load-bearing when RPC handlers are wired in A.5 / A.6" — bucket A wired them. Bucket B added 4 new handler functions (~150 LOC) wrapping existing lib helpers (`slide_set_image_model`, the `slide_list_versions` + `slide_restore_version` + `slide_get_content` composition for revert, `resolve_context`, `extract_theme_from_image` / `extract_theme_from_pdf`). ## Verification (2026-05-10, live workstation) Deployed the bucket-B binary to `~/hero/bin/hero_slides_server` and drove the dashboard at `http://127.0.0.1:9988/hero_slides/admin/` via Hero Browser MCP. Every one of the 13 newly-dispatched methods reached a real handler — **zero -32601 / "Method not found"** in the response set: | Method | Result | |---|---| | `wizard.runJobStatus/Logs/Result` | proper "no job found" / "no such file" — real handler errors | | `deckjobs.list` | returned a real `JobSnapshot[]` | | `deckjobs.status/logs/cancel/result` | returned `"unknown job id"` for the bogus id (proves JobManager dispatch) | | `slide.setImageModel({…model: ''})` | `{ok: true}` — actually cleared the per-slide override | | `slide.resolveContext({…})` | `{entries: [], fingerprint: "e3b0c4..."}` — resolver ran | | `slide.revertToLastGenerated({…})` | `{content: "# Write in markdown..."}` — restored snapshot | | `bg.extractTheme({…})` | proper validation error from the new handler | Dashboard loaded clean, console: `[]` (zero messages). ## Smoke harness delta `scripts/smoke_openrpc.py` METHOD_MISSING: 15 → 5 (only bucket C left, all tracked in [#54](https://forge.ourworld.tf/lhumina_code/hero_slides/issues/54)). ## Sister issues closed in the same arc - [hero_aibroker#66](https://forge.ourworld.tf/lhumina_code/hero_aibroker/issues/66) — Gemini image models registered (closes the *Generate All produces images end-to-end* gap from #49 acceptance bullet 7). Signed-off-by: mik-tf
Sign in to join this conversation.
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#51
No description provided.