Skip to content

feat: implement batch deletion and shift range selection#53

Open
lynicis wants to merge 9 commits into
andrew-waters:mainfrom
lynicis:feature/batch-delete-selection
Open

feat: implement batch deletion and shift range selection#53
lynicis wants to merge 9 commits into
andrew-waters:mainfrom
lynicis:feature/batch-delete-selection

Conversation

@lynicis

@lynicis lynicis commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

This PR implements batch deletion, shift-click range selection, Command+A select-all, and multi-selection detail summary views for containers, images, mounts, DNS domains, and networks. Additionally, it resolves a compiler bottleneck in Content.swift and a Cocoa events warning.

@lynicis
lynicis force-pushed the feature/batch-delete-selection branch from 2e8c8d9 to d002e86 Compare June 27, 2026 08:09
@andrew-waters

Copy link
Copy Markdown
Owner

Hi @lynicis thanks for the contribution. I've spent quite a bit of time refactoring some of the internals to get some proper test coverage and split out the responsibilities . Unfortunately, that has left this PR with quite a bit of drift. Would you like to rebase your branch and apply the changes again?

@lynicis
lynicis force-pushed the feature/batch-delete-selection branch from d002e86 to 4ada151 Compare July 6, 2026 09:35
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Orchard adds shared multi-selection handling, batch deletion APIs, multi-resource list interactions, multi-item detail cards, and selection-state propagation across the layout for images, mounts, DNS domains, and networks.

Changes

Orchard multi-selection and batch actions

Layer / File(s) Summary
Batch deletion services
Orchard/Services/*Service.swift
Batch deletion APIs reload resource lists and aggregate failures; mount deletion recreates affected containers while preserving configuration.
Shared selection handler
Orchard/Views/Components/SelectionHandler.swift
Selection supports single selection, Command toggling, Shift ranges, and anchor tracking.
DNS and image multi-select views
Orchard/Views/Features/DNS/*, Orchard/Views/Features/Images/*
DNS and image lists support set-based selection, select-all, bulk deletion, and multi-item summary cards.
Mount and network multi-select views
Orchard/Views/Features/Mounts/*, Orchard/Views/Features/Networks/*
Mount and network lists support set-based selection, select-all, protected default resources, bulk deletion, and multi-item summary cards.
Layout selection wiring
Orchard/Views/Layout/*
Plural selections are synchronised and propagated through the layout, with deferred DNS/network navigation and multi-item image or mount detail rendering.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ListView
  participant SelectionHandler
  participant ContentView
  participant DetailContentView
  participant MultiCardsView
  ListView->>SelectionHandler: handleSelection(clickedId, modifiers)
  SelectionHandler-->>ListView: updated selection set
  ListView->>ContentView: update selected bindings
  ContentView->>DetailContentView: propagate selection state
  DetailContentView->>MultiCardsView: render when selection count exceeds one
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is too brief and does not follow the required template sections or checklist. Reformat the PR text to include What does this PR do?, Related issues, Screenshots/recording, and the checklist items.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarises the main change: batch deletion with range selection.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
Orchard/Views/Features/DNS/ListDNS.swift (1)

91-97: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

"Make Default" restores the wrong (dead) selection binding.

This preserves selectedDNSDomain, but the List's actual selection state is selectedDNSDomains. Since dnsService.setDefault() reloads the list, the live selectedDNSDomains set is never restored, so the row's visual selection can be lost after making a domain default.

🐛 Proposed fix
 Button("Make Default") {
-    let currentSelection = selectedDNSDomain
+    let currentSelection = selectedDNSDomains
     Task {
         await dnsService.setDefault(domain.domain)
-        selectedDNSDomain = currentSelection
+        selectedDNSDomains = currentSelection
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Orchard/Views/Features/DNS/ListDNS.swift` around lines 91 - 97, The "Make
Default" action in ListDNS.swift restores the wrong selection state because it
writes back to selectedDNSDomain instead of the List’s live selectedDNSDomains
set. Update the Button action in the Make Default handler to preserve and
restore the actual selection binding used by the list, so after
dnsService.setDefault(domain.domain) reloads the data the row remains visually
selected. Use the existing List selection state and the Make Default Button
closure to locate the fix.
Orchard/Views/Layout/DetailContent.swift (1)

26-57: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

DNS and Network detail views are missing multi-card wiring; selectedDNSDomains/selectedNetworks are unused.

imageDetailView and mountDetailView were correctly updated to branch to MultiImageCardsView/MultiMountCardsView when more than one item is selected, but the .dns and .networks cases in body (lines 34-57) were not updated the same way. selectedDNSDomains and selectedNetworks (declared at lines 14 and 16) are threaded all the way down from ContentView through ThreeColumnLayout but are never referenced in this file's body — they're effectively dead parameters for these two tabs.

Given the PR explicitly claims "multi-selection detail summary views for containers, images, mounts, DNS domains, and networks", and this layer's own scope description states it "conditionally renders multi-card views when multiple items are selected" for DNS domains and networks too, this is a functional gap: shift-clicking/Cmd+A-selecting multiple DNS domains or networks will update the underlying Set (via DNSListView/NetworksListView), but the detail pane will still fall back to single-selection behaviour (or "Select a DNS domain"/"Select a network").

🐛 Suggested fix (mirror the images/mounts pattern)
         case .dns:
-            if let selectedDNSDomain = selectedDNSDomain {
+            if selectedDNSDomains.count > 1 {
+                MultiDNSCardsView(
+                    domainIds: selectedDNSDomains,
+                    selectedDNSDomainsBinding: $selectedDNSDomainsBinding
+                )
+            } else if let selectedDNSDomain = selectedDNSDomain {
                 DNSDetailView(
                     domain: selectedDNSDomain,
                     selectedTab: $selectedTabBinding,
                     selectedContainer: $selectedContainerBinding
                 )
             } else {
                 Text("Select a DNS domain")
                     .foregroundColor(.secondary)
                     .frame(maxWidth: .infinity, maxHeight: .infinity)
             }
         case .networks:
-            if let selectedNetwork = selectedNetwork {
+            if selectedNetworks.count > 1 {
+                MultiNetworkCardsView(
+                    networkIds: selectedNetworks,
+                    selectedNetworksBinding: $selectedNetworksBinding
+                )
+            } else if let selectedNetwork = selectedNetwork {
                 NetworkDetailView(
                     networkId: selectedNetwork,
                     selectedTab: $selectedTabBinding,
                     selectedContainer: $selectedContainerBinding
                 )
             } else {
                 Text("Select a network")
                     .foregroundColor(.secondary)
                     .frame(maxWidth: .infinity, maxHeight: .infinity)
             }

Adjust domainIds/networkIds parameter names to whatever MultiDNSCardsView/MultiNetworkCardsView actually declare.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Orchard/Views/Layout/DetailContent.swift` around lines 26 - 57, The DNS and
network detail branches in DetailContent.body still only handle single-item
selection, so the multi-selection sets selectedDNSDomains and selectedNetworks
are unused. Update the .dns and .networks cases to mirror the existing
imageDetailView and mountDetailView pattern by switching to the appropriate
multi-card views when multiple items are selected, and pass through the matching
collection parameters expected by MultiDNSCardsView and MultiNetworkCardsView;
keep the existing single-selection fallback for the one-item case.
🧹 Nitpick comments (5)
.github/workflows/release.yml (1)

358-368: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Fail loudly when the gh-pages SHA can't be resolved.

git ls-remote exits 0 with empty output when the ref isn't found (only a hard transport error is caught by the default pipefail shell). An empty sha output then propagates as an empty build_version, and pages.yml silently falls back to github.sha — the exact SHA ci.yml already deployed. That re-introduces the no-op/stale-appcast failure this PR is designed to prevent, with no signal in the logs. A guard surfaces the problem instead of shipping a stale appcast.

♻️ Proposed guard
       - name: Capture gh-pages commit for the Pages deploy
         id: ghpages_sha
         if: steps.appcast.outputs.generated == 'true'
         run: |
           SHA=$(git ls-remote "${{ github.server_url }}/${{ github.repository }}" refs/heads/gh-pages | cut -f1)
+          if [[ -z "$SHA" ]]; then
+            echo "::error::Could not resolve gh-pages HEAD; the Pages deploy would fall back to github.sha and risk a stale no-op."
+            exit 1
+          fi
           echo "sha=$SHA" >> "$GITHUB_OUTPUT"
           echo "gh-pages HEAD: $SHA"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 358 - 368, The gh-pages SHA
capture step in release workflow can silently produce an empty sha when git
ls-remote returns no ref, causing pages deploy to fall back to github.sha.
Update the Capture gh-pages commit for the Pages deploy step to explicitly
validate the SHA before writing GITHUB_OUTPUT, and fail the job with a clear
error if it is missing. Use the existing ghpages_sha step and the SHA lookup
command as the place to add the guard so the release stops instead of deploying
a stale appcast.
Orchard/Services/ContainerListService.swift (1)

455-494: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate reconstruction logic vs recoverContainer, with a subtle divergence.

The env/port/volume-mapping rebuild here (lines 455-480) duplicates recoverContainer (lines 345-369) almost verbatim, and the two have already drifted: this method explicitly sets removeAfterStop: false and commandOverride (lines 486, 491), while recoverContainer's equivalent runConfig (lines 371-381) omits both. Extracting a shared private helper (e.g. func runConfig(from container: Container, excludingMounts: [ContainerMount] = [])) would prevent this kind of divergence going forward.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Orchard/Services/ContainerListService.swift` around lines 455 - 494, The
run-config reconstruction logic here duplicates the same env/port/volume mapping
setup used in recoverContainer and has already diverged on fields like
removeAfterStop and commandOverride. Extract the shared rebuild into a private
helper, referenced from both ContainerListService methods, so the
ContainerRunConfig creation stays consistent and future changes only need to be
made in one place.
Orchard/Services/DNSService.swift (1)

129-150: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Aggregated error omits underlying failure detail, unlike the sibling services.

ImageService.deleteImages and NetworkService.deleteNetworks both track lastError and surface lastError?.localizedDescription in the aggregated alert. deleteDNSDomains drops the underlying error entirely ("Failed to delete \(failedCount) DNS domain(s)"), losing diagnostic detail the other two batch methods provide.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Orchard/Services/DNSService.swift` around lines 129 - 150, The aggregated DNS
delete alert in deleteDNSDomains currently drops the underlying failure detail.
Update DNSService.deleteDNSDomains to track the last encountered error like
ImageService.deleteImages and NetworkService.deleteNetworks, and include
lastError?.localizedDescription in the alertCenter.error message when failures
occur. Keep the existing per-domain loop and counters, but preserve and surface
the most recent error so the batch alert matches the sibling service behavior.
Orchard/Views/Components/SelectionHandler.swift (1)

24-33: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Non-deterministic new anchor after removing the current anchor via Cmd-click.

When the removed item equals lastSelectedId, the fallback selectedSet.first (line 28) picks an arbitrary element since Set has no defined order. This makes the next Shift-click's computed range non-deterministic/inconsistent for the user. Prefer a deterministic pick, e.g. the first remaining item in visual order:

-                if lastSelectedId == clickedId {
-                    lastSelectedId = selectedSet.first
-                }
+                if lastSelectedId == clickedId {
+                    lastSelectedId = orderedIds.first { selectedSet.contains($0) }
+                }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Orchard/Views/Components/SelectionHandler.swift` around lines 24 - 33, The
Cmd-click removal path in SelectionHandler should not assign lastSelectedId from
selectedSet.first because Set ordering is arbitrary and makes the next range
selection unpredictable. Update the selectedSet/remove logic in the
command-click branch so that when clickedId matches lastSelectedId, the new
anchor is chosen deterministically from the remaining items in visual order
using the relevant selection ordering logic in SelectionHandler, rather than
relying on Set iteration order.
Orchard/Views/Features/DNS/MultiDNSCards.swift (1)

118-131: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Delete confirmation logic duplicated from ListDNS.swift.

This inline NSAlert construction repeats the same pattern as confirmDNSDomainsDeletion in ListDNS.swift (and again in MultiMountCards.swift, MultiNetworkCards.swift). Consider extracting a shared confirmation helper (e.g. on SelectionHandler or a small dedicated utility) to avoid drift between the near-identical implementations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Orchard/Views/Features/DNS/MultiDNSCards.swift` around lines 118 - 131, The
delete confirmation flow in this Button action duplicates the same inline
NSAlert logic used elsewhere, so extract it into the shared confirmation helper
already represented by confirmDNSDomainsDeletion in ListDNS.swift or a common
utility/SelectionHandler method, then update MultiDNSCards to call that helper
instead of building the alert locally. Keep the behavior and button titles
identical, and reuse the same shared entry point in the other duplicate sites
like MultiMountCards and MultiNetworkCards to prevent the implementations from
drifting.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Orchard/Services/ContainerListService.swift`:
- Line 448: The container lookup in ContainerListService’s mount-removal loop is
silently skipping missing containers instead of treating them as failures.
Update the branch around containers.first(where:) so that when no matching
container is found, it increments failedCount and records the failure just like
the other error paths before continuing. Keep the fix localized to the
containerId lookup logic in the mount removal flow.
- Around line 446-509: The batch delete/recreate flow in
ContainerListService.deleteMounts is missing the same per-container
serialization used by startContainer and stopContainer, so concurrent operations
can race on the same container ID. Add a containerOperationLocks-based lock
around the backend.deleteContainer and runContainer sequence for each
containerId, using the same lock key pattern as the existing start/stop paths.
Make sure the lock is acquired before deleting, held through the restart
attempt, and released in a defer so failures still unblock later operations.
- Around line 443-513: deleteMounts(_:) is rebuilding the same container once
per mount using a stale oldConfig snapshot from containers, which can
reintroduce mounts when multiple mounts from one container are deleted together.
Update ContainerListService.deleteMounts(_:) to group the incoming
ContainerMounts by containerId, compute a single filtered mounts list per
container, and recreate each affected container once with all selected mounts
removed before calling backend.deleteContainer and runContainer.

In `@Orchard/Services/DNSService.swift`:
- Around line 129-150: Add a defensive guard in DNSService.delete(_:) so the
current default domain cannot be deleted even if called from DNSDetailHeader.
Mirror the existing default-domain filtering used by ListDNS and MultiDNSCards
before delegating to deleteDNSDomains(_:) or running the delete flow, and exit
early with an appropriate alert or no-op when the target matches the default
domain.
- Around line 129-150: The bulk delete flow in DNSService.deleteDNSDomains
currently invokes runWithSudo once per domain, which can trigger repeated admin
prompts. Update deleteDNSDomains to handle the entire domains array in a single
privileged execution by batching the dns delete operations into one command
using runWithSudo and safeContainerBinaryPath, while preserving
deletedCount/failedCount tracking and the existing load() and alertCenter.error
behavior.

In `@Orchard/Views/Features/DNS/ListDNS.swift`:
- Around line 64-69: The delete/copy confirmation text can show domains in a
non-deterministic order because `targetDomains` in `ListDNS` is built from
`Array(selectedDNSDomains)`, and `selectedDNSDomains` is a `Set`. Update the
`targetDomains` construction to produce a stable order by sorting the set before
use, and make sure the `domainsList` shown in the alert uses that ordered array
consistently. Use the `ListDNS` selection/confirmation flow and the
`domainsList` generation near the confirmation alert as the places to fix,
matching the sorted behavior already used in
`MultiDNSCardsView.selectedDNSList`.

In `@Orchard/Views/Features/Images/ListImages.swift`:
- Around line 80-84: The destructive image removal action in ListImages.swift
currently calls imageService.deleteImages(targetIds) immediately from the Button
handler with no confirmation. Add a confirmation flow consistent with
confirmDNSDomainsDeletion, confirmMountsDeletion, and confirmNetworksDeletion by
introducing a confirmImagesDeletion(references:) helper and invoking it before
deletion, so both single-image and bulk context-menu removals require user
approval.

In `@Orchard/Views/Features/Images/MultiImageCards.swift`:
- Around line 128-131: The ImageSummaryCard “Remove” action deletes immediately,
unlike DNSSummaryCard, MountSummaryCard, and NetworkSummaryCard which confirm
first; update the Button("Remove") handler in ImageSummaryCard to present the
same NSAlert confirmation flow before calling imageService.deleteImages. Use the
existing per-card confirmation pattern from the other summary card actions and
only proceed with Task { await imageService.deleteImages([ref]) } if the user
confirms.

In `@Orchard/Views/Layout/Content.swift`:
- Line 48: The isInIntentionalConfigurationMode state in Content is never set,
so the selection-reset guards in Content’s tab and service update flow are dead
code. Update the no-second-column path around TabColumnView.selectTab and the
related selection handling in Content to set this flag while clearing selections
for .registries, .systemLogs, and .dashboard, then clear it after the
intentional reset so subsequent service updates do not repopulate
selectedContainer, selectedImage, or other cleared state; if that flow is not
needed, remove isInIntentionalConfigurationMode and the guarded branches
entirely.

In `@Orchard/Views/Layout/MainInterface.swift`:
- Around line 37-43: The early isConfigurationMode return in MainInterface
should not override tabs that intentionally have no resource title. Update the
title logic in the relevant switch so that "Configuration" is only used as a
fallback for selection-based tabs, and leave the Dashboard, Registries, and
System Logs cases returning their existing blank title. Use the existing symbols
MainInterface and isConfigurationMode, and keep the per-tab cases for
.dashboard, .registries, and .systemLogs reachable before any generic
nil-selection fallback.

---

Outside diff comments:
In `@Orchard/Views/Features/DNS/ListDNS.swift`:
- Around line 91-97: The "Make Default" action in ListDNS.swift restores the
wrong selection state because it writes back to selectedDNSDomain instead of the
List’s live selectedDNSDomains set. Update the Button action in the Make Default
handler to preserve and restore the actual selection binding used by the list,
so after dnsService.setDefault(domain.domain) reloads the data the row remains
visually selected. Use the existing List selection state and the Make Default
Button closure to locate the fix.

In `@Orchard/Views/Layout/DetailContent.swift`:
- Around line 26-57: The DNS and network detail branches in DetailContent.body
still only handle single-item selection, so the multi-selection sets
selectedDNSDomains and selectedNetworks are unused. Update the .dns and
.networks cases to mirror the existing imageDetailView and mountDetailView
pattern by switching to the appropriate multi-card views when multiple items are
selected, and pass through the matching collection parameters expected by
MultiDNSCardsView and MultiNetworkCardsView; keep the existing single-selection
fallback for the one-item case.

---

Nitpick comments:
In @.github/workflows/release.yml:
- Around line 358-368: The gh-pages SHA capture step in release workflow can
silently produce an empty sha when git ls-remote returns no ref, causing pages
deploy to fall back to github.sha. Update the Capture gh-pages commit for the
Pages deploy step to explicitly validate the SHA before writing GITHUB_OUTPUT,
and fail the job with a clear error if it is missing. Use the existing
ghpages_sha step and the SHA lookup command as the place to add the guard so the
release stops instead of deploying a stale appcast.

In `@Orchard/Services/ContainerListService.swift`:
- Around line 455-494: The run-config reconstruction logic here duplicates the
same env/port/volume mapping setup used in recoverContainer and has already
diverged on fields like removeAfterStop and commandOverride. Extract the shared
rebuild into a private helper, referenced from both ContainerListService
methods, so the ContainerRunConfig creation stays consistent and future changes
only need to be made in one place.

In `@Orchard/Services/DNSService.swift`:
- Around line 129-150: The aggregated DNS delete alert in deleteDNSDomains
currently drops the underlying failure detail. Update
DNSService.deleteDNSDomains to track the last encountered error like
ImageService.deleteImages and NetworkService.deleteNetworks, and include
lastError?.localizedDescription in the alertCenter.error message when failures
occur. Keep the existing per-domain loop and counters, but preserve and surface
the most recent error so the batch alert matches the sibling service behavior.

In `@Orchard/Views/Components/SelectionHandler.swift`:
- Around line 24-33: The Cmd-click removal path in SelectionHandler should not
assign lastSelectedId from selectedSet.first because Set ordering is arbitrary
and makes the next range selection unpredictable. Update the selectedSet/remove
logic in the command-click branch so that when clickedId matches lastSelectedId,
the new anchor is chosen deterministically from the remaining items in visual
order using the relevant selection ordering logic in SelectionHandler, rather
than relying on Set iteration order.

In `@Orchard/Views/Features/DNS/MultiDNSCards.swift`:
- Around line 118-131: The delete confirmation flow in this Button action
duplicates the same inline NSAlert logic used elsewhere, so extract it into the
shared confirmation helper already represented by confirmDNSDomainsDeletion in
ListDNS.swift or a common utility/SelectionHandler method, then update
MultiDNSCards to call that helper instead of building the alert locally. Keep
the behavior and button titles identical, and reuse the same shared entry point
in the other duplicate sites like MultiMountCards and MultiNetworkCards to
prevent the implementations from drifting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9713e28e-16f3-4bb3-8fa0-831ce0717944

📥 Commits

Reviewing files that changed from the base of the PR and between 4012df5 and 4ada151.

📒 Files selected for processing (19)
  • .github/workflows/pages.yml
  • .github/workflows/release.yml
  • Orchard/Services/ContainerListService.swift
  • Orchard/Services/DNSService.swift
  • Orchard/Services/ImageService.swift
  • Orchard/Services/NetworkService.swift
  • Orchard/Views/Components/SelectionHandler.swift
  • Orchard/Views/Features/DNS/ListDNS.swift
  • Orchard/Views/Features/DNS/MultiDNSCards.swift
  • Orchard/Views/Features/Images/ListImages.swift
  • Orchard/Views/Features/Images/MultiImageCards.swift
  • Orchard/Views/Features/Mounts/ListMounts.swift
  • Orchard/Views/Features/Mounts/MultiMountCards.swift
  • Orchard/Views/Features/Networks/ListNetworks.swift
  • Orchard/Views/Features/Networks/MultiNetworkCards.swift
  • Orchard/Views/Layout/Content.swift
  • Orchard/Views/Layout/DetailContent.swift
  • Orchard/Views/Layout/MainInterface.swift
  • Orchard/Views/Layout/ThreeColumnLayout.swift

Comment thread Orchard/Services/ContainerListService.swift
Comment thread Orchard/Services/ContainerListService.swift
Comment thread Orchard/Services/ContainerListService.swift Outdated
Comment thread Orchard/Services/DNSService.swift
Comment thread Orchard/Views/Features/DNS/ListDNS.swift
Comment thread Orchard/Views/Features/Images/ListImages.swift
Comment thread Orchard/Views/Features/Images/MultiImageCards.swift
Comment thread Orchard/Views/Layout/Content.swift Outdated
Comment thread Orchard/Views/Layout/MainInterface.swift

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
Orchard/Views/Layout/Content.swift (2)

357-360: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the dead if false placeholders. This block (and the matching one at Lines 373-375 in startRefreshTimer) is unreachable dead code standing in for the "check for updates" feature. Either wire up the update check or drop these stubs to avoid confusion.

Want me to open an issue to track the startup update-check?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Orchard/Views/Layout/Content.swift` around lines 357 - 360, The startup
update-check stub is dead code: the empty if false block in Content.swift and
the matching placeholder in startRefreshTimer are unreachable and should be
removed or replaced with the real update-check logic. Update the relevant logic
in Content to either call the actual update-check flow from the startup path and
timer path, or delete both placeholder branches entirely so there are no fake
"check for updates" stubs left behind.

292-322: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Fragile 0.2s selection delay. The DNS and Network navigation paths schedule selection via DispatchQueue.main.asyncAfter(deadline: .now() + 0.2), which is a timing guess: on slower renders the selection/focus may not stick, and rapid repeated notifications stack independent delayed writes. Prefer driving selection off an observable state change (e.g. once the domain/network appears in the list) rather than a fixed delay.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Orchard/Views/Layout/Content.swift` around lines 292 - 322, The DNS and
Network navigation handlers in Content.swift rely on a fixed 0.2s asyncAfter
delay before setting selectedDNSDomain, selectedNetwork, and listFocusedTab,
which makes the update timing fragile. Replace the delay-based selection in the
NavigateToDNS and NavigateToNetwork Task blocks with state-driven logic that
reacts when dnsService.dnsDomains or networkService.networks actually contains
the target item, and apply the selection/focus immediately from that observable
update path instead of scheduling independent delayed writes.
Orchard/Services/DNSService.swift (1)

150-171: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Bulk-delete failures are opaque. deletedCount is computed but never read, and because the per-domain delete output is discarded (>/dev/null 2>&1) the aggregated alertCenter.error("Failed to delete \(failedCount) DNS domain(s)") cannot tell the user which domains failed or why. Consider echoing the failing domain (and capturing its stderr) so the message is actionable, and drop the unused deletedCount.

The batching itself correctly resolves the earlier per-item admin-prompt concern, and passing domains as positional $@ args avoids shell injection.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Orchard/Services/DNSService.swift` around lines 150 - 171, The bulk-delete
flow in DNSService should make failures actionable instead of only reporting a
count. In the delete path that parses `result.stdout` and calls
`alertCenter.error`, stop discarding per-domain output, capture stderr from the
underlying delete command, and include the specific failed domain(s) plus the
error details in the failure alert. Also remove the unused `deletedCount`
variable from this bulk-delete handling block.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@Orchard/Services/DNSService.swift`:
- Around line 150-171: The bulk-delete flow in DNSService should make failures
actionable instead of only reporting a count. In the delete path that parses
`result.stdout` and calls `alertCenter.error`, stop discarding per-domain
output, capture stderr from the underlying delete command, and include the
specific failed domain(s) plus the error details in the failure alert. Also
remove the unused `deletedCount` variable from this bulk-delete handling block.

In `@Orchard/Views/Layout/Content.swift`:
- Around line 357-360: The startup update-check stub is dead code: the empty if
false block in Content.swift and the matching placeholder in startRefreshTimer
are unreachable and should be removed or replaced with the real update-check
logic. Update the relevant logic in Content to either call the actual
update-check flow from the startup path and timer path, or delete both
placeholder branches entirely so there are no fake "check for updates" stubs
left behind.
- Around line 292-322: The DNS and Network navigation handlers in Content.swift
rely on a fixed 0.2s asyncAfter delay before setting selectedDNSDomain,
selectedNetwork, and listFocusedTab, which makes the update timing fragile.
Replace the delay-based selection in the NavigateToDNS and NavigateToNetwork
Task blocks with state-driven logic that reacts when dnsService.dnsDomains or
networkService.networks actually contains the target item, and apply the
selection/focus immediately from that observable update path instead of
scheduling independent delayed writes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 45983ce8-2873-43b6-b2e0-1e382aef56df

📥 Commits

Reviewing files that changed from the base of the PR and between 4ada151 and 2e9f052.

📒 Files selected for processing (7)
  • Orchard/Services/ContainerListService.swift
  • Orchard/Services/DNSService.swift
  • Orchard/Views/Features/DNS/ListDNS.swift
  • Orchard/Views/Features/Images/ListImages.swift
  • Orchard/Views/Features/Images/MultiImageCards.swift
  • Orchard/Views/Layout/Content.swift
  • Orchard/Views/Layout/MainInterface.swift
🚧 Files skipped from review as they are similar to previous changes (5)
  • Orchard/Views/Features/Images/ListImages.swift
  • Orchard/Views/Layout/MainInterface.swift
  • Orchard/Services/ContainerListService.swift
  • Orchard/Views/Features/Images/MultiImageCards.swift
  • Orchard/Views/Features/DNS/ListDNS.swift

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Orchard/Views/Layout/Content.swift`:
- Around line 139-149: The selection fallback in the Content.swift onChange
handlers can run before the deferred target is recorded, causing the first
DNS/network item to be selected instead of the intended destination. Update the
load(showLoading:) flow and the related onChange logic around
pendingDNSSelection and pendingNetworkSelection so the pending selection is set
before triggering any load that can fire these observers. Make the same
adjustment in the DNS, network, and any similar selection handlers to ensure the
deferred target is always available before the fallback branch runs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3e97f5ea-0d89-46e1-a6d2-294e87781189

📥 Commits

Reviewing files that changed from the base of the PR and between 2e9f052 and 7819e04.

📒 Files selected for processing (2)
  • Orchard/Services/DNSService.swift
  • Orchard/Views/Layout/Content.swift

Comment thread Orchard/Views/Layout/Content.swift
@lynicis

lynicis commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

@andrew-waters, before you merge my PR, could you create a Slack or Discord channel for better communication between the maintainers?

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
putComment timed out

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
Orchard/Views/Layout/Content.swift (2)

129-131: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Redundant, divergent mount auto-selection inside the containers handler.

This block runs on containerListService.containers changes but has no selectedTab == .mounts guard and never updates the plural selectedMounts, unlike the dedicated allMounts handler at Lines 144-154 which does both. Since allMounts is derived from containers, its own onChange already fires and handles this correctly, so this block just eagerly sets selectedMount while the user is on another tab (e.g. .containers at launch). Recommend removing it and relying on the dedicated handler.

♻️ Proposed removal
                 let existingIds = Set(newContainers.map { $0.configuration.id })
                 let pruned = selectedContainers.intersection(existingIds)
                 if pruned != selectedContainers {
                     selectedContainers = pruned
                 }
-                if selectedMount == nil && !containerListService.allMounts.isEmpty {
-                    selectedMount = containerListService.allMounts[0].id
-                }
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Orchard/Views/Layout/Content.swift` around lines 129 - 131, Remove the
redundant selectedMount auto-selection block from the containers-change handler
in ContentView. Rely on the dedicated allMounts change handler to perform mount
selection, including its selectedTab == .mounts guard and selectedMounts
updates.

324-342: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Fragile fixed delay for machine selection/focus.

Setting selectedMachine/listFocusedTab inside DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) relies on an arbitrary timing assumption about when the list has rendered. If rendering is slower, focus can silently fail; if faster, the delay is pointless latency. Consider driving focus off the actual list-population state (e.g. via the existing applyServiceSyncHandlers machine onChange) rather than a magic delay.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Orchard/Views/Layout/Content.swift` around lines 324 - 342, The
NavigateToMachine handler should not use the fixed 0.2-second DispatchQueue
delay to set selectedMachine and listFocusedTab. Coordinate these updates with
the actual machine-list population state, reusing the existing
applyServiceSyncHandlers machine onChange flow so selection and focus occur
after the requested machine is available, while preserving the current
navigation and validation behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@Orchard/Views/Layout/Content.swift`:
- Around line 129-131: Remove the redundant selectedMount auto-selection block
from the containers-change handler in ContentView. Rely on the dedicated
allMounts change handler to perform mount selection, including its selectedTab
== .mounts guard and selectedMounts updates.
- Around line 324-342: The NavigateToMachine handler should not use the fixed
0.2-second DispatchQueue delay to set selectedMachine and listFocusedTab.
Coordinate these updates with the actual machine-list population state, reusing
the existing applyServiceSyncHandlers machine onChange flow so selection and
focus occur after the requested machine is available, while preserving the
current navigation and validation behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 17598822-25f7-48b4-9c5d-13c530e64bae

📥 Commits

Reviewing files that changed from the base of the PR and between f22c79a and 0f246e8.

📒 Files selected for processing (6)
  • Orchard/Services/ContainerListService.swift
  • Orchard/Services/NetworkService.swift
  • Orchard/Views/Layout/Content.swift
  • Orchard/Views/Layout/DetailContent.swift
  • Orchard/Views/Layout/MainInterface.swift
  • Orchard/Views/Layout/ThreeColumnLayout.swift
🚧 Files skipped from review as they are similar to previous changes (5)
  • Orchard/Services/NetworkService.swift
  • Orchard/Views/Layout/MainInterface.swift
  • Orchard/Views/Layout/DetailContent.swift
  • Orchard/Services/ContainerListService.swift
  • Orchard/Views/Layout/ThreeColumnLayout.swift

- Remove redundant selectedMount auto-selection block from the containers-change handler
- Coordinate NavigateToMachine updates with actual machine-list population state using the existing applyServiceSyncHandlers machine onChange flow
- Fix missing properties in DetailColumnView
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.

2 participants