13 Commits
Author SHA1 Message Date
skylar taylor-barrick 4ab652afd8 Migrate Craft Launcher to egui 0.37 (#873)
* Migrate Launcher to egui 0.37 and preserve current-main behavior

* egui037: record final published pins and local qualification

* fix(ui): gate CJK language drawing until fonts are installed

* Document migrated launcher UI contracts and regression invariants
2026-10-25 10:49:00 -04:00
Dylan M. Taylor ec40f2e1cd fix(security): enforce bounded reads, deadlines, and terminal escaping in local mirror
Resolves F-019 (CWE-400), F-020 (CWE-117), and F-021 (CWE-117).
- Enforces per-byte read deadlines and socket timeouts during header parsing.
- Bounds maximum line length (8 KiB) and maximum total headers size (64 KiB) to prevent slowloris/DoS.
- Sanitizes request method and target before logging to stderr to prevent ANSI escape sequence injection.
2026-10-24 22:10:07 -04:00
smittie2000andClaude Opus 5.5 16285cd62e xtask local: order pre-releases by semver (rc.10 after rc.9) (#820)
compare_versions compared pre-release tags as plain strings, so
1.0.0-rc.10 sorted before 1.0.0-rc.9. `cargo xtask local` uses it to pick
the newest mirrored build for `status` and to choose what `prune` keeps,
so a tenth release candidate would have been treated as older than the
ninth and pruned first.

Parse with the workspace's semver crate and compare by precedence, which
orders numeric pre-release parts as numbers.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 01:43:05 -04:00
Dylan M. Taylor 7cf50d7a10 security: address CodeRabbit review on IPC token CSPRNG, retry, and mirror timeouts 2026-10-22 19:28:40 -04:00
Dylan M. Taylor d75fd3b2d1 security: authenticate single-instance replace and bound the local mirror server
Two local-service hardening findings:

- Single-instance replacement authority (CWE-862): the listener on the fixed loopback
  port 127.0.0.1:47413 granted a Replace (which can make an idle launcher quit) on a
  peer's claimed version alone, so a different local OS account could end the victim's
  launcher. A per-user token now gates Replace: it lives in the user's own data folder
  (created owner-only, 0600 on Unix), the second start echoes it in its hello, and the
  primary only honours Replace when the token matches. An unauthenticated peer may
  still bring the window forward (Show, harmless), never quit the launcher. The token
  is derived without a new RNG crate, hashing OS-seeded RandomState with sha2.

- Local mirror server DoS (xtask local serve, CWE-770): a thread was spawned per
  connection with no admission limit, request reads had no deadline (idle clients held
  threads forever), and a failed thread spawn could panic the listener. Add a bounded
  handler cap (64; excess connections get 503), a 15s request-header read deadline
  lifted before streaming the body, and graceful handling of spawn failure.

Addresses findings: single_instance.rs CWE-862, xtask/local.rs CWE-770.
2026-10-22 19:22:00 -04:00
Dylan M. Taylor 0091a46d3b time: keep old UTC output past jiff's narrow civil range
`civil_from_unix` (core) and `rfc3339` (xtask) took `i64` Unix seconds, which
the old hand-rolled Hinnant code converted across the whole `i64` range. The
jiff rewrite built the date with `epoch.checked_add(whole_days).unwrap_or(epoch)`,
but jiff's civil calendar only spans years -9999..=9999 — far narrower than
chrono's — so any instant past year 9999 silently became the epoch, changing
existing output. `i64::MAX` used to read `Dec 4, 292277026596` /
`292277026596-12-04T15:30:07Z`, and `i64::MIN` `Jan 27, -292277022657`, not 1970.

Fix: move the instant into jiff's range by whole 400-year Gregorian cycles
(146_097 days) before converting, then restore the real year afterwards. `rest`
is always under one cycle, so the in-range date lands in 1970..=2369 (well inside
jiff) and `rest * 86_400` can't overflow; the year is restored through `i128` so
`cycles * 400` is exact at the `i64` extremes. jiff stays the only calendar logic
(it does the month/day); the cycle count just shifts the year. No epoch fallback
for any value the old code converted. core still builds for wasm32.

Tests now pin the old output instead of the epoch:
- core civil_from_unix(i64::MAX) == (292277026596, 12, 4),
  civil_from_unix(i64::MIN) == (-292277022657, 1, 27),
  civil_from_unix(253_402_300_800) == (10000, 1, 1)
- xtask rfc3339(i64::MAX) == "292277026596-12-04T15:30:07Z",
  rfc3339(i64::MIN) == "-292277022657-01-27T08:29:52Z",
  rfc3339(253_402_300_800) == "10000-01-01T00:00:00Z"
2026-10-21 17:22:06 -04:00
Dylan M. Taylor e61e2407d7 Lock in the epoch fallback for clocks outside jiff's range
The jiff date conversions already fall back to the epoch instead of
panicking, and format years with {:04} (rfc3339) or a plain integer
(format_date), unchanged from the old hand-rolled code. Add tests that
civil_from_unix(i64::MAX/MIN) and rfc3339(i64::MAX) stay on the epoch date
and never panic.
2026-10-21 09:14:03 -04:00
Dylan M. Taylor caea05eff8 Use jiff for UTC/calendar conversion instead of hand-rolled copies
This repo already uses jiff (ui-egui, for the system time zone), so the
hand-rolled Howard Hinnant civil/days-from-civil copies move to jiff rather than
pulling in a second time crate. jiff is lifted to a workspace dependency:
- crates/core/src/time.rs: parse_rfc3339's days_from_civil and civil_from_unix
- xtask/src/local.rs: rfc3339 (feed timestamps)
ui-egui now takes jiff from the workspace. core keeps only civil-date math (no
time zone), so the wasm build is unaffected. Output and parsing semantics are
unchanged and pinned by the existing tests (RFC 3339 parsing, leap days, day
rollover, the feed's YYYY-MM-DDTHH:MM:SSZ). AGENTS.md now says time goes through
jiff.

Follow-up to storytold/cadcraft#1785
2026-10-21 07:59:58 -04:00
Dylan M. Taylor 721e960bda feat(catalog): align app catalog ordering with getartcraft.com, drop AppGroup, and standardize nix/freebsd workflows (#507) 2026-10-17 13:42:02 -06:00
Brandon ThomasandClaude Opus 5.5 cbae640688 Add FigCraft; release 0.4.1 (#313)
* Add FigCraft to the launcher

FigCraft (storytold/figcraft): interface design and prototyping, the
thirteenth Craft App and the sixth in the New group.

- Catalog row (brand colour #02da96 from FigCraft's icon README), details
  page (no pictures yet), Linux desktop categories, icon tables, tagline
  in every catalog, xtask local mirror list.
- Icon copied unmodified from FigCraft's repo; ATTRIBUTION.md row.
- The Windows installer art fits five rows of icons.
- Test that a companion figcraft-server tarball is never picked as the app.

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

* Release 0.4.1

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 12:28:29 -04:00
648546e942 Add an option to install Craft apps from local builds instead of GitHub (dev builds only) (#64)
* Install and update Craft apps from a local mirror

Add an opt-in developer mode for installing the Craft apps from builds on
this machine instead of GitHub:

- ARTCRAFT_LAUNCHER_DOWNLOAD_BASE (loopback http only) points the launcher
  at a local mirror. While set, every check reads the mirror's
  release_info.json, trusts download links under the mirror, never treats
  entries as stale, and never falls back to GitHub or ArtCraft's API.
- cargo xtask local sync pulls every sibling app checkout (git pull
  --ff-only --recurse-submodules), packages each app whose version isn't
  mirrored yet with its own packaging script, and files the results under
  <mirror>/<app>/<version>/ with SHA256SUMS.txt and notes from git log.
- cargo xtask local serve serves the mirror on 127.0.0.1 (with range
  requests, so paused downloads resume) and writes the feed fresh from the
  mirror's contents on every request.
- cargo xtask local status compares each checkout with what's mirrored.

* Sync the local mirror on an interval while serving it

cargo xtask local serve --sync-every <30m|1h|1d…> runs the same sync as
cargo xtask local sync on a background thread, one after another, so the
mirror follows origin without a second process. Downloads keep being
served during a build, and a version only joins the feed once its build
is complete. Pulls are --no-stat to keep the server's log readable.

* Build every new commit as a dev version with --every-commit

cargo xtask local sync --every-commit (and serve --sync-every … --every-commit)
builds each new commit rather than only new versions. A commit of a 0.4.0
checkout is packaged as 0.4.1-dev.<commit time> through the app's
<APP>_VERSION override, so it orders after 0.4.0, after earlier commits,
and before the real 0.4.1. The full version lands in the bundle's
<App>Version key, which the launcher reads, so it offers each build as an
update and sees it as current once installed.

* Keep the local mirror out of release builds; parse loopback URLs strictly

ARTCRAFT_LAUNCHER_DOWNLOAD_BASE makes the launcher trust the mirror's own
checksums and skip GitHub, so a shipped launcher must not read it: it now
needs the local-mirror cargo feature (cargo run --features local-mirror),
which release builds never enable. The loopback check parsed the URL by
prefix, so http://127.0.0.1:1@evil.example/ passed; it now requires the host
to be exactly 127.0.0.1, localhost or [::1] with a numeric port. The same
check guards ARTCRAFT_LAUNCHER_FEED_URL.

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

---------

Co-authored-by: khutchings <7854672+khutchings@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 05:03:14 -04:00
BFlatandClaude Opus 5.5 019330f99a Restyle to match getartcraft.com, add an installer wizard, blue app icon (#23)
The launcher now looks like the apps page on getartcraft.com: the site's
colour tokens, square corners, 1 px hairlines instead of shadows, app cards
in a shared-hairline grid with numbered tabs and "Photo*Craft*" wordmarks,
mono upper-case labels and buttons, and square badges for status.

Fonts are the site's own, embedded in the binary: Archivo (620, 118 % width)
for headings and app names, Inter for body text and Geist Mono for labels,
all variable fonts from google/fonts (OFL-1.1) set to the site's weights.

The window has a custom title bar on Windows and Linux (drag to move,
double-click to maximize, minimize/maximize/close, resize from the edges)
and a transparent one on macOS with the traffic lights in the header.

The MSI used to flash a progress bar and close. Double-clicked, it now opens
a WixUI wizard (Welcome, Ready, progress, Finish with "Launch ArtCraft
Launcher"), with art drawn by package.ps1 from the ArtCraft mark and the
Craft apps' icons. Self-update still runs it with /passive. CI installs the
WiX UI and Util extensions.

The app icon is now the blue "A" mark, getartcraft.com's favicon.
packaging/icons.sh runs on any OS: `cargo xtask icns` packs the .icns.

Also: two clippy fixes in crates/native that newer clippy rejects, and
refreshed README screenshots.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 17:39:01 +07:00
Brandon ThomasandClaude Opus 5.5 ec69f0be2c ArtCraft Launcher: install and update the Craft App Suite
A native Rust/egui launcher for the 12 Craft Apps (7 classic + 5 new).

- Grid (icon cards) or sortable table view, filters and search
- Status per app: available, installed and up to date, or update
  available with version and release date
- Installs where each OS keeps apps, from the official release packages:
  /Applications (DMG), Program Files (MSI), deb/rpm via the system
  package manager (Flatpak/AppImage fallback), /usr/local on FreeBSD
- Detects apps installed by hand; updates keep the installed format
- Every download verified against its published SHA-256; running apps
  are never replaced
- Tray/menu-bar mode, start at login, one notification per new version
- GitHub API with ETag cache, falling back to release pages when
  rate-limited; release source is pluggable for a future catalog API
- Web build lists latest releases with per-platform downloads
- Packaging and release workflows for macOS, Windows (x64/x86/arm64),
  Linux (x86_64/aarch64), FreeBSD and web

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 21:08:24 -04:00