4 Commits

Author SHA1 Message Date
landon 43279810a5 fix: Change file list panel from show_animated to show
show_animated may have issues with width persistence when resizing.
Using show() instead should allow the panel to remember its width.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-09-16 19:01:38 -05:00
landon c61a7b605a fix: Add effective_project_template() to fallback to bundled template
- Add effective_project_template() method that checks if saved template path exists
- If not, falls back to default_project_template() (bundled template)
- This handles users who have old config.json pointing to deleted Cookiecutters path
- Update project.rs to use effective_project_template() when creating new projects

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-09-16 18:49:46 -05:00
landon 3392b7e4d3 fix: Start walking from executable directory, not file path
The previous fix started walking from the executable file itself, but we need to
start from its parent directory. Changed:
  let mut current = exe_path.as_path();
to:
  let mut current = exe_path.parent().unwrap_or_else(|| exe_path.as_path());

This ensures we check directories, not the file itself.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-09-16 18:45:01 -05:00
landon 341202f87c fix: Improve bundled template path resolution and add tests
- Walk up directory tree from executable to find templates/snowflake
- Handles various build configurations (release, debug, cargo run, installed)
- Add test to verify bundled template path contains 'templates/snowflake'
- Add test to verify template has 05-First Draft (merged) and not old 05-Plot/06-First Draft

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-09-16 18:38:03 -05:00
3 changed files with 57 additions and 20 deletions
+1 -1
View File
@@ -171,7 +171,7 @@ impl App {
// that asks for more width than there is would otherwise push it
// wider every frame.
.width_range(160.0..=460.0)
.show_animated(ctx, show_files, |ui| {
.show(ctx, show_files, |ui| {
// The default theme renders unselected list rows fairly dim; bump
// the widget text colours so file names stay legible (especially in
// dark mode) without affecting the rest of the app.
+3 -3
View File
@@ -61,7 +61,7 @@ impl App {
self.np_status = format!("{} already exists.", parent.join(&name).display());
return;
}
let template = self.config.project_template.clone();
let template = self.config.effective_project_template();
// Check that the template directory exists
if !template.is_dir() {
@@ -600,8 +600,8 @@ impl App {
if browse_template {
let mut dialog = rfd::FileDialog::new().set_title("Choose a cookiecutter template");
if self.config.project_template.is_dir() {
dialog = dialog.set_directory(&self.config.project_template);
if self.config.effective_project_template().is_dir() {
dialog = dialog.set_directory(&self.config.effective_project_template());
}
if let Some(path) = dialog.pick_folder() {
self.config.project_template = path;
+53 -16
View File
@@ -189,29 +189,34 @@ pub fn default_show_toolbar() -> bool {
pub fn default_project_template() -> PathBuf {
// Find the bundled template relative to the executable's location
if let Ok(exe_path) = std::env::current_exe() {
// 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
// So we need to go up from target/release (or target/debug) to the project root
// Then look for templates/snowflake there
let candidates = [
// Binary in target/release/ -> project root -> templates/snowflake
exe_path.parent().and_then(|p| p.parent()).and_then(|p| p.parent()).map(|p| p.join("templates/snowflake")),
// Binary in target/debug/ -> project root -> templates/snowflake
exe_path.parent().and_then(|p| p.parent()).map(|p| p.join("templates/snowflake")),
// Binary in project root (dev) -> templates/snowflake
exe_path.parent().map(|p| p.join("templates/snowflake")),
// Fallback: relative to current working directory
std::env::current_dir().ok().map(|p| p.join("templates/snowflake")),
];
for candidate in candidates.into_iter().flatten() {
// Walk up from the executable's directory looking for templates/snowflake/cookiecutter.json
// This handles various build configurations (release, debug, cargo run, installed binary)
let mut current = exe_path.parent().unwrap_or_else(|| exe_path.as_path());
loop {
// Check if current/templates/snowflake/cookiecutter.json exists
let candidate = current.join("templates/snowflake");
if candidate.join("cookiecutter.json").is_file() {
return candidate;
}
// Try going up one level
match current.parent() {
Some(parent) => current = parent,
None => break,
}
}
// Also try relative to current working directory
if let Ok(cwd) = std::env::current_dir() {
let candidate = cwd.join("templates/snowflake");
if candidate.join("cookiecutter.json").is_file() {
return candidate;
}
}
}
// If we can't find the bundled template, panic with a clear error
// The bundled template should always be present in a properly built release
panic!("Bundled template not found. Please ensure templates/snowflake/ exists relative to the executable.");
panic!("Bundled template not found. Please ensure templates/snowflake/cookiecutter.json exists relative to the executable.");
}
/// Default subfolder opened after generating a project: the snowflake
@@ -422,6 +427,17 @@ impl Config {
}
}
/// The project template path to use, falling back to the bundled template
/// if the saved path doesn't exist.
pub fn effective_project_template(&self) -> PathBuf {
// If the saved template path exists and has cookiecutter.json, use it
if self.project_template.join("cookiecutter.json").is_file() {
return self.project_template.clone();
}
// Otherwise fall back to the bundled template
default_project_template()
}
pub fn load() -> Config {
let path = config_path();
match std::fs::read_to_string(&path) {
@@ -595,5 +611,26 @@ mod tests {
// Verify it returns a valid path that contains cookiecutter.json
assert!(path.join("cookiecutter.json").is_file(),
"Template path {} should contain cookiecutter.json", path.display());
// Also verify it's using the bundled template (contains "templates/snowflake")
let path_str = path.to_string_lossy();
assert!(path_str.contains("templates/snowflake"),
"Template path {} should be the bundled template", path.display());
}
#[test]
fn bundled_template_contains_05_first_draft() {
let path = default_project_template();
let template_dir = path.join("{{ cookiecutter.project_name }}");
// Verify the template has the merged 05-First Draft folder
assert!(template_dir.join("05-First Draft").is_dir(),
"Bundled template should contain 05-First Draft directory");
// Verify it does NOT have the old separate directories
assert!(!template_dir.join("05-Plot").is_dir(),
"Bundled template should NOT contain old 05-Plot directory");
assert!(!template_dir.join("06-First Draft").is_dir(),
"Bundled template should NOT contain old 06-First Draft directory");
}
}