.CooCoo 5a93050c9f update: integration of the open fixes to craft-update (#763)
* update: an unsigned Windows copy gets Download instead of Update and Restart

The update helper installs nothing for an unsigned copy, so the prompt's
Update and Restart button on a local or unsigned build downloaded the
release and then stopped with "this copy isn't signed". is_self_updating
now answers false for an unsigned copy on Windows, and the prompt offers
Download. CRAFT_UPDATE_DEV still offers the update.

* update: check that a Windows copy can install before downloading

windows_precheck ran after the download, so a copy that could not install
downloaded the whole release first. The checks that do not depend on the
download now run before it; a new MSI's signature is checked after it.
The cache folder is created first, so a first update passes the check.

* update: a failure recorded before the running program is not reported

take_outcome reported any recorded failure at the next start, so a copy
put in place after an update had failed still opened with "The update
could not be installed". A failure record older than the running program
is now removed without being reported. A failure recorded after the
program was put in place, such as a failed helper's, is still reported.

* update: a Cargo build with a separate build directory is a development build

With build.build-dir set, Cargo keeps .rustc_info.json and the profile's
.fingerprint, deps and build folders in the build directory, and the
program's own folder has only its lock file. Such a build counted as an
installed copy and was offered Update and Restart. A profile folder with
Cargo's .cargo-lock under a target root with CACHEDIR.TAG is now a
development build, and gets the download link.

* update: a cache folder that can't be created is reported as that

same_cache_as_helper ignored the error from creating the folder, so a
failure read as a cache in the wrong place. It now names the folder and
the error.

* craft-update: a slow macOS signature check is not reported as a bad signature

`signed_by` gave `codesign` 120 seconds and treated every failure as a bad
signature, so a Mac that could not finish in time was told the app "isn't
signed by its developer".

The check now has 10 minutes and checks one bundle at a time. A job waiting
for its turn reports `JobPhase::Waiting` and can be canceled. A timeout, a
`codesign` that cannot start or is stopped by a signal, and any exit other
than 1 or 3 report that the signature couldn't be checked. Exit 3 refuses the
app as not signed by its developer. Exit 1 refuses it with `codesign`'s own
reason, because `codesign` also exits 1 for a bundle it cannot read. The app
is not installed in any of these cases.

On macOS, an install or uninstall now stops when `pgrep` gives no answer,
where a timeout used to read as "not running". `pgrep` has 30 seconds. An
install looks again after the signature check, which can wait behind other
apps' checks, and does not replace an app opened during the wait. A
self-update skips that second look.

* craft-update: a copy that cannot be replaced gets Download

A macOS bundle, an AppImage or a Linux standalone program was offered
Update and Restart even when the update could not replace it, so the
update failed only after the whole download.

`current_at` now leaves out such a copy when `access(2)` refuses what
the update needs. On macOS that is adding a subdirectory to the parent,
and adding a subdirectory to and deleting the bundle. On Linux it is
writing and searching the folder. A macOS copy under App Translocation
is also left out. Any other error from the check leaves the update on
offer, and `CRAFT_UPDATE_DEV` still offers it.

* craft-update: a macOS standalone copy that cannot be replaced gets Download

macOS `detect` returns `Install::Standalone` for a loose program, and
the update replaces it by writing beside it, as on Linux. `can_replace`
now checks that folder on macOS too, so a copy in a folder the user
cannot write gets Download instead of failing after the download.
`CRAFT_UPDATE_DEV` still offers the update.

* craft-update: a standalone copy whose share files cannot be replaced gets Download

The update replaces a standalone copy's files under `share` as well as
the program when the app's `share/<id>` folder sits next to `bin`. The
offer now also checks those folders, the `applications` and
`icons/hicolor` folders a release writes to, and the nearest existing
parent of one that doesn't exist yet.

* craft-update: a standalone macOS program without a release tarball gets Download

On macOS a program outside an app bundle counts as a standalone copy, and
the self-update replaces it from a macOS .tar.gz. Releases carry a disk
image and a command-line zip for macOS and no .tar.gz, so the prompt showed
Update and Restart and the update stopped with "has no Tarball (.tar.gz)
for macOS".

The prompt now also asks release_installs, which on macOS offers Update and
Restart to a standalone program only when the release lists that asset.
Without it the prompt shows Download. A release whose file list was not
loaded keeps the offer, because the install fetches the full release first.
CRAFT_UPDATE_DEV still offers the update. Other platforms and other kinds of
install are not checked against the release.

* craft-update: CRAFT_UPDATE_DEV no longer skips the release check

A standalone macOS program run with CRAFT_UPDATE_DEV was still offered
Update and Restart for a release without a macOS .tar.gz, and that update
failed. release_installs now checks the release for that copy too.

release_installs_at takes the executable, the opt-in setting and the
platform, and a new test checks a loose program in a temporary folder
against releases with and without the .tar.gz. The tests use SoundCraft's
release files, since craft-launcher ships no command-line zip.

* craft-update: a failed self-update keeps its download for the next try

On macOS, Linux and FreeBSD, prepare_update removed the downloaded
release file whether or not the install succeeded, so every retry after
a failed copy, signature check, package install or swap downloaded the
whole release again. A failed install now keeps the file, as install_at
already does, and the next try reuses it through Http::download, which
checks it against the published SHA-256 first and fetches it again when
it does not match.

- The cache folder's self-update-download.txt records the name and size
  of the release file before the download starts. A self-update for a
  different release file removes the finished and partial download that
  the record names, so each app keeps the files of one release at most.
- An install that succeeds removes the file and the record. So does a
  failed install whose record could not be written, since nothing would
  remove that file later.
- On macOS, when hdiutil cannot attach the disk image, the file is still
  removed: an image left attached by an install that ended part way
  makes hdiutil refuse the same file with "Resource busy", while a new
  download of it attaches.
- Windows is unchanged: its update helper removes the download after
  the app exits.

* craft-update: a macOS install that stopped partway no longer blocks the next install

When a macOS install ends before it finishes (the app quits or
crashes), the copy it was making stays beside the app and the update's
disk image stays attached. The next attach of the same file fails with
"Resource busy".

- Each staging folder's name now carries the installing process's id,
  and the folder holds an empty marker file. Before it stages a copy, an
  install removes the marked staging folders of processes that are no
  longer running, and their empty unmarked ones. It also removes a
  staging folder from earlier launcher versions when it holds only the
  app's bundle and has not changed for a day. Backup folders and other
  apps' folders are left alone.
- Before it attaches an image, an install detaches the images that
  earlier installs of the same app left at their mount points in the
  cache, when their process has ended or, in this process, when no
  install uses them any more.
- When the attach fails, the install detaches its image if it is
  attached with no volume mounted and no other attach of the same file
  is running.
- A staging folder that can't be removed, or an image that stays
  attached, is logged, and the error of a failed install says so after
  its reason.

* craft-update: the kept-download test accepts either signature refusal

An unsigned bundle is refused as "isn't signed" today and as a failed signature check once codesign's exit status is read, so the test checks that the install stopped on its signature.

* craft-update: the share check covers nested and pixmaps folders

A standalone copy in a prefix gets Update and Restart only when the
update can write every `share` folder it replaces files in. The check
covered `share/<id>`, `share/applications` and `share/icons/hicolor`
only, but the update writes a copy of each file next to its
destination first. A folder inside `share/<id>` (such as `models`), an
icon folder such as `icons/hicolor/256x256/apps`, or `share/pixmaps`
(where an older flat release puts its icon) that the user can't write
stopped the update after the whole download.

The check now also takes in every folder inside `share/<id>`, each
`icons/hicolor/<size>/apps` folder that holds the app's icon, and
`share/pixmaps`. Icon folders of other packages and other themes are
not checked. A linked folder is checked but not walked. The walk reads
at most 4,096 folder entries, so a large tree does not slow every
start; a folder past that is reported by the install as before.

The same check runs for a standalone copy on Linux, so a Linux copy
whose nested share folder or `share/pixmaps` it can't write now gets
the download link too.

* craft-update: a release folder that can't be read stops a standalone update

A standalone update in a prefix copies every file under the release's
`share` folder. The walk skipped any folder it could not list, and the
archive keeps each folder's mode, so a folder that nobody may list left
its files out while the update reported success. An error reading an
entry of the release's `bin` folder was dropped the same way.

These are now errors that name the folder, and the update stops before
any installed file changes. A release without a `share` folder still
updates.

A release whose `share` or `bin` is a link was followed, so the update
copied files from outside the release into the prefix. A `share` or
`bin` that is a link, or a `share` that is a file, now stops the update.
Links further down under `share` are still skipped.

The unpacked release is removed after a failed update even when it holds
a folder that its owner can't list. Before, such a folder stayed in the
cache.

* craft-update: the share check covers doc, metainfo and mime, and follows links

The share check for a standalone copy in a prefix now also takes in
`share/doc/<id>` and every folder inside it, `share/metainfo` and
`share/mime/packages`. Release archives such as VectorCraft's Linux and
FreeBSD ones put files in all three.

The walk now follows links, as the update's writes do. A copy whose
`share/<id>` is itself a link had none of its folders checked, and a
folder behind a link inside `share/<id>` was not checked either. A
folder the walk reaches twice is read once.

When `doc/<id>`, `metainfo`, `mime/packages` or `pixmaps` is missing,
the check no longer falls back to `share` itself. A copy whose `share`
the user can't write, but whose app, `applications` and `icons/hicolor`
folders exist, gets Update and Restart again, as it did before the
previous commit.

The entry limit of 4,096 now applies to each of `share/<id>`,
`doc/<id>` and `icons/hicolor` separately, and it counts files and
folders. A large app folder no longer turns off the icon folder check.

In a folder with the sticky bit, such as `/tmp`, the user can rename
over a file only when the user owns the file or the folder. A copy now
gets the download link when such a folder holds one of the app's files
that another user owns: the program itself, its other programs
(`<id>-<name>`, such as `<id>-cli`), anything in `share/<id>` or
`share/doc/<id>`, or a `share` file named `<id>.<ext>` or
`<bundle id>.<ext>`.

An icon size that the installed copy doesn't have yet is still not
checked, and a read-only folder that the user added under `share/<id>`
does count. Linux runs the same checks for a standalone copy, and the
sticky check on the program file for an AppImage.

* craft-update: the app-program check has its own name

The check for the app's programs in a bin folder was named is_program, the name of the Windows check for an MZ header, so the crate did not build on Windows. It is now is_app_program.

* craft-update: a missing doc, metainfo or mime folder is checked through share

A Linux release writes files into `share/doc/<id>`, `share/metainfo`
and `share/mime/packages`, and the update creates each of these
folders when it is missing. When one was missing, the share check took
its nearest existing parent inside `share` and never `share` itself. A
copy whose `share` the user can't write and that lacked one of these
folders was offered Update and Restart, and the update failed after
the whole download.

For a missing `doc/<id>`, `metainfo` or `mime/packages`, the check now
takes the nearest existing parent with `share` included, as it already
did for `applications`. Only the old flat release layout writes to
`share/pixmaps`, so a missing `pixmaps` is still checked inside
`share` only, and a copy whose `share` the user can't write keeps
Update and Restart when every other release folder exists.

A folder in the app's folder or in `doc/<id>` that the user can't
read is still checked against its own permissions, as before. The
copy gets Download when the user can't write and search that folder.
The check can't list the folders inside it and does not check them.
A new test keeps this rule, and the check does not refuse a copy only
because a folder in it can't be read.

* craft-update: the staging cleanup's doc says which folders it removes

A marked staging folder is removed once its process has ended,
whichever app that install was for: the folder's name does not record
the app. Only staging folders of earlier launcher versions are checked
for the app's bundle.

* craft-update: a failed attach leaves its image attached when pgrep gives no answer

detach_unmounted, from #766, checked for another attach of the same
image file with is_running, which reads a pgrep that gives no answer as
"not running", and then force-detached the image's unmounted
attachments. It now uses process::running, from #753, and detaches only
when the check answers that nothing is running.
2026-11-11 00:07:01 -05:00

Craft App Libraries

Scanned by SonarCloud

This repo contains libraries used by the various Craft Apps. It's a Rust monorepo workspace, and the libraries can be included by any Craft Apps that need them.

In the future this may grow to include non-Rust library code.

If any libraries grow beyond Craft App usage, eg. RAW format support, they may be promoted out and into their own libraries for the broader ecosystem to take advantage of.

Layout

A Cargo workspace (edition 2024, resolver 3). Each library is a crate under crates/<name>, named craft-<name>, with version, edition, license and repository inherited from the workspace (license.workspace = true, and so on) and [lints] workspace = true. Register each crate in [workspace.dependencies] in the root Cargo.toml.

Apps depend on a crate by git:

craft-<name> = { git = "https://github.com/storytold/craft-libs", rev = "<commit>" }

Libraries

  • craft-platform: optional native dialog lifecycle, Linux/BSD desktop appearance observation and browser download ownership; application policy stays in the apps.

  • craft-ai-core: the data model and contract for AI in the Craft Apps (connections, models, tools, requests, jobs, consent, history, route spec). Pure, no I/O, wasm-safe. Contract in docs/ai-shared-layer.md.

  • craft-pdf: independently optional PDF parsing, interpretation, rasterization and guarded authoring. Apps retain layout, fonts, editing and export policy; writer-only consumers do not enable the shared reader.

  • craft-atomic: Crash-safe atomic file writing and replacement with same-directory staging, directory fsync, Apple F_FULLFSYNC fallback, transient lock backoff, and fault injection for the Craft suite.

  • craft-cms: pure-Rust ICC v2/v4 colour management: profile parsing and writing, transforms, rendering intents, black point compensation, soft proofing, gamut checks, 3D LUTs and built-in profiles (sRGB, Display P3, synthetic coated CMYK and others).

  • craft-crw: clean-room Canon CRW (CIFF) raw decoder, from LightCraft: the heap container, the entropy coding derived by black-box analysis (frozen code tables), sensor black and white levels and as-shot white balance. The complete derivation record (plan, sources log, scripts, verification) is in crates/crw/clean-room/. Colour stays in each app.

  • craft-dialogs: native file and message dialogs that never block the event loop: queued requests shown at the end of the frame and answered on a later frame, free-standing Open and Folder panels and Save sheets on macOS, case-insensitive filters. What to do with the answer stays in each app.

  • craft-doc: reads PhotoCraft .pcraft, VectorCraft .vectorcraft and EffectCraft .ecproj documents without the apps: the kind and version, the previews and PDF they carry, and their pages and layers. Rendering and editing stay in each app.

  • craft-docpath: decides whether a path or file: URL stored in a document may be opened. Paths stored in documents are followed only when they are local or on the document's own network share; other network paths, device paths and non-file URLs are refused.

  • craft-dxf: DXF tag reader and writer, from CADCraft: ASCII and binary DXF as (group code, value) tags, split into sections and entities, with pre-2007 code pages and version detection, and written back. Bounded on hostile input. Mapping tags to a drawing stays in each app.

  • craft-ecproj-format: the envelope of EffectCraft .ecproj project files: savedBy, the schema version and its check, and the file object with the project and its saved view as type parameters. The project model stays in EffectCraft.

  • craft-font-catalog: the installed fonts on every platform (font folders, fontconfig and Flatpak host folders, CoreText, DirectWrite, the font store), lookup by family, style, localized, legacy, full and PostScript names, family loads shared between threads, and rescans when fonts are installed or removed.

  • craft-font-manager: Craft Font Manager as a library: search and filters over the Google Fonts index, installed fonts by family, the font store interface, and (egui feature) FontsPanel, the whole interface as one panel for the launcher's Fonts tab and the standalone app.

  • craft-font-store: the suite's font store: the pinned Google Fonts and Noto indexes plus indexes the user adds (never curated), exact lookup by family and style or PostScript name, and installs from each source's URLs (jsDelivr, then raw GitHub) checked by size, sha256 and parsing, with the licence, a manifest, and removal. Off until the user turns downloading on; agents can't.

  • craft-fonts-build: embeds the optional craft-fonts faces from an app's build.rs (CRAFT_FONTS_DIR, CRAFT_FONTS_REQUIRED, script filters) as a table of EmbeddedFaces. No dependencies; the app picks the faces.

  • craft-heif: optional HEIF/HEIC still-image decoder, from LightCraft: heic-rs reads the container and HEVC pictures, and this crate converts to 16-bit RGB to match libheif (VUI range and matrix, chroma upsampling, imir, ICC on grid tiles). Also orientation, alpha, EXIF/XMP and thumbnails. It never panics, and apps put it behind a non-default feature.

  • craft-i18n: translations over the embedded craft-i18n catalogs: lookup with fallback (pt-br → pt → English), plurals, language names, the system locale and a pseudo locale. The catalogs stay a fully optional build input; apps keep tl! and their language setting.

  • craft-tiff: TIFF and BigTIFF IFD reader and writer, from LightCraft: every field type, the IFD chain, SubIFDs and Exif/GPS/Interop pointer IFDs, maker-note IFDs (Canon, Nikon, Sony, Fujifilm, Olympus, Panasonic, Pentax), strip and tile layout. Bounded and panic-free on hostile input. Pixel decompression stays with the callers.

  • craft-jpeg: clean-room decoder for 12-bit and arithmetic-coded JPEG (T.81 SOF0/SOF1 at 12 bits, SOF9, SOF10), from PhotoCraft, and a frame probe for routing. Metadata stays in each app.

  • craft-keymap: keyboard bindings: chords and their text forms, scopes, conflicts, user overrides stored through craft-settings and import of other applications' shortcut sets. Command catalogs, file readers and the shortcut editor UI stay in each app.

  • craft-audio: audio file I/O and codecs (WAV, AIFF, FLAC, AAC), streaming and seeking, and packet decoding for any demuxer. See its README for formats and features.

  • craft-media: bit-level I/O (bitstream), clean-room MP4/MOV (isobmff) and Matroska/WebM (matroska) demuxers and muxers, an Ogg demuxer (ogg), from FilmCraft, and an ISO BMFF box reader. Containers only; codecs are in craft-audio, and video codecs will go in a separate crate.

  • craft-menu: an app's menus as a macOS menu bar: the Mac layout (app menu, Window, Help, system-key clashes), a platform backend trait, and key equivalents fed back to egui as the input egui-winit would have made. Commands and their enabled state stay in each app.

  • craft-pcraft-format: the envelope of PhotoCraft's .pcraft bundle: Manifest<D> with the document as a type parameter, TileRef, the entry paths, the manifest decode limits and the migration runner. The document model stays in PhotoCraft.

  • craft-print: PDF submission to CUPS and Windows, printer discovery, duplex/copies/colour and driver preferences. PDF generation stays in each app.

  • craft-proof: proofing per language (spelling, suggestions, grammar, hyphenation, a Hunspell reader, user words, Language packs). The word lists and patterns are craft-i18n's language-data/, embedded at build time from CRAFT_I18N_DIR: English and the carry-over locales by default, the set CRAFT_PROOF_EMBED names otherwise (en for English only); other languages come from installed Language packs.

  • craft-quit: native macOS quit, egui close handling and an optional app menu. Unsaved-document decisions and save prompts stay in each app.

  • craft-settings: config.toml settings storage for every app: platform folders, portable mode and overrides, atomic saves while running, backup, damaged-file recovery and schema migrations. Policy in docs/settings-storage.md.

  • craft-text: text processing below the composer. Its hyphen module finds hyphenation points, with a bundled US English dictionary and patterns and optional hypher patterns for 47 more languages (one cargo feature each).

  • craft-ui: SVG catalogs, icon buttons, tabs, pane controls, transactional docking, print settings and theme helpers for egui frontends; assets, themes, localization and command handling stay in each app. Requires Rust 1.95 or later.

  • craft-vectorcraft-format: the envelope of VectorCraft's .vectorcraft file: its members, versions, embedded images and the data after the JSON, with the document as a type parameter. The document and its migrations stay in VectorCraft.

  • craft-web-api: the page command API every Craft web build offers its page — one window.<app> shape (request, execute, commands, docInspect, uiInspect, screenshot) over the app's JSON control channel.

  • craft-web-host: the host messages between a Craft app's web build and the page that hosts it (?host=parent or data-host="page"): one contract and one bridge for every app.

  • Shared UI component audit: source inventory, extraction priorities and compatibility boundaries across the Craft suite.

  • Theme infrastructure: eight-app comparison and the small shared widget-state styling boundary.

  • Panel docking: validated panel moves, floating groups, workspace adapters and an optional egui docking surface.

License and credits

craft-libs is dual-licensed under MIT or Apache-2.0, at your option. Copyright (c) 2026 ArtCraft Team and the craft-libs contributors. Required notices are in NOTICE.

Bundled fonts, icons, images and other assets keep their own open licenses; each one is listed with its author, source and license in ATTRIBUTION.md.

S
Description
No description provided
Readme
126 MiB
0 Stars 1 Watchers 0 Forks
Languages
Rust 98.8%
Max 0.8%
Python 0.3%