100 Commits
Author SHA1 Message Date
Brandon ThomasandClaude Opus 5.5 d6fdab6596 Release 0.4.0 (#291)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 06:29:23 -04:00
Brandon ThomasandClaude Opus 5.5 1aaf7b27be AGENTS.md: agents never land or approve third-party apps (#213)
A catalog row, download source or install path for an app outside storytold/, or a rule change
that would allow one, is for a maintainer to decide. Agents flag it and stop.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:41:18 -04:00
Brandon ThomasandClaude Opus 5.5 2b86d4ca52 Point the details pictures at storytold/craft-launcher-assets (#221)
The pictures repo was renamed from artcraft-launcher-assets to match craft-launcher. GitHub
redirects the old name for now, but the launcher should not depend on that.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:02:23 -04:00
AlexisandBrandon Thomas cb82e851cd Add a details drawer for each Craft App (#60)
* 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>
2026-10-12 05:00:22 -04:00
0e87e90aef macOS DMG: branded window and hdiutil create, so the app passes codesign --strict (#169)
* macOS: build the DMG with hdiutil create so the app passes codesign --strict

hdiutil makehybrid -hfs put a com.apple.FinderInfo on every file in the
image, so the signed app inside the DMG (and any ditto copy of it) failed
codesign --verify --deep --strict. That is why verify.sh had dropped
--strict. Build the image with hdiutil create -srcfolder (retried, since
create can hit "Resource busy" on CI), clear build-time xattrs before
signing, and make verify.sh check the app in the finished DMG and a ditto
copy of it with --strict again.

Ported from storytold/pdfcraft#823 and storytold/lightcraft#959.

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

* macOS DMG: branded window background and icon layout

The launcher's DMG opened as a plain Finder window, unlike every other
Craft app. Add packaging/macos/dmg/ the same way WordCraft and PhotoCraft
did: a background (the ArtCraft mark whole on its blue, title, Ink and
Paper), a .DS_Store with the window size and icon positions, and the
generate.py that writes both reproducibly. The volume is now named
"ArtCraft Launcher" without the version, which the background alias needs;
the DMG file name keeps the version.

Fixes #20

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

---------

Co-authored-by: Fabio Lussi <198260595+itrycodinganditsbad@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 04:26:54 -04:00
78ef4a8c77 Linux: draw on the desktop's GPU, not always the discrete one (#164)
* Linux: draw on the desktop's GPU, not always the discrete one

egui asks wgpu for the high-performance adapter. On a hybrid laptop in
PRIME on-demand mode (Intel + NVIDIA, X11, GNOME) that is the NVIDIA GPU
while the display server runs on the Intel one, so every frame is copied
between the GPUs. During a resize the window then shows stale or garbled
frames and can stop drawing until it is resized again.

On Linux and the BSDs, ask for no power preference instead: wgpu then
takes the first adapter Vulkan lists, and Mesa's device-select layer lists
the display server's GPU first (or the one DRI_PRIME picks). Measured on
an RTX 4060 + Intel RPL-S laptop: in on-demand mode the list starts with
the Intel GPU, in `prime-select nvidia` mode with the NVIDIA one, so the
launcher draws on whichever GPU drives the screens. WGPU_POWER_PREF still
overrides it. Windows and macOS keep their current choice.

Refs #69

* Linux GPU: keep wgpu's adapter order, but a software rasterizer last

Without Mesa's device-select layer the Vulkan order follows the ICD files, and lavapipe can come
before the real driver.

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>
2026-10-12 02:50:10 -04:00
1c03e2d2ea Accessibility/UI: follow the OS's animation setting (#201)
* 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>
2026-10-12 02:49:59 -04:00
cf573b2d35 Fix system theme matching on Linux Wayland (#189)
* Read the Linux desktop appearance portal so system theme matching works on Wayland.

* system theme: back off to every 5 minutes when there's no appearance portal

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>
2026-10-12 02:49:47 -04:00
250cc54335 Hide Craft apps the user is not interested in. (#185)
* 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>
2026-10-12 02:48:47 -04:00
93f12a5d9e i18n: add Japanese (ja) interface language (#177)
* 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.

* 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>

* 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: Brandon Thomas <echelon@gmail.com>
2026-10-12 02:48:34 -04:00
57105f8445 Add Hungarian localization and fix responsive settings layout (#146)
* 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(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>

* 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: Losminek <301412321+Losminek@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 02:48:21 -04:00
94d6480a11 Add Polish (Polski) as an interface language. (#138)
* 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.

* 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(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>

---------

Co-authored-by: Losminek <301412321+Losminek@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 02:48:08 -04:00
6d59370024 i18n: add Czech (cs) interface language (#167)
* 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(cs): translate "Installed · latest {latest}" (added by #195)

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

---------

Co-authored-by: Losminek <301412321+Losminek@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 02:45:12 -04:00
Brandon ThomasandClaude Opus 5.5 a464e432b3 i18n(es): translate "Installed · latest {latest}" (#211)
#195 added the string and #196 marked Spanish complete; they landed together, so the
completeness test failed on main.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 02:02:14 -04:00
d4c61ec110 Improve indeterminate installer progress bar animation (#163)
* Improve indeterminate progress bar animation

Refactor indeterminate progress bar rendering for smoother animation.

* progress: rustfmt, and skip the wrapped segment while it's off the track

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>
2026-10-12 01:57:06 -04:00
155d6bd6b0 Detect PdfCraft installs made by its PrintCraft-named MSI (#134)
* Match MSI entries and find app executables through pure functions

Windows detection decided which uninstall entry belongs to an app, and
which paths might hold its exe, inline in the registry-reading code
under #[cfg(windows)]. Neither decision needs the registry or the disk,
so move both into module-level functions, is_installer_entry and
exe_candidates, that tests can call on every platform. The windows
module keeps the registry reads and the exists() checks.

No behaviour change: an entry still has to carry the app's display name
and the Learning Machines LLC publisher, exactly, and the candidates are
the same paths in the same order. The new tests pin that down for every
app but PdfCraft, and for PdfCraft's own entry name.

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

* Detect PdfCraft installs made by its PrintCraft-named MSI

PdfCraft's Windows installer shipped as PrintCraft up to 0.2.1
(printcraft-0.2.1-windows-x64.msi). That MSI registers its uninstall
entry with DisplayName "PrintCraft" and installs printcraft.exe into
Program Files\PrintCraft, but Windows detection only accepted an entry
named after the app's current name and looked only for pdfcraft.exe, in
App Paths, the install folder and Program Files\PdfCraft. On a machine
still on that install the launcher showed PdfCraft as not installed,
with no version, and uninstall_msi found nothing to remove.

The catalog already knows the old asset stem (former_ids); add
former_names for the display names an app's installers registered,
since the capitalisation can't be derived from the stem, and names()
to go with stems(). Entry matching now accepts any of the app's names,
still exactly and still only from Learning Machines LLC, and the exe is
looked up under every stem in App Paths and under every name in Program
Files, current first. Apps without former names see the same match and
the same candidates as before.

The impact is limited to early adopters: PdfCraft 0.4.0 and later
register as PdfCraft, and their MSI keeps the same UpgradeCode, so
installing from the launcher already upgraded a PrintCraft install in
place. What this fixes is the wrong status and the missing Uninstall
until then.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 01:56:56 -04:00
a702ef16c8 Skip deb/rpm installs on rpm-ostree immutable systems (#107)
Fedora Atomic variants (Bazzite, Silverblue, etc.) block local dnf install;
prefer Flatpak and AppImage so one-click installs succeed without pkexec dnf.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 01:56:45 -04:00
0c3e639b4d Windows: compile DX12 shaders with FXC (#173)
wgpu's default DX12 compiler (Auto) loads whichever dxcompiler.dll the DLL
search path finds. The launcher ships none, and without a matching dxil.dll
the unsigned-DXIL warning fails wgpu's indirect-validation pipeline, the
device is lost and the launcher exits before the first frame. Pin the
compiler to FXC, which is part of Windows; WGPU_DX12_COMPILER still
overrides it. GL stays in the Windows backends: it is the deliberate
fallback for machines whose D3D12 adapters can't present to the window.

Fixes #126

Co-authored-by: Fabio Lussi <198260595+itrycodinganditsbad@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 01:56:24 -04:00
Nezuko-Chan 41ded15851 Fallback apps to install from release tarballs when no usable Flatpak spawn is available (#87) 2026-10-12 01:54:08 -04:00
Gerknis 98c31aa066 Add experimental flake.nix for NixOS/Linux development (#72)
* Add experimental flake.nix for NixOS/Linux development

This adds an experimental flake-based development environment and package entry for ArtCraft Launcher.

What this adds:
- `flake.nix` at the repository root
- Rust toolchain via `rust-overlay`
- Required native Linux libraries for the Rust workspace
- A dev shell via `nix develop`
- A package/app target via `nix run` / `nix build`

Why:
ArtCraft Launcher is a Rust workspace and currently has no Nix flake support. This makes local development and installation much easier for NixOS/Linux users.

Notes:
- This is intentionally minimal and experimental.
- It targets the development environment and local package use.
- Additional refinements may be needed for specific Wayland/X11 setups.

* Fix flake metadata to match ArtCraft Launcher and remove GTK dependency

* add flake.lock

* Update flake.nix: replace deprecated xorg packages with their new names
2026-10-12 01:54:03 -04:00
Diego OspinaandDiego Ospina c064fde960 i18n: add Spanish (es) interface language (#196)
es.tsv translates the whole interface, the tray menu and tooltip, each
app's tagline and the backend's install/update messages, in neutral
international Spanish. Settings > Language offers Español; es, es-ES,
es-MX, es-419 and the like select it under Auto.

AI-assisted, native review welcome.

Models: Opus 5.5, Terra 5.6, DeepSeek V4 Flash

Co-authored-by: Diego Ospina <diegozpy@users.noreply.github.com>
2026-10-12 01:54:00 -04:00
96ed437966 Desktop: write log records to standard error and a rotating log file (#178)
A launcher started from the Dock, the Start menu or at login has no
terminal, and the Windows build has no console, so its log records and
panic reports were lost.

env_logger now writes each record to standard error and to
logs/artcraft-launcher.log in the launcher's data folder (portable mode
included). Each start keeps the previous two runs' logs; the file is
capped at 16 MiB. The file is opened once this is the only launcher
running, so a second start doesn't rotate the first one's log away.
Panics are reported through the logger. The default filter is now info
for the launcher's crates and warn for the rest; RUST_LOG still wins.

Ported from storytold/pdfcraft#239.

Co-authored-by: Fabio Lussi <198260595+itrycodinganditsbad@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 01:53:54 -04:00
Osman Said e49bdc94dc Fix header label overlap in narrow windows (#130) 2026-10-12 01:53:50 -04:00
0358cda174 Linux: name the tray icon instead of showing the process id (#140)
tray-icon's default id is "<pid>-<n>" and the title is empty, so
StatusNotifierItem hosts showed numbers like "61965-1". On Linux and the
BSDs, give the icon the id "artcraft-launcher" and the title
"ArtCraft Launcher". Windows and macOS are unchanged.

Fixes #136

Co-authored-by: Fabio Lussi <198260595+itrycodinganditsbad@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 01:53:46 -04:00
MarekSpodBiedry 7938ed90ac macOS: set the notification bundle id so no 'Where is use_default?' picker appears (#95) (#116) 2026-10-12 01:53:42 -04:00
Val EnfysandClaude Opus 5.5 ba8a704908 macOS: keep the bundle's icon in the Dock while the launcher runs (#127)
eframe sets the app icon at runtime, and on macOS that replaces the
Dock icon with a raw bitmap (the 256 px PNG we passed, or egui's logo
when none is given), which skips the system's icon treatment. Pass the
empty icon on macOS so eframe leaves the bundle's .icns alone.

Fixes #32.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 01:53:38 -04:00
Vikram Bhaskaran 1f92b7db17 Show real versions for PdfCraft and SoundCraft in the launcher. (#195)
Those binaries print a former id (`printcraft 0.2.1`) or the display
name (`SoundCraft 0.5.0`). Detection required an exact machine-id
prefix, so the version was missing and the card said "installed
installed · latest …". Parse --version against stems and the product
name, and stop using the word "installed" as a version placeholder.
2026-10-12 01:53:34 -04:00
XianZheYangandYangXZ2008 80f70a1a33 Keep feed repo renames inside storytold, and never install without a checksum (#208)
Two download-trust gaps from #90.

A repo rename in the release feed only had to look like owner/name, so
a compromised feed could send an app's downloads, or the launcher's own
self-update, to another account's repository with matching links and
checksums, and every check would vouch for them. A rename now has to
stay under `catalog::GITHUB_OWNER` (storytold) at both ends, or it is
dropped and the feed's entry stops matching, so GitHub decides as before.

A release with neither a per-file digest nor a SHA256SUMS.txt line was
still installed, checked by size only, with a note after the fact; a
same-length altered file would have passed. `install::download` now
refuses it before fetching anything, with an error that says why. With
a checksum always in hand, the size-only path goes: `http::download`
takes the SHA-256 as `&str`, and `Verified` and `keep_download` are
removed.

Tests: a rename within storytold is followed, one out of it is dropped
in either direction, and a release without a checksum fetches nothing
and never reaches the installer.

Fixes #90

Co-authored-by: YangXZ2008 <141027290+YangXZ2008@users.noreply.github.com>
2026-10-12 01:53:30 -04:00
MarekSpodBiedry 31a5e426e7 linux: only run pacman when the pacman database exists, so an unrelated pacman on PATH is not launched (#158) (#180) 2026-10-12 01:53:25 -04:00
XianZheYangandYangXZ2008 165f5358e0 Dialogs: keep the uninstall prompt at its own size, not the last dialog's (#207)
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>
2026-10-12 01:53:22 -04:00
MarekSpodBiedryandskylar taylor-barrick 93caf01c4e fix(ui): remove third-party product comparisons
PR #84.

Closes #83.

Includes formatting-only corrections in the shared UI.

Co-authored-by: skylar taylor-barrick <hi@skylar.cc>
2026-10-11 23:38:46 -06:00
Dylan M. Taylor 7ba85e54f0 fix(ui): remove Classic and New app sections
PR #187.
2026-10-11 23:27:54 -06:00
Dylan M. Taylor 6124cf0693 fix(ui): enable website links for all Craft apps
PR #198.
2026-10-11 23:22:17 -06:00
Dylan M. Taylor 257e16c325 ci(actions): upgrade workflow actions to Node 24
Closes #159.
2026-10-11 22:44:40 -06:00
Brandon ThomasandClaude Opus 5.5 6e7a122866 Release 0.3.2: sign Windows builds again (#174)
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 <noreply@anthropic.com>
v0.3.2
2026-10-11 17:03:52 -04:00
Brandon ThomasandClaude Opus 5.5 b66486df82 Release 0.3.1: Linux riscv64 packages again under the shared release workflow (#170)
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 <noreply@anthropic.com>
2026-10-11 16:28:01 -04:00
Brandon ThomasandClaude Opus 5.5 41369d3416 Release 0.3.0; rustfmt platform.rs and install.rs (#160)
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 <noreply@anthropic.com>
2026-10-11 15:49:47 -04:00
Dylan M. Taylor c273a94a9a linux: fix Flatpak bundle updates, serialize installs, filter detection by arch, and add status logging (#149)
* linux: allow updating Flatpak bundles with --reinstall and improve error reporting

* linux: filter flatpak detection by arch, queue bundle installs, and log actions

* test(core): verify strict arch matching across all linux package formats
2026-10-11 14:49:33 -04:00
Dylan M. Taylor e2b01e7425 ci: refactor workflows to use storytold/craft-common-actions (#129)
* 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
2026-10-11 11:51:50 -04:00
BFlatandClaude Opus 5.5 c1a9b36f82 macOS: open disk images reliably, and say why when they won't open; 0.2.1 (#135)
* 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 <noreply@anthropic.com>

* Release 0.2.1

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
v0.2.1
2026-10-11 19:51:54 +07:00
Brandon ThomasandClaude Opus 5.5 c5b073f5f7 UI tests: the title-bar test needs no GPU unless it saves a snapshot
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>
2026-10-11 05:06:52 -04:00
Brandon ThomasandClaude Opus 5.5 8d76d193db macOS: attach disk images one at a time, and retry when the helper is busy
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 <noreply@anthropic.com>
2026-10-11 05:05:38 -04:00
e69164d8e5 Keep the footer's rate-limit notice clear of the links (#55)
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>
2026-10-11 05:04:17 -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
6f9722c91a Let a newer launcher take over from an older one that's already running (#59)
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 <198260595+itrycodinganditsbad@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 05:02:09 -04:00
MarekSpodBiedry 714522d577 Quit from the tray even when the window is minimized and gets no frames (#77) 2026-10-11 04:35:06 -04:00
5b7d4781f5 Windows: fix "Show in Explorer" opening Documents instead of the app's folder (#54)
* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

---------

Co-authored-by: Fabio Lussi <198260595+itrycodinganditsbad@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 03:59:59 -04:00
92e23e0348 Windows: prefer Direct3D 12 (including WARP) over GL when picking a GPU (#105)
* 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 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 02:38:31 -04:00
2b1b4412e5 Add a Brazilian Portuguese (pt-BR) interface (#92)
* 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 <noreply@anthropic.com>

---------

Co-authored-by: AlastorIRL <100581152+AlastorIRL@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 02:36:06 -04:00
89854ff8f2 Add a French interface (#85)
* 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 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 02:34:32 -04:00