Refs #257. Show matching entries and their ancestors, keep original edit paths and preserve per-document view state.
Assisted-by: OpenAI GPT-6.1 (gpt-6.1-sol)
* Issue #157: the Windows portable zip keeps settings, logs and recovery beside pdfcraft.exe
The portable zip had no portable.txt, and the app had no portable mode: it always wrote %APPDATA%\PdfCraft and %LOCALAPPDATA%\PdfCraft. Now portable.txt (or PdfCraft.portable) beside the executable puts the settings, log files, crash recovery and new digital IDs in PdfCraftData next to it, as PhotoCraft does, and the zip ships portable.txt. An unwritable PdfCraftData falls back to the per-user folders and says so.
* Issue #157: update ATTRIBUTION.toml SHA-256 for the translation catalogs this PR changes
---------
Co-authored-by: Brandon Thomas <[email protected]>
`tools/list` returns all 132 tools (109,578 bytes, ~27k tokens), which an
agent pays on every session. With `--compact` (library:
`McpServer::with_compact(true)`) it returns the 10 core tools
(`mcp::COMPACT_CORE_TOOLS`) plus `tool_search` (query / category / name
with the full schema) and `tool_call` (runs any tool through the same
`Automation::call`): 12 tools, 7,697 bytes. `tools/call` by name keeps
working for every tool; without the flag nothing changes. Same shape as
`lightcraft-cli mcp --compact`; the meta tools are not called
`*_command` because "command" already means a registry command here.
Both meta tools reject unknown outer keys and report failures as
`isError` results; `tool_call` honours `--root` exactly like a direct
call. Tests: automation integration tests (compact list, discovery,
dispatch, errors, root confinement) and a CLI test that launches the
binary with and without `--compact`.
Co-authored-by: Oldrich333 <[email protected]>
Co-authored-by: Claude Sonnet 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* #305: harden Windows MSI deployment and progress UI
Co-authored-by: Codex <[email protected]>
* Keep the MSI manufacturer as Learning Machines LLC
The company name matches the Linux packages; changing it is the owner's call.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* #305: keep compiled-MSI checks compatible with PowerShell COM
Validated the compiled x64 MSI and negative scope, publisher and progress mutations.
Co-authored-by: Codex <[email protected]>
* ROADMAP: the publisher name is unchanged
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Codex <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Also drop "signed installers" from the thin-or-missing list: release installers are signed.
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
* Release: Flatpak bundles (x86_64, aarch64), AppImage zsync updates, README Downloads
- AppImages embed gh-releases-zsync update information and ship a .zsync beside them;
the Linux job installs zsync and checks both.
- New flatpak job (x86_64 on ubuntu-24.04, aarch64 on ubuntu-24.04-arm) repackages the
Linux tarball with packaging/linux/flatpak-bundle.sh and the new bundle manifest, then
smoke-tests it in the sandbox. finish-args shared with the Flathub manifest (packaging-lint
checks they match): Wayland/X11, dri, CUPS for printing, Documents and Downloads.
- Only the signing jobs and the draft-release job keep `environment: release`, so a branch
dispatch is a dry run of everything else and can never draft a release.
- README: a Downloads section listing every build; docs/releasing.md updated.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Flatpak: no CUPS socket (the runtime has no lp); document printing as unavailable there
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
The desktop app installed no logger, so every log::warn!/log::error! in
the workspace was discarded: a failed autosave, a page that would not
render, a render worker that could not start. A launch from a desktop
menu has no terminal either, so there was nothing to attach to a bug
report.
apps/pdfcraft/src/logging.rs (no new dependency besides log):
- standard error plus <settings dir>/logs/pdfcraft.log, next to app.ron
(Linux $XDG_DATA_HOME/pdfcraft); each launch rotates the previous log
to pdfcraft.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 pdfcraft*, 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, so the panic hook's report is
recorded too; the start-up messages in main go through it. The file is
opened after argument parsing (--version leaves no file) and after the
PrintCraft folder migration, which a fresh settings folder would block.
Documented in docs/development.md (Logs, environment variables).
* i18n: Czech interface language
Adds Czech (Čeština, code `cs`) next to English and Japanese:
Preferences ▸ Interface language, `ui.set language cs`, `ui.state`
and persisted preferences all accept it.
- i18n: `Language::Cs`; `tr()` picks the language's table with a match
(English has none), panic-free. The CZECH table (83 entries) covers
every label passed to `.tr("…")` (main menu, View zoom/theme/side-panel
entries, Preferences dialog), every registered File/Edit/Pages/View/Help
command shown by `registry_menu`, and every key of the Japanese table.
Written clean-room from the English meaning, not from any Acrobat
localisation.
- Tests: Czech covers every registered menu title and label, every
`.tr("…")` literal found by scanning the crate's sources, and the
Japanese keys; nothing is left identical except an explicit KEEP_AS_IS
list ("OK"); ellipses match the source; keys are unique per language;
`cs` round-trips through set_option, ui.set/ui.state and persistence.
- theme: the font setup is split into `font_definitions()`, and a test
checks with skrifa (dev-dependency, already in the tree via egui) that
Inter Regular/Medium/SemiBold and JetBrains Mono draw all Czech letters
and every Czech translation, so nothing falls back to Shippori Mincho.
- The `language` error lists `cs`; docs/localization.md, the parity note
for misc.prefs-language and the ROADMAP log mention Czech.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
* M14: translation catalogs, same system as PhotoCraft
Move the interface translations to PhotoCraft's i18n system: one TSV
catalog per language (crates/ui-egui/src/i18n/ja.tsv), the tl! macro,
entries keyed by command id, context or plural forms, and catalog tests
(format, duplicates, placeholders, command ids).
The 49 existing Japanese strings move over unchanged, plus "Auto". The
language preference now defaults to "auto" and follows the system
language (English when it has no catalog); "en" and "ja" still persist
and unknown values are rejected.
Update docs/localization.md, ROADMAP.md and the misc.prefs-language
parity entry.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
* M14: complete Japanese dialogs, panels, shortcuts and diagnostics
Expand Japanese from 49 to 1830 interface entries, expose language preferences in the Edit menu, preserve opaque template and history values, and localize known application diagnostics only at the UI boundary. Add control, persistence, template and desktop/web glyph regressions.
Co-authored-by: Codex <[email protected]>
* i18n: add Brazilian Portuguese (pt-br) UI translation
Adds Language::PtBr (code pt-br, endonym Português (Brasil)) with serde rename so it persists as "pt-br", registers it in Language::ALL (Preferences ▸ Interface language and ui.set/ui.state) and translates the same 49 menu/command keys as the Japanese table. Extends the i18n tests (table parity, codes, persistence) and updates docs/localization.md.
* M14: strict catalog validation, one-pass fmt, translation assets
Fold in the infrastructure parts of #175:
- `fmt` fills placeholders in one pass, so inserted values (a file name
containing `{n}`) are never substituted again.
- Catalog validation rejects unknown escapes, unknown `@` contexts,
placeholder, ellipsis and plural-form mismatches and duplicates. The
bundled catalogs must have no errors (tests); at run time a bad line is
still only skipped and logged, so the rest of the catalog loads.
- `.tsv` files count as assets in `cargo xtask assets`, with a
`translation` kind that may never come from Adobe; ja.tsv gets its
ATTRIBUTION.toml entry.
- Control-channel test: switching the language keeps documents, their
dirty state and command ids.
Also make the drawing language thread-local, as in PhotoCraft, so
parallel kittest harnesses can't change each other's language.
Co-Authored-By: 才倾梦华V <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* M14: Brazilian Portuguese as a TSV catalog
Move #165's Portuguese table into crates/ui-egui/src/i18n/pt-br.tsv
(same 49 strings, plus Auto) and register it in LANGUAGES with
PhotoCraft's Portuguese plural rule (0 and 1 singular). Any Portuguese
locale (pt_BR, pt_PT, pt) follows the Brazilian catalog, as in
PhotoCraft. The catalog is an attributed asset (ATTRIBUTION.toml), and
the docs and parity notes list the new code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* M14: Czech as a TSV catalog
Move #68's Czech table into crates/ui-egui/src/i18n/cs.tsv (PrintCraft
labels renamed PdfCraft, plus Auto) and register it with PhotoCraft's
Czech plural rule (one / few / other). #68's tests become catalog
tests: Czech covers every registered menu title and menu command label,
keeps ellipses, uses no three-dot ellipsis or stray whitespace, and only
leaves "OK" untranslated; the code persists and `cz` is rejected. The
source-scanning `.tr("…")` test is dropped: strings now go through tl!
and the catalog tests check consistency.
The font-coverage test moves to tests/fonts.rs and covers both Latin
catalogs (cs, pt-br) against Inter and JetBrains Mono. It keeps skrifa
as a dev-dependency (the version pdfcraft-fonts already uses): egui's
has_glyphs can't answer it, since a family with only the primary face
also uses that face as its replacement face.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* M14: route every UI string through the catalog; Japanese as ja.tsv
Port #141's wiring to the catalog API from land/106:
- `crate::i18n::tr(ui, s)` becomes `tl!(s)`, `tr_fmt`/`tr_template`
become `i18n::fmt(tl!(..), ..)`, and `Language::get`/`store` (the
egui temp-data copy of the language) are gone: the drawing language
is the per-thread `i18n::current()`, set from the preference at the
start of each frame. Worker threads (export, OCR, batch actions) take
the UI's language with them for their summaries.
- ja.rs (1,830 pairs) becomes ja.tsv, merged with the existing entries
and renamed PrintCraft -> PdfCraft. The 29 diagnostic templates of
errors.rs are dropped.
- `Language::diagnostic` and errors.rs are dropped: matching engine and
OS messages against English templates breaks silently whenever the
wording changes. Error details are shown as they are, inside the
translated frame ("{label} failed: {e}").
- History labels (`command_label`/`action_label`) move into i18n and keep
captured file and field names; registry menus prefer `@id` entries.
- Preferences: Auto plus every registered language, relabelled at once.
- Tests: Japanese covers every command and All tools entry; Japanese
labels have glyphs with the desktop faces and with the web subset
(with craft-fonts); history labels; #141's control tests, with the
error-detail expectation updated.
Co-Authored-By: shuiyue-cmyk <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* M14: Preferences in the Edit menu, Cmd/Ctrl-comma
Split out of #141: register app.preferences in the Edit menu with the
platform's usual shortcut (Command-comma on macOS, Ctrl-comma
elsewhere), so the interface language can be found and changed with no
document open. Shortcut keys may now be punctuation (egui's key name
","). #141's control test drives the menu item, the language selector
and the shortcut in Japanese and English.
Co-Authored-By: sakamoto824 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Petr Leo Compel <[email protected]>
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
Co-authored-by: Simon Leblanc <[email protected]>
Co-authored-by: Codex <[email protected]>
Co-authored-by: RicSchonfelder <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: 才倾梦华V <[email protected]>
Co-authored-by: shuiyue-cmyk <[email protected]>
* Automation: --root refuses every outside path the same way (#136)
With a root set, `resolve` canonicalized a path before checking it against
the root, so an existing outside path got "is outside the allowed
directory" while a missing one got the OS error ("No such file or
directory", "Not a directory", "invalid path"): a confined MCP client or
script could probe which files and folders exist anywhere on the disk.
Containment is now decided before anything about an outside path is
reported:
- `..` is resolved by name first (as Windows does), so a path can't pass
through an outside folder on its way back in;
- on Windows another network share or device namespace (`\\host\share`,
`\\?\UNC\...`, `\\.\...`) is refused by name, without contacting it;
- the deepest existing ancestor, links followed, must be inside the root
before anything is said about the rest;
- a broken link is refused the same way, so whether its target exists
stays hidden too.
Every outside path now gets `<path> is outside the allowed directory
<root>`, existing or not, file or folder, read or write. Filesystem errors
such as "not found" are still reported for paths inside the root.
Behaviour change: on Unix, `..` now means the parent by name, as on
Windows (`link/../x` is `x` beside `link`), and a write such as
`new/../x.pdf` that stays in the root now succeeds instead of failing with
"'..' is not allowed here".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* CLI: script images stay inside --root (#137)
`pdfcraft-cli run --script steps.json --root DIR` wrote each step's
`"out"` PNG with `std::fs::write`, never through the root check the tools
use: an absolute `"out"` created or replaced any file the process could
write, and a relative one landed in the working directory instead of the
root.
The image is now saved through the new `Automation::write_output`, which
uses the same resolver and atomic write as a tool's own output: outside
paths get the usual "is outside the allowed directory" refusal (the step
fails, nothing is written), relative paths resolve inside the root, and
without --root the path is written as given, in place as before (so
`/dev/stdout` still works). `run <tool> --out` goes the same way.
Found while testing this, the same "." problem in two tools:
- a write path naming a folder, such as "." (the root itself), staged its
temporary file beside that folder, i.e. outside the root for ".",
overwriting and then deleting any file there called
`.root.pdfcraft-tmp`. `write_atomic` now refuses a folder before
writing anything;
- `image_save` adds an extension to a path without one, so "." became
`root.png` beside the root. It now refuses a folder first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Automation: files named after a document stay in the output folder
Found while reviewing the --root fixes for #136 and #137. Folder outputs
name their files after the document and joined that name straight onto the
resolved folder, without checking the result against the root:
- `doc_export_images` and `doc_export_all_images` used the whole document
name, so `doc_create` with `name: "../../escape"` followed by
`doc_export_images` wrote `escape_page_1.png` outside the root (an
absolute name went anywhere);
- `page_extract` (separate files), `doc_split`, `ocr_recognize_files` and
`action_run` took the last component, but on Windows a name such as
`x/C:evil.pdf` gives `C:evil`, and `folder.join("C:evil-page1.pdf")`
replaces the folder with a path relative to the current directory on C:.
Every such name now goes through `child()`, which turns separators,
colons and control characters into `_` (and prefixes a name of only dots),
so the file is always a direct child of the folder. `with_root` also
refuses a root that is not a folder.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
The app, crates, binaries, bundle id, packaging, icons, screenshots and docs are now
PdfCraft / pdfcraft (repo storytold/pdfcraft). On first launch the desktop app moves the
PrintCraft settings and crash-recovery folders to the new name, and both apps read settings
saved under the old storage key, so upgrades keep recent files, preferences and unsaved work.
The signing test fixtures keep their PrintCraft subject names (they are binary).
Co-authored-by: Claude Opus 5.5 <[email protected]>
Shippori Mincho (and its OFL file) leaves assets/fonts/: the Japanese fonts now
live in storytold/craft-fonts. printcraft-fonts gets craft-fonts' build.rs recipe:
with CRAFT_FONTS_DIR=<checkout> it embeds the manifest's fonts as CRAFT_FONTS
(wasm32: only BIZ UDPGothic Regular); without it CRAFT_FONTS is empty and the
app builds, tests and runs as before, minus the Japanese face.
- SHIPPORI_MINCHO is now Option<&[u8]> from CRAFT_FONTS; japanese_glyph
(was shippori_glyph) uses document_japanese_font() (Shippori Mincho, then
BIZ UDMincho, then any Jpan face) and returns GlyphError::NoFont without one.
- Editing PDF text into Japanese without craft-fonts is a clear EditError that
leaves the page untouched.
- UI: every Jpan craft font (BIZ UDPGothic first) is the last fallback of
every egui family, sharing the same static bytes.
- Tests: Japanese glyph/edit tests skip without craft-fonts; new UI tests for
no-tofu Japanese with craft-fonts and working fonts without it.
- CI: the Linux test job checks out craft-fonts at a pinned SHA and runs the
tests with CRAFT_FONTS_DIR. Releases build every platform with it
(CRAFT_FONTS_REQUIRED=1) and ship each font's OFL as OFL-<family>.txt.
- ATTRIBUTION.toml gains [[build_input]] (checked by xtask assets); NOTICE,
AGENTS.md, CLAUDE.md, README and docs say fonts live in craft-fonts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- package.ps1 takes -Arch arm64 (aarch64-pc-windows-msvc, WiX -arch arm64)
and now checks each binary's PE Machine as well as its Subsystem, so an x64
build can never ship labelled arm64.
- release.yml builds, signs and packages a Windows ARM64 MSI and portable zip
next to x64 and x86, cross-compiled on the x64 runner so signing and WiX
run exactly as they do for x64.
- New windows-arm64.yml (PRs touching Cargo/packaging, main, weekly):
packages the ARM64 MSI the same way (unsigned), installs it on a Windows 11
ARM64 runner, checks both installed binaries are ARM64, opens/reads/edits/
renders/saves a PDF with the installed CLI, uninstalls; and runs the whole
test suite natively on ARM64. The MSI is kept as an artifact for 7 days.
- README: Windows builds for x64, x86 and ARM64.
Requested by @maxffarrell.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The release workflow gains a FreeBSD x86_64 job: packaging/freebsd/package.sh
builds in the same pinned FreeBSD VM and writes a /usr/local-style tar.gz
(app, CLI, desktop entry, MIME type, AppStream metadata, icons, licences).
README and CLAUDE.md list FreeBSD now that its CI job builds PrintCraft and
passes all 688 tests on FreeBSD 14.3.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ROADMAP gains an honest assessment by dimension (features, effort,
foundations, robustness, fidelity, product readiness) and by area, what
'shipped' does and doesn't mean, and a ranked list of where we're lacking
and where we're going. Numbers refreshed (804 features, 49.5% shipped,
effort ≈ 30–35%). README's stale 'What's next' becomes an honest status
summary; CLAUDE.md points every agent session at both sections.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Adopt the shared Crafting Apps licensing standard. LICENSE-MIT names "ArtCraft Team and the
PrintCraft contributors"; LICENSE-APACHE matches the standard byte for byte; NOTICE gets the
standard header and brand paragraph above the existing third-party list. docs/brand/LICENSE-brand.txt
now holds the ArtCraft Team brand terms: the marks may be used only unmodified within PrintCraft,
and forks and modified versions must remove them. The brand entries in ATTRIBUTION.toml, AGENTS.md
§1.2 and the README follow suit, and the README's Licence section becomes the standard
"License and credits" block. xtask inherits the workspace licence.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Drop the app icon from the top of the README, matching PhotoCraft, and
show each app's icon next to its name in the Crafting Apps table in place
of the coloured name badges. The table is now identical across all seven
apps apart from the "you are here" row.
Co-Authored-By: Claude Opus 5.5 <[email protected]>