lets change the way how we do routes and location management of slides #32

Open
opened 2026-04-21 09:42:00 +00:00 by despiegk · 3 comments
Owner

it all starts with scanning a directory to find slides
whenw e scan we should give this collection (deck collection) a name

e.g. myslides
which needs to be remembered in hero_db

see /hero_db

so we basically know name:path

then we scan, if double name of deck, we give clean error where we explain where the 2 paths are who have same name

name (deck and collection) is always underscore, lowercase, ascii only

so now we have

  • collection_name
  • deck_name
  • slide_name

the metadata per deck and slide are in the directory we scanned

the routes we have are (see skill /hero_ui_routes)

advice how to create routes best, keep all simple and is combination of collection/deck and slide name
depending context

no path in url

make sure we have full route handling on the slide tool, ONLY usingabove names

and make sure we insert the prefixes well see skill /hero_web_prefix

it all starts with scanning a directory to find slides whenw e scan we should give this collection (deck collection) a name e.g. myslides which needs to be remembered in hero_db see /hero_db so we basically know name:path then we scan, if double name of deck, we give clean error where we explain where the 2 paths are who have same name name (deck and collection) is always underscore, lowercase, ascii only so now we have - collection_name - deck_name - slide_name the metadata per deck and slide are in the directory we scanned the routes we have are (see skill /hero_ui_routes) advice how to create routes best, keep all simple and is combination of collection/deck and slide name depending context no path in url make sure we have full route handling on the slide tool, ONLY usingabove names and make sure we insert the prefixes well see skill /hero_web_prefix
Member

Implementation Spec for Issue #32

Objective

Replace filesystem-path-based deck addressing with a three-level name-only identifier space collection_name / deck_name / slide_name that survives restarts, rejects duplicates at scan time, and drives every route and RPC the slide service exposes.

Routes must be built exclusively from those names (no filesystem paths in URLs), mounted correctly under the reverse-proxy X-Forwarded-Prefix (see /hero_web_prefix), and follow hash-routing conventions (see /hero_ui_routes). The collection_name to path registry is persisted in hero_db (see /hero_db).

Requirements

  • Scanning a directory registers a deck collection under a user-supplied collection_name. Mapping collection_name to root_path is stored in hero_db (Redis-compatible encrypted store, RESP2 protocol) under the canonical hash key hero_slides:collections.
  • collection_name and every discovered deck_name must satisfy a strict regex ^[a-z0-9_]+$ (lowercase ASCII letters, digits, underscore; non-empty; no leading/trailing underscore; no consecutive underscores). Upper-case / hyphen / whitespace / non-ASCII inputs are rejected with a clear error.
  • When scanning, if two .slides marker directories (inside a single collection or across all registered collections) yield the same deck_name, scanning must fail with an error that lists both offending paths verbatim: duplicate deck name '<name>': <path_a> and <path_b>.
  • Metadata per deck / slide stays where it is today (files in the scanned directory). Only the addressing changes.
  • Every UI SPA route is rebuilt to use names only:
    #/collections, #/collections/:collection, #/collections/:collection/decks/:deck, #/collections/:collection/decks/:deck/slides/:slide, #/collections/:collection/decks/:deck/slides/:slide/history, #/collections/:collection/decks/:deck/themes, #/collections/:collection/decks/:deck/present.
  • Every server-side Axum route (/api/slide-image/..., /api/deck-pdf, /present, log/SSE streams) is rebuilt to accept (collection, deck[, slide]) name parameters; the server resolves them to a path internally via the hero_db-backed registry.
  • Every JSON-RPC method accepts collection + deck_name (+ slide_name) instead of path / deck_path / src_deck_path / dst_deck_path. This is an outright rename: the server resolves names to paths via a single helper before delegating to hero_slides_lib. hero_slides_lib keeps its &Path-based internal API unchanged.
  • All UI templates and {{ base_path }} usage must keep the reverse-proxy prefix mechanism working (it already is, per /hero_web_prefix): no absolute URL strings without base_path, middleware stays registered.
  • Per workspace rules: Rust edition 2024, Rust 1.94 stable, snake_case for infra, kebab-case for URL slugs at the HTML/DOM level, build via make, workspace target dir /Users/casperstevens/hero/build/cargo, no emojis, no AI attribution.

Skill-derived rules honored by the spec

  • hero_db (/hero_db): use hero_db_sdk::HeroDbServerClient::connect_socket against ~/hero/var/sockets/hero_db/rpc.sock (override via HERO_DB_SOCKET). Use the redis.* namespace (redis.hset, redis.hget, redis.hgetall, redis.hdel, redis.hexists) on the hash key hero_slides:collections where each field is a collection_name and each value a JSON document {"root": "<absolute path>", "registered_at": "<rfc3339>"}. Dedicated database parameter left unset (default database). All calls go through the typed openrpc_client! client; no hand-written RPC.
  • hero_ui_routes (/hero_ui_routes): hash-based SPA routing, every object and subview gets a canonical URL, rows are navigable, nested subviews addressable (/#/collections/:collection/decks/:deck/slides/:slide/history etc.), filter state round-trips through the URL. Logs stay reachable under job-scoped routes #/jobs/:job_id.
  • hero_web_prefix (/hero_web_prefix): X-Forwarded-Prefix middleware is already wired (crates/hero_slides_ui/src/routes.rs::base_path_middleware). Do not touch it. Every template URL keeps {{ base_path }} prefix; every new server-side URL emitted in HTML uses the same mechanism. Presentation-time client JS fetches must continue to target BASE + '/rpc'.

Files to Modify/Create

  • crates/hero_slides_lib/src/name.rsnew. Defines DeckCollectionName, DeckName, SlideName newtype wrappers; validate_identifier(s) function enforcing ^[a-z0-9]+(_[a-z0-9]+)*$; NameError enum.
  • crates/hero_slides_lib/src/collection.rsnew. DeckCollection struct holding name + root_path + the list of (deck_name, deck_path) tuples; scan_collection(root, name) -> Result<DeckCollection, CollectionError> which returns a structured CollectionError::DuplicateDeck { name, first, second } as soon as it sees a collision. Pure, sync; no hero_db knowledge here.
  • crates/hero_slides_lib/src/lib.rs — re-export the new modules.
  • crates/hero_slides_lib/Cargo.toml — add regex = "1" for the identifier validator (and chrono = { version = "0.4", features = ["serde"] } if not already present, for registered_at).
  • crates/hero_slides_server/Cargo.toml — add hero_db_sdk = { git = "https://forge.ourworld.tf/lhumina_code/hero_db.git", branch = "development" }, hero_rpc_openrpc, and any already-pulled siblings. Align with existing patch rules.
  • crates/hero_slides_server/src/registry.rsnew. CollectionRegistry (Clone wrapper around an Arc of a hero_db client) exposing: register(name, path), unregister(name), list() -> Vec<(name, path)>, lookup(name) -> Option<PathBuf>, resolve_deck(collection, deck_name) -> Result<PathBuf, ResolveError>, resolve_slide(collection, deck_name, slide_name) -> Result<(PathBuf, String), ResolveError>. All persistence goes through redis.hset / redis.hgetall on key hero_slides:collections. Includes unit test doubles by accepting a trait so a memory-backed fake can be used from tests.
  • crates/hero_slides_server/src/main.rs — construct the CollectionRegistry once at startup; attach to ServerState. Fail fast with a clear message if hero_db socket is missing.
  • crates/hero_slides_server/src/rpc.rs — rewrite every parameter parsing block to read collection + deck + slide strings (and src_collection / src_deck + dst_collection / dst_deck for slide.copyTo), call registry.resolve_*, then delegate to hero_slides_lib exactly as today. Replace deck.scan { path } with deck.scan { collection, path } which registers the collection in hero_db only after successful duplicate-free validation.
  • crates/hero_slides_server/src/agent.rs, crates/hero_slides_server/src/generate_job.rs — swap deck_path/path param readers for the name-based resolver. Same RPC method names, new parameter names.
  • crates/hero_slides_server/openrpc.json — update every method's parameter schema: path / deck_path to collection + deck / slide. Add collection.list, collection.register, collection.unregister, collection.get management methods.
  • crates/hero_slides_sdk/src/lib.rs — unchanged Rust source; the openrpc_client! macro regenerates types from the new spec. Document a one-time cargo clean -p hero_rpc_derive && cargo clean -p hero_slides_sdk && cargo build step.
  • crates/hero_slides_ui/src/routes.rs — replace path-keyed Axum routes:
    • /present?deck=<path> to /present/{collection}/{deck}
    • /api/slide-image/{slide}?deck=<path> to /api/collections/{collection}/decks/{deck}/slides/{slide}/image
    • /api/slide-version-image/{slide}/{version}?deck=<path> to /api/collections/{collection}/decks/{deck}/slides/{slide}/versions/{version}/image
    • /api/deck-pdf?deck=<path> to /api/collections/{collection}/decks/{deck}/pdf
      The handlers must resolve names by calling /rpc (deck.get / collection.lookup) or, preferably, read a small in-process name→path cache populated from hero_db via the SDK.
  • crates/hero_slides_ui/Cargo.tomlhero_slides_sdk is already present; may add hero_db_sdk if the UI resolves names client-side (preferred: keep resolution server-side via the existing rpc_proxy so UI only depends on names).
  • crates/hero_slides_ui/templates/present.html — replace DECK_PATH / deck_path_encoded with COLLECTION_NAME + DECK_NAME; build image URLs from BASE + '/api/collections/' + COLLECTION + '/decks/' + DECK + '/slides/' + slide + '/image'; change slide.list, deck.runAgent, deck.agentStatus, deck.generateAsync, deck.generateJobStatus RPC params to { collection, deck }.
  • crates/hero_slides_ui/static/js/dashboard.js — replace every deck_path: selectedDeckPath with collection: selectedCollection, deck: selectedDeckName; replace every src_deck_path / dst_deck_path with src_collection / src_deck / dst_collection / dst_deck; rewrite hash routes:
    • #slides/<path> to #collections/<collection>/decks/<deck>/slides
    • #slide/<path>/<slide>[/history] to #collections/<collection>/decks/<deck>/slides/<slide>[/history]
    • #themes/<path> to #collections/<collection>/decks/<deck>/themes
    • #presentation/<path> to #collections/<collection>/decks/<deck>/present
    • #docs/..., #jobs, #job/..., #stats, #admin, #templates unchanged.
      Surface a top-level #collections section listing every registered collection (deck count, root path) with rows linking into #collections/:collection.
  • crates/hero_slides_ui/templates/index.html — add a "Collections" tab at the top of #main-tabs; keep existing tabs; the "Scan" form now takes two inputs: Collection name (free text, validated client-side by regex) + Path.
  • crates/hero_slides/src/main.rs — CLI --scan must now take <collection_name>=<path> or accept a second positional. Update cmd_scan / cmd_generate to pass collection + deck instead of path.
  • crates/hero_slides_rhai/src/deck_module.rs, crates/hero_slides_rhai/src/slide_module.rs — rename Rhai bindings to take (collection, deck[, slide]) strings. Internally the rhai bindings can still resolve names via the registry — but since rhai is typically run against a filesystem root in scripts, provide both a name-based binding and a path-based helper deck_path_from_names(collection, deck) for escape-hatch scenarios.
  • crates/hero_slides/README.md and IMPLEMENTATION_SPEC.md — update snippets to reflect the new addressing model.

Implementation Plan

Step 1: Add identifier validator and collection scanner

Files:

  • crates/hero_slides_lib/src/name.rs (new)

  • crates/hero_slides_lib/src/collection.rs (new)

  • crates/hero_slides_lib/src/lib.rs

  • crates/hero_slides_lib/Cargo.toml

  • Create NameError enum (Empty, InvalidChar(char), LeadingUnderscore, TrailingUnderscore, DoubleUnderscore) and validate_identifier(&str) -> Result<&str, NameError> accepting only [a-z0-9_]+ with no leading/trailing/double underscore.

  • Add DeckCollectionName, DeckName, SlideName newtype wrappers with from_str / as_str / Display and a TryFrom<String> that runs the validator.

  • Implement scan_collection(root: &Path, name: &DeckCollectionName) -> Result<DeckCollection, CollectionError>. Reuses crate::discovery::scan_decks_dirs, then for each path derives deck_name from the directory name via validate_identifier, collecting HashMap<String, PathBuf>. On collision, returns CollectionError::DuplicateDeck { name, first: PathBuf, second: PathBuf } whose Display prints exactly the error wording required by the issue.

  • Re-export both modules from lib.rs.

  • Add unit tests: valid names, rejected names (uppercase, hyphen, leading underscore, double underscore, empty, whitespace, UTF-8), scan detects duplicates, scan rejects deck directories whose name is not a valid identifier.

Dependencies: none.

Step 2: hero_db-backed collection registry inside the server

Files:

  • crates/hero_slides_server/Cargo.toml

  • crates/hero_slides_server/src/registry.rs (new)

  • crates/hero_slides_server/src/main.rs

  • Add dependencies: hero_db_sdk (via its git source as listed in /hero_db), hero_rpc_openrpc for the shared OpenRpcError, chrono.

  • Define trait CollectionStore with async methods register / unregister / list / lookup; provide HeroDbCollectionStore impl that wraps HeroDbServerClient and persists to the hash hero_slides:collections via redis.hset / redis.hget / redis.hgetall / redis.hdel / redis.hexists. Value format: JSON {"root": String, "registered_at": String}.

  • CollectionRegistry owns an Arc<dyn CollectionStore> and exposes the resolver helpers: resolve_collection_root(&CollectionName) -> Result<PathBuf, ResolveError>, resolve_deck(&CollectionName, &DeckName) -> Result<PathBuf, ResolveError> (rescans the collection and returns the matching absolute deck path, also re-checking for duplicates), resolve_slide(&CollectionName, &DeckName, &SlideName) -> Result<(PathBuf, String), ResolveError>.

  • ResolveError variants: UnknownCollection, CollectionRootMissing, UnknownDeck, UnknownSlide, DuplicateDeck { first, second }, InvalidName(NameError), Store(String) — each mapped to a clean JSON-RPC error message by rpc.rs.

  • In main.rs, at startup construct HeroDbServerClient::connect_socket(...), wrap in CollectionRegistry, attach to ServerState. Log the hero_db socket path and fail hard with a pointer to the setup docs if unreachable.

  • Unit-test the registry against an in-memory CollectionStore fake.

Dependencies: Step 1.

Step 3: Rewrite RPC parameter parsing to be name-based

Files:

  • crates/hero_slides_server/src/rpc.rs

  • crates/hero_slides_server/src/agent.rs

  • crates/hero_slides_server/src/generate_job.rs

  • crates/hero_slides_server/openrpc.json

  • Add a helper module rpc_params.rs (inside crates/hero_slides_server/src/) with read_collection_deck(params, state) -> Result<(CollectionName, DeckName, PathBuf), String> and read_collection_deck_slide(...). Every handler calls this first.

  • For each RPC method in rpc.rs, replace deck_path / path parameter lookup with the helper calls; call hero_slides_lib::deck_* with the resolved &Path exactly as before. Keep response shapes unchanged except: drop path fields from deck responses and add collection + deck_name (path may remain as a debug-only optional field).

  • deck.scan now takes { collection: String, path: String }: validates collection via the identifier validator, runs scan_collection, persists via the registry (overwriting on re-scan), returns { collection, root, decks: [{ name, slide_count, generated_count, has_pdf, has_background }] }. On duplicate it returns the structured error text.

  • Add four new RPC methods: collection.list, collection.get { collection }, collection.unregister { collection }, collection.rescan { collection } (convenience wrapper around scan using the stored root).

  • Update agent.rs and generate_job.rs handlers the same way (they currently accept deck_path strings).

  • Update openrpc.json to match (regenerate every method's params section). Bump info.version to 0.2.0.

Dependencies: Step 2.

Step 4: Rewrite Axum server-side HTTP routes in the UI crate

Files:

  • crates/hero_slides_ui/src/routes.rs

  • crates/hero_slides_ui/templates/present.html

  • Replace the four path-parameter / query-string routes with name-parameter routes listed in the Files section.

  • Inside each handler, build the target PNG / PDF path by making a server-side deck.get { collection, deck } RPC call over the existing rpc_proxy helper to get the absolute deck path, then stream the file exactly as today. (Do not introduce a direct hero_db dependency in the UI crate; keep it a pure proxy.)

  • present_page now takes Path((collection, deck)): Path<(String, String)>; template context gets collection_name and deck_name.

  • present.html: replace DECK_PATH with COLLECTION + DECK; rebuild every fetch URL and RPC params.

  • Keep base_path_middleware registered; keep every {{ base_path }} prefix in HTML / JS. Do not bypass the prefix for the new name-based endpoints.

Dependencies: Step 3.

Step 5: Rewrite the SPA router and dashboard RPC calls

Files:

  • crates/hero_slides_ui/static/js/dashboard.js

  • crates/hero_slides_ui/templates/index.html

  • Add a new top-level collections tab and the associated render code: table of { collection, deck_count, root } backed by collection.list; row click to #collections/<collection>; add "Unregister" and "Rescan" buttons.

  • Replace every deck_path / selectedDeckPath usage with selectedCollection + selectedDeckName; keep selectedDeckPath only as a derived read-only field if some code still needs the path (prefer deleting it).

  • Rewrite applyCurrentRoute to handle the new hash shape: route sections become collections, and subroutes are parsed by positional tokens decks, slides, themes, present, history. Every navigateTo(...) callsite is updated; present opens a new window at BASE + '/present/' + collection + '/' + deck.

  • Change the Scan form in index.html to take both inputs (collection name + path) and call deck.scan { collection, path }. Client-side validator mirrors the server regex.

  • Ensure filter / tab / deck-select state is reflected in the hash (per /hero_ui_routes).

Dependencies: Step 3, Step 4. Can proceed in parallel with Step 6.

Step 6: Update CLI and Rhai bindings

Files:

  • crates/hero_slides/src/main.rs

  • crates/hero_slides_rhai/src/deck_module.rs

  • crates/hero_slides_rhai/src/slide_module.rs

  • crates/hero_slides_rhai/scripts/*.rhai

  • CLI: change --scan <path> to --scan <collection>=<path>; add --collection <name> and --deck <name> arguments consumed by --generate. Update cmd_scan / cmd_generate to call the new SDK methods with name parameters.

  • Rhai: rename deck_scan(root) -> decks to deck_scan(collection, root) -> decks; rename deck_info(path) / deck_generate(path, force) / etc. to take (collection, deck) strings. The bindings resolve names locally by calling scan_collection themselves (they have no RPC client), then delegate to the same hero_slides_lib functions.

  • Update the seven built-in .rhai scripts to accept ARGS = [collection, deck, ...].

  • Update README.md usage examples accordingly.

Dependencies: Step 3. Can proceed in parallel with Step 5 (non-overlapping file sets).

Step 7: Docs, smoke tests, end-to-end validation

Files:

  • IMPLEMENTATION_SPEC.md

  • README.md

  • crates/hero_slides/README.md (if present)

  • testplan/ (update or add a file documenting the new route / RPC contract)

  • Refresh all route/RPC tables and diagrams to use the collection/deck/slide model.

  • Document the hero_db dependency: socket path, required HERO_DB_SOCKET env var override, "how to run hero_db locally" pointer.

  • Document the error-message format for duplicate decks verbatim.

  • Run make fmt / make lint / make test / make build to confirm everything compiles and existing tests still pass. Manual smoke: make installdev && make run then exercise deck.scan / collection.list / deck.get / slide.generate / the new /api/collections/.../pdf endpoint and /present/:collection/:deck. Walk the SPA URL-by-URL to confirm hash routes round-trip through browser reload per /hero_ui_routes.

Dependencies: Steps 1-6.

Acceptance Criteria

  • Scanning /path/to/workspace under a collection name writes hero_slides:collections[<name>] in hero_db and survives a server restart.
  • Re-scanning produces identical output; a modified tree is reflected on the next collection.rescan.
  • A directory tree containing two decks with the same directory name (inside the same collection or across registered collections) fails deck.scan and collection.rescan with: duplicate deck name 'foo': <path_a> and <path_b>. No partial registration is persisted.
  • collection_name / deck_name / slide_name that violate ^[a-z0-9_]+$ (upper-case, hyphen, whitespace, empty, leading/trailing underscore, double underscore, non-ASCII) are rejected at every entry point (RPC, CLI, Rhai) with a clear error naming the offending value.
  • hero_slides_lib internally still operates on &Path; only the server / UI / CLI / Rhai boundaries deal with names.
  • No JSON-RPC method accepts path / deck_path / src_deck_path / dst_deck_path any more — they are replaced by collection, deck, slide, src_collection, dst_collection, etc. openrpc.json and the regenerated SDK reflect this.
  • No URL in the UI — hash route, Axum route, template href, template src, or JS fetch URL — contains a filesystem path.
  • Every hash-route from the issue body has a canonical form; a browser MCP can deterministically walk #collections to #collections/:c to #collections/:c/decks/:d to #collections/:c/decks/:d/slides/:s[/history].
  • X-Forwarded-Prefix still works: when the service is fronted under /hero_slides/, every generated link starts with that prefix and every image / PDF / RPC fetch resolves correctly.
  • make fmt, make lint, make test, make build, make installdev, make run all succeed from a clean workspace.
  • Existing unit tests in hero_slides_lib (discovery.rs, parser.rs, hashing.rs, slide_ops.rs, deck.rs) still pass; new unit tests added for name.rs, collection.rs, registry.rs.

Notes

  • The hero_db dependency is new for this repo; verify the daemon is reachable at ~/hero/var/sockets/hero_db/rpc.sock before launching. The server should emit a single, loud log line if the socket is missing and exit non-zero — silently degrading to in-memory state would break restart persistence and is explicitly not allowed by the issue.
  • slide_name is simply the slide file stem (e.g. 01_intro). It may contain digits and underscores; it must still pass the validator. When existing decks on disk have slide files with hyphens, mixed case, or other characters, the scan rejects them at the slide.list boundary — this is deliberate and called out in Step 1 tests.
  • Step 5 and Step 6 can safely run in parallel; they touch disjoint file sets once Step 3 lands.
  • The slide.copyTo RPC crosses deck boundaries; its parameter rename is src_collection + src_deck + slide + dst_collection + dst_deck (five strings, no paths).
  • Backwards compatibility is not a goal. Issue #32 asks to "change the way" routes and locations are managed; hence RPCs and URLs are renamed outright rather than aliased. Migration note belongs in IMPLEMENTATION_SPEC.md Step 7.
  • Keep the reverse-proxy behaviour from /hero_web_prefix untouched: the middleware reads the header per request; templates use {{ base_path }}; JS reads BASE from the <meta name="base-path"> tag. No BASE_PATH env var is introduced.
  • The routes and hash segments in this spec intentionally use kebab-case-free names: both URL segments and identifiers share the snake_case / [a-z0-9_]+ alphabet because the identifiers themselves are already lowercase ASCII with underscores — no separate URL slug transform is needed. The overall URL shape uses plural-resource segments (collections, decks, slides) per /hero_ui_routes.
## Implementation Spec for Issue #32 ### Objective Replace filesystem-path-based deck addressing with a three-level name-only identifier space `collection_name / deck_name / slide_name` that survives restarts, rejects duplicates at scan time, and drives every route and RPC the slide service exposes. Routes must be built exclusively from those names (no filesystem paths in URLs), mounted correctly under the reverse-proxy `X-Forwarded-Prefix` (see `/hero_web_prefix`), and follow hash-routing conventions (see `/hero_ui_routes`). The `collection_name` to `path` registry is persisted in `hero_db` (see `/hero_db`). ### Requirements - Scanning a directory registers a deck **collection** under a user-supplied `collection_name`. Mapping `collection_name` to `root_path` is stored in `hero_db` (Redis-compatible encrypted store, RESP2 protocol) under the canonical hash key `hero_slides:collections`. - `collection_name` and every discovered `deck_name` must satisfy a strict regex `^[a-z0-9_]+$` (lowercase ASCII letters, digits, underscore; non-empty; no leading/trailing underscore; no consecutive underscores). Upper-case / hyphen / whitespace / non-ASCII inputs are rejected with a clear error. - When scanning, if two `.slides` marker directories (inside a single collection **or** across all registered collections) yield the same `deck_name`, scanning must fail with an error that lists both offending paths verbatim: `duplicate deck name '<name>': <path_a> and <path_b>`. - Metadata per deck / slide stays where it is today (files in the scanned directory). Only the addressing changes. - Every UI SPA route is rebuilt to use names only: `#/collections`, `#/collections/:collection`, `#/collections/:collection/decks/:deck`, `#/collections/:collection/decks/:deck/slides/:slide`, `#/collections/:collection/decks/:deck/slides/:slide/history`, `#/collections/:collection/decks/:deck/themes`, `#/collections/:collection/decks/:deck/present`. - Every server-side Axum route (`/api/slide-image/...`, `/api/deck-pdf`, `/present`, log/SSE streams) is rebuilt to accept `(collection, deck[, slide])` name parameters; the server resolves them to a path internally via the hero_db-backed registry. - Every JSON-RPC method accepts `collection` + `deck_name` (+ `slide_name`) instead of `path` / `deck_path` / `src_deck_path` / `dst_deck_path`. This is an outright rename: the server resolves names to paths via a single helper before delegating to `hero_slides_lib`. `hero_slides_lib` keeps its `&Path`-based internal API unchanged. - All UI templates and `{{ base_path }}` usage must keep the reverse-proxy prefix mechanism working (it already is, per `/hero_web_prefix`): no absolute URL strings without `base_path`, middleware stays registered. - Per workspace rules: Rust edition 2024, Rust 1.94 stable, `snake_case` for infra, `kebab-case` for URL slugs at the HTML/DOM level, build via `make`, workspace target dir `/Users/casperstevens/hero/build/cargo`, no emojis, no AI attribution. ### Skill-derived rules honored by the spec - **hero_db** (`/hero_db`): use `hero_db_sdk::HeroDbServerClient::connect_socket` against `~/hero/var/sockets/hero_db/rpc.sock` (override via `HERO_DB_SOCKET`). Use the `redis.*` namespace (`redis.hset`, `redis.hget`, `redis.hgetall`, `redis.hdel`, `redis.hexists`) on the hash key `hero_slides:collections` where each field is a `collection_name` and each value a JSON document `{"root": "<absolute path>", "registered_at": "<rfc3339>"}`. Dedicated database parameter left unset (default database). All calls go through the typed `openrpc_client!` client; no hand-written RPC. - **hero_ui_routes** (`/hero_ui_routes`): hash-based SPA routing, every object and subview gets a canonical URL, rows are navigable, nested subviews addressable (`/#/collections/:collection/decks/:deck/slides/:slide/history` etc.), filter state round-trips through the URL. Logs stay reachable under job-scoped routes `#/jobs/:job_id`. - **hero_web_prefix** (`/hero_web_prefix`): `X-Forwarded-Prefix` middleware is already wired (`crates/hero_slides_ui/src/routes.rs::base_path_middleware`). Do not touch it. Every template URL keeps `{{ base_path }}` prefix; every new server-side URL emitted in HTML uses the same mechanism. Presentation-time client JS fetches must continue to target `BASE + '/rpc'`. ### Files to Modify/Create - `crates/hero_slides_lib/src/name.rs` — **new**. Defines `DeckCollectionName`, `DeckName`, `SlideName` newtype wrappers; `validate_identifier(s)` function enforcing `^[a-z0-9]+(_[a-z0-9]+)*$`; `NameError` enum. - `crates/hero_slides_lib/src/collection.rs` — **new**. `DeckCollection` struct holding `name` + `root_path` + the list of `(deck_name, deck_path)` tuples; `scan_collection(root, name) -> Result<DeckCollection, CollectionError>` which returns a structured `CollectionError::DuplicateDeck { name, first, second }` as soon as it sees a collision. Pure, sync; no hero_db knowledge here. - `crates/hero_slides_lib/src/lib.rs` — re-export the new modules. - `crates/hero_slides_lib/Cargo.toml` — add `regex = "1"` for the identifier validator (and `chrono = { version = "0.4", features = ["serde"] }` if not already present, for `registered_at`). - `crates/hero_slides_server/Cargo.toml` — add `hero_db_sdk = { git = "https://forge.ourworld.tf/lhumina_code/hero_db.git", branch = "development" }`, `hero_rpc_openrpc`, and any already-pulled siblings. Align with existing patch rules. - `crates/hero_slides_server/src/registry.rs` — **new**. `CollectionRegistry` (`Clone` wrapper around an `Arc` of a hero_db client) exposing: `register(name, path)`, `unregister(name)`, `list() -> Vec<(name, path)>`, `lookup(name) -> Option<PathBuf>`, `resolve_deck(collection, deck_name) -> Result<PathBuf, ResolveError>`, `resolve_slide(collection, deck_name, slide_name) -> Result<(PathBuf, String), ResolveError>`. All persistence goes through `redis.hset` / `redis.hgetall` on key `hero_slides:collections`. Includes unit test doubles by accepting a trait so a memory-backed fake can be used from tests. - `crates/hero_slides_server/src/main.rs` — construct the `CollectionRegistry` once at startup; attach to `ServerState`. Fail fast with a clear message if hero_db socket is missing. - `crates/hero_slides_server/src/rpc.rs` — rewrite every parameter parsing block to read `collection` + `deck` + `slide` strings (and `src_collection` / `src_deck` + `dst_collection` / `dst_deck` for `slide.copyTo`), call `registry.resolve_*`, then delegate to `hero_slides_lib` exactly as today. Replace `deck.scan { path }` with `deck.scan { collection, path }` which registers the collection in hero_db only after successful duplicate-free validation. - `crates/hero_slides_server/src/agent.rs`, `crates/hero_slides_server/src/generate_job.rs` — swap `deck_path`/`path` param readers for the name-based resolver. Same RPC method names, new parameter names. - `crates/hero_slides_server/openrpc.json` — update every method's parameter schema: `path` / `deck_path` to `collection` + `deck` / `slide`. Add `collection.list`, `collection.register`, `collection.unregister`, `collection.get` management methods. - `crates/hero_slides_sdk/src/lib.rs` — unchanged Rust source; the `openrpc_client!` macro regenerates types from the new spec. Document a one-time `cargo clean -p hero_rpc_derive && cargo clean -p hero_slides_sdk && cargo build` step. - `crates/hero_slides_ui/src/routes.rs` — replace path-keyed Axum routes: - `/present?deck=<path>` to `/present/{collection}/{deck}` - `/api/slide-image/{slide}?deck=<path>` to `/api/collections/{collection}/decks/{deck}/slides/{slide}/image` - `/api/slide-version-image/{slide}/{version}?deck=<path>` to `/api/collections/{collection}/decks/{deck}/slides/{slide}/versions/{version}/image` - `/api/deck-pdf?deck=<path>` to `/api/collections/{collection}/decks/{deck}/pdf` The handlers must resolve names by calling `/rpc` (`deck.get` / `collection.lookup`) or, preferably, read a small in-process name→path cache populated from hero_db via the SDK. - `crates/hero_slides_ui/Cargo.toml` — `hero_slides_sdk` is already present; may add `hero_db_sdk` if the UI resolves names client-side (preferred: keep resolution server-side via the existing `rpc_proxy` so UI only depends on names). - `crates/hero_slides_ui/templates/present.html` — replace `DECK_PATH` / `deck_path_encoded` with `COLLECTION_NAME` + `DECK_NAME`; build image URLs from `BASE + '/api/collections/' + COLLECTION + '/decks/' + DECK + '/slides/' + slide + '/image'`; change `slide.list`, `deck.runAgent`, `deck.agentStatus`, `deck.generateAsync`, `deck.generateJobStatus` RPC params to `{ collection, deck }`. - `crates/hero_slides_ui/static/js/dashboard.js` — replace every `deck_path: selectedDeckPath` with `collection: selectedCollection, deck: selectedDeckName`; replace every `src_deck_path` / `dst_deck_path` with `src_collection` / `src_deck` / `dst_collection` / `dst_deck`; rewrite hash routes: - `#slides/<path>` to `#collections/<collection>/decks/<deck>/slides` - `#slide/<path>/<slide>[/history]` to `#collections/<collection>/decks/<deck>/slides/<slide>[/history]` - `#themes/<path>` to `#collections/<collection>/decks/<deck>/themes` - `#presentation/<path>` to `#collections/<collection>/decks/<deck>/present` - `#docs/...`, `#jobs`, `#job/...`, `#stats`, `#admin`, `#templates` unchanged. Surface a top-level `#collections` section listing every registered collection (deck count, root path) with rows linking into `#collections/:collection`. - `crates/hero_slides_ui/templates/index.html` — add a "Collections" tab at the top of `#main-tabs`; keep existing tabs; the "Scan" form now takes **two** inputs: `Collection name` (free text, validated client-side by regex) + `Path`. - `crates/hero_slides/src/main.rs` — CLI `--scan` must now take `<collection_name>=<path>` or accept a second positional. Update `cmd_scan` / `cmd_generate` to pass `collection` + `deck` instead of `path`. - `crates/hero_slides_rhai/src/deck_module.rs`, `crates/hero_slides_rhai/src/slide_module.rs` — rename Rhai bindings to take `(collection, deck[, slide])` strings. Internally the rhai bindings can still resolve names via the registry — but since rhai is typically run against a filesystem root in scripts, provide both a name-based binding and a path-based helper `deck_path_from_names(collection, deck)` for escape-hatch scenarios. - `crates/hero_slides/README.md` and `IMPLEMENTATION_SPEC.md` — update snippets to reflect the new addressing model. ### Implementation Plan #### Step 1: Add identifier validator and collection scanner Files: - `crates/hero_slides_lib/src/name.rs` (new) - `crates/hero_slides_lib/src/collection.rs` (new) - `crates/hero_slides_lib/src/lib.rs` - `crates/hero_slides_lib/Cargo.toml` - Create `NameError` enum (`Empty`, `InvalidChar(char)`, `LeadingUnderscore`, `TrailingUnderscore`, `DoubleUnderscore`) and `validate_identifier(&str) -> Result<&str, NameError>` accepting only `[a-z0-9_]+` with no leading/trailing/double underscore. - Add `DeckCollectionName`, `DeckName`, `SlideName` newtype wrappers with `from_str` / `as_str` / `Display` and a `TryFrom<String>` that runs the validator. - Implement `scan_collection(root: &Path, name: &DeckCollectionName) -> Result<DeckCollection, CollectionError>`. Reuses `crate::discovery::scan_decks_dirs`, then for each path derives `deck_name` from the directory name via `validate_identifier`, collecting `HashMap<String, PathBuf>`. On collision, returns `CollectionError::DuplicateDeck { name, first: PathBuf, second: PathBuf }` whose `Display` prints exactly the error wording required by the issue. - Re-export both modules from `lib.rs`. - Add unit tests: valid names, rejected names (uppercase, hyphen, leading underscore, double underscore, empty, whitespace, UTF-8), scan detects duplicates, scan rejects deck directories whose name is not a valid identifier. Dependencies: none. #### Step 2: hero_db-backed collection registry inside the server Files: - `crates/hero_slides_server/Cargo.toml` - `crates/hero_slides_server/src/registry.rs` (new) - `crates/hero_slides_server/src/main.rs` - Add dependencies: `hero_db_sdk` (via its git source as listed in `/hero_db`), `hero_rpc_openrpc` for the shared `OpenRpcError`, `chrono`. - Define `trait CollectionStore` with async methods `register / unregister / list / lookup`; provide `HeroDbCollectionStore` impl that wraps `HeroDbServerClient` and persists to the hash `hero_slides:collections` via `redis.hset` / `redis.hget` / `redis.hgetall` / `redis.hdel` / `redis.hexists`. Value format: JSON `{"root": String, "registered_at": String}`. - `CollectionRegistry` owns an `Arc<dyn CollectionStore>` and exposes the resolver helpers: `resolve_collection_root(&CollectionName) -> Result<PathBuf, ResolveError>`, `resolve_deck(&CollectionName, &DeckName) -> Result<PathBuf, ResolveError>` (rescans the collection and returns the matching absolute deck path, also re-checking for duplicates), `resolve_slide(&CollectionName, &DeckName, &SlideName) -> Result<(PathBuf, String), ResolveError>`. - `ResolveError` variants: `UnknownCollection`, `CollectionRootMissing`, `UnknownDeck`, `UnknownSlide`, `DuplicateDeck { first, second }`, `InvalidName(NameError)`, `Store(String)` — each mapped to a clean JSON-RPC error message by `rpc.rs`. - In `main.rs`, at startup construct `HeroDbServerClient::connect_socket(...)`, wrap in `CollectionRegistry`, attach to `ServerState`. Log the hero_db socket path and fail hard with a pointer to the setup docs if unreachable. - Unit-test the registry against an in-memory `CollectionStore` fake. Dependencies: Step 1. #### Step 3: Rewrite RPC parameter parsing to be name-based Files: - `crates/hero_slides_server/src/rpc.rs` - `crates/hero_slides_server/src/agent.rs` - `crates/hero_slides_server/src/generate_job.rs` - `crates/hero_slides_server/openrpc.json` - Add a helper module `rpc_params.rs` (inside `crates/hero_slides_server/src/`) with `read_collection_deck(params, state)` -> `Result<(CollectionName, DeckName, PathBuf), String>` and `read_collection_deck_slide(...)`. Every handler calls this first. - For each RPC method in `rpc.rs`, replace `deck_path` / `path` parameter lookup with the helper calls; call `hero_slides_lib::deck_*` with the resolved `&Path` exactly as before. Keep response shapes unchanged except: drop `path` fields from deck responses and add `collection` + `deck_name` (path may remain as a debug-only optional field). - `deck.scan` now takes `{ collection: String, path: String }`: validates `collection` via the identifier validator, runs `scan_collection`, persists via the registry (overwriting on re-scan), returns `{ collection, root, decks: [{ name, slide_count, generated_count, has_pdf, has_background }] }`. On duplicate it returns the structured error text. - Add four new RPC methods: `collection.list`, `collection.get { collection }`, `collection.unregister { collection }`, `collection.rescan { collection }` (convenience wrapper around scan using the stored root). - Update `agent.rs` and `generate_job.rs` handlers the same way (they currently accept `deck_path` strings). - Update `openrpc.json` to match (regenerate every method's params section). Bump `info.version` to `0.2.0`. Dependencies: Step 2. #### Step 4: Rewrite Axum server-side HTTP routes in the UI crate Files: - `crates/hero_slides_ui/src/routes.rs` - `crates/hero_slides_ui/templates/present.html` - Replace the four path-parameter / query-string routes with name-parameter routes listed in the Files section. - Inside each handler, build the target PNG / PDF path by making a server-side `deck.get { collection, deck }` RPC call over the existing `rpc_proxy` helper to get the absolute deck path, then stream the file exactly as today. (Do **not** introduce a direct hero_db dependency in the UI crate; keep it a pure proxy.) - `present_page` now takes `Path((collection, deck)): Path<(String, String)>`; template context gets `collection_name` and `deck_name`. - `present.html`: replace `DECK_PATH` with `COLLECTION` + `DECK`; rebuild every fetch URL and RPC params. - Keep `base_path_middleware` registered; keep every `{{ base_path }}` prefix in HTML / JS. Do not bypass the prefix for the new name-based endpoints. Dependencies: Step 3. #### Step 5: Rewrite the SPA router and dashboard RPC calls Files: - `crates/hero_slides_ui/static/js/dashboard.js` - `crates/hero_slides_ui/templates/index.html` - Add a new top-level `collections` tab and the associated render code: table of `{ collection, deck_count, root }` backed by `collection.list`; row click to `#collections/<collection>`; add "Unregister" and "Rescan" buttons. - Replace every `deck_path` / `selectedDeckPath` usage with `selectedCollection` + `selectedDeckName`; keep `selectedDeckPath` only as a derived read-only field if some code still needs the path (prefer deleting it). - Rewrite `applyCurrentRoute` to handle the new hash shape: route sections become `collections`, and subroutes are parsed by positional tokens `decks`, `slides`, `themes`, `present`, `history`. Every `navigateTo(...)` callsite is updated; `present` opens a new window at `BASE + '/present/' + collection + '/' + deck`. - Change the Scan form in `index.html` to take both inputs (collection name + path) and call `deck.scan { collection, path }`. Client-side validator mirrors the server regex. - Ensure filter / tab / deck-select state is reflected in the hash (per `/hero_ui_routes`). Dependencies: Step 3, Step 4. Can proceed in parallel with Step 6. #### Step 6: Update CLI and Rhai bindings Files: - `crates/hero_slides/src/main.rs` - `crates/hero_slides_rhai/src/deck_module.rs` - `crates/hero_slides_rhai/src/slide_module.rs` - `crates/hero_slides_rhai/scripts/*.rhai` - CLI: change `--scan <path>` to `--scan <collection>=<path>`; add `--collection <name>` and `--deck <name>` arguments consumed by `--generate`. Update `cmd_scan` / `cmd_generate` to call the new SDK methods with name parameters. - Rhai: rename `deck_scan(root) -> decks` to `deck_scan(collection, root) -> decks`; rename `deck_info(path)` / `deck_generate(path, force)` / etc. to take `(collection, deck)` strings. The bindings resolve names locally by calling `scan_collection` themselves (they have no RPC client), then delegate to the same `hero_slides_lib` functions. - Update the seven built-in `.rhai` scripts to accept `ARGS = [collection, deck, ...]`. - Update `README.md` usage examples accordingly. Dependencies: Step 3. Can proceed in parallel with Step 5 (non-overlapping file sets). #### Step 7: Docs, smoke tests, end-to-end validation Files: - `IMPLEMENTATION_SPEC.md` - `README.md` - `crates/hero_slides/README.md` (if present) - `testplan/` (update or add a file documenting the new route / RPC contract) - Refresh all route/RPC tables and diagrams to use the `collection/deck/slide` model. - Document the hero_db dependency: socket path, required `HERO_DB_SOCKET` env var override, "how to run hero_db locally" pointer. - Document the error-message format for duplicate decks verbatim. - Run `make fmt` / `make lint` / `make test` / `make build` to confirm everything compiles and existing tests still pass. Manual smoke: `make installdev && make run` then exercise `deck.scan` / `collection.list` / `deck.get` / `slide.generate` / the new `/api/collections/.../pdf` endpoint and `/present/:collection/:deck`. Walk the SPA URL-by-URL to confirm hash routes round-trip through browser reload per `/hero_ui_routes`. Dependencies: Steps 1-6. ### Acceptance Criteria - [ ] Scanning `/path/to/workspace` under a collection name writes `hero_slides:collections[<name>]` in hero_db and survives a server restart. - [ ] Re-scanning produces identical output; a modified tree is reflected on the next `collection.rescan`. - [ ] A directory tree containing two decks with the same directory name (inside the same collection or across registered collections) fails `deck.scan` and `collection.rescan` with: `duplicate deck name 'foo': <path_a> and <path_b>`. No partial registration is persisted. - [ ] `collection_name` / `deck_name` / `slide_name` that violate `^[a-z0-9_]+$` (upper-case, hyphen, whitespace, empty, leading/trailing underscore, double underscore, non-ASCII) are rejected at every entry point (RPC, CLI, Rhai) with a clear error naming the offending value. - [ ] `hero_slides_lib` internally still operates on `&Path`; only the server / UI / CLI / Rhai boundaries deal with names. - [ ] No JSON-RPC method accepts `path` / `deck_path` / `src_deck_path` / `dst_deck_path` any more — they are replaced by `collection`, `deck`, `slide`, `src_collection`, `dst_collection`, etc. `openrpc.json` and the regenerated SDK reflect this. - [ ] No URL in the UI — hash route, Axum route, template `href`, template `src`, or JS fetch URL — contains a filesystem path. - [ ] Every hash-route from the issue body has a canonical form; a browser MCP can deterministically walk `#collections` to `#collections/:c` to `#collections/:c/decks/:d` to `#collections/:c/decks/:d/slides/:s[/history]`. - [ ] `X-Forwarded-Prefix` still works: when the service is fronted under `/hero_slides/`, every generated link starts with that prefix and every image / PDF / RPC fetch resolves correctly. - [ ] `make fmt`, `make lint`, `make test`, `make build`, `make installdev`, `make run` all succeed from a clean workspace. - [ ] Existing unit tests in `hero_slides_lib` (`discovery.rs`, `parser.rs`, `hashing.rs`, `slide_ops.rs`, `deck.rs`) still pass; new unit tests added for `name.rs`, `collection.rs`, `registry.rs`. ### Notes - The hero_db dependency is new for this repo; verify the daemon is reachable at `~/hero/var/sockets/hero_db/rpc.sock` before launching. The server should emit a single, loud log line if the socket is missing and exit non-zero — silently degrading to in-memory state would break restart persistence and is explicitly not allowed by the issue. - `slide_name` is simply the slide file stem (e.g. `01_intro`). It may contain digits and underscores; it must still pass the validator. When existing decks on disk have slide files with hyphens, mixed case, or other characters, the scan rejects them at the `slide.list` boundary — this is deliberate and called out in Step 1 tests. - Step 5 and Step 6 can safely run in parallel; they touch disjoint file sets once Step 3 lands. - The `slide.copyTo` RPC crosses deck boundaries; its parameter rename is `src_collection` + `src_deck` + `slide` + `dst_collection` + `dst_deck` (five strings, no paths). - Backwards compatibility is not a goal. Issue #32 asks to "change the way" routes and locations are managed; hence RPCs and URLs are renamed outright rather than aliased. Migration note belongs in `IMPLEMENTATION_SPEC.md` Step 7. - Keep the reverse-proxy behaviour from `/hero_web_prefix` untouched: the middleware reads the header per request; templates use `{{ base_path }}`; JS reads `BASE` from the `<meta name="base-path">` tag. No `BASE_PATH` env var is introduced. - The routes and hash segments in this spec intentionally use `kebab-case`-free names: both URL segments and identifiers share the `snake_case` / `[a-z0-9_]+` alphabet because the identifiers themselves are already lowercase ASCII with underscores — no separate URL slug transform is needed. The overall URL *shape* uses plural-resource segments (`collections`, `decks`, `slides`) per `/hero_ui_routes`.
Member

Test Results

All tests passed.

Suite Passed Failed Ignored
hero_slides_server 90 0 0
hero_slides_lib 14 0 1
hero_slides_server (registry) 6 0 0
hero_slides_lib (doc) 2 0 0
hero_slides_rhai (doc) 1 0 0
Total 113 0 1

Run: cargo test --workspace

## Test Results All tests passed. | Suite | Passed | Failed | Ignored | |---|---|---|---| | hero_slides_server | 90 | 0 | 0 | | hero_slides_lib | 14 | 0 | 1 | | hero_slides_server (registry) | 6 | 0 | 0 | | hero_slides_lib (doc) | 2 | 0 | 0 | | hero_slides_rhai (doc) | 1 | 0 | 0 | | **Total** | **113** | **0** | **1** | Run: `cargo test --workspace`
Member

Implementation Summary

All changes for the collection-named routing feature are complete on branch development_collection_named_routing.

Changes Made

New files:

  • crates/hero_slides_lib/src/collection.rsCollectionRegistry backed by hero_db: register/unregister/rescan/list/get/resolve collection roots, backed by a Redis hash hero_slides:collections
  • crates/hero_slides_lib/src/name.rsCollectionName and DeckName newtype wrappers with validation (lowercase ASCII + underscores, 1–64 chars)
  • crates/hero_slides_server/src/registry.rs — server-side registry singleton (lazy-init CollectionRegistry)
  • crates/hero_slides_server/src/rpc_params.rs — shared RPC parameter structs: CollectionParam, DeckParam, SrcDst, etc.

Modified files:

  • crates/hero_slides_lib/src/lib.rs — exported collection and name modules; wired scan_decks to return DeckInfo with collection field
  • crates/hero_slides_server/openrpc.json — replaced all path-based methods with collection/deck/slide named params; added collection.* methods; updated DeckSummary schema
  • crates/hero_slides_server/openrpc.client.generated.rs — regenerated SDK reference
  • crates/hero_slides_server/src/rpc.rs — reimplemented all RPC handlers using CollectionRegistry for path resolution
  • crates/hero_slides_server/src/agent.rs — updated agent job to accept collection + deck instead of path
  • crates/hero_slides_server/src/generate_job.rs — updated generate job params
  • crates/hero_slides_server/src/main.rs — wired new Axum routes: /present/{collection}/{deck}, /api/collections/{collection}/decks/{deck}/slides/{slide}/image, /api/collections/{collection}/decks/{deck}/pdf
  • crates/hero_slides_ui/templates/base.html — added Collections tab nav item as default
  • crates/hero_slides_ui/templates/index.html — rewritten dashboard HTML for collections/decks views; scan form now takes collection name + path
  • crates/hero_slides_ui/templates/present.html — updated for named collection/deck routing
  • crates/hero_slides_ui/static/js/dashboard.js — full SPA router rewrite: hash routing changed from #slides/<encoded_path> to #collections/:col/decks/:deck/slides/:slide; all RPC calls updated to use named params
  • crates/hero_slides_ui/src/routes.rs — updated Axum routes for UI
  • crates/hero_slides_rhai/src/deck_module.rs — added deck_scan(collection, path) overload
  • crates/hero_slides/src/main.rs — CLI --scan COLLECTION=PATH and --generate COLLECTION/DECK flags
  • crates/hero_slides_ui/Cargo.toml, crates/hero_slides_server/Cargo.toml, crates/hero_slides_lib/Cargo.toml — updated dependency wiring

Test Results

113 tests passed, 0 failed. See previous comment for full breakdown.

## Implementation Summary All changes for the collection-named routing feature are complete on branch `development_collection_named_routing`. ### Changes Made **New files:** - `crates/hero_slides_lib/src/collection.rs` — `CollectionRegistry` backed by hero_db: register/unregister/rescan/list/get/resolve collection roots, backed by a Redis hash `hero_slides:collections` - `crates/hero_slides_lib/src/name.rs` — `CollectionName` and `DeckName` newtype wrappers with validation (lowercase ASCII + underscores, 1–64 chars) - `crates/hero_slides_server/src/registry.rs` — server-side registry singleton (lazy-init `CollectionRegistry`) - `crates/hero_slides_server/src/rpc_params.rs` — shared RPC parameter structs: `CollectionParam`, `DeckParam`, `SrcDst`, etc. **Modified files:** - `crates/hero_slides_lib/src/lib.rs` — exported `collection` and `name` modules; wired `scan_decks` to return `DeckInfo` with collection field - `crates/hero_slides_server/openrpc.json` — replaced all path-based methods with collection/deck/slide named params; added `collection.*` methods; updated `DeckSummary` schema - `crates/hero_slides_server/openrpc.client.generated.rs` — regenerated SDK reference - `crates/hero_slides_server/src/rpc.rs` — reimplemented all RPC handlers using `CollectionRegistry` for path resolution - `crates/hero_slides_server/src/agent.rs` — updated agent job to accept `collection` + `deck` instead of path - `crates/hero_slides_server/src/generate_job.rs` — updated generate job params - `crates/hero_slides_server/src/main.rs` — wired new Axum routes: `/present/{collection}/{deck}`, `/api/collections/{collection}/decks/{deck}/slides/{slide}/image`, `/api/collections/{collection}/decks/{deck}/pdf` - `crates/hero_slides_ui/templates/base.html` — added Collections tab nav item as default - `crates/hero_slides_ui/templates/index.html` — rewritten dashboard HTML for collections/decks views; scan form now takes collection name + path - `crates/hero_slides_ui/templates/present.html` — updated for named collection/deck routing - `crates/hero_slides_ui/static/js/dashboard.js` — full SPA router rewrite: hash routing changed from `#slides/<encoded_path>` to `#collections/:col/decks/:deck/slides/:slide`; all RPC calls updated to use named params - `crates/hero_slides_ui/src/routes.rs` — updated Axum routes for UI - `crates/hero_slides_rhai/src/deck_module.rs` — added `deck_scan(collection, path)` overload - `crates/hero_slides/src/main.rs` — CLI `--scan COLLECTION=PATH` and `--generate COLLECTION/DECK` flags - `crates/hero_slides_ui/Cargo.toml`, `crates/hero_slides_server/Cargo.toml`, `crates/hero_slides_lib/Cargo.toml` — updated dependency wiring ### Test Results 113 tests passed, 0 failed. See previous comment for full breakdown.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
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#32
No description provided.