Commit Graph
69 Commits
Author SHA1 Message Date
Dylan M. Taylor 9257c78cef Merge pull request #508 from cdhdt/update-dialog-translations
craft-update: the update dialogs take the app's translator
2026-11-02 22:52:25 -05:00
Dylan M. Taylor 0c332d87de Merge pull request #460 from marshiyar/fix/update-package-manager-copies
update: a package manager's copy and another app's bundle do not self-update on macOS and Windows
2026-11-02 21:39:55 -05:00
Dylan M. Taylor 0035af1539 Merge pull request #392 from Tomcatt/fix/standalone-update-whole-tree
fix(update): a standalone self-update replaces the whole tarball install
2026-11-02 21:39:39 -05:00
Dylan M. Taylor e21dfdba5d Merge pull request #481 from VoiceLessQ/fix/flatpak-bundle-reinstall
fix(update): handle a bundle Flatpak before 1.16.2 won't reinstall
2026-11-02 21:34:54 -05:00
Dylan M. Taylor 40751df9ac Merge pull request #465 from Jeremy8776/fix/msi-context-native
craft-update: read an MSI's install context without starting PowerShell
2026-11-02 21:34:50 -05:00
Dylan M. Taylor b49ce8590f Merge pull request #452 from ZIDAN44/fix/391-safe-release-link
update: the prompt opens only a safe release link; document token logging (#391)
2026-11-02 21:33:30 -05:00
Dylan M. Taylor e4108a3172 Merge pull request #513 from ljkx/update/sums-duplicate-names
update: one asset per name in SHA256SUMS.txt (#478)
2026-11-02 21:30:55 -05:00
ljkx e6189423a0 update: test Http::new's HTTPS-only setting, and keep refused links out of messages
An integration test builds the library without cfg(test), so it checks the release configuration: the remote agents refuse plain HTTP (an upper-case scheme too) without connecting, loopback links still work, and with local-mirror the remote agents take plain HTTP.

A refused hop's message names only the host. ureq's message holds the whole link, and a redirect to the file host carries a signed query. host() also drops a query and any user:password@.
2026-11-03 01:13:37 +01:00
ljkx 240a005494 update: look SHA256SUMS.txt names up in a map, not by scanning
A 4 MiB list holds some 60,000 names, and searching the earlier entries
for each one made parsing quadratic: about 8 s for a full-size list in
a release build. A map from name to position and a set of conflicting
names make it linear again, with the same entries in the same order.
2026-11-02 23:56:02 +01:00
ljkx 867c9479b4 update: keep the remote HTTP agents and their redirects on HTTPS (#477)
The ureq agents for remote hosts now set https_only, so a redirect to http:// is refused before connecting instead of being followed in plain text. A download refused this way fails at once rather than retrying until it pauses. Tests and the local-mirror feature keep plain HTTP, and the direct agents still serve loopback links.
2026-11-02 19:38:56 +01:00
ljkx e412b14fa3 update: one asset per name in SHA256SUMS.txt (#478)
A line repeated as is counts once. A name listed with two different
hashes is dropped, with a warning, so the result no longer depends on
line order: the file has no published checksum and its install is
refused, as for any file the release doesn't vouch for.
2026-11-02 18:43:43 +01:00
cdhdt 939ded3e3d craft-update: a translator given after the prompt opened reaches it (review)
Updater::with_translator stored the function for later prompts only; a prompt already open
kept the old one. It now also sets the open prompt's.
2026-11-02 13:13:12 +01:00
cdhdt 76e2bb0eb9 craft-update: the updater's own install failures are translated; an error written to status_message wins over a note (review)
A panicking or dropped install worker produced English text shown as a literal error. The
worker's answer is now a Failure: the installer's words, shown as they came, or the updater's
own, a Note translated when the prompt draws. StartupPrompt::message() prefers status_message,
so an error written straight to the public field isn't hidden by an earlier note.
2026-11-02 12:08:03 +01:00
cdhdt 493bdb9b1c craft-update: the prompt's own messages are translated when it draws; fill keeps unknown placeholders whole (review)
The installed, save-your-work-first, can't-auto-update and couldn't-start messages were
translated when set, so a language change while the prompt showed one left it in the old
language. They are now a Note (English text and values) in StartupPrompt::status_note,
translated each time the prompt draws; errors from elsewhere stay in status_message as they
came. set_note/set_message/clear_message keep only one; message() is what the prompt shows.

fill keeps an unknown placeholder through its closing brace (`{bad {app}` stays), the rule
craft_i18n::fmt follows.
2026-11-02 11:17:00 +01:00
cdhdt a9e5afebf8 craft-update: the update dialogs take the app's translator
The startup prompt, the Check for Updates result card and the messages the Updater puts in the
prompt were English whatever the app's interface language (storytold/wordcraft#5257).
`StartupPrompt`, `CheckResultDialog` and `Updater` get `with_translator(Translate)`: a
`fn(&str, &[(&str, &str)]) -> String` that gets the English text (the key, as in the craft-i18n
catalogs) and its `{name}` values, so translations may reorder them and an app on craft-i18n
passes `craft_i18n::fmt(i18n.t(lang, text), args)`. It is called when the card draws, so a
language change shows on the next frame. Buttons are looked up in title case and drawn in
capitals, so the apps' existing button rows serve. Without a translator nothing changes.
2026-11-02 10:55:49 +01:00
TomcattandClaude Opus 5.5 30b8911dd0 fix(update): refuse a folder in a file's place; per-process staging names
A shipped file whose destination is a folder is now caught before anything is
staged, instead of failing its rename after earlier files were replaced. The
unpack folder and staged .update files carry the process ID, so two updates
running at once can't remove or overwrite each other's files. Adds a test for
each.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019oRr8q5tawLko7mLqEXf8g
2026-11-02 00:44:39 +09:00
TomcattandClaude Opus 5.5 eb2d0c1bc7 fix(update): stage every file before replacing any in a standalone update
A copy that fails (full disk, unwritable folder) now stops the update before
any installed file changes; staged copies are removed. Adds tests for an
unwritable destination and for the modes given to replaced files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019oRr8q5tawLko7mLqEXf8g
2026-11-02 00:23:54 +09:00
Tomcatt 488a234d84 Merge remote-tracking branch 'upstream/main' into fix/standalone-update-whole-tree 2026-11-02 00:23:45 +09:00
VoiceLessQ aaa6742479 fix(update): handle a bundle Flatpak before 1.16.2 won't reinstall
Flatpak before 1.16.2 ignores --reinstall for a bundle and fails with
"<id> already installed" when that commit is deployed.

An app install now uninstalls the user's copy and installs again, without
--delete-data, as the launcher does (storytold/craft-launcher#1014). A failure
after that says what happened and what to do.

A self-update never uninstalls the app it runs from: its download goes right
after, so a failed install would leave nothing. That error there means an
earlier update landed and never restarted, so it goes on to the restart.

Root gets a flatpak command, so tests can record the real calls.
2026-11-01 12:54:41 -02:00
JeremyandClaude Opus 5.5 cb39b5e8ca test: accept any well-formed account SID, not only S-1-5-
A Microsoft Entra ID account's SID starts S-1-12-1-, so the test failed on
those machines although the lookup works. It now checks the SID's shape.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:56:25 +00:00
JeremyandClaude Opus 5.5 c591798d51 fix(update): read an MSI's install context without starting PowerShell
msi_context::probe now reads Windows Installer's own registration keys (the
packed product code under HKLM Classes\Installer\Products, HKCU
Installer\Products and HKLM Installer\Managed\<SID>) instead of a hidden
PowerShell ProductsEx lookup. Same route as storytold/craft-launcher#1082.
msi_context.ps1 is deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 10:26:05 +00:00
aiongg c5a1cbfb5f update: CheckResultDialog shows the result of a manual update check 2026-10-31 04:39:40 +00:00
marshiyar 61bde471fd update: a package manager's copy and another app's bundle do not self-update on macOS and Windows
self_update::detect fell back to Install::Standalone for any executable it
could not place, so on macOS a Homebrew, MacPorts or Nix copy, or a program
inside another app's bundle, was replaced in place, and on Windows so was a
scoop, winget, Chocolatey or Store copy. The package manager's record then
stays on the old version, and the bundle belongs to another app.

macOS now treats executables under /opt/homebrew, /usr/local/Cellar,
/usr/local/Homebrew, /usr/local/opt, /opt/local and /nix as owned by a package
manager, and any executable inside an .app bundle that is not this app's own
as not ours; none of them self-update. Windows does the same for scoop, winget,
Chocolatey and WindowsApps folders. The rules are plain path functions, so
they are tested on every platform.

Tested: three new tests failed before the change (the macOS and Windows folder
rules, and detect on an executable in another app's bundle) and pass after it.
cargo test -p craft-update (209 tests), clippy -D warnings and fmt pass on
macOS; the Windows branch was not compiled here.

Fixes #390 for macOS and Windows; the Linux side is #388.
2026-10-30 20:02:37 -07:00
Dylan M. Taylor 4323a0f90e Merge pull request #406 from solarmaster961/fix/flatpak-system-bundle-self-update
fix(linux): self-update a system-wide Flatpak bundle in place
2026-10-30 22:56:43 -04:00
Dylan M. Taylorandcoderabbitai[bot] 003f133f6d Update crates/update/src/install.rs
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-10-30 22:48:52 -04:00
VoiceLessQ 7ec82661cb fix(update): cap a GitHub rate-limit wait at one hour
An absurd x-ratelimit-reset or retry-after stopped the API until restart.
GitHub's quota refills within the hour.
2026-10-30 22:44:18 -04:00
VoiceLessQ f0b5b22dce fix(update): read GitHub API answers up to a 16 MiB cap
get_api shared the 4 MiB limit for small answers. A launcher version list of
100 releases can pass that: SolidCraft's was 6.5 MB (craft-launcher#1021).
Other small requests keep 4 MiB.
2026-10-30 22:43:46 -04:00
Zinadin ZidanandClaude Opus 5.5 7070670d5f Merge main into fix/391-safe-release-link
Brings in main's launcher version pins (craft-update: apps respect the
launcher's per-app version choice, and the follow-ups through 9817c81).

One conflict, in updater.rs's tests: main made the newer() helper
pub(super) for updater_pin_tests.rs, and this branch added its two #391
tests (download_opens_only_a_safe_release_page,
every_releases_page_is_a_safe_web_link) just above it. Kept both tests
and main's pub(super). Everything else merged cleanly: Download still
goes through download_action (safe_web_link, then the app's releases
page), "What's new" still checks safe_web_link, and main's pin checks
in offer, check_now, finish_check and start_install are untouched. Main
adds no new way for the prompt to open a URL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:09:40 +06:00
JeremyandClaude Opus 5.5 9817c81fd2 craft-update: the version-choice file sits in a host folder Flatpaks can read
A Flatpak launcher wrote versions.json inside its own sandbox, and no Flatpak
app could read the launcher's folder, so pins never reached Flatpak apps, nor
host apps started from their menu entry. The file now sits in its own folder,
ArtCraft/versions under the host's per-user data folder, found from inside a
Flatpak too (HOST_XDG_DATA_HOME, else ~/.local/share). A Flatpak app reads it
with one read-only grant for that folder alone (pins::FLATPAK_GRANT).

folders::host_data_local_dir replaces install's private copy of the same rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 21:55:00 +00:00
Zinadin ZidanandClaude Opus 5.5 35b908febb update: only ureq's Trace logs print the GitHub token (#391)
ureq 3.5's Debug request log lists an allow-list of safe headers and
leaves Authorization out; the unredacted wire is its Trace output
(ureq-proto's log_data). `Http::with_github_token` now says so: apps
should redact Bearer tokens or keep `ureq` and `ureq_proto` at Debug or
quieter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 23:56:36 +06:00
Zinadin ZidanandClaude Opus 5.5 7ae5bbf07d update: Download falls back to the app's releases page (#391)
Download closes the prompt, so a release link that fails
`release::safe_web_link` left the user with nothing opened. It now opens
`App::releases_url()` instead, which passes the check for every app in the
suite; a release link with `&` or another shell metacharacter is still
refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 23:56:36 +06:00
Zinadin ZidanandClaude Opus 5.5 3f21cfa0ae update: the prompt opens only a safe release link; document token logging (#391)
`Release`'s fields are public, so an app that builds one from its own
feed or cache could hand any URL to the prompt's "What's new" link and to
Action::OpenUrl after Download. Both now go through
`release::safe_web_link`, as the launcher does before every open_url.
`Http::with_github_token` says that ureq's debug logs print the Bearer
token, so apps should redact it or keep ureq below Debug.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:22:32 +06:00
JeremyandClaude Opus 5.5 0ada678488 craft-update: every check answer uses the launcher's choice as it stands when the check ends
finish_check now re-reads the choice before looking at the answer, so an app
the launcher pinned mid-check reports UpToDate and an up-to-date managed()
whether the network found a release, nothing, or an error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:52:57 +00:00
JeremyandClaude Opus 5.5 a710ab7897 craft-update: a pin made while a check runs still holds its answer
finish_check used the choice cached when the check started, so a launcher
pin made during the network request still opened the stable prompt. It now
re-reads the choice when the check finishes, as offer does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:42:23 +00:00
JeremyandClaude Opus 5.5 0db8c1ba47 craft-update: a pin closes a stale prompt; tracks() skips stable; isolate the pretend test
Review fixes for #449:
- A prompt opened while the app followed stable stayed open after the
  launcher pinned it, and its Update button still installed the stable
  release. Reading a managed choice now closes a prompt that is not
  installing, and an install re-reads the choice before it starts. A
  running or finished install keeps its prompt, which tick reports through.
- Pins::tracks listed an explicit Stable entry despite its docs.
- The pretend-version child test read the developer's real pins file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:36:42 +00:00
JeremyandClaude Opus 5.5 7d303b4b50 craft-update: apps respect the launcher's per-app version choice
The launcher lets someone keep an app on an older release or on nightlies,
but the app's own update check never knew, so a pinned app prompted to
update on every start and "Update and restart" undid the downgrade.

`pins` is one small file the launcher writes (versions.json in its per-user
data folder, or the absolute path in CRAFT_VERSIONS_FILE) and apps read:
app id to Stable, Nightly or Pinned(tag), the launcher's own serde shape.
A missing or damaged file means everyone follows stable; an entry a newer
launcher wrote is left to the launcher and kept on save.

`Updater` reads it before each check and before an offer. An app kept off
stable gets no prompt and no network call; a requested check reports
UpToDate, and `Updater::managed()` says why, so the public Status enum is
unchanged and no app breaks on a rev bump.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:22:38 +00:00
Dylan M. Taylor 04aa1703b3 update: honor pretend versions in app release checks (#436) 2026-10-30 02:12:36 -04:00
Dylan M. Taylor 1065653e75 craft-update: the startup prompt shows the app's icon, accent colour and developer (#434)
The update prompt drew the app name's first letter in a blue square and used that blue for
its buttons and highlights. App gains accent and developer, and the prompt now draws:

- App::icon_png (decoded once per context, PNG only, at most 1024 px a side and never past
  the renderer's texture limit), keeping the letter badge for an app without a usable icon;
- App::accent filling its buttons, the badge and an UPDATE chip, with white or black text,
  whichever contrasts more, so every Craft app's accent reaches WCAG AA (4.5:1) for these
  small labels; the What's new link is body text with an accent underline (accent-coloured
  text missed 4.5:1 for several apps). No accent keeps the default blue;
- "by <developer>" beside the app name: App::developer, ArtCraft (app::DEVELOPER) for every
  app in the storytold organisation unless the app passes its own;
- its own button states: backgrounds for unfilled buttons (LATER, DISMISS) when hovered and
  pressed, as the host app's style can hover in a colour its dark text doesn't read on, and a
  2 px focus ring in the body text colour on every button (egui shows focus in that state)
  and on the What's new link.

Apps opt in by passing their own App, e.g.
App { icon_png: Some(include_bytes!("…128x128…png")), accent: Some([0x3f, 0x5f, 0xe0]), ..suite::WORDCRAFT }.
2026-10-30 01:25:19 -04:00
aiongg 1b5582a72e Merge pull request #423 from Jeremy8776/feat/self-update-migrate-to-machine
craft-update: an app installed for one account moves to an install for everyone on its update
2026-10-29 11:44:02 -04:00
aiongg 488c1da8fe Merge pull request #425 from Jeremy8776/fix/update-local-build-downgrade
craft-update: a build from source is never offered the release it already contains
2026-10-29 11:35:24 -04:00
JeremyandClaude Opus 5.5 df399edff8 craft-update: a Windows MSI copy updates itself in its own context, wherever it is installed (#419)
* craft-update: a Windows MSI copy updates itself in its own context, wherever it is installed

Self-update only recognised an MSI copy by the launcher's per-user folder (the HKCU InstallDir
key, or %LOCALAPPDATA%\Programs\<App>). Every app installed for everyone, in
Program Files\ArtCraft\<App> or the older Program Files\<App>, was treated as a loose folder and
updated by unpacking the portable zip over Program Files without permission.

- msi_context moves here from the launcher (craft-launcher crates/native/src/msi_context.rs at
  b827fed, unchanged apart from visibility, MsiScope and scope_arguments). Windows Installer's
  own registration decides a copy's context, for the launcher and the apps alike, so the
  launcher can drop its copy and both routes follow one rule.
- New msi_copy module: a copy is an MSI copy when an App Paths entry names its exe (a registry
  read, cheap enough for status checks). Its update asks Windows Installer for the context; the
  App Paths hive is only the fallback when that lookup cannot run.
- The update stops, before the app exits, when the copy is registered in both hives, the lookup
  is unclear, or Windows lists the app more than once.
- After the update the helper starts the exe the registrations name now, since the package may
  have moved an exact old default folder.
- A failed self-update now names the Windows Installer log, as the launcher's installs do.
- The launcher's own per-user package is recognised and updated exactly as before.

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

* craft-update: a copy only takes the Windows Installer context of its own hive

Review follow-up (CodeRabbit on #419). The product code for the context lookup came from the
app's only uninstall record, which was not proven to own the running exe. A package writes App
Paths under HKMU, always the hive of its own context, so a registration whose Windows Installer
context differs from the hive that named this exe belongs to another copy. The update now stops
then, instead of taking the other copy's context.

Also documents the remaining helpers the diff touches.

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

* craft-update: an app in the old per-user folder is judged by its registration first

Review follow-up (CodeRabbit on #419). The folder rule for the launcher's per-user package also
matched an app installed in %LOCALAPPDATA%\Programs\<App>, and it ran before the App Paths
check, so such a copy skipped the product-count, conflict and context checks. Now an app is
judged by its registration first; the folder rule only covers an app copy no registration names.

The launcher in its own folder still keeps its old path whatever is registered: on a real PC it
has an App Paths entry and a stale second uninstall record, and registration-first would have
stopped its self-update.

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

* craft-update: when the Windows Installer lookup cannot run, the uninstall records decide

Review follow-up (CodeRabbit on #419). The fallback trusted the App Paths hive, so a stale entry
or a product registered in both scopes could run the update in the wrong context. The fallback
now follows the launcher's rule: the context every uninstall record of the product agrees on
(msi_context::single_scope over HKLM in both views and HKCU). Missing or mixed records stop the
update, and an agreed context that differs from the App Paths hive still stops it as another
copy's registration.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 11:26:20 -04:00
JeremyandClaude Opus 5.5 25c2e9ffc5 craft-update: resolve the whole default folders, not just LOCALAPPDATA
Review follow-up (CodeRabbit on #423). If LOCALAPPDATA\Programs or Programs\ArtCraft is itself a
junction, the exe's folder resolves through it but a default path built from a resolved
LOCALAPPDATA does not, so a default install was planned as a chosen folder. The Windows layer now
resolves each complete default folder (new default_folders) and hands them to the planner, which
only compares.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:50:36 +00:00
JeremyandClaude Opus 5.5 eff2500447 craft-update: migration review fixes (profile links, recovery message, Linux test)
Review follow-up (CodeRabbit and CI on #423):
- The profile roots and LOCALAPPDATA are canonicalised like the exe's folder, so a profile behind
  a junction or a subst drive still matches its default folder and still counts as the profile.
- When a failed move leaves no program to start, the helper starts nothing, so the recorded
  "run its installer" message is not replaced by a restart error.
- The default-folder test builds its expected path the way the plan does, so it also passes
  where Path::join uses '/' (Linux CI).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:33:30 +00:00
JeremyandClaude Opus 5.5 7fd60c5870 craft-update: a build from source is never offered the release it already contains
Reported by FredApps: PhotoCraft built from main as 0.17.0-integration.20261029.f5b3f123 offered
"Update and Restart" to 0.17.0, a downgrade. Semver puts a pre-release before its release, and
the update check and the self-update guard compared with plain semver.

New version::offers_update: a pre-release label the suite publishes (alpha, beta, rc, nightly)
sorts as before; any other label marks a build from source, which is only offered a strictly
higher X.Y.Z. check_for_update (through a new testable offered_release) and the self-update
guard both use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:29:47 +00:00
JeremyandClaude Opus 5.5 2e2e8f8daa craft-update: an app installed for one account moves to an install for everyone on its update
Temporary (craft-libs#422, step 4): new msi_migrate module, to be removed two releases after the
app packages become per-machine only.

The released packages refuse a per-machine install while a per-user copy exists, so the move
removes first and installs second: remove the per-user product (no permission prompt; if that
fails nothing has changed), install the new version for everyone, and if that fails or
permission is refused, install the new version for the account again in its old folder.
Settings and documents live outside the program folder and are never touched.

Which copies move: a per-user default folder goes to Program Files\ArtCraft\<App>; a chosen
folder outside the profile moves in place; a chosen folder inside the profile keeps updating
for the account. The launcher's own package never moves.

Tested with real Windows Installer runs of a renamed dual-scope package carrying the apps' own
install-policy.wxi: the move with the permission prompt accepted (twice), and with it refused
(the app came back for the account at the new version, the failed log kept). Also unit tests for
every plan and the order of steps, and a mutation check (9 of 9 killed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:14:18 +00:00
JeremyandClaude Opus 5.5 cd7cc8eb39 craft-update: when the Windows Installer lookup cannot run, the uninstall records decide
Review follow-up (CodeRabbit on #419). The fallback trusted the App Paths hive, so a stale entry
or a product registered in both scopes could run the update in the wrong context. The fallback
now follows the launcher's rule: the context every uninstall record of the product agrees on
(msi_context::single_scope over HKLM in both views and HKCU). Missing or mixed records stop the
update, and an agreed context that differs from the App Paths hive still stops it as another
copy's registration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:51:39 +00:00
JeremyandClaude Opus 5.5 1e01964871 craft-update: an app in the old per-user folder is judged by its registration first
Review follow-up (CodeRabbit on #419). The folder rule for the launcher's per-user package also
matched an app installed in %LOCALAPPDATA%\Programs\<App>, and it ran before the App Paths
check, so such a copy skipped the product-count, conflict and context checks. Now an app is
judged by its registration first; the folder rule only covers an app copy no registration names.

The launcher in its own folder still keeps its old path whatever is registered: on a real PC it
has an App Paths entry and a stale second uninstall record, and registration-first would have
stopped its self-update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:36:08 +00:00
JeremyandClaude Opus 5.5 7898779169 craft-update: a copy only takes the Windows Installer context of its own hive
Review follow-up (CodeRabbit on #419). The product code for the context lookup came from the
app's only uninstall record, which was not proven to own the running exe. A package writes App
Paths under HKMU, always the hive of its own context, so a registration whose Windows Installer
context differs from the hive that named this exe belongs to another copy. The update now stops
then, instead of taking the other copy's context.

Also documents the remaining helpers the diff touches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:21:27 +00:00
JeremyandClaude Opus 5.5 9ce610bf16 craft-update: a Windows MSI copy updates itself in its own context, wherever it is installed
Self-update only recognised an MSI copy by the launcher's per-user folder (the HKCU InstallDir
key, or %LOCALAPPDATA%\Programs\<App>). Every app installed for everyone, in
Program Files\ArtCraft\<App> or the older Program Files\<App>, was treated as a loose folder and
updated by unpacking the portable zip over Program Files without permission.

- msi_context moves here from the launcher (craft-launcher crates/native/src/msi_context.rs at
  b827fed, unchanged apart from visibility, MsiScope and scope_arguments). Windows Installer's
  own registration decides a copy's context, for the launcher and the apps alike, so the
  launcher can drop its copy and both routes follow one rule.
- New msi_copy module: a copy is an MSI copy when an App Paths entry names its exe (a registry
  read, cheap enough for status checks). Its update asks Windows Installer for the context; the
  App Paths hive is only the fallback when that lookup cannot run.
- The update stops, before the app exits, when the copy is registered in both hives, the lookup
  is unclear, or Windows lists the app more than once.
- After the update the helper starts the exe the registrations name now, since the package may
  have moved an exact old default folder.
- A failed self-update now names the Windows Installer log, as the launcher's installs do.
- The launcher's own per-user package is recognised and updated exactly as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:08:01 +00:00
solarmaster961andCursor bd9a4d7b16 fix(linux): self-update a system-wide Flatpak bundle in place
A system install was offered only as a download. Reinstalling that bundle
through Flatpak's install-bundle action is denied without a password dialog
when polkit cannot fill in $(path). Use pkexec on the host instead, and keep
per-user bundles on flatpak install --user.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-28 09:11:52 +01:00