The sort control leaves the list — three pickers settle into the bottom bar

Same segments, same persistence; the picker now rides each screen's
toolbar as a bottom-bar item shown only when there are rows to reorder,
instead of masquerading as the first row of the list it sorts.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-08-08 12:42:44 -04:00
parent e8791b239d
commit 49b2f4db6e
4 changed files with 60 additions and 30 deletions
+20 -10
View File
@@ -5,7 +5,7 @@ import SwiftUI
/// Holds only `boardRoot`, never a `BoardSummary`/`BoardModel` value: the session and its
/// snapshot are pulled from the environment's index store fresh on every body evaluation, so a
/// write from anywhere in the stack (a lane created, a card moved) reaches this screen the moment
/// the session's reload lands. A segmented control above the lane rows lets the list be sorted by
/// the session's reload lands. A segmented control in the toolbar lets the list be sorted by
/// name or by most recent change, on top of the board's own manual arrangement, remembered across
/// launches.
struct BoardScreen: View {
@@ -39,6 +39,9 @@ struct BoardScreen: View {
}
}
}
.toolbar {
sortToolbarItem
}
.task { session.open() }
.onDisappear {
// Stops the materializing/downloading retry timer. In practice a no-op here: a
@@ -73,6 +76,22 @@ struct BoardScreen: View {
return title
}
/// Shown only once there is something to sort an empty or not-yet-loaded lane list has no
/// rows for the segments to reorder.
@ToolbarContentBuilder
private var sortToolbarItem: some ToolbarContent {
if case .ready = session.phase, let snapshot = session.snapshot, !snapshot.lanes.isEmpty {
ToolbarItem(placement: .bottomBar) {
Picker("Sort", selection: sortOrder) {
Text("Manual").tag(ItemSortOrder.manual)
Text("Name").tag(ItemSortOrder.name)
Text("Recent").tag(ItemSortOrder.recent)
}
.pickerStyle(.segmented)
}
}
}
@ViewBuilder
private var content: some View {
switch session.phase {
@@ -123,15 +142,6 @@ struct BoardScreen: View {
description: Text("Add a lane to start organizing cards.")
)
} else {
Picker("Sort", selection: sortOrder) {
Text("Manual").tag(ItemSortOrder.manual)
Text("Name").tag(ItemSortOrder.name)
Text("Recent").tag(ItemSortOrder.recent)
}
.pickerStyle(.segmented)
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
// The trusted order is `.manual`'s base `BoardModel.lanes` is already in display
// order (the loader's `Ranks.sortedForDisplay`) and `.name`/`.recent` layer a
// display-only re-sort on top, never touching that base or what's on disk.