Commit Graph
429 Commits
Author SHA1 Message Date
FredApps 1f168df0af Merge pull request #1149 from tareq-halaby/feat/pin-apps-to-top
Add pin-to-top and reorder for launcher apps
2026-11-03 09:17:12 +01:00
FredApps 8aa8df744a Fix pinned app drops outside the scroll viewport 2026-11-03 09:08:50 +01:00
FredAppsandClaude Opus 5.5 644b2adb6f style(ui): rustfmt the pinned-row test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 09:06:33 +01:00
FredAppsandClaude Opus 5.5 c20bd909d9 Merge current main into #1149
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 09:00:18 +01:00
FredAppsandClaude Opus 5.5 cbe1582624 fix(ui): keep pinned rows' buttons in their slot, one place ends the drag
- The action row's width lost a gap for every app, pinned or not (`+ gap`), so
  each row's menu ran 8 px past its column. The main button keeps its old slot;
  a pinned app's grip now leads the row and the table widens its actions column
  by one square while a pinned app is shown, so no button is squeezed (#956) and
  every row stays in line. A test checks a pinned row against an unpinned one.
- The grip only shows in the grid and the table, where dropping works; on the
  details page it was a control that did nothing.
- A released drag ends in one place, after the list draws, instead of in the
  grid, the table, the empty list and each drop target.
- Move up and Move down use the drag's `reorder_pinned_to`; `reorder_pinned`
  and its test go, and `pinned_neighbor` returns the slot.
- Drop the settings test that only re-ran its own steps (the UI test covers
  hiding a pinned app), the tool-generated design note, and use "Lösen" for
  Unpin in German.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 09:00:18 +01:00
Dylan M. Taylor 61230e5fb8 Merge pull request #1147 from dylanmtaylor/feat/flatpark-externally-managed
feat: preserve Flatpak remotes and add opt-in OCI migration
2026-11-03 00:09:39 -05:00
Dylan M. Taylor 5ea8d5bcd9 Preserve Flatpak bundle installation scope 2026-11-03 00:03:22 -05:00
Dylan M. Taylor ba0335b52c Fix Flatpak bundle ownership and signed updates 2026-11-03 00:01:58 -05:00
Eric Kaden 39a200f25e macOS: the Dock icon brings the window back after closing it (#456) (#799)
* macOS: the Dock icon brings the window back after closing it (#456)

Closing the window to the menu bar ordered it out, and clicking the Dock
icon afterward brought nothing back: the Dock sends a "reopen" Apple
Event, which neither winit 0.30 nor eframe passes on, and handling it
directly needs unsafe code.

On macOS, closing to the tray now hides the app, as Cmd-H does. macOS
unhides a hidden app by itself, window and all, when its Dock icon is
clicked, it is opened again from Finder or Spotlight, or it is picked
with Cmd-Tab. "Show ArtCraft Launcher" unhides it before showing the
window. A start in the tray keeps ordering the window out, as the app
is still being activated then, which would unhide it.

* macOS: say why Shell.visible isn't resynced after the Dock unhides the app

A left click on the tray icon opens its menu on macOS, so the toggle that
reads this flag never arrives there.
2026-11-02 22:43:43 -05:00
VoiceLessQ dd21313655 fix(packaging): no second menu entry when an AppImage manager already lists the launcher (#474, #394) (#1032)
* fix(packaging): declare the launcher's real window class in its .desktop file (#1026)

The window sets its app id (Wayland) and WM_CLASS (X11) to
ai.storyteller.artcraft-launcher, but the entry said
StartupWMClass=artcraft-launcher. Desktops that find the entry by its
file name were fine; an entry under another name (AppImageLauncher,
Gear Lever, a hand-made one) never matched, so the dock showed a
generic icon and the launcher could not be pinned.

* fix(packaging): no second menu entry when an AppImage manager already lists the launcher (#474, #394)

appimaged, AppImageLauncher and Gear Lever write their own entry for the
AppImage under another file name. AppRun added
ai.storyteller.artcraft-launcher.desktop next to it on every start, so the
launcher showed up twice in the app menu, and deleting the extra entry only
lasted until the next launch.

AppRun now installs nothing while another entry's Exec runs this AppImage,
and removes the entry it wrote earlier when own_entry says it is still
unchanged. own_entry also accepts entries that carry the old
StartupWMClass=artcraft-launcher, since that line is the only difference
from what earlier releases wrote.

* fix(packaging): a hidden manager entry doesn't stand in for ours

Menu editors hide an entry with NoDisplay=true or Hidden=true. When a user
hid the manager's duplicate that way, AppRun still counted it and removed
our entry, leaving the launcher out of the menu. Such an entry no longer
counts. Test 15e covers both keys.

* fix(packaging): a manager entry stands in for ours only with our window class

Read only the [Desktop Entry] group, so an Exec in a [Desktop Action] group
doesn't count, and require StartupWMClass=ai.storyteller.artcraft-launcher.
Gear Lever doesn't refresh its entry when the launcher replaces the AppImage
in place, so its entry can keep the old class, and without ours GNOME would
have nothing to match the window against.
2026-11-02 22:43:36 -05:00
C0ukiandClaude afe9bf3162 i18n(ja): align spacing around Latin text and fix two lines (#717)
- Put a space between Japanese and Latin words/placeholders in the
  install-folder messages and "Show {n} more changes", as the rest of
  the catalog does.
- "Installing, then restarting…": drop the completed-state wording.
- "On Linux, custom folders use AppImage format.": no longer identical
  to the line above it.

Drafted with AI assistance; native-speaker review welcome.


Claude-Session: https://claude.ai/code/session_01K5NRZNWfaSLBWVzV8hivyS

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-02 22:43:30 -05:00
smittie2000andClaude Opus 5.5 e46328fe37 refactor(native): one PATH lookup (#784)
* refactor(native): one PATH lookup for detect and process

process.rs (`which_on_path`) and two places in detect.rs's Linux module
each searched PATH the same way. They call `process::which` now.

These all ask what the user can run, so PATH is the right place to look.
install.rs is left alone: programs run as root come from the root-owned
SYSTEM_BIN_DIRS, never from PATH.

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

* Document the Linux detect, find_binary and binary_version functions

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

* Say every case where binary_version gives up

The 5-second limit matches the code. The doc also missed that the binary
can fail to start or exit with an error, and that it is killed on timeout.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 22:43:23 -05:00
293fefacda Timestamps saved to disk never wrap the schedule arithmetic (#774) (#840)
* Timestamps saved to disk never wrap the schedule arithmetic (#774)

A saved feed-throttle `not_before` outside the sane range — the far-future
check missed anything at or below zero — survived `load`, and `wait`'s
`at - now` wrapped: near `i64::MIN` it panicked a debug build and, wrapped
in release, kept the feed from ever being asked again. `load` now drops
any wait outside 0..=now + 2 * MAX_THROTTLE_BACKOFF and the subtraction
saturates.

The update-check schedule had the same shape: a `last_check` in the future
(an edited app.ron, a clock that ran fast and was later corrected)
postponed checks until real time caught up, and i64::MIN panicked. The
condition is now `update_check_due`, which treats a future time as due and
saturates; the two rescan comparisons that share the pattern saturate too.

Co-Authored-By: GLM-5.3 <noreply@z.ai>

* Test the saved throttle state at its limits (#774)

Covers the review's remaining cases: a saved `not_before` of i64::MAX is
dropped while a sane one (now + 600) stands, and `failures`/`throttles`
saved at u32::MAX cap the next backoff at its maximum without overflowing
(`doubled` caps the doublings at 16 and the counts add saturating).

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

* Merge schedule tests into existing test module

* Preserve long Retry-After waits

---------

Co-authored-by: GLM-5.3 <noreply@z.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 22:43:16 -05:00
VoiceLessQ 50ea99324d fix(ui): keep long Retry/Update labels from pushing the menu out of the row (#956) (#1012)
* fix(ui): keep a long Retry update button from pushing the menu out of the row (#956)

The table gives the main button about 160 px, but buttons grow to fit
their label, so "Retry update to 0.12.0" (205 px) pushed the menu out
of the actions column. When the full label doesn't fit, the button now
says "Retry update"; the version column already shows the version.
"Update to {v}" gets the same fallback to "Update".

Adds "Retry update" to the ten complete catalogs.

* fix(ui): fall back to the plain action when a short retry label still doesn't fit

Running the launcher in German showed "Update wiederholen" (173 px) is
itself wider than the table's 162 px slot, so the menu was still pushed
out. Retry and update buttons now try their labels longest first and end
on the plain action ("Update", "Install"), which fits in every catalog.

The German test checks both retry rows against an Install button.

* test(ui): check which Retry label each view shows (#956)

The slot test matched "Retry update" by substring, so it passed whether the
button kept its version or dropped it. Now it names the label. The table's
narrow slot shows "Retry update", while a card with room for it keeps the
full "Retry update to 0.12.0", which is what a wide enough slot should do.
The doc of `fitted` also names the card's top strip, which shows the version
there.

* test(ui): main buttons fit the narrowest window in every language (#956)
2026-11-02 22:43:10 -05:00
VoiceLessQ d90d2e1c6c fix(core): tighten tag, file name and notice link checks (#776, #782) (#1024)
* fix(core): tighten tag, file name and notice link checks (#776, #782)

- Tags from GitHub, the feed and the latest-release redirect must be up to
  64 of [A-Za-z0-9._+-], not starting with a dot (release::valid_tag).
  A feed tag like `v0.6.0~beta-` plus a bidi override still read as
  0.6.0-beta and was served; such a release now falls back to GitHub.
- Asset file names use the same allowlist, with no trailing dot and no
  Windows device names (CON, nul.txt, COM1.msi).
- Notice links go through safe_web_link, so plain http and user-info
  links are dropped, as in release notes.
- The 20-notice cap now counts the rename notices too.

* fix(core): treat only COM1-COM9 and LPT1-LPT9 as Windows device names

Microsoft's naming rules reserve COM1 to COM9 and LPT1 to LPT9 (plus the
superscript forms, which the ASCII allowlist already refuses), not COM0
or LPT0. Match them with a slice pattern instead of indexing.

* test(core): cover the hostile tags, names and links from review (#776, #782)

Adds a NUL in a tag, a file: notice link, 1,000 announcements, and direct
checks that a download link and a redirect use the same rules.

* fix(core): keep rename notices inside the notice cap (#782)

Twenty announcements left no room, so the cap cut the rename added after them.

* docs(core): notice links are https only now (#776)
2026-11-02 22:43:03 -05:00
VoiceLessQ 45e3cf9e72 fix(ui): a press on the window edge only resizes (#571) (#1055)
* fix(ui): a press on the window edge only resizes (#571)

egui gives a press with no direct hit to the closest widget within 5 px, so a press in the resize band landed on the title-bar control below it, and a quick drag clicked it. Put a click-sensing Area over each band, above dialogs too.

* fix(ui): cover a grown window's edge from its first frame (#571)

An Area's own hit rect keeps last frame's size, so a press on the new
part of an edge still reached Close. A widget sized from this frame's
rect closes that gap.

* fix(ui): an edge press on the first restored frame only resizes (#571)

That frame has no edge areas yet and egui hit-tests last frame's widgets, so the press lands on a full-height caption button. window_buttons now drops a pointer click inside the resize band. Key presses carry no pointer position, so they still work.

* test(ui): press either side of the band's edge on the first restored frame (#571)
2026-11-02 22:42:57 -05:00
VoiceLessQ 651e392960 fix(ui): the release notes card grows with its zoom (#594) (#1062)
* fix(ui): the release notes card grows with its zoom (#594)

The card's size limit now scales with notes_zoom, and sheet::size still
holds it inside the window. Below 100% it keeps the usual size.

* test(ui): the notes card's height follows the zoom too (#594)

* test(ui): the notes card stays in the smallest window, and keeps its size below 100% (#594)
2026-11-02 22:42:50 -05:00
JeremyandClaude Opus 5.5 8c651be747 fix(windows): read an MSI's install context without starting PowerShell (#1082)
* fix(windows): read an MSI's install context without starting PowerShell

The update path ran a hidden PowerShell that asked the WindowsInstaller COM
object which context a product was installed in. That is the pattern
behavioural antivirus flagged in #1045. The context now comes from
Windows Installer's own registration keys, read the way ProductsEx reads
them: this account's managed and unmanaged products and the machine's,
under the product code's packed name. A key the launcher may not read is
still a lookup that didn't run, so the uninstall-record fallback is
unchanged. Nothing in an update starts PowerShell any more.

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

* fix(windows): declare winsafe's advapi feature and avoid a panicking split

From review:
- current_user_sid uses HACCESSTOKEN and TokenInfo, which need winsafe's advapi
  feature. It built only because craft-update enables it; now declared here.
- packed_code uses split_at_checked, like the .get() calls after it.
- The incident note lists the replaced lookup under "What changed", not "Still open".

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

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 22:42:43 -05:00
Dylan M. Taylor 57143114d1 fix(install): bind a Replace answer to the path the user was shown (#1189)
Command::Replace carried only the app. Between the "already exists"
question and the answer, the release can change (a new version, another
asset name, another folder), and the install then recomputed its
destination and wrote over a different existing file with replace=true.

Replace now carries the path from Event::Occupied. The backend checks
what is in the way again: if it is exactly that path the install
overwrites it; if something else is in the way it asks again; if nothing
is, it installs without leave to overwrite.
2026-11-02 22:42:36 -05:00
Dylan M. Taylor 90cf925a44 fix(single-instance): exit only for a launcher that proved itself (#1186)
* fix(single-instance): exit only for a launcher that proved itself

A second start exited (Instance::Secondary) whenever the port's holder
answered with a version at least as new as its own, or failed the token
proof. Any local listener on 47413 could therefore keep the launcher from
starting without proving it belongs to the same account.

The running launcher now challenges every newcomer that offers a nonce,
whatever its version, and still grants Replace only to a newer one that
proves itself. The newcomer defers only to an authenticated answer; an
unproven or failed one is treated as an unrelated program and it starts
anyway, as it already did for a port held by something else.

* fix(single-instance): re-prove the port holder before exiting after a stalled takeover

* fix(single-instance): prove a stalled holder without the 0.0.0 probe

Launchers from 0.8.1 to 0.14.x only challenge a newer newcomer, so a 0.0.0
probe of a busy older launcher that stayed after a takeover was never proven
and the new start ran beside it. Greet with our own version, check the
holder's proof, and never answer the challenge, so the probe can only show
the window and never grant Replace.
2026-11-02 22:42:30 -05:00
Dylan M. Taylor cbc1620306 fix(fonts): match CJK face names without decoding every record (#1191)
* fix(fonts): match CJK face names without decoding every record

face_index decoded every family and style name record of each face into
an owned String, collected them, and only then compared. Name records can
share one long encoded string, so a crafted font at a candidate path
(a hand-installed MiSans, say) could make the UI thread decode and hold
hundreds of MiB of names.

Compare each name char by char against the short wanted name, stopping at
the first mismatch or one char past its length, keep nothing, and look at
no more than MAX_NAME_RECORDS records per lookup.

* fix(fonts): cap compared name records per name ID, not across both
2026-11-02 22:42:24 -05:00
Dylan M. Taylor d929e8f8e2 test(xtask): build the feed fixture in a private temp directory (#1190)
The feed test wrote its fixture under a predictable PID-based name in the
shared temp root. Another local user could pre-create that tree with
symlinks, so the fixture writes would follow them with the test runner's
permissions. Use tempfile::tempdir, which creates a fresh 0700 directory
with an unpredictable name and removes it when dropped.
2026-11-02 22:42:17 -05:00
Dylan M. Taylor b16a15a210 fix(logging): drop HTTP wire dumps whatever RUST_LOG says (#1185)
* fix(logging): drop HTTP wire dumps whatever RUST_LOG says

ureq_proto traces every byte it sends as hex-and-ASCII rows, the
Authorization header with the optional ARTCRAFT_LAUNCHER_GITHUB_TOKEN
included. The builder capped ureq_proto at debug, but env_logger lets the
longest matching module win, so RUST_LOG=ureq_proto::util=trace still
wrote the token to standard error and the log file, split across rows
where the Bearer redaction can't see it.

Wrap the env_logger Logger in a gate that drops ureq_proto trace records
before they are formatted, whatever the filter says.

* test(logging): pin wire-dump test filters against inherited RUST_LOG
2026-11-02 21:37:17 -05:00
Dylan M. Taylor e5d3039747 fix: Flatpak review fixes, and add the signature lookaside for signed bundles
Review of #1147:
- A failed OCI migration precheck (no version in the remote's AppStream data, polkit refusing
  remote-add, offline) now falls back to the bundle update instead of failing it; only errors
  once the reinstall starts are reported. The ref must list exactly the selected version.
- `flatpak list` names a named installation `system (<name>)`; the parser keeps `<name>`, and
  one packages::flatpak_scope builds the --user/--system/--installation= argument everywhere.
- A repository-managed Flatpak on a pinned or nightly track gets the "update with flatpak"
  hint instead of a plain `flatpak update`, and status no longer offers a GitHub version for it.
- The migration refuses while the app runs; the origin check after the reinstall can't be
  cancelled; is_flatpark_origin matches only `flatpark`; settings_install keeps its doc; the
  no-op oci_migration Cargo features are gone.

Signed bundles (storytold/craft-common-actions#129): a signed bundle's origin has the release
key but no signature lookaside, which `flatpak build-bundle` can't record, so its first update
from the ArtCraft OCI repository fails with "no valid signatures". The updater then sets
--signature-lookaside=https://getartcraft.com/signatures on that origin once and retries;
any other origin or error is left alone.
2026-11-02 21:29:10 -05:00
Dylan M. Taylor 43d0ff9fd8 fix(install): remove only machine-registered products in an elevated downgrade (#1187)
An all-users downgrade removed, under one elevated prompt, every product
code whose uninstall record carried the app's DisplayName and Publisher,
the current account's own HKCU records included. Those are writable
without elevation, so a program running as the user could plant one that
names the app and an unrelated all-users product's code, and have that
product removed under the app's permission prompt.

The elevated session now takes its removal targets only from the
machine's uninstall records (HKLM), which only administrators can write.
An all-users downgrade replaces an all-users copy, which a per-user
record never names.
2026-11-02 21:17:11 -05:00
Brandon ThomasandClaude Opus 5.5 4add62aaf9 Release 0.14.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.14.0
2026-11-02 19:25:11 -05:00
Dylan M. Taylor b1c3e8025d style: format Flatpak migration error 2026-11-02 17:55:21 -05:00
Dylan M. Taylor 89abb2e5d3 fix: keep Flatpak OCI migration safe to cancel and retry
Add the artcraft-oci remote with --if-not-exists so concurrent migrations
do not fail, let the --reinstall step finish once started because killing
flatpak-spawn does not stop the host transaction, and when the reinstall
fails after removing the ref, point to the .flatpak bundle on the releases
page.
2026-11-02 17:55:21 -05:00
Dylan M. Taylor f8386cd1d4 fix: refresh the OCI remote's AppStream data before checking versions 2026-11-02 17:37:36 -05:00
Dylan M. Taylor 55b6d660be test: exercise OCI settings and fix Flatpak remote creation 2026-11-02 17:37:36 -05:00
Dylan M. Taylor a4e76179cc fix: preserve repository ownership and Flatpak update results 2026-11-02 17:37:36 -05:00
Dylan M. Taylor 25e53ccbba refactor: name OCI migration controls oci_migration 2026-11-02 17:37:36 -05:00
Dylan M. Taylor 53509be742 docs: keep experimental OCI migration details in the PR 2026-11-02 17:37:36 -05:00
Dylan M. Taylor fc9ef02fee feat: expose OCI migration as an opt-in advanced setting 2026-11-02 17:37:36 -05:00
Dylan M. Taylor 774bc395eb feat: gate bundle to OCI migration behind experimental feature 2026-11-02 17:37:32 -05:00
Dylan M. Taylor 3e09878c6b fix: compile Flatpak cancellation helper only on Linux 2026-11-02 17:37:32 -05:00
Dylan M. Taylor 1794bc1541 fix: match the published ArtCraft OCI remote endpoint 2026-11-02 17:37:32 -05:00
Dylan M. Taylor 4a237c9aff fix: update ArtCraft repository Flatpaks through their remote 2026-11-02 17:37:32 -05:00
Dylan M. Taylor b17ca19ae8 fix: preserve Flatpak update scope and handle cancellation 2026-11-02 17:37:32 -05:00
Dylan M. Taylor 6708ee4c64 update: mark FlatPark flatpaks as externally managed and drive updates through FlatPark 2026-11-02 17:37:32 -05:00
Dylan M. Taylor ab20d70e38 Add MaterialCraft to the launcher suite (#1162)
* Add MaterialCraft to the launcher suite

* Stage MaterialCraft integration for its first published release

* Fix MaterialCraft website link and installer icon layout
2026-11-02 17:36:42 -05:00
tareq 8e5a52b442 Handle Escape pin cancel when search leaves no visible apps.
grid/table were skipped on empty results, so Escape never restored the
drag snapshot; poll cancel before that branch and end the drag on release.
2026-11-02 15:47:14 +01:00
tareq d140e09b57 Restore pin order when Escape cancels a mid-drag reorder.
Live hover mutates settings.pinned; Escape must undo that, not only clear
pin_dragging. Move up/down also skip pins hidden by search/filter.
2026-11-02 15:32:34 +01:00
tareq 154b510ae7 Cancel pin reorder on Escape before drop targets run.
Clears pin_dragging when Escape is pressed at the start of grid/table so a cancelled drag cannot still reorder on release.
2026-11-02 15:09:51 +01:00
tareq c92c73af13 Apply pin drop on pointer release before clearing drag state. 2026-11-02 14:38:01 +01:00
tareq de48730115 Fix pin drop axis and rightward insertion index.
Use horizontal half-tests for grid and vertical for table, drop only once per pointer, and adjust the insertion slot after removing the dragged pin.
2026-11-02 13:51:37 +01:00
tareq c05897abac Fix leftward grip-to-grip pin reordering.
Use before/after half hits to choose the final pin index so horizontal drops onto another grip work in both directions.
2026-11-02 13:41:42 +01:00
tareq f8f6d6c63a Keep pin drag id stable and skip stale pins for Move up/down.
Latch pin_dragging only on drag start so table cell reuse cannot reverse a reorder, and pick the next visible pinned neighbor when moving from the menu.
2026-11-02 10:10:13 +01:00
tareq 7362fe2067 Address CodeRabbit pin/reorder review findings.
Widen table drop targets, reserve paused-row width for the grip, hide Pin on hidden apps, and add Move up/down menu actions for keyboard reorder.
2026-11-02 09:59:47 +01:00
tareq f0f8b46b64 Add pin-to-top with drag reorder for launcher apps.
Lets users pin apps above the sorted catalog (grid and table), reorder pinned apps with a grip handle, and clears pins when an app is hidden.
2026-11-02 09:33:03 +01:00