default to Inbox perspective, show GTD item detail, reduce toolbar

- default to Inbox perspective (not INBOX mailbox) after first sync
- selectItemForDetail bridges GTD item selection to detail pane by
  creating a synthetic ThreadSummary for email items
- reduce GTD toolbar from 5 to 3 buttons (defer, complete, discard),
  someday/project remain accessible via keyboard shortcuts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 12:56:19 +01:00
parent 3c21a4c461
commit c4374b42ea
2 changed files with 27 additions and 21 deletions

View File

@@ -194,6 +194,27 @@ final class MailViewModel {
startObservingThreads(accountId: accountConfig.id, mailboxId: mailbox.id) startObservingThreads(accountId: accountConfig.id, mailboxId: mailbox.id)
} }
func selectItemForDetail(_ item: ItemSummary?) {
guard let item else {
selectedThread = nil
messages = []
return
}
switch item {
case .email(let msg):
// Show the message directly in the detail pane
selectedThread = ThreadSummary(
id: msg.id, accountId: accountConfig?.id ?? "", subject: msg.subject,
lastDate: msg.date, messageCount: 1, unreadCount: 0,
senders: msg.from?.displayName ?? "", snippet: msg.snippet
)
messages = [msg]
case .task:
selectedThread = nil
messages = []
}
}
func selectPerspective(_ perspective: Perspective) { func selectPerspective(_ perspective: Perspective) {
selectedMailbox = nil selectedMailbox = nil
selectedThread = nil selectedThread = nil
@@ -238,6 +259,7 @@ final class MailViewModel {
} }
private var isSyncing = false private var isSyncing = false
private var hasLoadedInitialPerspective = false
func syncNow() async { func syncNow() async {
guard let coordinator, !isSyncing else { return } guard let coordinator, !isSyncing else { return }
@@ -249,9 +271,10 @@ final class MailViewModel {
// Reload mailboxes after sync (they may have been created on first sync) // Reload mailboxes after sync (they may have been created on first sync)
if let accountConfig { if let accountConfig {
await loadMailboxes(accountId: accountConfig.id) await loadMailboxes(accountId: accountConfig.id)
// On first sync, default to INBOX view // On first sync, load Inbox perspective
if selectedMailbox == nil, let inbox = mailboxes.first(where: { $0.name.lowercased() == "inbox" }) { if !hasLoadedInitialPerspective {
selectMailbox(inbox) hasLoadedInitialPerspective = true
selectPerspective(.inbox)
} }
} }
} catch { } catch {

View File

@@ -56,6 +56,7 @@ struct ThreadListView: View {
get: { viewModel.selectedItem?.id }, get: { viewModel.selectedItem?.id },
set: { newId in set: { newId in
viewModel.selectedItem = viewModel.items.first { $0.id == newId } viewModel.selectedItem = viewModel.items.first { $0.id == newId }
viewModel.selectItemForDetail(viewModel.selectedItem)
} }
)) { item in )) { item in
ItemRow(item: item) ItemRow(item: item)
@@ -176,24 +177,6 @@ struct ThreadListView: View {
.keyboardShortcut("d", modifiers: []) .keyboardShortcut("d", modifiers: [])
.help("Defer (d)") .help("Defer (d)")
Button {
if let item = selectedItemSummary {
viewModel.deferItem(item, until: nil)
}
} label: {
Label("Someday", systemImage: "moon.zzz")
}
.keyboardShortcut("d", modifiers: [.shift])
.help("Someday (⇧D)")
Button {
showLabelPicker = true
} label: {
Label("Project", systemImage: "folder.badge.plus")
}
.keyboardShortcut("p", modifiers: [])
.help("File to Project (p)")
Button { Button {
if let item = selectedItemSummary { if let item = selectedItemSummary {
viewModel.completeItem(item) viewModel.completeItem(item)