From 229e2413c4c2866060e027eee061a80a4f0d8293 Mon Sep 17 00:00:00 2001 From: landon Date: Sat, 19 Sep 2026 11:32:56 -0500 Subject: [PATCH] Add summary: to character templates; fix two bugs that were hiding it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a summary: field (with a short explanatory comment) to 01_character_template.md, and fill the field in on the two shipped examples, 01_protagonist.md and 02_antagonist.md. Documented in the Story Bible README's "Adding a New Character" example and its new "Automatic Linking in the Draft" section, and in the main README's Story Bible docs. While wiring this up against the real bundled template rather than a hand-written test fixture, found two bugs that were silently defeating last session's auto-linking feature for every project made from that template: - extract_from_frontmatter() only read a title: field for an item's name, but every bundled Story Bible template — character, location, lore, object, all of them — writes name:, never title:. Every real item's display name (and therefore everything matched against it in prose) was falling back to a garbled, uncapitalized filename guess instead. Now name: is read (title: still works too, for a sheet that uses that word instead). - build_editor_job()'s fast path skipped straight to an unformatted run whenever there were no grammar/spelling issues and no active search — without checking whether there was a Story Bible reference to highlight. A name with no spelling issue of its own (which is most of them) never got its purple background, and with it no visible way to find the hover card that depends on it. Both were invisible in the unit tests added last session because they exercised find_references() directly against a hand-built index, never through StoryBibleIndex::build() against real files, and the manual smoke test happened to use a name ("Bixby") that the spell checker flagged anyway — which incidentally kept the fast path from short- circuiting. Added a disk-built-index test and a fast-path regression test so both stay caught. Co-Authored-By: Claude Sonnet 5 --- README.md | 36 ++++++---- src/app/editor.rs | 29 +++++++- src/story_bible.rs | 67 +++++++++++++++++-- .../main_characters/01_character_template.md | 6 +- .../main_characters/01_protagonist.md | 1 + .../main_characters/02_antagonist.md | 1 + .../03-Story-Bible/README.md | 12 ++++ 7 files changed, 134 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 0e123ab..d9565b3 100644 --- a/README.md +++ b/README.md @@ -412,35 +412,47 @@ it shows: The Story Bible window indexes all Story Bible items from your project's character, location, lore, and other reference directories. It supports: -* Automatic indexing from directories like `03-Characters`, `02-Story Bible`, - `Characters`, `Locations`, `Lore`, etc. -* Two reference syntaxes in your draft text: +* Automatic indexing from the first directory found: `03-Story-Bible` (the + bundled template's own folder), then `03-Characters`, `02-Story Bible`, + `Characters`, `Locations`, `Lore`, and so on. +* **Automatic linking in the draft — no markup required.** A name spelled and + capitalized the same way as an item's `name:` or an `aliases:` entry is + matched wherever it appears in ordinary prose ("**Bixby** stood at the + door"). Two explicit syntaxes still work too, and win any overlap with an + automatic match: - Wiki-style: `[[Character Name]]` or `[[John Doe]]` - Mention-style: `@CharacterName` or `@John` -* **Purple background highlighting** of Story Bible references in the editor +* **Purple background highlighting** of every match — automatic or explicit — + in the editor. Hovering one shows a card with the item's name, type, and a + summary (see below), and a link to open its full entry; the card stays put + while you move the pointer onto it to click that link. * Click items in the Story Bible window to see all references to them in the current file * Click reference buttons to jump directly to that location in the editor -* Metadata extraction from YAML frontmatter (id, title, type, **aliases** fields) or from filenames +* Metadata extraction from YAML frontmatter (id, name, type, **aliases**, + **summary** fields) or from filenames * Case-insensitive search and lookup * **Aliases support**: References match item IDs, names, **and aliases** for flexible linking Character sheets in the `Characters` folder are automatically indexed, and any -markdown file with YAML frontmatter containing `id:` and `title:` fields will be -included in the index. You can also add an `aliases:` field with alternative names -for a character or item: +markdown file with YAML frontmatter containing `id:` and `name:` fields will be +included in the index (`title:` is read too, if a sheet uses that word instead). +You can also add `aliases:` for alternative names, and `summary:` for the one-line +hover card: ```yaml --- id: john_smith -title: "John Smith" +name: "John Smith" type: character aliases: ["Johnny", "JS"] +summary: "A retired locksmith dragged back for one last job." --- ``` -With aliases defined, references like `[[Johnny]]` or `@JS` will match the "John Smith" -character entry. References in your draft text that match Story Bible items -by ID, name, or alias are highlighted and clickable. +With aliases defined, references like `[[Johnny]]`, `@JS`, or a bare "Johnny" in +prose will all match the "John Smith" character entry. Every reference in your +draft that matches a Story Bible item — by ID, name, or alias — is highlighted +and clickable, whether or not you typed any markup to get it there. ### Story Bible Editor (Edit ▸ Story Bible…) diff --git a/src/app/editor.rs b/src/app/editor.rs index 2a4acd9..1549bcb 100644 --- a/src/app/editor.rs +++ b/src/app/editor.rs @@ -489,7 +489,7 @@ pub(super) fn build_editor_job( let mut job = LayoutJob::default(); job.wrap.max_width = wrap_width; - if grammar.is_empty() && find.is_empty() { + if grammar.is_empty() && find.is_empty() && bible.is_empty() { job.append(text, 0.0, TextFormat::simple(font_id, color)); return job; } @@ -809,6 +809,33 @@ pub(super) fn toggle_line_comment( mod tests { use super::*; + #[test] + fn a_bible_only_reference_is_not_skipped_by_the_grammar_find_fast_path() { + // build_editor_job short-circuits to one plain run when there's + // nothing to highlight — a regression check that it doesn't take + // that shortcut when the only thing to highlight is a Story Bible + // mention (no grammar issues, no active search), which used to + // mean the purple background — and the hover card that depends on + // it being visible to find — never appeared for a name with no + // spelling issue of its own. + let text = "Protagonist stood at the door."; + let job = build_editor_job( + text, + 14.0, + egui::Color32::WHITE, + &[], + &[], + None, + &[(0, 11)], // "Protagonist" + f32::INFINITY, + ); + let highlighted = job + .sections + .iter() + .any(|s| s.format.background != egui::Color32::default()); + assert!(highlighted, "a bible-only reference must still be formatted"); + } + #[test] fn byte_to_char_is_the_inverse_of_char_to_byte() { // "café!" — é is 2 bytes, so byte and char indices diverge after it. diff --git a/src/story_bible.rs b/src/story_bible.rs index 236ddd7..975fef6 100644 --- a/src/story_bible.rs +++ b/src/story_bible.rs @@ -201,6 +201,7 @@ fn extract_from_frontmatter(relative_path: &str, content: &str) -> Option = None; + let mut title: Option = None; let mut id: Option = None; let mut item_type: Option = None; let mut aliases: Vec = Vec::new(); @@ -209,8 +210,13 @@ fn extract_from_frontmatter(relative_path: &str, content: &str) -> Option Option