Finish bundled-template migration; simplify frontmatter; de-duplicate toolbar/menu
Completes the move off the external cookiecutter tool: drops src/cookiecutter.rs and the cookiecutter_bin/Template-path setting now that scaffolding always runs through the built-in MiniJinja engine, and rewrites default_project_template() to walk up from the executable without panicking when the bundled template can't be found. Rounds out the snowflake template with the remaining 06-Revisions, 07-Final, 08-Covers and 09-Archive folders. Simplifies the scene/chapter YAML frontmatter (drops type/word_count_min/ word_count_max in favor of status + a flat characters list) and updates the new-file template, header parsing, outline, file list and Story Bible editor to match. Separately, de-duplicates the toolbar and menu bar: settings that only existed as always-visible toolbar controls (draft marker, zero-pad, spell-check toggle, dictionary picker) move into their Settings windows, toolbar buttons that exactly duplicated a menu item are dropped, and toolbar-only actions with no menu path (open project folder, git sync/init) gain one under File. Also fixes two ambiguous menu labels: the Story Bible editor now reads "Story Bible editor…" next to "Story Bible…" in Tools instead of sharing a label with Edit, and Settings' "New project…" is now "New-project settings…" so it doesn't read like File's actual create action. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,7 @@ as an ordered manuscript.
|
||||
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…**),
|
||||
* **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
|
||||
@@ -43,8 +43,17 @@ sudo apt install libgl1 libxkbcommon0 libwayland-client0 libx11-6
|
||||
|
||||
```sh
|
||||
install -Dm755 target/release/md-manuscript ~/.local/bin/md-manuscript
|
||||
# The project templates are read from disk, so they travel with the binary:
|
||||
cp -r templates ~/.local/bin/templates
|
||||
```
|
||||
|
||||
The app looks for `templates/snowflake/cookiecutter.json` beside the executable
|
||||
and then up through its parent folders, so a release build run from the source
|
||||
tree finds it without installing anything. Without it the app still starts and
|
||||
everything else works — only **File ▸ New project…** has nothing to scaffold
|
||||
from, and says so. You can also point **Settings ▸ New project… ▸ Template** at
|
||||
a copy anywhere else.
|
||||
|
||||
Optionally add a desktop launcher at
|
||||
`~/.local/share/applications/md-manuscript.desktop`:
|
||||
|
||||
@@ -184,11 +193,9 @@ target is stripped from the exported document.
|
||||
|
||||
### YAML frontmatter for planning metadata
|
||||
|
||||
For projects using the **snowflake template** or similar structured workflows, files
|
||||
can store planning metadata in **YAML frontmatter** at the top of the file. This keeps
|
||||
all planning information (target word count, POV, characters, setting, etc.) together with
|
||||
the draft content in a single file, while allowing the metadata to be collapsed in the
|
||||
editor.
|
||||
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
|
||||
---
|
||||
@@ -197,31 +204,57 @@ slug: chapter-one-the-beginning
|
||||
act: 1
|
||||
chapter: 1
|
||||
scene: 1
|
||||
type: chapter
|
||||
status: draft
|
||||
word_count_target: 2000
|
||||
word_count_min: 1800
|
||||
word_count_max: 2200
|
||||
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
|
||||
setting: "The Old Reynold's House"
|
||||
---
|
||||
|
||||
# Chapter One: The Beginning
|
||||
|
||||
The prose begins here...
|
||||
|
||||
### Rough Draft:
|
||||
|
||||
Actual draft content...
|
||||
```
|
||||
|
||||
The YAML frontmatter (delimited by `---` lines) is automatically detected and can be
|
||||
collapsed in the editor using the **Collapse header** toggle in the top bar. The
|
||||
draft marker (`### Rough Draft:`) is still supported for backward compatibility with
|
||||
legacy files.
|
||||
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
|
||||
|
||||
@@ -340,11 +373,14 @@ 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.
|
||||
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
|
||||
@@ -353,7 +389,8 @@ 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.
|
||||
A file with neither frontmatter nor a draft marker has nothing to fold, so the
|
||||
line does not appear.
|
||||
|
||||
### Characters (Tools ▸ Characters…)
|
||||
|
||||
@@ -608,8 +645,8 @@ single finished document, use **Export ODT**.
|
||||
## New projects from templates
|
||||
|
||||
**File ▸ ✨ New project…** scaffolds a whole manuscript project from a
|
||||
template and then opens its drafting folder as the workspace, so you go from
|
||||
nothing to editing chapters in one step.
|
||||
template and then opens it as the workspace, so you go from nothing to editing
|
||||
chapters in one step.
|
||||
|
||||
The app now uses **built-in MiniJinja templating** (no external Python
|
||||
cookiecutter required) to render templates. A default "snowflake" template is
|
||||
@@ -620,7 +657,7 @@ 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.
|
||||
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
|
||||
@@ -642,8 +679,8 @@ rendering.
|
||||
|
||||
| Setting | Meaning |
|
||||
|---|---|
|
||||
| **Template** | the folder holding the template's `cookiecutter.json`. Defaults to the bundled `templates/snowflake`; falls back to `~/Documents/Cookiecutters/snowflake` |
|
||||
| **Open subfolder** | which folder of the generated project becomes the workspace (default `05-First Draft`; blank opens the project root) |
|
||||
| **Template** | the folder holding the template's `cookiecutter.json`. Defaults to the bundled `templates/snowflake`, found beside the executable or up through its parent folders |
|
||||
| **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 |
|
||||
|
||||
@@ -664,18 +701,16 @@ the project is still generated locally.
|
||||
The token is stored in `config.json` in plain text, the same as the Mistral API
|
||||
key.
|
||||
|
||||
This path runs entirely inside the template's own hook, so it applies to
|
||||
projects made through **New project** and nothing else, and it does nothing if
|
||||
**Run the template's hooks** is off, if any of the three Gitea settings is
|
||||
blank, or if the template has no such hook. When it fails it fails in someone
|
||||
else's Python, and all this app can report is that a hook exited non-zero — the
|
||||
Git log window carries whatever the hook printed.
|
||||
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… does not go through the template at all.** It calls the
|
||||
Gitea API directly, works on any workspace, 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). If the hook is not publishing and you would rather not debug it, that
|
||||
is the way round.
|
||||
**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
|
||||
|
||||
@@ -685,23 +720,26 @@ The file list has two create buttons:
|
||||
|---|---|---|
|
||||
| **+ 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 set a `# Title:` header and let the chapter title come
|
||||
from that.
|
||||
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:
|
||||
Edit the template under **Settings ▸ New-file template…**. The built-in default
|
||||
is frontmatter, the same shape a scene uses:
|
||||
|
||||
```
|
||||
# Title: {{name}}
|
||||
# Slug:
|
||||
# POV:
|
||||
# Word Count Target:
|
||||
|
||||
### Rough Draft:
|
||||
---
|
||||
title: {{name}}
|
||||
slug:
|
||||
status: draft
|
||||
pov:
|
||||
characters: []
|
||||
word_count_target:
|
||||
---
|
||||
|
||||
```
|
||||
|
||||
@@ -710,12 +748,13 @@ 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:`) |
|
||||
| `{{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. 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**
|
||||
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
|
||||
@@ -901,8 +940,11 @@ 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:
|
||||
* **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.
|
||||
|
||||
@@ -970,8 +1012,24 @@ from the menu bar.
|
||||
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.
|
||||
- **Linked scene/chapter templates**: New file templates now support linked
|
||||
planning+draft file pairs. Creating a scene in the draft folder automatically
|
||||
creates a corresponding planning file in the Plot folder with references.
|
||||
- **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.
|
||||
|
||||
Reference in New Issue
Block a user