Commit Graph
100 Commits
Author SHA1 Message Date
Brandon Thomas 37542ad9db Merge remote-tracking branch 'origin/main' into dmg-background
# Conflicts:
#	ATTRIBUTION.md
2026-10-09 09:21:35 -04:00
4e79bbdbdf CI: no macOS on PRs or main; macOS is built and tested by releases (#512)
The org has 5 macOS slots in total (GitHub Team), shared by every repo's CI and
release builds, and PR CI kept them full, so release builds queued behind it.
macOS now runs only in release.yml; a release that fails on macOS is fixed and
rebuilt.

Co-authored-by: Brandon Thomas <shared2@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 08:51:23 -04:00
4dc79ccc41 README Downloads: where to get it first, then builds with Windows before macOS (#279)
Also drop "signed installers" from the thin-or-missing list: release installers are signed.

Co-authored-by: Brandon Thomas <shared2@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 09:58:57 -04:00
7f4677dc93 Release: Flatpak bundles, AppImage zsync updates, README Downloads (#275)
* 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 <noreply@anthropic.com>

* Flatpak: no CUPS socket (the runtime has no lp); document printing as unavailable there

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <shared2@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 09:58:13 -04:00
Brandon ThomasandClaude Opus 5.5 4dd738b60f CLI text tests: each protected-PDF test writes its own files (#267)
text_of_a_protected_document_without_the_password_fails failed on main: both protected
tests wrote to-protect.pdf / protected.pdf / protect.json at the same paths, and tests run
in parallel. Each now uses its own names.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 08:33:26 -04:00
932967a30d Restore #196, #202, #203 and the macOS release-runner change, reverted by the i18n chain (#229)
* Release CI: macOS job on macos-15-xlarge

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>
(cherry picked from commit 90d60b0c98)

* CI: tell actionlint about the org's macos-release runner label (#202)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit ddea3c72e0)

* About: contributor and model credits compiled in (grab bag + table, self-submitted names) (#196)

* About: contributors compiled in from contributors.json (grab bag + table, sortable, name toggle)

Adds About ▸ Contributors and About ▸ Models tabs per the craftrules standard
(standards/contributors.md): contributors/contributors.json is generated by
craftrules/scripts/contributors.py and baked into the binary by crates/ui-egui/build.rs as
static tables; nothing is read at run time. Each contributor line: @username, opt-in
display/real name, PRs, commits, lines +/−/Δ, binary assets +/−, first/last commit.
Sortable by every column; alphabetical ignores case and the leading @.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Contributors: self-submitted names in contributors/people.toml; AGENTS.md credit-yourself section

build.rs reads contributors/people.toml next to the generated contributors.json and shows a name only
for entries the generator verified as written by that person (craftrules standards/contributors.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Contributors: regenerate (verified people.toml entries)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 41e214c318)

* M13: exit cleanly when stdout pipe closes (#203)

Co-authored-by: Brandon Thomas <echelon@gmail.com>
(cherry picked from commit 1cf0d319e4)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Ayan <108534604+excited-raccoon@users.noreply.github.com>
2026-10-08 03:33:35 -04:00
Brandon ThomasandClaude Opus 5.5 827c61bba9 Picker tests wait for the worker instead of polling with a deadline (#221)
a_panicking_worker_clears_showing_and_queues_nothing failed on main (Linux CI and
Windows ARM64): panic unwinding on a loaded runner can take longer than the
1 s polling window. Tests now join the worker thread, so they can't time out.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 02:33:11 -04:00
Brandon ThomasandClaude Opus 5.5 ddea3c72e0 CI: tell actionlint about the org's macos-release runner label (#202)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 22:12:47 -04:00
Brandon ThomasandClaude Opus 5.5 41e214c318 About: contributor and model credits compiled in (grab bag + table, self-submitted names) (#196)
* About: contributors compiled in from contributors.json (grab bag + table, sortable, name toggle)

Adds About ▸ Contributors and About ▸ Models tabs per the craftrules standard
(standards/contributors.md): contributors/contributors.json is generated by
craftrules/scripts/contributors.py and baked into the binary by crates/ui-egui/build.rs as
static tables; nothing is read at run time. Each contributor line: @username, opt-in
display/real name, PRs, commits, lines +/−/Δ, binary assets +/−, first/last commit.
Sortable by every column; alphabetical ignores case and the leading @.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Contributors: self-submitted names in contributors/people.toml; AGENTS.md credit-yourself section

build.rs reads contributors/people.toml next to the generated contributors.json and shows a name only
for entries the generator verified as written by that person (craftrules standards/contributors.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Contributors: regenerate (verified people.toml entries)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 22:12:01 -04:00
Brandon ThomasandClaude Opus 5.5 90d60b0c98 Release CI: macOS job on macos-15-xlarge
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>
2026-10-07 22:08:01 -04:00
Brandon ThomasandClaude Opus 5.5 e7431915ae Release CI: macOS job on the org macos-release runner
The standard macOS runners are shared with all CI and queue for an hour or
more. The macos-release runner (GitHub-hosted M2 Pro) is in a runner group
usable only by release.yml on the release branch, with a monthly spend cap
on the actions_macos_xl SKU.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:54:57 -04:00
Brandon ThomasandClaude Opus 5.5 1f26938565 CI: the ARM64 smoke test writes its page render inside --root (#198)
Since #164, a script step's "out" resolves inside --root like every other path, so
"smoke/page.png" landed in smoke/smoke/. Name it relative to the root.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:40:41 -04:00
Brandon ThomasandClaude Opus 5.5 d3db99818f Rename PrintCraft to PdfCraft (#174)
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 <noreply@anthropic.com>
2026-10-07 16:03:57 -04:00
Brandon ThomasandClaude Opus 5.5 282a12cc89 ROADMAP: session 14 log
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 14:56:31 -04:00
Brandon Thomas 8197e60eb7 Merge pull request #75 from storytold/pr/craft-fonts
Fonts: use craft-fonts as an optional build input; move the Japanese fonts out
2026-10-06 14:13:50 -04:00
Brandon ThomasandClaude Opus 5.5 1d67833599 Fonts: use craft-fonts as an optional build input; move the Japanese fonts out
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 <noreply@anthropic.com>
2026-10-06 11:36:23 -04:00
Brandon ThomasandClaude Opus 5.5 2e49a84ace CI: remove the temporary ARM64 crash-diagnosis job
It was only for finding the WARP crash (fixed in the previous commit) and
was merged with #66 by accident; it ran a 60-iteration crash-dump loop and
edited the runner's registry on every push.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 10:43:49 -04:00
Brandon ThomasandClaude Opus 5.5 d341651b20 Tests: one wgpu device compiles shaders at a time in view.rs (WARP ARM64 crash)
On GitHub's Windows 11 ARM64 runner (no GPU, so wgpu uses Microsoft's WARP)
tests/view.rs died with STATUS_ACCESS_VIOLATION in about 1 run in 4. A crash
dump puts it inside WARP's pixel-shader JIT
(d3d10warp!PixelJitProgram::ClassifyVars, on WARP's own thread pool) when the
two tests that render pixels compile shaders on separate devices at once.
Single-threaded, or each test alone, it never crashed (180 runs); bigger
thread stacks didn't help (6/30 vs 7/30), so it's that race, not our code.
The app has one device on a real GPU and can't hit it.

Those two tests now hold a per-binary lock for their whole run (taken before
the harness, so released after its device drops). Other test binaries render
from at most one test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 10:43:49 -04:00
Brandon Thomas 05031c81e9 Merge pull request #66 from storytold/feat/61-windows-arm64
Windows on ARM64: release builds, tested on ARM64 hardware (#61)
2026-10-06 09:26:40 -04:00
Brandon Thomas 5112e34f37 TEMP: print the faulting thread and the loaded graphics modules 2026-10-06 09:08:36 -04:00
Brandon Thomas c3007277ef TEMP: capture a crash dump of the ARM64 view-test crash and print its stack 2026-10-06 08:55:59 -04:00
Brandon Thomas 7afb93b06b TEMP: A/B the ARM64 crash: parallel vs parallel with big thread stacks 2026-10-06 08:41:24 -04:00
Brandon Thomas 4ad43487ee TEMP: diagnose the intermittent ARM64 access violation in tests/view.rs 2026-10-06 08:23:15 -04:00
Brandon Thomas dcb0023889 Merge pull request #67 from storytold/pr/release-0.2.1
Release: PrintCraft v0.2.1
2026-10-06 08:02:41 -04:00
Brandon ThomasandClaude Opus 5.5 30f09f2f9b Release: PrintCraft v0.2.1
Fixes the Windows installer: the Start Menu shortcut and Add/Remove Programs icon were blank. (printcraft#57)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 08:02:34 -04:00
Brandon ThomasandClaude Opus 5.5 3feea57cd7 package.ps1: skip the CLI smoke test for an ARM64 build on an x64 machine
The cross-compiled ARM64 binary can't run on the x64 packaging runner; the
windows-arm64 workflow installs and runs it on ARM64 instead. (x86 builds
still run there: x64 Windows runs them.)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 07:41:47 -04:00
Brandon ThomasandClaude Opus 5.5 07d7fbfa07 Windows on ARM64: release builds, and CI that installs and runs them on ARM64 (#61)
- 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 <noreply@anthropic.com>
2026-10-06 07:41:47 -04:00
Brandon ThomasandClaude Opus 5.5 604c560e5f Export tables: linear column lookups, a column cap, RTF spans
Review follow-ups on top of the table detection:
- Each block's column comes from a precomputed map and a binary search
  instead of scanning every column's members, so pages with many text
  blocks stay linear.
- A table has at most 64 columns. Rows are materialized to every column, so
  a hostile page laid out as a staircase of blocks would otherwise make
  (blocks / 2)^2 cells. Test: a_staircase_of_blocks_is_not_a_huge_table.
- RTF: a row's cell boundaries are written before its cells (RTF requires
  it), and a spanning cell ends at the last column it spans. The test checks
  the Totals row now spans two columns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 07:38:17 -04:00
Brandon Thomas 28108ec555 Export detected tables to office formats 2026-10-06 07:38:17 -04:00
Brandon ThomasandClaude Opus 5.5 ea2ed3d911 Forms: say so when a document is an XFA form (#60)
XFA forms aren't read yet, and opening one gave no hint: a dynamic XFA form
showed only its "Please wait…" placeholder page, and a static one looked like
an ordinary form though Acrobat shows its XFA data in preference to the
fields that get filled.

- render: DocInfo.xfa (Static / Dynamic) from /AcroForm /XFA; dynamic when
  /NeedsRendering is true or there are no fields. Exported for doc_info
  (automation and CLI) as "xfa".
- ui: the notice bar explains each case (above the form-fields notice); a
  static form keeps its Highlight fields button.
- Tests: tests/xfa.rs (dynamic with and without /NeedsRendering, static,
  ordinary forms, a malformed /XFA). Parity notes for form.xfa-static and
  form.xfa-dynamic (still planned: this explains XFA, it doesn't support it).

Reported by @Sudachi-dev.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 07:22:33 -04:00
Brandon ThomasandClaude Opus 5.5 93221c6886 M7: move and resize existing text boxes in Edit text (#40)
In Edit a PDF ▸ Edit text, dragging a paragraph's box moves it, and dragging
the handle on its right edge rewraps it to a new width; a click still opens
it for typing.

- edit: BlockStyle gains offset (user space) and width. rewrite_block puts the
  move inside the paragraph's own q … Q as a cm, taken back through the CTM's
  linear part so a page-space drag lands right in scaled/rotated content
  (the CTM is now recorded at each line's origin). Non-finite numbers are
  refused; widths are clamped to one character .. 14 400 pt.
- ui: BlockDrag on DocView; the box follows the pointer (or its right edge
  does) and the release applies one undoable Edit text step. The width handle
  shows on upright pages only (on a quarter-turned page the screen's
  horizontal is the text's vertical).
- automation: text_edit takes dx, dy and width.
- Tests: engine (move, move under a scaling cm, narrower/wider rewrap,
  non-finite and zero inputs), kittest pointer drags, automation tool.
  Parity: new edit.text-move-resize (shipped).

Requested by @TCB13.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 07:19:53 -04:00
Brandon ThomasandClaude Opus 5.5 19673084bd Identity test: wait for the page's text before selecting it
On a slow Windows runner the test selected text before the page had loaded,
which selects nothing, so no highlight was made and the author check failed
(seen on PR #59's CI). Settle rendering first, as the comment tests do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 07:06:50 -04:00
Brandon ThomasandClaude Opus 5.5 fdfdc879d7 Windows: no console window with the app, and the Start Menu shortcut gets its icon (#57)
- printcraft.exe release builds use the GUI subsystem, so launching it no
  longer opens a console window next to it (debug builds keep the console).
- The MSI's Icon id gains its .ico extension: Windows Installer requires it
  for advertised shortcuts and ARPPRODUCTICON, so the Start Menu shortcut and
  Add/Remove Programs showed a blank icon.
- Guards: package.ps1 checks the PE subsystem of both binaries (app = GUI,
  CLI = console) before packaging; packaging-lint fails on an Icon id without
  .ico/.exe.
- The shortcut's description said "image editor"; it's a PDF workbench.

Reported, with the exact fixes, by @MikailuReeves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 21:33:17 -04:00
Brandon ThomasandClaude Opus 5.5 d7749e6379 Identity test: wait for the highlight to land before reading its author
The edit applies on a later frame, so reading the annotation straight away
raced (failed about 1 run in 3 locally). Poll for up to ~100 frames.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 19:31:23 -04:00
Brandon ThomasandClaude Opus 5.5 b315eff24d Preferences ▸ Identity: the name on new comments, remembered
Comments were signed with the login name (or set_option author), with no way
to change it in the app. Preferences gains an Identity section whose name
field (labelled for screen readers) sets the author of new comments; it is
saved with the settings and restored (an empty name keeps the login-name
default; restored names are capped at 200 characters). Japanese labels
added. Test: identity.rs. Parity comment.author-identity: shipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 19:31:23 -04:00
Brandon ThomasandClaude Opus 5.5 b4e1d488de Comments: an opacity control in the quick bar (per tool, 10-100 %)
The comment tools' quick bar had colour and line thickness but no opacity,
though comments already carry it (/CA) and the Properties dialog edits it.
An Opacity slider joins them for every tool; set_opacity clamps to 10-100 %
so a comment can't be made invisible. Test: a strikeout made at 50 % carries
/CA 0.5. Parity comment.properties-bar: shipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 19:12:24 -04:00
Brandon ThomasandClaude Opus 5.5 c02e1e91c2 Comments: Squiggly underline tool (Highlight flyout, All tools, command)
The engine and agent tool already made squiggly underlines; the UI had no
way to. CommentTool::Squiggly joins Underline and Strikethrough in the
Highlight flyout and the Comment tool's Markup section, as the
comment.squiggly command, and existing squiggly comments edit as themselves.
Test: comments.rs::the_squiggly_tool_marks_selected_text. Parity
comment.squiggly: shipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 18:57:28 -04:00
Brandon ThomasandClaude Opus 5.5 ead70695c8 Tests: dropping a PDF on the window, and hovering a comment (two features now shipped)
Both features worked but had no test, so parity listed them as partial:
- core.drag-drop-open: a file dropped on the window opens in a tab
  (ui.rs::dropping_a_pdf_on_the_window_opens_it, through egui's DroppedFile).
- view.annotation-hover: hovering a comment shows a popup with its author and
  text (comments.rs::hovering_a_comment_shows_its_author_and_text).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 18:29:38 -04:00
Brandon ThomasandClaude Opus 5.5 8703172692 ROADMAP: log session 13's later work (FreeBSD, fuzzing fixes, community PRs, Windows crash)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 18:24:32 -04:00
Brandon ThomasandClaude Opus 5.5 0be70c0c8e Issue #37: on Windows, draw with Direct3D 12 (OpenGL fallback) and never load Vulkan drivers
wgpu's default backends on Windows include Vulkan, and creating a Vulkan
instance loads every installed Vulkan driver into the process. On the
reporter's machine Intel's (igvk64.dll) crashed PrintCraft with an access
violation before its window appeared; WGPU_BACKEND=dx12 or gl started it
normally. PrintCraft now asks for D3D12 with OpenGL as the fallback, unless
WGPU_BACKEND says otherwise. The issue #8 low-power preference is kept in
the same configure_gpu, and a unit test (run by the Windows CI job) checks
Vulkan is left out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 18:05:27 -04:00
Brandon ThomasandClaude Opus 5.5 73fdb261ef UI: share the Japanese fallback font with printcraft-fonts (one 8.7 MB copy, not two)
The UI theme include_bytes!'d ShipporiMincho-Regular.ttf, which
printcraft-fonts already embeds as SHIPPORI_MINCHO for Japanese text in
PDFs, so the app carried two copies. ui-egui now depends on printcraft-fonts
(layer 2, wasm-clean) and uses its static. The desktop app binary drops from
85.8 MB to 77.5 MB (debug build; one copy of the font left, counted).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 18:03:34 -04:00
Brandon ThomasandClaude Opus 5.5 7b85af8785 Tests: full screen and the About dialog get their own tests (parity evidence)
Both were marked shipped on the generic 'every registered command is
implemented' test, which the honest assessment flagged. Full screen: the
shortcut enters, Escape leaves, and the command toggles both ways. About:
the dialog shows the running version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 17:30:22 -04:00
Brandon ThomasandClaude Opus 5.5 0447a0fc7b FreeBSD: package in the FreeBSD CI job; release job matches its VM setup
Builds on #45's FreeBSD workflow: it now also runs packaging/freebsd/package.sh
end to end (a quicker release profile) and triggers on packaging changes. The
release job uses the same packages, 4 vCPUs and safe.directory, and removes
target/ in the VM before copying dist/ back out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 10:57:30 -04:00
Brandon ThomasandClaude Opus 5.5 37748260ff FreeBSD: release tarball and platform docs
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 <noreply@anthropic.com>
2026-10-05 10:57:30 -04:00
Brandon ThomasandClaude Opus 5.5 a416714ebd FreeBSD: CI builds and tests PrintCraft in a FreeBSD VM
A new FreeBSD workflow builds the workspace and runs its tests in a FreeBSD
14.3 VM (vmactions/freebsd-vm, pinned by commit) with rustup stable, on PRs,
pushes to main, nightly and on demand. The fuzzer's address-space cap now
applies on FreeBSD too (its sh has ulimit -v).

PrintCraft's platform code was already Unix-general: everything that isn't
macOS-specific takes the same path on Linux and FreeBSD.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 10:57:30 -04:00
Brandon ThomasandClaude Opus 5.5 20842c72e3 Never crash: Type 1 font programs can't hang or overflow the stack (skrifa 0.47)
The nightly fuzz job found a Type 1 font that hung hayro: read-fonts 0.39
(through skrifa 0.42) looked ahead after every integer by parsing the next
token, which looked ahead again, recursing through a whole run of numbers.
A long run hung; 20,000 numbers overflowed the stack and aborted the
process. Upstream fixed the tokenizer; the vendored hayro-interpret now uses
skrifa 0.47 (read-fonts 0.44), with its one API change. The finding finishes
in 0.5 s. Synthetic regression test (it aborts with the old skrifa).
Corpus sweep: 0 regressions; text oracle unchanged (median F1 0.980, mean
0.866, every per-file score identical).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 10:47:39 -04:00
Brandon Thomas 3870bea079 Merge pull request #45 from storytold/pr/freebsd
FreeBSD: build and test the workspace in a FreeBSD VM
2026-10-05 09:44:57 -04:00
Brandon ThomasandClaude Opus 5.5 49d2a2040c FreeBSD CI: git safe.directory, 4 vCPUs, pinned vmactions/freebsd-vm
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 09:21:23 -04:00
Brandon ThomasandClaude Opus 5.5 6fafe67653 FreeBSD CI: install git in the VM (xtask's asset test lists tracked files)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 08:46:32 -04:00
Brandon ThomasandClaude Opus 5.5 6e90710e4b Never crash: JBIG2 images can't decode billions of pixels
The nightly fuzz job found a JBIG2 symbol dictionary that decoded for
minutes: each bitmap side may be 65535, and up to 65535 symbols are decoded
pixel by pixel. Vendored hayro-jbig2 0.3.0 with two bounds: at most 2^28
pixels per bitmap (also the page bitmap, halftone grid and grayscale image),
and at most 2^26 symbol pixels per dictionary. The finding now finishes in
about a second. Synthetic regression test (a 65535 x 65535 page and region
behind a small /Width and /Height); the corpus sweep shows 0 regressions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 08:28:52 -04:00
Brandon ThomasandClaude Opus 5.5 1e32ac276c docs: point agents to the shared test corpora (#44)
Real-file test oracles live in storytold/photocraft-corpus, explained in
craftrules standards/test-corpora.md. Never commit large binary fixtures
here; fetch them pinned and sha256-verified.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 08:24:34 -04:00
Brandon ThomasandClaude Opus 5.5 9147bd946f FreeBSD: build and test the workspace in a FreeBSD VM
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 08:23:31 -04:00
Brandon ThomasandClaude Opus 5.5 2f364665b8 Never crash: the fuzz job's eleven hangs finish (parser retries, inline images, Type 3 fan-out)
Every hang the nightly fuzz job found (11 findings, 8 source files) now
finishes in 0.1-2 s; each cause has a synthetic regression test that times
out without its fix.
- cos (7 findings, our own parser): a stream whose /Length pointed at
  itself, with no endstream, made every parse attempt fail and each attempt
  reload the same object: ~2^32 parses. Objects being loaded are a seen-set,
  /Length values are memoised per load, and the shifted-header retry only
  runs when the header is shifted.
- hayro-syntax (3): deciding where an inline image's data ends re-parsed the
  rest of the stream, nesting through every inline image; 50 images ran for
  over ten minutes. The look-ahead no longer nests and is bounded (256).
- hayro-interpret (1): a Type 3 glyph showing eight glyphs of its own font,
  sixteen deep. Nested paints share a per-page budget (50,000).
The pdf.js corpus sweep matches its baseline (963 ok, same 20 known).
xtask check writes its report under CARGO_TARGET_DIR too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 07:35:38 -04:00
Brandon ThomasandClaude Opus 5.5 f97684f0d8 Docs: honest parity assessment, gap list and direction (ROADMAP, README, CLAUDE.md)
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 <noreply@anthropic.com>
2026-10-05 06:19:06 -04:00
Brandon ThomasandClaude Opus 5.5 de9b9e82bc Updates: check only when asked (no check at start)
By the owner's decision there is no update check at start and no in-place
install for the foreseeable future. The startup option, its setting and the
first-frame trigger are gone; Help ▸ Check for updates is the only request.
Settings written by a build that had the option are ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 06:07:11 -04:00
Brandon Thomas 2ea5846a16 Merge pull request #33 from storytold/pr/release-0.2.0
Release: PrintCraft v0.2.0
2026-10-05 02:41:04 -04:00
Brandon ThomasandClaude Opus 5.5 df3457ed92 Release: PrintCraft v0.2.0
Bump the workspace version 0.1.1 -> 0.2.0 (cargo xtask version set 0.2.0).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:40:53 -04:00
Brandon ThomasandClaude Opus 5.5 85a8652de1 Issue #28: Help ▸ Check for updates, with an opt-in check at start
PrintCraft asks GitHub for the latest release and, when it is newer, offers
its release page to download from. Nothing is downloaded or installed
automatically (installing in place needs signed release artifacts).

- ui-egui: updates module (version comparison, background check, dialog);
  no network code: the desktop app supplies the source, and without one
  (web build, tests) the command opens the releases page.
- desktop: one HTTPS request (ureq over rustls with the OS's trusted roots,
  10 s timeout, 1 MiB cap). Only a page under the releases list is offered.
  No bundled root list: webpki-roots/webpki-root-certs are CDLA-licensed.
- The check at start is opt-in and silent unless there is a newer version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:40:27 -04:00
Brandon ThomasandClaude Opus 5.5 27f7ef96fe Issue #8: prefer the integrated GPU, and the save prompt answers to the keyboard
- On hybrid-graphics laptops wgpu picked the discrete GPU (egui-wgpu defaults
  to HighPerformance). NVIDIA's can lose or corrupt its memory across suspend
  and screen lock, which matches the illegible window in #8. PrintCraft now
  asks for the low-power adapter unless WGPU_POWER_PREF is set; one-GPU
  machines are unaffected, and it saves battery.
- "Save changes?" had no keyboard: Enter saves, Escape cancels, and Don't save
  takes Cmd/Ctrl+D (macOS) or Alt+D / Alt+N (the mnemonics the reporter tried).
  Its keys are read before the canvas (which took Escape), and no other
  shortcut runs underneath it (Cmd+D opened Document properties).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:17:31 -04:00
Brandon ThomasandClaude Opus 5.5 a5d830bf25 Never crash: predictor rows and stroke widths from the input can't exhaust memory
The second full nightly fuzz run on main found two more:
- hayro-syntax computed a Flate/LZW predictor row as columns × bits in
  wrapping arithmetic: /Columns 9223372036854775807 asked for 2^61 bytes and
  aborted on any machine. Row widths are now saturating usize (colors × bits
  also overflowed u8), a zero width is refused (it divided by zero), and a
  row longer than the data is not allocated (empty output, as before).
- hayro stroked /LW 9223372036854775807 as given, and stroke expansion
  allocated 10 GB. Widths are clamped to 4 × (canvas width + height) device
  pixels, which covers the same pixels.
Both findings now run in 8 MB. Synthetic regression test in render.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:06:45 -04:00
Brandon ThomasandClaude Opus 5.5 6058068266 Never crash: absurd image-mask and CCITT sizes are refused before decoding
The first full nightly fuzz run on main (now that the runner survives) found
two 4 GiB allocations, caught by the 4 GiB child cap:
- an inline stencil mask claiming /W 4294967295 was decoded in hayro-interpret
  before the renderer's MAX_IMAGE_PIXELS check could skip it (locally 9.6 GB,
  42 s). Vendored patch image_size_ok: images and masks over 2^28 pixels are
  not decoded.
- a CCITT image whose /DecodeParms claimed /Columns 4294967295 (with a small
  /Width) sized the decoder's line buffers (locally 4.3 GB). Vendored patch
  ccitt_size_ok in hayro-syntax: at most 2^20 columns and 2^28 pixels.
Both findings now run in 7 MB. Synthetic regression test in render.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 00:10:17 -04:00
Brandon ThomasandClaude Opus 5.5 1449b71a19 Never crash: bound the allocations behind the nightly fuzz runner's out-of-memory deaths
Fuzzed inputs drove one check-one process to 2-6 GB (the 16 GB CI runner
running two of them was killed every night). Three causes, each with a test:
- lopdf (bootstrap inspector) sized PNG predictor rows from /Columns before
  reading data: /Columns 4294967295 allocated two 4 GiB rows. Vendored lopdf
  0.45.0 refuses rows longer than the data (and uses saturating row widths);
  its load-time stream decoding also gets a 256 MiB limit (it had none).
- hayro sized a tiling pattern's cell pixmap from /XStep x scale, bounded only
  by u16: a step far beyond /BBox allocated 2 GB. The vendored patch keeps
  the cell within 3000 px a side, lowering the resolution instead.
- cos decoded cross-reference streams up to the general 1 GiB limit; they now
  stop at what 8,388,607 objects' rows can hold (~200 MB at most), and the
  section is reconstructed (logged) instead.

On the nightly's seed (41) the worst captured input fell from 6.2 GB to 6 MB,
and a 300 s fuzz run had no process above 300 MB (before: many at 2-5 GB).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 17:59:25 -04:00
Brandon ThomasandClaude Opus 5.5 330a4f59cf Fuzz: run the CLI cargo built, refuse to run without it, and cap child memory on Linux
- xtask looked for target/release/printcraft-cli even under CARGO_TARGET_DIR
  (which parallel agents use), and a child that failed to spawn counted as a
  pass: such runs fuzzed nothing and reported 0 crashes. The binary now comes
  from CARGO_TARGET_DIR (gates' corpus check and text oracle too), and fuzz
  stops if it is missing.
- On Linux each child runs under a 4 GiB address-space limit, so an input that
  allocates without bound becomes a crash finding instead of exhausting the
  machine: the nightly fuzz job's runner was killed this way every night,
  losing the findings. Allocation failures are kept as the finding's message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 17:59:25 -04:00
Brandon ThomasandClaude Opus 5.5 1ca576309e Git: no line-ending conversion for any file, so Windows checkouts keep asset hashes
Windows runners check out with core.autocrlf=true, which turned the SVG icons'
LF into CRLF, so their SHA-256 no longer matched ATTRIBUTION.toml and
xtask's the_real_manifest_passes failed there (hidden until now behind the
perf-test failure, since cargo test stops at the first failing binary).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:55:57 -04:00
Brandon ThomasandClaude Opus 5.5 9ecfd0afbc Tests: frame budgets take turns and keep the best of three rounds
The two budget tests ran in parallel, each with its own render workers, and
a single preempted stretch on a shared CI runner pushed the average over the
40 ms budget (41-52 ms on Ubuntu and Windows). They now hold a lock while
measuring, and a round over budget is measured again (up to three rounds):
noise only adds time, so the best round is the UI's own cost and a real
regression still fails every round. The budget is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:55:57 -04:00
Brandon Thomas f5e3255b80 Merge pull request #25 from storytold/pr/never-crash-edit-xfdf
Never crash: edit and xfdf have no expect calls
2026-10-04 14:49:48 -04:00
Brandon Thomas 342593af46 Merge pull request #24 from storytold/pr/never-crash-attrs
Never crash: deny the panic lints in crates that are already clean
2026-10-04 14:49:29 -04:00
Brandon Thomas fb0ea34a01 Merge pull request #23 from storytold/pr/never-crash-create
Never crash: create has no expect calls
2026-10-04 14:49:10 -04:00
Brandon Thomas cec677cd64 Merge pull request #22 from storytold/pr/never-crash-ui
Never crash: UI and app shell have no expect/unreachable
2026-10-04 14:48:48 -04:00
Brandon Thomas b8f40d0c04 Merge pull request #21 from storytold/pr/never-crash-automation
Never crash: the automation (MCP/script) tools have no expect/unreachable
2026-10-04 14:48:27 -04:00
Brandon Thomas 0772e99cde Merge pull request #20 from storytold/pr/never-crash-engine
Never crash: engine denies the panic lints
2026-10-04 14:48:05 -04:00
Brandon Thomas 66d127bb73 Merge pull request #19 from storytold/pr/never-crash-render
Never crash: render has no expect/unreachable
2026-10-04 14:47:45 -04:00
Brandon Thomas 0133cb7454 Merge pull request #18 from storytold/pr/never-crash-organize
Never crash: organize has no expect/unreachable
2026-10-04 14:47:26 -04:00
Brandon Thomas e373293926 Merge pull request #17 from storytold/pr/never-crash-sign
Never crash: sign has no expect/unreachable
2026-10-04 14:47:06 -04:00
Brandon Thomas 414060a3ff Merge pull request #16 from storytold/pr/never-crash-small
Never crash: filters, fonts, js, optimize and a11y deny the panic lints
2026-10-04 14:46:43 -04:00
Brandon Thomas 7da7ec6148 Merge pull request #15 from storytold/pr/never-crash-crypt
Never crash: crypt has no panic paths
2026-10-04 14:46:22 -04:00
Brandon Thomas a76263a9a7 Merge pull request #14 from storytold/pr/never-crash-guard
Never crash: last-resort guard around edits, opens, saves and UI commands
2026-10-04 14:46:00 -04:00
Brandon Thomas 5c4988aed6 Merge pull request #13 from storytold/pr/never-crash-js
Never crash: hostile form scripts can't overflow the stack
2026-10-04 14:45:39 -04:00
Brandon Thomas ff8c75f46f Merge pull request #12 from storytold/pr/never-crash-forms
Never crash: forms scripts can't overflow the stack or the date arithmetic
2026-10-04 14:35:21 -04:00
Brandon Thomas 3dc6a81d31 Merge pull request #11 from storytold/pr/never-crash-steering
Never crash: agent rules, clippy.toml, forbid unsafe
2026-10-04 14:34:59 -04:00
Brandon ThomasandClaude Opus 5.5 ac222e818b Never crash: edit and xfdf have no expect calls
- edit: the substitute font name is a `&str` constant, so there's no `from_utf8(..).expect`.
  Paragraph grouping uses `let … else` / `if let` chains, and the reuse-font path takes the
  metrics with `filter` instead of `expect("checked")`.
- xfdf: a comment key that vanished between listing and lookup is skipped.

Both crates now deny the panic lints. Adds the root clippy.toml (identical to #11) so tests may
still unwrap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 14:23:06 -04:00
Brandon ThomasandClaude Opus 5.5 59f74ea386 Root clippy.toml: tests may unwrap, expect, panic and index
The same file as the steering PR (#11), so this PR stands on its own. The crate-level
`#![deny(clippy::unwrap_used, …)]` applies to production code only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:35:01 -04:00
Brandon ThomasandClaude Opus 5.5 4e74f9ed6a Root clippy.toml: tests may unwrap, expect, panic and index
The same file as the steering PR (#11), so this PR stands on its own. The crate-level
`#![deny(clippy::unwrap_used, …)]` applies to production code only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:35:00 -04:00
Brandon ThomasandClaude Opus 5.5 e96a89c871 Root clippy.toml: tests may unwrap, expect, panic and index
The same file as the steering PR (#11), so this PR stands on its own. The crate-level
`#![deny(clippy::unwrap_used, …)]` applies to production code only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:35:00 -04:00
Brandon ThomasandClaude Opus 5.5 f688415504 Root clippy.toml: tests may unwrap, expect, panic and index
The same file as the steering PR (#11), so this PR stands on its own. The crate-level
`#![deny(clippy::unwrap_used, …)]` applies to production code only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:35:00 -04:00
Brandon ThomasandClaude Opus 5.5 0e2f8a2fb3 Root clippy.toml: tests may unwrap, expect, panic and index
The same file as the steering PR (#11), so this PR stands on its own. The crate-level
`#![deny(clippy::unwrap_used, …)]` applies to production code only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:35:00 -04:00
Brandon ThomasandClaude Opus 5.5 6ea62a0f6b Root clippy.toml: tests may unwrap, expect, panic and index
The same file as the steering PR (#11), so this PR stands on its own. The crate-level
`#![deny(clippy::unwrap_used, …)]` applies to production code only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:35:00 -04:00
Brandon ThomasandClaude Opus 5.5 dd0eed7425 Root clippy.toml: tests may unwrap, expect, panic and index
The same file as the steering PR (#11), so this PR stands on its own. The crate-level
`#![deny(clippy::unwrap_used, …)]` applies to production code only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:35:00 -04:00
Brandon ThomasandClaude Opus 5.5 926171569e Root clippy.toml: tests may unwrap, expect, panic and index
The same file as the steering PR (#11), so this PR stands on its own. The crate-level
`#![deny(clippy::unwrap_used, …)]` applies to production code only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:35:00 -04:00
Brandon ThomasandClaude Opus 5.5 cfe84da351 Root clippy.toml: tests may unwrap, expect, panic and index
The same file as the steering PR (#11), so this PR stands on its own. The crate-level
`#![deny(clippy::unwrap_used, …)]` applies to production code only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:35:00 -04:00
Brandon ThomasandClaude Opus 5.5 9902ff3043 Root clippy.toml: tests may unwrap, expect, panic and index
The same file as the steering PR (#11), so this PR stands on its own. The crate-level
`#![deny(clippy::unwrap_used, …)]` applies to production code only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:35:00 -04:00
Brandon ThomasandClaude Opus 5.5 b834c6c063 Root clippy.toml: tests may unwrap, expect, panic and index
The same file as the steering PR (#11), so this PR stands on its own. The crate-level
`#![deny(clippy::unwrap_used, …)]` applies to production code only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:34:59 -04:00
Brandon ThomasandClaude Opus 5.5 554969da72 Never crash: forbid unsafe, clippy.toml for tests, guard and craftrules link
Follows the owner's AGENTS.md §4 / CLAUDE.md rules with what the shared standard
(craftrules/standards/never-crash.md) adds: why (lost work) and that it outranks feature work,
the `unsafe` ban (workspace `unsafe_code = "forbid"`; no unsafe exists outside vendor/),
the last-resort guard around commands and file import/export, the clippy ratchet attribute,
and a root clippy.toml that lets tests use unwrap/expect/panic/indexing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:24:00 -04:00
Brandon ThomasandClaude Opus 5.5 d9d7caca22 Agent rules: never panic (AGENTS.md §4, CLAUDE.md non-negotiables)
Untrusted input (PDFs, scripts, MCP calls, settings) must yield errors, not crashes: Result
over unwrap/expect, no input-derived indexing, checked arithmetic, capped allocations, bounded
recursion, lock-poison handling, and a regression test for every crash fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:24:00 -04:00
Brandon ThomasandClaude Opus 5.5 1434db626a Never crash: deny the panic lints in crates that are already clean
annot, compare, content, cos, export, geom, model, ocr, preflight, print and redact have no
production `unwrap`/`expect`/`panic!`/`unreachable!`/`todo!`/`unimplemented!`. The attribute
keeps it that way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:12:21 -04:00
Brandon ThomasandClaude Opus 5.5 7632d8b179 Never crash: create has no expect calls
`add_page` returns the crate's error when the new document has no page tree, and text pages are
filled with `if let`. The crate now denies the panic lints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:12:21 -04:00
Brandon ThomasandClaude Opus 5.5 51027324e1 Never crash: UI and app shell have no expect/unreachable
Each "checked" `expect` becomes `let … else`, `if let` or `?`, with the obvious fallback (the
dialog closes, the command is ignored, the view refresh is skipped). ui-egui and the three apps
now deny the panic lints. The Windows `build.rs` reports a failed resource step with
`cargo::error` instead of panicking.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:12:20 -04:00
Brandon ThomasandClaude Opus 5.5 d54f7c2fa7 Never crash: the automation (MCP/script) tools have no expect/unreachable
- Renderer cache: built with the entry API instead of insert-then-`expect`.
- Page-text cache: filled and read with `get`/`get_mut`.
- Schemas and form JSON: extended through `if let` instead of `as_object_mut().expect`.
- A replace-text mark of the wrong shape is an invalid-argument error.

The crate now denies the panic lints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:12:20 -04:00
Brandon ThomasandClaude Opus 5.5 f7a0d3a3b9 Never crash: render has no expect/unreachable
The text reading order uses `let … else`, `while let` and `get_mut` instead of `expect`, and
named destinations bind their key with an or-pattern instead of `unreachable!`. The crate now
denies the panic lints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:12:20 -04:00
Brandon ThomasandClaude Opus 5.5 c74fb89a0d Never crash: organize has no expect/unreachable
The outline import, bookmark lookup and page-label code use `first`/`last`, a final `Err` and a
default range instead of `expect`/`unreachable!`. The crate now denies the panic lints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:12:20 -04:00