feat(slides): implement slide.getIncomingLinks + soften bg.listFolders empty case (closes #60) #61

Merged
mik-tf merged 1 commit from development_mik into development 2026-05-11 03:40:13 +00:00
Owner

Summary

Closes #60.

Two unrelated bugs surfaced during s91 reproduction work:

  1. slide.getIncomingLinks not implemented — UI calls 11x per dashboard load (one per slide) since PR #55 introduced the link-source badge; every call returned Method not found. Now implemented: walks every deck under the same collection root, scans metadata.toml.slides[*].source_link, returns {collection, deck, slide} entries pointing at the queried slide.
  2. bg.listFolders errored on missing backgrounds dir — returned -32000 "Not a directory: …" for any deck without backgrounds. Now returns {folders: [], root_files: []} — "no backgrounds" is the steady state for new decks, not an error.

source_link.slide_name stores whatever the setLink caller passed as src_slide_slug. Per the existence check in deck_slide_link_set (uses format!("{}.md", source_slide)), that value must be the full stem (NN_slug); a bare slug would fail at setLink time. So the handler matches the queried slide name against link.slide_name directly with no prefix stripping. The SlideLink.slide_name docstring in hashing.rs claiming "slug only" disagrees with the consumer in deck.rs:1646 — that latent inconsistency from #54 is out of scope here.

Out of scope

legacy_param_shim synthesizes path = <deck_path>/backgrounds for bg.* methods (rpc.rs:399), but lib helpers append content/background/ to whatever they receive. The synthesized path therefore never matches the actual filesystem layout — after this PR all bg.listFolders calls return empty until the shim mismatch is addressed separately. Filing a follow-up if this surfaces.

Test plan

  • cargo build --release -p hero_slides_server clean (10 pre-existing warnings, none new).
  • python3 scripts/smoke_openrpc.py — 91 methods, METHOD_MISSING 0 (was 0 already; new method wired). PARAM_MISMATCH still 4 on deckjobs.* (harness can't synthesize job_id).
  • bg.listFolders curl reproducer from the issue body returns {folders: [], root_files: []} instead of -32000.
  • Positive case: created probe link hero_slides_intro/07_s92_link_probesample_deck/01_intro, getIncomingLinks returned the linker, deleted probe after.
  • Negative case: getIncomingLinks for an unlinked slide returns {incoming_links: []}.

Signed-off-by: mik-tf

## Summary Closes https://forge.ourworld.tf/lhumina_code/hero_slides/issues/60. Two unrelated bugs surfaced during s91 reproduction work: 1. **`slide.getIncomingLinks` not implemented** — UI calls 11x per dashboard load (one per slide) since PR #55 introduced the link-source badge; every call returned `Method not found`. Now implemented: walks every deck under the same collection root, scans `metadata.toml.slides[*].source_link`, returns `{collection, deck, slide}` entries pointing at the queried slide. 2. **`bg.listFolders` errored on missing backgrounds dir** — returned `-32000 "Not a directory: …"` for any deck without backgrounds. Now returns `{folders: [], root_files: []}` — "no backgrounds" is the steady state for new decks, not an error. ## Comparison detail (getIncomingLinks) `source_link.slide_name` stores whatever the setLink caller passed as `src_slide_slug`. Per the existence check in `deck_slide_link_set` (uses `format!("{}.md", source_slide)`), that value must be the full stem (`NN_slug`); a bare slug would fail at setLink time. So the handler matches the queried slide name against `link.slide_name` directly with no prefix stripping. The `SlideLink.slide_name` docstring in `hashing.rs` claiming "slug only" disagrees with the consumer in `deck.rs:1646` — that latent inconsistency from #54 is out of scope here. ## Out of scope `legacy_param_shim` synthesizes `path = <deck_path>/backgrounds` for `bg.*` methods (`rpc.rs:399`), but lib helpers append `content/background/` to whatever they receive. The synthesized path therefore never matches the actual filesystem layout — after this PR all `bg.listFolders` calls return empty until the shim mismatch is addressed separately. Filing a follow-up if this surfaces. ## Test plan - [x] `cargo build --release -p hero_slides_server` clean (10 pre-existing warnings, none new). - [x] `python3 scripts/smoke_openrpc.py` — 91 methods, METHOD_MISSING 0 (was 0 already; new method wired). PARAM_MISMATCH still 4 on `deckjobs.*` (harness can't synthesize `job_id`). - [x] `bg.listFolders` curl reproducer from the issue body returns `{folders: [], root_files: []}` instead of `-32000`. - [x] Positive case: created probe link `hero_slides_intro/07_s92_link_probe` → `sample_deck/01_intro`, `getIncomingLinks` returned the linker, deleted probe after. - [x] Negative case: `getIncomingLinks` for an unlinked slide returns `{incoming_links: []}`. Signed-off-by: mik-tf
feat(slides): implement slide.getIncomingLinks + soften bg.listFolders empty case (closes #60)
Some checks failed
Test / test (push) Failing after 3s
Test / test (pull_request) Failing after 3s
be1b1d68c9
`slide.getIncomingLinks`: new RPC that walks every deck under the same
collection root, loads `metadata.toml`, and returns every
`{collection, deck, slide}` whose `source_link.deck_path` +
`source_link.slide_name` point at the queried slide. Eliminates the
~11x-per-dashboard-load `Method not found` log spam since the dashboard
introduced the "used as link source in N place(s)" badge in PR #55.

Comparison detail: `link.slide_name` stores whatever the setLink caller
passed as `src_slide_slug`. Per the existence check in
`deck_slide_link_set` (`format!("{}.md", source_slide)`), that value is
required to be the full stem (`NN_slug`), not a bare slug — no working
caller can pass a bare slug because setLink would error on the missing
source `.md` file. The handler therefore matches the queried slide name
against `link.slide_name` directly. The `SlideLink.slide_name` docstring
in hashing.rs (claiming "slug only, survives renumbering") is currently
aspirational and disagrees with the consumer in deck.rs:1646; that's a
latent inconsistency from #54 that this PR does not attempt to resolve.

`bg.listFolders`: returns `{folders: [], root_files: []}` when the
target directory is absent rather than `-32000 "Not a directory: …"`. A
deck with no backgrounds is the steady state for new decks; the UI
catches the error in try/catch and renders empty anyway, but the
server logs every failure on every dashboard load.

Note unrelated to #60 scope: `legacy_param_shim` synthesizes
`path = <deck_path>/backgrounds` for bg.* methods (rpc.rs:399), but the
lib helpers internally append `content/background/` to whatever they
receive as `deck_path`. So in steady state the synthesized path never
matches the actual filesystem layout, and after this fix all
`bg.listFolders` calls return empty until the shim mismatch is
addressed. Tracked separately if it surfaces.

Verified locally:
- `python3 scripts/smoke_openrpc.py` — 91 methods, METHOD_MISSING 0
  (slide.getIncomingLinks now wired). PARAM_MISMATCH still 4 on
  deckjobs.* (smoke harness limitation, not dispatch).
- bg.listFolders curl reproducer from the issue body returns
  `{folders: [], root_files: []}`.
- Created a probe link from hero_slides_intro to sample_deck/01_intro,
  confirmed getIncomingLinks returns the linker, cleaned up after.

The generated client `openrpc.client.generated.rs` is regenerated by
build.rs from `openrpc.json` and picks up both the new method and a
prior `SlideRestoreVersionOutput` shape update from
#59 that had
not been re-checked in. Both are autogenerated artifacts of the
checked-in schema.

Closes #60

Signed-off-by: mik-tf <mik-tf@users.noreply.forge.ourworld.tf>
mik-tf merged commit f34f18afe1 into development 2026-05-11 03:40:13 +00:00
mik-tf deleted branch development_mik 2026-05-11 03:40:13 +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!61
No description provided.