Commit 1b7270d767
1b7270d7670fde8d62d459d14f5aa8844aca20cf
parent: ab77dbf977
Unsigned
cmc <hello@cleberg.net> · 2026-07-16 02:56 UTC
fix: push patchset views directly instead of by route
Tapping a patch failed with "no matching navigationDestination declaration
visible from the location of the link".
MailingListDetailView is presented from four places, but only the More tab and
Lookup declare a MoreRoute destination. Reached from a project, via
ProjectMailingListView, there is no such destination in the surrounding stack,
so a NavigationLink carrying MoreRoute.patchset had nowhere to resolve. The
thread rows beside it already use the closure form for exactly this reason.
Push PatchsetDetailView directly, from the rows and from the version-chain
links inside the detail view, which inherits whatever stack presented it. That
leaves MoreRoute.patchset with no users, so it and its two destinations are
removed rather than left as a route nothing links to.
Neither the compiler nor the tests catch this: it is a runtime SwiftUI
resolution failure.
Layout: unified · split
Hutch/App/RootView.swift
−3
| @@ -439,7 +439,6 @@ enum MoreRoute: Hashable { |
| 439 | 439 | case projectDashboard(id: String, title: String?) |
| 440 | 440 | case mailingList(InboxMailingListReference) |
| 441 | 441 | case thread(InboxThreadSummary) |
| 442 | | case patchset(id: Int, listName: String?) |
| 443 | 442 | case manPageBrowser |
| 444 | 443 | case manPage(URL) |
| 445 | 444 | } |
| @@ -473,8 +472,6 @@ private struct MoreNavigationRoot: View { |
| 473 | 472 | ProjectDashboardDeepLinkView(projectID: id, title: title) |
| 474 | 473 | case .mailingList(let mailingList): |
| 475 | 474 | MailingListDetailView(mailingList: mailingList) |
| 476 | | case .patchset(let id, let listName): |
| 477 | | PatchsetDetailView(patchsetID: id, listName: listName) |
| 478 | 475 | case .thread(let thread): |
| 479 | 476 | ThreadDetailView( |
| 480 | 477 | thread: thread, |
Hutch/Views/Lookup/LookupView.swift
−2
| @@ -465,8 +465,6 @@ struct LookupView: View { |
| 465 | 465 | ProjectDashboardDeepLinkView(projectID: id, title: title) |
| 466 | 466 | case .mailingList(let mailingList): |
| 467 | 467 | MailingListDetailView(mailingList: mailingList) |
| 468 | | case .patchset(let id, let listName): |
| 469 | | PatchsetDetailView(patchsetID: id, listName: listName) |
| 470 | 468 | case .thread(let thread): |
| 471 | 469 | ThreadDetailView( |
| 472 | 470 | thread: thread, |
Hutch/Views/Patchsets/PatchsetDetailView.swift
+10 −2
| @@ -122,8 +122,14 @@ struct PatchsetDetailView: View { |
| 122 | 122 | |
| 123 | 123 | // The version chain matters during review: a superseded series should |
| 124 | 124 | // usually be read at its newest version instead. |
| 125 | // |
| 126 | // Pushed directly rather than by value, for the same reason as the rows |
| 127 | // that lead here — this view inherits whatever stack presented it, and |
| 128 | // not all of them declare a MoreRoute destination. |
| 125 | 129 | if let supersededBy = patchset.supersededBy { |
| 126 | | NavigationLink(value: MoreRoute.patchset(id: supersededBy, listName: listName)) { |
| 130 | NavigationLink { |
| 131 | PatchsetDetailView(patchsetID: supersededBy, listName: listName) |
| 132 | } label: { |
| 127 | 133 | SwiftUI.Label("Superseded by a newer version", systemImage: "arrow.right.circle") |
| 128 | 134 | .font(.subheadline) |
| 129 | 135 | } |
| @@ -131,7 +137,9 @@ struct PatchsetDetailView: View { |
| 131 | 137 | } |
| 132 | 138 | |
| 133 | 139 | if let supersedes = patchset.supersedes { |
| 134 | | NavigationLink(value: MoreRoute.patchset(id: supersedes, listName: listName)) { |
| 140 | NavigationLink { |
| 141 | PatchsetDetailView(patchsetID: supersedes, listName: listName) |
| 142 | } label: { |
| 135 | 143 | SwiftUI.Label("Revises an earlier version", systemImage: "arrow.left.circle") |
| 136 | 144 | .font(.subheadline) |
| 137 | 145 | } |
Hutch/Views/Projects/ProjectMailingListView.swift
+5 −1
| @@ -420,7 +420,11 @@ struct MailingListDetailView: View { |
| 420 | 420 | |
| 421 | 421 | if showingPatches(viewModel) { |
| 422 | 422 | ForEach(viewModel.filteredPatchsets) { patchset in |
| 423 | | NavigationLink(value: MoreRoute.patchset(id: patchset.id, listName: mailingList.name)) { |
| 423 | // Pushed directly rather than by value: this view is also shown |
| 424 | // from a project, whose stack declares no MoreRoute destination. |
| 425 | NavigationLink { |
| 426 | PatchsetDetailView(patchsetID: patchset.id, listName: mailingList.name) |
| 427 | } label: { |
| 424 | 428 | PatchsetRow(patchset: patchset) |
| 425 | 429 | } |
| 426 | 430 | .themedRow() |