hero_slides_server: align JSON-RPC dispatcher with openrpc.json schema (sweep) #49

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

Context

PR #48 closed the collection.* + rpc.examplesPath dispatcher gap surfaced during #47 validation. While exercising the rest of the dashboard's CRUD on top of that fix, three more schema-vs-dispatcher mismatches surfaced in the same class. Filing as a sweep so the dashboard's full UI is unblocked, not the patch-one-method-at-a-time pattern.

Symptoms (each silently breaks a UI flow)

OpenRPC schema declares Server dispatcher actually wants UI flow blocked
deck.list records: {collection, deck_name, name, slide_count, generated_count, has_pdf, has_background} (per DeckSummary) {collection, name, path, slide_count, generated_count, has_pdf, has_background} (no deck_name) Slides tab deck dropdown — both options collapse to ${col}::undefined so user can't switch decks; URL-hash auto-select doesn't fire
slide.insert params: {collection, deck, at, slug} {deck_path, at, slug} (legacy) Slide → Create Slide modal that the JS would build (one is wired today); also blocks any clean SDK-driven creation
slide.getContent params: {collection, deck, slide} (per generated client) {deck_path, slide_name} (legacy) Slide content viewer / editor

These are all the same root cause as PR #48: the schema (and codegen + JS UI) are on the new {collection, deck, ...} API; the dispatcher is partially still on the legacy {deck_path, ...} form. Each silent failure is invisible to the operator because the JS wraps in try { … } catch { toast(…) }console.messages stays empty, browser DevTools shows nothing, only the side-effect (counter not incrementing, dropdown not selectable) tells you something failed.

Acceptance

  • deck.list response records include deck_name (matches DeckSummary schema in openrpc.json); UI deck dropdown options are uniquely-keyed.
  • deck.list accepts the optional collection filter param the JS sends in loadDecks().
  • slide.insert accepts {collection, deck, at, slug} per schema (with deck_path kept as a deprecation-warn fallback for one release if needed).
  • slide.getContent accepts {collection, deck, slide} per schema (likewise).
  • Audit pass: every method advertised in crates/hero_slides_server/openrpc.json round-trips a sample call (whether by cargo test or a small smoke script) so any other drift surfaces in CI rather than a user click.
  • Open Example Deck → click into deck → see slide thumbnails + click into a slide and see its content all work without manual dropdown gymnastics.
  • Generate All actually generates (full aibroker round-trip). Confirm where hero_aibroker reads its API keys (grep -rn 'GROQ_API_KEY\|GEMINI\|OPENAI_API_KEY' lhumina_code/hero_aibroker/crates/). Two cases: (a) reads from process env → ensure ~/hero/cfg/env/env.sh is sourced for the shell that started hero_proc_server, since hero_proc-supervised actions inherit hero_proc's env (not the operator's). (b) reads from hero_proc secrets per home#225 META compliance → populate via proc secret set GEMINI_API_KEY_PAID … etc. Then trigger deck.generate on a deck with markdown slides + theme; tail proc logs get hero_aibroker to see the outbound call; verify a generated .png lands in examples/<deck>/output/. Acceptance: at least one 01_title.md01_title.png round-trip via the configured image model (gemini-3.1-flash-image-preview is the default per <meta image-model-basic>). Without this, the dashboard's centerpiece feature ('AI slide generation') stays unverified end-to-end on this stack.

Design note

The schema is the single source of truth (per memory project_openrpc_philosophy). The fix direction should be: bring the dispatcher to match openrpc.json, not the other way. Where backward compat matters (existing scripts hitting deck_path), keep the legacy alias with a tracing::warn! deprecation just like the param_string(primary, legacy, …) helper already does for deck_path/path.

Follow-up

Also surfaced in #47's session 85 close-out:

  • service slides start --all brings up proc → router → db but not mycelium even though hero_aibroker (a transitive prereq) needs it. Plus --clear on proc wipes prior mycelium registration. → file in hero_skills separately.
  • --target root is documented + dispatched in service_mycelium.nu (line 521) but rejected by clients/target.nu validator ({driver|common|self} only). → file in hero_skills separately.

Refs:

Signed-off-by: mik-tf

## Context [PR #48](https://forge.ourworld.tf/lhumina_code/hero_slides/pulls/48) closed the `collection.*` + `rpc.examplesPath` dispatcher gap surfaced during [#47](https://forge.ourworld.tf/lhumina_code/hero_slides/issues/47) validation. While exercising the rest of the dashboard's CRUD on top of that fix, three more schema-vs-dispatcher mismatches surfaced in the same class. Filing as a sweep so the dashboard's full UI is unblocked, not the patch-one-method-at-a-time pattern. ## Symptoms (each silently breaks a UI flow) | OpenRPC schema declares | Server dispatcher actually wants | UI flow blocked | |---|---|---| | `deck.list` records: `{collection, deck_name, name, slide_count, generated_count, has_pdf, has_background}` (per `DeckSummary`) | `{collection, name, path, slide_count, generated_count, has_pdf, has_background}` (no `deck_name`) | Slides tab deck dropdown — both options collapse to `${col}::undefined` so user can't switch decks; URL-hash auto-select doesn't fire | | `slide.insert` params: `{collection, deck, at, slug}` | `{deck_path, at, slug}` (legacy) | Slide → ➕ Create Slide modal that the JS would build (one is wired today); also blocks any clean SDK-driven creation | | `slide.getContent` params: `{collection, deck, slide}` (per generated client) | `{deck_path, slide_name}` (legacy) | Slide content viewer / editor | These are all the same root cause as PR #48: the schema (and codegen + JS UI) are on the new `{collection, deck, ...}` API; the dispatcher is partially still on the legacy `{deck_path, ...}` form. **Each silent failure is invisible to the operator** because the JS wraps in `try { … } catch { toast(…) }` — `console.messages` stays empty, browser DevTools shows nothing, only the side-effect (counter not incrementing, dropdown not selectable) tells you something failed. ## Acceptance - [ ] `deck.list` response records include `deck_name` (matches `DeckSummary` schema in `openrpc.json`); UI deck dropdown options are uniquely-keyed. - [ ] `deck.list` accepts the optional `collection` filter param the JS sends in `loadDecks()`. - [ ] `slide.insert` accepts `{collection, deck, at, slug}` per schema (with `deck_path` kept as a deprecation-warn fallback for one release if needed). - [ ] `slide.getContent` accepts `{collection, deck, slide}` per schema (likewise). - [ ] Audit pass: every method advertised in `crates/hero_slides_server/openrpc.json` round-trips a sample call (whether by `cargo test` or a small smoke script) so any other drift surfaces in CI rather than a user click. - [ ] Open Example Deck → click into deck → see slide thumbnails + click into a slide and see its content all work without manual dropdown gymnastics. - [ ] **Generate All actually generates (full aibroker round-trip).** Confirm where `hero_aibroker` reads its API keys (`grep -rn 'GROQ_API_KEY\|GEMINI\|OPENAI_API_KEY' lhumina_code/hero_aibroker/crates/`). Two cases: (a) reads from process env → ensure `~/hero/cfg/env/env.sh` is sourced for the shell that started `hero_proc_server`, since hero_proc-supervised actions inherit hero_proc's env (not the operator's). (b) reads from hero_proc secrets per [home#225 META compliance](https://forge.ourworld.tf/lhumina_code/home/issues/225) → populate via `proc secret set GEMINI_API_KEY_PAID …` etc. Then trigger `deck.generate` on a deck with markdown slides + theme; tail `proc logs get hero_aibroker` to see the outbound call; verify a generated `.png` lands in `examples/<deck>/output/`. Acceptance: at least one `01_title.md` → `01_title.png` round-trip via the configured image model (`gemini-3.1-flash-image-preview` is the default per `<meta image-model-basic>`). Without this, the dashboard's centerpiece feature ('AI slide generation') stays unverified end-to-end on this stack. ## Design note The schema is the single source of truth (per memory `project_openrpc_philosophy`). The fix direction should be: bring the dispatcher to match `openrpc.json`, not the other way. Where backward compat matters (existing scripts hitting `deck_path`), keep the legacy alias with a `tracing::warn!` deprecation just like the `param_string(primary, legacy, …)` helper already does for `deck_path`/`path`. ## Follow-up Also surfaced in #47's session 85 close-out: - `service slides start --all` brings up proc → router → db but **not** mycelium even though hero_aibroker (a transitive prereq) needs it. Plus `--clear` on proc wipes prior mycelium registration. → file in `hero_skills` separately. - `--target root` is documented + dispatched in `service_mycelium.nu` (line 521) but rejected by `clients/target.nu` validator (`{driver|common|self}` only). → file in `hero_skills` separately. Refs: - https://forge.ourworld.tf/lhumina_code/hero_slides/issues/47 - https://forge.ourworld.tf/lhumina_code/hero_slides/pulls/48 - https://forge.ourworld.tf/lhumina_code/home/issues/230 - https://forge.ourworld.tf/lhumina_code/home/issues/225 Signed-off-by: mik-tf
Author
Owner

Session 86 closure — 2026-05-09

Merged: PR #50 (632603f).

Acceptance status

# Bullet Status
1 deck.list records include deck_name; UI deck dropdown options uniquely-keyed
2 deck.list accepts optional collection filter param
3 slide.insert accepts {collection, deck, at, slug} (legacy deck_path warn-fallback) via shim
4 slide.getContent accepts {collection, deck, slide} (legacy slide_name warn-fallback) via shim
5 Audit pass: every method in openrpc.json round-trips a sample call scripts/smoke_openrpc.py shipped
6 Open Example Deck → click into deck → see thumbnails + click slide → see content verified via Hero Browser MCP
7 Generate All actually generates (full aibroker round-trip) ⚠️ partial — chain works but configuration gap (see below)

What shipped

PR #50 adds a single request-preprocessor (legacy_param_shim in crates/hero_slides_server/src/rpc.rs) that translates the public openrpc.json shape ({collection, deck, slide, slides, to, filename}) into the legacy keys the existing handlers expect (deck_path, slide_name, slide_names, to_position, file, root_path, parent_path, src_deck_path, dst_deck_path, plus a synthesised path for bg.*). Reduces what would have been ~46 per-handler edits to one centralised translation step.

Also adds:

  • deck_name field on deck.list records (was the load-bearing fix for the broken deck dropdown — every option was collapsing to ${col}::undefined).
  • Optional collection filter on deck.list.
  • Three async helpers (param_deck_path, param_collection_root, param_slide) for handlers that want to call the resolver directly.
  • scripts/smoke_openrpc.py — Python stdlib smoke harness, classifies every method as OK / METHOD_MISSING / PARAM_MISMATCH / RUNTIME_ERROR.

Smoke results

Pre-fix:  ✓ 18  ! 18  ≠ 46  · 40  ? 0
Post-fix: ✓ 40  ! 15  ≠  0  · 35  ? 0   (90 non-destructive methods)

Zero PARAM_MISMATCH on read paths. Every UI flow that depended on the new {collection, deck, slide} API now reaches its handler.

Hero Browser MCP UI smoke

Against http://127.0.0.1:9988/hero_slides/admin (1440×900 headless, dark theme, console interceptor on):

  • Open Example Deck → Collections counter 0→1.
  • Click into hero_slides_examples collection → 2 deck cards (hero_slides_intro, sample_deck) render with state badges + per-deck Preview/Play/PDF actions.
  • Click into hero_slides_intro → all 6 slide thumbnails render with PNG previews.
  • Click Edit on 01_title → editor opens with markdown content + preview pane + Save / Save & Quit / Save & Generate / Image Model selector / Context selector.
  • console.messages = [] at every stage.

Remaining work — filed separately

  1. Undispatched feature handlers (15 methods)deckjobs.* (5), wizard.* (4), slide.setLink/clearLink/setImageModel/getStaleness/revertToLastGenerated/resolveContext (6), bg.extractTheme, deck.staleness, folder.pick. Same silent-failure class as the collection.* gap PR #48 closed; these need new handlers, not just shape fixes. → filed as a separate issue.
  2. "Generate All" produces images end-to-end — request chain works (hero_slides → hero_aibroker → upstream → 400 "property 'image_config' is unsupported"). Failure is configuration: this workstation's hero_aibroker has Qwen / wan2.7 image models registered (via Alibaba) but no gemini-3.1-flash-image-preview (the dashboard's default per <meta name="image-model-basic">). Routing falls back to a Qwen model that doesn't accept Gemini's image_config parameter. → filed as a hero_aibroker issue.
  3. hero_skills side trackers — (a) service slides start --all brings up proc → router → db but not mycelium even though hero_aibroker (transitive prereq) requires it; --clear on proc additionally wipes prior mycelium registration. (b) --target root documented + dispatched in service_mycelium.nu line 521 but rejected by clients/target.nu validator ({driver|common|self} only). → filed as 2 hero_skills issues.

Closing as DONE for the schema-alignment scope. Continuation work is in the new tracker(s).

Signed-off-by: mik-tf

## Session 86 closure — 2026-05-09 Merged: [PR #50](https://forge.ourworld.tf/lhumina_code/hero_slides/pulls/50) (`632603f`). ### Acceptance status | # | Bullet | Status | |---|---|---| | 1 | `deck.list` records include `deck_name`; UI deck dropdown options uniquely-keyed | ✅ | | 2 | `deck.list` accepts optional `collection` filter param | ✅ | | 3 | `slide.insert` accepts `{collection, deck, at, slug}` (legacy `deck_path` warn-fallback) | ✅ via shim | | 4 | `slide.getContent` accepts `{collection, deck, slide}` (legacy `slide_name` warn-fallback) | ✅ via shim | | 5 | Audit pass: every method in openrpc.json round-trips a sample call | ✅ `scripts/smoke_openrpc.py` shipped | | 6 | Open Example Deck → click into deck → see thumbnails + click slide → see content | ✅ verified via Hero Browser MCP | | 7 | **Generate All actually generates (full aibroker round-trip)** | ⚠️ partial — chain works but configuration gap (see below) | ### What shipped PR #50 adds a single request-preprocessor (`legacy_param_shim` in `crates/hero_slides_server/src/rpc.rs`) that translates the public `openrpc.json` shape (`{collection, deck, slide, slides, to, filename}`) into the legacy keys the existing handlers expect (`deck_path`, `slide_name`, `slide_names`, `to_position`, `file`, `root_path`, `parent_path`, `src_deck_path`, `dst_deck_path`, plus a synthesised `path` for `bg.*`). Reduces what would have been ~46 per-handler edits to one centralised translation step. Also adds: - `deck_name` field on `deck.list` records (was the load-bearing fix for the broken deck dropdown — every option was collapsing to `${col}::undefined`). - Optional `collection` filter on `deck.list`. - Three async helpers (`param_deck_path`, `param_collection_root`, `param_slide`) for handlers that want to call the resolver directly. - `scripts/smoke_openrpc.py` — Python stdlib smoke harness, classifies every method as OK / METHOD_MISSING / PARAM_MISMATCH / RUNTIME_ERROR. ### Smoke results ``` Pre-fix: ✓ 18 ! 18 ≠ 46 · 40 ? 0 Post-fix: ✓ 40 ! 15 ≠ 0 · 35 ? 0 (90 non-destructive methods) ``` **Zero PARAM_MISMATCH on read paths.** Every UI flow that depended on the new `{collection, deck, slide}` API now reaches its handler. ### Hero Browser MCP UI smoke Against `http://127.0.0.1:9988/hero_slides/admin` (1440×900 headless, dark theme, console interceptor on): - ✅ Open Example Deck → Collections counter 0→1. - ✅ Click into `hero_slides_examples` collection → 2 deck cards (`hero_slides_intro`, `sample_deck`) render with state badges + per-deck Preview/Play/PDF actions. - ✅ Click into `hero_slides_intro` → all 6 slide thumbnails render with PNG previews. - ✅ Click `Edit` on `01_title` → editor opens with markdown content + preview pane + Save / Save & Quit / Save & Generate / Image Model selector / Context selector. - ✅ `console.messages = []` at every stage. ### Remaining work — filed separately 1. **Undispatched feature handlers (15 methods)** — `deckjobs.*` (5), `wizard.*` (4), `slide.setLink/clearLink/setImageModel/getStaleness/revertToLastGenerated/resolveContext` (6), `bg.extractTheme`, `deck.staleness`, `folder.pick`. Same silent-failure class as the `collection.*` gap PR #48 closed; these need new handlers, not just shape fixes. **→ filed as a separate issue.** 2. **"Generate All" produces images end-to-end** — request chain works (hero_slides → hero_aibroker → upstream → 400 "property 'image_config' is unsupported"). Failure is configuration: this workstation's hero_aibroker has Qwen / wan2.7 image models registered (via Alibaba) but no `gemini-3.1-flash-image-preview` (the dashboard's default per `<meta name="image-model-basic">`). Routing falls back to a Qwen model that doesn't accept Gemini's `image_config` parameter. **→ filed as a hero_aibroker issue.** 3. **hero_skills side trackers** — (a) `service slides start --all` brings up proc → router → db but **not** mycelium even though `hero_aibroker` (transitive prereq) requires it; `--clear` on proc additionally wipes prior mycelium registration. (b) `--target root` documented + dispatched in `service_mycelium.nu` line 521 but rejected by `clients/target.nu` validator (`{driver|common|self}` only). **→ filed as 2 hero_skills issues.** Closing as DONE for the schema-alignment scope. Continuation work is in the new tracker(s). 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#49
No description provided.