feat: Remove ### Rough Draft: marker, use YAML frontmatter only for draft files
- Everything after YAML frontmatter closing --- is now considered the draft content - No ### Rough Draft: marker needed - Chapter titles are handled by the program on export based on file order - Draft content should not include chapter titles - Updated default_new_file_template to use YAML frontmatter - Updated preprocess.rs to parse YAML frontmatter for metadata - Updated all relevant tests - Updated documentation in help.rs - Updated template files Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
+2
-2
@@ -834,8 +834,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn commented_prose_is_dropped_from_the_exported_body() {
|
fn commented_prose_is_dropped_from_the_exported_body() {
|
||||||
// The whole point of the hotkey: preprocess must not see the hidden line.
|
// The whole point of the hotkey: preprocess must not see the hidden line.
|
||||||
let md = "### Rough Draft:\n\nKeep this.\n\n<!-- Cut this. -->\n";
|
let md = "---\ntitle: Test\n---\n\nKeep this.\n\n<!-- Cut this. -->\n";
|
||||||
let body = crate::preprocess::parse(md, "### Rough Draft:").body;
|
let body = crate::preprocess::parse(md, "").body;
|
||||||
assert!(body.contains("Keep this."));
|
assert!(body.contains("Keep this."));
|
||||||
assert!(!body.contains("Cut this."));
|
assert!(!body.contains("Cut this."));
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-13
@@ -813,10 +813,6 @@ order: {}
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Draft Content
|
|
||||||
|
|
||||||
### Rough Draft:
|
|
||||||
|
|
||||||
[Write your scene content here...]
|
[Write your scene content here...]
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -2027,14 +2023,14 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn render_template_expands_every_placeholder() {
|
fn render_template_expands_every_placeholder() {
|
||||||
let out = render_template(
|
let out = render_template(
|
||||||
"# Title: {{name}}\n# Started: {{date}}\n\n{{marker}}\n",
|
"---\ntitle: {{name}}\n---\n",
|
||||||
"untitled-7",
|
"untitled-7",
|
||||||
"### Rough Draft:",
|
"",
|
||||||
"2026-08-22",
|
"2026-08-22",
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
"# Title: untitled-7\n# Started: 2026-08-22\n\n### Rough Draft:\n"
|
"---\ntitle: untitled-7\n---\n"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2074,7 +2070,7 @@ mod tests {
|
|||||||
assert_eq!(h.slug, None);
|
assert_eq!(h.slug, None);
|
||||||
assert_eq!(h.pov, None);
|
assert_eq!(h.pov, None);
|
||||||
assert_eq!(h.goal, None);
|
assert_eq!(h.goal, None);
|
||||||
// Nothing above the marker leaks into the exported prose.
|
// Nothing above the --- leaks into the exported prose.
|
||||||
assert_eq!(h.body.trim(), "");
|
assert_eq!(h.body.trim(), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2083,8 +2079,7 @@ mod tests {
|
|||||||
fn filled_template_parses_into_header_fields() {
|
fn filled_template_parses_into_header_fields() {
|
||||||
let marker = crate::config::default_marker();
|
let marker = crate::config::default_marker();
|
||||||
let seeded = render_template(
|
let seeded = render_template(
|
||||||
"# Title: {{name}}\n# Slug: the door\n# POV: Ada\n\
|
"---\ntitle: {{name}}\nslug: the door\npov: Ada\nword_count_target: 1500 - 2000\n---\n\nReal prose here.\n",
|
||||||
# Word Count Target: 1500 - 2000\n\n{{marker}}\n\nReal prose here.\n",
|
|
||||||
"chapter-02",
|
"chapter-02",
|
||||||
&marker,
|
&marker,
|
||||||
"2026-08-22",
|
"2026-08-22",
|
||||||
@@ -2102,11 +2097,12 @@ mod tests {
|
|||||||
let out = render_template(
|
let out = render_template(
|
||||||
&crate::config::default_new_file_template(),
|
&crate::config::default_new_file_template(),
|
||||||
"chapter-01",
|
"chapter-01",
|
||||||
"### Rough Draft:",
|
"",
|
||||||
"2026-08-22",
|
"2026-08-22",
|
||||||
);
|
);
|
||||||
assert!(out.starts_with("# Title: chapter-01\n"), "got {out:?}");
|
assert!(out.starts_with("---\n"), "got {out:?}");
|
||||||
assert!(out.contains("\n### Rough Draft:\n"), "got {out:?}");
|
assert!(out.contains("title: chapter-01"), "got {out:?}");
|
||||||
|
assert!(out.contains("---\n"), "got {out:?}");
|
||||||
assert!(!out.contains("{{"), "placeholder left unexpanded in {out:?}");
|
assert!(!out.contains("{{"), "placeholder left unexpanded in {out:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -249,7 +249,7 @@ mod tests {
|
|||||||
Character {
|
Character {
|
||||||
name: heading_name(text).unwrap_or_default(),
|
name: heading_name(text).unwrap_or_default(),
|
||||||
file: PathBuf::from("x.md"),
|
file: PathBuf::from("x.md"),
|
||||||
fields: crate::preprocess::fields(text, "### Rough Draft:"),
|
fields: crate::preprocess::fields(text, ""),
|
||||||
subtitle: subtitle(text),
|
subtitle: subtitle(text),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-15
@@ -236,20 +236,16 @@ pub fn default_projects_dir() -> PathBuf {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Default new-file template: the editorial header fields this app already
|
/// Default new-file template: Uses YAML frontmatter for metadata,
|
||||||
/// understands, followed by the draft marker so the body starts below it.
|
/// with everything after the closing --- being the draft content.
|
||||||
///
|
|
||||||
/// Note: For YAML frontmatter support, create files manually with --- delimiters.
|
|
||||||
/// The legacy header format is kept for backward compatibility with the parser.
|
|
||||||
pub fn default_new_file_template() -> String {
|
pub fn default_new_file_template() -> String {
|
||||||
[
|
[
|
||||||
"# Title: {{name}}",
|
"---",
|
||||||
"# Slug:",
|
"title: {{name}}",
|
||||||
"# POV:",
|
"slug: ",
|
||||||
"# Word Count Target:",
|
"pov: ",
|
||||||
"",
|
"word_count_target: ",
|
||||||
"{{marker}}",
|
"---",
|
||||||
"",
|
|
||||||
"",
|
"",
|
||||||
]
|
]
|
||||||
.join("\n")
|
.join("\n")
|
||||||
@@ -296,8 +292,9 @@ pub fn default_languagetool_language() -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Default header/body separator, matching the manuscript drafting convention.
|
/// Default header/body separator, matching the manuscript drafting convention.
|
||||||
|
/// Now empty by default since we use YAML frontmatter.
|
||||||
pub fn default_marker() -> String {
|
pub fn default_marker() -> String {
|
||||||
"### Rough Draft:".to_string()
|
"".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
@@ -456,7 +453,7 @@ mod tests {
|
|||||||
assert!(cfg.show_preview);
|
assert!(cfg.show_preview);
|
||||||
// The new field is filled in from its default rather than left blank.
|
// The new field is filled in from its default rather than left blank.
|
||||||
assert_eq!(cfg.new_file_template, default_new_file_template());
|
assert_eq!(cfg.new_file_template, default_new_file_template());
|
||||||
assert!(cfg.new_file_template.contains("{{marker}}"));
|
assert!(cfg.new_file_template.contains("---"));
|
||||||
// Likewise the Gitea pair. `use_ssh` defaulting to *true* is the point:
|
// Likewise the Gitea pair. `use_ssh` defaulting to *true* is the point:
|
||||||
// a `#[serde(default)]` bool would come back false and quietly attach
|
// a `#[serde(default)]` bool would come back false and quietly attach
|
||||||
// every repository over HTTPS.
|
// every repository over HTTPS.
|
||||||
@@ -472,7 +469,6 @@ mod tests {
|
|||||||
"workspace": "/home/writer/Manuscript",
|
"workspace": "/home/writer/Manuscript",
|
||||||
"export_path": "/home/writer/Manuscript/book.odt",
|
"export_path": "/home/writer/Manuscript/book.odt",
|
||||||
"show_preview": true,
|
"show_preview": true,
|
||||||
"draft_marker": "### Rough Draft:",
|
|
||||||
"editor_zoom": 10.0,
|
"editor_zoom": 10.0,
|
||||||
"languagetool_host": "lt.example.com",
|
"languagetool_host": "lt.example.com",
|
||||||
"mistral_api_key": "secret",
|
"mistral_api_key": "secret",
|
||||||
|
|||||||
+11
-9
@@ -1,6 +1,6 @@
|
|||||||
//! In-app **Markdown cheatsheet** window: a quick reference for the markdown
|
//! In-app **Markdown cheatsheet** window: a quick reference for the markdown
|
||||||
//! md-manuscript understands, plus the manuscript-specific conventions (chapter
|
//! md-manuscript understands, plus the manuscript-specific conventions (YAML frontmatter
|
||||||
//! metadata, the draft-marker header, and comments) applied at export time.
|
//! metadata and comments) applied at export time.
|
||||||
//!
|
//!
|
||||||
//! Pure rendering — the caller owns the `open` flag so the window's ✕ works via
|
//! Pure rendering — the caller owns the `open` flag so the window's ✕ works via
|
||||||
//! [`egui::Window::open`].
|
//! [`egui::Window::open`].
|
||||||
@@ -136,24 +136,26 @@ fn cheatsheet_body(ui: &mut egui::Ui) {
|
|||||||
ui,
|
ui,
|
||||||
"cs-header",
|
"cs-header",
|
||||||
&[
|
&[
|
||||||
("### Rough Draft:", "The draft marker — everything above it is your working header."),
|
("---", "YAML frontmatter delimiter — metadata between --- lines is not exported."),
|
||||||
|
("title: ...", "Chapter title (used for export, not included in draft content)."),
|
||||||
|
("slug: ...", "Optional caption shown beneath the chapter title."),
|
||||||
|
("pov: ...", "Point of view character for this chapter."),
|
||||||
|
("word_count_target: ...", "Target word count (e.g., 2000 or 1500-2000)."),
|
||||||
("<!-- note -->", "A comment; dropped on export (may span multiple lines)."),
|
("<!-- note -->", "A comment; dropped on export (may span multiple lines)."),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
note(
|
note(
|
||||||
ui,
|
ui,
|
||||||
"The draft marker text is configurable in the top bar. Everything above the \
|
"Files use YAML frontmatter for metadata. Everything between --- delimiters is \
|
||||||
marker line is treated as notes and left out of the export; leave the marker \
|
metadata and is not included in the export. Comments are removed everywhere. \
|
||||||
blank to export the whole file. Comments are removed everywhere.",
|
Chapter titles are automatically numbered based on file order in the file list.",
|
||||||
);
|
);
|
||||||
note(
|
note(
|
||||||
ui,
|
ui,
|
||||||
"Typing a header field name above the marker (e.g. “## PO”) pops up an \
|
"Typing a YAML field name (e.g. \"title:\" or \"pov:\") in the frontmatter pops up an \
|
||||||
autocomplete — Tab/Enter to accept, ↑/↓ to choose, Esc to dismiss. It \
|
autocomplete — Tab/Enter to accept, ↑/↓ to choose, Esc to dismiss. It \
|
||||||
suggests common fields plus ones learned from your other files.",
|
suggests common fields plus ones learned from your other files.",
|
||||||
);
|
);
|
||||||
|
|
||||||
section(ui, "Spelling & grammar");
|
|
||||||
body(
|
body(
|
||||||
ui,
|
ui,
|
||||||
"Spelling is checked live and offline: misspellings are underlined in red \
|
"Spelling is checked live and offline: misspellings are underlined in red \
|
||||||
|
|||||||
+191
-2
@@ -281,13 +281,55 @@ fn meta_line(line: &str) -> Option<(Meta, String)> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Pre-process one markdown document, splitting header metadata from the body.
|
/// Pre-process one markdown document, splitting header metadata from the body.
|
||||||
/// `marker` is the header/body separator line (e.g. `### Rough Draft:`); an empty
|
/// Tries YAML frontmatter first, then falls back to the legacy marker-based header.
|
||||||
/// marker disables header-block splitting.
|
/// `marker` is the legacy header/body separator line (e.g. `### Rough Draft:`); an empty
|
||||||
|
/// marker disables marker-based splitting, but YAML frontmatter is still parsed.
|
||||||
pub fn parse(markdown: &str, marker: &str) -> Header {
|
pub fn parse(markdown: &str, marker: &str) -> Header {
|
||||||
let cleaned = strip_comments(markdown);
|
let cleaned = strip_comments(markdown);
|
||||||
let lines: Vec<&str> = cleaned.lines().collect();
|
let lines: Vec<&str> = cleaned.lines().collect();
|
||||||
let marker_norm = marker.trim().to_ascii_lowercase();
|
let marker_norm = marker.trim().to_ascii_lowercase();
|
||||||
|
|
||||||
|
// Try YAML frontmatter first
|
||||||
|
if let Some(body_start_line) = find_yaml_frontmatter_end(&lines) {
|
||||||
|
// Parse metadata from YAML frontmatter
|
||||||
|
let mut title = None;
|
||||||
|
let mut slug = None;
|
||||||
|
let mut pov = None;
|
||||||
|
let mut goal = None;
|
||||||
|
|
||||||
|
// Extract YAML fields from the frontmatter lines
|
||||||
|
for line in &lines[..body_start_line] {
|
||||||
|
// Skip the --- delimiters
|
||||||
|
if line.trim() == "---" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Parse YAML-style key: value lines
|
||||||
|
if let Some((key, value)) = parse_yaml_field(line) {
|
||||||
|
match key.to_ascii_lowercase().as_str() {
|
||||||
|
"title" => title = Some(value),
|
||||||
|
"slug" => slug = Some(value),
|
||||||
|
"pov" => pov = Some(value),
|
||||||
|
"word_count_target" | "word count target" | "wordcounttarget" => {
|
||||||
|
goal = parse_goal(&value);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body is everything after the closing ---
|
||||||
|
let body_lines: Vec<&str> = lines[body_start_line..].to_vec();
|
||||||
|
|
||||||
|
return Header {
|
||||||
|
title: title.filter(|s| !s.is_empty()),
|
||||||
|
slug: slug.filter(|s| !s.is_empty()),
|
||||||
|
pov: pov.filter(|s| !s.is_empty()),
|
||||||
|
goal,
|
||||||
|
body: body_lines.join("\n").trim().to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to legacy marker-based parsing
|
||||||
let marker_idx = if marker_norm.is_empty() {
|
let marker_idx = if marker_norm.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
@@ -345,6 +387,115 @@ pub fn parse(markdown: &str, marker: &str) -> Header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Find the line index where YAML frontmatter ends (after the closing `---`).
|
||||||
|
/// Returns Some(body_start_line) where body_start_line is the index after the closing ---.
|
||||||
|
fn find_yaml_frontmatter_end(lines: &[&str]) -> Option<usize> {
|
||||||
|
if lines.is_empty() || lines[0].trim() != "---" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, line) in lines.iter().enumerate().skip(1) {
|
||||||
|
if line.trim() == "---" {
|
||||||
|
return Some(i + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a YAML field line like `key: value` or `key: "value"`.
|
||||||
|
/// Returns (key, value) if the line looks like a YAML field.
|
||||||
|
/// Strips inline comments (everything after # that's not in quotes).
|
||||||
|
fn parse_yaml_field(line: &str) -> Option<(String, String)> {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
|
||||||
|
// Skip empty lines and full-line comments
|
||||||
|
if trimmed.is_empty() || trimmed.starts_with('#') {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip inline comments first (everything after # that's not in quotes)
|
||||||
|
let mut in_quote = false;
|
||||||
|
let mut quote_char = '\0';
|
||||||
|
let mut comment_pos = None;
|
||||||
|
|
||||||
|
for (i, c) in trimmed.char_indices() {
|
||||||
|
match c {
|
||||||
|
'"' | '\'' => {
|
||||||
|
if in_quote && c == quote_char {
|
||||||
|
in_quote = false;
|
||||||
|
quote_char = '\0';
|
||||||
|
} else if !in_quote {
|
||||||
|
in_quote = true;
|
||||||
|
quote_char = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'#' => {
|
||||||
|
if !in_quote && comment_pos.is_none() {
|
||||||
|
comment_pos = Some(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim at the comment position if found
|
||||||
|
let trimmed_no_comment = if let Some(pos) = comment_pos {
|
||||||
|
trimmed[..pos].trim()
|
||||||
|
} else {
|
||||||
|
trimmed
|
||||||
|
};
|
||||||
|
|
||||||
|
if trimmed_no_comment.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the first colon that's not part of a quoted string
|
||||||
|
let mut in_quote = false;
|
||||||
|
let mut quote_char = '\0';
|
||||||
|
let mut colon_pos = None;
|
||||||
|
|
||||||
|
for (i, c) in trimmed_no_comment.char_indices() {
|
||||||
|
match c {
|
||||||
|
'"' | '\'' => {
|
||||||
|
if in_quote && c == quote_char {
|
||||||
|
in_quote = false;
|
||||||
|
quote_char = '\0';
|
||||||
|
} else if !in_quote {
|
||||||
|
in_quote = true;
|
||||||
|
quote_char = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
':' => {
|
||||||
|
if !in_quote && colon_pos.is_none() {
|
||||||
|
colon_pos = Some(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let colon_pos = colon_pos?;
|
||||||
|
|
||||||
|
let key = trimmed_no_comment[..colon_pos].trim().to_string();
|
||||||
|
let value = trimmed_no_comment[colon_pos + 1..].trim();
|
||||||
|
|
||||||
|
// Remove surrounding quotes if present
|
||||||
|
let value = if value.starts_with('"') && value.ends_with('"') {
|
||||||
|
value[1..value.len() - 1].to_string()
|
||||||
|
} else if value.starts_with('\'') && value.ends_with('\'') {
|
||||||
|
value[1..value.len() - 1].to_string()
|
||||||
|
} else {
|
||||||
|
value.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
if key.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some((key, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -443,6 +594,44 @@ mod tests {
|
|||||||
let md2 = "### Rough Draft:\n\nPOV of the crowd was tense.";
|
let md2 = "### Rough Draft:\n\nPOV of the crowd was tense.";
|
||||||
assert_eq!(parse(md2, MARKER).pov, None);
|
assert_eq!(parse(md2, MARKER).pov, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_yaml_frontmatter() {
|
||||||
|
let md = "---\ntitle: The Gate\nslug: an opening\npov: Bixby\nword_count_target: 1500 - 2000\n---\n\nThe prose begins here.\n\n## A scene";
|
||||||
|
let h = parse(md, "");
|
||||||
|
assert_eq!(h.title.as_deref(), Some("The Gate"));
|
||||||
|
assert_eq!(h.slug.as_deref(), Some("an opening"));
|
||||||
|
assert_eq!(h.pov.as_deref(), Some("Bixby"));
|
||||||
|
assert_eq!(h.goal, Some(WordGoal { min: 1500, max: 2000 }));
|
||||||
|
assert!(h.body.starts_with("The prose begins here."));
|
||||||
|
assert!(h.body.contains("## A scene"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_yaml_frontmatter_with_quoted_values() {
|
||||||
|
let md = "---\ntitle: \"The Gate\"\nslug: \"an opening\"\n---\n\nProse here.";
|
||||||
|
let h = parse(md, "");
|
||||||
|
assert_eq!(h.title.as_deref(), Some("The Gate"));
|
||||||
|
assert_eq!(h.slug.as_deref(), Some("an opening"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_yaml_frontmatter_with_comments() {
|
||||||
|
let md = "---\n# This is a comment
|
||||||
|
title: The Gate # inline comment\nslug: an opening\n---\n\nProse here.";
|
||||||
|
let h = parse(md, "");
|
||||||
|
assert_eq!(h.title.as_deref(), Some("The Gate"));
|
||||||
|
assert_eq!(h.slug.as_deref(), Some("an opening"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn falls_back_to_marker_if_no_yaml_frontmatter() {
|
||||||
|
let md = "# Title: The Gate\n# Slug: an opening\n### Rough Draft:\n\nThe prose begins here.";
|
||||||
|
let h = parse(md, "### Rough Draft:");
|
||||||
|
assert_eq!(h.title.as_deref(), Some("The Gate"));
|
||||||
|
assert_eq!(h.slug.as_deref(), Some("an opening"));
|
||||||
|
assert!(h.body.starts_with("The prose begins here."));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
+3
-118
@@ -1,128 +1,13 @@
|
|||||||
---
|
---
|
||||||
id: scene_01
|
title: "Scene 1"
|
||||||
title: "Chapter One: The Beginning"
|
slug: "scene-1"
|
||||||
slug: chapter-one-the-beginning
|
|
||||||
act: 1
|
act: 1
|
||||||
chapter: 1
|
chapter: 1
|
||||||
scene: 1
|
scene: 1
|
||||||
type: chapter
|
type: scene
|
||||||
status: draft
|
status: draft
|
||||||
word_count_target: 2000
|
word_count_target: 2000
|
||||||
word_count_min: 1800
|
|
||||||
word_count_max: 2200
|
|
||||||
pov: ""
|
pov: ""
|
||||||
characters: []
|
|
||||||
setting: ""
|
|
||||||
date: ""
|
|
||||||
time: ""
|
|
||||||
conflict: ""
|
|
||||||
theme: ""
|
|
||||||
tone: ""
|
|
||||||
word_count: 0
|
|
||||||
word_count_updated: ""
|
|
||||||
session_word_count: 0
|
|
||||||
last_session: ""
|
|
||||||
total_sessions: 0
|
|
||||||
version: 1
|
|
||||||
created: ""
|
|
||||||
updated: ""
|
|
||||||
order: 1
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Chapter One: The Beginning
|
|
||||||
|
|
||||||
**Scene File** | **ID:** scene_01 | **Status:** Draft
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Scene Metadata
|
|
||||||
|
|
||||||
| Attribute | Value |
|
|
||||||
|-----------|-------|
|
|
||||||
| **Act** | 1 |
|
|
||||||
| **Chapter** | 1 |
|
|
||||||
| **Scene** | 1 |
|
|
||||||
| **Type** | Chapter |
|
|
||||||
| **POV** | Not set |
|
|
||||||
| **Version** | 1 |
|
|
||||||
| **Word Count Target** | 2000 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Word Count Tracking
|
|
||||||
|
|
||||||
| Metric | Value |
|
|
||||||
|--------|-------|
|
|
||||||
| **Target** | 2000 |
|
|
||||||
| **Minimum** | 1800 |
|
|
||||||
| **Maximum** | 2200 |
|
|
||||||
| **Current** | 0 |
|
|
||||||
| **This Session** | 0 |
|
|
||||||
| **Last Updated** | Never |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Creative Elements
|
|
||||||
|
|
||||||
### POV
|
|
||||||
> Not specified
|
|
||||||
|
|
||||||
### Characters
|
|
||||||
|
|
||||||
### Setting
|
|
||||||
> Not specified
|
|
||||||
|
|
||||||
### Date & Time
|
|
||||||
> Not specified | Not specified
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Story Structure
|
|
||||||
|
|
||||||
### Conflict
|
|
||||||
> Not specified
|
|
||||||
|
|
||||||
### Theme
|
|
||||||
> Not specified
|
|
||||||
|
|
||||||
### Tone
|
|
||||||
> Not specified
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Draft Content
|
|
||||||
|
|
||||||
### Rough Draft:
|
|
||||||
|
|
||||||
[Write your scene content here...]
|
[Write your scene content here...]
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Writing Notes
|
|
||||||
|
|
||||||
### Session Log
|
|
||||||
| Date | Words Added | Notes |
|
|
||||||
|------|--------------|-------|
|
|
||||||
|
|
||||||
|
|
||||||
### Revision Notes
|
|
||||||
-
|
|
||||||
-
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Status Checklist
|
|
||||||
|
|
||||||
- [ ] Planning complete
|
|
||||||
- [ ] First draft complete
|
|
||||||
- [ ] Word count target met
|
|
||||||
- [ ] POV consistent
|
|
||||||
- [ ] Character voices distinct
|
|
||||||
- [ ] Setting details accurate
|
|
||||||
- [ ] Conflict resolved/advanced
|
|
||||||
- [ ] Ready for revision
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Created:** | **Updated:**
|
|
||||||
|
|
||||||
|
|||||||
+5
-121
@@ -1,129 +1,13 @@
|
|||||||
---
|
---
|
||||||
id: scene_02_01
|
title: "Scene 2"
|
||||||
title: "Chapter Two: Development"
|
slug: "scene-2"
|
||||||
slug: chapter-two-development
|
|
||||||
act: 2
|
act: 2
|
||||||
chapter: 1
|
chapter: 2
|
||||||
scene: 1
|
scene: 2
|
||||||
type: chapter
|
type: scene
|
||||||
status: draft
|
status: draft
|
||||||
word_count_target: 2000
|
word_count_target: 2000
|
||||||
word_count_min: 1800
|
|
||||||
word_count_max: 2200
|
|
||||||
pov: ""
|
pov: ""
|
||||||
characters: []
|
|
||||||
setting: ""
|
|
||||||
date: ""
|
|
||||||
time: ""
|
|
||||||
conflict: ""
|
|
||||||
theme: ""
|
|
||||||
tone: ""
|
|
||||||
word_count: 0
|
|
||||||
word_count_updated: ""
|
|
||||||
session_word_count: 0
|
|
||||||
last_session: ""
|
|
||||||
total_sessions: 0
|
|
||||||
version: 1
|
|
||||||
created: ""
|
|
||||||
updated: ""
|
|
||||||
planning_template: ""
|
|
||||||
draft_file: ""
|
|
||||||
order: 1
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Chapter Two: Development
|
|
||||||
|
|
||||||
**Draft File** | **ID:** scene_02_01 | **Status:** Draft
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Scene Metadata
|
|
||||||
|
|
||||||
| Attribute | Value |
|
|
||||||
|-----------|-------|
|
|
||||||
| **Act** | 2 |
|
|
||||||
| **Chapter** | 1 |
|
|
||||||
| **Scene** | 1 |
|
|
||||||
| **Type** | Chapter |
|
|
||||||
| **POV** | Not set |
|
|
||||||
| **Version** | 1 |
|
|
||||||
| **Word Count Target** | 2000 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Word Count Tracking
|
|
||||||
|
|
||||||
| Metric | Value |
|
|
||||||
|--------|-------|
|
|
||||||
| **Target** | 2000 |
|
|
||||||
| **Minimum** | 1800 |
|
|
||||||
| **Maximum** | 2200 |
|
|
||||||
| **Current** | 0 |
|
|
||||||
| **This Session** | 0 |
|
|
||||||
| **Last Updated** | Never |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Creative Elements
|
|
||||||
|
|
||||||
### POV
|
|
||||||
> Not specified
|
|
||||||
|
|
||||||
### Characters
|
|
||||||
|
|
||||||
### Setting
|
|
||||||
> Not specified
|
|
||||||
|
|
||||||
### Date & Time
|
|
||||||
> Not specified | Not specified
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Story Structure
|
|
||||||
|
|
||||||
### Conflict
|
|
||||||
> Not specified
|
|
||||||
|
|
||||||
### Theme
|
|
||||||
> Not specified
|
|
||||||
|
|
||||||
### Tone
|
|
||||||
> Not specified
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Draft Content
|
|
||||||
|
|
||||||
### Rough Draft:
|
|
||||||
|
|
||||||
[Write your scene content here...]
|
[Write your scene content here...]
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Writing Notes
|
|
||||||
|
|
||||||
### Session Log
|
|
||||||
| Date | Words Added | Notes |
|
|
||||||
|------|--------------|-------|
|
|
||||||
|
|
||||||
|
|
||||||
### Revision Notes
|
|
||||||
-
|
|
||||||
-
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Status Checklist
|
|
||||||
|
|
||||||
- [ ] Planning complete
|
|
||||||
- [ ] First draft complete
|
|
||||||
- [ ] Word count target met
|
|
||||||
- [ ] POV consistent
|
|
||||||
- [ ] Character voices distinct
|
|
||||||
- [ ] Setting details accurate
|
|
||||||
- [ ] Conflict resolved/advanced
|
|
||||||
- [ ] Ready for revision
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Created:** | **Updated:**
|
|
||||||
|
|||||||
+5
-121
@@ -1,129 +1,13 @@
|
|||||||
---
|
---
|
||||||
id: scene_03_01
|
title: "Scene 3"
|
||||||
title: "Chapter Three: Climax"
|
slug: "scene-3"
|
||||||
slug: chapter-three-climax
|
|
||||||
act: 3
|
act: 3
|
||||||
chapter: 1
|
chapter: 3
|
||||||
scene: 1
|
scene: 3
|
||||||
type: chapter
|
type: scene
|
||||||
status: draft
|
status: draft
|
||||||
word_count_target: 2000
|
word_count_target: 2000
|
||||||
word_count_min: 1800
|
|
||||||
word_count_max: 2200
|
|
||||||
pov: ""
|
pov: ""
|
||||||
characters: []
|
|
||||||
setting: ""
|
|
||||||
date: ""
|
|
||||||
time: ""
|
|
||||||
conflict: ""
|
|
||||||
theme: ""
|
|
||||||
tone: ""
|
|
||||||
word_count: 0
|
|
||||||
word_count_updated: ""
|
|
||||||
session_word_count: 0
|
|
||||||
last_session: ""
|
|
||||||
total_sessions: 0
|
|
||||||
version: 1
|
|
||||||
created: ""
|
|
||||||
updated: ""
|
|
||||||
planning_template: ""
|
|
||||||
draft_file: ""
|
|
||||||
order: 1
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Chapter Three: Climax
|
|
||||||
|
|
||||||
**Draft File** | **ID:** scene_03_01 | **Status:** Draft
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Scene Metadata
|
|
||||||
|
|
||||||
| Attribute | Value |
|
|
||||||
|-----------|-------|
|
|
||||||
| **Act** | 3 |
|
|
||||||
| **Chapter** | 1 |
|
|
||||||
| **Scene** | 1 |
|
|
||||||
| **Type** | Chapter |
|
|
||||||
| **POV** | Not set |
|
|
||||||
| **Version** | 1 |
|
|
||||||
| **Word Count Target** | 2000 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Word Count Tracking
|
|
||||||
|
|
||||||
| Metric | Value |
|
|
||||||
|--------|-------|
|
|
||||||
| **Target** | 2000 |
|
|
||||||
| **Minimum** | 1800 |
|
|
||||||
| **Maximum** | 2200 |
|
|
||||||
| **Current** | 0 |
|
|
||||||
| **This Session** | 0 |
|
|
||||||
| **Last Updated** | Never |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Creative Elements
|
|
||||||
|
|
||||||
### POV
|
|
||||||
> Not specified
|
|
||||||
|
|
||||||
### Characters
|
|
||||||
|
|
||||||
### Setting
|
|
||||||
> Not specified
|
|
||||||
|
|
||||||
### Date & Time
|
|
||||||
> Not specified | Not specified
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Story Structure
|
|
||||||
|
|
||||||
### Conflict
|
|
||||||
> Not specified
|
|
||||||
|
|
||||||
### Theme
|
|
||||||
> Not specified
|
|
||||||
|
|
||||||
### Tone
|
|
||||||
> Not specified
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Draft Content
|
|
||||||
|
|
||||||
### Rough Draft:
|
|
||||||
|
|
||||||
[Write your scene content here...]
|
[Write your scene content here...]
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Writing Notes
|
|
||||||
|
|
||||||
### Session Log
|
|
||||||
| Date | Words Added | Notes |
|
|
||||||
|------|--------------|-------|
|
|
||||||
|
|
||||||
|
|
||||||
### Revision Notes
|
|
||||||
-
|
|
||||||
-
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Status Checklist
|
|
||||||
|
|
||||||
- [ ] Planning complete
|
|
||||||
- [ ] First draft complete
|
|
||||||
- [ ] Word count target met
|
|
||||||
- [ ] POV consistent
|
|
||||||
- [ ] Character voices distinct
|
|
||||||
- [ ] Setting details accurate
|
|
||||||
- [ ] Conflict resolved/advanced
|
|
||||||
- [ ] Ready for revision
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Created:** | **Updated:**
|
|
||||||
|
|||||||
@@ -1,164 +1,21 @@
|
|||||||
---
|
---
|
||||||
# Merged Planning + Draft Template
|
# Basic metadata for your scene/chapter
|
||||||
# YAML frontmatter contains all planning fields; body is the draft content
|
# Everything after the closing --- is your draft content
|
||||||
|
|
||||||
id: scene_new
|
|
||||||
# Unique identifier for this scene
|
|
||||||
|
|
||||||
|
id: ""
|
||||||
title: "New Scene Title"
|
title: "New Scene Title"
|
||||||
# Display title for the scene
|
slug: ""
|
||||||
|
|
||||||
slug: new-scene-title
|
|
||||||
# URL-friendly slug
|
|
||||||
|
|
||||||
# === Core Metadata ===
|
|
||||||
act: 1
|
act: 1
|
||||||
chapter: 1
|
chapter: 1
|
||||||
scene: 99
|
scene: 1
|
||||||
type: scene
|
type: scene
|
||||||
# Type: scene, chapter, interlude, etc.
|
|
||||||
|
|
||||||
# === Planning Fields ===
|
|
||||||
status: draft
|
status: draft
|
||||||
# Status: planning, draft, revised, done
|
|
||||||
word_count_target: 2000
|
word_count_target: 2000
|
||||||
word_count_min: 1800
|
|
||||||
word_count_max: 2200
|
|
||||||
|
|
||||||
# === Creative Elements ===
|
|
||||||
pov: ""
|
pov: ""
|
||||||
# Point of view character
|
|
||||||
|
|
||||||
characters: []
|
characters: []
|
||||||
# Characters appearing in this scene (array of names)
|
|
||||||
|
|
||||||
setting: ""
|
setting: ""
|
||||||
# Location/setting
|
|
||||||
date: ""
|
date: ""
|
||||||
time: ""
|
time: ""
|
||||||
# Date and time of scene
|
|
||||||
|
|
||||||
conflict: ""
|
|
||||||
# Central conflict in this scene
|
|
||||||
theme: ""
|
|
||||||
# Themes explored
|
|
||||||
tone: ""
|
|
||||||
# Mood/tone
|
|
||||||
|
|
||||||
# === Draft Tracking ===
|
|
||||||
word_count: 0
|
|
||||||
word_count_updated: ""
|
|
||||||
session_word_count: 0
|
|
||||||
last_session: ""
|
|
||||||
total_sessions: 0
|
|
||||||
version: 1
|
|
||||||
|
|
||||||
# === Timestamps ===
|
|
||||||
created: ""
|
|
||||||
updated: ""
|
|
||||||
|
|
||||||
# === Linking ===
|
|
||||||
# These are kept for backward compatibility but are no longer needed
|
|
||||||
# since planning and draft are merged
|
|
||||||
planning_template: ""
|
|
||||||
draft_file: ""
|
|
||||||
order: 99
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# New Scene Title
|
|
||||||
|
|
||||||
**Scene File** | **ID:** scene_new | **Status:** Draft
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Scene Metadata
|
|
||||||
|
|
||||||
| Attribute | Value |
|
|
||||||
|-----------|-------|
|
|
||||||
| **Act** | 1 |
|
|
||||||
| **Chapter** | 1 |
|
|
||||||
| **Scene** | 99 |
|
|
||||||
| **Type** | Scene |
|
|
||||||
| **POV** | Not set |
|
|
||||||
| **Version** | 1 |
|
|
||||||
| **Word Count Target** | 2000 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Word Count Tracking
|
|
||||||
|
|
||||||
| Metric | Value |
|
|
||||||
|--------|-------|
|
|
||||||
| **Target** | 2000 |
|
|
||||||
| **Minimum** | 1800 |
|
|
||||||
| **Maximum** | 2200 |
|
|
||||||
| **Current** | 0 |
|
|
||||||
| **This Session** | 0 |
|
|
||||||
| **Last Updated** | Never |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Creative Elements
|
|
||||||
|
|
||||||
### POV
|
|
||||||
> Not specified
|
|
||||||
|
|
||||||
### Characters
|
|
||||||
|
|
||||||
### Setting
|
|
||||||
> Not specified
|
|
||||||
|
|
||||||
### Date & Time
|
|
||||||
> Not specified | Not specified
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Story Structure
|
|
||||||
|
|
||||||
### Conflict
|
|
||||||
> Not specified
|
|
||||||
|
|
||||||
### Theme
|
|
||||||
> Not specified
|
|
||||||
|
|
||||||
### Tone
|
|
||||||
> Not specified
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Draft Content
|
|
||||||
|
|
||||||
### Rough Draft:
|
|
||||||
|
|
||||||
[Write your scene content here...]
|
[Write your scene content here...]
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Writing Notes
|
|
||||||
|
|
||||||
### Session Log
|
|
||||||
| Date | Words Added | Notes |
|
|
||||||
|------|--------------|-------|
|
|
||||||
|
|
||||||
|
|
||||||
### Revision Notes
|
|
||||||
-
|
|
||||||
-
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Status Checklist
|
|
||||||
|
|
||||||
- [ ] Planning complete
|
|
||||||
- [ ] First draft complete
|
|
||||||
- [ ] Word count target met
|
|
||||||
- [ ] POV consistent
|
|
||||||
- [ ] Character voices distinct
|
|
||||||
- [ ] Setting details accurate
|
|
||||||
- [ ] Conflict resolved/advanced
|
|
||||||
- [ ] Ready for revision
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Created:** | **Updated:**
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user