Classify localized page routes as core
This commit is contained in:
@@ -20,7 +20,7 @@ The project is under active development. Core blogging workflows are broadly ava
|
|||||||
- Local MCP automation over stdio or a localhost-only stateless HTTP endpoint, with project resources, read/search/count tools, inert write proposals, explicit desktop approval, and opt-in Claude Code/Copilot configuration.
|
- Local MCP automation over stdio or a localhost-only stateless HTTP endpoint, with project resources, read/search/count tools, inert write proposals, explicit desktop approval, and opt-in Claude Code/Copilot configuration.
|
||||||
- A fully localized Ratatui terminal workspace, available locally through `bds-cli tui`/`BDS_MODE=tui` and remotely through authenticated SSH shell sessions, with shared post/template/script editing and publishing, project/search/command overlays, settings, tags, Git, reports, task progress, live multi-client locale updates, and airplane-mode AI gating.
|
- A fully localized Ratatui terminal workspace, available locally through `bds-cli tui`/`BDS_MODE=tui` and remotely through authenticated SSH shell sessions, with shared post/template/script editing and publishing, project/search/command overlays, settings, tags, Git, reports, task progress, live multi-client locale updates, and airplane-mode AI gating.
|
||||||
- `bds-cli server` hosting the shared application engines over a loopback-by-default, public-key-only SSH service, with restrictive private key material, live authorization updates, terminal-session transport, CLI-change synchronization, ordered domain/task events, and native desktop remote-project selection.
|
- `bds-cli server` hosting the shared application engines over a loopback-by-default, public-key-only SSH service, with restrictive private key material, live authorization updates, terminal-session transport, CLI-change synchronization, ordered domain/task events, and native desktop remote-project selection.
|
||||||
- bDS2-compatible Markdown/Liquid rendering with built-in macros rendered from bundled Liquid templates in isolated scopes (customizable with `macros/*` partial-template slugs), descriptive category archive titles, canonical multilingual and flat page routes for every configured blog language, recursive menus, calendar archives, feeds, a root hreflang sitemap, Pagefind, and incremental site generation through cancellable section task groups.
|
- bDS2-compatible Markdown/Liquid rendering with built-in macros rendered from bundled Liquid templates in isolated scopes (customizable with `macros/*` partial-template slugs), descriptive category archive titles, canonical multilingual and flat page routes for every configured blog language, recursive menus, calendar archives, feeds, a root hreflang sitemap, Pagefind, and incremental site generation and validation repair through cancellable section task groups.
|
||||||
- Navigable generated-route preview in the app or system browser, with draft database overlays and published filesystem content.
|
- Navigable generated-route preview in the app or system browser, with draft database overlays and published filesystem content.
|
||||||
- Optional one-shot AI translation, description, analysis, taxonomy, and language-detection operations run in background tasks with editor-level waiting indicators, using provider-portable JSON-only requests through independent online and local OpenAI-compatible profiles. Each profile has secure credentials, persistently discovered chat/title/image model selections, explicit tool/vision overrides, chat testing, and restart-persistent status-bar airplane-mode routing.
|
- Optional one-shot AI translation, description, analysis, taxonomy, and language-detection operations run in background tasks with editor-level waiting indicators, using provider-portable JSON-only requests through independent online and local OpenAI-compatible profiles. Each profile has secure credentials, persistently discovered chat/title/image model selections, explicit tool/vision overrides, chat testing, and restart-persistent status-bar airplane-mode routing.
|
||||||
- Persistent conversational AI with safe Markdown, streamed and cancellable responses, model/session/token tracking, bounded project-aware blog tools, and localized conversation management in the Chat workspace. Allowlisted render tools add persistent native cards, charts, forms, lists, metrics, mind maps, tables, and tabs without executing assistant-provided HTML or JavaScript.
|
- Persistent conversational AI with safe Markdown, streamed and cancellable responses, model/session/token tracking, bounded project-aware blog tools, and localized conversation management in the Chat workspace. Allowlisted render tools add persistent native cards, charts, forms, lists, metrics, mind maps, tables, and tabs without executing assistant-provided HTML or JavaScript.
|
||||||
|
|||||||
@@ -823,20 +823,10 @@ pub(crate) fn classify_generated_path(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn has_language_prefix(parts: &[&str], metadata: &ProjectMetadata) -> bool {
|
fn has_language_prefix(parts: &[&str], metadata: &ProjectMetadata) -> bool {
|
||||||
match parts {
|
parts.len() > 1
|
||||||
[first, second, ..] => {
|
&& render_languages(metadata)
|
||||||
render_languages(metadata)
|
.iter()
|
||||||
.iter()
|
.any(|language| language.eq_ignore_ascii_case(parts[0]))
|
||||||
.any(|language| language.eq_ignore_ascii_case(first))
|
|
||||||
&& (*second == "index.html"
|
|
||||||
|| *second == "404.html"
|
|
||||||
|| *second == "page"
|
|
||||||
|| is_year_segment(second)
|
|
||||||
|| *second == "category"
|
|
||||||
|| *second == "tag")
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_year_segment(value: &str) -> bool {
|
fn is_year_segment(value: &str) -> bool {
|
||||||
|
|||||||
@@ -850,6 +850,68 @@ fn configured_language_prefixes_select_their_actual_validation_section() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn localized_page_paths_select_the_core_validation_section() {
|
||||||
|
let mut metadata = make_metadata();
|
||||||
|
metadata.blog_languages.push("de".into());
|
||||||
|
let report = bds_core::engine::validate_site::SiteValidationReport {
|
||||||
|
missing_pages: vec!["de/about/index.html".into()],
|
||||||
|
extra_pages: Vec::new(),
|
||||||
|
stale_pages: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
sections_from_validation_report(&report, &metadata),
|
||||||
|
vec![GenerationSection::Core]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validation_apply_repairs_and_cleans_localized_page_outputs() {
|
||||||
|
let (db, dir) = setup();
|
||||||
|
let mut metadata = make_metadata();
|
||||||
|
metadata.blog_languages = vec!["en".into(), "de".into()];
|
||||||
|
bds_core::engine::meta::write_project_json(dir.path(), &metadata).unwrap();
|
||||||
|
let mut page = make_post("about", 1_710_000_000_000);
|
||||||
|
page.categories = vec!["page".into()];
|
||||||
|
write_published_snapshot(&dir, &mut page, "About");
|
||||||
|
bds_core::db::queries::post::insert_post(db.conn(), &page).unwrap();
|
||||||
|
let posts = vec![PublishedPostSource {
|
||||||
|
post: page,
|
||||||
|
body_markdown: "About".into(),
|
||||||
|
}];
|
||||||
|
|
||||||
|
generate_starter_site(db.conn(), dir.path(), "p1", &metadata, &posts, "en").unwrap();
|
||||||
|
let localized_page = dir.path().join("de/about/index.html");
|
||||||
|
let localized_extra = dir.path().join("de/ghost/index.html");
|
||||||
|
std::fs::write(&localized_page, "tampered").unwrap();
|
||||||
|
std::fs::create_dir_all(localized_extra.parent().unwrap()).unwrap();
|
||||||
|
std::fs::write(&localized_extra, "ghost").unwrap();
|
||||||
|
|
||||||
|
let validation = validate_site(db.conn(), dir.path(), "p1").unwrap();
|
||||||
|
let sections = sections_from_validation_report(&validation, &metadata);
|
||||||
|
let applied =
|
||||||
|
apply_validation_sections(db.conn(), dir.path(), "p1", &metadata, &posts, §ions)
|
||||||
|
.unwrap();
|
||||||
|
let repaired = validate_site(db.conn(), dir.path(), "p1").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(sections, vec![GenerationSection::Core]);
|
||||||
|
assert!(
|
||||||
|
applied
|
||||||
|
.written_paths
|
||||||
|
.contains(&"de/about/index.html".into())
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
applied
|
||||||
|
.deleted_paths
|
||||||
|
.contains(&"de/ghost/index.html".into())
|
||||||
|
);
|
||||||
|
assert!(!localized_extra.exists());
|
||||||
|
assert!(repaired.missing_pages.is_empty());
|
||||||
|
assert!(repaired.extra_pages.is_empty());
|
||||||
|
assert!(repaired.stale_pages.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validation_apply_removes_extra_output_under_configured_language() {
|
fn validation_apply_removes_extra_output_under_configured_language() {
|
||||||
let (db, dir) = setup();
|
let (db, dir) = setup();
|
||||||
|
|||||||
Reference in New Issue
Block a user