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>
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 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>
Window, Utilities, Data Merge lists the linked fields and previews a
record without merging. Create Merged Document runs the merge. The
desktop open dialog offers CSV, TSV, tab, text, and Excel files. The
roadmap records this phase of the feature.
A line that is not a JSON object with a string `method` (an HTTP request
line, other non-JSON text, a JSON array, invalid UTF-8, a line over 4 MiB)
now gets one error reply and the connection is closed, so a browser's
cross-origin fetch can't smuggle a command in its body. Connections are
capped at 16. Same transport as LightCraft (storytold/lightcraft#129) and
FilmCraft (storytold/filmcraft#123).
`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>
The MSI Icon id had no .ico/.exe extension, which Windows Installer needs for
advertised shortcuts and ARPPRODUCTICON, so both showed a blank icon. Release
builds of the desktop app are now GUI-subsystem (no console window; the CLI stays
a console program), the shortcut/summary description names what DesignCraft does, and
packaging-lint rejects an Icon id without an extension. Found in printcraft#57.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A FreeBSD workflow builds and tests the workspace in a FreeBSD 14 VM
(vmactions/freebsd-vm) on pushes to main, dependency changes and on
demand. The X11 backend choice for file drag-and-drop covered only
Linux; FreeBSD desktops use the same X11/Wayland stack, so it now applies
to all(unix, not(macos)).
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>
winit 0.30 has no file drag-and-drop on Wayland. DESIGNCRAFT_WAYLAND=1
keeps the native Wayland backend.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- designcraft-textimport (L3): DOCX (styles by name with spacing/indents/alignment and font,
size, bold/italic/underline/strike/caps/position; runs, tabs, breaks, footnotes, tables,
hyperlink/field results; used styles only), RTF (text, paragraphs, bold/italic/underline/
strike, size, Unicode/cp1252, skipped destinations), plain text (+ tests)
- engine: file.place routes text files into the insertion point, the selected frame or a new
frame on the page; imported styles added (existing win); removeStyles; autoflow adds pages
with threaded frames until the story fits (+ test)
- app/web: Place dialogs accept .docx/.rtf/.txt/.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- color: Swatch.hidden — unnamed colours (applied, not listed in Swatches until added)
- engine: object.color (reuses a swatch with the same colour, else an unnamed one; fill or
stroke), swatch.addToSwatches (optionally named; users follow the rename),
swatch.addUnnamed (+ test)
- UI: Color panel with fill/stroke proxy, CMYK/RGB/Lab, channel sliders with gradient tracks and
value fields, tint slider for named swatches, spectrum ramp, Add to Swatches; Color Picker
dialog (double-click a proxy chip); Swatches hides unnamed colours
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- engine::recovery: unsaved documents written to a recovery folder (atomic write + metadata),
reopened as unsaved documents that remember their files; entries dropped on save/close
(+ tests); file.recovery.list/save/open, file.revert, file.saveACopy (+ test)
- app: recovery folder per platform, recovered on launch with a status message; UI writes
recovery data every 30 s while documents are unsaved; File › Save a Copy…, Revert
- parity: recovery, revert, save a copy done
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- render: damage()/damage_with() — changed items and only the frames whose composition changed
(cache keeps the composition each edit replaced; Cache::composed_for matches a document
version exactly); conservative full repaint for styles, swatches, layers, parents, wrap,
effects and cross-story text (+ tests incl. pixel-identical patch vs full render)
- UI: canvas patches its texture after edits with the view unchanged (0.6 ms vs 22 ms on the
sample when typing); perf harness row for the damage repaint
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- doc: FOOTNOTE_REF marker + Story::notes (kept in sync on insert/delete/thread), FootnoteOptions,
NumberStyle::Symbols, footnote text addressed like cell text (CellAddr::footnote)
- compose: notes numbered (continuous / restart per page, spread, section), composed at column
width, body text makes room, stacked at the column bottom with the rule above
- render + PDF draw footnote text; IDML export/import in InDesign's structure (verified in
InDesign 2026), FootnoteOption preferences
- engine: footnote.insert/options/list/setText/delete/goToReference (+ tests)
- UI: caret/selection in footnote text, Footnote Options dialog, Type menu entries; menu items
labelled '…' with parameters open a dialog generated from the command's params doc
- sample magazine carries a footnote
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>