Five productivity features: search/replace across the whole workspace
(with a safety commit before Replace all), a Synopsis window compiling
every chapter's slug, a Diff button per Auto-Snapshot to compare against
an older snapshot instead of only HEAD, typewriter-style cursor centering
and paragraph dimming gated on Distraction-free mode, and a per-day
word-count streak persisted to word_log.json alongside order.json/titles.json.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add a summary: field (with a short explanatory comment) to
01_character_template.md, and fill the field in on the two shipped
examples, 01_protagonist.md and 02_antagonist.md. Documented in the
Story Bible README's "Adding a New Character" example and its new
"Automatic Linking in the Draft" section, and in the main README's
Story Bible docs.
While wiring this up against the real bundled template rather than a
hand-written test fixture, found two bugs that were silently defeating
last session's auto-linking feature for every project made from that
template:
- extract_from_frontmatter() only read a title: field for an item's
name, but every bundled Story Bible template — character, location,
lore, object, all of them — writes name:, never title:. Every real
item's display name (and therefore everything matched against it in
prose) was falling back to a garbled, uncapitalized filename guess
instead. Now name: is read (title: still works too, for a sheet that
uses that word instead).
- build_editor_job()'s fast path skipped straight to an unformatted
run whenever there were no grammar/spelling issues and no active
search — without checking whether there was a Story Bible reference
to highlight. A name with no spelling issue of its own (which is
most of them) never got its purple background, and with it no visible
way to find the hover card that depends on it.
Both were invisible in the unit tests added last session because they
exercised find_references() directly against a hand-built index, never
through StoryBibleIndex::build() against real files, and the manual
smoke test happened to use a name ("Bixby") that the spell checker
flagged anyway — which incidentally kept the fast path from short-
circuiting. Added a disk-built-index test and a fast-path regression
test so both stay caught.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ui.set_width() was called on the shared horizontal-layout Ui before the
tree and editor panels were split off, so it shrank the whole row —
tree, separator, and editor panel together — down to a third of the
window's width instead of just the tree column. The editor's own
heading (the open item's path) was narrow enough to wrap one letter
per line as a result. Moving the width constraint onto the tree
panel's own child Ui, where it belongs, leaves the editor panel free
to take the rest of the row.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bare proper nouns in prose ("Bixby walked in") now resolve against the
Story Bible automatically, the same as the existing [[wiki-links]] and
@mentions markup — no manual linking required. Matching is case-sensitive
and requires a capitalized, 3+ character match to a sheet's name or alias,
which is what keeps a character named "Hope" or "Will" from lighting up
on every unrelated use of the word; the longest phrase wins an overlap
("Bixby Reynolds" over "Bixby"), and a bare match never overrides the
author's own explicit markup.
Hovering a highlighted mention shows a card with the item's name, type
(Person/Place/Thing) and a summary (from a new summary:/description:
frontmatter field), with a link that opens the entry in the Story Bible
editor. The card appears after a short delay and stays open while the
pointer crosses onto it to reach the link.
Along the way:
- StoryBibleReference now carries absolute byte offsets (start/end) and
the resolved item_id, replacing column/length, which were actually
per-line and were being used as whole-buffer offsets — every reference
past a file's first line highlighted and jumped-to in the wrong place.
find_references() also now walks the buffer one char at a time rather
than one byte, which could previously land mid-character and panic on
any manuscript with accented letters or an em dash.
- build_story_bible_index() had its own, separately hardcoded list of
Story Bible directory names that had drifted from
find_story_bible_directory()'s list and no longer included
03-Story-Bible, the bundled template's actual folder — so the index,
and everything built on it, was silently empty for every project made
from that template. It now reuses the one list.
- Story Bible Editor window: its tree and content ScrollAreas (and the
content TextEdit) had no id_salt, so egui treated them as the same
widget — fixed with explicit salts.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
- Add effective_project_template() method that checks if saved template path exists
- If not, falls back to default_project_template() (bundled template)
- This handles users who have old config.json pointing to deleted Cookiecutters path
- Update project.rs to use effective_project_template() when creating new projects
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Update project_template test to verify old directories are NOT created
- Update hover text and comments in project.rs
- Update comments and code in file_list.rs
- Update comment in spelling.rs
- Add test assertions to verify 05-Plot and 06-First Draft don't exist in new projects
This ensures all code references match the merged directory structure where
planning and draft content are combined in 05-First Draft with YAML frontmatter.
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Everything after YAML frontmatter closing --- is now considered the draft content
- No ### Rough Draft: marker needed
- Chapter titles are handled by the program on export based on file order
- Draft content should not include chapter titles
- Updated default_new_file_template to use YAML frontmatter
- Updated preprocess.rs to parse YAML frontmatter for metadata
- Updated all relevant tests
- Updated documentation in help.rs
- Updated template files
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Add new module src/app/auto_snapshot.rs for auto-snapshot functionality
- Add configuration options: auto_snapshot_enabled (default: false) and
auto_snapshot_threshold (default: 250 words)
- Automatically create git commits when word count crosses threshold boundaries
- Add 'Edit -> Snapshots' menu option to open snapshots window
- Snapshots window shows: enable/disable toggle, threshold setting, current
word count, next snapshot target, list of all snapshots with rollback buttons
- Manual snapshot creation via button in snapshots window
- Rollback to any previous snapshot with one click
- Snapshot info includes: word count, timestamp, commit message, commit hash
- Make run_git in gitsync.rs public for use by auto_snapshot module
- Update README.md with Auto-Snapshots documentation
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Add new module src/app/story_bible_editor.rs for dedicated Story Bible editing
- Add 'Edit -> Story Bible' menu option to open the editor
- Tree view (left 1/3) shows Story Bible directory structure with collapsible folders
- Editor (right 2/3) allows editing selected Story Bible items
- Auto-load first file on open, support Ctrl+S to save
- Rebuild Story Bible index after saving to pick up changes
- Update README.md with Story Bible Editor documentation
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Add YAML frontmatter support with split_yaml_frontmatter() in preprocess.rs
- Update create_scene_pair to create single merged files with YAML frontmatter
- Rename 06-First Draft to 05-First Draft and merge with 05-Plot content
- Update default_project_open_subdir to '05-First Draft'
- Add capitalizes filter to MiniJinja environment for template rendering
- Fix template placeholder escaping in CONSISTENCY.md
- Update all references from 06-First Draft and 05-Plot to 05-First Draft
- Update test expectations to match new folder structure
The merged approach uses YAML frontmatter (--- delimiters) at the top of
each draft file to store planning metadata, allowing it to be collapsed
in the editor while keeping all planning and draft content in one file.
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Add story_bible.rs module with StoryBibleIndex, StoryBibleItem, and
StoryBibleReference types for indexing Story Bible content
- Support wiki-style [[...]] and mention-style @... syntax for references
- Highlight Story Bible references in editor with purple background
- Add Story Bible window (View > Tools > Story Bible) to browse items
- Show references from current draft file in Story Bible window
- Allow clicking references to jump to their location in the editor
- Auto-index Story Bible items from common directories (03-Characters,
02-Story Bible, Characters, etc.)
- Extract metadata from YAML frontmatter or filenames
- Add walkdir dependency for directory scanning
- Fix pre-existing format string errors in workspace.rs
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Add depth and path fields to Beat struct to track heading hierarchy
- Rewrite beats() parser to use heading stack and process_headings() helper
- Add BeatTree enum with Heading and Beat variants for tree structure
- Add build_beat_tree() to convert flat beats to nested tree
- Add render_beat_tree() for recursive tree rendering with collapsible headers
- Add count_beat_tree() helper to count beats in subtree
- Replace flat list rendering with tree-based rendering in outline_window()
The implementation currently groups beats by section, with infrastructure
in place for full hierarchical nesting based on beat.path.
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Added create_scene_pair() method to workspace.rs
- Added '+ New Scene' button in file_list.rs UI
- Button creates both planning file (05-Plot/act_N/scene_XX.md) and draft file (06-First Draft/Act N/sceneXX.md)
- Files are automatically linked via YAML frontmatter (draft_file and planning_template fields)
- Auto-detects current act from folder context
- Auto-generates next scene number
- Selects the draft file after creation (ready to edit)
- Includes hover text explaining the feature
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
Implement a polling-based file watcher that automatically refreshes
the file list when markdown files are created, deleted, or renamed.
- Add background thread that polls workspace every 2 seconds
- Watcher starts when workspace opens, stops when app exits
- Uses pure Rust std library (no external dependencies)
- Fixes issue where new folders like Act 2 don't appear until manual refresh
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
Implement the outline file popup window that opens when a user
right-clicks on a folder in the file list (e.g., Act 1 in 06-First Draft)
and selects 'Outline' from the context menu. The window displays the
corresponding outline file (e.g., 04-Outline/scene_breakdown/act_01.md)
in a read-only view.
Changes:
- Add outline_popup_window() function in file_list.rs
- Add window rendering call in mod.rs update loop
- Popup reads file content on open and displays it in scrollable view
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Add explicit code in open_workspace to ensure manuscript folder is not collapsed
- Update README to clarify that project root is opened with manuscript folder expanded
- Keep workspace at project root but ensure file list shows 06-First Draft expanded
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
Gitea builds ssh_url from its own SSH_DOMAIN, which is whatever the
server was told to advertise. A server announcing a public hostname
whose SSH port is only open on the LAN hands out a remote that connects
from nowhere — and because "Create and attach" does not push, the
failure surfaces later, at the first Sync:
ssh: connect to host <wan-name> port 2228: Connection refused
The app already knows an address that works, having just made an API
call over it, so the SSH host is taken from the configured server URL
instead. The port Gitea reports is kept, as are the user and path, and
both URL shapes are handled (ssh://user@host:port/path and the scp-like
user@host:path). Setting the server to a tailnet address therefore gets
a remote that works on and off the LAN alike.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBWj9TphFMCoh7VHaSRnvQ
Repository creation was delegated entirely to a cookiecutter template's
post-gen hook. That only ever covered projects made through New project,
and when it went wrong it went wrong inside someone else's Python, where
all this app could report was that a hook exited non-zero.
The app now talks to Gitea itself. Settings > Git remote... gains a
"Create and attach" button: it POSTs to /api/v1/user/repos with the
configured server, user and token, then points origin at the clone URL
that comes back. A name that is already taken is fetched and attached to
rather than raised as an error, so retrying after a failure works.
The call runs on a worker thread with the existing channel-and-poll
pattern, so the UI does not block on the network. Errors are mapped by
status code, because a rejected token, a URL that is not a Gitea API and
a name the server refuses each need a different fix.
Uses ureq's send_string rather than send_json, whose feature this build
does not enable; no new dependency, and ldd still shows only
libc/libgcc/libm.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBWj9TphFMCoh7VHaSRnvQ
Attaching a workspace to an existing repository meant leaving for a
terminal: gitsync could init, commit and push, and could tell whether an
'origin' existed, but had no way to create one, and nothing in the
settings held a remote URL. Sync just logged "no 'origin' remote
configured" and committed locally.
Settings > Git remote... now shows the workspace repository's origin and
lets it be set, repointed or cleared. The URL is read from and written
to the repository, never to config.json, because a remote belongs to the
checkout — so each workspace shows its own, and the box is refreshed
whenever the repository in play changes.
This is separate from the Gitea publishing in New project, which is a
cookiecutter post-gen hook and only ever applies to generated projects.
Also factors out first_output_line, which repo_root and the new
origin_url were both open-coding, and origin_args, so the add-vs-set-url
decision can be tested without a repository on disk.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBWj9TphFMCoh7VHaSRnvQ
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
The app already treats `<!-- ... -->` as editorial notes: preprocess
strips them from the export and the word count. Ctrl+/ makes that a
one-key move, so a paragraph can be shelved without being deleted.
It grows the selection out to line boundaries first, and only toggles a
block back off when every non-blank line in it is already commented.
Blank lines are passed through so paragraph breaks survive the round
trip, and pressing it on an empty line opens `<!-- -->` with the caret
inside.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBWj9TphFMCoh7VHaSRnvQ
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
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