thewintershadow76 downloadsCapture plot points and campaign notes as plain markdown, then connect and group them on an interactive graph.
An Obsidian plugin for quickly capturing plot points and campaign notes. Each one is a plain markdown note. Afterwards you can arrange, connect, and group them on an interactive graph.
type. Click or tap a card to open the real note. Hovering a card highlights what it's connected to.[[wikilinks]] in note bodies are shown automatically as dashed edges.Notes live in one configurable folder (default Plots/), in any arrangement of subfolders you like — a subfolder is a group, so Plots/Act 1/Heist/The blueprints.md shows up as a card in a "Heist" box nested inside an "Act 1" box. A note directly in Plots/ is ungrouped.
---
blurb: "The vault is already empty when the crew breaks in" # node label; falls back to the filename
type: fiction # optional; the graph can filter by it
connects_to: ["[[Someone got there first]]"] # optional; manual edges, as wikilinks
x: 120 # written by the plugin when you move a node
y: 340
---
The note body is yours. The plugin never modifies it.
Every field is optional. A note with no frontmatter still shows up, labelled with its filename. type can be any value you like, for example fiction, campaign, or worldbuilding.
Upgrading from a version before groups were folders? The old
group:frontmatter field is no longer read. Move those notes into a matching subfolder (drag them in Obsidian's file explorer, or use "Move to group…" on the graph) and delete the now-unusedgroup:line whenever you're touching that note next — it's otherwise harmless to leave behind.
| Action | Desktop | Touch (iPad) |
|---|---|---|
| Open the graph | Ribbon icon or Story Web: Open graph | same |
| New plot point | + New on the graph, Story Web: New plot point (bind a hotkey in Settings → Hotkeys), or the lightbulb ribbon icon | same |
| Open a note | Click (Cmd/Ctrl-click opens a new tab) | Tap |
| Move a node | Drag | Drag |
| Pan / zoom | Drag background / scroll wheel, or the zoom buttons bottom right | One-finger drag / pinch, or the zoom buttons |
| Fit everything in view | The frame button under the zoom buttons | same |
| Connect | Connect (top right), then click the note to start from, then the note it leads to | same, with taps. Or long-press a note → Connect from here… |
| Delete a manual connection | Select the edge, then press Delete, or right-click it | Long-press the edge |
| Add/move to a group | Right-click a note → Add to group… / Move to group… — moves the file | Long-press a note |
| Remove from its group | Right-click a note → Remove from "…" — moves the file back to the root | Long-press a note |
| Rename a group | Right-click the group box → Rename group… — renames the folder | Long-press the group box |
| Ungroup | Right-click the group box → Ungroup — moves its contents up one level, deletes the folder | Long-press the group box |
| Leave connect mode | Done (the Connect button while active), or Esc | Done |
Clicking a note reuses an open note pane when there is one, so the graph stays put. A good setup is to keep the graph in one split and your notes in the other.
The first time you open the graph, notes without a position are laid out automatically, and those positions are saved. After that, new notes appear in the middle of what you're looking at, or next to their group.
type added to notes created with quick capture.npm install
npm run dev # watch build; also copies into test-vault/.obsidian/plugins/story-web
npm run lint # the same eslint-plugin-obsidianmd rules the community directory's review runs
npm test # unit tests (pure model + frontmatter logic)
npm run build # typecheck + production bundle → main.js
Open test-vault/ as a vault in Obsidian, enable community plugins, and Story Web loads with some sample notes. If you also install the Hot Reload plugin in that vault, it reloads Story Web on every rebuild.
Don't point dev builds at a vault you care about until you've tested them. Use STORY_WEB_VAULT_PLUGIN_DIR to change where dev builds are copied.
npm version patch # or minor / major: bumps package.json, manifest.json, versions.json and tags (no "v" prefix)
git push --follow-tags
The Release workflow builds from the tag and publishes a GitHub release with main.js, manifest.json and styles.css attached, which is what Obsidian installs from. It refuses to publish if the tag doesn't match manifest.json. Each release also gets a signed build-provenance attestation, so anyone can check the files were built from this repo by CI:
gh attestation verify main.js --repo TheWinterShadow/story-web
Copy main.js, manifest.json, and styles.css into <vault>/.obsidian/plugins/story-web/ on a vault that syncs to the iPad, then enable the plugin there. Or install it with BRAT (a plugin for installing betas from GitHub) using TheWinterShadow/story-web.
src/
main.ts plugin entry: commands, ribbon, settings, view registration
view.ts ItemView + Cytoscape: rendering, diffing, gestures, menus
store.ts the only module that touches the vault (reads cache, writes frontmatter, moves/renames files for groups)
model.ts pure: notes → nodes/edges/group tree (+ type filter)
frontmatter.ts pure: frontmatter mutations used inside processFrontMatter
folders.ts pure: group-path arithmetic (a group is a folder — see model.ts + store.ts)
links.ts pure: parsing `connects_to` wikilinks
theme.ts visual design: theme colours → Cytoscape stylesheet, type colours
wrap.ts pure: word-wrapping blurbs into card lines
layout.ts pure: initial column layout
modals.ts quick capture + text prompt
settings.ts settings + tab
tests/ vitest suites for the pure modules
docs/DECISIONS.md why things are the way they are
The view and store depend on Obsidian and can only be tested manually in the test vault. Everything that decides what to render or what to write is kept in the pure modules, so it can be unit tested.