fix: route project roots (/<slug>/) to a landing page
The home index and every page breadcrumb link a project root
`<a href="/<slug>/">`, but the router declared only `/`, `/assets/theme.css`,
and the `/{slug}/{*page}` catch-all (which requires a concrete page segment),
so every project-root link 404'd. Add a `/{slug}/` route and a project_index
handler that renders a landing page listing the project's sections and pages
(unknown slug -> 404); the index and breadcrumb root links are now live.
RED-first via the debug skill: tests/e2e.rs
`project_root_resolves_to_landing_with_section_nav` (a GET to /aura/ asserting
200 + the section/page nav) failed 404 before the fix, passes after.
cargo test green (42); clippy clean.
This commit is contained in:
@@ -27,6 +27,7 @@ pub fn router(state: AppState) -> Router {
|
||||
Router::new()
|
||||
.route("/", get(index))
|
||||
.route("/assets/theme.css", get(theme_css))
|
||||
.route("/{slug}/", get(project_index))
|
||||
.route("/{slug}/{*page}", get(page))
|
||||
.with_state(state)
|
||||
}
|
||||
@@ -46,6 +47,28 @@ async fn index(State(st): State<AppState>) -> Html<String> {
|
||||
Html(s)
|
||||
}
|
||||
|
||||
/// A project root (`/<slug>/`): a landing page listing the project's sections
|
||||
/// and their pages, so the home index and the per-page breadcrumb both have a
|
||||
/// live destination. Unknown slug -> 404.
|
||||
async fn project_index(State(st): State<AppState>, Path(slug): Path<String>) -> Response {
|
||||
let Some(proj) = st.reg.project(&slug) else {
|
||||
return (StatusCode::NOT_FOUND, "404 — unknown project").into_response();
|
||||
};
|
||||
let slug_e = crate::theme::html_escape(&proj.slug);
|
||||
let mut body = format!("<h1>{slug_e}</h1>");
|
||||
for sec in &proj.section {
|
||||
body.push_str(&format!("<h2>{}</h2><ul>", crate::theme::html_escape(&sec.title)));
|
||||
for page in &sec.pages {
|
||||
let url = page.strip_suffix(".md").unwrap_or(page);
|
||||
let url_e = crate::theme::html_escape(url);
|
||||
body.push_str(&format!("<li><a href=\"/{slug_e}/{url_e}\">{url_e}</a></li>"));
|
||||
}
|
||||
body.push_str("</ul>");
|
||||
}
|
||||
let html = crate::theme::page_html(&st.reg, &proj.slug, &proj.slug, &body);
|
||||
Html(html).into_response()
|
||||
}
|
||||
|
||||
async fn page(State(st): State<AppState>, Path((slug, page)): Path<(String, String)>) -> Response {
|
||||
let url_page = page.trim_end_matches('/');
|
||||
let Some(pref) = st.reg.resolve(&slug, url_page) else {
|
||||
|
||||
@@ -192,6 +192,24 @@ docs="docs"
|
||||
);
|
||||
}
|
||||
|
||||
/// A registered project's root URL (`/<slug>/`) must resolve to a landing page
|
||||
/// that lists that project's sections/pages — not 404. Both the home index
|
||||
/// (`index`, src/server/mod.rs) and every page breadcrumb (`page_html`,
|
||||
/// src/theme/mod.rs) emit `<a href="/<slug>/">` for the project root, so it has
|
||||
/// to be a live route. The router declares only `/`, `/assets/theme.css`, and a
|
||||
/// `/{slug}/{*page}` catch-all that requires a concrete page segment, leaving a
|
||||
/// bare project root such as `/aura/` unrouted (404) while a concrete page like
|
||||
/// `/aura/design/INDEX` resolves — so every index and breadcrumb root link is dead.
|
||||
#[tokio::test]
|
||||
async fn project_root_resolves_to_landing_with_section_nav() {
|
||||
let (status, body) = get(router(state()), "/aura/").await;
|
||||
assert_eq!(status, StatusCode::OK, "a project root must resolve, not 404: {body}");
|
||||
assert!(
|
||||
body.contains("<a href=\"/aura/design/INDEX\">"),
|
||||
"project landing must list the project's sections/pages: {body}"
|
||||
);
|
||||
}
|
||||
|
||||
/// A Fetcher that counts `fetch_raw` calls, wrapping a MockFetcher. Lets the
|
||||
/// E2E layer prove the cache-before-fetch data-flow.
|
||||
struct CountingFetcher {
|
||||
|
||||
Reference in New Issue
Block a user