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 <noreply@openai.com>
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.
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 <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
- 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 <shared2@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.
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).
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
Custom macOS larger runners in a runner group do not pick up jobs; the
built-in M2 Pro label does. Only release.yml uses it, and the
actions_macos_xl budget caps the monthly spend.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>