Files
md-manuscript/README.md
T
landon aa050cf411 Add a right-click context menu to file rows
Renaming, archiving and deleting a file all lived in buttons under the
list, and all of them required selecting the file first. The same
operations are now on the row itself: right-click a file for Rename,
Copy path, Archive and Delete.

The menu records what was picked rather than acting on it, because the
list is drawn inside a closure still borrowing `self` — the same
deferred pattern the click and drag-drop handling already use. Right-
clicking selects the row first, so the menu always acts on the file you
clicked; selecting is a no-op when it is already current, so this cannot
reload the buffer out from under an unsaved edit.

Rename opens a focused dialog with the path pre-filled, and stays open
if the name is rejected so a clash does not cost what was typed;
rename_selected reports success for that. Delete asks first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBWj9TphFMCoh7VHaSRnvQ
2026-09-07 11:57:49 -05:00

795 lines
39 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 cookiecutter 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
```
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, add a remote yourself:
```sh
cd ~/Manuscript
git remote add origin <url>
git push -u origin main
```
After that, the **⟳ Sync (git)** button commits all changes, `pull --rebase`s,
and pushes.
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.
## 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** (`06-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), [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 `06-First Draft` — and
the setting is global, so every project on this machine uses the same layout.
If your writing actually lives in `05-Plot`, point it there.
### 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 card'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 away everything above the draft marker, leaving the prose alone
in the editor. A single line above the editor says so — `▸ header hidden (8
fields)` — and clicking it flips 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.
Files with no draft marker have 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.
### 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.
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.
### 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 a cookiecutter template
**File ▸ ✨ New project…** scaffolds a whole manuscript project from a
[cookiecutter](https://cookiecutter.readthedocs.io/) template and then opens its
drafting folder as the workspace, so you go from nothing to editing chapters in
one step.
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 template whose hooks talk to the network doesn't freeze the editor.
When it finishes, the app opens the project's **`06-First Draft`** subfolder as
the workspace — that is where the snowflake template keeps the chapter files, in
`Act 1/`, `Act 2/` and `Act 3/` subfolders that the
[folder tree](#organising-with-folders) shows directly. If the template didn't
produce that folder, the project root is opened instead and the status bar says
so.
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.
### Settings ▸ New project…
| Setting | Meaning |
|---|---|
| **Template** | the folder holding the template's `cookiecutter.json`. Defaults to `~/Documents/Cookiecutters/snowflake`; the dialog confirms whether a `cookiecutter.json` is actually there |
| **Open subfolder** | which folder of the generated project becomes the workspace (default `06-First Draft`; blank opens the project root) |
| **cookiecutter path** | leave blank to search `PATH` and then the usual per-user Python prefixes (`~/.anaconda3/bin`, `~/miniconda3/bin`, `~/.local/bin`, …), which is what a desktop launcher needs since it does not inherit a conda `PATH`. The dialog shows which executable was found |
| **Run the template's hooks** | whether to run the template's pre/post-generation scripts (on by default) |
| **Gitea** | server URL, user and token, handed to the hooks as `GITEA_URL` / `GITEA_USER` / `GITEA_TOKEN` |
Cookiecutter is a Python program and is **not bundled** — install it with
`pip install cookiecutter` or `conda install cookiecutter` if the settings window
reports it missing.
### Hooks and publishing
The snowflake template's `hooks/post_gen_project.py` creates a repository on a
Gitea server and pushes the new project to it. It only does that when all three
Gitea settings are filled in; with any of them blank it prints a note and skips,
and **the project is still generated either way**. The token is passed to the
push command alone and is not written into the project's `.git/config`.
The token is stored in `config.json` in plain text, the same as the Mistral API
key.
## 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 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 set a `# Title:` header and let the chapter title come
from that.
Edit the template under **Settings ▸ New-file template…**. The built-in default is:
```
# Title: {{name}}
# Slug:
# POV:
# Word Count Target:
### Rough Draft:
```
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 (default `### Rough Draft:`) |
| `{{date}}` | today's date in UTC, as `YYYY-MM-DD` |
Anything else in double braces is left alone. Using `{{marker}}` rather than
typing the marker literally keeps the template working if you change the marker
later. 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
`06-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 header block** — everything above the **Draft marker** line (set in the
top bar; default `### Rough Draft:`) — is dropped. From that 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.