The tab bar retires — boards are the app, settings behind the gear, recency the default order

The boards stack becomes the root screen: Settings moves from its own
tab to a sheet behind a leading gearshape button, the sort pickers trade
their text segments for symbols (hand.draw, textformat.abc, clock) with
the words kept for accessibility, and every list now opens sorted by
most recent change. UI tests re-aimed at the gear button and Done-button
scoping fixed for the sheet-over-sheet nav bars — 4/4 green in the
simulator.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-08-08 13:07:44 -04:00
parent 53be63bb03
commit 871083e5ca
9 changed files with 65 additions and 48 deletions
+20 -6
View File
@@ -1,7 +1,8 @@
import SwiftUI
/// The root of the boards navigation stack: board list lanes cards card detail
/// (`BoardRoute`'s destinations).
/// (`BoardRoute`'s destinations) and, now that the tab bar is gone, the app's root screen.
/// Settings is presented from the leading gear button rather than a tab of its own.
///
/// Renders both of `BoardIndexStore.Phase`'s live states and, above the list, the iCloud notice
/// which is a row, not a wall (softened 2026-08-08). A phone with no account still has a device home
@@ -12,10 +13,11 @@ struct BoardsTabView: View {
@Environment(BoardIndexStore.self) private var index
@State private var isPresentingNewBoard = false
@State private var isPresentingSettings = false
/// Persisted as its raw value, not the enum itself `AppStorage` needs a property-list type, and
/// the raw `String` is exactly what `BoardSortOrder` promises to keep stable.
@AppStorage("boardListSortOrder") private var sortOrderRaw = BoardSortOrder.name.rawValue
@AppStorage("boardListSortOrder") private var sortOrderRaw = BoardSortOrder.recent.rawValue
/// The board whose settings sheet is up. A value, not a URL: the sheet renders the row's own
/// summary, and a successful move dismisses it before the stale copy could matter.
@@ -25,6 +27,15 @@ struct BoardsTabView: View {
NavigationStack {
content
.navigationTitle("Boards")
.toolbar {
// Unconditional settings (About, license, changelog) must stay reachable
// even while the index is still loading or the board list is empty.
ToolbarItem(placement: .topBarLeading) {
Button("Settings", systemImage: "gearshape") {
isPresentingSettings = true
}
}
}
.toolbar {
if case .ready = index.phase {
Button("New Board", systemImage: "plus") {
@@ -61,13 +72,16 @@ struct BoardsTabView: View {
.sheet(item: $boardInSettings) { board in
BoardSettingsSheet(board: board)
}
.sheet(isPresented: $isPresentingSettings) {
SettingsScreen()
}
}
/// The stored raw value as the enum, defaulting to `.name` on anything the store didn't write
/// The stored raw value as the enum, defaulting to `.recent` on anything the store didn't write
/// itself an unset key or a stale raw value from a build that no longer has this case.
private var sortOrder: Binding<BoardSortOrder> {
Binding(
get: { BoardSortOrder(rawValue: sortOrderRaw) ?? .name },
get: { BoardSortOrder(rawValue: sortOrderRaw) ?? .recent },
set: { sortOrderRaw = $0.rawValue }
)
}
@@ -83,8 +97,8 @@ struct BoardsTabView: View {
if case .ready = index.phase, !index.boards.isEmpty {
ToolbarItem(placement: .principal) {
Picker("Sort", selection: sortOrder) {
Text("Name").tag(BoardSortOrder.name)
Text("Recent").tag(BoardSortOrder.recent)
Label("Name", systemImage: "textformat.abc").tag(BoardSortOrder.name)
Label("Recent", systemImage: "clock").tag(BoardSortOrder.recent)
}
.pickerStyle(.segmented)
.frame(maxWidth: 260)