Skip to content

Repository customization: per-repo title and color in the sidebar#276

Merged
sbertix merged 3 commits intomainfrom
sbertix/customize-repo
Apr 25, 2026
Merged

Repository customization: per-repo title and color in the sidebar#276
sbertix merged 3 commits intomainfrom
sbertix/customize-repo

Conversation

@sbertix
Copy link
Copy Markdown
Collaborator

@sbertix sbertix commented Apr 25, 2026

Summary

  • Adds Customize Repository… to each git repo's sidebar ellipsis menu — opens a sheet with a title field and color swatches (default, six predefined tints, plus a custom rainbow swatch backed by the system color picker).
  • Customizations land in sidebar.json under each section's optional title / color fields. Legacy files round-trip cleanly via decodeIfPresent / encodeIfPresent.
  • Sidebar header text picks up the custom title (falling back to the folder name) and the picked tint.

Implementation notes

  • New RepositoryColor enum: predefined cases plus .custom("#RRGGBB"), codable as a string.
  • RepositoryCustomizationFeature mirrors the canonical WorktreeCreationPromptFeature sheet pattern (BindableAction, delegate, .ifLet presentation).
  • preserveOrphanSections carries title/color through transient orphan reconciles so a filesystem-flutter reload doesn't strip the user's tint. Explicit Remove Repository still purges the section so re-adding the same path doesn't silently restore the old customization.
  • requestCustomizeRepository rejects non-git repos so a future deeplink or palette entry can't write customization that the folder row would never render.
  • NSColorPanel.shared.isRestorable = false opts the system color panel out of macOS state restoration so a left-open panel can't outlive the app across launches; .onDisappear on the customize sheet hides it explicitly. mainWindow(from:) filters NSPanel from its fallback so a transient panel can't be surfaced as the main window.

Test plan

  • Open the customize sheet from the sidebar ellipsis menu, set a title and a predefined color, save — sidebar header reflects both.
  • Reopen the sheet — fields seed from the stored values.
  • Pick a custom color via the rainbow swatch + system panel.
  • Restart the app — customization persists; legacy sidebar.json files without title/color load unchanged.
  • Remove a customized repo, re-add the same path — old customization does NOT come back.
  • Force-quit with the system color panel visible — next launch opens on the main window.
  • Cmd-tab away and back while editing — color panel is not dismissed mid-edit.
  • make check passes; make test passes (new suites: RepositoryColorTests, RepositoriesFeatureCustomizationTests, plus additions to SidebarStateTests and RepositoryCustomizationFeatureTests).

Closes #275

sbertix added 3 commits April 25, 2026 01:27
- New `Customize Repository…` entry on each git repo's ellipsis menu
  opens a sheet with title text field and color swatches (default,
  six predefined tints, plus a custom rainbow swatch backed by the
  system color picker). Saved values land in `sidebar.json` under
  each section's optional `title` / `color` fields.
- `RepositoryColor` enum carries the predefined cases plus
  `.custom("#RRGGBB")`; legacy `sidebar.json` files round-trip via
  `decodeIfPresent` / `encodeIfPresent`.
- `preserveOrphanSections` now propagates `title` and `color` so
  customization survives transient orphan reconciles (matching how
  `.archived` / `.pinned` curation already survives).
- `.binding(\.customColor)` is gated on `state.color?.isCustom == true`
  so a stray sRGB-quantization round-trip can't demote a predefined
  pick to a `.custom("#FF3B30")` hex; tapping the rainbow swatch
  fires an explicit `selectCustomColor` action that enters custom
  mode.
- `requestCustomizeRepository` rejects non-git repos so a future
  deeplink or palette entry can't write customization that the
  folder row would never render.
- Tests cover the parent reducer save/cancel paths, the binding
  promotion contract, and Section codable round-trips. Section /
  parser tests live in their owning suites.
- Custom color selection works again. Reverted the binding-gate
  (`state.color?.isCustom == true`) and the `selectCustomColor`
  entry action that the previous round added — that combination
  blocked the very first ColorPicker drag from promoting to
  `.custom`. Trade-off: a deliberate drag in the panel demotes
  `.red` to its sRGB-quantized hex, which is correct intent
  capture.
- System color panel no longer outlives the customization sheet.
  `RepositoryCustomizationView.onDisappear` calls
  `NSColorPanel.shared.orderOut(nil)`. Combined with the main
  Window's new `.restorationBehavior(.disabled)`, the panel can't
  be resurrected at next launch via state restoration.
- Force-quit-with-panel-visible now lands on the main window.
  `mainWindow(from:)`'s fallback filters `NSPanel` instances so
  `makeKeyAndOrderFront` can't surface a restored color panel as
  the "main window".
- Alt-tab no longer kills the in-progress color panel.
  `applicationDidBecomeActive` previously called
  `NSColorPanel.shared.orderOut(nil)` unconditionally, dismissing
  the user's panel mid-edit on every cmd-tab return. Removed
  alongside the launch-time orderOut now that
  `.restorationBehavior(.disabled)` handles resurrection at the
  source.
- Explicit `Remove Repository` no longer leaves a customization
  tombstone. `.repositoriesRemoved` purges
  `state.sidebar.sections[id]` before reconcile fires, so re-adding
  the same path doesn't silently restore the old title / color.
  `preserveOrphanSections` still keeps customization across
  transient drops (filesystem flutter).
- Dropped `RepositoryColor.hexString` (zero callers; the doc
  comment described a path that doesn't exist) and updated
  `displayName`'s docstring to reflect actual usage.
- Added a `RepositoriesFeatureCustomizationTests` regression
  asserting that explicit removal purges the sidebar tombstone.
- Set `NSColorPanel.shared.isRestorable = false` in
  `applicationDidFinishLaunching` so AppKit stops persisting the
  panel's visibility across launches. Reverted the main `Window`
  scene's `.restorationBehavior(.disabled)` — the main window
  stays restorable, only the system color panel opts out.
- Dropped the launch-time `NSColorPanel.shared.orderOut(nil)`;
  `isRestorable = false` alone handles forward sessions.
- Added a regression test pinning the predefined→custom demote
  on user-driven ColorPicker drags so a future refactor can't
  silently re-add the binding gate that broke first-drag.
@sbertix sbertix enabled auto-merge (squash) April 25, 2026 00:26
@tuist
Copy link
Copy Markdown

tuist Bot commented Apr 25, 2026

🛠️ Tuist Run Report 🛠️

Builds 🔨

Scheme Status Duration Commit
supacode 2m 3s 4979a147f

@sbertix sbertix merged commit 9bae228 into main Apr 25, 2026
2 checks passed
@sbertix sbertix deleted the sbertix/customize-repo branch April 25, 2026 00:33
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.

Feature request: ability to rename repo / give project another name

1 participant