Files
md-manuscript/src/app/placeholders.rs
T
landon 7854a961a9 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>
2026-09-30 11:38:31 -05:00

85 lines
3.3 KiB
Rust

//! The Placeholders window: every `[TODO]`/`[TK]`/`[???]`-style marker left
//! in the manuscript, so a fast first-draft pass doesn't lose track of the
//! gaps it left behind.
use super::*;
use crate::placeholder_scan::find_placeholders;
impl App {
/// The Placeholders window: one row per marker found, grouped by file in
/// manuscript order. Clicking a row jumps straight to it.
pub(super) fn placeholders_window(&mut self, ctx: &egui::Context) {
let mut open = self.show_placeholders;
// Collect up front (reads the in-memory buffer for the open file,
// disk for the rest), so the window closure only reads.
let selected_name = self.selected.and_then(|i| self.files.get(i)).cloned();
let rows: Vec<(String, Vec<crate::placeholder_scan::Placeholder>)> = self
.manuscript_files()
.into_iter()
.filter_map(|(_, name)| {
let raw = if Some(name) == selected_name.as_ref() {
self.document().into_owned()
} else {
std::fs::read_to_string(self.path_for(name)).unwrap_or_default()
};
let found = find_placeholders(&raw);
if found.is_empty() {
None
} else {
Some((name.clone(), found))
}
})
.collect();
let total: usize = rows.iter().map(|(_, m)| m.len()).sum();
let mut jump_to: Option<(String, usize, usize)> = None;
egui::Window::new("Placeholders")
.open(&mut open)
.resizable(true)
.collapsible(false)
.default_width(480.0)
.default_height(420.0)
.show(ctx, |ui| {
ui.label(
egui::RichText::new(
"Bracketed markers like [TODO], [TK], [FIXME] or [???] left in \
the draft. Click one to jump to it.",
)
.small()
.weak(),
);
ui.separator();
egui::ScrollArea::vertical()
.auto_shrink([false, false])
.show(ui, |ui| {
if total == 0 {
ui.label("No placeholders found — 🎉");
return;
}
for (name, found) in &rows {
ui.label(egui::RichText::new(name).strong());
for marker in found {
if ui
.link(format!(" line {} · [{}]", marker.line, marker.marker))
.clicked()
{
jump_to = Some((name.clone(), marker.start, marker.end));
}
}
ui.add_space(4.0);
}
});
});
if let Some((name, start, end)) = jump_to {
if let Some(idx) = self.files.iter().position(|f| *f == name) {
self.select(idx);
self.issue_jump = Some((start, end));
}
}
self.show_placeholders = open;
}
}