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 case projectDashboard(id: String, title: String?) 439 case projectDashboard(id: String, title: String?)
440 case mailingList(InboxMailingListReference) 440 case mailingList(InboxMailingListReference)
441 case thread(InboxThreadSummary) 441 case thread(InboxThreadSummary)
442 case patchset(id: Int, listName: String?)
443 case manPageBrowser 442 case manPageBrowser
444 case manPage(URL) 443 case manPage(URL)
445} 444}
@@ -473,8 +472,6 @@ private struct MoreNavigationRoot: View {
473 ProjectDashboardDeepLinkView(projectID: id, title: title) 472 ProjectDashboardDeepLinkView(projectID: id, title: title)
474 case .mailingList(let mailingList): 473 case .mailingList(let mailingList):
475 MailingListDetailView(mailingList: mailingList) 474 MailingListDetailView(mailingList: mailingList)
476 case .patchset(let id, let listName):
477 PatchsetDetailView(patchsetID: id, listName: listName)
478 case .thread(let thread): 475 case .thread(let thread):
479 ThreadDetailView( 476 ThreadDetailView(
480 thread: thread, 477 thread: thread,
Hutch/Views/Lookup/LookupView.swift −2
@@ -465,8 +465,6 @@ struct LookupView: View {
465 ProjectDashboardDeepLinkView(projectID: id, title: title) 465 ProjectDashboardDeepLinkView(projectID: id, title: title)
466 case .mailingList(let mailingList): 466 case .mailingList(let mailingList):
467 MailingListDetailView(mailingList: mailingList) 467 MailingListDetailView(mailingList: mailingList)
468 case .patchset(let id, let listName):
469 PatchsetDetailView(patchsetID: id, listName: listName)
470 case .thread(let thread): 468 case .thread(let thread):
471 ThreadDetailView( 469 ThreadDetailView(
472 thread: thread, 470 thread: thread,
Hutch/Views/Patchsets/PatchsetDetailView.swift +10 −2
@@ -122,8 +122,14 @@ struct PatchsetDetailView: View {
122 122
123 // The version chain matters during review: a superseded series should 123 // The version chain matters during review: a superseded series should
124 // usually be read at its newest version instead. 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 if let supersededBy = patchset.supersededBy { 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 SwiftUI.Label("Superseded by a newer version", systemImage: "arrow.right.circle") 133 SwiftUI.Label("Superseded by a newer version", systemImage: "arrow.right.circle")
128 .font(.subheadline) 134 .font(.subheadline)
129 } 135 }
@@ -131,7 +137,9 @@ struct PatchsetDetailView: View {
131 } 137 }
132 138
133 if let supersedes = patchset.supersedes { 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 SwiftUI.Label("Revises an earlier version", systemImage: "arrow.left.circle") 143 SwiftUI.Label("Revises an earlier version", systemImage: "arrow.left.circle")
136 .font(.subheadline) 144 .font(.subheadline)
137 } 145 }
Hutch/Views/Projects/ProjectMailingListView.swift +5 −1
@@ -420,7 +420,11 @@ struct MailingListDetailView: View {
420 420
421 if showingPatches(viewModel) { 421 if showingPatches(viewModel) {
422 ForEach(viewModel.filteredPatchsets) { patchset in 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 PatchsetRow(patchset: patchset) 428 PatchsetRow(patchset: patchset)
425 } 429 }
426 .themedRow() 430 .themedRow()