The preview rendered the whole document as one clipped line running off
the right edge.
An egui child `Ui` inherits its parent's layout, and `ScrollArea` passes
that layout straight through to its content. Both columns were allocated
with `allocate_ui` inside `horizontal_top`, so the preview's content ui
was left-to-right: every `ui.label` in `render_preview` was placed beside
the previous one instead of below it. The editor escaped notice because
it is a single `TextEdit` widget, so a horizontal layout looks the same.
Allocate both columns with an explicit top-down layout instead. The
editor did not need it, but leaving one column implicit is how this would
come back.
Verified by running the app against an isolated config before and after:
the old binary shows "Title: The Opening Slug: opening Rough Dra..." on
one line; the new one wraps paragraphs to the column and sizes headings.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017GLYKcrX18ytCCuKozDXk6
A screenplay is its own project rather than a file type mixed in with
prose, so this is one switch in Manuscript details, not a second file
extension threaded through order.rs and the file panel. Files stay `.md`
and keep the same editorial header; only the draft below the marker is
read as Fountain. They list, reorder and header-strip exactly as before.
src/fountain.rs parses the format. Almost nothing in Fountain is marked
up -- what makes a line a character cue is that it is in capitals with
something directly beneath it, and what makes the same words a
transition is a blank line below instead. The forcing characters are all
there for where that is not enough, along with notes, the boneyard and
inline emphasis. Sections and synopses are parsed and kept but never
printed: they are the writer's scaffolding.
src/pdf.rs writes the PDF by hand, for the reason odt.rs writes ODT by
hand -- no pandoc, no LibreOffice at runtime. It costs no dependency
either: a screenplay is set entirely in Courier, which is one of the
fourteen faces every reader must provide, so there is no font to embed
and no metrics to parse. Streams are left uncompressed; a feature script
is a few hundred kilobytes that way and stays readable when something
needs debugging.
src/screenplay.rs does layout. The geometry is the conventional one --
55 lines of 12pt on US Letter, action at 1.5", dialogue 2.5",
parentheticals 3.1", cues 3.7", transitions flush to 7.5" -- because a
page only reads as a minute of screen time if it is. A speech broken by
a page boundary is marked (MORE) and resumed under a repeated cue, a `^`
cue sets two speeches side by side, and a scene heading is never left
stranded at the foot of a page.
Two faults the tests missed and measuring the rendered PDF caught. A
hard-wrapped action paragraph was getting a blank line between every
source line, which on the page reads as a beat the writer never wrote;
consecutive lines are now one paragraph, with the breaks kept. And
reserving one line after a scene heading did not stop it stranding,
because every element that can follow a heading is separated from it by
a blank -- the reserve has to cover both. Both now have tests.
Verified beyond the unit tests: pdffonts confirms base-14 Courier with
nothing embedded, pdftotext -bbox puts every indent within a hundredth
of an inch of standard, and the export was driven through the real UI
against a scratch workspace and an isolated config.
Not built, because they were offered and never asked for: rendering
Fountain in the Preview pane, and exporting a manuscript as a .fountain
file.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Bq2fUZNgksdPp3zeHqzSw
The launcher pointed at `Icon=story-editor`, a name that only the Breeze
theme happened to ship, and only at 16 and 22 px -- so anywhere the
desktop wanted a larger icon it fell back to something generic.
assets/md-manuscript.svg is the source: a sheaf of manuscript pages, an
amber chapter-title line over three of prose. Drawn in plain rounded
rectangles because a finer design silts up into a grey smear at 16 px.
It is rasterised to the eight sizes a desktop asks for, and the 256px
PNG is compiled into the binary as the window icon, so the title bar and
the task switcher carry it whether or not anything is installed. That
costs no new dependency: eframe already pulls `image` with png decoding.
A test decodes the bundled PNG, since a rename would otherwise show up
only as a blank icon at runtime.
Installation is now `./assets/install.sh` -- binary, icons, launcher and
both cache refreshes, safe to re-run after a rebuild. The launcher is a
file in the repository rather than a here-document in the instructions:
pasting one into a terminal leaves the shell sat at a `>` prompt waiting
for a terminator that never arrives, which is exactly what happened, and
this way the launcher is versioned along with everything else.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYSGPDwkSzhm4qLqjCbqxU
Five changes to the export and the window chrome:
* Manuscript details gains a contact field and a "Begin exports with a
title page" option: the title, the author beneath it, and the contact
details beneath that, laid out line for line as typed. A chapters +
master export puts the title page on the master, not on each chapter.
* "Standard manuscript format" lays an export out the way an agent or an
editor expects a submission: 12pt Courier, double-spaced, half-inch
first-line indents, a Surname / Title / page header on every page but
the title page, chapters opening a third of the way down, and `---`
rendered as the conventional centred `#` scene break. The title page
becomes the submission kind, with contact top-left and an approximate
word count top-right. Off by default; margins were already the
standard 1in on US Letter and are unchanged either way.
* A folder button beside Export ODT opens the project folder in the file
manager -- the enclosing git work tree, including one still awaiting
confirmation, since showing a folder is a smaller question than
choosing which repository to commit to.
* The toolbar and the file list each collapse, from the pair of buttons
at the right of the menu bar or from the View menu, and Ctrl+D folds
both away together for distraction-free writing. Both choices persist.
* The editor sizes to its viewport instead of a fixed 30 rows, so the
height a folded panel gives back reaches the page rather than leaving
grey space below the text box that did not even take focus.
Two traps worth recording. ODF's style:master-page-name is inherited,
and any paragraph style carrying one forces a page break before every
paragraph that uses it -- a four-line contact block came out as four
pages until Contact_20_Line stopped inheriting from Contact_20_Block;
a test now pins which styles may carry one. And the status bar shares
a function with the toolbar, so guarding that function's top rather
than the top panel alone silently took the word counts away with it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYSGPDwkSzhm4qLqjCbqxU
Not every flag deserves a fix, so give the checkers a way to be told no:
- ✖ beside an issue in the panel, and a matching entry in the editor's
right-click menu, wave that issue away.
- A dismissal is remembered as the offending text paired with the message
rather than a byte range, since offsets move as soon as you type. Both
checkers filter their fresh results against it, so a dismissed complaint
stays gone across re-checks and repeats of the same phrase in the file.
- The panel header counts the dismissals and ↩ takes them back. The offline
spell check then rebuilds its own list; LanguageTool's can only come from
the server, so those are dropped with a nudge towards ✓ Check.
Dismissals belong to the open file and the session — they are cleared when
another file or workspace is opened, and never written to disk. A name or
invented term still belongs in the word list, which persists.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011CVsxwa6YukFS2YFUY8W2j
Rounds out project mode, where the workspace is the project root and one
subfolder holds the manuscript proper:
- Characters and Outline windows, backed by new `characters` and `outline`
modules that read the cast from character sheets and measure how much of
the snowflake outline is actually written.
- Edit ▸ Changes… diffs the open file against its last committed version.
- Revision status, per-file and project word counts, an archive action and
hidden folders in the file panel.
- Chapter-file export alongside the ODT master, richer header parsing, and
a project word list for names and invented terms.
- The export path now follows the workspace: opening a project points it at
that project root, keeping a file name you chose yourself and re-deriving
one that merely echoed the folder it sat in.
- Clicking an issue in the grammar/spelling panel takes the editor to it,
selecting the words and centring them; applying a suggestion jumps to the
rewritten text as well.
README covers the new windows and workflows.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017bSn3Xijp8GofZVUnRX4oq
The beat generator had one brief, which assumed the proposal was a prose
premise and asked for a whole three-act sheet at "roughly four to eight
beats per act". Handed a proposal that already had acts beaten out past
that, it would regenerate them — compressing the author's own work into a
shorter paraphrase, losing the specific detail that made it worth
keeping, and burying the one act they actually wanted among two they did
not.
`mistral::analyse` now reads a proposal's act headings and counts the
numbered beats under each, in whatever notation the author used. An act
with three or more beats is drafted; fewer, or an act never mentioned, is
still to write. Two stray numbered lines are a note to self, not an act.
When some acts are drafted and some are not, a second briefing is sent.
That briefing asks for the missing acts only, and asks for them under the
author's own heading text and depth, so what comes back drops into their
document. It drops the per-act cap in favour of matching the density of
the drafted acts, and states which acts are context and which to write.
The drafted acts are canon: not to be rewritten, reordered, summarised,
condensed or reproduced, and — since a reveal often lands in a later act
— the model is told to work backwards from those reveals and plant what
they need using setups already placed.
The model is asked not to reproduce the drafted acts at all, rather than
to echo them unchanged. Reproducing twenty-odd beats verbatim is exactly
the drift being avoided; not asking makes it impossible.
Because the output is then partial, it is labelled: the status line names
the acts written, the file is saved as `<proposal> — beats Two.md`, and
its title says what it holds, so a partial sheet is not later mistaken
for a whole one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ZGoPiDuZ7vmryNCJWjYSD
Two features that arrived together, since the second depends on the first
to show what it generates.
Nested folders
--------------
`App::files` now holds workspace-relative paths (`part-1/ch-03.md`) rather
than bare names, and the workspace scan recurses eight levels, skipping
dot-directories, `target/` and `node_modules/`. `order::tree_order`
normalises the flat order so every folder's files are contiguous and each
folder sits where its earliest-ordered file put it — which is what keeps
the panel and the export in agreement: the export concatenates the tree
read top to bottom.
The panel draws a collapsible tree. Dragging within a folder reorders as
before; dropping onto a folder header, or among another folder's files,
moves the file on disk and carries its title override, session word
baseline and cached header info with it. Emptied folders are pruned. New
files take a path (`part-1/ch-01`) to create folders, and the Rename box
now holds the whole relative path, so editing its folder part moves the
file.
File ▸ New project
------------------
Scaffolds a project from a cookiecutter template and opens its drafting
subfolder (`06-First Draft`) as the workspace. `cookiecutter.rs` resolves
the executable from PATH and the usual per-user Python prefixes — a
desktop launcher inherits neither a conda PATH nor the tools a template's
hooks shell out to, so the resolved binary's directory is prepended for
the child — builds the non-interactive command line, and identifies the
result by diffing the output directory, which works whatever a template
names its root.
Generation runs off the UI thread because hooks can reach the network.
Settings ▸ New project… covers the template path, the subfolder to open,
the cookiecutter path, a hooks toggle, and the Gitea credentials passed
to hooks as GITEA_URL / GITEA_USER / GITEA_TOKEN.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ZGoPiDuZ7vmryNCJWjYSD
egui's Stroke::new takes `width: impl Into<f32>`, so the unsuffixed 2.0
literal had no concrete type to infer from and relied on rustc falling
back to f32. That fallback is being removed (rust#154024), and the
float_literal_f32_fallback lint already warns it will become a hard
error, so spell the type out as 2.0_f32.
No behaviour change - the literal was already compiling as f32. The only
other Stroke::new call site passes an f32 variable and was unaffected.
cargo build is now warning-free; clippy is down to the four long-standing
warnings in odt.rs and order.rs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBWj9TphFMCoh7VHaSRnvQ
The file list's "+ New" needs a typed name and seeds only '# <name>',
so every new chapter started from a blank header. Add a second button,
"+ New from template", that takes no name: it picks the first free
untitled-N.md (reusing a gap left by a deleted file), seeds it from a
configurable template, appends it to the manuscript order and selects it.
The template lives in config.json and is edited under Settings > New-file
template..., alongside the existing LanguageTool and Mistral dialogs.
Three placeholders expand at creation time: {{name}} (file stem),
{{marker}} (the configured draft marker, so a template keeps working if
the marker changes) and {{date}} (today's UTC date). Blanking the box
falls back to the built-in default, which seeds the header fields the app
already understands followed by the draft marker.
The date is computed with a local civil-from-days conversion rather than
a date crate, keeping the binary self-contained - ldd still shows only
libc/libgcc/libm.
"+ New" is unchanged; both paths now share insert_new_file. Covered by
13 new tests: placeholder expansion, untitled-N gap reuse, civil date
conversion against known dates, config.json written before this field
still deserializing (Config::load falls back to Default on a parse error,
which would otherwise discard the user's workspace and API keys), and a
round trip proving the seeded header parses back into Title/Slug/POV/goal
via the app's own preprocess::parse.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBWj9TphFMCoh7VHaSRnvQ
src/app.rs had grown to 3,700 lines, ~2,400 of them a single impl App
block. Move it to src/app/mod.rs and spread the behaviour across eleven
child modules grouped by feature: workspace, grammar, spelling, beats,
find, editor, autocomplete, file_list, ui, style and util.
The new modules are children of app rather than siblings, so they still
reach App's private fields without widening its interface; methods and
free helpers that are now used across module boundaries are marked
pub(super). mod.rs keeps the state types, App::new and the eframe::App
update loop.
This is pure code motion - every non-blank line of the original file
reappears exactly once, and the only edits are the pub(super) markers,
the module scaffolding, and rewrapping five signatures that the added
prefix pushed past 100 columns. Largest file is now editor.rs at 520
lines. Tests still 56/56, and cargo clippy --release reports the same
five warnings as before the split.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBWj9TphFMCoh7VHaSRnvQ
Broaden the plot-beat system prompt beyond characters + plot: instruct
the model to read and honour every attribute the proposal states —
title, setting, genre, tone, characters, target age (adult / YA / middle
grade / …), and the loose plot summary. The beats now follow the stated
genre's conventions, keep the tone, and stay appropriate to the target
age; missing attributes are inferred rather than contradicted. Updates
the user-message framing and the README to match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XDfaEsTgcn61n3wgP62DFF
New Tools ▸ "Generate plot beats (Mistral)…" command: pick a novel
proposal markdown file (characters + loose plot summary) and have the
Mistral chat API lay it out as plot beats in the classic three-act
structure. The result opens in a floating window where it can be edited,
saved as a new "<proposal> — beats.md" workspace file, or copied.
- src/mistral.rs: pure-Rust ureq/rustls POST to /v1/chat/completions
(self-contained; no C bindings). System prompt fixes the three-act
markdown shape; friendly API-error surfacing. Unit tests for content
parsing, empty/blank responses, error-message extraction, and the
missing-key guard.
- config: mistral_api_key / mistral_model / mistral_base_url with
effective-value helpers and defaults (mistral-large-latest,
https://api.mistral.ai).
- app: background generation + poll (editor stays responsive), a
Settings ▸ Mistral… window (key/model/base URL), and the results
window with save/copy.
- Docs: README "Plot beats (Mistral)" section and help cheatsheet note.
Verified: cargo build --release clean, 56 tests pass (+6 Mistral),
ldd still only libc/libgcc/libm. Clippy: no new warnings in the added
code (the two extra vs. the old 3-warning baseline are toolchain-surfaced
in pre-existing app.rs code).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XDfaEsTgcn61n3wgP62DFF
Typing a header line above the draft marker (`#`/`##`/… + the start of a
field name) now shows an autocomplete popup: Tab/Enter or click to insert
"Field: ", Up/Down to choose, Esc to dismiss (and stay dismissed until the
partial changes). It never triggers in the prose below the marker.
Suggestions combine a built-in list (Title, Slug, POV, Word Count Target,
Characters, Setting, Conflict, …) with field names learned from every
file's header, refreshed on open and merged on save, so custom fields
autocomplete too.
Implementation: the editor gets a stable id so completions can drive its
cursor/focus; nav keys are consumed before the TextEdit sees them, and the
popup is recomputed from the caret each frame. Pure helpers
(header_completion_context, field_matches, header_field_names) are unit
tested; 50 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N9kRuP7JvXoUGdNNeg5ZSs
Each file row now shows a compact progress bar beside its name when the
file sets a Word Count Target, alongside the existing detailed bar in the
status bar for the active file. Same colour scheme (amber under / green in
range / blue over) and a hover tooltip with the exact words / target.
The per-file slug/POV cache is generalised to a FileMeta (slug, POV, goal,
prose word count), filled on open and refreshed on save; the selected
file's row reads live from the buffer so its bar tracks as you type.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N9kRuP7JvXoUGdNNeg5ZSs
Header parsing now also lifts a `POV:` line (any heading level). The
file-list tooltip combines it with the slug — "POV: <name>" then the
slug synopsis — cached per file alongside the slug (HoverInfo) and read
live from the buffer for the selected file. Adds a POV parse test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N9kRuP7JvXoUGdNNeg5ZSs
Hovering a file in the left pane now shows its `# Slug:` line as a
tooltip — a one-line synopsis per chapter. Slugs are cached per file
(name -> slug), filled when the workspace opens (snapshot_slugs) and
refreshed on save; the selected file is parsed live from the buffer so
unsaved slug edits show immediately. The cache is kept consistent on
rename and delete.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N9kRuP7JvXoUGdNNeg5ZSs
Header parsing now recognises a `Word Count Target:` line at any heading
level (## included) in addition to `# Title:`/`# Slug:`, and accepts one
or more leading `#`. The value parses a single number or a range —
"1500 - 2000", "1500-2000", "1,500 to 2,000" — into a min/max WordGoal
(stripped from the exported document like the other header lines).
When a target is set, the status bar shows a progress bar of the file's
prose word count (the body below the draft marker) against it: amber
under the target, green in range, blue over. Aliases "Word Count Goal:"
and "Word Count:" also work.
Adds preprocess tests for the range forms, ## heading level, alias, and
absence; 44 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N9kRuP7JvXoUGdNNeg5ZSs
Spelling is now checked live as you type, fully offline, using the
pure-Rust `spellbook` crate to read Hunspell .aff/.dic dictionaries (no C
libhunspell — the binary stays self-contained; ldd unchanged).
- Canadian (en-CA, default) and British (en-GB) English dictionaries are
compiled in; more languages are auto-discovered from system Hunspell
folders and ~/.config/md-manuscript/dictionaries/. A "Spelling" row in
the top bar toggles live checking and picks the dictionary.
- Misspellings are underlined in red; right-clicking a word opens a menu
of suggested corrections. A results-panel and one-click fixes work too.
- Checks run on a background thread, debounced ~400ms after the last edit.
Prose is extracted with pulldown-cmark so code spans, code blocks and
link targets are skipped; ALL-CAPS initialisms are ignored.
- LanguageTool still layers on top: when its results are fresh they own
the underlines (spelling + grammar); once you edit, the offline checker
resumes. The editor underlines, issues panel and fix-apply logic are
now shared between the two sources.
Bundled dictionaries are SCOWL-derived under a permissive license (kept
in dictionaries/<lang>/license). Adds spell-check unit tests (tokenizer,
code-block skipping, offset mapping, en-CA/en-GB spelling); 40 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N9kRuP7JvXoUGdNNeg5ZSs
Ctrl+F opens a find bar above the editor (Ctrl+H focuses the replace
field; Edit ▸ Find / Replace… opens it too). Matches are shaded in the
editor with the active one highlighted more strongly; Enter/Shift+Enter
and ▼/▲ step through them with wraparound and scroll-to-view. An "Aa"
toggle controls case sensitivity. Replace swaps the current match and
advances; Replace all swaps every match in one pass. Esc closes the bar.
Search/replace logic lives in pure, unit-tested functions (find_matches,
replace_all) with non-overlapping matching and correct multibyte offset
mapping for case-insensitive search. Highlighting reuses the editor's
layout-job builder alongside the grammar underlines.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N9kRuP7JvXoUGdNNeg5ZSs
Ctrl/Cmd+B/I/E wrap the selection in bold / italic / inline-code markers,
Ctrl/Cmd+Shift+X in strikethrough, and Ctrl/Cmd+K as a link (with the url
placeholder selected). Pressing the same key on already-wrapped text
removes the markers. Applied to the live selection via the TextEdit
cursor range, with the caret/selection restored around the edit.
Documented in the built-in cheatsheet and the README.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirror the logrs treatment: at startup, pull every widget state's
foreground stroke toward white (dark) / black (light) so all UI text —
menus, top bar, buttons, panels, headings, not just the file list and
editor — reads more clearly against the theme background. Also scale
every base font size up ~12%. Explicitly coloured spans and the existing
per-pane contrast controls are unaffected and still layer on top.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirrors the logrs cheatsheet: a scrollable reference window covering the
markdown md-manuscript renders on export, plus the manuscript-specific
conventions (# Title:/# Slug: metadata, the draft-marker header, and
comments). Opened from Help ▸ Markdown cheatsheet in the menu bar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When a workspace folder isn't a git repo itself but sits inside one, ask
the user to confirm before using the parent repository for Sync, rather
than silently operating on whichever repo git walks up to find.
- gitsync: add repo_root() via `git rev-parse --show-toplevel`
- app: detect_repo() distinguishes repo-root / parent-repo / no-repo;
a confirmation dialog adopts or declines the parent repo; git_init
tracks repo_root and clears any pending prompt
- Sync button hover names the enclosing repo when one is adopted
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The left file pane now boosts its widget text colours (toward white in dark
mode, black in light mode) so file names stay legible against the panel
background; the change is scoped to that panel only.
Add a "Contrast" slider beside Zoom that interpolates the editor text colour
from the theme default toward maximum contrast, persisted as
editor_text_contrast in the config. build_editor_job takes the computed colour
so underlined grammar/spelling runs match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Check the current file against a LanguageTool server (typically a local
instance) from a background thread. Issues are underlined in the editor
(red spelling, blue grammar) and listed in a bottom panel with one-click
fixes that re-align the remaining matches.
A menu bar (File / View / Settings) exposes a LanguageTool settings
dialog with discrete scheme / host / port / token / language fields, an
endpoint preview, and a Test connection button. Config stores the parts
and assembles the base URL. The token is sent as a bearer header.
Uses ureq with a statically-linked rustls backend, so both http and
https endpoints work with no new runtime library dependencies.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add an XFCE section to the install guide covering Whisker Menu
favourites (right-click → Add to Favorites), panel/desktop launcher
alternatives, and menu-refresh commands. Fold XFCE equivalents into the
on-screen keyboard, HiDPI, and X11 tips, and add a troubleshooting row.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A Zoom slider (with Reset) above the editor scales the monospace font
from -50% to +200%, defaulting to 0%. The editor keeps infinite desired
width inside a vertical-only scroll area, so text wraps to the pane at
any zoom with no horizontal scrolling. The zoom level persists in config.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A "Zero-pad #" checkbox pads chapter titles that default to their
position number with leading zeros to a uniform width (the digit count
of the largest chapter number, e.g. 03. of 12). Padding never applies to
"# Title:" headers or manual overrides. The setting persists in config.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Every chapter after the first now starts on a new page via a
Chapter_20_Break heading style (fo:break-before="page"); the first
chapter keeps the plain heading so there is no blank leading page.
Chapter titles now resolve as: manual override > "# Title:" header >
the chapter's 1-based position in the left pane (e.g. "3."). The old
"# heading" / filename fallbacks are replaced by this numbering, and the
same logic drives the chapter-title hint.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A 📂 button beside each path field opens a native picker: a folder
chooser for the workspace and an .odt save dialog for the export (which
appends the extension if omitted). Uses rfd with the XDG Desktop Portal
backend, so there is no GTK build dependency and no new shared library
at runtime.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Shows a live word count for the open file plus the net words added or
removed since this session started. Per-file baselines are snapshotted
when the workspace opens (session_start_counts) and follow renames.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each file is now cleaned before conversion: HTML comments are removed
and everything above the configurable Draft marker (default
"### Rough Draft:") is dropped. A "# Title:" line becomes the chapter
heading and a "# Slug:" line becomes an italic caption beneath it.
- new preprocess module (strip_comments + header parse) with tests
- Chapter gains a slug rendered via a Chapter_20_Caption style, italic
at both paragraph-style and character-run level
- draft_marker config setting + top-bar field
- export title priority: override > # Title: > first # heading > filename
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>