Since the release workflow moved to craft-common-actions (#129), the caller
passed only the Apple and WINDOWS_CERTIFICATE secrets, not the AZURE_* ones that
Trusted Signing uses, so the 0.3.1 MSIs and portable exes came out unsigned.
Co-authored-by: Claude Opus 5.5 <[email protected]>
craft-common-actions calls `package.sh --formats deb,rpm,tar`, but package.sh
split formats on spaces only, so the riscv64 job built the binary, matched no
format, wrote nothing to dist/release and failed the 0.3.0 release at upload.
package.sh now accepts commas or spaces and rejects unknown format names.
Co-authored-by: Claude Opus 5.5 <[email protected]>
Bumps the workspace version to 0.3.0 for the new features since 0.2.1, and
applies cargo fmt to the two files from #149 that failed CI's format check.
Co-authored-by: Claude Opus 5.5 <[email protected]>
* ci: refactor workflows to use reusable artcraft-common-actions
* ci: point reusable workflows to storytold/craft-common-actions
* ci: pin storytold/craft-common-actions to commit 27a978f and pass secrets explicitly
* ci: use @main (unpinned) for craft-common-actions with explanation comment
* macOS: open disk images reliably, and say why when they won't open
Installing or updating an app on macOS could fail with only "couldn't open
the disk image". hdiutil's own reason was thrown away, and every install of
an app mounted its image at the same folder, which an attach that was
interrupted or timed out could still be holding.
- Each install mounts its image at a new, empty folder.
- The attach skips hdiutil's checksum pass (-noverify): the download's
SHA-256 is already checked against the release, and on a large image that
pass alone could run past the time limit, now 5 minutes.
- When the image still won't open, or the app won't copy, the message says
why in hdiutil's or ditto's words ("couldn't open the disk image:
Resource temporarily unavailable").
- hdiutil, ditto and codesign are run by full path: a launcher started from
Finder or at login gets a minimal PATH.
process::run is capture's sibling that returns the helper's error message;
capture is now built on it. The launcher's own macOS self-update installs
through the same code, so it gets all of this too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Release 0.2.1
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
It asked for a wgpu device up front, which panics where there is none: the
FreeBSD VM job has failed on it since it landed. Like the other tests, let
the default renderer create a device only when a snapshot is rendered.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
diskimages-helper turns an attach away ('Resource temporarily unavailable')
while it is busy with another image. The DMG tests added in #51 run in
parallel and made this a flaky failure (about 1 run in 5 locally); the
launcher hits the same thing when several apps update at once. Attaches now
take a process-wide lock (which the tests' hdiutil create shares) and back
off and retry (1, 2, 4, 8 s) when something outside the launcher holds it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The footer laid out the check status and the rate-limit notice first at
their full width, then put the links, social icons and platform label in a
right-to-left layout in whatever space was left. When the left side was
wider than that space (720 and 960 px, and even 1180 px while rate limited)
the two drew on top of each other.
The right side now comes first and keeps its size; the status and notice
share what is left. The notice gets priority: it switches to a short form
("GitHub limit hit; back in N min", full text on hover) when the full one
won't fit, and the check status is cut short with "…" (new
widgets::hud_elided). Below about 300 px of room for the status the
platform label is dropped.
A UI test renders the rate-limited footer at 720, 960 and 1180 px and checks
the notice and status don't intersect any link, icon or the platform label.
Fixes#16
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
* 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:[email protected]/ 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 <[email protected]>
---------
Co-authored-by: khutchings <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Issue #29's window wasn't 0.1.2: its tagline and header are 0.1.0's, and
it offered Download where a portable 0.1.2 offers Update and restart. A
0.1.0 launcher sat in the tray; starting 0.1.2 asked it to show its
window and exited, and that window still offered 0.1.1, found while 0.1.1
was the latest release.
A second start now says hello with its version and the running launcher
answers with its own. A newer start makes the running one quit (unless
it's downloading or installing; then it shows its window) and takes over
the port. The same version or older shows the running window as before.
Launchers from before the handshake hang up without an answer and can't
be asked to quit, so the new start runs anyway. A bare "show" from an
old launcher is still understood.
Tests: the handshake over loopback, including a running launcher that
predates it.
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
* Fix Show in Explorer opening Documents for paths with spaces
reveal() built `/select,<path>` as one argument and passed it with
Command::arg, which quotes any argument containing a space. The Craft
apps' MSIs install under C:\Program Files, so Explorer received
"/select,C:\Program Files\...\app.exe", didn't recognise the switch and
opened its default folder, Documents.
The argument is now `/select,"<path>"`, with only the path quoted and
forward slashes turned into backslashes, passed with raw_arg so Rust
leaves it alone. The argument building is a plain function with unit
tests that run on every platform.
Fixes#39
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Refuse a path with a quote in it; give the Explorer tests their own module
A " would end the quoted /select path early and pass the rest to Explorer as
switches. The module name clashed with the pgrep tests that landed in #81.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* fix(wgpu): prefer D3D12 (incl. WARP) over GL on Windows (#2087)
On Windows, rank any Direct3D 12 adapter (WARP included) above GL when
WGPU_BACKEND is unset. wgpu ranks WARP last, so GPUs without a D3D12
driver (Intel HD 4000, Radeon TeraScale) got the GL adapter, whose legacy
driver fails Surface::configure; wgpu panics on that before any app code
runs. Hardware D3D12 adapters keep wgpu's order. Reporter verified
WGPU_BACKEND=d3d12 (WARP) works on the affected machine.
Refs storytold/artcraft#2087
* Drop a reference to an issue number from another tracker
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]>
* Add Brazilian Portuguese (pt-BR) UI localization
* Rebase onto German and French: pt-BR last in the registry, new update strings
Document that pt-PT falls back to Brazilian Portuguese; smoother wording for
'Verifying checksum…'.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: AlastorIRL <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
* Add a French interface
fr.tsv translates the whole interface, the tray menu and tooltip, each app's
tagline and the backend's install/update messages, and Settings > Language
offers Français. fr, fr-FR, fr_CA.UTF-8 and the like select it. French counts
0 and 1 as singular, so it gets its own plural rule. tests/french.rs checks the
grid, the table, dates and a backend message.
* Rebase onto German: French last in the registry, new update strings, lowercase months
French writes month abbreviations in lower case (12 août 2026). Tests use a
never-supported tag (xx-XX) next to fr-FR.
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]>
* Add a German interface
de.tsv translates the whole interface, the tray menu and tooltip, each
app's tagline, the package formats and the backend's install/update
messages into German, and Settings > Language offers Deutsch. German
locales (de, de-DE, de-AT, de-CH, de_DE.UTF-8…) select it through Auto.
German has two plural forms (one|other), like English. Dates read
"3. Okt. 2026", relative times "vor 5 Min." / "vor 2 Tagen", and the
user is addressed with the informal "du". Product and app names stay in
English.
The locale test that used de-DE as an unsupported tag now uses fr-FR,
and new tests cover German tag matching, plurals, the date format and a
backend message.
* Test the German UI and shorten labels the table clipped
tests/german.rs picks Deutsch in Settings, checks the setting is saved,
and checks the grid, the table and the web build in German, like the
Chinese UI test. The table clipped three German labels, now shorter:
the "Installed" and "Released" columns read "Lokal" and "Datum", and
the "Available to install" status reads "Installierbar".
* Rebase onto main: translate the new update strings; use a never-supported tag in tests
xx-XX instead of fr-FR, so the test keeps meaning 'unsupported' as more
languages arrive.
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]>
* Find AppImages outside ~/Applications; add a menu entry for installed ones
Detection only looked in ~/Applications and only for the exact release
file name, so an AppImage the user kept in Gear Lever's ~/AppImages or in
~/.local/bin, or one renamed to photocraft.appimage (Gear Lever's rename,
lower-case extension), showed as not installed. Detection now also looks
in ~/AppImages and ~/.local/bin, accepts either case of the extension, and
matches only the release name or the bare app name (version unknown for
the latter). Updates still install into ~/Applications and only remove
old copies there; AppImages the user keeps elsewhere are never deleted.
Installing an AppImage now writes ai.storyteller.<app>.desktop under
$XDG_DATA_HOME/applications (the host's ~/.local/share when the launcher
runs as a Flatpak) and copies the app's bundled 128 px icon into
icons/hicolor/128x128/apps. Exec is quoted per the Desktop Entry spec;
Categories and StartupWMClass match each app's own .desktop file. The
entry carries X-ArtCraft-Launcher=true and the record lists the files
written, so the launcher only replaces or removes what it wrote (plus an
entry the app wrote for itself that runs the copy being replaced or
removed). The .desktop text is a pure function with unit tests.
Fixes#14Fixes#37
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Remove only AppImages the launcher installed; rebase onto main
AppImages detected in ~/AppImages or ~/.local/bin belong to the user (or Gear
Lever): Remove now deletes only those in ~/Applications or recorded by the
launcher, and explains otherwise.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
The launcher offered a .deb whenever apt-get and dpkg were on PATH, and
an .rpm only when dpkg was absent. Fedora can have apt and dpkg
installed (mmdebstrap and friends pull them in), so there it downloaded
the .deb and ran apt-get on it, with .rpm ruled out entirely (#21).
The distro family now comes from ID and ID_LIKE in /etc/os-release:
Debian and Ubuntu get .deb, Fedora, RHEL, CentOS, SUSE and openSUSE get
.rpm, each only with pkexec and the matching tools. Arch and unknown
distros get no system package. After the distro's package, AppImage now
ranks ahead of Flatpak, whose bundles need a Flathub runtime. Installing
a .deb without apt-get fails with a clear message, and removing one falls
back to dpkg -r.
The parsing and the choice are pure functions in core's new distro
module, tested for Fedora, Rocky, openSUSE, Ubuntu, Mint, Arch and an
unknown distro.
Fixes#21
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
* macOS: install only apps signed by the Craft team, at the release's version
A Craft App, or the launcher's own update, now replaces the installed copy
only when the staged bundle carries a valid Developer ID signature from team
DJ6XS33FX8 (codesign with a requirement pinning the team, which works
offline) and reports the release's version. A bundle that reports only the
numbers matches a pre-release with the same numbers. On failure the staged
copy is removed and the installed app is left as it was.
* Rebase onto main: keep both sides' tests in install.rs
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: s17sh70x2jfl031ag <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
ureq only reads HTTPS_PROXY, ALL_PROXY and friends, so a proxy set in
the OS was ignored and GitHub stayed out of reach where it needs one
(#30). Http::new now picks the proxy once and gives it to every agent:
the environment variables first, as before, else the OS setting.
On Windows that is the manual proxy in HKCU Internet Settings, in
either the single host:port form or the per-protocol
http=...;https=... form (the https entry wins), with ProxyOverride as
the bypass list. On macOS it is the HTTPS (else HTTP) proxy from
scutil --proxy with its exceptions list. Linux and the BSDs keep
using the environment. Loopback always goes direct. The chosen proxy
is logged at info level without credentials.
ureq's own win-system-proxy feature treats the whole ProxyServer
value as one address, which breaks the per-protocol form, so the
parsing lives here in pure, tested functions. PAC scripts and WPAD
are not supported.
Fixes#30
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Issue #44: the footer kept saying "GitHub's hourly limit was reached"
when it hadn't been, or long after it stopped mattering.
A 403 or 429 from the API counted as rate limited until
x-ratelimit-reset. GitHub sends that header with every answer, so a
secondary limit (quota left, Retry-After: 60) became up to an hour of
"hourly limit reached". The reset now only counts when
x-ratelimit-remaining is 0; otherwise Retry-After decides, or a minute
on a bare 429, as GitHub documents.
The notice also stayed up after the ArtCraft feed had answered every
app. A check now reports the limit only when some app had to come from
the release pages or the saved answers.
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
* linux: prefer Flatpak if available and support Flatpak sandbox execution
* linux: match default package format to launcher format (Flatpak for Flatpak, AppImage for AppImage)
* ui: default package preference selection to launcher format (Flatpak for Flatpak, AppImage for AppImage)
* Automatically download and install updates when available
* Fix clippy (unnecessary_map_or, collapsible_if) and rustfmt
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]>
* Add Linux riscv64 release build and switch runners to Ubuntu 26.04
- release.yml: new linux-riscv64 job cross-compiled on ubuntu-26.04 against
ports.ubuntu.com riscv64 libs, tar.gz only, artcraft-launcher --version
smoke-tested under QEMU. Updated flatpak runner matrix to ubuntu-26.04.
Draft-release job now requires linux-riscv64 and checks the tarball.
- packaging/linux/package.sh: CROSS_ARCH / CROSS_TARGET / CROSS_COMPILE / EMULATOR support.
- platform.rs: add Arch::Riscv64 variant, map linux-riscv64 token, and prioritize
tarball format for riscv64 on Linux.
- web.rs: detect riscv64 architecture in visitor user agent.
- README and AGENTS.md: document the riscv64 tarball.
* linux: build deb and rpm packages for riscv64
* ci: configure actionlint to recognize ubuntu-26.04 runner labels
* Keep riscv64 and Flatpak release jobs on Ubuntu 24.04
24.04 is what PhotoCraft's linux-riscv64 job uses, so the riscv64 binaries
need glibc 2.39 (as the README says) rather than 26.04's newer glibc, and
the release pipeline keeps runner images it has already proven on. Drop the
actionlint label config that 26.04 needed, revert the default_kinds and
download_kinds matches to match on the OS alone, simplify the riscv UA check,
and rustfmt.
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]>
When dpkg/rpm don't list the app, the launcher can't know which package
manager installed it, so label it a system binary (open it, leave update
and removal to whatever installed it) instead of Deb/Rpm with a guessed
package name. Require the binary itself; fix clippy and rustfmt.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Include InstallSource::Rpm in ours package filter
- Add bundle_id to candidate names for Deb and Rpm packages
- Strip architecture suffix from package name in parse_dpkg
- Clear AppImage environment variables in process::command to prevent polluting system tools
- Fall back to detecting system desktop entries and binaries with CLI version extraction
- Add unit tests for candidate names, RPM filtering, and binary CLI version
Fixes#97
A minor release, not 0.1.4 (the previous commit's number, never built or
tagged): it adds features. Since 0.1.3, from community contributions: an
interface in Russian and Simplified Chinese (Chinese drawn with the
system's own font); several apps can install at once with one password
prompt on Linux; a verified download is kept for a retry when its install
step fails; the window closes to the tray on Wayland; HTTPS certificates
are verified by the OS; Direct3D 12 by default on Windows (fixes a startup
crash on hybrid AMD laptops); app names centred on their icons.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Since 0.1.3, from community contributions: an interface in Russian and
Simplified Chinese (Chinese drawn with the system's own font); several
apps can install at once with one password prompt on Linux; a verified
download is kept for a retry when its install step fails; the window
closes to the tray on Wayland; HTTPS certificates are verified by the OS;
Direct3D 12 by default on Windows (fixes a startup crash on hybrid AMD
laptops); app names centred on their icons.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
zh-CN.tsv translates the whole interface, the tray menu and tooltip, each
app's tagline and the backend's install/update messages into Simplified
Chinese, and Settings > Language offers 简体中文. Simplified Chinese
locales (zh-CN, zh-SG, zh-Hans, zh_CN.UTF-8, a bare zh) select it; an
explicit Traditional script (zh-Hant-CN) or a Traditional region (zh-TW,
zh-HK, zh-MO) doesn't. Chinese has one plural form. The tray's menu and
tooltip now follow the interface language (Russian rows added too).
The bundled faces have no Chinese glyphs, so on the desktop the
launcher borrows the system's own Chinese font (cjk_font.rs): PingFang
(in /System/Library/Fonts on older macOS, a downloaded font asset on
newer ones), Hiragino Sans GB or Heiti on macOS; Microsoft YaHei or
SimSun on Windows; Noto Sans CJK (static or variable) or WenQuanYi on
Linux and FreeBSD. The face is picked by its family name (PingFang SC,
Noto Sans CJK SC…), regular weight, rather than a hard-coded index in
the collection. Files over 100 MB are skipped and nothing panics. The
font (PingFang is 78 MB) is read only when Chinese is the language in
use, chosen or through Auto, and then kept for the session. Until then
the picker says "Simplified Chinese", and "Simplified Chinese (简体中文)"
once the font is loaded. With no Chinese font (and on the web), the
interface stays in English and Settings explains why, instead of boxes
for every character.
Rebased onto main from the original PR (#34). Changed from the original:
- No bundled font: NotoSansSC-Regular.ttf (10.6 MB), its OFL and README,
their ATTRIBUTION and NOTICE rows and the zh-CN screenshots are left
out; the system font replaces them, as above.
- The i18n framework fixes in that PR (trim_end in the catalog tests,
the n == 1 plural fallback, 1-3 @plural forms, the registry-driven
language label) landed with the Russian catalog; this adds the
script-aware zh tag matching on top.
- zh-CN.tsv: "Verifying checksum…" mentions the checksum (正在验证校验和…),
update.check_auto keeps "automatically" (自动检查更新), "Launcher" stays
in English like the rest of the product name, rows were added for
every string main gained since, and rows for messages the launcher
never sends were dropped.
- tests/chinese.rs works with or without a system Chinese font: with
one it switches to 简体中文 and checks the grid, table, web and saved
setting; without one it checks the English fallback and the note. The
glyph-coverage test now covers the system font, when there is one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every user-visible string in the interface is now looked up from a
per-language catalog at render time instead of being compiled in as
English. English stays the source of truth and the lookup key; a missing
row falls back to English rather than failing.
- crates/ui-egui/src/i18n: `t`/`tl!` for literals, `tr_ctx` (one English
word needing different translations), `tr_id` (command-id keyed),
`trn`/`tn` (plurals), `fmt` ({name} placeholders filled after the
lookup) and `message`/`msg` (backend status and error text, translated
where it is shown). Split into lang.rs (the registry and locale tags),
detect.rs (the system language), translate.rs (lookups) and catalog.rs
(the TSV parser); mod.rs only declares them.
- ru.tsv: the whole interface in Russian, with East Slavic plural forms,
month abbreviations, a day-month-year date template and each app's
tagline. Product and app names (ArtCraft Launcher, Craft App Suite,
PhotoCraft…) stay in English.
- The language follows the system by default: ARTCRAFT_LAUNCHER_LOCALE,
then the OS's preferred UI languages through the sys-locale crate
(desktop only; LANGUAGE/LC_*/LANG on Linux and the BSDs, the preferred
languages on macOS, the user's UI languages on Windows), and `?lang=`
then `navigator.languages` on the web. Settings > Language overrides it.
- core stays pure and wasm-safe: `time::ago_kind` and `month_short_en`
let the UI render relative times and dates in the current language.
Ported onto main from the original PR (#28): every string main added
since (feed notices and alerts, the "using ArtCraft API and GitHub"
footer, window buttons, waiting and retry states, the install-cancelled
toasts, filter tooltips, package formats, the reveal label) goes through
the catalog, with Russian rows. Also changed from the original:
- `@msg` templates now match real messages: `Couldn't open {name}: {e}`
translates "Couldn't open PhotoCraft: …", the most specific template
wins. Rows for messages the launcher never sends (clipboard, recovery
data) are gone; rows for its install/update/remove toasts are added.
- The locale probes no longer spawn /usr/bin/defaults or reg.exe; the
small, dependency-free sys-locale crate asks the OS instead.
- Folded in from #34's framework fixes: catalog tests split rows with
trim_end (trim dropped the leading TAB, so plain rows were silently
skipped), the English plural fallback goes by n == 1, @plural rows take
1 to 3 forms, and Settings::language_label comes from the registry.
- Tests are registry-driven: placeholders, plural form counts and, for
every catalog marked complete, coverage of every tl!/t literal, every
tn plural key, each tagline and the package formats. A Russian UI test
renders the grid and table. Catalog warnings go to the log, not stderr.
- ru.tsv: "Launcher" and "Craft App Suite" stay in English; "лаунчер" is
spelled one way; "Verifying…" (Проверка целостности…) and "Discard
download" (Удалить скачанное) no longer read like "Checking…" and
"Cancel download"; the table's Installed/Latest headers are short
enough to fit; the reveal rows match the backend's "Show in …" labels.
- The toolbar drops its "Sort" caption when longer labels leave no room
for it, rather than drawing over the filters.
- Unused settings helpers and LANGUAGE_CODES were removed, and core's doc
comments now describe what the UI actually translates.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each install ran its own pkexec call at the same time; all but the first
lost the dpkg lock and failed with error 100. Installs that reach the
system installer now wait their turn ("Waiting for another app…", with
Cancel), and Linux packages waiting together go into one privileged call,
so they share one password prompt. apt waits up to 60 s for another
package tool's lock. On Windows msiexec runs one at a time (error 1618).
The launcher's own update waits for other installs before it restarts.
Ported onto main and onto "Keep the verified download when the install
step fails", with these changes from the original PR:
- A failed job counts as installed when the new version is found
afterwards only if it failed in a Linux call shared with other apps;
an MSI, AppImage or single-package error, a pause, and either
cancellation (including INSTALL_CANCELLED) stand.
- The launcher's update waits for other installs at most 10 minutes and
can be cancelled from its banner ("Cancel update"); giving up names the
apps still installing and says to answer or close the password prompt.
- Cancellation toasts say what was called off: "Download cancelled",
"Install cancelled" (download kept), "Uninstall cancelled" or
"Update cancelled", instead of a bare "Cancelled".
- The waiting card uses the restyled layout and the app's own colour,
with no hard-coded sizes.
Fixes#6
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A download that passed its SHA-256 check was deleted whether or not the
install succeeded, so every retry downloaded it again (usually after the
password prompt was dismissed). It now stays until the install succeeds,
is hashed again before it is reused, and shows as "Retry install" (or
"Retry update to <version>") with "Download again" in the menu. A
dismissed system prompt now says "Install cancelled" rather than
"Download cancelled". A download checked by size only is still deleted.
Ported onto main from the original PR: the check thread, rewritten for
ArtCraft's release feed, reports kept downloads after it marks the apps
loaded; the Retry button uses the restyled widgets::button; the backend
tests build on main's Shared and ask no real service; the grid test
scrolls the card into view. Finished files kept after a failed install
are now cleaned up after 7 days instead of 30 (paused downloads keep 30).
Fixes#5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
winit cannot hide a Wayland window, so closing to the tray cancelled the
close and then did nothing. Where the window cannot hide, the close now
goes through and the launcher keeps running with only the tray; a tray
click or a second launch opens a new window in the same process. Other
platforms keep the single hidden window. The header's own close button
(no decorations) takes the same path as the window manager's.
With no window, settings that change (the last check, updates announced)
are saved to eframe's app.ron as they change, so quitting from the tray
no longer loses them. A --hidden start at login on Wayland now starts in
the tray without mapping a window first, so none flashes up.
Rebased onto main from the original PR: the Launcher/Shell split now
carries main's custom title bar, has_desktops, macOS "show on the current
desktop", wgpu options and theme-token clear colour. The settings saving
with no window and the windowless start at login were added here.
Fixes#4
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The grid card's text started at the top of its row and sat 11 pt below the
icon's centre. The text is now measured (centered_lines) and the icon's slot
is as tall as the text, so the two share a centre at any scale. The table
already centred its rows on main. The icon now reports itself to
accessibility, which the alignment test uses to find it.
Ported onto the restyled card from the original PR.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
harness() called .wgpu(), which builds a wgpu device for every test up
front. Nine tests then created, used and destroyed a Vulkan device at the
same time on parallel test threads, and Vulkan loaders before 1.4.350
crash on that (KhronosGroup/Vulkan-Loader#1863): about one run in ten
ended in SIGSEGV here.
Without .wgpu() egui_kittest's default renderer builds the same wgpu
renderer on the first render, which only happens when
ARTCRAFT_LAUNCHER_SNAPSHOTS is set. Ordinary runs no longer need a
graphics adapter at all.
Fixes#12
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
On Windows the launcher now asks wgpu for DX12 with GL as the fallback instead
of wgpu's default list, which starts with Vulkan. WGPU_BACKEND still overrides
it; other platforms keep wgpu's defaults.
Without this the launcher died on startup with STATUS_ACCESS_VIOLATION on an
ASUS G513QY (Ryzen 9 5900HX integrated Radeon + Radeon RX 6800M). The crash is
inside AMD's Vulkan driver (amdvlk64.dll): the integrated GPU runs the OEM
driver from 2021 while the 6800M runs Adrenalin from 2026, both register a
Vulkan ICD, the loader reports the 6800M twice, and wgpu's pick dies right
after the surface is configured. AMD's installer never updates the OEM iGPU
driver, so this state is common on hybrid laptops. DX12 and GL both work.
Also reorders one import in self_update.rs that stable rustfmt flagged, so
`cargo xtask ci` passes again.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
On some Windows PCs every app showed "Not available here" / "Offline" and
the tooltip said "invalid peer certificate: UnknownIssuer". We read the
Windows root store once with rustls-native-certs, but Windows only adds
many roots when something asks it to verify a chain. Chrome and Edge
verify on their own, so a PC that only uses them can lack the root
GitHub's certificate needs. Antivirus HTTPS scanning hits the same path.
Use ureq's platform-verifier feature (rustls-platform-verifier) so the OS
checks each chain itself and fetches missing roots. Linux still reads the
system store. No bundled root list comes in.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Since 0.1.2: release information comes from ArtCraft's API, with GitHub
as the fallback; calls to the API are rate-limited and back off hard on
HTTP 429; the footer names the services in use; ArtCraft announcements
and alerts are shown.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every launcher polls the same server, so a FeedThrottle now gates each request:
- at most one a minute once the first load is over;
- up to ten a minute during the first load, front-loaded, with the gaps doubling after each
failure (the first load ends at the first answer or after ten requests);
- failures back off 2, 4, 8 … minutes (at most an hour), with jitter;
- HTTP 429 waits at least 15 minutes, doubling per 429 in a row up to six hours, never sooner
than Retry-After, plus up to 50% jitter so a herd of clients doesn't return in step. The
backoff is saved to cache/feed-throttle.json, so relaunching doesn't skip it.
A check that would have to wait more than ten seconds goes without the feed this time, and the
saved answer stands in.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Footer: "Checked just now · using ArtCraft API", "· using GitHub" or
"· using ArtCraft API and GitHub", from where each release on screen came
from (Latest events now carry their origin), so it follows what's shown.
Retries and fallback:
- The very first poll (nothing ever loaded or saved) tries the ArtCraft
API once, then GitHub at once, so the window fills
- Every other poll tries the API up to three times (1 s, then 3 s apart)
- Until every app has loaded once, a failing API lets GitHub be asked
fairly freely (every 5 minutes per app)
- After that, GitHub is asked only for apps the API doesn't list or
hasn't refreshed in a day, or once the API has seriously failed (three
checks in a row and an hour without an answer); then hourly per app.
Until then the API's last answer keeps being shown
Tests: the fallback policy for each stage, and the footer wording.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The launcher now asks storyteller-web
(GET https://api.storyteller.ai/v1/craft_apps/release_info), which polls
GitHub for every Craft App on a schedule, instead of polling GitHub itself.
- At launch and every five minutes: one request (10 s timeout) for every
app's latest release, plus renames and notices
- Per app, GitHub is asked only when the feed can't vouch for it: the app
isn't listed, its entry is more than 24 hours old, or the feed didn't
answer. Even then, at most once an hour per app (Refresh asks at once).
With pre-releases on, GitHub decides (the feed serves stable releases)
- Read leniently (crates/core/src/feed.rs): every field optional, unknown
fields ignored, wrong types read as missing, one bad entry never spoils
the rest. Download links must still point at the app's own GitHub
releases; a moved repo counts only when the feed's repo_renames says so
- The last good answer is saved for an instant first paint and stands in
(while still fresh) when the feed can't be reached
- Announcements show inline until dismissed; alerts pop up until the user
presses OK (with "Open link" for web links); app renames show as
announcements; dismissals persist
- "Check for updates": Automatically (every five minutes) or Only when I
ask; older saved choices load as Automatically
- ARTCRAFT_LAUNCHER_FEED_URL points at a staging server or a local copy
Tests: the feed parser against hostile input, renames and notices; a
contract test against a response produced by storyteller-web's own code;
the feed-first rule with a counting fake GitHub; the notices UI.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The setup wizard gains a folder page between Welcome and Ready: pick where
ArtCraft Launcher goes (default %LOCALAPPDATA%\Programs\ArtCraft Launcher;
still per user, so it notes to pick a folder you can write to), and a
"Create a desktop shortcut" box. The page is the stock WixUI_InstallDir one
plus the box, with the same path checks and Change... browser.
Both choices are recorded under HKCU and reused by the next version, so an
update (self-update runs the MSI with /passive) installs into the same folder
and keeps the shortcut choice. The box starts ticked on a first install; an
update from 0.1.0 or 0.1.1, which never asked, adds no desktop icon. A value
given on the command line (INSTALLDESKTOPSHORTCUT=0/1) wins. The desktop
shortcut is a normal MSI component, removed on uninstall.
self_update.rs now recognises an MSI install by the folder the MSI records
(InstallDir), not only the default one, so a launcher installed elsewhere
still updates itself.
The Windows ARM64 workflow now installs twice on real hardware: with the
defaults (default folder, Start Menu and desktop shortcuts) and into a chosen
folder without the desktop shortcut, checking each uninstall removes it all.
Version 0.1.2.
Co-authored-by: Claude Opus 5.5 <[email protected]>
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 <[email protected]>
- LinkedIn icon linking to https://www.linkedin.com/company/artcraft-ai,
in the footer and Settings › Community
- Social icons now read X, Instagram, LinkedIn, YouTube, TikTok
- The icon is from Font Awesome Free 6.7.2 (CC BY 4.0), since Simple
Icons no longer carries LinkedIn's logo; white fill and a square view
box, artwork unchanged. Credited in assets/social/README.md,
ATTRIBUTION.md and NOTICE
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Opening the launcher from the menu bar used to switch to the desktop
(Space) its window was last on, which is jarring when you're working on
another one. A new setting, "Show the window on the current desktop"
(on by default), sets AppKit's "move to active space" behavior on the
window whenever it's shown, so it comes to the desktop you're on instead.
Turning it off restores macOS's default of switching to the window.
macOS only: the code and its dependencies compile only for macOS, and
the setting appears only where the host has desktops. objc2,
objc2-foundation and objc2-app-kit are already in Cargo.lock through
winit and tray-icon; they're pinned to those versions, and only safe
AppKit calls are used.
Co-Authored-By: Claude Opus 5.5 <[email protected]>