7854a961a9
links, a sprint timer, placeholder scanning, corkboard, and a quick-capture inbox - Compile templates/snowflake into the binary via include_dir, so New project… never depends on files installed beside the executable; Settings ▸ Template becomes an optional external override, and a broken saved override now falls back to the bundled template instead of failing project generation. - Outline beats now link to a matching chapter for a one-click jump. - Sprint window: a daily whole-manuscript word goal plus a Pomodoro-style writing sprint timer, with a compact countdown in the status bar while in Distraction-free mode. - Placeholders window: find [TODO]/[TK]/[FIXME]/[XXX]/[NOTE]/[???] markers left in the draft and jump straight to any of them. - Snowflake progress strip in the file panel: a nudge-only readout of which snowflake stage folders actually have content. - Corkboard window: drag-reorderable synopsis cards, one per chapter. - Quick-capture popup (Ctrl+Shift+N): jot a stray idea straight into the project's Scratch Pad without losing your place in the draft. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1050 lines
52 KiB
Markdown
1050 lines
52 KiB
Markdown
# md-manuscript
|
||
|
||
A small desktop application for Debian that treats a directory of markdown files
|
||
as an ordered manuscript.
|
||
|
||
* **Left pane** — every `*.md` file in the workspace, as a collapsible folder
|
||
tree, in a manual order you set by **dragging the `⠿` handle** up and down.
|
||
Drag a file onto a folder to move it there. **Hover a file** to see a tooltip
|
||
built from its header — the `POV:` line and the `# Slug:` synopsis — a quick
|
||
at-a-glance summary of each chapter.
|
||
* **Right pane** — a markdown text editor for the selected file (optional live
|
||
preview via the *Preview* checkbox).
|
||
* **Order + contents are saved and version-controlled with git.** The order lives
|
||
in `order.json` inside the workspace, so it syncs along with the files. It
|
||
records each file's path, so subfolders travel with it.
|
||
* **Export** the whole manuscript as a single concatenated `.odt` file, with a
|
||
chapter heading before each file's content — or, for a screenplay project, as
|
||
a paginated Fountain-to-PDF script (see
|
||
[Screenplays](#screenplays-fountain)).
|
||
* **Start a new project** from a bundled template (**File ▸ New project…**),
|
||
which builds the whole folder structure and opens its drafting folder.
|
||
|
||
Built in Rust with [`egui`](https://github.com/emilk/egui)/`eframe`. The ODT
|
||
and PDF writers are both native (no `pandoc`/LibreOffice needed at runtime).
|
||
|
||
## Build
|
||
|
||
```sh
|
||
cargo build --release
|
||
# binary: target/release/md-manuscript
|
||
```
|
||
|
||
Requires a Rust toolchain. The GUI needs a normal desktop session (X11 or
|
||
Wayland) with OpenGL — the usual runtime libraries on Debian:
|
||
|
||
```sh
|
||
sudo apt install libgl1 libxkbcommon0 libwayland-client0 libx11-6
|
||
```
|
||
|
||
(Only needed to *run* the GUI; building does not require them.)
|
||
|
||
## Install
|
||
|
||
```sh
|
||
install -Dm755 target/release/md-manuscript ~/.local/bin/md-manuscript
|
||
```
|
||
|
||
That's it — the **File ▸ New project…** template is compiled into the binary,
|
||
so there's nothing else to install alongside it. If you want to use a
|
||
customized copy of the template instead of the bundled one, point
|
||
**Settings ▸ New project… ▸ Template** at a folder of your own containing a
|
||
`cookiecutter.json`; leave it blank to keep using the bundled template.
|
||
|
||
Optionally add a desktop launcher at
|
||
`~/.local/share/applications/md-manuscript.desktop`:
|
||
|
||
```ini
|
||
[Desktop Entry]
|
||
Type=Application
|
||
Name=md-manuscript
|
||
Exec=md-manuscript
|
||
Categories=Office;TextEditor;
|
||
Terminal=false
|
||
```
|
||
|
||
## Use
|
||
|
||
1. Set the **Workspace** path in the top bar (default: `~/Manuscript`) — type it,
|
||
or click **📂** to pick a folder in a native file browser — and press **Open**.
|
||
The directory is created if it does not exist.
|
||
2. Press **Init git** once to make the workspace a git repository. To sync with
|
||
another machine, point it at a remote through **Settings ▸ Git remote…**:
|
||
paste the repository's clone URL into the **origin** box and press **Set
|
||
origin**. The repository has to exist on the server already — this attaches
|
||
to it, it does not create it. Clearing the box and pressing Set detaches
|
||
again. The equivalent from a terminal, if you prefer:
|
||
```sh
|
||
cd ~/Manuscript
|
||
git remote add origin <url>
|
||
```
|
||
Either way the URL lives in the repository's own `.git/config`, not in this
|
||
app's settings, so every workspace keeps its own.
|
||
|
||
The same dialog can **make the repository for you**. Fill in the Gitea rows
|
||
under **Settings ▸ New project** (server, user, token — the token needs write
|
||
access to repositories), then give the repository a name and press **Create
|
||
and attach**. The app calls Gitea's API itself, then points `origin` at what
|
||
came back. A name that already exists is attached to rather than treated as
|
||
an error, so a second attempt after a failure does the right thing. Untick
|
||
**Attach over SSH** to use the HTTPS clone URL instead, which asks for a
|
||
credential on every push.
|
||
|
||
The SSH host comes from the **server URL you configured**, not from the URL
|
||
Gitea advertises. Gitea builds that from its own `SSH_DOMAIN`, which is
|
||
whatever it was told to announce — and a server announcing a public hostname
|
||
whose SSH port is only reachable on the LAN hands out a remote that connects
|
||
from nowhere, with the failure surfacing at the first push rather than at
|
||
creation. The server URL is known to work, because the API call just went
|
||
over it. The port Gitea reports is kept as-is. So on a tailnet, setting the
|
||
Gitea server to `http://100.x.y.z:4000` gets you
|
||
`ssh://git@100.x.y.z:2228/…`, which works on and off the LAN alike.
|
||
|
||
After that, the **⟳ Sync (git)** button commits all changes, `pull --rebase`s,
|
||
and pushes (setting the upstream on the first push). Until origin is set it
|
||
commits locally and says so in the log.
|
||
|
||
If you open a folder that is *not* itself a repository but sits inside one
|
||
(a parent directory is a git work tree), the app asks whether to **use the
|
||
enclosing repository**. Accept it to have **⟳ Sync** commit and push to that
|
||
repo; decline to leave the workspace on its own, where **Init git** creates a
|
||
separate repository in the folder.
|
||
3. Create files with **+ New** (type a name first — give it a path like
|
||
`part-1/ch-01` to nest it, see [Organising with folders](#organising-with-folders))
|
||
or **+ New from template**
|
||
(no name needed — see [New files from a template](#new-files-from-a-template)),
|
||
edit on the right, `Ctrl+S` (or the Save button)
|
||
to write to disk. Drag the `⠿` handles to reorder or to move between folders. Use the **Zoom** slider
|
||
above the editor to scale the editor text (0% = default; **Reset** returns to
|
||
it), and the **Contrast** slider to brighten dim editor text (0% = theme
|
||
default; higher pushes the text toward white in dark mode / black in light
|
||
mode). Text always wraps to the pane width, so there is no horizontal
|
||
scrolling. Inline markdown has keyboard shortcuts, applied to the current
|
||
selection (press again on wrapped text to remove the markers):
|
||
|
||
| Shortcut | Effect |
|
||
| --- | --- |
|
||
| `Ctrl/Cmd + B` | **bold** (`**…**`) |
|
||
| `Ctrl/Cmd + I` | *italic* (`*…*`) |
|
||
| `Ctrl/Cmd + E` | `inline code` |
|
||
| `Ctrl/Cmd + Shift + X` | ~~strikethrough~~ (`~~…~~`) |
|
||
| `Ctrl/Cmd + K` | link — `[selection](url)`, with `url` selected to replace |
|
||
| `Ctrl/Cmd + /` | comment the selected lines out (`<!-- … -->`), or take it back off |
|
||
|
||
`Ctrl/Cmd + /` is the odd one out: it works on whole lines, growing the
|
||
range out to line boundaries first, and it toggles the block off again
|
||
only when every non-blank line in it is already commented. Commented lines
|
||
stay in the file but drop out of the export and the word count, so this is
|
||
the way to shelve a paragraph without losing it. Blank lines in the range
|
||
are left as-is so paragraph breaks survive; pressing it on an empty line
|
||
opens `<!-- -->` with the caret inside.
|
||
|
||
Press **`Ctrl/Cmd + F`** (or **Edit ▸ Find / Replace…**) to open the
|
||
find/replace bar above the editor; **`Ctrl/Cmd + H`** opens it with the
|
||
replace field focused. Type a search term to shade every match in the editor
|
||
(the current one brighter); **Enter** / **Shift+Enter** (or **▼** / **▲**)
|
||
step through them, wrapping around, and scroll the match into view. **Aa**
|
||
toggles case sensitivity. **Replace** swaps the current match and advances;
|
||
**Replace all** swaps every match in the file at once. **Esc** closes the bar.
|
||
4. Set the **Chapter title** for the open file if you want to override the
|
||
auto-derived one (the field shows the automatic title as a hint). Leaving it
|
||
blank falls back to a `# Title:` line in the header, and if there is none, to
|
||
the chapter's **position in the left pane** followed by a period (`1.`, `2.`,
|
||
…). Tick **Zero-pad #** in the top bar to pad those numbered titles with
|
||
leading zeros to a uniform width (e.g. `03.` when there are 12 chapters).
|
||
Titles are saved to `titles.json` and sync with git.
|
||
5. Set the **Export** path (type it, or click **📂** to choose the `.odt` file in a
|
||
native save dialog) and press **Export ODT** to produce the concatenated
|
||
document. Each file becomes a chapter headed by its title, optionally followed
|
||
by a caption from its `# Slug:` line. **Every chapter starts on a new page**
|
||
(the first chapter excepted, so there is no blank leading page).
|
||
|
||
The status bar shows a live **word count** for the current file, plus the net
|
||
words added (or removed) to it since the current session started. The count
|
||
updates as you type, including unsaved edits.
|
||
|
||
### Per-file word-count targets
|
||
|
||
Give a file a target by adding a **`Word Count Target:`** line to its header (at
|
||
any heading level):
|
||
|
||
```markdown
|
||
## Word Count Target: 1500 - 2000
|
||
```
|
||
|
||
A **progress bar** then appears in two places, comparing the file's **prose**
|
||
word count (the body below the draft marker, so header metadata isn't counted)
|
||
against the target: **amber** while under the target, **green** once inside the
|
||
range, **blue** when over it.
|
||
|
||
* A **compact bar beside the file name** in the left pane, so you can scan every
|
||
chapter's progress at a glance (hover it for the exact `words / target`). It
|
||
updates live as you type in the open file, and reflects the last saved state
|
||
for the others.
|
||
* A larger, labelled bar in the **status bar** for the file you're currently
|
||
editing.
|
||
|
||
A single number (`## Word Count Target: 1800`) sets a point goal; ranges accept
|
||
`-`, `–`, `to`, and grouped digits (`1,500`). Like the other header lines, the
|
||
target is stripped from the exported document.
|
||
|
||
### YAML frontmatter for planning metadata
|
||
|
||
A scene keeps what it knows about itself in **YAML frontmatter** at the top of
|
||
the file, and its prose below. This is the form **+ New Scene** writes and the
|
||
form the snowflake template ships:
|
||
|
||
```markdown
|
||
---
|
||
title: "Chapter One: The Beginning"
|
||
slug: chapter-one-the-beginning
|
||
act: 1
|
||
chapter: 1
|
||
scene: 1
|
||
status: draft
|
||
pov: "Bixby"
|
||
characters: [Bixby, Mom]
|
||
setting: "The Old Reynold's House"
|
||
date: ""
|
||
time: ""
|
||
conflict: ""
|
||
theme: ""
|
||
tone: ""
|
||
word_count_target: 1800-2200
|
||
---
|
||
|
||
The prose begins here.
|
||
```
|
||
|
||
**Everything below the closing `---` is prose**, and nothing else: that is what
|
||
the status bar counts, what the progress bars measure, and what the exporter
|
||
writes into the `.odt`. Restating the header underneath it — a metadata table, a
|
||
POV section, a word-count tracker — puts all of that in the word count and in the
|
||
finished book, so the header is written once, above the line.
|
||
|
||
Fields the app reads: **`title`** (the chapter heading), **`slug`** (its
|
||
caption, and the file-list tooltip), **`pov`** (also the tooltip),
|
||
**`status`** (the badge and filter in the file panel), **`characters`** (which
|
||
scenes a character appears in, in the Characters window) and
|
||
**`word_count_target`** (the progress bars). A `word_count_min`/`word_count_max`
|
||
pair works too, and wins over `word_count_target` when both are given. The rest —
|
||
`act`, `chapter`, `scene`, `setting`, `date`, `time`, `conflict`, `theme`,
|
||
`tone` — the app carries but does not act on; they are yours, and anything else
|
||
you add is kept the same way.
|
||
|
||
Lists take either YAML form — `characters: [Bixby, Mom]` or
|
||
|
||
```yaml
|
||
characters:
|
||
- Bixby
|
||
- Mom
|
||
```
|
||
|
||
Values may be quoted or bare, an empty value reads as no value at all (an unset
|
||
`pov: ""` is not a POV), and `#` comments are ignored, so the template's notes
|
||
to itself cost nothing.
|
||
|
||
The frontmatter can be folded away while you write with **View ▸ Hide header in
|
||
editor**, the same as a legacy header.
|
||
|
||
**Legacy files still work.** A file with no frontmatter falls back to the older
|
||
form — `# Title:`, `# Slug:`, `# POV:` lines above a **`### Rough Draft:`**
|
||
marker, described under [Header stripping on export](#header-stripping-on-export).
|
||
The two are read through the same code, so a status filter or a character search
|
||
covers both. Frontmatter wins wherever a file has it.
|
||
|
||
## Project mode
|
||
|
||
Open a **project root** — a folder holding the manuscript alongside the
|
||
characters, outline, premise and the rest — and the app recognises the layout
|
||
instead of treating every file in it as a chapter.
|
||
|
||
The trigger is simple: if the folder you open contains a subfolder named like
|
||
the **manuscript folder** (`05-First Draft` by default), it is a project. A
|
||
plain folder of chapters has no such subfolder, so it behaves exactly as before
|
||
— everything in it is the manuscript.
|
||
|
||
In a project:
|
||
|
||
* **Files under the manuscript folder are the book.** They are ordered,
|
||
numbered, and they are what **Export ODT** concatenates. Chapter numbering
|
||
counts only them, so a defaulted title is `3.` because it is the third
|
||
*chapter*, not the third file in the project.
|
||
* **Everything else is reference** — character sheets, the outline, the scratch
|
||
pad — and is **not listed in the file panel**, which stays about the book.
|
||
Reference material is reached through the windows that understand it
|
||
([Characters](#characters-tools--characters), [Story Bible](#story-bible-tools--story-bible), [Outline](#outline-tools--outline)),
|
||
and it stays out of the order and out of the export.
|
||
|
||
Turn on **View ▸ Show reference files** to list it in the tree anyway, dimmed,
|
||
with the manuscript folder tagged **· manuscript**.
|
||
* `order.json`, `titles.json` and `wordlist.json` live at the **project root**,
|
||
so one word list covers the whole project rather than one folder of it.
|
||
* **File ▸ New project…** opens the project root, not the drafting folder.
|
||
|
||
Set which folder is the manuscript under **Settings ▸ New project… ▸ Manuscript
|
||
folder**. The number is optional — `First Draft` matches `05-First Draft` — and
|
||
the setting is global, so every project on this machine uses the same layout.
|
||
|
||
### Hidden folders
|
||
|
||
An archive of superseded drafts can hold hundreds of files that would swamp the
|
||
tree, so folders named like **`Archive`** are skipped by the scan entirely — the
|
||
same treatment `target/` and `node_modules/` already get. The match ignores a
|
||
leading number, so `10-Archive` is covered.
|
||
|
||
Change the list with `hidden_folders` in
|
||
`~/.config/md-manuscript/config.json`; set it to `[]` to see everything.
|
||
|
||
## Organising with folders
|
||
|
||
The file list is a tree. Any `*.md` file at or below the workspace is part of the
|
||
manuscript, so you can group a long book into `part-1/`, `part-2/`, or go further
|
||
and give a chapter its own folder of scenes — nesting goes eight levels deep.
|
||
|
||
* **Make a folder** by giving a new file a path: type `part-1/ch-01` beside
|
||
**+ New** and both the folder and the file appear. There are no standalone
|
||
empty folders — a folder exists for as long as it holds a file, and is cleaned
|
||
up automatically once its last file leaves.
|
||
* **Collapse or expand** a folder by clicking its header. The number beside the
|
||
name counts every file beneath it, at any depth.
|
||
* **Move a file** by dragging its `⠿` handle onto a folder header, or in among
|
||
another folder's files. The file really moves on disk, taking its chapter-title
|
||
override with it. Drop it on **↥ drop here for the top level** to bring it back
|
||
out of every folder.
|
||
* **Move by typing** instead, if you prefer: the **Rename** box holds the file's
|
||
whole path within the workspace, so editing the folder part of it moves the
|
||
file just as dragging does.
|
||
* **+ New from template** puts its `untitled-N.md` in the folder of the file you
|
||
are editing, so working inside a part doesn't scatter new files at the top.
|
||
* **Right-click a file** for a menu of the things you can do to it:
|
||
**📝 Rename…**, **⧉ Copy path**, **🗄 Archive** and **🗑 Delete…**. It acts on
|
||
the file you clicked — right-clicking selects it first — so you do not have to
|
||
select a file before reaching for the operation. Rename opens a small dialog
|
||
with the path already filled in and the box focused; it stays open if the name
|
||
is rejected, so a clash never costs you what you typed. Delete asks first.
|
||
These are the same operations as the buttons under the list, which stay where
|
||
they are.
|
||
|
||
Folders are skipped when they cannot hold manuscript prose: anything starting
|
||
with a `.` (so `.git` is never walked), plus `target/` and `node_modules/`.
|
||
|
||
### Folders and the manuscript order
|
||
|
||
Order is still yours to set by dragging, and still lives in `order.json` — as
|
||
paths (`part-1/ch-03.md`) rather than bare names. The one rule the app enforces
|
||
is that **a folder's files stay together**: a folder sits wherever its
|
||
earliest-ordered file puts it, and everything under it follows in a block.
|
||
|
||
That is what keeps the panel honest — **the export is the tree read top to
|
||
bottom**, so the order you see down the left is the order the chapters are
|
||
concatenated in, folders and all. Chapter numbering follows the same sequence,
|
||
so a defaulted title in `part-2/` continues counting from `part-1/`.
|
||
|
||
## Working on the novel's other parts
|
||
|
||
A manuscript project is more than its chapters. These read the rest of it.
|
||
|
||
### Distraction-free writing (Ctrl+D)
|
||
|
||
**Ctrl+D** folds away everything that is not the page — the toolbar and the file
|
||
list together — leaving the menu bar, the editor and the status line. Press it
|
||
again to bring both back. It is also **View ▸ 🖹 Distraction-free**.
|
||
|
||
The two panels toggle separately as well, from the pair of buttons at the right
|
||
of the menu bar or from **View ▸ Toolbar** and **View ▸ File list**:
|
||
|
||
| | Hides | Gives back |
|
||
|---|---|---|
|
||
| **⬆** / **⬇** | the Workspace, Export, Grammar and Spelling rows | about an inch of height |
|
||
| **⬅** / **➡** | the file list down the left | about 260px of width |
|
||
|
||
Both choices are remembered between runs, so the window opens the way you left
|
||
it. The toggles live in the menu bar rather than in the panels they hide, since
|
||
a control that hides a panel cannot sit inside it, and the menu bar is the one
|
||
strip that never goes away. The status bar is unaffected — the word counts stay
|
||
visible while you write.
|
||
|
||
The editor grows into whatever the panels give back rather than staying a fixed
|
||
height, so folding them is worth real writing space and not just grey margin.
|
||
|
||
### Hiding the header while you write
|
||
|
||
A scene's fields sit at the top of the same file as its prose, and once drafting
|
||
is under way they are mostly in the way. **View ▸ Hide header in editor** folds
|
||
them away, leaving the prose alone in the editor. A single line above the editor
|
||
says so — `▸ header hidden (15 fields)` — and clicking it flips back.
|
||
|
||
**Either kind of header folds**: a YAML frontmatter block, delimiters and all, or
|
||
a legacy block above the draft marker. That line is the quickest way to do it —
|
||
click `▾ header shown` to fold, click `▸ header hidden` to bring it back.
|
||
|
||
While the header is hidden it is genuinely **not in the editor's text**: the
|
||
file is put back together on save, so what lands on disk is unchanged. Doing it
|
||
that way rather than merely scrolling past the header means search, replace,
|
||
spelling underlines and the caret all address exactly what you can see, with no
|
||
offsets to go wrong. Everything that wants the whole file — the chapter title
|
||
hint, the word-count target, the file-list tooltip — still reads it whole.
|
||
|
||
A file with neither frontmatter nor a draft marker has nothing to fold, so the
|
||
line does not appear.
|
||
|
||
### Characters (Tools ▸ Characters…)
|
||
|
||
Reads the project's character sheets — a file whose level-1 heading is the name,
|
||
as either `# Bixby Reynolds` or `# Name: Jonathan Bronski`. For each character
|
||
it shows:
|
||
|
||
* their descriptor, from a `Slug:` field or the heading beneath the name;
|
||
* **which scenes they appear in**, by matching the `Characters:` line of every
|
||
manuscript file. A first name matches a full name, so `Characters: Bixby, Mom`
|
||
finds *Bixby Reynolds*. Click a scene to open it;
|
||
* **▦ Rebuild grid**, which regenerates the project's `character_grid.md` from
|
||
the sheets as a markdown table. A hand-kept grid drifts the moment a sheet
|
||
changes; this one cannot. Long values are trimmed, since a grid is a
|
||
quick-look summary.
|
||
|
||
### Story Bible (Tools ▸ Story Bible…)
|
||
|
||
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 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 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, 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 `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
|
||
name: "John Smith"
|
||
type: character
|
||
aliases: ["Johnny", "JS"]
|
||
summary: "A retired locksmith dragged back for one last job."
|
||
---
|
||
```
|
||
|
||
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…)
|
||
|
||
For a dedicated editing experience, use the **Story Bible breakout editor**. This
|
||
provides a focused workspace for managing your Story Bible items:
|
||
|
||
* **Tree view** (left 1/3 of window): Shows the complete Story Bible directory structure
|
||
with collapsible folders for Characters, Locations, Lore, and other categories
|
||
* **Editor** (right 2/3 of window): Full-featured text editor for the selected item
|
||
* **Auto-load**: Select any file from the tree to load it into the editor
|
||
* **Save**: Click **Save** or press `Ctrl+S` to write changes back to disk
|
||
* **Index updates**: Saving a file automatically rebuilds the Story Bible index
|
||
|
||
The editor is ideal for extended Story Bible work, allowing you to browse and edit
|
||
items without leaving the context of your reference material.
|
||
|
||
### Outline (Tools ▸ Outline…)
|
||
|
||
The snowflake outline files are scaffolds of HTML comments, one per beat
|
||
(`<!-- Midpoint: A major turning point. -->`). The app strips comments, so
|
||
these files otherwise render and export as *blank* — there is no way to see how
|
||
much of the outline exists.
|
||
|
||
This window reads the scaffold back: every beat it names, and whether prose has
|
||
been written under it, with a progress bar over the whole outline. A beat counts
|
||
as written when there is ordinary text between its prompt and the next one.
|
||
|
||
**Heading hierarchy is preserved**: markdown headings (`#`, `##`, `###`) in the
|
||
outline files create a nested tree structure in the Outline window. Beats under
|
||
the same heading are grouped together under a collapsible header showing the
|
||
progress for that section (`3/5` beats written, for example). This makes it easy
|
||
to navigate large outlines and see progress at every level of your story structure.
|
||
|
||
Beats generated by [the Mistral tool](#plot-beats-mistral) can be appended
|
||
straight into the matching act file with **📝 Append to act_0N.md**, which
|
||
appears when a run filled exactly one act. It appends under a dated comment
|
||
rather than replacing, because the prompts are the reason the file is worth
|
||
keeping.
|
||
|
||
### Auto-Snapshots (Edit ▸ Snapshots…)
|
||
|
||
Never lose your work again. Enable automatic git commits at regular word-count
|
||
intervals to create recovery points you can roll back to at any time.
|
||
|
||
* **Enable/Disable**: Turn auto-snapshotting on or off from the Snapshots window
|
||
* **Configurable threshold**: Set how many words between snapshots (default: 250)
|
||
* **Automatic commits**: Commits are created automatically when you cross a
|
||
threshold boundary (250, 500, 750, 1000, etc.)
|
||
* **Manual snapshots**: Click "Create Manual Snapshot" to force a commit at any time
|
||
* **Rollback**: Restore your entire workspace to any previous snapshot
|
||
* **Live status**: See current word count and words until next snapshot
|
||
|
||
Snapshots appear in the Snapshots window with:
|
||
- Word count at the time of the snapshot
|
||
- Timestamp
|
||
- Commit message
|
||
- Commit hash (shown on hover)
|
||
|
||
**Important**: This feature requires the workspace to be a git repository. Snapshots
|
||
are regular git commits with messages like "Auto-snapshot at N words", so they
|
||
integrate with your normal git workflow.
|
||
|
||
### Revision status
|
||
|
||
Give a file a `Status:` header line and the file panel shows a compact badge for
|
||
it (initials, so `Structural Edit` becomes `SE`) and offers a **Status** filter
|
||
above the list. The filter narrows the tree to one stage — every file you have
|
||
still to proofread, say.
|
||
|
||
### Archive
|
||
|
||
**🗄 Archive** moves the selected file into the project's archive folder,
|
||
keeping its place inside it (`Act 1/scene.md` → `10-Archive/Act 1/scene.md`).
|
||
It leaves the manuscript order and, since archive folders are
|
||
[hidden](#hidden-folders), the file panel — but stays on disk. Deleting is for
|
||
things you want gone; this is for things you have moved past.
|
||
|
||
### Changes since the last commit (Edit ▸ Changes…)
|
||
|
||
Diffs the open file against its committed version, coloured the way git colours
|
||
a diff. Revising is comparing, and the repository already holds the answer.
|
||
|
||
### Word counts
|
||
|
||
The status bar carries the open file's word count, its change this session, and
|
||
the **whole manuscript's total**. Reference files are not counted.
|
||
|
||
### Manuscript details (Settings ▸ Manuscript details…)
|
||
|
||
Title, author and contact details. The title and author are written into
|
||
exported `.odt` files as their document properties — what a word processor shows
|
||
under File ▸ Properties. A blank title uses the project folder's name. Exports
|
||
previously carried no metadata at all.
|
||
|
||
**Standard manuscript format** lays the export out the way an agent or an editor
|
||
expects a submission to arrive, which is not a matter of taste: a monospaced,
|
||
double-spaced page is a predictable ~250 words, and the margins leave somewhere
|
||
for their notes to go. Turning it on gives you
|
||
|
||
- 12pt Courier (readers substitute a metric-compatible font where it is absent),
|
||
double-spaced, with half-inch first-line indents and no blank line between
|
||
paragraphs — a blank line would read as a scene break;
|
||
- `Surname / Title / page` flush right on every page but the title page, with
|
||
the numbering starting at 1 on the first page of text;
|
||
- chapters opening about a third of the way down their page;
|
||
- `---` in your markdown rendered as the conventional centred `#` scene break;
|
||
- a submission title page: contact details in the top-left corner, an
|
||
approximate word count opposite them in the top-right, and the title a third
|
||
of the way down with the byline beneath it.
|
||
|
||
Margins are 1 inch on all four sides of US Letter either way — that part was
|
||
already standard before the option existed.
|
||
|
||
**Begin exports with a title page** puts a page of its own at the front of every
|
||
export: the title, the author beneath it, and the contact details beneath that,
|
||
with the first chapter starting on the page after (in standard manuscript
|
||
format it takes the submission layout described above instead). The contact box
|
||
is free-form
|
||
and laid out line for line as typed, so an address, an email and a phone number
|
||
each land where you put them — including the blank line you leave between them.
|
||
The option is off by default, and an export with nothing to put on the page
|
||
skips it rather than opening on a blank sheet.
|
||
|
||
For a chapters-plus-master export the title page goes on the master, not on each
|
||
chapter file.
|
||
|
||
### Screenplays (Fountain)
|
||
|
||
Tick **Screenplay (Fountain)** in Manuscript details to make the project a
|
||
screenplay. Two things change, and nothing else does:
|
||
|
||
- the draft below each file's marker is read as
|
||
[Fountain](https://fountain.io) rather than markdown, and
|
||
- **Export ODT** becomes **Export PDF**, writing a paginated screenplay instead
|
||
of a word-processor document.
|
||
|
||
The files stay ordinary `.md` files with the same editorial header
|
||
(`# Title:`, `# Slug:`, `### Rough Draft:`), they list and reorder in the left
|
||
pane the same way, and the header is stripped on export exactly as it is for
|
||
prose. One file per scene or sequence is the natural layout, but nothing
|
||
enforces it — the files are joined into one continuous script, because what
|
||
divides a screenplay is its scene headings, not its chapters.
|
||
|
||
Fountain needs no markup for the common elements; the shape of the line is
|
||
enough:
|
||
|
||
```
|
||
INT. REYNOLD'S HOUSE - KITCHEN - NIGHT
|
||
|
||
Rain on the window. Bixby stands at the sink with a plate in
|
||
each hand, not washing either of them.
|
||
|
||
BIXBY
|
||
(barely)
|
||
I said I'd do it.
|
||
|
||
MOM (O.S.)
|
||
That was Tuesday.
|
||
|
||
CUT TO:
|
||
```
|
||
|
||
A line starting `INT.`, `EXT.`, `EST.`, `INT./EXT.` or `I/E.` is a scene
|
||
heading. A line in capitals with something directly beneath it is a character
|
||
cue, and what follows is dialogue until the next blank line; a line in
|
||
parentheses inside that block is a parenthetical. A line in capitals ending
|
||
`TO:` with blank lines above and below is a transition. Everything else is
|
||
action, and consecutive lines are one paragraph.
|
||
|
||
Where the shape is not enough, force it: `.` for a scene heading, `!` for
|
||
action, `@` for a character cue, `>` for a transition. `> text <` centres a
|
||
line, `===` starts a new page, `~` marks a lyric. `*italic*`, `**bold**`,
|
||
`***both***` and `_underline_` work as they look. `#` sections, `=` synopses,
|
||
`[[notes]]` and `/* boneyard */` blocks are for you and never reach the page.
|
||
|
||
The PDF is the conventional layout, so a page reads as roughly a minute of
|
||
screen time: 12pt Courier on US Letter, 55 lines a page, scene headings and
|
||
action at 1.5″, dialogue at 2.5″, parentheticals at 3.1″, cues at 3.7″,
|
||
transitions flush to the 7.5″ right margin, and page numbers in the top-right
|
||
from the second page. A speech broken by a page boundary is marked `(MORE)` and
|
||
picked up under the cue again with `(CONT'D)`; a scene heading is never left
|
||
stranded at the foot of a page; a `^` cue sets the two speeches side by side.
|
||
Courier is one of the fonts every PDF reader is required to provide, so nothing
|
||
is embedded and the file stays small.
|
||
|
||
**Begin exports with a title page** works here too, giving the standard spec
|
||
title page: the title a third of the way down, the credit beneath it, and the
|
||
contact details at the foot. **Standard manuscript format** has no effect on a
|
||
screenplay — a script is always set in screenplay format.
|
||
|
||
### Exporting chapter files (File ▸ Export chapters + master)
|
||
|
||
Writes one `.odt` per chapter into a `chapters/` folder, plus an `.odm` master
|
||
document that links them — the shape the template's `Full Text.odm` implies.
|
||
|
||
**The per-chapter files are the reliable part**: they are ordinary documents and
|
||
open anywhere. The master is a shell with the title, the author (or the full
|
||
title page, if you asked for one) and one linked section per chapter; LibreOffice does not follow those links when it opens the file, so
|
||
treat it as a starting point to relink rather than as the assembled book. For a
|
||
single finished document, use **Export ODT**.
|
||
|
||
## New projects from templates
|
||
|
||
**File ▸ ✨ New project…** scaffolds a whole manuscript project from a
|
||
template and then opens it as the workspace, so you go from nothing to editing
|
||
chapters in one step.
|
||
|
||
The app uses **built-in MiniJinja templating** (no external Python cookiecutter
|
||
required) to render templates. A default "snowflake" template is compiled
|
||
into the binary itself — nothing has to be installed alongside it. Its source
|
||
lives in the `templates/snowflake/` folder of this repository, for anyone
|
||
building from source who wants to customise it (a rebuild picks up the
|
||
change); at runtime, point **Settings ▸ New project… ▸ Template** at a folder
|
||
of your own to use a customised copy instead, without rebuilding.
|
||
|
||
The dialog asks for a **name**, **author**, **description** and the **folder to
|
||
create it in** (prefilled with wherever you put the last one). Those are passed
|
||
to the template as the `project_name`, `author` and `description` variables.
|
||
Nothing is written until you press **Create**; generation runs in the background,
|
||
so a large template doesn't freeze the editor.
|
||
|
||
When it finishes, the app opens the project root as the workspace,
|
||
with the **`05-First Draft`** folder (where the snowflake template keeps the
|
||
chapter files in `Act 1/`, `Act 2/` and `Act 3/` subfolders) automatically
|
||
expanded in the file list. If the template didn't produce that folder, the
|
||
status bar notes that nothing is marked as the manuscript.
|
||
|
||
A folder only appears in the file panel once it holds a `.md` file — the
|
||
template's empty acts are held open with `.gitkeep`, which is not markdown, so
|
||
they show up as you add scenes to them.
|
||
|
||
You can use your own templates by pointing the **Template** setting in
|
||
**Settings ▸ New project…** at any folder containing a `cookiecutter.json` file.
|
||
The template should contain a subdirectory named with a template expression
|
||
(e.g., `{{ cookiecutter.project_name }}`) which becomes the project root after
|
||
rendering. Leave the setting blank (the default) to use the bundled template;
|
||
an override that stops resolving to a valid template (folder moved, deleted)
|
||
silently falls back to the bundled one rather than failing project creation.
|
||
|
||
### Settings ▸ New project…
|
||
|
||
| Setting | Meaning |
|
||
|---|---|
|
||
| **Template** | an external folder holding a `cookiecutter.json` to use instead of the bundled template. Blank (the default) uses the template compiled into the binary |
|
||
| **Manuscript folder** | which folder of a project holds the book (default `05-First Draft`). Its files are ordered, numbered and exported; the rest of the project is reference. A leading number is optional, so `First Draft` also matches `05-First Draft`. A generated project always opens at its root — this does not change which folder the workspace is |
|
||
| **Publish to Gitea after creation** | when enabled and Gitea credentials are configured below, a new repository will be created and the project pushed to it |
|
||
| **Gitea** | server URL, user and token, used to create repositories when publishing is enabled |
|
||
|
||
No external tools are required — templating is built into the application.
|
||
|
||
### Publishing to Gitea
|
||
|
||
When **Publish to Gitea after creation** is enabled and all three Gitea settings
|
||
(**Server URL**, **User**, **Token**) are filled in, the app will:
|
||
1. Initialize a git repository in the new project
|
||
2. Create a repository on the Gitea server
|
||
3. Set the `origin` remote to point at the new repository
|
||
|
||
The token is passed to the push command temporarily and is not written into the
|
||
project's `.git/config`. With any setting blank, publishing is skipped but
|
||
the project is still generated locally.
|
||
|
||
The token is stored in `config.json` in plain text, the same as the Mistral API
|
||
key.
|
||
|
||
This runs in the app itself — it used to be a Python hook inside the template,
|
||
and is not one any more — so it applies to projects made through **New project**
|
||
and nothing else, and it does nothing if the checkbox is off or any of the three
|
||
Gitea settings is blank. Whatever the server said is reported in the Git log
|
||
window.
|
||
|
||
**Settings ▸ Git remote… is the same thing for a workspace you already have.**
|
||
It calls the Gitea API directly, works on any folder rather than only a freshly
|
||
generated one, and reports what the server actually said (a rejected token, a
|
||
URL that is not a Gitea API, a name the server would not take).
|
||
|
||
## New files from a template
|
||
|
||
The file list has two create buttons:
|
||
|
||
| Button | Name | Contents |
|
||
|---|---|---|
|
||
| **+ New** | the name you type beside it, optionally with folders | `# <name>` and a blank line |
|
||
| **+ New from template** | auto-assigned `untitled-N.md` | the configured template |
|
||
| **+ New Scene** | `scene-NN.md` in the current act | a scene's frontmatter, described under [YAML frontmatter](#yaml-frontmatter-for-planning-metadata) |
|
||
|
||
**+ New from template** takes no typed name — it picks the first free
|
||
`untitled-N.md` (reusing a gap if you have deleted one) in the folder of the file
|
||
you are currently editing, seeds it from the template, adds it to the manuscript
|
||
order and selects it. Rename it later with
|
||
the **Rename** box, or fill in `title:` and let the chapter title come from that.
|
||
|
||
Edit the template under **Settings ▸ New-file template…**. The built-in default
|
||
is frontmatter, the same shape a scene uses:
|
||
|
||
```
|
||
---
|
||
title: {{name}}
|
||
slug:
|
||
status: draft
|
||
pov:
|
||
characters: []
|
||
word_count_target:
|
||
---
|
||
|
||
```
|
||
|
||
Three placeholders are expanded when the file is created:
|
||
|
||
| Placeholder | Expands to |
|
||
|---|---|
|
||
| `{{name}}` | the file stem, e.g. `untitled-3` |
|
||
| `{{marker}}` | the draft marker set in the top bar (empty by default) |
|
||
| `{{date}}` | today's date in UTC, as `YYYY-MM-DD` |
|
||
|
||
Anything else in double braces is left alone. `{{marker}}` is there for a
|
||
legacy-style template — using it rather than typing `### Rough Draft:` literally
|
||
keeps such a template working if you change the marker later; a frontmatter
|
||
template has no use for it. Emptying the box restores the built-in default, and **Reset to default**
|
||
does the same in one click.
|
||
|
||
The template is stored in `~/.config/md-manuscript/config.json`, so it is shared
|
||
by every workspace on the machine rather than committed with a manuscript.
|
||
|
||
## Spelling (offline)
|
||
|
||
Spelling is checked **live as you type**, entirely offline — no server, no
|
||
network. Misspelled words get a red underline; **right-click** one for a menu of
|
||
suggested corrections (click a suggestion to apply it). It uses Hunspell
|
||
dictionaries read by the pure-Rust [`spellbook`](https://crates.io/crates/spellbook)
|
||
crate, so the binary stays self-contained.
|
||
|
||
* **Two dictionaries are built in** — Canadian English (`en-CA`, the default)
|
||
and British English (`en-GB`). Pick one from the **Dictionary** dropdown on
|
||
the *Spelling* row of the top bar; the choice is remembered.
|
||
* **More languages** are discovered automatically from the usual Hunspell
|
||
folders (`/usr/share/hunspell`, `/usr/share/myspell`, …) and from a per-user
|
||
folder, `~/.config/md-manuscript/dictionaries/`. Drop a matching
|
||
`xx_YY.aff` + `xx_YY.dic` pair in there (e.g. from your distro's
|
||
`hunspell-de-de` package) and it appears in the dropdown.
|
||
* **Untick “Check as I type”** on the Spelling row to turn the underlines off.
|
||
* Code spans, fenced/indented code blocks and link targets are skipped, and
|
||
ALL-CAPS initialisms (ODT, HTTP) are left alone, to cut false positives.
|
||
|
||
When you run a **LanguageTool** check (below), its richer spelling-and-grammar
|
||
results take over the underlines until you next edit the text — at which point
|
||
the live offline checker resumes. In other words, LanguageTool is used when it's
|
||
available and current; the offline checker is the always-on default.
|
||
|
||
### The word list (names and invented terms)
|
||
|
||
No Hunspell dictionary knows your characters, so without somewhere to record
|
||
them a novel's cast is underlined on every line of every scene. **Settings ▸
|
||
Word list…** is that place.
|
||
|
||
* **Right-click an underlined word** in the editor and choose
|
||
**📗 Add “…” to the word list**. The underline goes immediately — there is no
|
||
wait for the next check.
|
||
* **👤 Add character names** reads the project's character sheets and lists
|
||
every name in them at once. It looks for a `Characters` folder (a numeric
|
||
prefix like `03-Characters` is fine) beside the workspace and up to three
|
||
levels above it, so it still finds them while you are editing in
|
||
`05-First Draft`. Both `# Bixby Reynolds` and `# Name: Jonathan Bronski`
|
||
headings are understood, and an unfilled template sheet (`# Name`,
|
||
`# Protagonist`) contributes nothing. Each part of a name is listed
|
||
separately, since prose says *Bixby* far more often than *Bixby Reynolds*.
|
||
* Words can also be **typed in** directly, and taken back out with **✖**.
|
||
|
||
Matching is case-insensitive, and a listed name covers its **possessive and
|
||
plural** — `Bixby` accepts `Bixby's`, `Bixbys` and `Bixbys'` — because the
|
||
tokenizer hands the checker `Bixby's` as a single word.
|
||
|
||
The list is saved as **`wordlist.json`** in the workspace, beside `order.json`
|
||
and `titles.json`, so it is committed to git and travels with the manuscript. It
|
||
is a plain sorted JSON array, and a corrupt or hand-mangled file is treated as
|
||
empty rather than stopping the checker.
|
||
|
||
Note that the list belongs to the *workspace*, not the whole project — open a
|
||
different folder of the same project and it has its own list.
|
||
|
||
The bundled dictionaries live under `dictionaries/` and are derived from
|
||
[SCOWL](http://wordlist.sourceforge.net/) under a permissive license (kept
|
||
alongside them in each `license` file).
|
||
|
||
## Plot beats (Mistral)
|
||
|
||
**Tools ▸ ✨ Generate plot beats (Mistral)…** turns a rough novel proposal into
|
||
a structured set of plot beats laid out in the classic **three-act structure**,
|
||
using the [Mistral](https://mistral.ai/) chat API.
|
||
|
||
1. Open **Settings ▸ Mistral…** once and paste your **API key** (from
|
||
`console.mistral.ai`). You can also set the **Model** (default
|
||
`mistral-large-latest`) and the **Base URL** (default `https://api.mistral.ai`,
|
||
handy if you route through a proxy). The settings are remembered.
|
||
2. Choose **Tools ▸ Generate plot beats…** and pick a markdown (or text) file
|
||
describing the novel. Anything the file states is read and honoured — the
|
||
**title**, **setting**, **genre**, **tone**, **characters**, **target age**
|
||
(adult, young adult, …), and a **loose plot summary**; the beats follow the
|
||
genre's conventions, keep the tone, and stay age-appropriate. Missing
|
||
attributes are inferred from the rest. The request runs in the background, so
|
||
the editor stays responsive.
|
||
3. The result opens in a **floating window** with the beats grouped under
|
||
*Act I — Setup*, *Act II — Confrontation*, and *Act III — Resolution*, each a
|
||
numbered list. You can **edit** the text in place, then:
|
||
* **💾 Save as new file** — writes `<proposal> — beats.md` into the workspace
|
||
(auto-numbered if that name is taken) and opens it in the editor, or
|
||
* **⧉ Copy** — copies the beats to the clipboard.
|
||
|
||
### Filling in an act you haven't written
|
||
|
||
If the proposal is **already partly beaten out**, the generator switches to a
|
||
different brief: instead of producing a whole sheet, it writes only the acts you
|
||
are missing and leaves the ones you have written alone.
|
||
|
||
It counts the numbered beats under each act heading it finds (`### Act One:`,
|
||
`## Act 2`, `# Act III — Resolution` — the notation doesn't matter). An act with
|
||
**three or more** beats is treated as drafted; anything fewer, or an act you
|
||
never mention, is treated as still to write. If at least one act is drafted and
|
||
at least one is not, you get the fill-the-gaps brief.
|
||
|
||
So a proposal whose Act One and Act Three are beaten out in detail and whose
|
||
Act Two is a note saying *"this is what I need help with"* sends this:
|
||
|
||
```
|
||
It is already partly beaten out:
|
||
- Act One: 11 beats already drafted — context only
|
||
- Act Two: write this one
|
||
- Act Three: 10 beats already drafted — context only
|
||
|
||
Write only the missing act(s), under exactly these headings:
|
||
### Act Two
|
||
|
||
Do not output the drafted acts.
|
||
```
|
||
|
||
Why it matters: the full-sheet brief tells the model to aim for *four to eight
|
||
beats per act*, which would **compress** an act you had already written to
|
||
eleven. The fill-the-gaps brief drops that cap, tells the model to match the
|
||
density of your drafted acts, and treats them as canon — including any reveal
|
||
that lands in a later act, which it must work backwards from and plant for.
|
||
It also reuses your own heading text and depth, so what comes back drops
|
||
straight into your document.
|
||
|
||
The result is labelled accordingly: the status line says which acts were
|
||
written, the saved file is named `<proposal> — beats Two.md` rather than
|
||
`<proposal> — beats.md`, and its title says which acts it contains — so a
|
||
partial sheet is never mistaken for a whole one.
|
||
|
||
To force a full three-act regeneration anyway, run it against a copy of the
|
||
proposal with the act headings removed.
|
||
|
||
The chosen proposal text is sent to Mistral to generate the beats; nothing else
|
||
in your workspace is transmitted. The API key is stored in the app's config file
|
||
in plain text. Because the request uses the pure-Rust `ureq`/rustls stack, the
|
||
binary stays self-contained.
|
||
|
||
## Grammar & spelling (LanguageTool)
|
||
|
||
The editor can *additionally* check the current file against a
|
||
[LanguageTool](https://languagetool.org/) server — typically a **local
|
||
instance**, so your manuscript never leaves your machine.
|
||
|
||
1. Run a LanguageTool server. The standalone server listens on
|
||
`http://localhost:8010` by default (the official Docker image uses `:8081` —
|
||
just point the app at whichever you run):
|
||
```sh
|
||
# Standalone (needs Java):
|
||
java -cp 'languagetool-server.jar' org.languagetool.server.HTTPServer --port 8010
|
||
# or via Docker:
|
||
docker run -d -p 8010:8010 --entrypoint java \
|
||
erikvl87/languagetool -cp languagetool-server.jar \
|
||
org.languagetool.server.HTTPServer --port 8010 --allow-origin '*'
|
||
```
|
||
2. Open **Settings ▸ LanguageTool…** (from the menu bar, or the **⚙** button on
|
||
the Grammar row) and set the connection:
|
||
* **Scheme** — `http` for a local server, `https` for a remote one (TLS is
|
||
supported).
|
||
* **Host / domain** — an IP address or hostname (default `localhost`).
|
||
* **Port** — the server's port (default `8010`).
|
||
* **Token** — optional; sent as an `Authorization: Bearer` header for a server
|
||
behind an auth proxy, or a premium API key. Leave blank for an
|
||
unauthenticated local server.
|
||
* **Language** — `auto` to detect, or a code like `en-US`, `en-GB`, `de-DE`.
|
||
|
||
Press **Test connection** to confirm the settings reach a working server. All
|
||
fields are remembered in the config.
|
||
3. Open a file and press **✓ Check**. The check runs in the background (the UI
|
||
stays responsive) and issues appear two ways:
|
||
* **Underlines in the editor** — red for spelling, blue for grammar/style.
|
||
* **A "Grammar & spelling" panel** at the bottom listing each issue with its
|
||
explanation and suggested fixes. Click a suggestion to apply it; the
|
||
remaining underlines re-align automatically.
|
||
|
||
Editing the text after a check hides the underlines (their positions would no
|
||
longer be accurate) and disables the fix buttons until you re-check. The checker
|
||
sends the raw file text, so occasional markdown tokens (e.g. a `#` heading
|
||
marker) may be flagged.
|
||
|
||
## Header stripping on export
|
||
|
||
Manuscript files often carry editorial notes and metadata above the prose that
|
||
should not appear in the finished document. On export, each file is cleaned up:
|
||
|
||
* **HTML comments** (`<!-- ... -->`, including multi-line ones) are removed.
|
||
* **The frontmatter**, when the file has it — everything down to the closing
|
||
`---`. Otherwise **the header block**: everything above the **Draft marker**
|
||
line set in the top bar. That marker is empty by default, since a new file is
|
||
written as frontmatter; set it to `### Rough Draft:` for files in the older
|
||
form. From either header:
|
||
* `# Title:` becomes the chapter heading (unless a manual override is set), and
|
||
* `# Slug:` becomes an italic caption printed beneath the heading.
|
||
|
||
Any other header text is discarded. Files with no marker line are exported
|
||
whole, but stray `# Title:` / `# Slug:` lines are still lifted out. Clear the
|
||
Draft marker field to disable header splitting entirely.
|
||
|
||
For example, this source file:
|
||
|
||
```markdown
|
||
# Title: The Gate
|
||
# Slug: in which the door will not open
|
||
<!-- remember to foreshadow the key -->
|
||
outline: she arrives, she knocks, nothing
|
||
|
||
### Rough Draft:
|
||
|
||
The prose that actually gets exported begins here.
|
||
```
|
||
|
||
exports as a chapter titled **The Gate**, captioned *in which the door will not
|
||
open*, containing only the prose below the marker.
|
||
|
||
### Field-name autocomplete
|
||
|
||
While typing a header line above the draft marker — `#`, `##`, … followed by the
|
||
start of a field name — a small popup suggests matching field names. Press
|
||
**Tab** or **Enter** (or click) to insert the field and its `": "`, **↑/↓** to
|
||
change the highlighted suggestion, and **Esc** to dismiss. The suggestions
|
||
include common fields (`Title`, `Slug`, `POV`, `Word Count Target`,
|
||
`Characters`, `Setting`, `Conflict`, …) plus any field names it **learns from
|
||
the headers of your other files**, so your own conventions autocomplete too. It
|
||
only triggers in the header region, never in the prose below the marker.
|
||
|
||
## Files the app writes
|
||
|
||
| Location | Purpose |
|
||
|---|---|
|
||
| `<workspace>/**/*.md` | your manuscript files, in folders if you like |
|
||
| `<workspace>/order.json` | the manual ordering, as paths (committed to git) |
|
||
| `<workspace>/titles.json` | chapter-title overrides, keyed by path (committed to git) |
|
||
| `<workspace>/wordlist.json` | names the spell checker accepts (committed to git) |
|
||
| `~/.config/md-manuscript/config.json` | last workspace, export path, prefs, new-file template, project/Gitea settings |
|
||
|
||
In [project mode](#project-mode) the workspace is the project root, so those
|
||
three bookkeeping files sit there and cover the whole project.
|
||
|
||
## Markdown supported in ODT export
|
||
|
||
Headings, paragraphs, **bold**, *italic*, ***both***, `inline code`, fenced/indented
|
||
code blocks (monospace, whitespace preserved), ~~strikethrough~~, ordered and
|
||
unordered (nested) lists, block quotes, horizontal rules, and links. Images are
|
||
rendered as a text placeholder (not embedded).
|
||
|
||
A quick reference for all of this — plus the chapter metadata, draft-marker
|
||
header, and comment conventions — is built in: open **Help ▸ Markdown cheatsheet**
|
||
from the menu bar.
|
||
|
||
## Changelog
|
||
|
||
### Latest
|
||
|
||
- **Heading-based nesting outlines**: Markdown headings (`#`, `##`, `###`) in outline
|
||
files now create a hierarchical tree structure in the Outline window. Beats under
|
||
the same heading are grouped together under collapsible headers with progress
|
||
counts, making it easy to navigate large outlines and track progress at every
|
||
level of your story structure.
|
||
- **A scene says each thing once**: planning metadata lives in the YAML
|
||
frontmatter and nowhere else. **+ New Scene** used to write 28 keys and then
|
||
restate nearly all of them below the closing `---` as a metadata table, a
|
||
word-count tracker, POV/characters/setting/conflict/theme/tone sections, a
|
||
notes block and a checklist — all of it body text, so all of it counted toward
|
||
the word count and was exported into the `.odt`. A new scene now starts at the
|
||
prose. Nine keys the app could not keep true (`word_count`, `session_word_count`,
|
||
`total_sessions`, `version`, `created`, `updated`, …) are gone with it, along
|
||
with `id` and `order`, which restated the file's own path and `order.json`.
|
||
- **The header fold works on frontmatter**: the `▾ header shown` line above the
|
||
editor asked only whether the file had a draft marker, so it never appeared on
|
||
a frontmatter file — the one form the app writes. Both forms now offer it.
|
||
- **Frontmatter is read properly**: the status column, the Characters window and
|
||
the scene card now read frontmatter through the same parser as a legacy
|
||
`## POV:` header. `characters:` works in either YAML list form (it matched
|
||
nothing before, or one character called `[]`), keys with underscores are no
|
||
longer invisible, and prose below the frontmatter is no longer mistaken for
|
||
metadata — a line of dialogue like `Bixby: run` used to register as a field.
|
||
A `word_count_min`/`word_count_max` pair now sets the target, as documented.
|
||
- **Automatic workspace refresh**: File list now automatically refreshes when
|
||
files change on disk using a file watcher.
|