Demo button in repo list posts to unregistered /api/demo → 404 #14
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_foundry#14
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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 returns404 Not Found, so the "generate sample repos" flow is unreachable from the UI.Reproduce
hero_foundry(server + UI) via hero_proc.Expected: sample repositories are created and the page reloads with them.
Actual: browser receives
404 Not Found, no repos created.Confirmed from CLI:
Root cause
crates/hero_foundry_ui/templates/repo_list.html:5posts 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 atcrates/hero_foundry_examples/examples/seed_data.rs— it was simply never exposed as an HTTP handler.Suggested fix
hero_foundry_examplesinto a crate the UI can depend on (e.g.hero_foundry_coreorhero_foundry_sdk) —hero_foundry_examplesshouldn't be a runtime dependency of the UI binary.handle_demo_seedincrates/hero_foundry_ui/src/that invokes the extracted function against the configured repo root.main.rsalongside the other/api/admin/*routes:303 See OtherwithLocation: {bp}/so the browser reloads the repo list with the newly-seeded repos.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.htmlposts to{bp}/api/demo, which is not registered, so clicking it returns 404. Introduce a new application-level workspace cratehero_foundry_demothat owns the seeding code. The UI adds aPOST /api/demohandler that calls intohero_foundry_demo::seed_demo_repos, registers each resulting.forgepath via the existingFoundryClientonAppState, and redirects to{bp}/. The standalone CLI exampleseed_datais refactored to delegate to the same crate so there is a single source of truth.hero_foundry_coreis not modified, andhero_foundry_uinever depends onhero_foundry_examples.Requirements
hero_foundry_demoexposes a public seeding API that both the UI and the CLI example use.hero_foundry_demodepends onhero_foundry_core; no reverse dep, no dep onhero_foundry_ui,hero_foundry_sdk, orhero_foundry_server.hero_foundry_uigains ahero_foundry_demopath dep and registersPOST /api/demo..forgepaths are skipped, so a second click never returns 500.303 See OtherwithLocation: {bp}/(via axum'sRedirect::to, matching the existing redirect pattern atmain.rs:171).state.client.register_repositoryso 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_examplesbecomes a thin wrapper that callshero_foundry_demo::seed_demo_repos.hero_foundry_core,hero_foundry_server,hero_foundry_sdk,hero_foundry_web,hero_foundry_webdav_clientare 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 onlyhero_foundry_demopath dep; do not addhero_foundry_core)crates/hero_foundry_ui/src/main.rs(modify — add handler + route)crates/hero_foundry_examples/Cargo.toml(modify — addhero_foundry_demopath dep)crates/hero_foundry_examples/examples/seed_data.rs(modify — call into new crate)Implementation Plan
Step 1: Create the
hero_foundry_democrateFiles:
crates/hero_foundry_demo/Cargo.toml(new),crates/hero_foundry_demo/src/lib.rs(new),Cargo.toml(workspace root, modify)."crates/hero_foundry_demo"to thememberslist in the workspace rootCargo.toml, grouped after the existing core/server/sdk/ui entries.crates/hero_foundry_demo/Cargo.toml:[package]mirroringhero_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]: onlyhero_foundry_core = { path = "../hero_foundry_core" }. No other deps — the seeding code uses onlyhero_foundry_core::Repository,std::path, andstd::fs.crates/hero_foundry_demo/src/lib.rs:std::fs::create_dir_all(dir)— convert anyio::ErrorintoHeroFoundryErrorusing whatever varianthero_foundry_core::erroralready exposes (no new core variants).hero-app,hero-docs), compute its.forgepath; ifpath.exists(), skip and do not push to the result vec (idempotent). Otherwise call the private helper and push aSeededRepo { name, path }.create_hero_app_repo(path)andcreate_docs_repo(path)with bodies copied verbatim from the currentexamples/seed_data.rs, with three adjustments:.forgepath as input, not the parent dir.Result<(), hero_foundry_core::HeroFoundryError>instead ofResult<(), Box<dyn std::error::Error>>. The inner?calls already returnHeroFoundryError(they come fromRepository::init,commit_builder().execute(),tags().create(...).execute(),branches().create(...).execute()).println!progress messages — the library stays quiet; log-suitable output lives only in the example'smain.Dependencies: none.
Step 2: Wire
hero_foundry_demointohero_foundry_uiFiles:
crates/hero_foundry_ui/Cargo.toml(modify),crates/hero_foundry_ui/src/main.rs(modify).crates/hero_foundry_ui/Cargo.toml: addhero_foundry_demo = { path = "../hero_foundry_demo" }to[dependencies], directly under the existinghero_foundry_sdkline. Do NOT addhero_foundry_core— the UI must not grow a direct dep on core.crates/hero_foundry_ui/src/main.rs:use hero_foundry_demo::{seed_demo_repos, SeededRepo};alongside the existinguse hero_foundry_sdk::FoundryClient;.socket_dir/socket_pathhelpers: Matches the resolution rule inexamples/seed_data.rsso the UI and the CLI point at the same directory.handle_admin_registeratmain.rs:410:Redirectis already imported atmain.rs:14.Redirect::toon axum 0.8 issues HTTP 303.run_server(), right after the existing admin JSON routes atmain.rs:115–116:postis already imported atmain.rs:16.Dependencies: Step 1.
Step 3: Refactor the CLI example to reuse
hero_foundry_demoFiles:
crates/hero_foundry_examples/Cargo.toml(modify),crates/hero_foundry_examples/examples/seed_data.rs(modify).crates/hero_foundry_examples/Cargo.toml: addhero_foundry_demo = { path = "../hero_foundry_demo" }to[dependencies]. The example uses the crate at runtime, matching howhero_foundry_coreis already listed.crates/hero_foundry_examples/examples/seed_data.rs:repos_dir()helper and CLI arg handling.mainwith:use hero_foundry_core::Repository;line,create_hero_app_repo, andcreate_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_democargo build -p hero_foundry_uicargo build --example seed_data -p hero_foundry_examplescargo test --workspacecargo 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 viahero_foundry_demois acceptable.{bp}/and thathero-app+hero-docsappear. Click Demo again and confirm no 500.Dependencies: Steps 1–3.
Acceptance Criteria
POST /api/demoreturns303 See OtherwithLocation: {bp}/.hero-app,hero-docs)..forgepaths skipped, handler still returns 303.cargo build -p hero_foundry_uisucceeds.cargo test --workspacepasses.hero_foundry_examplesis not a (direct or transitive) dependency ofhero_foundry_ui.hero_foundry_ui,hero_foundry_sdk,hero_foundry_serverare not dependencies ofhero_foundry_demo.hero_foundry_coresource files are unchanged; only the workspace rootCargo.tomland files undercrates/hero_foundry_demo/,crates/hero_foundry_ui/, andcrates/hero_foundry_examples/are modified or created.Notes
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.hero_foundry_ui. The CLI example atcrates/hero_foundry_examples/examples/seed_data.rsis 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.handle_admin_registeratmain.rs:410–420. Only difference: drops theFormextractor and returnsRedirect::to(&format!("{}/", bp))instead of re-rendering.list_repositories()returns server-side state populated viaregister_repository. Without the calls the UI shows a 303 but an empty list on the next page load.hero_foundry_core::HeroFoundryErrorandhero_foundry_core::Resultare already re-exported at the crate root, so the new crate uses them with nopub usechange in core.register_repositorycan fail underaccess_control.allowed_dirs; handler logs and continues per-repo so the request still returns 303. (c)Redirect::to→ 303 is already relied on atmain.rs:171, so the status code contract is identical to existing production code.Cargo.toml, two files underhero_foundry_ui, two files underhero_foundry_examples, and the two new files underhero_foundry_demoare modified or created.Test Results
Re-run after fixing a non-ASCII em-dash in
b"..."byte-string literals inside the newhero_foundry_demo/src/lib.rs(replaced with ASCII hyphens).Builds
cargo build -p hero_foundry_demo: passcargo build -p hero_foundry_ui: passcargo build --example seed_data -p hero_foundry_examples: passWorkspace tests
Layering checks
hero_foundry_examplesis not a dep ofhero_foundry_ui: passhero_foundry_demohas no dep onhero_foundry_ui/hero_foundry_sdk/hero_foundry_server: passhero_foundry_uihas no direct dep onhero_foundry_core(transitive viahero_foundry_demois allowed): passVerdict
All green
Implementation Summary
What was broken
templates/repo_list.htmlposted to{bp}/api/demo, but no route was registered inhero_foundry_ui/src/main.rs, so clicking Demo returned 404.Fix (shape)
Introduced a new workspace crate
hero_foundry_demothat owns the demo-seeding logic. Both the UI handler and the existing CLI exampleseed_datanow delegate to the same function, so they cannot drift.hero_foundry_corewas not modified;hero_foundry_uihas no direct dependency onhero_foundry_core(only transitive viahero_foundry_demo).Files changed
Cargo.toml— addedcrates/hero_foundry_demoto workspace members.crates/hero_foundry_demo/Cargo.toml— new, depends only onhero_foundry_core.crates/hero_foundry_demo/src/lib.rs— new. Public:seed_demo_repos(dir) -> Result<Vec<SeededRepo>, HeroFoundryError>andstruct SeededRepo { name, path }. Idempotent (skips existing.forgepaths).crates/hero_foundry_ui/Cargo.toml— addedhero_foundry_demopath dep.crates/hero_foundry_ui/src/main.rs— addedfoundry_repos_dir(),handle_demo_seed(seeds, registers each new repo viaFoundryClient::register_repository, returns303to{bp}/), and.route("/api/demo", post(handle_demo_seed)).crates/hero_foundry_examples/Cargo.toml— addedhero_foundry_demopath dep.crates/hero_foundry_examples/examples/seed_data.rs— 207 → 37 lines; now delegates tohero_foundry_demo::seed_demo_repos.Test results
cargo build -p hero_foundry_demo: passcargo build -p hero_foundry_ui: passcargo build --example seed_data -p hero_foundry_examples: passcargo test --workspace --no-fail-fast: 178 passed, 0 failed, 2 ignored (unchanged from pre-existing state).hero_foundry_examplesnot a dep ofhero_foundry_ui;hero_foundry_demohas no dep onhero_foundry_ui/sdk/server;hero_foundry_uihas no direct dep onhero_foundry_core.Notes
—) embedded insideb"..."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_repositoryerrors are logged viaeprintln!and swallowed, so a second click or an already-registered name still returns303.developmentlocally pending merge-workflow decision.