# Scribe of Lagash - Visualization ![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg) ![Minimum Obsidian version](https://img.shields.io/badge/obsidian-%E2%89%A51.10.0-8b6cef) An [Obsidian](https://obsidian.md) plugin that helps writers visualize their chapters and scenes. It is part of the **Scribe of Lagash** series, a set of independent, focused tools for planning and writing stories in Obsidian. This plugin doesn't own your prose: chapters and scenes are just regular notes in your vault. Point the plugin at the folder that holds a story and it recognizes chapters and scenes from their titles ("Chapter 1", "Scene II", "Prologue", …); a little `scribe-note-*` frontmatter is optional and only needed to override the title or add detail. You can see the story in three views: **StoryLines**, where you arrange the cards on your own lines, and the read-only **Characters** and **Locations** views, which group the same chapters and scenes by who appears and where they happen. It never edits a note you already wrote. What it does write is limited to: a per-story **StoryLines file** that stores the lines and card positions of the StoryLines view; the optional **Story Outline file**, created as an empty table when you ask for it (and filled from your notes only if you run **Generate Story Outline from notes** while it is still empty); and a new chapter or scene note when you click a planned card from the outline. The Characters, Locations and Chronological views are derived from your notes and save nothing. ## Installation Requires Obsidian ≥1.10.0. You can install it from the [community plugins page](https://community.obsidian.md/plugins/scribe-of-lagash-visualization), or download `main.js`, `manifest.json`, and `styles.css` from the [latest release](https://github.com/aescanes/scribe-of-lagash-visualization/releases/latest) and copy them into `/.obsidian/plugins/scribe-of-lagash-visualization/`. ## Scribe of Lagash Plugins **Scribe of Lagash** is a series of independent, single-concern Obsidian plugins for planning and writing stories: - **[Scribe of Lagash - Visualization](https://community.obsidian.md/plugins/scribe-of-lagash-visualization)** ([github repository](https://github.com/aescanes/scribe-of-lagash-visualization)) — helps writers visualize their chapters and scenes. - **[Scribe of Lagash - Goals and Stats](https://community.obsidian.md/plugins/scribe-of-lagash-goals-stats)** ([github repository](https://github.com/aescanes/scribe-of-lagash-goals-stats)) — helps writers set goals and see detailed statistics for their story. Each plugin works fine on its own, so each has its own **Story folder** setting and neither requires the other. They are designed to complement each other, though, and work best together: point both at the same story folder so the cards you see here and the counts and goals tracked in Goals and Stats describe the same notes. ### About the name "Scribe" is the writer at the center of every plugin in the series, and "Lagash" nods to _Nippur de Lagash_, the classic Argentine comic. ## Current features ### StoryLines view The story's default view (ribbon icon / "Open StoryLines" command). Horizontal, colored **lines** for a story, with each chapter/scene as a card sitting on a line. Pick "Create lines" the first time to seed a "Main line", then drag cards between lines or along the shared column grid — columns line up across every line by reading order, and you can leave deliberate gaps between cards. Dropping a card onto an occupied column nudges the others right. When a Story Outline is set up, an **Align cards to Story Outline** toolbar button snaps every card — real or planned — back to the outline's reading order and onto the line its row names. Add / rename / recolor / reorder / delete lines from the line headers. Changes save to the StoryLines file (`StoryLines.md`) automatically; `Mod+Z` undoes. StoryLines view ### What a card shows A card shows its title, folder breadcrumb, a short text, then the date, the characters and the locations (each on its own line, with a small icon), and the word count. The date, characters and locations come from the **note first**: its `scribe-note-date`, `scribe-note-characters` and `scribe-note-locations` frontmatter. If the note doesn't have one, the **Story Outline row** fills it in, and if neither has it nothing is shown. Planned cards show their row's. The Characters and Locations views leave their own field off the cards, since the lines already say it. The short text is the Story Outline's Synopsis; a note whose row has none shows the first paragraph of the note instead. ### Characters view The combo box at the top right of the StoryLines tab switches between the different views. Characters keeps the same look but is read-only: one line per character (alphabetical), each showing the chapters/scenes that name them in reading order, so a scene with two characters appears on both lines. Names come from the notes' `scribe-note-characters` frontmatter and the Story Outline's `Characters` column, merged; if a note and its outline row list different characters a ⚠ explains the difference. Planned (not yet written) rows show as dashed, non-clickable cards, and notes with no characters sit in a "No characters listed" strip. Nothing is moved, created or saved in this view, and each tab remembers its own view. Characters view ### Locations view The same read-only view, with one line per location. Names come from the notes' `scribe-note-locations` frontmatter and the Story Outline's `Locations` column, merged the same way (⚠ when they differ; a "No locations listed" strip for the rest). Locations view ### Chronological view The same read-only view, with a single line that orders every chapter and scene by its in-story date. The date comes from the note's `scribe-note-date` frontmatter or the Story Outline's `Date` column (if both exist the note wins, and a ⚠ explains a difference). The button next to the combo box switches between oldest first and newest first. Scenes with the same date keep their chapter/scene order; notes with no date, or a date that isn't recognized (⚠), sit in a "No date" strip. When two neighbouring cards are much further apart in time than usual (more than twice the average gap), a bold **…** marker takes a narrow slot between them; hover it to see how long passed (for example "about 10 years later"). Cards in every view show a readable date (`November 3, 2026`) whatever form you typed (in the **Title language**); the notes are never rewritten. Accepted dates (English and Spanish month names, full names only): | Form | Examples | |---|---| | Year / month / day (ISO) | `2026`, `2026-11`, `2026-11-03` | | Month and year | `November 2026`, `noviembre de 2026` | | Day, month and year | `November 3, 2026`, `3 November 2026`, `3 de noviembre de 2026` | A month followed by a single number is always a **year**, so a story without a real calendar can use small made-up years: `November 1`, `December 1`, `January 2` are November and December of year 1, then January of year 2. A day needs a year too (`November 3, 1`). A bare month, `03/01/2026`, abbreviations and impossible dates (`2026-02-30`) are not recognized. Real and made-up years in the same story simply sort by number. Chronological view ### Story Outline file _(optional)_ Name a file under **Story Outline file name** in settings and click **Create**; it's written as an empty Markdown table (Act / Chapter / Scene / Line / Synopsis / Characters / Locations). The `.md` extension is optional — `Outline` and `Outline.md` both create `(SL) Outline.md`. Fill it in to plan the story before the notes exist: rows with no matching note appear as dashed placeholder cards on the StoryLines view, and clicking one creates the note (title, Synopsis, frontmatter) on that line. Once a note exists its Synopsis shows on the card; a row that disagrees with the real note/folder gets a ⚠ marker — the files always win. The optional `Characters` and `Locations` columns (comma-separated names) feed the **Characters** and **Locations views**, including for notes not written yet. If you already have notes, the **Generate Story Outline from notes** command fills an empty outline from them; see [Generating the Story Outline from your notes](#generating-the-story-outline-from-your-notes). See [docs/feature-plans/outline-file-plan.md](docs/feature-plans/outline-file-plan.md). Story Outline file ## Planned next - a **Plot view**: a read-only matrix of the whole story, with one row per chapter/scene and one column per piece of information (Title, Status, Characters, Locations, Date, …), built from the notes' `scribe-note-*` frontmatter and the Story Outline. ## Setting up a story In the plugin settings, add the vault-relative folder that holds your story's notes under **Story folder**. The plugin scans that folder and classifies each note by its **title**: | Title looks like | Recognized as | | ---------------------------------------------- | ------------------------- | | `Chapter 1`, `Chapter IV`, `Ch. 12 — The Fall` | chapter (number 1, 4, 12) | | `Scene 2`, `Scene IX` | scene | | `Prologue`, `Epilogue`, `Interlude` | chapter (no number) | | anything else | ignored | Leave **Story folder** empty to scan the whole vault instead. ### Recognized title words The **Title language** setting picks which language's words the plugin looks for at the **start** of a note's title. The number may be digits or a roman numeral (`IV`), an optional `.` can follow the keyword, and matching is case-insensitive. English (`en`) and Spanish (`es`) ship today: | Meaning | English | Español | | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | ---------------------------------------------------------- | | Chapter _N_ | `Chapter N`, `Chap N`, `Ch N` | `Capítulo N`, `Capitulo N`, `Cap N` | | Scene _N_ | `Scene N`, `Sc N` | `Escena N`, `Esc N` | | Chapter, no number | `Prologue`, `Epilogue`, `Interlude`, `Foreword`, `Afterword`, `Preface` | `Prólogo`, `Epílogo`, `Interludio`, `Prefacio`, `Epígrafe` | | Act folder — the word the Story Outline file prepends to an `Act` cell when it builds a path | `Act` | `Acto` | So with **Title language** set to Español, `Cap. 3 — La caída` is chapter 3 and `Escena II` is scene 2. Adding a language is one more pattern table in [`src/data/titleParser.ts`](src/data/titleParser.ts) — nothing else changes. ### Story structure A note's place in the manuscript comes from **where it sits in folders**, not from frontmatter. Any of these layouts works — but pick **one per story**: | Layout | On disk (under the story folder) | | ----------------------------- | ---------------------------------------------------------------------- | | Chapters as files | `Chapter 1.md`, `Chapter 2.md`, … | | …grouped in acts | `Act I/Chapter 1.md`, `Act II/Chapter 5.md`, … | | Chapters as folders of scenes | `Chapter 1/Scene 1.md`, `Chapter 1/Scene 2.md`, … | | …grouped in acts | `Act I/Chapter 1/Scene 1.md`, … | | Scenes with no chapter | `Scene 1.md`, or `Act I/Scene 1.md` | | Deeper nesting | `Act I/Part 2/Chapter 3.md` — every folder just adds to the breadcrumb | A scene's chapter is simply its containing folder — there is no `parent` key. `Prologue.md` / `Epilogue.md` / `Interlude.md` can go anywhere; having no number, they sort after the numbered notes in the same folder. > **Don't mix "chapter as a file" and "chapter as a folder" in the same story.** > If you do, every file-chapter sorts before any folder-chapter's scenes. Pick > one style and convert the whole story to it. Manuscript order is then: folder path first (all of `Act I/…` before `Act II/…`; a numbered folder sorts by its number, so `Chapter 2/` comes before `Chapter 10/`), then the number in the title, then the title text. Sub-folders also show as a breadcrumb under each card — a note at `My Story/Act I/Chapter I/Scene 1.md` (story folder `My Story`) shows "Scene 1" with "Act I - Chapter I" underneath. ### Planning ahead with the Story Outline file Set **Story Outline file name** in settings (the `.md` is optional — `Outline` and `Outline.md` both mean `(SL) Outline.md`) and click **Create**. The plugin writes one `(SL) .md` per story folder with an empty table and a column guide. When you plan a story in the **Story Outline file** table before writing the notes, fill the columns that match your layout: | Layout | Columns to fill | The row's note | | ------------------------- | --------------------------- | ---------------------------- | | Chapters as files | `Chapter` | `Chapter 1.md` | | …grouped in acts | `Act` + `Chapter` | `Act I/Chapter 1.md` | | Scenes in chapter folders | `Chapter` + `Scene` | `Chapter 1/Scene 2.md` | | …grouped in acts | `Act` + `Chapter` + `Scene` | `Act I/Chapter 1/Scene 2.md` | | Scenes with no chapter | `Scene` (+ optional `Act`) | `Scene 2.md` | | Custom folder | `Folder` (overrides `Act`) | `/Chapter 1.md` | Numbers may be digits or roman numerals, optionally followed by free text — e.g. a `Chapter` cell of `1 - The beginning` plans `Chapter 1 - The beginning.md`, the same as if you'd typed that title directly. The `Act` / `Chapter` / `Scene` words and folder names follow the **Title language** setting. `Line` is a line name or id from the StoryLines file; `Synopsis` shows on the card and becomes the note body when you create it. List the people in a chapters/scenes under `Characters` (comma-separated, e.g. `Harry, Ron, Hermione`) to enable the **Characters view**: each name becomes a line showing the chapters/scenes it appears in. `Locations` does the same for the **Locations view**. Add a `Date` column (see the [Chronological view](#chronological-view) for the accepted forms) to place planned rows on the **Chronological view**. A row with neither a `Chapter` nor a `Scene` value is ignored, and un-numbered units (`Prologue`, …) can't be planned here — create those notes directly. The Story Outline file created for you repeats this guide below the table. ### Generating the Story Outline from your notes Already have chapters and scenes written? Instead of typing the outline by hand, run the **Generate Story Outline from notes** command from the command palette. It builds the table from the notes the plugin already recognizes in your story folder, so you can start planning from what exists. Before you run it, set **Story Outline file name** in settings (the command asks you to if you haven't). You don't need to click **Create** first: if the file doesn't exist yet, the command creates it. Then, for each recognized chapter or scene note, it writes one row: | Column | What the command fills in | | ---------- | --------------------------------------------------------------------------------------------------- | | `Folder` | The folders the note sits in under the story folder, e.g. `Act I` (empty for a note directly in it) | | `Chapter` | The chapter number (for a scene, read from its chapter folder's name) | | `Scene` | The scene number, for scene notes | | `Line` | The name of the line the note's card sits on in the StoryLines file, if it has been placed | | `Synopsis` | Left empty for you to fill in | Things to know: - **It never overwrites your work.** It only fills an outline whose table has no rows yet. If the file already has any row, the command leaves it untouched and tells you so. To regenerate, empty the table first. - **Un-numbered notes are skipped.** `Prologue`, `Epilogue` and the like have no number, so the table can't represent them; the notice tells you how many were skipped. - **The generated table uses the columns above.** It replaces the empty starter table, so the optional `Act`, `Characters` and `Locations` columns are not part of it (the act is already in `Folder`). Add those columns back by hand if you want them. Any text around the table, such as the column guide, is kept. - **It doesn't change your notes or the StoryLines file.** Because every row matches a note that already exists, the outline shows no placeholder cards until you add new rows. ### Optional frontmatter None of this is required — it only adds detail the cards can show: ```yaml --- scribe-note-date: 1901-03-04 # in-story date, any free-form string scribe-note-characters: [Alice, Bob] scribe-note-locations: [Riverside Tavern] scribe-note-status: draft --- ``` ## Development ```bash npm install npm run prepare # activate the Husky pre-commit hook (once; npm install skips it because ignore-scripts=true) npm run dev # watch build, outputs main.js npm run build # type-check + production build npm test # unit tests (Node's built-in runner; no test framework dependency) npm run validate # typecheck + test + lint + lint:css (run before every commit) ``` ### Supply-chain safety - Every dependency in `package.json` is pinned to an exact version — no `^`/`~` ranges and no `latest`. `.npmrc` sets `save-exact=true` so future `npm install ` additions stay pinned by default. - `.npmrc` also sets `ignore-scripts=true`, so `npm install`/`npm ci` never runs a dependency's `preinstall`/`install`/`postinstall` script automatically. The only dependency that ships one is `esbuild`, and its script just optimizes linking its already-installed platform binary — the build works fine without it. On the rare platform where it doesn't (e.g. an environment without a matching prebuilt `@esbuild/*` package), run `npm run rebuild:esbuild` to explicitly and visibly opt that one script back in for that single command. To try the plugin in a vault, copy (or symlink) `manifest.json`, `main.js`, and `styles.css` into `/.obsidian/plugins/scribe-of-lagash-visualization/`, then enable it from Obsidian's Community Plugins settings. ## Contributing Found a bug, or have a feature request? Open one on the [GitHub Issues page](https://github.com/aescanes/scribe-of-lagash-visualization/issues) — there's a template for each. Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for dev setup, code conventions, and the PR process. This project follows a [Code of Conduct](CODE_OF_CONDUCT.md). Found a security issue? See [SECURITY.md](SECURITY.md) instead of opening a public issue. See [CHANGELOG.md](CHANGELOG.md) for release history. ## License [MIT](LICENSE).