Bundle the project template into the binary; add outline-to-draft

links, a sprint timer, placeholder scanning, corkboard, and a
quick-capture inbox

- Compile templates/snowflake into the binary via include_dir, so New
  project… never depends on files installed beside the executable;
  Settings ▸ Template becomes an optional external override, and a
  broken saved override now falls back to the bundled template
  instead of failing project generation.
- Outline beats now link to a matching chapter for a one-click jump.
- Sprint window: a daily whole-manuscript word goal plus a
  Pomodoro-style writing sprint timer, with a compact countdown in the
  status bar while in Distraction-free mode.
- Placeholders window: find [TODO]/[TK]/[FIXME]/[XXX]/[NOTE]/[???]
  markers left in the draft and jump straight to any of them.
- Snowflake progress strip in the file panel: a nudge-only readout of
  which snowflake stage folders actually have content.
- Corkboard window: drag-reorderable synopsis cards, one per chapter.
- Quick-capture popup (Ctrl+Shift+N): jot a stray idea straight into
  the project's Scratch Pad without losing your place in the draft.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 11:38:31 -05:00
parent 6ca8681d08
commit 7854a961a9
19 changed files with 1256 additions and 163 deletions
+37
View File
@@ -147,6 +147,27 @@ impl App {
);
}
// A sprint countdown, but only in Distraction-free mode —
// outside it, the full detail lives in the Sprint window.
let distraction_free =
!self.config.show_toolbar && !self.config.show_file_panel;
if distraction_free {
if let Some(left) = self.sprint_remaining() {
let secs = left.as_secs();
ui.separator();
ui.label(
egui::RichText::new(format!(
"⏱ {:02}:{:02}",
secs / 60,
secs % 60
))
.weak(),
)
.on_hover_text("Time left in the current writing sprint");
ctx.request_repaint_after(Duration::from_millis(500));
}
}
// Progress toward the file's Word Count Target, if set.
if let Some((goal, prose)) = goal {
let (frac, color, text) = goal_progress(goal, prose);
@@ -283,6 +304,22 @@ impl App {
ui.close_menu();
self.show_synopsis = true;
}
if ui.button("⏱ Sprint…").clicked() {
ui.close_menu();
self.show_sprint = true;
}
if ui.button("🔍 Placeholders…").clicked() {
ui.close_menu();
self.show_placeholders = true;
}
if ui.button("🗂 Corkboard…").clicked() {
ui.close_menu();
self.show_corkboard = true;
}
if ui.button("📥 Quick note… Ctrl+Shift+N").clicked() {
ui.close_menu();
self.show_quick_capture = true;
}
ui.separator();
let busy = self.beats_rx.is_some();
if ui