* M6: close the TOC field in its last entry, not in a text box inside it
`para_inner` took `toc_end_here` only after writing the paragraph's content,
so a nested story in the last TOC entry (a text box) reached `para_inner`
first, took the flag, and wrote the field's end inside `w:txbxContent`.
Take it before the content.
Review follow-up on #52.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M6: Update Table reads a heading above the TOC at its own index
Pass 1 shifted every heading by `entries.len()` to account for the entries
now in the body, but entries go in below the TOC heading, so a heading above
it never moves. Its lookup landed on a TOC entry and took that page (a
Preface on page 1 above a TOC on page 2 showed 2). Shift only headings below
the TOC.
Review follow-up on #52.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M6: start the TOC field in the first entry, as Word does
The writer opened the TOC field at the end of the "Contents" heading.
Word reads that fine, but LibreOffice splits the heading: "Content" as
body text, then "s" as the TOC title (on the next page when the TOC
follows a page break). Word itself writes the heading before the field
and begins the field in the first entry; written that way, LibreOffice
shows the heading whole.
Hold the heading's field back and write its begin/instruction/separate
at the start of the first entry, taken before the paragraph content so
a text box in the heading or an entry can't claim it (as with the end).
The reader moves a TOC field that starts a paragraph right after a TOC
Heading back to the end of the heading, so WordCraft's own files read
back as before and a TOC saved by Word now updates in WordCraft too
(before, Update Table left Word's first entry behind as a duplicate).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
`hostile_params_never_panic` runs every command four times, Read Aloud
included, and on macOS that spawns `say` with the whole sample document:
four overlapping voices for minutes after `cargo test` returns. Skip it
with the other commands that reach outside the session; it ignores its
params, so junk payloads had nothing to exercise there.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Typing parity with Word: lists, AutoFormat and AutoCorrect as you type
Typed side by side with Word (same keystrokes, results read back) and matched it:
- Enter after a list no longer gets stuck: the paragraph that leaves a list goes back to
Normal, and later Enters make blank paragraphs (the `num: 0` marker counted as a list item,
so every Enter re-ran "leave the list" and did nothing; the list indent also stuck to every
following paragraph).
- Enter on an empty nested item moves it up a level before leaving the list.
- Second Backspace at the start of an un-numbered list paragraph returns it to Normal
instead of joining it to the paragraph above.
- "1. " always starts numbering at 1; "a. " and "I. " start lettered/roman lists; "- " makes
a dash bullet.
- Undo right after an AutoFormat/AutoCorrect change undoes only that change.
- AutoCorrect: sentence capitals (with abbreviation/initial exceptions), runs on Enter too,
"word -- word" en dash, "wait..." ellipsis, 1/2 1/4 3/4, ordinal superscripts, web
addresses become links.
- Border lines: Enter after ---, ===, ***, ___, ~~~, ### draws a bottom border.
- Layout: no line break right after a slash ("and/or", URLs), as in Word.
- ui_shot: UI_SHOT_FULL=1 prints whole replies for scripted checks.
Regression tests in crates/engine/src/tests_typing.rs and the layout tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Typing parity: record Word's observed typing rules, two more tests
docs/typing-parity.md lists what Word does for each key sequence (lists, Backspace, Tab,
AutoCorrect, AutoFormat, slash line breaks) with the test that pins it, the known gaps, and how
to compare without taking over the shared machine. CLAUDE.md/AGENTS.md point at it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M6: Update Table puts each heading on its real page
The second pass removed the entries and laid the document out again before
looking headings up at `path + entries.len()`, so every lookup landed past
its heading: later entries showed the next heading's page and the last fell
back to page 1. Lay out with the entries still in place, then replace them.
A heading typed after a manual page break also starts with the break, so its
page is now read after any leading breaks rather than before them.
The existing test only had a heading on page 1; the new one puts headings on
pages 2, 3 and 4 (before: 4, 1, 1).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M6: save the table of contents as a TOC field Word can update
The writer saved the TOC heading's field empty (begin, instruction, separate
and end all at the end of "Contents"), with the entries as plain paragraphs
after it. Word showed them as ordinary text: no TOC frame, and no Update
Table or Update Field on the entries.
Write the field so its result spans the entries (closed at the end of the
last entry) and wrap heading and entries in the Table of Contents content
control (w:sdt with docPartGallery "Table of Contents", ECMA-376 17.5.2).
The reader already keeps a multi-paragraph field's start as an empty field
in its first paragraph and the rest as text, so WordCraft reads the TOC back
exactly as before; the new round-trip test checks both the file and that.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <shared2@storyteller.ai>
apply_style's guard ended with `&& id == "Normal"`, making the
`id.starts_with("Heading") || id == "Title"` terms dead: only Normal
cleared a numbered paragraph's direct numbering, so applying Heading 1/2/3
or Title to a list item left the number behind — contradicting the
comment right above it. Drop the redundant term and cover it with a
regression test.
Also clears the clippy overly_complex_bool_expr error this expression
raised, which was failing `cargo xtask ci`.
Co-authored-by: justinjohn0306 <babyjjbabyjj549@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>