Demo button in repo list posts to unregistered /api/demo → 404 #14

Closed
opened 2026-04-23 12:02:35 +00:00 by salmaelsoly · 3 comments
Member

Summary

The Demo button on the repo-list page posts to POST /api/demo, but no such route is registered in the UI binary. Every click returns 404 Not Found, so the "generate sample repos" flow is unreachable from the UI.

Reproduce

  1. Start hero_foundry (server + UI) via hero_proc.
  2. Open the UI, repo-list page.
  3. Click Demo.

Expected: sample repositories are created and the page reloads with them.
Actual: browser receives 404 Not Found, no repos created.

Confirmed from CLI:

$ curl -sS -X POST --unix-socket .../hero_foundry/ui.sock http://localhost/api/demo -i
HTTP/1.1 404 Not Found

Root cause

crates/hero_foundry_ui/templates/repo_list.html:5

<form method="POST" action="{{ bp }}/api/demo">

posts to a path that crates/hero_foundry_ui/src/main.rs (~line 104-130, the route table) never registers. The seeding logic itself does exist — as a standalone CLI example at crates/hero_foundry_examples/examples/seed_data.rs — it was simply never exposed as an HTTP handler.

Suggested fix

  1. Move/extract the seeding logic out of hero_foundry_examples into a crate the UI can depend on (e.g. hero_foundry_core or hero_foundry_sdk) — hero_foundry_examples shouldn't be a runtime dependency of the UI binary.
  2. Add handle_demo_seed in crates/hero_foundry_ui/src/ that invokes the extracted function against the configured repo root.
  3. Register the route in main.rs alongside the other /api/admin/* routes:
    .route("/api/demo", post(handle_demo_seed))
    
  4. Return 303 See Other with Location: {bp}/ so the browser reloads the repo list with the newly-seeded repos.
## Summary The **Demo** button on the repo-list page posts to `POST /api/demo`, but no such route is registered in the UI binary. Every click returns `404 Not Found`, so the "generate sample repos" flow is unreachable from the UI. ## Reproduce 1. Start `hero_foundry` (server + UI) via hero_proc. 2. Open the UI, repo-list page. 3. Click **Demo**. Expected: sample repositories are created and the page reloads with them. Actual: browser receives `404 Not Found`, no repos created. Confirmed from CLI: ``` $ curl -sS -X POST --unix-socket .../hero_foundry/ui.sock http://localhost/api/demo -i HTTP/1.1 404 Not Found ``` ## Root cause `crates/hero_foundry_ui/templates/repo_list.html:5` ```html <form method="POST" action="{{ bp }}/api/demo"> ``` posts to a path that `crates/hero_foundry_ui/src/main.rs` (~line 104-130, the route table) never registers. The seeding logic itself **does** exist — as a standalone CLI example at `crates/hero_foundry_examples/examples/seed_data.rs` — it was simply never exposed as an HTTP handler. ## Suggested fix 1. Move/extract the seeding logic out of `hero_foundry_examples` into a crate the UI can depend on (e.g. `hero_foundry_core` or `hero_foundry_sdk`) — `hero_foundry_examples` shouldn't be a runtime dependency of the UI binary. 2. Add `handle_demo_seed` in `crates/hero_foundry_ui/src/` that invokes the extracted function against the configured repo root. 3. Register the route in `main.rs` alongside the other `/api/admin/*` routes: ```rust .route("/api/demo", post(handle_demo_seed)) ``` 4. Return `303 See Other` with `Location: {bp}/` so the browser reloads the repo list with the newly-seeded repos.
Author
Member

Implementation Spec for Issue #14 (revised)

Revision note: the earlier draft placed seeding logic inside hero_foundry_core, which would mix application-level demo content into a layering-sensitive primitives crate. This version introduces a dedicated workspace crate instead.

Objective

The demo button in crates/hero_foundry_ui/templates/repo_list.html posts to {bp}/api/demo, which is not registered, so clicking it returns 404. Introduce a new application-level workspace crate hero_foundry_demo that owns the seeding code. The UI adds a POST /api/demo handler that calls into hero_foundry_demo::seed_demo_repos, registers each resulting .forge path via the existing FoundryClient on AppState, and redirects to {bp}/. The standalone CLI example seed_data is refactored to delegate to the same crate so there is a single source of truth. hero_foundry_core is not modified, and hero_foundry_ui never depends on hero_foundry_examples.

Requirements

  • New workspace crate hero_foundry_demo exposes a public seeding API that both the UI and the CLI example use.
  • hero_foundry_demo depends on hero_foundry_core; no reverse dep, no dep on hero_foundry_ui, hero_foundry_sdk, or hero_foundry_server.
  • hero_foundry_ui gains a hero_foundry_demo path dep and registers POST /api/demo.
  • The handler is idempotent: pre-existing .forge paths are skipped, so a second click never returns 500.
  • The handler returns 303 See Other with Location: {bp}/ (via axum's Redirect::to, matching the existing redirect pattern at main.rs:171).
  • Registration happens via state.client.register_repository so the UI's repo list actually reflects the newly seeded repos; per-repo failures are logged and swallowed so the overall request still returns 303.
  • hero_foundry_examples becomes a thin wrapper that calls hero_foundry_demo::seed_demo_repos.
  • hero_foundry_core, hero_foundry_server, hero_foundry_sdk, hero_foundry_web, hero_foundry_webdav_client are untouched.

Files to Modify/Create

  • Cargo.toml (workspace root) — add "crates/hero_foundry_demo" to [workspace] members.
  • crates/hero_foundry_demo/Cargo.toml (new)
  • crates/hero_foundry_demo/src/lib.rs (new)
  • crates/hero_foundry_ui/Cargo.toml (modify — add only hero_foundry_demo path dep; do not add hero_foundry_core)
  • crates/hero_foundry_ui/src/main.rs (modify — add handler + route)
  • crates/hero_foundry_examples/Cargo.toml (modify — add hero_foundry_demo path dep)
  • crates/hero_foundry_examples/examples/seed_data.rs (modify — call into new crate)

Implementation Plan

Step 1: Create the hero_foundry_demo crate

Files: crates/hero_foundry_demo/Cargo.toml (new), crates/hero_foundry_demo/src/lib.rs (new), Cargo.toml (workspace root, modify).

  • Add "crates/hero_foundry_demo" to the members list in the workspace root Cargo.toml, grouped after the existing core/server/sdk/ui entries.
  • crates/hero_foundry_demo/Cargo.toml:
    • [package] mirroring hero_foundry_core: name = "hero_foundry_demo", version = "0.2.2", edition = "2024", rust-version = "1.92.0", authors = ["HeroCode Team"], license = "Apache-2.0", description = "HeroFoundry demo-content seeder (shared by UI and CLI examples)", publish = false.
    • [dependencies]: only hero_foundry_core = { path = "../hero_foundry_core" }. No other deps — the seeding code uses only hero_foundry_core::Repository, std::path, and std::fs.
  • crates/hero_foundry_demo/src/lib.rs:
    • Public types:
      pub struct SeededRepo {
          pub name: String,
          pub path: std::path::PathBuf,
      }
      
    • Public entry point:
      pub fn seed_demo_repos(dir: &std::path::Path)
          -> Result<Vec<SeededRepo>, hero_foundry_core::HeroFoundryError>
      
    • Behaviour:
      • std::fs::create_dir_all(dir) — convert any io::Error into HeroFoundryError using whatever variant hero_foundry_core::error already exposes (no new core variants).
      • For each demo repo (hero-app, hero-docs), compute its .forge path; if path.exists(), skip and do not push to the result vec (idempotent). Otherwise call the private helper and push a SeededRepo { name, path }.
      • Return the vec of newly created repos (empty on second run).
    • Private helpers create_hero_app_repo(path) and create_docs_repo(path) with bodies copied verbatim from the current examples/seed_data.rs, with three adjustments:
      • Take the full target .forge path as input, not the parent dir.
      • Return Result<(), hero_foundry_core::HeroFoundryError> instead of Result<(), Box<dyn std::error::Error>>. The inner ? calls already return HeroFoundryError (they come from Repository::init, commit_builder().execute(), tags().create(...).execute(), branches().create(...).execute()).
      • Drop the println! progress messages — the library stays quiet; log-suitable output lives only in the example's main.

Dependencies: none.

Step 2: Wire hero_foundry_demo into hero_foundry_ui

Files: crates/hero_foundry_ui/Cargo.toml (modify), crates/hero_foundry_ui/src/main.rs (modify).

  • crates/hero_foundry_ui/Cargo.toml: add hero_foundry_demo = { path = "../hero_foundry_demo" } to [dependencies], directly under the existing hero_foundry_sdk line. Do NOT add hero_foundry_core — the UI must not grow a direct dep on core.
  • crates/hero_foundry_ui/src/main.rs:
    • Add use hero_foundry_demo::{seed_demo_repos, SeededRepo}; alongside the existing use hero_foundry_sdk::FoundryClient;.
    • Add a private helper near the existing socket_dir / socket_path helpers:
      fn foundry_repos_dir() -> std::path::PathBuf {
          if let Ok(dir) = std::env::var("HERO_FOUNDRY_REPOS") {
              return std::path::PathBuf::from(dir);
          }
          let home = std::env::var("HOME").unwrap_or_else(|_| "/root".into());
          std::path::PathBuf::from(home).join("hero/var/hero_foundry/repos")
      }
      
      Matches the resolution rule in examples/seed_data.rs so the UI and the CLI point at the same directory.
    • Add the handler, modelled on handle_admin_register at main.rs:410:
      async fn handle_demo_seed(
          axum::Extension(BasePath(bp)): axum::Extension<BasePath>,
          State(state): State<Arc<AppState>>,
      ) -> Response {
          let dir = foundry_repos_dir();
          match seed_demo_repos(&dir) {
              Ok(created) => {
                  for SeededRepo { name, path } in created {
                      let path_str = path.to_string_lossy().to_string();
                      if let Err(e) = state
                          .client
                          .register_repository(&path_str, Some(&name), true)
                          .await
                      {
                          eprintln!("[UI] demo: register_repository({}) failed: {}", name, e);
                      }
                  }
              }
              Err(e) => {
                  eprintln!("[UI] demo: seed_demo_repos failed: {}", e);
              }
          }
          Redirect::to(&format!("{}/", bp)).into_response()
      }
      
      Redirect is already imported at main.rs:14. Redirect::to on axum 0.8 issues HTTP 303.
    • Register the route in the router builder in run_server(), right after the existing admin JSON routes at main.rs:115–116:
      .route("/api/demo", post(handle_demo_seed))
      
      post is already imported at main.rs:16.

Dependencies: Step 1.

Step 3: Refactor the CLI example to reuse hero_foundry_demo

Files: crates/hero_foundry_examples/Cargo.toml (modify), crates/hero_foundry_examples/examples/seed_data.rs (modify).

  • crates/hero_foundry_examples/Cargo.toml: add hero_foundry_demo = { path = "../hero_foundry_demo" } to [dependencies]. The example uses the crate at runtime, matching how hero_foundry_core is already listed.
  • crates/hero_foundry_examples/examples/seed_data.rs:
    • Keep the repos_dir() helper and CLI arg handling.
    • Replace main with:
      fn main() -> Result<(), Box<dyn std::error::Error>> {
          let dir = repos_dir();
          let created = hero_foundry_demo::seed_demo_repos(&dir)?;
          if created.is_empty() {
              println!("Repos already exist in {}, skipping seed.", dir.display());
          } else {
              println!("Seeding demo repositories in {}", dir.display());
              for repo in &created {
                  println!("  + {} -> {}", repo.name, repo.path.display());
              }
              println!("\nSeed data complete.");
          }
          Ok(())
      }
      
    • Delete the now-unused use hero_foundry_core::Repository; line, create_hero_app_repo, and create_docs_repo. The example shrinks from ~207 lines to under 30.

Dependencies: Step 1.

Step 4: Verify (runs in Phase 5)

Files: none.

  • cargo build -p hero_foundry_demo
  • cargo build -p hero_foundry_ui
  • cargo build --example seed_data -p hero_foundry_examples
  • cargo test --workspace
  • cargo tree -p hero_foundry_ui | grep hero_foundry_examples → must print nothing.
  • cargo tree -p hero_foundry_demo | grep -E 'hero_foundry_ui|hero_foundry_sdk|hero_foundry_server' → must print nothing (layering check).
  • cargo tree -p hero_foundry_ui | grep hero_foundry_core → no direct dep; only transitive via hero_foundry_demo is acceptable.
  • Manual smoke: start server + UI, navigate to the repo list with an empty repos dir, click Demo, confirm 303 to {bp}/ and that hero-app + hero-docs appear. Click Demo again and confirm no 500.

Dependencies: Steps 1–3.

Acceptance Criteria

  • POST /api/demo returns 303 See Other with Location: {bp}/.
  • Clicking Demo on an empty repo list surfaces at least two repos (hero-app, hero-docs).
  • A second Demo click is idempotent — existing .forge paths skipped, handler still returns 303.
  • cargo build -p hero_foundry_ui succeeds.
  • cargo test --workspace passes.
  • hero_foundry_examples is not a (direct or transitive) dependency of hero_foundry_ui.
  • hero_foundry_ui, hero_foundry_sdk, hero_foundry_server are not dependencies of hero_foundry_demo.
  • hero_foundry_core source files are unchanged; only the workspace root Cargo.toml and files under crates/hero_foundry_demo/, crates/hero_foundry_ui/, and crates/hero_foundry_examples/ are modified or created.

Notes

  • Why a new crate, not hero_foundry_core. Core is layering-sensitive and scoped to repository primitives. Demo content is a curated set of fake repos with fake commit messages and fake authors — application-level content, not a primitive.
  • Why not just inline the seeder inside hero_foundry_ui. The CLI example at crates/hero_foundry_examples/examples/seed_data.rs is a legitimate second consumer — it seeds demo data without standing up the UI (useful for dev, tests, scripting). Inlining would force either duplication or deletion of the CLI; a dedicated crate preserves both paths from one implementation.
  • Handler pattern mirrors handle_admin_register at main.rs:410–420. Only difference: drops the Form extractor and returns Redirect::to(&format!("{}/", bp)) instead of re-rendering.
  • Registration via SDK is required. Server has no auto-discovery — list_repositories() returns server-side state populated via register_repository. Without the calls the UI shows a 303 but an empty list on the next page load.
  • Error type from core. hero_foundry_core::HeroFoundryError and hero_foundry_core::Result are already re-exported at the crate root, so the new crate uses them with no pub use change in core.
  • Risks. (a) New workspace member triggers a cold build; cost trivial. (b) register_repository can fail under access_control.allowed_dirs; handler logs and continues per-repo so the request still returns 303. (c) Redirect::to → 303 is already relied on at main.rs:171, so the status code contract is identical to existing production code.
  • Scope. Only the workspace root Cargo.toml, two files under hero_foundry_ui, two files under hero_foundry_examples, and the two new files under hero_foundry_demo are modified or created.
## Implementation Spec for Issue #14 (revised) _Revision note: the earlier draft placed seeding logic inside `hero_foundry_core`, which would mix application-level demo content into a layering-sensitive primitives crate. This version introduces a dedicated workspace crate instead._ ### Objective The demo button in `crates/hero_foundry_ui/templates/repo_list.html` posts to `{bp}/api/demo`, which is not registered, so clicking it returns 404. Introduce a new application-level workspace crate `hero_foundry_demo` that owns the seeding code. The UI adds a `POST /api/demo` handler that calls into `hero_foundry_demo::seed_demo_repos`, registers each resulting `.forge` path via the existing `FoundryClient` on `AppState`, and redirects to `{bp}/`. The standalone CLI example `seed_data` is refactored to delegate to the same crate so there is a single source of truth. `hero_foundry_core` is not modified, and `hero_foundry_ui` never depends on `hero_foundry_examples`. ### Requirements - New workspace crate `hero_foundry_demo` exposes a public seeding API that both the UI and the CLI example use. - `hero_foundry_demo` depends on `hero_foundry_core`; no reverse dep, no dep on `hero_foundry_ui`, `hero_foundry_sdk`, or `hero_foundry_server`. - `hero_foundry_ui` gains a `hero_foundry_demo` path dep and registers `POST /api/demo`. - The handler is idempotent: pre-existing `.forge` paths are skipped, so a second click never returns 500. - The handler returns `303 See Other` with `Location: {bp}/` (via axum's `Redirect::to`, matching the existing redirect pattern at `main.rs:171`). - Registration happens via `state.client.register_repository` so the UI's repo list actually reflects the newly seeded repos; per-repo failures are logged and swallowed so the overall request still returns 303. - `hero_foundry_examples` becomes a thin wrapper that calls `hero_foundry_demo::seed_demo_repos`. - `hero_foundry_core`, `hero_foundry_server`, `hero_foundry_sdk`, `hero_foundry_web`, `hero_foundry_webdav_client` are untouched. ### Files to Modify/Create - `Cargo.toml` (workspace root) — add `"crates/hero_foundry_demo"` to `[workspace] members`. - `crates/hero_foundry_demo/Cargo.toml` (new) - `crates/hero_foundry_demo/src/lib.rs` (new) - `crates/hero_foundry_ui/Cargo.toml` (modify — add only `hero_foundry_demo` path dep; do not add `hero_foundry_core`) - `crates/hero_foundry_ui/src/main.rs` (modify — add handler + route) - `crates/hero_foundry_examples/Cargo.toml` (modify — add `hero_foundry_demo` path dep) - `crates/hero_foundry_examples/examples/seed_data.rs` (modify — call into new crate) ### Implementation Plan #### Step 1: Create the `hero_foundry_demo` crate Files: `crates/hero_foundry_demo/Cargo.toml` (new), `crates/hero_foundry_demo/src/lib.rs` (new), `Cargo.toml` (workspace root, modify). - Add `"crates/hero_foundry_demo"` to the `members` list in the workspace root `Cargo.toml`, grouped after the existing core/server/sdk/ui entries. - `crates/hero_foundry_demo/Cargo.toml`: - `[package]` mirroring `hero_foundry_core`: `name = "hero_foundry_demo"`, `version = "0.2.2"`, `edition = "2024"`, `rust-version = "1.92.0"`, `authors = ["HeroCode Team"]`, `license = "Apache-2.0"`, `description = "HeroFoundry demo-content seeder (shared by UI and CLI examples)"`, `publish = false`. - `[dependencies]`: only `hero_foundry_core = { path = "../hero_foundry_core" }`. No other deps — the seeding code uses only `hero_foundry_core::Repository`, `std::path`, and `std::fs`. - `crates/hero_foundry_demo/src/lib.rs`: - Public types: ```rust pub struct SeededRepo { pub name: String, pub path: std::path::PathBuf, } ``` - Public entry point: ```rust pub fn seed_demo_repos(dir: &std::path::Path) -> Result<Vec<SeededRepo>, hero_foundry_core::HeroFoundryError> ``` - Behaviour: - `std::fs::create_dir_all(dir)` — convert any `io::Error` into `HeroFoundryError` using whatever variant `hero_foundry_core::error` already exposes (no new core variants). - For each demo repo (`hero-app`, `hero-docs`), compute its `.forge` path; if `path.exists()`, skip and do not push to the result vec (idempotent). Otherwise call the private helper and push a `SeededRepo { name, path }`. - Return the vec of newly created repos (empty on second run). - Private helpers `create_hero_app_repo(path)` and `create_docs_repo(path)` with bodies copied verbatim from the current `examples/seed_data.rs`, with three adjustments: - Take the full target `.forge` path as input, not the parent dir. - Return `Result<(), hero_foundry_core::HeroFoundryError>` instead of `Result<(), Box<dyn std::error::Error>>`. The inner `?` calls already return `HeroFoundryError` (they come from `Repository::init`, `commit_builder().execute()`, `tags().create(...).execute()`, `branches().create(...).execute()`). - Drop the `println!` progress messages — the library stays quiet; log-suitable output lives only in the example's `main`. Dependencies: none. #### Step 2: Wire `hero_foundry_demo` into `hero_foundry_ui` Files: `crates/hero_foundry_ui/Cargo.toml` (modify), `crates/hero_foundry_ui/src/main.rs` (modify). - `crates/hero_foundry_ui/Cargo.toml`: add `hero_foundry_demo = { path = "../hero_foundry_demo" }` to `[dependencies]`, directly under the existing `hero_foundry_sdk` line. Do NOT add `hero_foundry_core` — the UI must not grow a direct dep on core. - `crates/hero_foundry_ui/src/main.rs`: - Add `use hero_foundry_demo::{seed_demo_repos, SeededRepo};` alongside the existing `use hero_foundry_sdk::FoundryClient;`. - Add a private helper near the existing `socket_dir` / `socket_path` helpers: ```rust fn foundry_repos_dir() -> std::path::PathBuf { if let Ok(dir) = std::env::var("HERO_FOUNDRY_REPOS") { return std::path::PathBuf::from(dir); } let home = std::env::var("HOME").unwrap_or_else(|_| "/root".into()); std::path::PathBuf::from(home).join("hero/var/hero_foundry/repos") } ``` Matches the resolution rule in `examples/seed_data.rs` so the UI and the CLI point at the same directory. - Add the handler, modelled on `handle_admin_register` at `main.rs:410`: ```rust async fn handle_demo_seed( axum::Extension(BasePath(bp)): axum::Extension<BasePath>, State(state): State<Arc<AppState>>, ) -> Response { let dir = foundry_repos_dir(); match seed_demo_repos(&dir) { Ok(created) => { for SeededRepo { name, path } in created { let path_str = path.to_string_lossy().to_string(); if let Err(e) = state .client .register_repository(&path_str, Some(&name), true) .await { eprintln!("[UI] demo: register_repository({}) failed: {}", name, e); } } } Err(e) => { eprintln!("[UI] demo: seed_demo_repos failed: {}", e); } } Redirect::to(&format!("{}/", bp)).into_response() } ``` `Redirect` is already imported at `main.rs:14`. `Redirect::to` on axum 0.8 issues HTTP 303. - Register the route in the router builder in `run_server()`, right after the existing admin JSON routes at `main.rs:115–116`: ```rust .route("/api/demo", post(handle_demo_seed)) ``` `post` is already imported at `main.rs:16`. Dependencies: Step 1. #### Step 3: Refactor the CLI example to reuse `hero_foundry_demo` Files: `crates/hero_foundry_examples/Cargo.toml` (modify), `crates/hero_foundry_examples/examples/seed_data.rs` (modify). - `crates/hero_foundry_examples/Cargo.toml`: add `hero_foundry_demo = { path = "../hero_foundry_demo" }` to `[dependencies]`. The example uses the crate at runtime, matching how `hero_foundry_core` is already listed. - `crates/hero_foundry_examples/examples/seed_data.rs`: - Keep the `repos_dir()` helper and CLI arg handling. - Replace `main` with: ```rust fn main() -> Result<(), Box<dyn std::error::Error>> { let dir = repos_dir(); let created = hero_foundry_demo::seed_demo_repos(&dir)?; if created.is_empty() { println!("Repos already exist in {}, skipping seed.", dir.display()); } else { println!("Seeding demo repositories in {}", dir.display()); for repo in &created { println!(" + {} -> {}", repo.name, repo.path.display()); } println!("\nSeed data complete."); } Ok(()) } ``` - Delete the now-unused `use hero_foundry_core::Repository;` line, `create_hero_app_repo`, and `create_docs_repo`. The example shrinks from ~207 lines to under 30. Dependencies: Step 1. #### Step 4: Verify (runs in Phase 5) Files: none. - `cargo build -p hero_foundry_demo` - `cargo build -p hero_foundry_ui` - `cargo build --example seed_data -p hero_foundry_examples` - `cargo test --workspace` - `cargo tree -p hero_foundry_ui | grep hero_foundry_examples` → must print nothing. - `cargo tree -p hero_foundry_demo | grep -E 'hero_foundry_ui|hero_foundry_sdk|hero_foundry_server'` → must print nothing (layering check). - `cargo tree -p hero_foundry_ui | grep hero_foundry_core` → no direct dep; only transitive via `hero_foundry_demo` is acceptable. - Manual smoke: start server + UI, navigate to the repo list with an empty repos dir, click Demo, confirm 303 to `{bp}/` and that `hero-app` + `hero-docs` appear. Click Demo again and confirm no 500. Dependencies: Steps 1–3. ### Acceptance Criteria - [ ] `POST /api/demo` returns `303 See Other` with `Location: {bp}/`. - [ ] Clicking **Demo** on an empty repo list surfaces at least two repos (`hero-app`, `hero-docs`). - [ ] A second **Demo** click is idempotent — existing `.forge` paths skipped, handler still returns 303. - [ ] `cargo build -p hero_foundry_ui` succeeds. - [ ] `cargo test --workspace` passes. - [ ] `hero_foundry_examples` is not a (direct or transitive) dependency of `hero_foundry_ui`. - [ ] `hero_foundry_ui`, `hero_foundry_sdk`, `hero_foundry_server` are not dependencies of `hero_foundry_demo`. - [ ] `hero_foundry_core` source files are unchanged; only the workspace root `Cargo.toml` and files under `crates/hero_foundry_demo/`, `crates/hero_foundry_ui/`, and `crates/hero_foundry_examples/` are modified or created. ### Notes - **Why a new crate, not `hero_foundry_core`.** Core is layering-sensitive and scoped to repository primitives. Demo content is a curated set of fake repos with fake commit messages and fake authors — application-level content, not a primitive. - **Why not just inline the seeder inside `hero_foundry_ui`.** The CLI example at `crates/hero_foundry_examples/examples/seed_data.rs` is a legitimate second consumer — it seeds demo data without standing up the UI (useful for dev, tests, scripting). Inlining would force either duplication or deletion of the CLI; a dedicated crate preserves both paths from one implementation. - **Handler pattern** mirrors `handle_admin_register` at `main.rs:410–420`. Only difference: drops the `Form` extractor and returns `Redirect::to(&format!("{}/", bp))` instead of re-rendering. - **Registration via SDK is required.** Server has no auto-discovery — `list_repositories()` returns server-side state populated via `register_repository`. Without the calls the UI shows a 303 but an empty list on the next page load. - **Error type from core.** `hero_foundry_core::HeroFoundryError` and `hero_foundry_core::Result` are already re-exported at the crate root, so the new crate uses them with no `pub use` change in core. - **Risks.** (a) New workspace member triggers a cold build; cost trivial. (b) `register_repository` can fail under `access_control.allowed_dirs`; handler logs and continues per-repo so the request still returns 303. (c) `Redirect::to` → 303 is already relied on at `main.rs:171`, so the status code contract is identical to existing production code. - **Scope.** Only the workspace root `Cargo.toml`, two files under `hero_foundry_ui`, two files under `hero_foundry_examples`, and the two new files under `hero_foundry_demo` are modified or created.
Author
Member

Test Results

Re-run after fixing a non-ASCII em-dash in b"..." byte-string literals inside the new hero_foundry_demo/src/lib.rs (replaced with ASCII hyphens).

Builds

  • cargo build -p hero_foundry_demo: pass
  • cargo build -p hero_foundry_ui: pass
  • cargo build --example seed_data -p hero_foundry_examples: pass

Workspace tests

  • Total: 178
  • Passed: 178
  • Failed: 0

Layering checks

  • hero_foundry_examples is not a dep of hero_foundry_ui: pass
  • hero_foundry_demo has no dep on hero_foundry_ui / hero_foundry_sdk / hero_foundry_server: pass
  • hero_foundry_ui has no direct dep on hero_foundry_core (transitive via hero_foundry_demo is allowed): pass

Verdict

All green

## Test Results _Re-run after fixing a non-ASCII em-dash in `b"..."` byte-string literals inside the new `hero_foundry_demo/src/lib.rs` (replaced with ASCII hyphens)._ ### Builds - `cargo build -p hero_foundry_demo`: pass - `cargo build -p hero_foundry_ui`: pass - `cargo build --example seed_data -p hero_foundry_examples`: pass ### Workspace tests - Total: 178 - Passed: 178 - Failed: 0 ### Layering checks - `hero_foundry_examples` is not a dep of `hero_foundry_ui`: pass - `hero_foundry_demo` has no dep on `hero_foundry_ui` / `hero_foundry_sdk` / `hero_foundry_server`: pass - `hero_foundry_ui` has no direct dep on `hero_foundry_core` (transitive via `hero_foundry_demo` is allowed): pass ### Verdict All green
Author
Member

Implementation Summary

What was broken

templates/repo_list.html posted to {bp}/api/demo, but no route was registered in hero_foundry_ui/src/main.rs, so clicking Demo returned 404.

Fix (shape)

Introduced a new workspace crate hero_foundry_demo that owns the demo-seeding logic. Both the UI handler and the existing CLI example seed_data now delegate to the same function, so they cannot drift. hero_foundry_core was not modified; hero_foundry_ui has no direct dependency on hero_foundry_core (only transitive via hero_foundry_demo).

Files changed

  • Cargo.toml — added crates/hero_foundry_demo to workspace members.
  • crates/hero_foundry_demo/Cargo.toml — new, depends only on hero_foundry_core.
  • crates/hero_foundry_demo/src/lib.rs — new. Public: seed_demo_repos(dir) -> Result<Vec<SeededRepo>, HeroFoundryError> and struct SeededRepo { name, path }. Idempotent (skips existing .forge paths).
  • crates/hero_foundry_ui/Cargo.toml — added hero_foundry_demo path dep.
  • crates/hero_foundry_ui/src/main.rs — added foundry_repos_dir(), handle_demo_seed (seeds, registers each new repo via FoundryClient::register_repository, returns 303 to {bp}/), and .route("/api/demo", post(handle_demo_seed)).
  • crates/hero_foundry_examples/Cargo.toml — added hero_foundry_demo path dep.
  • crates/hero_foundry_examples/examples/seed_data.rs — 207 → 37 lines; now delegates to hero_foundry_demo::seed_demo_repos.

Test results

  • cargo build -p hero_foundry_demo: pass
  • cargo build -p hero_foundry_ui: pass
  • cargo build --example seed_data -p hero_foundry_examples: pass
  • cargo test --workspace --no-fail-fast: 178 passed, 0 failed, 2 ignored (unchanged from pre-existing state).
  • Layering checks: hero_foundry_examples not a dep of hero_foundry_ui; hero_foundry_demo has no dep on hero_foundry_ui/sdk/server; hero_foundry_ui has no direct dep on hero_foundry_core.

Notes

  • During testing, 9 em-dashes () embedded inside b"..." byte-string literals in the new crate caused compile errors (Rust byte strings only accept ASCII). Replaced with ASCII hyphens; no behavioural change since those bytes are inline markdown fixture content.
  • register_repository errors are logged via eprintln! and swallowed, so a second click or an already-registered name still returns 303.
  • No push and no PR — changes stay on development locally pending merge-workflow decision.
## Implementation Summary ### What was broken `templates/repo_list.html` posted to `{bp}/api/demo`, but no route was registered in `hero_foundry_ui/src/main.rs`, so clicking **Demo** returned 404. ### Fix (shape) Introduced a new workspace crate `hero_foundry_demo` that owns the demo-seeding logic. Both the UI handler and the existing CLI example `seed_data` now delegate to the same function, so they cannot drift. `hero_foundry_core` was not modified; `hero_foundry_ui` has no direct dependency on `hero_foundry_core` (only transitive via `hero_foundry_demo`). ### Files changed - `Cargo.toml` — added `crates/hero_foundry_demo` to workspace members. - `crates/hero_foundry_demo/Cargo.toml` — new, depends only on `hero_foundry_core`. - `crates/hero_foundry_demo/src/lib.rs` — new. Public: `seed_demo_repos(dir) -> Result<Vec<SeededRepo>, HeroFoundryError>` and `struct SeededRepo { name, path }`. Idempotent (skips existing `.forge` paths). - `crates/hero_foundry_ui/Cargo.toml` — added `hero_foundry_demo` path dep. - `crates/hero_foundry_ui/src/main.rs` — added `foundry_repos_dir()`, `handle_demo_seed` (seeds, registers each new repo via `FoundryClient::register_repository`, returns `303` to `{bp}/`), and `.route("/api/demo", post(handle_demo_seed))`. - `crates/hero_foundry_examples/Cargo.toml` — added `hero_foundry_demo` path dep. - `crates/hero_foundry_examples/examples/seed_data.rs` — 207 → 37 lines; now delegates to `hero_foundry_demo::seed_demo_repos`. ### Test results - `cargo build -p hero_foundry_demo`: pass - `cargo build -p hero_foundry_ui`: pass - `cargo build --example seed_data -p hero_foundry_examples`: pass - `cargo test --workspace --no-fail-fast`: 178 passed, 0 failed, 2 ignored (unchanged from pre-existing state). - Layering checks: `hero_foundry_examples` not a dep of `hero_foundry_ui`; `hero_foundry_demo` has no dep on `hero_foundry_ui`/`sdk`/`server`; `hero_foundry_ui` has no direct dep on `hero_foundry_core`. ### Notes - During testing, 9 em-dashes (`—`) embedded inside `b"..."` byte-string literals in the new crate caused compile errors (Rust byte strings only accept ASCII). Replaced with ASCII hyphens; no behavioural change since those bytes are inline markdown fixture content. - `register_repository` errors are logged via `eprintln!` and swallowed, so a second click or an already-registered name still returns `303`. - No push and no PR — changes stay on `development` locally pending merge-workflow decision.
Sign in to join this conversation.
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_foundry#14
No description provided.