Skip to content

Repository appearance: whole-snapshot persist can wipe a concurrently detected icon #624

Description

@onevcat

Problem

Two writers own @Shared(.repositoryAppearances) with no sync path between them:

  • RepositorySettingsFeature.persistAppearance (RepositorySettingsFeature.swift:450-464) writes the whole state.appearance snapshot, captured once when the pane is built (AppFeature.swift:399-406).
  • Automatic icon detection commits .detectedImage icons into the same dict via read-modify-write (RepositoriesFeature+IconDetection.swift:79-84).

appearanceLoaded is never sent in production (tests only), so the Settings pane never refreshes its snapshot.

Failure scenario

Icon detection lands while Settings → repository pane is open; the user then clicks a color swatch. The stale {color: X, icon: nil} snapshot overwrites the detected icon — it vanishes from sidebar/shelf/canvas and from disk, and the imported asset file is leaked.

Suggested fix

Make persistAppearance mutate fields inside withLock ($0[id]?.color = …) instead of replacing the whole snapshot; alternatively give the pane a refresh path when the shared dict changes.

Related: #623 (same whole-snapshot writeback pattern), #622 (the persist also lives in a cancellable effect).

Origin

TCA-conventions audit of the Settings area (2026-07-30), agent finding — not yet re-verified line by line; confirm the exact write sites before fixing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions