* 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>
* Add a hand-written details page for each Craft App
core::details holds, for every app in the suite, a headline, a few
paragraphs, a list of features, where it runs and the list of its
pictures. The copy is the launcher's own: written from each app's page on
getartcraft.com and, for the five newer apps, from its README. It is
compiled in, so it works offline and in the web build, and it leaves out
figures that go stale (effect counts, timings).
The pictures are not in this repository. A page names them (a key and a
caption each) and AppDetails::screenshot_url turns a key into the address
of a 1200 px JPEG in storytold/artcraft-launcher-assets, pinned to a tag
(SCREENSHOTS_REV) so a saved copy never goes stale. It only answers for
keys the page lists, so no address is ever built from anyone else's text.
MAX_SCREENSHOT_BYTES caps what is accepted, and looks_like_jpeg refuses a
login page or error page that arrives as a 200.
PdfCraft lists none yet: its screenshots still show its old name
(PrintCraft). ATTRIBUTION.md points to the pictures repository, which
carries each picture's own provenance.
Tests check that every app has exactly one page, that each is filled in,
and that addresses come only from a page's own pictures.
* Open an app's details in a drawer from its card
Clicking a card (or an app's name in the table) slides a drawer in from
the right over the dimmed grid. It shows the app's headline, version and
main button, links to its website, GitHub and releases, a gallery of
screenshots, its features, the facts and notes of its latest release, and
the same trademark notice as the grid. Esc, the close button or a click
on the grid closes it. The page's text is the hand-written copy in
core::details; the version, date, download size and notes come from the
release the launcher already fetches.
The pictures are fetched when a page is opened, not bundled. The UI sends
Command::FetchScreenshots and gets one Event::Screenshot per picture, the
lead one first. The desktop backend downloads them through the launcher's
Http (its User-Agent, a 2 MB cap, redirects followed) and keeps them under
<cache>/screenshots/<revision>/<app>/<key>.jpg, so later opens, and any
time offline, read from disk. Only a JPEG is kept: a login page or error
page that arrives as a 200 is refused. A missing picture doesn't hold the
others back, but once the network has failed the rest fail at once instead
of each waiting for its own timeout. The web build does the same with
fetch and leaves the caching to the browser.
Until a picture is here, a frame of the same size says "Loading pictures…"
or, when it failed, that it couldn't be loaded and that reopening the page
tries again; nothing moves when the picture arrives. The lead picture
opens full-window, the arrows over the pictures and the arrow keys flip
through them, wrapping around, and a scroll bar stays visible (egui's
floating one only shows under the pointer).
The drawer's labels are tl! strings, with 13 rows in each of ru, zh-CN,
de, fr and pt-BR; details.rs joins the sources the catalog coverage test
reads. Every size taken from the window is kept non-negative: the new
tests resize it from 40 x 30 to 4000 x 2400 with the drawer open.
actions() becomes actions_in(), which can leave the links out of the
... menu where the page already shows them as buttons. The image crate's
jpeg feature is enabled to decode the pictures (zune-jpeg and zune-core
join Cargo.lock). The dim and the enlarged picture's backdrop are colours
in theme.rs.
* Bring the details drawer up to date with main
- Website is always a button now (#198); drop the "Like {name}" line and the trademark notice,
which main removed with the product comparisons (93caf01).
- Bring back widgets::eyebrow for the drawer's section headings (#187 dropped its last caller).
- The drawer's ⋯ menu leaves out only the links, so Hide/Unhide (#185) stays.
- No slide when the OS asks for reduced motion (#201).
- Translate the screen readers' "{name} details" label, and add the drawer's strings to the
Spanish, Czech, Polish, Hungarian and Japanese catalogs.
- AGENTS.md: name the pictures' download origin.
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Add a Polish interface
- pl.tsv: the full catalog (287 rows), written clean-room from the English
labels; plurals use the same one|few|many rule as Russian.
- Registered as the seventh language: "Polski" in the picker, locales pl,
pl-PL and pl_PL.UTF-8, complete_menus = true so the completeness tests
cover it.
- tests/polish.rs: the Polish UI test, modeled on the French one.
- README and AGENTS updated for the new language.
* Add Hungarian localization and fix responsive settings layout
* i18n: add Czech (cs) interface language
Czech catalog with every key of the most complete catalog (287 rows), the
cs language entry with locale detection (cs-CZ, cs_CZ.UTF-8, ?lang=cs),
a Czech plural rule (1 / 2-4 / other) with a unit test, and the README
language list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: add Japanese (ja) interface language
ja.tsv translates the whole interface, the tray menu and tooltip, each app's
tagline and the backend's install/update messages into Japanese, and
Settings > Language offers 日本語. Japanese locales (ja, ja-JP, ja_JP.UTF-8…)
select it; Japanese has one plural form.
Japanese is a CJK language, so cjk_font.rs now looks for a Japanese system
font (Hiragino Sans on macOS; Yu Gothic, Meiryo or MS Gothic on Windows;
Noto Sans CJK JP, Noto Sans JP, IPAGothic, Takao or VL Gothic on Linux and
FreeBSD), falling back to a Chinese one, which draws kana and most kanji,
where the system has none. The font is loaded per language, so switching
between Japanese and Chinese reloads the right face.
A fallback face is nudged onto the body font's baseline: egui puts a fallback
glyph's baseline at that face's own ascent and only centres the two line
boxes, which left a face whose metrics differ from Inter's riding high. On
Windows, Yu Gothic's line gap is half an em, so its kana and kanji sat about
0.29 em above the Latin text beside them (Microsoft YaHei, used for Chinese,
happens to match). The nudge, applied through a FontTweak, is the difference
in ascent and line height, and helps the Chinese faces too.
The README and AGENTS list the new language. Tests: the complete-catalog
checks cover ja.tsv (placeholders, plural forms, every tl! literal, taglines,
package formats), plus a locale/plural test, a Japanese glyph-coverage test,
a baseline test and tests/japanese.rs (grid, table, dates, a backend message).
* i18n: make the CJK font choice language-neutral
The CJK comments, the missing-font note and the plural-rule docs named the
languages, so each new one meant editing them all; they now say "CJK". Each
language's writing system (its `script`) now lives in the registry, and a
language is drawn only with a face for its own script — never another's,
which would leave characters as tofu — so a language the system has no font
for stays in English, like any other. Adding Korean or Traditional Chinese is
a registry row and a candidate list, not a special case.
* Hide Craft apps the user is not interested in.
* Read the Linux desktop appearance portal so system theme matching works on Wayland.
* UI: follow the OS's animation setting
With Windows' "Animation effects" switched off, the launcher still glided each
notch of the mouse wheel over several frames and faded its dropdowns in. egui
does both by default, and nothing asked the OS.
The shell now says what the OS wants (Host::reduce_motion) and the UI asks it
about once a second while it draws, so a change shows without a restart. With
fewer animations asked for:
- a mouse wheel scrolls at once. egui has no switch for its wheel smoothing but
applies a trackpad's small steps as they come, so each wheel event is
rewritten as a run of those in the shell's raw_input_hook;
- dropdowns, tooltips and dialogs appear without a fade, and scrolling to a
place jumps there (style.animation_time, style.scroll_animation);
- the rule along the top of a hovered card appears instead of wiping in.
Progress bars and spinners keep moving: they say that work is going on.
Where the setting comes from:
- Windows: SPI_GETCLIENTAREAANIMATION, read as bit 33 of UserPreferencesMask in
the registry, as SystemParametersInfoW needs unsafe;
- macOS: NSWorkspace's accessibilityDisplayShouldReduceMotion;
- Linux and the BSDs: the XDG settings portal over D-Bus (its reduced-motion,
GNOME's enable-animations, KDE's AnimationDurationFactor), followed on a
thread so the UI never waits for D-Bus;
- web: prefers-reduced-motion.
Animations stay on wherever the setting can't be read. winreg and zbus become
direct dependencies of the desktop app; both were already in Cargo.lock.
The Windows path was run on a machine with the setting off. The macOS and Linux
paths were type-checked for those targets but never run, and the web one only
compiled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* UI tests: hovering a card follows the OS's animation setting
With animations off, moving the pointer onto a card leaves the window still
within 3 frames: its rule and its icon's rise show at once. With them on, the
0.3 s wipe takes 19 frames at 60 frames a second. Each test fails when the
card's timing is made to ignore the setting.
The test window no longer has a download in progress, so it is still unless
something animates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(pl): use Polish plural rule, so 21 is "21 aplikacji"
plural_east_slavic treats 21, 31 and 101 as "one", which is right for
Russian but not Polish, where only 1 itself takes the singular.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* system theme: back off to every 5 minutes when there's no appearance portal
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Merge #189 and main into #201: one raw_input_hook, and test anchors on taglines
Main no longer shows the "Like …" product lines, so the tests find cards by their taglines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(cs): translate "Installed · latest {latest}" (added by #195)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(pl): translate "Installed · latest {latest}" (added by #195)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(hu): translate "Installed · latest {latest}" (added by #195)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(ja): translate "Installed · latest {latest}" (added by #195)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: hide/unhide strings for the Spanish, Czech, Polish, Hungarian and Japanese catalogs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* hungarian test: count only the settings body's links, not the cards' Website links behind the modal
#198 gave every card a Website link, and Hungarian labels both Webhely.
* hungarian test: card links are 14px text, settings icons 24px; filter on that
---------
Co-authored-by: DzikStar <82441193+DzikStar@users.noreply.github.com>
Co-authored-by: ferivoq <ferikegameplay@gmail.com>
Co-authored-by: Losminek <301412321+Losminek@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: leonaci <leonacism@gmail.com>
Co-authored-by: satchmo91 <eherrmann1991@gmail.com>
Co-authored-by: Vikram Bhaskaran <vikram.bhaskaran@outlook.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Add a Polish interface
- pl.tsv: the full catalog (287 rows), written clean-room from the English
labels; plurals use the same one|few|many rule as Russian.
- Registered as the seventh language: "Polski" in the picker, locales pl,
pl-PL and pl_PL.UTF-8, complete_menus = true so the completeness tests
cover it.
- tests/polish.rs: the Polish UI test, modeled on the French one.
- README and AGENTS updated for the new language.
* Add Hungarian localization and fix responsive settings layout
* i18n: add Czech (cs) interface language
Czech catalog with every key of the most complete catalog (287 rows), the
cs language entry with locale detection (cs-CZ, cs_CZ.UTF-8, ?lang=cs),
a Czech plural rule (1 / 2-4 / other) with a unit test, and the README
language list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: add Japanese (ja) interface language
ja.tsv translates the whole interface, the tray menu and tooltip, each app's
tagline and the backend's install/update messages into Japanese, and
Settings > Language offers 日本語. Japanese locales (ja, ja-JP, ja_JP.UTF-8…)
select it; Japanese has one plural form.
Japanese is a CJK language, so cjk_font.rs now looks for a Japanese system
font (Hiragino Sans on macOS; Yu Gothic, Meiryo or MS Gothic on Windows;
Noto Sans CJK JP, Noto Sans JP, IPAGothic, Takao or VL Gothic on Linux and
FreeBSD), falling back to a Chinese one, which draws kana and most kanji,
where the system has none. The font is loaded per language, so switching
between Japanese and Chinese reloads the right face.
A fallback face is nudged onto the body font's baseline: egui puts a fallback
glyph's baseline at that face's own ascent and only centres the two line
boxes, which left a face whose metrics differ from Inter's riding high. On
Windows, Yu Gothic's line gap is half an em, so its kana and kanji sat about
0.29 em above the Latin text beside them (Microsoft YaHei, used for Chinese,
happens to match). The nudge, applied through a FontTweak, is the difference
in ascent and line height, and helps the Chinese faces too.
The README and AGENTS list the new language. Tests: the complete-catalog
checks cover ja.tsv (placeholders, plural forms, every tl! literal, taglines,
package formats), plus a locale/plural test, a Japanese glyph-coverage test,
a baseline test and tests/japanese.rs (grid, table, dates, a backend message).
* i18n: make the CJK font choice language-neutral
The CJK comments, the missing-font note and the plural-rule docs named the
languages, so each new one meant editing them all; they now say "CJK". Each
language's writing system (its `script`) now lives in the registry, and a
language is drawn only with a face for its own script — never another's,
which would leave characters as tofu — so a language the system has no font
for stays in English, like any other. Adding Korean or Traditional Chinese is
a registry row and a candidate list, not a special case.
* Hide Craft apps the user is not interested in.
* i18n(pl): use Polish plural rule, so 21 is "21 aplikacji"
plural_east_slavic treats 21, 31 and 101 as "one", which is right for
Russian but not Polish, where only 1 itself takes the singular.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(cs): translate "Installed · latest {latest}" (added by #195)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(pl): translate "Installed · latest {latest}" (added by #195)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(hu): translate "Installed · latest {latest}" (added by #195)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(ja): translate "Installed · latest {latest}" (added by #195)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: hide/unhide strings for the Spanish, Czech, Polish, Hungarian and Japanese catalogs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* hungarian test: count only the settings body's links, not the cards' Website links behind the modal
#198 gave every card a Website link, and Hungarian labels both Webhely.
* hungarian test: card links are 14px text, settings icons 24px; filter on that
---------
Co-authored-by: DzikStar <82441193+DzikStar@users.noreply.github.com>
Co-authored-by: ferivoq <ferikegameplay@gmail.com>
Co-authored-by: Losminek <301412321+Losminek@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: leonaci <leonacism@gmail.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Every dialog shared one egui area id, "dialog". egui lays an area out in
the size it had the frame before, and the uninstall prompt's button row
was a right-to-left layout placed straight in the column, which fills
whatever height is left. So after Settings or the release notes had been
open, the prompt came up as tall as they were, with its buttons floating
in the middle, and never shrank back.
- Each dialog gets its own area id (`Dialog::key`), so a small prompt
starts from its own size, and so does each feed alert.
- The uninstall prompt's and the feed alert's button rows sit in
`ui.horizontal` like the other dialogs' rows, so they take only a
row's height.
- A UI test opens Settings, closes it, opens the uninstall prompt and
checks the buttons follow the text (they sat 243 px below it before).
Fixes#88
Co-authored-by: YangXZ2008 <141027290+YangXZ2008@users.noreply.github.com>
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 <noreply@anthropic.com>
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 <198260595+itrycodinganditsbad@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Automatically download and install updates when available
* Fix clippy (unnecessary_map_or, collapsible_if) and rustfmt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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) <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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>
- 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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
When a newer launcher is released, the banner offers "Update and
restart": the new build is downloaded and verified like any Craft App,
put in place the way this copy was installed, and started by a small
helper once this process has exited (it holds the single-instance lock,
and Windows locks a running exe).
- macOS .app: new bundle copied from the DMG over this one, reopened
- Windows MSI (per-user): helper runs the new MSI after exit
- Windows portable: helper unzips over the folder after exit; settings stay
- Linux AppImage: new AppImage renamed over this one
- Linux .deb / .rpm: system package manager, then restart
- Flatpak, FreeBSD and dev builds keep the download link
ARTCRAFT_LAUNCHER_PRETEND_VERSION makes an installed copy act old, to try
the update against the current release. Tested: the helper waits for the
old process to exit; a real DMG replaces a bundle in place.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ideas from the ArtCraft Suite Updater:
- Arch / AUR: detect each app through pacman under all three AUR names
(<app>, <app>-bin, <app>-git; PdfCraft also as printcraft*). Updates
and removals of AUR installs show the paru / yay / pacman command
instead of installing a second copy
- Read package-manager versions the package-manager way: drop the
revision (0.5.0-1), epoch (1:) and VCS suffix (0.3.0.r47.g…), and map
Debian's ~. Fixes .deb installs of the latest release showing an
update forever
- Flatpak installs that list no version are read with `flatpak info`
- Look again at what's installed every 5 minutes, and when the window
regains focus (at most every 30 s), so apps installed or removed
outside the launcher show up; one detection pass at a time
- Package-list parsing lives in crates/native/src/packages.rs, tested on
every platform
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Small monochrome service icons (Simple Icons 16.34.0, CC0) linking to
ArtCraft's accounts, after Website · GitHub · Discord in the footer and
in Settings › Community; quieter than Discord, brighter on hover
- Settings: the community description gets its own line, so the
Website and GitHub buttons no longer overlap it
- Credit the icons in assets/social/README.md, ATTRIBUTION.md and NOTICE
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Header: the ArtCraft wordmark (white on dark, ink on light), "Launcher ·
Craft App Suite" and the tagline "Open Source Tools for Creatives,
Makers, and Builders"
- Tray / menu-bar icon is now the ArtCraft "A" mark, rendered unmodified
from docs/brand; the old self-drawn tray icons are gone
- Repo docs (NOTICE, ATTRIBUTION, README, asset licenses, AGENTS.md)
call out that the ArtCraft name, logo and mark are not MIT/Apache
- Table: "Inspired by" column; every column sorts both ways, empty cells
last; the grid's sort menu has the same options
- Adobe / Microsoft / Autodesk / Avid trademark notice under both views;
inspirations shown with ®
- Links: Discord button in the header; Website · GitHub · Discord in the
footer, tray menu and Settings; each card links to its app's website
(classic apps) and GitHub repo, and the … menu has both
- Fix links never opening: enable eframe's "links" feature
- Clear stale partial downloads at startup
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every download failed at its last read: ureq's body limit errors once
the limit is reached, and it was set to the exact file size. Sizes and
checksums are now checked by the launcher, with headroom on the limit.
- Pause keeps the partial file; Resume continues from the same byte
(HTTP range requests). Cancel discards it.
- Dropped or stalled connections retry with backoff from where they
stopped, shown as "Reconnecting…"; if the connection stays down the
download pauses itself instead of failing
- Partial downloads survive quitting and show as paused next launch
- Ask for identity encoding so bytes arrive exactly as published
- SHA-256 from GitHub's digest or SHA256SUMS.txt; a mismatch refetches
once from scratch. Releases with no checksum install with a size
check and say so; a checksum list that can't be fetched is an error
- Tests against a scripted local server for each failure mode
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>