feat: Remove fallback to Cookiecutters, use bundled template only

- Remove fallback to ~/Documents/Cookiecutters/snowflake from default_project_template()
- Panic with clear error if bundled template not found
- Update tests to use 05-First Draft instead of 06-First Draft
- Update comments to reflect merged structure (05-First Draft contains planning+draft)

The code now only uses the bundled template at templates/snowflake/ relative to the
executable, ensuring all new projects use the merged 05-First Draft structure.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
2026-09-16 18:27:54 -05:00
parent aab943de6b
commit 2471312f05
2 changed files with 19 additions and 22 deletions
+8 -11
View File
@@ -185,9 +185,9 @@ pub fn default_show_toolbar() -> bool {
} }
/// Default cookiecutter template for **File ▸ New project…**. /// Default cookiecutter template for **File ▸ New project…**.
/// First tries the bundled template, then falls back to the user's Cookiecutters directory. /// Uses the bundled template from the project's templates/snowflake directory.
pub fn default_project_template() -> PathBuf { pub fn default_project_template() -> PathBuf {
// Try the bundled template first (relative to the executable) // Find the bundled template relative to the executable's location
if let Ok(exe_path) = std::env::current_exe() { if let Ok(exe_path) = std::env::current_exe() {
// The templates are at the project root level, same as Cargo.toml // The templates are at the project root level, same as Cargo.toml
// binary is typically at target/release/md-manuscript or target/debug/md-manuscript // binary is typically at target/release/md-manuscript or target/debug/md-manuscript
@@ -209,17 +209,14 @@ pub fn default_project_template() -> PathBuf {
} }
} }
} }
// Fallback to user's Cookiecutters directory // If we can't find the bundled template, panic with a clear error
dirs::home_dir() // The bundled template should always be present in a properly built release
.unwrap_or_else(|| PathBuf::from(".")) panic!("Bundled template not found. Please ensure templates/snowflake/ exists relative to the executable.");
.join("Documents")
.join("Cookiecutters")
.join("snowflake")
} }
/// Default subfolder opened after generating a project: the snowflake /// Default subfolder opened after generating a project: the snowflake
/// template's drafting folder, where the chapter files live. /// template's drafting folder (05-First Draft), where the chapter files live.
/// Supports both "05-First Draft" (merged planning+draft) and "06-First Draft" (legacy). /// Contains merged planning and draft content in a single folder.
pub fn default_project_open_subdir() -> String { pub fn default_project_open_subdir() -> String {
"05-First Draft".to_string() "05-First Draft".to_string()
} }
@@ -594,7 +591,7 @@ mod tests {
#[test] #[test]
fn bundled_template_is_found_via_exe_path() { fn bundled_template_is_found_via_exe_path() {
let path = default_project_template(); let path = default_project_template();
// The function should find either the bundled template or the fallback // The function should find the bundled template
// Verify it returns a valid path that contains cookiecutter.json // Verify it returns a valid path that contains cookiecutter.json
assert!(path.join("cookiecutter.json").is_file(), assert!(path.join("cookiecutter.json").is_file(),
"Template path {} should contain cookiecutter.json", path.display()); "Template path {} should contain cookiecutter.json", path.display());
+11 -11
View File
@@ -576,10 +576,10 @@ mod tests {
assert!(dir_matches("Archive", "Archive")); assert!(dir_matches("Archive", "Archive"));
assert!(dir_matches("archive", "ARCHIVE")); assert!(dir_matches("archive", "ARCHIVE"));
assert!(dir_matches("03-Characters", "Characters")); assert!(dir_matches("03-Characters", "Characters"));
assert!(dir_matches("06-First Draft", "06-First Draft")); assert!(dir_matches("05-First Draft", "05-First Draft"));
assert!(dir_matches("06-First Draft", "First Draft")); assert!(dir_matches("05-First Draft", "First Draft"));
assert!(dir_matches("6 First Draft", "First Draft")); assert!(dir_matches("5 First Draft", "First Draft"));
assert!(dir_matches("06_First Draft", "First Draft")); assert!(dir_matches("05_First Draft", "First Draft"));
} }
#[test] #[test]
@@ -597,7 +597,7 @@ mod tests {
let ws = scratch( let ws = scratch(
"hidden", "hidden",
&[ &[
"06-First Draft/ch-01.md", "05-First Draft/ch-01.md",
"03-Characters/ada.md", "03-Characters/ada.md",
"10-Archive/old/dead-draft.md", "10-Archive/old/dead-draft.md",
"10-Archive/also-dead.md", "10-Archive/also-dead.md",
@@ -609,7 +609,7 @@ mod tests {
let hidden = vec!["Archive".to_string()]; let hidden = vec!["Archive".to_string()];
let mut kept = list_markdown_files(&ws, &hidden); let mut kept = list_markdown_files(&ws, &hidden);
kept.sort(); kept.sort();
assert_eq!(kept, v(&["03-Characters/ada.md", "06-First Draft/ch-01.md"])); assert_eq!(kept, v(&["03-Characters/ada.md", "05-First Draft/ch-01.md"]));
let _ = std::fs::remove_dir_all(&ws); let _ = std::fs::remove_dir_all(&ws);
} }
@@ -624,16 +624,16 @@ mod tests {
fn the_manuscript_folder_is_found_by_role_name() { fn the_manuscript_folder_is_found_by_role_name() {
let ws = scratch( let ws = scratch(
"project", "project",
&["06-First Draft/ch-01.md", "03-Characters/ada.md"], &["05-First Draft/ch-01.md", "03-Characters/ada.md"],
); );
// The configured name matches with or without its number. // The configured name matches with or without its number.
assert_eq!( assert_eq!(
child_dir_matching(&ws, "06-First Draft").as_deref(), child_dir_matching(&ws, "05-First Draft").as_deref(),
Some("06-First Draft") Some("05-First Draft")
); );
assert_eq!( assert_eq!(
child_dir_matching(&ws, "First Draft").as_deref(), child_dir_matching(&ws, "First Draft").as_deref(),
Some("06-First Draft") Some("05-First Draft")
); );
// A folder the project does not have is not invented. // A folder the project does not have is not invented.
assert_eq!(child_dir_matching(&ws, "09-Covers"), None); assert_eq!(child_dir_matching(&ws, "09-Covers"), None);
@@ -646,7 +646,7 @@ mod tests {
#[test] #[test]
fn a_plain_chapter_folder_is_not_a_project_root() { fn a_plain_chapter_folder_is_not_a_project_root() {
let ws = scratch("plain", &["ch-01.md", "ch-02.md"]); let ws = scratch("plain", &["ch-01.md", "ch-02.md"]);
assert_eq!(child_dir_matching(&ws, "06-First Draft"), None); assert_eq!(child_dir_matching(&ws, "05-First Draft"), None);
let _ = std::fs::remove_dir_all(&ws); let _ = std::fs::remove_dir_all(&ws);
} }
} }