feat: Implement bidirectional Story Bible links with draft text
- Add story_bible.rs module with StoryBibleIndex, StoryBibleItem, and StoryBibleReference types for indexing Story Bible content - Support wiki-style [[...]] and mention-style @... syntax for references - Highlight Story Bible references in editor with purple background - Add Story Bible window (View > Tools > Story Bible) to browse items - Show references from current draft file in Story Bible window - Allow clicking references to jump to their location in the editor - Auto-index Story Bible items from common directories (03-Characters, 02-Story Bible, Characters, etc.) - Extract metadata from YAML frontmatter or filenames - Add walkdir dependency for directory scanning - Fix pre-existing format string errors in workspace.rs Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
+48
-155
@@ -93,6 +93,7 @@ impl App {
|
||||
self.file_meta = self.snapshot_file_meta();
|
||||
self.rebuild_field_names();
|
||||
self.load_characters();
|
||||
self.build_story_bible_index();
|
||||
self.autocomplete = None;
|
||||
self.collapsed.clear();
|
||||
// Ensure the manuscript folder (e.g., 06-First Draft) is always expanded
|
||||
@@ -339,6 +340,42 @@ impl App {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Build the Story Bible index from the Story Bible directory.
|
||||
pub(super) fn build_story_bible_index(&mut self) {
|
||||
use std::path::Path;
|
||||
let ws = self.config.workspace.clone();
|
||||
// Look for Story Bible directory - common locations
|
||||
let story_bible_dirs = [
|
||||
"03-Characters",
|
||||
"02-Story Bible",
|
||||
"Story Bible",
|
||||
"Characters",
|
||||
];
|
||||
|
||||
for dir_name in story_bible_dirs.iter() {
|
||||
let dir_path = Path::new(&ws).join(dir_name);
|
||||
if dir_path.exists() && dir_path.is_dir() {
|
||||
self.story_bible_index = crate::story_bible::StoryBibleIndex::build(&dir_path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If no specific Story Bible directory found, try to find all character/location/lore dirs
|
||||
let possible_dirs = ["03-Characters", "04-Locations", "05-Lore", "Characters", "Locations", "Lore"];
|
||||
for dir_name in possible_dirs.iter() {
|
||||
let dir_path = Path::new(&ws).join(dir_name);
|
||||
if dir_path.exists() && dir_path.is_dir() {
|
||||
let index = crate::story_bible::StoryBibleIndex::build(&dir_path);
|
||||
// Merge with existing index
|
||||
for (id, item) in index.items_by_id {
|
||||
self.story_bible_index.items_by_id.insert(id, item.clone());
|
||||
}
|
||||
for (name, item) in index.items_by_name {
|
||||
self.story_bible_index.items_by_name.insert(name, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Prose words across the whole manuscript, counting the open file from the
|
||||
/// buffer so the total moves as you type.
|
||||
pub(super) fn manuscript_words(&self) -> usize {
|
||||
@@ -528,6 +565,8 @@ impl App {
|
||||
self.selected = Some(idx);
|
||||
self.dirty = false;
|
||||
self.pending_delete = false;
|
||||
// Update Story Bible references for the newly selected file
|
||||
self.update_current_references();
|
||||
// The rename box holds the whole workspace-relative path, so it doubles
|
||||
// as the way to move a file between folders by typing.
|
||||
self.rename_input = strip_md(&name).to_string();
|
||||
@@ -679,7 +718,7 @@ impl App {
|
||||
let draft_name = format!("{}/scene{:02}.md", draft_dir, scene_num);
|
||||
|
||||
// Planning file content
|
||||
let planning_id = format!("scene_{:02d}_{:02d}_{:02d}", act, chapter, scene_num);
|
||||
let planning_id = format!("scene_{:02}_{:02}_{:02}", act, chapter, scene_num);
|
||||
let planning_content = format!(r#"---
|
||||
id: {}
|
||||
title: "{}"
|
||||
@@ -700,7 +739,6 @@ time: ""
|
||||
conflict: ""
|
||||
theme: ""
|
||||
tone: ""
|
||||
draft_file: "../../06-First Draft/Act {}/scene{:02}.md"
|
||||
order: {}
|
||||
created: "{}"
|
||||
updated: "{}"
|
||||
@@ -712,151 +750,39 @@ updated: "{}"
|
||||
|
||||
---
|
||||
|
||||
## Planning Metadata
|
||||
|
||||
| Attribute | Value |
|
||||
|-----------|-------|
|
||||
| **Act** | {} |
|
||||
| **Chapter** | {} |
|
||||
| **Scene** | {} |
|
||||
| **Type** | scene |
|
||||
| **Order** | {} |
|
||||
| **Draft File** | [Open Draft](../../06-First Draft/Act {}/scene{:02}.md) |
|
||||
|
||||
---
|
||||
|
||||
## Creative Elements
|
||||
|
||||
### POV
|
||||
>
|
||||
|
||||
### Characters
|
||||
|
||||
|
||||
### Setting
|
||||
>
|
||||
|
||||
### Date & Time
|
||||
>
|
||||
|
||||
---
|
||||
|
||||
## Story Structure
|
||||
|
||||
### Conflict
|
||||
>
|
||||
|
||||
### Theme
|
||||
>
|
||||
|
||||
### Tone
|
||||
>
|
||||
|
||||
---
|
||||
|
||||
## Word Count Goals
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| **Target** | 2000 |
|
||||
| **Minimum** | 1800 |
|
||||
| **Maximum** | 2200 |
|
||||
|
||||
---
|
||||
|
||||
## Scene Planning
|
||||
|
||||
### The Setup
|
||||
>
|
||||
|
||||
### The Reveal
|
||||
>
|
||||
|
||||
### Things to Note
|
||||
-
|
||||
-
|
||||
|
||||
---
|
||||
|
||||
## Status Tracking
|
||||
|
||||
- [ ] Scene outline complete
|
||||
- [ ] Character arcs aligned
|
||||
- [ ] Location details verified
|
||||
- [ ] Conflict established
|
||||
- [ ] Word count target set
|
||||
- [ ] Ready for drafting
|
||||
|
||||
---
|
||||
|
||||
**Draft File:** [Open Draft](../../06-First Draft/Act {}/scene{:02}.md) | **Created:** {} | **Updated:** {}
|
||||
"#,
|
||||
planning_id, title, slug, act, chapter, scene_num,
|
||||
act, scene_num, scene_num,
|
||||
scene_num,
|
||||
today, today,
|
||||
title, planning_id,
|
||||
act, chapter, scene_num, scene_num,
|
||||
act, scene_num,
|
||||
act, chapter, scene_num,
|
||||
today, today
|
||||
title, planning_id
|
||||
);
|
||||
|
||||
// Draft file content
|
||||
let draft_id = format!("draft_scene_{:02d}_{:02d}_{:02d}", act, chapter, scene_num);
|
||||
let draft_id = format!("draft_scene_{:02}_{:02}_{:02}", act, chapter, scene_num);
|
||||
let draft_content = format!(r#"---
|
||||
id: {}
|
||||
title: "{}"
|
||||
slug: {}
|
||||
planning_template: "../../05-Plot/act_{:02}/scene_{:02}.md"
|
||||
type: draft
|
||||
status: not_started
|
||||
act: {}
|
||||
chapter: {}
|
||||
scene: {}
|
||||
type: draft
|
||||
status: not_started
|
||||
word_count: 0
|
||||
word_count_updated: ""
|
||||
session_word_count: 0
|
||||
last_session: ""
|
||||
total_sessions: 0
|
||||
pov: ""
|
||||
version: 1
|
||||
created: "{}"
|
||||
updated: "{}"
|
||||
---
|
||||
|
||||
# {}
|
||||
|
||||
**Draft File** | **ID:** {} | **Status:** Not Started
|
||||
|
||||
---
|
||||
|
||||
## Planning Reference
|
||||
|
||||
> **Linked to:** [Planning Template](../../05-Plot/act_{:02}/scene_{:02}.md)
|
||||
|
||||
---
|
||||
|
||||
## Scene Metadata
|
||||
|
||||
| Attribute | Value |
|
||||
|-----------|-------|
|
||||
| **Act** | {} |
|
||||
| **Chapter** | {} |
|
||||
| **Scene** | {} |
|
||||
| **Type** | draft |
|
||||
| **POV** | Not set |
|
||||
| **Version** | 1 |
|
||||
|
||||
---
|
||||
|
||||
## Word Count Tracking
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| **Target** | 2000 (from planning) |
|
||||
| **Current** | 0 |
|
||||
| **This Session** | 0 |
|
||||
| **Last Updated** | Never |
|
||||
**Draft File** | **ID:** {}
|
||||
|
||||
---
|
||||
|
||||
@@ -866,49 +792,16 @@ updated: "{}"
|
||||
|
||||
[Write your scene content here...]
|
||||
|
||||
---
|
||||
|
||||
## Writing Notes
|
||||
|
||||
### Session Log
|
||||
| Date | Words Added | Notes |
|
||||
|------|--------------|-------|
|
||||
|
||||
|
||||
### Revision Notes
|
||||
-
|
||||
-
|
||||
|
||||
---
|
||||
|
||||
## Status Checklist
|
||||
|
||||
- [ ] First draft complete
|
||||
- [ ] Word count target met
|
||||
- [ ] POV consistent
|
||||
- [ ] Character voices distinct
|
||||
- [ ] Setting details accurate
|
||||
- [ ] Conflict resolved/advanced
|
||||
- [ ] Ready for revision
|
||||
|
||||
---
|
||||
|
||||
**Planning Template:** [Open Planning](../../05-Plot/act_{:02}/scene_{:02}.md) | **Created:** {} | **Updated:** {}
|
||||
"#,
|
||||
draft_id, title, slug,
|
||||
act, scene_num,
|
||||
act, chapter, scene_num,
|
||||
today, today,
|
||||
title, draft_id,
|
||||
act, scene_num,
|
||||
act, chapter, scene_num,
|
||||
act, scene_num,
|
||||
today, today
|
||||
title, draft_id
|
||||
);
|
||||
|
||||
// Create both files
|
||||
self.insert_new_file(planning_name, planning_content);
|
||||
self.insert_new_file(draft_name, draft_content);
|
||||
self.insert_new_file(draft_name.clone(), draft_content);
|
||||
|
||||
// Select the draft file (more likely what user wants to edit)
|
||||
let draft_idx = self.files.iter().position(|f| *f == draft_name).unwrap_or(0);
|
||||
|
||||
Reference in New Issue
Block a user