* docs: register the MCP server from an installed release
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs: drop the third-party link; the Windows path is the default install folder
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Reuse packaged-link candidates for embedded IDML images and native files.
Resolve missing original paths beside the opened document, retaining the
embedded image bytes and any original link that still exists. Bound path
lookup allocations and cover actual package relocation through commands.
Make existing data-merge test path assertions portable to Windows.
Co-authored-by: Codex <[email protected]>
The rows share one fixed height that preserves their composed total. Merged rows and rows that are not laid out are refused, and the table stays unchanged.
* Desktop: write log records to standard error and a rotating log file
The desktop app installed no logger, so every log::warn!/log::error! in
the workspace was discarded: the crash guard's panic report, a failed
crash-recovery save, font folders that can't be read. A launch from a
desktop menu has no terminal either, so there was nothing to attach to a
bug report.
apps/designcraft/src/logging.rs (no new dependency):
- standard error plus <settings dir>/logs/designcraft.log (beside
ui.json and prefs.json); each launch rotates the previous log to
designcraft.1.log and .2.log, so the log of a run that crashed
survives the next start; the file is capped at 16 MiB
- records logged before the settings directory is known are buffered
(bounded) and written once the file is attached
- default levels: info for designcraft*, warn for everything else;
RUST_LOG replaces them with env_logger-style directives
- never panics: an unusable directory or a failed write leaves standard
error as the only sink
The logger is installed first in main (before the engine's panic hook)
and the file is opened after argument parsing (--version leaves no
file). The settings directory moves out of prefs_path into config_dir.
A --control or DESIGNCRAFT_CONTROL_PORT value that is not a port is now
logged instead of silently leaving the control channel off. Documented
in README.md (Quick start > Logs).
* Desktop: no eprintln! on the log and panic paths
eprintln! panics when standard error is a broken pipe (a GUI launch on
Windows), and a panic inside the panic hook aborts. The "no log file"
message goes through log::warn! (standard error only by then); the panic
hook's fallback writes to standard error with write_fmt and ignores the
error. Same fix as storytold/vectorcraft#465.
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 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
A placed PNG, GIF or WebP that is cut off (an interrupted download) or
has a bad checksum made the whole PDF export fail with
"PDF writer error: Image(Image(ImageRepr {..}), None, "unexpected end of
file")", which names no file. A damaged TIFF or BMP in the same place is
skipped with the warning "image `name` could not be decoded and was
skipped", and the rest of the document is exported.
The exporter decides whether an image can be embedded by asking krilla
to open it. krilla opens PNG, GIF and WebP lazily: it reads the header,
accepts the file, and decodes the pixels only when the PDF is written,
where an error ends the export. TIFF and BMP are decoded by us first, so
they took the skip path.
lossless() now decodes the image first for every format. A file that
does not decode returns None and takes the existing skip-with-warning
path. This costs one extra decode per PNG, GIF or WebP asset: the sample
magazine (two PNGs, 820 KB) exports in 0.19 s instead of 0.17 s in a
debug build.
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
In every workspace that shows the Control panel (Advanced, Typography,
Printing and Proofing, Book), the second row of fields was cut off at the
panel's lower edge: Y and H in object mode, leading, tracking and the
indents in text mode.
The panel lays its groups out in a row that centres each child
vertically. The two-row grids were placed as if they were one row tall
and then grew downward, so they started 13.5 pt below the panel's top
instead of at it and ended 5.5 pt past the 59 pt panel.
The row is now top-aligned, which is where the font menus, the
separators and the reference-point proxy already sat.
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* Keyboard Shortcuts: the recorder waits for the key pressed with the modifiers
* Workspaces: Interactive for PDF and Digital Publishing open their own panels
* UI: the Quick Apply shortcut doesn't also run its first entry
Cmd+Return opened Quick Apply, and in the same frame the list saw the
same press as Enter and ran its first entry (New Document). A key press
that fires a command shortcut is now consumed, so nothing else handles
it that frame. This also stops Cmd+Return from inserting a paragraph
break while typing.
* UI: Quick Apply rows are left-aligned
Rows without a shortcut or style kind were drawn with their label
centred, because add_sized lays a button out centred and only the rows
with a shortcut have an element that fills the remaining width. Each
row is now laid out left to right in a fixed 460×22 area.
* UI: Quick Apply runs its first entry only on a fresh Return
Pressing Cmd+Return again while Quick Apply is open, or holding it until
it repeats, reached the palette as Enter and ran the first entry. The
palette now counts only a Return press that isn't a repeat and has no
Cmd.
Packages name the folder `Document fonts`; on a case-sensitive file system, opening a document beside it loaded none of its fonts. The exact name still wins when both exist.
`object.opacity` documents `blend?: normal|multiply|…`, but parsed the
value with `serde_json::from_value`, which only accepts the enum's
PascalCase variant names. Every documented lowercase form was silently
dropped: the command returned `{"changed": 1}` while the stored blend
stayed as it was, so a client following the parameter description could
save and export the previous appearance without any error (#260).
Parse with the codebase's own `BlendMode::parse`, which is already
case-, space-, underscore- and hyphen-insensitive and covered by tests,
so `multiply`, `MULTIPLY`, `Color Burn`, `soft_light` and the serialized
`Multiply` all resolve to the same mode. An unrecognized value is now an
error rather than a silent no-op, matching how sibling commands treat a
bad parameter — restoring `normal` after `Multiply` really does go back
to Normal.
Three tests in `blend_mode_tests`: the documented forms apply, the
PascalCase names the Properties panel submits still apply, and an
unknown value is rejected without changing the item.
Fixes#260🤖 Generated with Codebuff
Co-authored-by: Codebuff <[email protected]>
A frame that a column, frame or page break ends early held only a few
characters, and autoflow took that as the rate frames fill at: placing
text that starts with a page break added a page per few characters
(33 pages for 98 characters, 271 for a one-page Word section). The
estimate now comes from full frames only; with none yet, one page is
added and the story measured again. Regression test covers a leading
break and a break between every paragraph. Fixes#262.
Claude-Session: https://claude.ai/code/session_01GLcnoDFjqsh5x3MzVbfZeb
Co-authored-by: Claude Opus 5.5 <[email protected]>
Text inside a tracked move destination was dropped on .docx import, so
moved passages vanished from both places. Treat moveTo like ins (both
kept as accepted); moveFrom stays skipped like del. Regression test
covers ins, del, moveFrom and moveTo together. Fixes#256.
Also adds my contributor credit entry.
Co-authored-by: Claude Opus 5.5 <[email protected]>
Placing an RTF or Tagged Text file dropped every character outside the
Basic Multilingual Plane: emoji, mathematical letters and the rarer CJK
ideographs such as 𠮷 (U+20BB7, used in Japanese names). The text
around them came in, so the loss was silent. It also broke our own round
trip: Export Text writes these characters correctly and Place lost them.
Both formats carry such a character as a UTF-16 surrogate pair, two
units in a row: \u-10179?\u-8704? in RTF, <0xD83D><0xDE00> in Tagged
Text. Both readers passed each unit to char::from_u32 on its own. Half a
pair is not a character, so each half was dropped.
A small shared Utf16Units joins the units: a first half waits for the
second, and the pair becomes one character. Half a pair with no partner
is still left out, as before.
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
* UI: right-clicking a ruler picks that ruler's units, and the vertical ruler measures in the vertical units (#133)
* UI: the Properties panel's Character section sets the text's language, from the same list as the Character panel (#130)
DrawCraft was renamed VectorCraft, and its repository is now
storytold/vectorcraft. Three places still used the old name:
- AGENTS.md and CLAUDE.md told contributors and agents that the sibling
checkout lives at ../drawcraft. A clone made from the README link lands
in ../vectorcraft, so that path does not exist.
- The About window's sibling list used the app-page slug "drawcraft".
getartcraft.com/apps/drawcraft still redirects to /apps/vectorcraft,
so the link worked, but the README (which the list says it matches)
uses /apps/vectorcraft.
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
In the browser, pressing AltGr while editing text on the canvas inserted
the word "AltGraph" into the story.
eframe's web backend (0.36.2, web/input.rs text_from_keyboard_event)
turns a keydown into a text event unless the key is on a fixed list of
names. Browsers report many more named keys than that list holds:
AltGraph, Dead, Unidentified, the media keys. Each one arrived as text.
Panel text fields are not affected, because a focused egui text field
takes its text from the browser's input events instead of keydown.
The web shell now drops a text event that is a key name (ASCII letters
and digits, starting with a capital, longer than one character) unless
an egui text field has the keyboard. One key press never types such a
word; a text field's input events can, so they are left alone.
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
Three commands took a count from their parameters and used it unchecked.
Parameters come from MCP, the control channel and scripts, so the count
is hostile input (never-crash: cap input-sized allocations, checked
arithmetic).
- layout.createGuides made one or two guides per row and column with no
upper limit: 30 million rows took 1.2 GB, and 2^32 - 1 would exhaust
memory, which aborts the process past the command guard. A count over
2^32 wrapped to a small one. Both counts are now capped at 100.
- edit.stepAndRepeat multiplied rows by columns as usize. In debug builds
the overflow panicked; in release builds 2^32 x 2^32 wraps to 0, passes
the 1000-copy limit and starts a grid of 2^64 copies. The product is
now checked, and a grid that large falls back to `count` copies like
any other grid over 1000.
- file.new stored any column count in the page (4 billion, or a wrapped
value past 2^32). Page::column_rects builds one rect per column each
time the columns are laid out. The count is now clamped to 1..=216,
the range layout.marginsAndColumns already uses.
Each case has a regression test that failed before the fix.
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
* PDF: PDF/A exports keep placed PNG, JPEG, GIF and WebP images by writing them without interpolation (#71)
* Tables: new tables take their paragraph's direction, and the Table panel sets a table's direction (#100)
* Text: stories made in a right-to-left-bound document fill their columns from the right (#100)
* 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)
* UI: toolbars that don't fit the window switch to two columns or scroll (#57)
In a short window the Tools panel lays its tools out in two columns,
without changing the single/double-column preference, which comes back
when the window grows again. While the window forces two columns the
column toggle is disabled. Whatever two columns can't fit scrolls
vertically.
The Control panel and the application bar scroll sideways when their
controls are wider than the window. The Control panel keeps its height.
In the application bar the workspace controls follow the menus instead of
overlapping them. A plain mouse wheel scrolls these one-direction bars,
and a thin floating scroll bar shows only when something overflows, so
nothing changes while everything fits.
Tool buttons, icon buttons and the Fill proxy carry accessibility
labels, which the new window-size tests use to find them.
* UI: captions in a sideways-scrolled bar stay on one line
The last group of a scrolled Control bar row got only the width left
over, so a caption like Columns wrapped one letter per line.
The macOS menu bar takes Cmd+A, Cmd+C, Cmd+X, Cmd+V, Cmd+Z and Cmd+Shift+Z
before the window sees them, so in a panel, dialog or search field they ran
the document's commands: Select All selected every frame, Paste pasted into
the layout, Undo undid the last document change.
While a text field has keyboard focus, these Edit items now act on the field,
whether chosen by shortcut or from the menu: Select All, Undo and Redo reach
it as their keys, Copy and Cut as clipboard events, and Paste asks the
window for the system clipboard. The items stay enabled for the field even
when the document has nothing to copy or undo. With no field focused, and
for every other item, the menu works as before.
* Opening an InDesign document says to export IDML (#21)
.indd and .indt files (recognised by their published 16-byte signature)
failed to open or place with a confusing format error. They now explain
that the format is closed and how to export IDML from InDesign instead.
File › Open offers .indd and .indt files (desktop and web), and dropping
one on the window opens it like a .designcraft or .idml file. A file that
can't be opened or placed from File › Open, Place, a drop, the command
line or the control channel's app.open shows an alert with the reason, as
well as the status line.
* File open: an .indd file is an unreadable file like any other
Remove the INDD signature check and message, and .indd/.indt from the
Open filters and drop routing. The alert tests use a generic unreadable
file.
* Control channel: a failed app.open answers with the error and opens no alert
An alert waits for a click that a control client can't give. app.open
reports the error in its response, as engine.execute does; File › Open,
drops and the command line still show the alert.
Rust 1.98 adds the clippy lint chunks_exact_to_as_chunks. It flags two
loops in the PSD decoder, so `cargo xtask ci` stops at the clippy step on
unmodified main for anyone on current stable.
Both loops now use as_chunks / as_chunks_mut (stable since 1.88, below the
workspace rust-version 1.90). The chunks are arrays, so the try_from that
could never fail goes away too. Decoded pixels are unchanged; the existing
PSD tests cover both paths.
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
The DMG opened as a plain Finder window. It now shows the app icon's figure on
the DesignCraft colour, with DesignCraft.app and the Applications link side by side.
Same approach as PhotoCraft's and VectorCraft's (storytold/photocraft#1201,
storytold/vectorcraft#493).
- packaging/macos/dmg/: background.svg (source; text outlined, no paper grain,
the figure an <image> of assets/app-icon), background.tiff (1x + 2x HiDPI,
16-colour palette, Deflate, sRGB; ~90 KB)
and dmg-layout.DS_Store (Finder view settings: window, icon size 128,
positions, background).
- generate.py writes the TIFF and the layout on any OS, byte-for-byte
reproducible, with nothing from the machine that ran it (no paths, users,
disk names or volume UUIDs).
- package.sh copies them into the staging folder; the image is still built with
hdiutil makehybrid. Nothing extra runs in the signing job.
- The volume name drops the version: the layout finds the background through
an alias that includes the volume name. The DMG file name keeps the version.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- freebsd job: builds packaging/freebsd/package.sh in a FreeBSD 14.3 VM (same image and
packages as freebsd.yml) into designcraft-<ver>-freebsd-x86_64.tar.gz.
- flatpak jobs (x86_64 on ubuntu-24.04, aarch64 on ubuntu-24.04-arm): repackage the Linux
tarball with packaging/linux/flatpak-bundle.sh and a new bundle manifest; runtime and
finish-args shared with the from-source manifest (checked by packaging-lint).
- AppImages embed gh-releases-zsync update information and ship a .zsync beside them.
- version, linux, web, flatpak and freebsd no longer use the release environment (they read
no secrets), so a workflow_dispatch on a branch dry-runs them; the signing jobs and the
draft release stay environment-gated.
- nfpm package description described an image editor; now describes DesignCraft.
- README: Downloads section listing every build.
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Resolve i18n conflicts: keep this branch's revised zh/ar wording plus
main's new rows (font-name, missing-glyph, Korean line-break labels).
Brazilian Portuguese (#103) now uses the same cached HashMap lookup as
Arabic; the cached-lookup test covers pt-br too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Resolve conflicts keeping both sides: doc gains both `cjk` and
`datamerge` modules; file.open resolves data-merge sources and loads the
document's fonts; DocState keeps preview_stash/preview_record and fonts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>