Commit Graph
32 Commits
Author SHA1 Message Date
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
skylar taylor-barrick 9ff719499d Cover ZIP drive-prefix rejection and standalone update helper ownership (#335)
* test(update): cover ZIP drive prefixes and standalone helper ownership

* test(update): synchronize standalone launcher replacement regression

* test(update): assert standalone updates select portable data paths

* Supply pinned fonts throughout instrumented coverage

* Make development-build regression independent of test runner location

* Pin coverage workflow with immutable nested actions

* Use reviewed optional-font coverage setup

* ci: restore craft-common-actions tracking main
2026-10-25 23:41:12 -04:00
Dylan M. Taylor e3bcd5657f update(install): retry a busy disk-image attach for about a minute (#349) 2026-10-25 19:56:47 -04:00
Dylan M. Taylor 47edfe3255 fix(update): use egui::Role for the close button's accessibility info 2026-10-25 16:31:38 -04:00
Dylan M. Taylor 4f6fe25d77 fix(update): draw the prompt's close mark and version pill without glyphs a font may lack
The close button was a ✕ (U+2715) text glyph and the version pill used →, neither of which the bundled faces are guaranteed to have; apps that panic on a missing glyph took the whole prompt down. The close mark is two painted strokes, the pill reads 'v0.1.0 to 0.2.0', and a test asserts the prompt paints only ASCII text.

Fixes #343
2026-10-25 16:31:12 -04:00
Dylan M. Taylor 05c9f70e6e fix(update): keep the close request pending while the app holds unsaved work
A user can dismiss the prompt while the update installs and then edit. The close request is now kept until the app can close, so a close the user cancels is asked again once the work is saved. The installed message is set when the install finishes.
2026-10-25 14:05:40 -04:00
Dylan M. Taylor 12f199b2b5 feat(update): Updater::show_if holds the install back while the app has unsaved work
The restart helper is scheduled before the app closes, so a close the user then cancels would leave it waiting for an exit that never comes. Apps pass whether it is safe to start.
2026-10-25 13:55:49 -04:00
Dylan M. Taylor f3540a1327 fix(update): check the executable's name before the app quits for an update
windows_precheck now refuses an executable that isn't the app's own, as the helper does, so the error is shown while the app can still show it. check_helper keeps the same check as the final guard.
2026-10-25 13:18:35 -04:00
Dylan M. Taylor 51da82901c test(update): a standalone executable updates from the portable zip
Detection now treats any executable as a standalone install, so the helper's test no longer expects a refusal once portable.txt is gone.
2026-10-25 13:12:18 -04:00
Dylan M. Taylor 8b7e8a13de fix(update): the update helper only restarts the app's own executable
check_helper accepted any existing file as a standalone install, so a differently named executable passed (the Windows test caught it). The program's name must be one of the app's own.
2026-10-25 13:05:31 -04:00
Dylan M. Taylor dbc86fdbf3 fix(update): zip 9's entry name is fallible
Fixes the Windows build after the move to zip 9: an entry whose name can't be read counts as unsafe.
2026-10-25 12:59:00 -04:00
Dylan M. Taylor 9eba2520da fix(update): clean clippy on macOS and Windows
The macOS module imported staging_dir without using it, and the Windows helper never used its app argument.
2026-10-25 12:55:32 -04:00
Dylan M. Taylor 6ff2a458b4 Merge remote-tracking branch 'storytold/main' into migration/egui-037-20261024 2026-10-25 12:54:10 -04:00
Dylan M. Taylor 13ceaf2f31 fix(update): keep Windows known-folder locations and never start a second install
Local AppData comes from the known-folder API (a redirected profile is honoured), with the environment variable as the fallback. Once an update is installed and waits for the app to exit, no check, offer or install starts, so a cancelled close cannot relaunch the app twice.
2026-10-25 12:41:38 -04:00
Dylan M. Taylor 9d2bea410e feat(update): Updater::offer, and dismissing a running check keeps it quiet 2026-10-25 12:18:20 -04:00
Dylan M. Taylor c2adf14ae3 feat(update): an Updater that owns the check, prompt and self-update flow for egui apps
One tested implementation of what each app had copied: results are picked up even after the prompt is dismissed, a dropped or panicking worker is an error, the UI is repainted while work runs, a failed install returns to the prompt with its reason, and success asks the app to close through its own close path instead of exiting the process.
2026-10-25 12:16:42 -04:00
skylar taylor-barrick e5da4a7a68 M0: reconcile shared UI and updater changes with egui 0.37 2026-10-25 17:07:55 +01:00
Dylan M. Taylor fcd3441993 fix(update): drop the dirs crate and move to zip 9
dirs pulls in option-ext (MPL-2.0), which the apps' cargo-deny licence policy rejects, and zip 8.6 carries RUSTSEC-2026-0367, -0368 and -0369. The per-user folders now come from the environment.
2026-10-25 11:56:58 -04:00
Dylan M. Taylor fe61243ebf feat(update): add craft-update crate for release checking, install, and self-updating (#311)
* feat(update): add craft-update crate for release checking, install, and self-updating

* fix(update): provide non-windows stub for finish

* fix(update): address CodeRabbit review feedback on craft-update

* feat(dialog,self_update): enhance prompt styling, support standalone self-updates

* fix(update): make startup prompt modal with Escape, skip non-file tar entries
2026-10-25 09:32:22 -04:00