* chore: resolve SonarCloud maintainability issues
- Replace lambda closures with method references (S1612)
- Replace wildcard imports with explicit imports (S2208)
- Refactor complex functions with extracted helpers (S3776)
- Replace OR pattern with range pattern in math parser (S9047)
* style: apply rustfmt to warichu helper
* fix(compose): revert slice conversion in tests and remove unused var in warichu
* Group warichu row layout scalars to satisfy S107 parameter limit
* style: satisfy rustfmt in warichu.rs
---------
Co-authored-by: Dylan Taylor <dylan@craftmaker.dev>
A path passes when it lies inside one of the roots of its kind. The roots'
common parent, a sibling named like a root, `..` or a link out of them and
the other kind's roots stay refused; a folder given twice counts once.
Track text state through graphics saves and drawn forms; exempt invisible text. Report font names and pages in export warnings and validation without embedding or outlining placed fonts.
Found while testing apps/designcraft-cli/src/main.rs:361 (`--scale` parse) and :386 (export); crates/render/src/lib.rs:422-423 (render_page)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
frame.create, story.setText, text.insert and the other commands that take
text kept U+000B as a character, drawn as a missing-glyph box. It is the
forced line break of Word and PowerPoint text (Chr(11), Shift+Enter), and
what people porting scripts try first, so text_param now reads it as
U+2028, the forced line break, next to the CR handling it already has.
script --help and docs/agents.md say so.
Callers from outside the app now get a parameter error for a key a
command's params description doesn't name, instead of the command
running with its defaults: `text.select {story, start, end}` and
`style.list {bogus: 1}` are refused with "unknown parameter `…`" and the
nearest or the accepted keys.
- engine: `cmd::params` (ported from VectorCraft #1442) reads the keys
from each params description; `check_params` / `check_keys`.
`Session::execute` itself stays lenient so old journals replay; in the
engine's own tests it checks too, as the audit of the descriptions.
- MCP: tools/call answers -32602 for an argument a tool doesn't take and
for params a command doesn't take (`execute`, every `batch` step before
the first runs); the headless backend checks as well.
- Control channel (`engine.execute`) and `designcraft-cli run --cmd`.
- Descriptions now name every key their commands read (table target
cells, anchored objects, gap.move, file.place, data merge, prefs.set,
…); type.char / type.para stay unchecked (their attributes may be the
params object itself; the setters refuse unknown attributes).
- Tests that passed keys the commands never read are fixed, and the pen
tool no longer sends the unread `notifyPen`.
Fixes#776
upstream/main c3859c82 (#614e198eb5d, #6159509b2bc, #6168e151a2f, #617c3859c82).
Conflict: ROADMAP.md: header keeps next's change list plus orderly native exit recovery; progress log both
Conflict: apps/designcraft-cli/src/main.rs: next's case-insensitive .jpg/.jpeg check with main's write_atomic
Conflict: apps/designcraft/src/main.rs: on_exit keeps next's startup.presented() (not an Option) and adds preserve_documents_on_exit; next's discard_marked_text and main's preserve_documents_on_exit both; Services write uses write_atomic and keeps next's clipboard_text; tests both
Conflict: crates/engine/src/cmd/book.rs: main's ChapterBatch paginate with next's chapter-number resolution and {pages, chapters} result and doc
Conflict: crates/engine/src/cmd/file.rs: revert takes main's discard_document ordering without the revision + 1 (next's DocState::new hands out a fresh revision)
Conflict: crates/engine/src/recovery.rs: main's save_tracked, write_entry and open (st.bump_revision() for next's global revisions); next's reopen-twice test assertions kept
Conflict: docs/gaps.md, docs/target-app-parity.md: headers combined; next's Language, Books and Numbering lines plus main's book update safety line
Route desktop writes and CLI raster exports through the engine atomic writer. Add actual-caller regressions for reader visibility, private modes, symlinks and valid long filenames.
Follow-up to #485.
PR #614.
`run --pdf-options '[1]' --export a.pdf` panicked when the PDF's path
was added to the options (exit status 101). The value is now checked
where it is read.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
--export knew .pdf, .epub, .idml and .designcraft and saved a PNG of
the page under every other name: out.html, out.txt, OUT.PDF, and
out.designcraft from `script`. The exit status was 0.
.html and .htm now run file.exportHtml, .rtf and .txt run
file.exportText (the story being edited or of the selected frame), and
extensions match in any case. A name with another extension is an
error before anything runs; one with no extension is still a PNG.
`run` and `script` share one list, and `script` saves a copy for
.designcraft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Asking for help is not an error, but `designcraft-cli --help` shared the
catch-all arm with unknown commands, so it wrote the usage block to
stderr and exited 1 — unusable in a pipeline and wrong by convention
(`--version` already exits 0).
Split the arm: `--help`, `-h` and `help` now print the same usage block
to stdout and succeed, while an unknown command keeps it on stderr and
still fails. The usage text and links line are hoisted into a shared
`USAGE` const and `usage_footer()` so the two paths can't drift.
Fixes#243🤖 Generated with Codebuff
Co-authored-by: Codebuff <noreply@codebuff.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* CLI: run rejects an export option with no export after it (#67)
--page, --scale and --pdf-options apply to the exports that follow them; one
given after the last --export or --all-pages did nothing and said nothing.
The usage text and docs/agents.md say so.
* MCP: document that text offsets and story lengths are UTF-8 bytes (#68)
* MCP: docs name the UI-only commands precisely; app.links works headless (#62)
* README: PDF and EPUB export ship (#60)
New designcraft-pptx crate (L3) and file.exportPptx command (File ›
Export PowerPoint…, CLI --export x.pptx). Each page becomes a slide of
native objects: shapes with fills, gradients, strokes and effects,
pictures cropped like their frames, native tables, and text boxes.
Text keeps DesignCraft's composition by default: composed lines end in
line breaks, justified spacing becomes character spacing, and leading
becomes percentage line spacing (PowerPoint rounds exact spacing to
whole points; its 100% line is 1.2 em for every font). Threaded
stories split at their frames and wrapped lines get their own boxes.
lineBreaks: reflow writes one box per frame in native columns instead.
TrueType fonts are embedded as Embedded OpenType when their licences
allow. Objects PowerPoint can't represent (placed PDF/EPS, type on a
path, some effects) are rasterised through the existing
rasterize_where path.
Checked in PowerPoint for Mac and LibreOffice: on the sample magazine,
word positions match DesignCraft's PDF to within 0.8 pt horizontally
and 1.6 pt vertically.
`designcraft-cli commands | head -1` panicked and exited with status 101:
thread 'main' panicked at library/std/src/io/stdio.rs:1168:9:
failed printing to stdout: Broken pipe (os error 32)
println! panics when stdout is closed. Output now goes through outln!, which
ends the program with status 0 on a broken pipe (the reader has what it
wanted) and reports any other write error on stderr with status 1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
perf/bench return errors instead of expect() (bench runs on any document),
and the Windows resource build step fails the build without panic!. The
three app crates deny the panicking lints.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New crate designcraft-pdf (L3): pages or spreads with MediaBox/TrimBox/BleedBox,
parent items with per-page folios, fills (solid + gradient swatches), aligned/dashed
strokes, corner options, opacity/blend, simple drop shadow, clipped images (JPEG
passthrough), and real text: composed glyph runs with embedded font subsets and
Unicode mapping from the story. CMYK as DeviceCMYK, RGB as DeviceRGB, spot swatches
as Separation, [Registration] as /All. Crop/bleed marks and page info. PDF/A-2b via
krilla; PDF/X-4 writes PDF 1.6 (output intent TODO).
Engine command file.exportPdf {path?, pages?, spreads?, bleed?, marks?, standard?,
compressImages?, title?, author?} (base64 when no path), CLI --export out.pdf with
--pdf-options, File > Export PDF... in the UI.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>