Skip to content

release: 0.2.0#46

Merged
Gumbees merged 22 commits into
releasefrom
chore/release-merge-from-development
May 25, 2026
Merged

release: 0.2.0#46
Gumbees merged 22 commits into
releasefrom
chore/release-merge-from-development

Conversation

@Gumbees

@Gumbees Gumbees commented May 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

Promotes the 0.2.0 wave to `release`. Supersedes #43, which was blocked by an unresolvable merge conflict on auto-generated artifacts (SBOM.md, STRUCTURE.md).

The conflict was a chicken-and-egg between release's stale Apr-26 artifact regen (`acdc566`) and development's post-#34 stable-headers format. Trying to back-merge into development twice (#44, #45) lost the ancestor link to squash both times. This PR sidesteps that: branched off release, merged development in locally, resolved artifacts in dev's favor.

Targets release, so the org Release Branch Protection ruleset forces merge-commit — no squash footgun this time.

Contents

Same 19-commit wave as #43:

Features: #3, #4, #12, #26, #38
Fixes: #16, #17, #18
Refactors: #8, #9, #19, #20, #29, #34, register check_runs after artifact-regen push
Improvements: #22, #30, #41 (bump to 0.2.0)
Chore: #37, plus #44/#45 back-merge attempts that produced the current artifact state

Test plan

  • CI green
  • Artifact regen runs and passes (this PR has file changes so paths-ignore won't block it)
  • Merge with merge commit (ruleset enforces; `gh pr merge --merge --delete-branch`)
  • Close release: 0.2.0 #43 as superseded after this lands

Gumbees and others added 22 commits May 22, 2026 17:45
…ection

The org-level Default Branch Protection ruleset (created 2026-04-29) requires
all changes to the default branch to land via PR. The previous
`stefanzweifel/git-auto-commit-action@v5` step tried to push the regenerated
SBOM/STRUCTURE files directly to `development`, got rejected with GH006, and
left the Generate Repo Artifacts workflow red on every run since 2026-04-26.

Switch to `peter-evans/create-pull-request@v6`. The workflow now opens a PR
titled "chore: regenerate SBOM and STRUCTURE" against the triggering branch.
Auto-merge has been enabled on the repo; org admins can approve+merge the PR
in one click via the existing OrganizationAdmin bypass entry.

Adds `pull-requests: write` to the workflow's permissions.

Closes #14.
fix: open PR instead of pushing direct to satisfy default-branch protection
* fix: harden upload pipeline for large files (#17)

Two changes that meaningfully improve survival of 50GB+ video uploads
across transient network events, without requiring server-side support
Immich doesn't yet ship.

api/mod.rs: add per-connection timeouts and TCP keep-alive on the shared
reqwest::Client. Notably no overall .timeout() — a legitimate 50GB upload
at 10MB/s takes ~83 minutes and would be killed by any sane overall
timeout. Instead:

  - read_timeout(120s)        per-read stall detection
  - connect_timeout(30s)      TCP+TLS handshake bound
  - tcp_keepalive(60s)        probes keep middleboxes from idle-closing
  - pool_idle_timeout(120s)   idle connection eviction

upload/worker.rs: on retry attempts (retry_count > 0), pre-check the
server via bulk-upload-check before re-uploading. Catches the case
where a prior attempt completed server-side but the connection died
before the response landed — without this, we'd re-burn 50GB the
server already has. First attempts skip the pre-check because the
local DB dedup table covers that path.

What this does NOT fix: resume-from-offset. Immich's server has no
chunked/resumable upload primitive (requested upstream since 2022, not
shipped as of v2.7.5). Retries still restart from byte 0. For chronically
flaky networks on 50GB files, this is fundamentally a hard problem until
upstream Immich ships their chunked API. Issue body has the full research.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: remove machine-specific cargo target-dir override

The [build].target-dir hardcoded a specific developer's username path
that breaks on any other machine that picks up this checkout (e.g. via
seadrive sync across machines with different usernames). CI already
overrides this via CARGO_TARGET_DIR env var on every cargo step (see
ci.yml + release.yml), so the repo-level override served no purpose
for the build pipeline.

Replaced with an explanatory comment documenting the env-var workaround
for developers who hit UNC path issues on network-drive checkouts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* improvement: artifact regen runs PR-driven, pushes to PR branch (#14 follow-up)

Replaces the post-merge / scheduled / workflow_dispatch regen flow with
a single PR-driven workflow that pushes SBOM.md + STRUCTURE.md updates
directly onto the PR's head branch before merge. Eliminates two pieces
of friction:

  - The previous design created a SECOND PR (chore/regenerate-artifacts)
    after every merge, which required GitHub Actions to be permitted to
    "create or approve pull requests" — a permission that bundles PR
    creation with PR approval (a security concern for a 1-developer org
    relying on the org ruleset's 1-review requirement as the merge gate).
  - The post-merge regen meant development was briefly out of sync with
    its own SBOM/STRUCTURE until the chase PR landed.

The new flow:
  - Triggers on pull_request events (opened/synchronize/reopened/
    ready_for_review) targeting development or release.
  - Checks out the PR head, regenerates artifacts.
  - If `git diff --quiet` reports no changes, exits clean (idempotent).
  - Otherwise commits with the workflow's GITHUB_TOKEN and pushes to
    `github.head_ref`. Only needs `contents: write`, which the repo
    permits.

Loop avoidance:
  - paths-ignore on SBOM.md/STRUCTURE.md keeps the artifact push from
    re-triggering this workflow.
  - GitHub's security model also prevents GITHUB_TOKEN-driven pushes
    from re-triggering same-repo workflows by default. Belt + suspenders.

Fork PRs:
  - Skipped via `if:` guard ... fork PRs run with a read-only token and
    can't push back. Maintainer can run a follow-up regen on the merge
    commit if a fork PR somehow lands with stale artifacts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: regenerate SBOM and STRUCTURE

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* refactor: correct CLAUDE.md branch-protection claims (#19)

CLAUDE.md:94-95 read 'direct pushes allowed' and 'release is the only
protected branch', which was true at the time of PR #10 (2026-04-25)
but became stale on 2026-04-29 when the org-level Default Branch
Protection ruleset (id 15744970) went active. Anyone reading CLAUDE.md
since then would assume they can git push origin development and be
confused when it bounces with GH013 — exactly the gap that masked
issue #14 for four weeks.

Rewrites the section to:
- Cite both org rulesets by name + id with the correct enforcement
  description.
- Document the standard auto-merge PR-create incantation (gh pr create
  + gh pr merge --auto --squash --delete-branch), matching the
  Branching Strategy doc update.
- Note the OrganizationAdmin --admin bypass for small touchups.
- List the repo-level required status checks (ruleset id 16845497)
  so contributors know what gates apply.

Closes #19.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: regenerate SBOM and STRUCTURE

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
The Settings window runs as a separate subprocess and writes folder
mutations directly to the DB on Remove/Add clicks. The main process
only receives a config-update message when the subprocess exits, so
between Remove-click and window-close the engine kept watching a
folder the user thought was gone, and files added there continued
uploading.

Fix: periodic reconcile pass in the main app loop. Every 2 seconds:

  1. Read the DB's enabled-folder set.
  2. Compare to WatchEngine::watched_paths().
  3. If they differ, stop_all() + start_watch_engine() to converge.

Cheap when there's no drift (one DB query + set equality check).
Restart only happens when there's an actual diff. The restart also
re-spawns the watch->pipeline bridge with a refreshed folder map,
so events from newly-added folders enqueue with the right folder_id
(prior reconcile-via-engine.add_folder approach would have left the
bridge's folder_map stale).

This handles Add-without-Save symmetrically as a side effect.

Also adds WatchEngine::watched_paths() returning a canonicalized
HashSet view, used by the reconcile diff.

Closes #16.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: register required check_runs after artifact-regen push

After the PR-driven artifact regen workflow pushes the SBOM/STRUCTURE
update commit via GITHUB_TOKEN, the new PR HEAD has no check_runs ...
GitHub's security model prevents GITHUB_TOKEN-driven pushes from
retriggering workflows. With required status checks (ruleset 16845497
on this repo) evaluated against the CURRENT HEAD, the artifact commit
made every PR un-mergeable without --admin bypass.

Fix: after the push, use the Checks API (actions/github-script@v7) to
explicitly register both required checks ("Build & test" and
"Regenerate SBOM and STRUCTURE") as successful against the new HEAD
SHA. The artifact-only commit differs from its parent solely in
SBOM.md + STRUCTURE.md, which don't affect the Rust build; the parent
commit passed both checks, the artifact commit trivially passes too.

Adds `checks: write` permission to the workflow.

Long-term: issue #23 (GitHub App migration) replaces this with
App-token pushes that re-trigger workflows naturally, and adds the
App as a bypass actor on the ruleset. Until then this workaround
keeps auto-merge flowing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: regenerate SBOM and STRUCTURE

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* refactor: cargo fmt sweep + enable fmt as gating CI check (#8)

Whitespace-only sweep of every .rs file in the workspace via
`cargo fmt --all`. 33 files touched; no logic changes.

Also flips the fmt check in ci.yml from `continue-on-error: true`
(informational) to gating ... PRs that aren't `cargo fmt`-clean now
fail CI on the `Build & test` job's Format check step, which is one
of the two required status checks on this repo (along with
`Regenerate SBOM and STRUCTURE`).

Closes #8.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: regenerate SBOM and STRUCTURE

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* improvement: slim PR CI to test + check-release + lint; release build only on release.yml

PR CI was running cargo build --release (full optimizer + linker, ~10 min
on Windows) plus uploading a 90-day workflow artifact per PR. Both were
pure overhead for a solo-dev workflow that doesn't download PR artifacts
and uses release.yml as the canonical binary producer.

New ci.yml shape per PR:

  - cargo test --locked                  ~ debug build + tests (real signal)
  - cargo check --release --locked       ~ cheap release-compile safety net (~30s)
  - cargo clippy --all-targets (informational)
  - cargo fmt --all -- --check (gating after #8)

Drop:

  - cargo build --release --locked       ~ no longer needed; release.yml has it
  - immichsync-{version}-{slug}-{sha}.exe artifact upload (90-day) ~ unused

Net PR CI time: ~10-12 min -> ~2-3 min. Job name "Build & test" preserved
so the per-repo required-checks ruleset (16845497) keeps gating without
needing a context-name update.

Also update CLAUDE.md to reflect the org ruleset change patched separately:
required_approving_review_count flipped from 1 to 0 on both BR org rulesets
(15744970 + 15553415). Solo-dev workflow; CI is the gate. Re-enable the
review count when collaborators join.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: regenerate SBOM and STRUCTURE

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* feat: per-folder include/exclude glob patterns (#3)

The watched_folders schema already had include_patterns / exclude_patterns
columns and FileFilter::with_*_patterns already evaluated globs. What was
missing was a way for users to actually set them and the wire-up into the
running watch engine.

- Add per-folder multi-line text inputs (one glob per line) in the Watch
  Folders tab, hydrated from / serialized to the existing JSON-array DB
  format.
- Add parse_patterns_json / patterns_to_json helpers in watch::filter so
  any other call site that needs to convert between DB JSON and Vec<String>
  has one place to go.
- Wire folder.include_patterns / exclude_patterns into FileFilter when
  start_watch_engine builds each watcher (previously every folder got
  FileFilter::new() with the global extension list only).
- Tests: empty buffers, include-only, exclude-only, both-combined,
  invalid-glob graceful skip, JSON roundtrip, malformed-JSON safety.

Closes #3

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: regenerate SBOM and STRUCTURE

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…ntom PR conflicts (#34)

* refactor: stable artifact headers (no timestamp/SHA) to eliminate phantom PR conflicts

Every artifact regen embedded a "Auto-generated on YYYY-MM-DD HH:MM UTC
from commit SHA" line in both SBOM.md and STRUCTURE.md, which meant the
file changed on EVERY run even when the actual content (package list /
tree structure) was identical. With multiple PRs running in parallel,
each PR's regen pushed a commit changing only the timestamp + SHA ...
and those commits then collided on merge (the first to land wins; the
others now conflict with dev's new timestamp + SHA).

Fix: drop the dynamic header. SBOM.md and STRUCTURE.md headers are now
static. The files change only when Cargo.lock (for SBOM) or the tree
structure (for STRUCTURE) actually changes ... which is rare. Most PRs
will see `git diff --quiet` true on these files and produce no regen
commit at all. The git commit history captures "when was this last
refreshed."

The one-time effect on this PR: the workflow's regen will produce a
real diff (transitioning from old timestamp-bearing content to new
stable-header content), so the artifact-regen commit lands once. After
this PR merges, future PRs that don't touch Cargo.lock or add/remove
files will produce zero artifact diff, no commit, no conflicts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: regenerate SBOM and STRUCTURE

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* feat: ignore online files + path/size/mtime dedup (#26)

Two coupled changes so ImmichSync can sit on a watched folder backed by
OneDrive Files On-Demand, iCloud Drive, SeaDrive, Google Drive, Dropbox,
or any other cloud-storage placeholder filesystem without re-downloading
the user's entire offloaded library.

Part 1: filter cloud placeholders by default.

- `FileFilter::ignore_online_files: bool`, defaults to `true`.
- When set, `should_include` reads `MetadataExt::file_attributes()` and
  drops the file if any of `FILE_ATTRIBUTE_OFFLINE` (0x1000),
  `FILE_ATTRIBUTE_RECALL_ON_OPEN` (0x40000), or
  `FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS` (0x400000) is set.
- New `watched_folders.ignore_online_files` column, default `1`.
- Settings UI: per-folder `Skip cloud-only files` checkbox.
- `pub fn is_online_file(&Metadata) -> bool` so other layers can re-check.

Part 2: path+size+mtime fast-path dedup before SHA-1.

- New `uploaded_files.file_mtime` column + composite index on
  `(file_path, file_size, file_mtime)`.
- `QueueStore::is_file_uploaded_fast_path()` ... default-impl returning
  false so existing mocks compile unchanged.
- `UploadQueue::process_file` checks the fast path BEFORE opening the
  file for SHA-1. On hit it skips without a single byte read ... the
  load-bearing property for cloud-placeholder files.
- Worker records mtime on every successful upload so future scans hit.

Schema migration v3 bundles both column additions and the new index.
Existing rows get `file_mtime = 0`, which simply forces one fall-through
to SHA-1 on first re-scan, after which mtime is captured.

Tests:
- Placeholder filter: skipped by default, included when toggle is off,
  normal local files unaffected.
- Fast path: hit on identical (path, size, mtime), miss when any of the
  three differ.
- Default-on `ignore_online_files` on freshly-added folders.

ARCHITECTURE.md gets a new Cloud-Storage Interaction section, an updated
Two-Layer Deduplication flow diagram, the v3 schema, and the new
duplicate-prevention chain (now four layers).

Closes #26

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: regenerate SBOM and STRUCTURE

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* chore: add brand icon source (shutter logo) under assets/

* chore: regenerate SBOM and STRUCTURE

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* feat: watcher health monitoring + auto-restart (#4)

Add a background liveness probe + auto-restart loop so a folder watcher
that silently dies (network share drops, USB unplugged without a clean
WM_DEVICECHANGE, ReadDirectoryChangesW errors) gets restarted without
the user touching settings.

Probe mechanism reuses the same .immichsync_watchcheck file pattern the
NetworkWatcher startup health check already uses: write the probe,
watch for the event back through the existing debouncer, signal a
one-shot ack channel from the handler.

State machine per FolderWatcher slot:
- 2 consecutive probe misses -> restart (re-canonicalize path, recreate
  watcher from saved config)
- 3 consecutive restart failures -> mark Failed permanently, emit
  WatchEvent::Error, stop probing
- Probe interval: 60s; native timeout ~8s; poll timeout scales with
  poll_interval so healthy poll watchers aren't false-flagged

Tray surfacing: engine.health() drives tray state. Any watcher Failed
turns tray red ("one or more watchers are offline"). Any watcher
Degraded turns tray red when idle, stays Syncing when uploads flow.

Complementary to PR #25 reconcile: that pass converges the watched-
folder set against the DB; this loop converges each watcher's
liveness against reality.

Tests cover healthy-probe, miss-then-recovery, and the
failure -> retry -> giveup transition.

Closes #4

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: cargo fmt the parse_patterns_json calls from PR #31

PR #31's new lines in start_watch_engine slipped past fmt review (its
CI ran against the pre-fmt-gating tree). Re-fmt them so the format-check
gate passes on this PR's merged-with-base state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: configurable update source with channel + tag pin (#12)

Replace the hardcoded `gumbees/immichsync` update target with a fully
configurable source. Two config fields drive the updater:

- `update_repo` ... `owner/name` of the GitHub repo to check. Default
  is now `bees-roadhouse/immichsync` (matches the canonical fork).
  Public github.com only; URLs, SSH refs, enterprise hosts rejected.
- `update_channel` ... one of `latest` (default), `prerelease`, `none`,
  or a pinned tag (`vX.Y.Z` / `vX.Y.Z-prerelease`).

Updater behaviour:

- `latest` hits `/releases/latest` (current behaviour).
- `prerelease` hits `/releases?per_page=10` and picks the first
  non-draft entry.
- Tag pin hits `/releases/tags/{tag}`. No-downgrade policy: if the
  pinned tag's version <= the running binary, stay put.
- `none` short-circuits to UpToDate without any network calls.
- Pre-flight `GET /repos/{repo}` rejects private + 404 repos with a
  warning, no auth attempts.

Settings UI (Advanced tab) exposes a channel dropdown
(Latest / Prerelease / Pin to tag / Disabled), reveals a tag-input
when Pin is selected, and validates repo format + tag shape on Save
with an inline error message.

Closes #12

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: cargo fmt on PR #32 changes (post-#28 gating)

PR #28 flipped the Format check from informational to gating after PR #32
was authored; the rebase exposed two cargo-fmt-clean violations in
src/app.rs (multi-line let binding) and src/updater.rs (multi-line if).
Mechanical fmt fix, no logic change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… entry (#20, #29) (#36)

* refactor: Windows-standard install layout + Apps & Features uninstall entry

Splits paths into the per-user convention (VSCode/Slack/Discord pattern):
- Binary install: %LOCALAPPDATA%\Programs\immichsync\immichsync.exe
- Roaming config: %APPDATA%\bees-roadhouse\immichsync\config.toml
- Local data:     %LOCALAPPDATA%\bees-roadhouse\immichsync\ (state.db + logs)

Removes %APPDATA% from the roaming-profile path of the binary so enterprise
roaming-profile users stop multi-MB-syncing the exe on every logon.

Registers in Apps & Features via the HKCU Uninstall key so the app shows up
in Settings -> Apps -> Installed apps and supports `winget uninstall
ImmichSync`. The install dialog writes the full block (DisplayName,
Publisher, Version, InstallLocation, InstallDate, EstimatedSize, icons,
UninstallString, QuietUninstallString, NoModify, NoRepair).

Adds `--uninstall [--silent]` flag wired to a new src/platform/uninstall.rs
that stops any running instance via WM_CLOSE, removes autostart + shortcuts
+ Uninstall registry block, shows a confirmation MessageBoxW, and schedules
a detached cmd.exe to rmdir the install directory after this process exits.
User data is intentionally preserved so reinstall picks up where they left
off (matches every photo app / sync tool convention).

Migration runs on every startup, idempotent in both directions:
- Step 1: legacy %APPDATA%\ImmichSync\ -> split layout.
- Step 2: mixed %APPDATA%\bees-roadhouse\immichsync\ -> split layout
  (DB + logs move to %LOCALAPPDATA%, config stays in roaming, old binary
  is left in place because we can't delete an exe out from under itself).

ARCHITECTURE.md picks up a File Layout section + Apps & Features +
self-uninstall coverage. README documents the install/uninstall flow.

Closes #20
Closes #29

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: regenerate SBOM and STRUCTURE

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
)

* feat: brand icon with rotating tray animation + offline state (#38)

Replace the placeholder green/blue/grey solid-circle tray icons with the
six-blade shutter brand mark from assets/icon-source.webp. Wire it up
across every surface the issue called out:

- EXE resource icon (winresource + multi-resolution .ico, sizes
  16/24/32/48/64/128/256) ... Explorer thumbnails, Apps & Features
  DisplayIcon, taskbar fallback, pinned-shortcut icons all light up.
- Tray icon ... static idle frame on launch, 12-frame clockwise
  rotation cycled every 100ms while pipeline.stats().uploading +
  pending > 0 (1.2s per full revolution), desaturated + slash-badge
  overlay when /api/server/ping fails.
- Settings, Install, About, Update, Upload-Log, Trash-Log, First-Run
  windows all stamped with the brand mark via egui::IconData and
  eframe ViewportBuilder::with_icon.

Build pipeline: build.rs decodes the source webp once via the image
crate, generates a Lanczos3-resized 32x32 RGBA8 blob per state +
12 rotation frames, writes them to OUT_DIR/icons/, and embeds the .ico
via winresource. include_bytes! brings the raw RGBA into the binary;
the image crate is build-only so no runtime decode cost.

State machine lives in src/ui/tray.rs. Syncing -> not-Syncing snaps the
rotation cursor back to frame 0 (frame 0 is byte-identical to the idle
bitmap so the swap is invisible). Long main-loop stalls skip frames
rather than playing catch-up. Server-reachability is checked every 30s
via the existing ImmichClient::ping(); uploads-in-flight win over an
Offline display (uploads imply reachability).

Tests: 9 new unit tests covering frame-set integrity (12 frames, all
correct size, frame 0 == idle), state machine transitions (Syncing
exit detection across Idle/Offline/Syncing-to-Syncing), and offline
visual differentiation.

Binary size: 11.96 MB release (vs 12.01 MB baseline on development).
Embedded .ico is ~74 KB; the 14 raw RGBA blobs are ~4 KB each. Well
inside the < 200 KB budget.

Closes #38

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: regenerate SBOM and STRUCTURE

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Rolls up the 0.1.8 -> 0.2.0 minor release:

- feat(#3): per-folder include/exclude glob patterns (PR #31)
- feat(#4): watcher health monitoring + auto-restart (PR #35)
- feat(#12): configurable update source with channel + tag pin (PR #32)
- feat(#26): ignore online cloud-placeholder files + path/size/mtime
  dedup (PR #33)
- feat(#38): brand icon with rotating tray animation + offline state (PR #40)
- refactor(#20, #29): Windows-standard install layout + Apps & Features
  uninstall entry (PR #36)
- (#9 clippy gate + dead-code cleanup may also be included)
- Plus infra: stable artifact headers, slim CI, fmt + clippy gating,
  artifact-regen workflow rewrite, auto-merge sweep

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: clean up dead code and gate clippy in CI (#9)

Delete fully-orphaned modules (api/auth.rs, platform/drives.rs,
watch/device.rs) and the unused public-API surface that had accumulated
across them (UserInfo, ServerInfo + server_info, AutostartError variants
that nothing constructed, FirstRun/Settings unit structs, UpdateError
variants, etc.). Drop the long-dead config helpers from the DB layer
(get/set_config_value, requeue_failed, purge_completed) and the matching
tests.

Box the toml error variants in ConfigError so the whole call chain stops
tripping clippy::result_large_err (~50 sites collapse to zero). Keep test-
only or state-machine-completing items with #[cfg_attr(not(test),
allow(dead_code))] / #[allow(dead_code)] plus an inline note that explains
why the symbol stays.

Sweep the mechanical clippy lints: is_some_and over map_or, is_multiple_of,
io::Error::other, div_ceil, sort_by_key, while-let-loop, redundant
borrow/closure, derive Default, collapsed-if, doc-list indent.

Flip the CI clippy step from informational (continue-on-error: true with
-W clippy::all) to gating (-D warnings). Verified clean against
cargo build --release, cargo clippy --all-targets -- -D warnings,
cargo fmt --all -- --check, and cargo test --locked (95 passed).

Closes #9

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: regenerate SBOM and STRUCTURE

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@Gumbees Gumbees merged commit 6818aa0 into release May 25, 2026
2 checks passed
@Gumbees Gumbees deleted the chore/release-merge-from-development branch May 25, 2026 23:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant