263 Commits

Author SHA1 Message Date
Dylan M. Taylor 9a9a1004b4 Add a setting to hide community apps (#1136) 2026-11-01 12:28:36 -05:00
Dylan M. Taylor c3b6a8dfc2 Add TextureCraft to the launcher suite (#1135)
* Add TextureCraft to the launcher suite

* Fix formatting of TextureCraft release fixture test

* Distinguish TextureCraft details headline from its card
2026-11-01 12:22:34 -05:00
VoiceLessQ 7eeb66d01f fix(i18n): fill placeholders in one pass (#782) (#1063)
A value holding {other} was expanded again by a later argument. fmt now
walks the template once, so values stay exactly as they came.
2026-11-01 10:54:15 +01:00
VoiceLessQ 82b60070f6 fix(detect): sort MSI entries without a version last (#936) (#1049)
* fix(detect): sort MSI entries without a version last (#936)

msi_installs promised newest first but had the no-version arms swapped,
so an entry without a DisplayVersion came first and .next() took it as
the primary install.

* test(detect): check every arm of newest_first (#936)

The sort in this test never compares None against Some in that order, so flipping that arm still passed.
2026-11-01 10:53:57 +01:00
Dylan M. Taylor c0fd8b06e4 feat: add Craft Anything to the launcher suite (#1113) 2026-10-31 23:32:54 -04:00
Dylan M. Taylor f3ac5c265d Revert "feat(install): one permission prompt for an Update all of all-users apps on Windows (#974)" (#1077)
This reverts commit b827fed74a.
2026-10-31 01:31:34 -04:00
Dylan M. Taylor 21615e80fc Merge branch 'main' into fix/main-ci-tests 2026-10-30 23:22:00 -04:00
Dylan M. Taylor dce0a77083 Merge pull request #924 from SusieeTheLinuxUser/fix/881-flatpak-user-over-system
Detection: a user Flatpak install is read before a system one (#881)
2026-10-30 23:17:43 -04:00
Dylan M. Taylor 367c35981b test(ui): keep app detection cadence separate 2026-10-30 23:09:52 -04:00
SusieeTheFemboy 677b9047ed Detection: a user Flatpak wins over a system one under any app id
packages::find tried each bundle id across every record, so a system
io.tangencylabs.solidcraft beat a user ai.storyteller.solidcraft. Packages now say
whether they're a user install; find looks through the user ones first, current id
first within each scope.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 04:04:02 +01:00
SusieeTheFemboy 55842dd91a Merge remote-tracking branch 'origin/main' into fix/881-flatpak-user-over-system 2026-10-31 04:00:55 +01:00
VoiceLessQ 1f5aed9f66 test(native): a shared call only counts on Linux and Windows
failed_in_shared_call is false on other systems, but the test from #974 expected true everywhere, so the FreeBSD x86_64 job has failed since.
2026-10-31 00:35:26 -02:00
VoiceLessQ c5586148e3 test(ui): the installed-apps rescan still waits five minutes
#1071 moved release checks to twelve minutes and changed this test to match, but detection runs on RESCAN_EVERY in app.rs, a separate timer that stayed at five. The test has failed on main since.
2026-10-31 00:35:26 -02:00
VoiceLessQ ae5ac6aef7 fix(native): cap a GitHub rate-limit wait at one hour (#782)
An absurd x-ratelimit-reset or retry-after stopped the API until restart
and went to the footer as it came. GitHub's quota refills within the hour.
2026-10-30 22:14:18 -04:00
Dylan M. Taylor 7da8ef2dec fix(ui): reduce automatic release polling 2026-10-30 22:13:36 -04:00
VoiceLessQ 02a99f01a1 fix(ui): find the host's CJK fonts inside the Flatpak sandbox (#620)
Flatpak mounts the host's /usr/share/fonts at /run/host/fonts and
/usr/local/share/fonts at /run/host/local-fonts (add_font_path_args in
flatpak's common/flatpak-run.c). The sandbox's own /usr/share/fonts holds
only the runtime's fonts, so the Flatpak launcher found no Chinese or
Japanese font and fell back to English.

Each system candidate is now followed by its /run/host path, for the same
family. User fonts need no twin, since both manifests grant
--filesystem=home. PhotoCraft fixed the same lookup in
storytold/photocraft#1204.
2026-10-30 22:02:02 -04:00
Jeremy 73a824c4dc feat(versions): go back to an older version of a launcher-installed AppImage
An AppImage the launcher installed is renamed into place whole, and the old file is removed
only after that, the same as an update. Going back to a version chosen by name now takes
that route too (Downgrade::KeepOldCopy), with no older package fetched first. It needs the
launcher's own record of an AppImage: a user's own AppImage, an unrecorded one and the
tarball install (whose bin and share swap one at a time) are still refused.

Not run on a real Linux desktop; the linux module only compiles there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:01:23 -04:00
VoiceLessQ d95ae4b5be fix(install): name the code in the pkexec 127 error
127 also covers other pkexec errors and a wrapped installer exiting 127, so
say which code came back and list the likely causes instead of naming one.
2026-10-30 21:58:07 -04:00
VoiceLessQ cbc6fe60d9 fix(install): stop reading pkexec exit 127 as a cancel (#737)
pkexec exits 126 when the prompt is dismissed. 127 is its default code and
also covers not authorized and no authentication agent, so a desktop with no
polkit agent ended an install as a quiet "Install cancelled" with no prompt.
2026-10-30 21:58:07 -04:00
VoiceLessQ 361e3e82f4 fix(http): read long release lists up to a 16 MiB cap (#1021)
get_api shared the 4 MiB limit for small answers, and SolidCraft's list of 67
releases is 5.4 to 6.5 MB. The request failed every time, so Choose version
showed an error and no versions. Other small requests keep 4 MiB.
2026-10-30 21:57:43 -04:00
VoiceLessQ e1ada85d02 test(ui): compare the table date with its whole width
The check for more than 110 px passed at any column from 111 to 129 px, where
the date is still cut. Lay out the same text and require its full width.
2026-10-30 21:55:09 -04:00
VoiceLessQ c911793006 fix(ui): widen the table's released column so dates show whole (#1038, #754)
Dates ran to 130 px in Brazilian Portuguese and 108 px in Chinese and
Japanese, past the 104 px column, and were cut with an ellipsis. The
column is now 132 px; Author gives up 16 px ("Tangency Labs" still fits)
and the app column 12 px.
2026-10-30 21:55:09 -04:00
SusieeTheFemboy 4cea3dea9f Merge remote-tracking branch 'origin/main' into fix/881-flatpak-user-over-system
# Conflicts:
#	crates/native/src/detect.rs
2026-10-31 02:34:54 +01:00
Dylan M. Taylor effb212ae5 Merge pull request #1053 from Jeremy8776/feat/version-pins
feat(versions): apps respect the version the launcher keeps them on
2026-10-30 20:55:24 -04:00
skylar fc6708641f fix(macos): reject missing teams outside test builds 2026-10-30 17:19:24 -06:00
skylar 2f5a86aa8d fix(macos): verify staged app trust before replacement 2026-10-30 16:49:35 -06:00
Jeremy 2738185596 feat(versions): the shared version folder exists before any app starts
Flatpak mounts an app's read-only grant for ArtCraft/versions only when the
folder exists as the app starts. The launcher now makes it on start, and its
Flatpak grants xdg-data/ArtCraft/versions with :create, which also covers an
XDG_DATA_HOME outside home, where --filesystem=home does not reach.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:08:38 +00:00
Martin Ball 627598eb7d fix(macos): install SolidCraft, checked against its own developer's signature
SolidCraft's macOS build has been Developer ID signed and notarized since 0.27 (its README),
but the launcher still treated it as unsigned (`installs_on_mac: false`), so on a Mac it
offered the update and then said no macOS version was available.

`installs_on_mac` becomes `mac_team`: the Apple team whose signature the build must carry.
Official apps keep ArtCraft's (DJ6XS33FX8); SolidCraft gets its own (G5QA5U5X2S, read from
the signature on its 0.27.1 macOS CLI). `None` still means the launcher installs no DMG, and
an install that reaches the DMG path without a team is now refused, not left unchecked.

Not run on a real Mac: the macos module only compiles there, and the DMG's own signature
still needs `codesign -dv` on a Mac to confirm the team matches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 15:37:08 +00:00
Jeremy 1167239dfe feat(versions): every app the launcher opens is told where the version choices are
Only tarball apps on Linux were given CRAFT_VERSIONS_FILE. Now every launch
route passes it: Windows (exe and MSI) and AppImages through the process
environment, Flatpak apps through `flatpak run --env=`, and anything the
Flatpak launcher starts on the host through `flatpak-spawn --env=`. macOS
apps open through Launch Services with the real home, so they find the file
at its default path; `open` is left unchanged since not every macOS version
takes an environment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:00:55 +00:00
Jeremy 54bbc2ab9e feat(versions): tell the apps which version the launcher keeps them on
A version chosen in the launcher lived only in its own settings, so an app's
own update check (craft-update's Updater) still offered the latest release on
every start, and accepting it undid the downgrade.

The backend now writes craft_update::pins' shared file on every Configure:
each app's track from the settings, stable ones removed, entries a newer
launcher wrote kept. The first Configure after an upgrade moves choices saved
earlier. A tarball app on Linux, whose data folder points into its own home,
is told the real file through CRAFT_VERSIONS_FILE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:00:54 +00:00
Dylan M. Taylor 3fad1d643f deps: bump tar, zip, roxmltree, rustls-webpki and symphonia to their latest versions (#1036)
- `rustls-webpki`: 0.103.15 → 0.103.16
- `zip`: 8.6.0, 9.1.0 → 9.1.3

Requirement `zip` 8 → 9. The portable-ZIP check now treats an entry name that doesn't decode as unsafe, like one with `\\` or `:`. That code is Windows-only and wasn't compiled here (Linux); Windows CI is the check.
2026-10-29 22:21:27 -04:00
Jeremy e60a042b48 feat(versions): go back to an older version of a macOS app, keeping the old copy aside
A Mac update already moves the installed .app aside, renames the new bundle in and moves
the old one back if that fails. Going back to a version chosen by name now uses the same
swap for a DMG over a Mac app install, so no older package has to be found and fetched
first. Downgrade becomes an enum: Reinstall (the Windows Installer route, unchanged) and
KeepOldCopy (macOS). Each is let through only for the install it can undo; anything else
is still refused before anything is downloaded.

Not run on a real Mac: the macos module only compiles there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 20:28:23 +00:00
Jeremy e77c72aff5 fix(versions): no separate-prompt fallback for an all-users downgrade; one queue turn per downgrade
Two review findings, both ways for the 'app left uninstalled' trap to come back:
- If the one-prompt elevated session could not start, an all-users downgrade fell back to a
  prompt per step. It now stops before anything is removed and says nothing was changed.
- The step-by-step route (per-user copies) took a separate installer-queue turn per step, so
  another queued install, or a cancel while waiting, could stop the put-back after the copy
  was removed. Remove, install and put-back now run in one turn, with nothing to wait on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 18:25:48 +00:00
Jeremy a7491e9d15 fix(versions): put the previous version back when removing a second registration fails
An app can be registered more than once (per-user and all-users). If removing one succeeded
and the next failed, both downgrade routes stopped there, said nothing had changed, and never
put the previous version back. Now a removal that fails after another succeeded reinstalls
the previous version, in the elevated script and the step-by-step route, and says so with
outcomes of its own. A first removal that fails still changes nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 18:13:10 +00:00
Jeremy 45e2b6ec7e feat(versions): a quiet note when a kept version is behind; colour only the new version in an update
A version kept on purpose shows 'v0.16.0 · latest 0.17.0' in its card strip and the table,
with the note in plain grey: it informs without counting as an update (no badge, Update
all or notification). The published list for each kept app is fetched once in the
background, through the same request and cache as the version menu.

When an update is waiting, only the new version takes the app's colour ('0.16.0 →' stays
plain). The table cell stays one label in two colours, so it still reads as one piece.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:58:46 +00:00
Jeremy 46cffe6527 fix(versions): one permission prompt for a whole Windows downgrade
A real run showed three separate prompts are fragile: they look identical, so a refused
install was followed by a refused put-back (read as a retry), leaving FilmCraft uninstalled.

For an all-users copy, removal, install and put-back now run in one elevated session, as
Update all's batch does, with both checked packages held unchanged meanwhile. Saying no
changes nothing; once it is yes, the put-back needs no second answer. The session's exit
code says which step stopped it. Product codes must have the exact {GUID} shape to reach
it. Per-user copies need no permission and keep the step-by-step route, which is also the
fallback when the elevated session cannot start.

The generated script is tested for real with cmd.exe standing in for msiexec. Those tests
take a lock: in parallel the stand-in sometimes never started (out-of-range code, no
marker), which the script rightly treated as a failed step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:58:46 +00:00
Jeremy ab9aad6ca7 feat(versions): go back to an older Windows Installer version, putting the newer one back on failure
The app packages refuse to install over a newer copy, and the launcher's own guard refused
every downgrade. A version chosen by name below the installed one now:
1. finds the installed version's published release and downloads and checks both packages
   before anything changes (without them, nothing is removed);
2. removes the installed copy;
3. installs the chosen version in the same folder and context, and reinstalls the previous
   version if that fails or permission is refused.

A feed still can't send an app back. Other kinds of install refuse a downgrade plainly.
ReleaseSource::chosen's default now answers pins and nightly from versions(), so a pinned
app's check reports its pinned release and a newer one is never offered as an update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:58:46 +00:00
Jeremy 5840c5e367 feat(versions): a version chosen in the dialog is a draft until Install
Closing the Choose version dialog without installing (Close, Escape or a click outside)
puts back the choice it had when it opened and checks again, so the card returns to Open.
Install from the dialog keeps the new choice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:58:45 +00:00
Jeremy 5803c0f304 test(versions): closing the dialog keeps the chosen version on the card
Lifts the version tests' fake backend into a helper (version_menu) and covers picking an
older release, pressing Close, and finding Install version on the card.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:58:45 +00:00
Jeremy 270a6cac26 feat(versions): label the newest release in place instead of a separate Latest release row
The newest stable release that installs here reads 'v0.14.0 (latest release)', and choosing
it follows new releases as the old row did. The plain 'Latest release' row only shows while
the list is loading or unavailable, so that choice is never out of reach.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:58:45 +00:00
Jeremy 36818cc387 style(versions): version rows touch so the selected highlight fills its slot
With a gap between rows, only the selected row drew a box, covering 42 of its 52 px slot,
so the space around it looked uneven. Rows keep their 42 px height.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:58:45 +00:00
Jeremy c9c0b5c35a style(versions): drop the divider between Nightly and the releases
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:58:45 +00:00
Jeremy e78145cb7c fix(versions): show a missing nightly inline as 'Nightly (none published yet)'
The disabled Nightly row carries the reason in brackets instead of a separate grey line
under it, so the menu no longer opens with a large block before the releases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:58:44 +00:00
Jeremy 81d6aec2dd feat(versions): install the chosen version from the Choose version dialog
Choosing a version keeps the dialog open. An Install version {tag} button sits at its
bottom right, enabled once that exact release has been checked (Checking... while a
nightly is looked up, Installed when it is the installed copy). Install from the dialog
may open the folder dialog in its place, which dialogs() now keeps instead of
restoring the old one. The help line keeps only the files warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:58:44 +00:00
Jeremy 41ca7ef5bb feat(versions): choose an app or launcher version: latest, nightly or an exact release
Each app's menu gets "Choose version", and Settings > Updates gets a "Launcher version"
dropdown. Choosing a version closes the dialog, re-checks that exact release, and turns the
card's main action into "Install version {tag}" until that copy is installed.

- Nightly means an explicit -nightly prerelease tag with a published checksum, never any beta.
- A pinned tag never falls back to another release, and an install re-checks the track, so a
  stale stable result cannot install as nightly.
- A release asset must sit under its own repository, tag and file name.
- Older launcher versions are a manual download beside the pin; self-update only moves forward.
- The version list is the newest 100 releases from GitHub (one page), cached for 15 minutes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:58:43 +00:00
Dylan M. Taylor 032a1224d3 Show PdfCraft in red (#975)
PdfCraft's app colour changed from green to red (storytold/pdfcraft#5245). The catalog
colours become #e8204f (app), #b70228 (light ink) and #f23a62 (dark ink), matching the
website, and assets/app-icons/pdfcraft.png is the new 128 px icon, copied unmodified.
The status-green comment no longer names PdfCraft as its source; the value is unchanged.
2026-10-29 12:13:44 -04:00
Dylan M. Taylor 9fabc13be2 Focus app descriptions on features and workflows (#1000) 2026-10-29 12:10:04 -04:00
Jeremy b827fed74a feat(install): one permission prompt for an Update all of all-users apps on Windows (#974)
* feat(install): one permission prompt for an Update all of all-users apps

Updates of apps installed for all users wait until each has downloaded and
checked its package, then run in one elevated PowerShell session: one UAC
prompt for the batch. A batch of one keeps the direct msiexec route. If
PowerShell cannot start, each package runs with its own prompt.

* fix(review): keep each checked package unchangeable until its batch runs; fall back when the batch is too long

The batch's permission prompt names PowerShell, not the package's publisher, so a
package swapped in the download folder between its check and the elevated
msiexec would go unnoticed (CWE-367). Each all-users package is now opened
sharing only reading, checked again through that handle against the checksum it
was downloaded against, and held until its call ends.

A batch whose script is too long for one command line now updates each app with
its own prompt instead of failing every job.

* fix(review): a shared-run failure says "in one go", not "together"

The fallback runs each package by itself but still gives the run one result, so
"handling 3 apps together" was wrong there. The marker the backend uses to check
what landed is unchanged in role; only its wording.

* fix(review): a failed launch of the elevated session falls back instead of cancelling

The outer script read every Start-Process failure as a declined prompt, so a
session that could not start cancelled the whole batch. A declined prompt
carries the Windows error 1223 in its exception chain; only that is a cancel.
Anything else exits with a code no msiexec run returns, and the launcher then
updates each app with its own prompt.
2026-10-29 00:05:28 -04:00
Jeremy 67aefb33c0 Install new Windows apps for everyone in Program Files\ArtCraft, and show that folder as the default (#972)
* fix(installer): install new Windows apps for everyone, in Program Files\ArtCraft\<App>

A fresh MSI install now asks Windows Installer for the machine-wide context
(ALLUSERS=1) instead of taking the package's own default, which for a
dual-scope package is per-user (%LOCALAPPDATA%). Updates are unchanged: they
keep the context of the copy they replace. README row restored to Program Files.

* fix(ui): show the real default install folder, not just "System default"

The install-folder fields (the per-app Install dialog and Settings'
Default install folder) now show the folder a fresh install really
uses: Program Files\ArtCraft for a Windows MSI (machine-wide, from
the ALLUSERS=1 scope), the launcher's own apps folder for a portable
ZIP, /Applications on macOS. The host answers through the new
Host::default_install_folder, computed by native install::default_folder;
where no single default exists (web build, Linux system packages,
FreeBSD) the field keeps saying "System default". The same value is
the start folder of Browse, so it no longer starts at a literal
"System default" string.

* fix(review): the default folder follows the package and the installer

CodeRabbit review of #972: the Windows default now follows the selected
package's architecture (an x86 MSI picked because a release ships no
x64 one installs under Program Files (x86)), and answers nothing when
the architecture is unknown, so the field says "System default"
rather than guessing. The macOS default comes from the installer's own
applications_dir, so a machine where /Applications is not writable
shows ~/Applications. Browse now starts at the nearest folder that
exists, so a default created only by the first install
(Program Files\ArtCraft) no longer falls back to the picker's
unrelated start location. The desktop host memoises its answer because
the macOS path probes the disk and the fields redraw often.

* fix(review): keep the Linux and macOS builds of default_folder warning-free

The ubuntu Clippy gate caught what the dev machine cannot check (ring
needs a Linux C compiler): the Arch import is Windows-only, the macOS
arm consumes its unused parameters, and the Linux test calls the
three-argument signature.

* fix(ci): import Arch on every target, default_folder's signature uses it everywhere

0f5bd68 gated the import to Windows but the signature names Arch on all
targets, so the Linux clippy job failed with E0425.

* fix(review): the folder hint matches the real install and touches nothing

CodeRabbit review of #972.

- The install dialog's default follows the package the backend will really
  install: the preferred formats first, then every other format this
  platform installs, as choose_kinds does. A Portable ZIP preference with a
  release that ships only an MSI now shows Program Files, not the apps
  folder. The choice lives in the new install_target module.
- Settings covers every app, so it shows a folder only when every app that
  can be installed shares one; a 32-bit-only app beside a 64-bit one leaves
  it at "System default" instead of using the launcher's architecture.
- The macOS hint no longer borrows the installer's probe, which wrote a
  fixed-name file in /Applications (following a planted link) and created
  ~/Applications when it fell back. Both now use write_probe (fresh name,
  create_new), and only the installer creates ~/Applications.
- The desktop host caches each question, since Settings now asks per app.
2026-10-28 21:50:54 -04:00
Dylan M. Taylor 46761082e6 feat: add SolidCraft as a community app (Official / Community badges) (#963)
* feat: add SolidCraft as a community app, with Official and Community badges

Add AppKind (Official | Community) to the catalog and a bundle id override, since
SolidCraft's Flatpak and desktop id is io.tangencylabs.solidcraft. The grid card shows
the kind as a badge, and the table has a Type column. Strings are translated in the
seven complete catalogs.

Community apps are not under storytold/. AGENTS.md reserves that decision for a
maintainer; this PR needs that approval before it merges.

* details: add the SolidCraft page and fix its attribution licence

The page has no screenshots yet: the pictures repository has none for SolidCraft.
ATTRIBUTION.md now records the MIT OR Apache-2.0 licence that SolidCraft's README gives.

* fix: pass fmt, clippy, tests and wasm for the SolidCraft change

- Show each app's publisher as a lowercase byline ("by ArtCraft", "by Tangency Labs")
  in place of the Official and Community words, with a publisher field on CraftApp.
- Translate the byline, the By column and the SolidCraft tagline in every complete catalog.
- Format the catalog and details tables with rustfmt, and fix two needless borrows.

* fix: address review comments; Author column and shorter SolidCraft tagline

- Detect SolidCraft's MSI by its own publisher ("SolidCraft contributors"), recorded per
  app as CraftApp::msi_publisher.
- Look up missing Flatpak versions for every catalog bundle id, not only ai.storyteller.*.
- Keep SolidCraft's MIT and Apache-2.0 license texts next to its icon.
- Table: an Author column (plain text) after Released, before the buttons.
- SolidCraft's tagline is "Parametric 3D CAD": the card's byline names Tangency Labs.

* detect: find SolidCraft's Flatpak under its older ids

SolidCraft's Flatpak shipped as ai.storyteller.solvecraft, then ai.storyteller.solidcraft,
before io.tangencylabs.solidcraft. Add CraftApp::former_bundles and bundle_ids() (current
first), match them in the Flatpak list and the version lookup, and open and remove a
Flatpak under the id it was found under.

* fix: address CodeRabbit review (removal, Flatpak migration, macOS, website)

- Remove a deb/rpm/pacman install by the package name detection found, not the app id.
- After a Flatpak update from one of an app's older ids, uninstall the old copy (keeping
  its ~/.var/app data, which SolidCraft's first launch copies its settings from), so it
  doesn't reappear once the new one is removed.
- macOS: don't install SolidCraft from its macOS build, which carries no Developer ID
  signature to check (CraftApp::installs_on_mac); the Developer ID check for official apps
  is unchanged. A copy that's there is still detected and opened, under its current or a
  former bundle id.
- SolidCraft's Website link goes to solidcraft.io (CraftApp::site).

* docs(AGENTS): allow tangency-labs/solidcraft as an approved community app

Downloads come from storytold/<app>/releases or, for an approved community app, its own repo:
today only tangency-labs/solidcraft, approved by the project owner on 2026-10-28. Its repo is
pinned in the catalog; the release feed can't move an app out of storytold/. Community apps
get the same SHA256SUMS check, and macOS installs still need a verifiable Developer ID. The
rule that agents never land or approve another third-party app is unchanged.
2026-10-28 18:51:51 -04:00