Commit Graph

11 Commits

Author SHA1 Message Date
landon 043cc692ac Add project-mode tooling: characters, outline, diff and revision status
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
2026-08-24 19:56:36 -05:00
landon 41c3f088cd Add nested folders to the file panel and File ▸ New project
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
2026-08-23 15:18:22 -05:00
landon 8fdb9a4cbd Add a template-backed new-file button to the file list
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
2026-08-22 09:30:32 -05:00
landon e5eadc08c1 Add Mistral plot-beat generation (Tools menu)
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
2026-08-20 14:39:08 -05:00
landon 19dddf30c7 Add offline live spell checking (Hunspell via spellbook)
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
2026-08-14 06:25:08 -05:00
landon 496bbfd298 Improve readability: brighter left-pane text and an editor contrast slider
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>
2026-07-27 11:55:25 -05:00
landon 5ecbf3ffbb Add LanguageTool grammar/spell checking with a settings dialog
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>
2026-07-27 08:33:00 -05:00
landon 2b401b9a3b Add editor text zoom slider
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>
2026-07-26 15:21:27 -05:00
landon f05ee655c3 Add zero-pad option for numbered chapter titles
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>
2026-07-26 14:59:13 -05:00
landon 507e0ab724 Strip comments and header metadata on ODT export
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>
2026-07-25 19:01:45 -05:00
landon f473b63ee3 Initial commit: md-manuscript editor
Rust/egui desktop app: draggable markdown file list, editor, git sync,
per-file chapter titles, and native ODT export. Includes README and
Debian 12 (Surface) install guide.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 15:17:42 -05:00