Files
craft-launcher/AGENTS.md
T
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 <[email protected]>

* 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 <[email protected]>

* i18n(cs): translate "Installed · latest {latest}" (added by #195)

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* i18n(pl): translate "Installed · latest {latest}" (added by #195)

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* i18n(hu): translate "Installed · latest {latest}" (added by #195)

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* i18n(ja): translate "Installed · latest {latest}" (added by #195)

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* 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 <[email protected]>
Co-authored-by: ferivoq <[email protected]>
Co-authored-by: Losminek <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-12 02:48:34 -04:00

10 KiB

AGENTS.md: guide for AI agents and contributors

ArtCraft Launcher installs, updates and launches the Craft App Suite (PhotoCraft, LightCraft, FilmCraft, DesignCraft, VectorCraft, PdfCraft, EffectCraft, WordCraft, DeckCraft, GridCraft, CADCraft, SoundCraft). It is written in Rust only (no JavaScript or TypeScript): the desktop app is native egui/eframe, and the web build is the same Rust compiled to WebAssembly (trunk + wasm-bindgen). No Tauri, Electron or webview shells, ever.

The product name is always ArtCraft Launcher in user-facing text: UI, window titles, About, tray menu, notifications, installers, release names, docs prose. The suite is the Craft App Suite. Machine names stay lowercase: crates and binaries (artcraft-launcher, artcraft-launcher-*), file names, and the app id ai.storyteller.artcraft-launcher. Note the GitHub repo is storytold/craft-launcher, while every file and package is artcraft-launcher-*.

Standards and learnings shared across the Craft apps live in ../craftrules (read its README.md and AGENTS.md). Contribute reusable learnings there, never code.

1. Workspace map

crates/
  core      artcraft-launcher-core      pure and wasm-safe: the app catalog, versions, GitHub
                                        release parsing, picking the right asset per platform
  native    artcraft-launcher-native    desktop only: HTTP, detecting installed apps, download,
                                        verify and install
  ui-egui   artcraft-launcher-ui-egui   the shared egui UI (desktop and web)
apps/
  artcraft-launcher                     desktop shell (eframe): window, tray icon, notifications
  artcraft-launcher-web                 the same UI in the browser (trunk; catalog and downloads only)
xtask/                                  cargo xtask ci | wasm | version | ico
packaging/                              macOS, Windows, Linux, FreeBSD and web packaging scripts
assets/app-icon/                        the launcher's own icon (master SVG, renders, tray icons)
assets/app-icons/                       the Craft apps' icons, copied from each app's repo
assets/fonts/                           Archivo, Inter, Geist Mono (OFL-1.1): getartcraft.com's faces

Layering: core depends on no other workspace crate and must build for wasm32-unknown-unknown. native may use core. ui-egui may use core (and native only behind cfg(not(target_arch = "wasm32")) or through a trait the desktop shell implements). Nothing below the apps opens windows, tray icons or notifications.

2. Golden rules

Never crash (outranks feature work)

A bad network response, a malformed release, a half-downloaded file, a full disk or a corrupt settings file must produce an error the user can act on, never a panic. Shared standard: ../craftrules/standards/never-crash.md.

  • Non-test code never panics. No unwrap(), expect(), panic!, unreachable!, todo! or unimplemented!. Return the crate's error type and propagate with ?.
  • Every production crate root (lib.rs, each binary's main.rs, and xtask) carries #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)].
  • No unsafe. The workspace sets unsafe_code = "forbid".
  • Everything from the network is hostile. Use get() instead of indexing, checked arithmetic for sizes and offsets, and cap downloads and JSON sizes.

Installing other people's software safely

  • Download only from https://github.com/storytold/<app>/releases (and GitHub's asset CDN). The one exception is a developer's own loopback mirror (ARTCRAFT_LAUNCHER_DOWNLOAD_BASE, cargo xtask local); keep it loopback-only and behind the local-mirror cargo feature, which release builds never enable.
  • Verify each download against the release's SHA256SUMS.txt before running or installing it.
  • Never run an installer with elevated rights silently; tell the user what is about to happen.
  • Never delete a user's files outside what the launcher itself installed.

Look and feel

The UI matches the apps page on getartcraft.com (artcraft-website-next in artcraft-services): hard square corners, 1 px hairlines instead of shadows, Archivo (620, 118 % width) for headings and app names, Inter for body text, upper-case Geist Mono labels and buttons, and the site's colour tokens by name (theme::Tokens). Build new UI from widgets.rs; never hard-code a colour.

Languages

Every user-facing string goes through crates/ui-egui/src/i18n (tl!("…") for literals, fmt for {placeholders}, tn for counts). English in the code is the lookup key; adding a string means adding its row to every catalog marked complete_menus (the *.tsv files in crates/ui-egui/src/i18n/), which the i18n tests check. Translate clean-room from the meaning of the English text, never through another catalog. Product and app names (ArtCraft Launcher, Craft App Suite, PhotoCraft…) stay in English. A CJK language is drawn with the system's own CJK font (crates/ui-egui/src/cjk_font.rs, desktop only; English where there is none): never bundle a CJK font, they are 10 MB and more.

Other rules

  1. Assets need attribution. Every bundled asset gets a row in ATTRIBUTION.md (path, title, author, source, license) in the same change. The ArtCraft name, logo and "A" mark in docs/brand/ (the header logo and the tray icon) are trademarks, not MIT/Apache (docs/brand/LICENSE-brand.txt): use them exactly as supplied, never redraw or recolor them.
  2. Icons are generated. The app icon and the tray icon are the supplied ArtCraft "A" mark from docs/brand/ (a trademark, not MIT/Apache); never edit the renders by hand. Run packaging/icons.sh (needs resvg; runs on any OS) and commit the outputs. assets/app-icons/ is copied verbatim from each app's repo (assets/app-icon/hicolor/128x128/apps/ai.storyteller.<app>.png); refresh it from there, never redraw.
  3. Never break wasm. cargo xtask wasm must pass. File-system, process and OS code is cfg(not(target_arch = "wasm32")).
  4. Tests are the gate. Release parsing and asset picking in core get unit tests with real GitHub release JSON fixtures (small, committed under crates/core/tests/fixtures/).
  5. When fetching anything, use a generic ArtCraft-Launcher/<version> User-Agent and never put a person's name, email or other personal details in requests.

3. Before you finish a task

cargo xtask ci              # fmt --check, clippy -D warnings, test, wasm check

Or piecemeal: cargo test -p <crate>, cargo clippy -p <crate> --all-targets -- -D warnings, cargo xtask wasm. If you touched packaging: shellcheck packaging/*.sh packaging/*/*.sh packaging/linux/AppRun and actionlint.

4. Releasing

The version lives only in [workspace.package] version in the root Cargo.toml (cargo xtask version, cargo xtask version set X.Y.Z[-pre]). Pushing to the release branch runs .github/workflows/release.yml, which builds every platform and creates the draft GitHub Release ArtCraft Launcher v with SHA256SUMS.txt. The canonical recipe is ../craftrules/release/playbook.md; cutting a release is ../craftrules/release/cut-a-release.md.

Release assets (keep these names exact; the launcher's own self-update and the README rely on them):

Platform Files
Windows artcraft-launcher-<ver>-windows-{x64,x86,arm64}.msi, …-windows-{x64,x86,arm64}-portable.zip
macOS artcraft-launcher-<ver>-macos-universal.dmg
Linux artcraft-launcher-<ver>-linux-{x86_64,aarch64}.{AppImage,AppImage.zsync,deb,rpm,tar.gz,flatpak}, artcraft-launcher-<ver>-linux-riscv64.{deb,rpm,tar.gz}
FreeBSD artcraft-launcher-<ver>-freebsd-x86_64.tar.gz
Web artcraft-launcher-web-<ver>.zip

Packaging facts the code relies on:

  • The Windows MSI is per-user (no UAC prompt), by default into %LOCALAPPDATA%\Programs\ArtCraft Launcher. Double-clicked it opens a WixUI setup wizard (Welcome; a folder page with a "Create a desktop shortcut" box; Ready; progress; Finish with "Launch"), whose art package.ps1 draws at build time; self-update runs it with /passive, no wizard. The folder and the shortcut choice are recorded under HKCU\Software\Learning Machines LLC\ArtCraft Launcher (InstallDir, DesktopShortcutChoice) and reused by the next version, and self_update.rs reads InstallDir to know the running copy came from the MSI. Its shortcuts set AppUserModelID ai.storyteller.artcraft-launcher, which toast notifications need. portable.txt beside the exe switches on portable mode (ArtCraftLauncherData\ next to the exe).
  • The macOS bundle is ArtCraft Launcher.app (executable Contents/MacOS/ArtCraft Launcher), a normal Dock app that can also sit in the menu bar: LSUIElement must never be set.
  • Linux: the tray uses StatusNotifierItem over D-Bus and notifications use org.freedesktop.Notifications, both in pure Rust (no GTK, appindicator or libdbus).
  • Inside the Flatpak launcher (process::in_sandbox()), apps normally install as host Flatpaks and run through flatpak-spawn --host. When the host has no usable Flatpak — none installed, or flatpak-spawn not allowed — linux::order_available_kinds offers the release tarball instead: it is unpacked into <data>/apps/<app> (bin/, share/) and run with HOME = <data>/apps/<app>/home, so a Craft App's settings stay in the sandbox. An update swaps bin/ and share/ and keeps home/; removing the app takes those two folders only. Outside a sandbox the tarball is not on offer at all.
  • --socket=pulseaudio in the launcher manifests is for an app running inside the sandbox (SoundCraft's ALSA reaches the runtime's PulseAudio plugin through it); it also covers the microphone, which SoundCraft records through.
  • build.rs reads ARTCRAFT_LAUNCHER_BUILD_SHA / ARTCRAFT_LAUNCHER_BUILD_DATE and, on Windows, embeds assets/app-icon/artcraft-launcher.ico, failing the build when ARTCRAFT_LAUNCHER_REQUIRE_WINRES=1 and it can't.

5. Parallel agents

  • Use your own target dir (CARGO_TARGET_DIR=target/agent-<name>) and edit only the files you own.
  • Keep every Cargo.toml valid at all times: the crates/* and apps/* globs mean one broken manifest breaks everyone's build. Write manifests to a temp file and mv them into place.
  • If someone else's in-progress edit breaks the build, wait and retry; don't fix their files.