Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions ios/TaskWraithKit/Sources/TaskWraithUI/AppShell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ public struct RootView: View {
// Theme tokens are computed statics — a revision bump rebuilds the
// tree so every TWTheme read picks up the new selection.
.id(themes.revision)
// Settings sheet lives ABOVE the revision teardown (attached after
// `.id`) so a theme/composer/font change made inside it keeps the sheet
// open — AppSettingsSheet re-themes live via its own @ObservedObject
// themes. Presented here, not from HomeView, which is inside the
// `.id(revision)` subtree that rebuilds on every settings change.
.sheet(isPresented: $model.settingsPresented) {
AppSettingsSheet()
.presentationDetents([.medium, .large])
.presentationDragIndicator(.visible)
}
.animation(.easeInOut(duration: 0.25), value: showShellDuringDrop)
// Privacy shield: iOS snapshots the UI for the app switcher —
// transcripts and file contents must not be readable there.
Expand Down Expand Up @@ -176,7 +186,6 @@ struct MastheadRow: View {
struct ConnectedShell: View {
@ObservedObject var model: RemoteSessionModel
@Environment(\.horizontalSizeClass) private var sizeClass
@State private var selectedTaskId: String?
@StateObject private var fileState = MobileFileEditorState()
@StateObject private var diffState = MobileDiffStudioState()
@State private var shellMode: ShellMode = .app
Expand Down Expand Up @@ -232,11 +241,11 @@ struct ConnectedShell: View {
}
} else {
NavigationSplitView {
HomeView(model: model, selection: $selectedTaskId, explicitSelection: true)
HomeView(model: model, selection: $model.selectedTaskId, explicitSelection: true)
.navigationSplitViewColumnWidth(min: 300, ideal: 340)
.iPadSidebarInnerRim(edge: .trailing)
} detail: {
if let taskId = selectedTaskId, taskId.hasPrefix("new-") {
if let taskId = model.selectedTaskId, taskId.hasPrefix("new-") {
NavigationStack {
NewChatBootstrapView(
model: model,
Expand All @@ -247,7 +256,7 @@ struct ConnectedShell: View {
? String(taskId.split(separator: ":")[1]) : nil)
}
.id(taskId)
} else if let taskId = selectedTaskId {
} else if let taskId = model.selectedTaskId {
// Hand-rolled third column: SwiftUI's `.inspector`
// presents as an overlay here regardless of attach
// level (tried both); an HStack pane DETERMINISTICALLY
Expand All @@ -259,7 +268,7 @@ struct ConnectedShell: View {
if model.inspectorPresented {
ThreadInspector(model: model, threadId: taskId) { childId in
model.inspectorPresented = false
selectedTaskId = childId
model.selectedTaskId = childId
}
.frame(width: 390)
.background(TWTheme.appBg)
Expand All @@ -281,8 +290,8 @@ struct ConnectedShell: View {
}
} else {
NavigationStack {
HomeView(model: model, selection: $selectedTaskId)
.navigationDestination(for: String.self) { taskId in
HomeView(model: model, selection: $model.selectedTaskId)
.navigationDestination(item: $model.selectedTaskId) { taskId in
ThreadDetailView(model: model, taskId: taskId)
// Compact: the same binding presents as a sheet.
.inspector(isPresented: $model.inspectorPresented) {
Expand Down
41 changes: 28 additions & 13 deletions ios/TaskWraithKit/Sources/TaskWraithUI/HomeListViews.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ import TaskWraithKit
struct HomeView: View {
@ObservedObject var model: RemoteSessionModel
@Binding var selection: String?
/// Parent chats whose sub-thread/side-chat children are collapsed.
@State private var collapsedParents: Set<String> = []
@State private var showSettings = false
/// Parent chats whose sub-thread/side-chat children are collapsed. Backed by
/// the model so it survives the theme-revision teardown (see selectedTaskId).
private var collapsedParents: Set<String> {
get { model.collapsedParents }
nonmutating set { model.collapsedParents = newValue }
}
@State private var canvasMode: ComposeMode? = nil

private func openCanvas(_ mode: ComposeMode) {
Expand All @@ -40,10 +43,16 @@ struct HomeView: View {
/// Workspace folders the user has EXPANDED — inverted from the old
/// collapsed-set so folders start collapsed (a tidy first open; expand
/// state then sticks for the session).
@State private var expandedWorkspaces: Set<String> = []
private var expandedWorkspaces: Set<String> {
get { model.expandedWorkspaces }
nonmutating set { model.expandedWorkspaces = newValue }
}
/// Top-level sections (activeRuns / pinned / recents / workspaces /
/// globalChats) the user has collapsed — sections start expanded.
@State private var collapsedSections: Set<String> = []
private var collapsedSections: Set<String> {
get { model.collapsedSections }
nonmutating set { model.collapsedSections = newValue }
}

/// Top-level threads per workspace; sub-threads/side chats nest under
/// their parent like the desktop sidebar.
Expand Down Expand Up @@ -112,7 +121,7 @@ struct HomeView: View {
Image(systemName: "arrow.clockwise")
}
Button {
showSettings = true
model.settingsPresented = true
} label: {
Image(systemName: "gearshape")
}
Expand All @@ -128,11 +137,6 @@ struct HomeView: View {
.navigationDestination(item: $canvasMode) { mode in
NewChatBootstrapView(model: model, mode: mode, initialWorkspaceId: nil)
}
.sheet(isPresented: $showSettings) {
AppSettingsSheet()
.presentationDetents([.medium, .large])
.presentationDragIndicator(.visible)
}
}

@ViewBuilder
Expand Down Expand Up @@ -488,9 +492,20 @@ struct HomeView: View {
.listRowSeparator(.hidden)
.listRowBackground(selectedChrome)
} else {
NavigationLink(value: card.id) {
TaskRow(model: model, card: card, nested: nested)
// Compact: selection-driven push via `navigationDestination(item:)`
// (not NavigationLink) so the open chat is restorable from the
// model after the theme-revision teardown.
Button {
selection = card.id
} label: {
HStack(spacing: 6) {
TaskRow(model: model, card: card, nested: nested)
Image(systemName: "chevron.right")
.font(.caption2)
.foregroundStyle(TWTheme.textMuted)
}
}
.buttonStyle(.plain)
.listRowInsets(rowInsets)
.listRowSeparator(.hidden)
.listRowBackground(rowChrome)
Expand Down
16 changes: 16 additions & 0 deletions ios/TaskWraithKit/Sources/TaskWraithUI/RemoteSessionModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,22 @@ public final class RemoteSessionModel: ObservableObject {
@Published public private(set) var lastActionMessage: String?
/// Set after createThread succeeds — HomeView navigates to the new chat.
@Published public var navigationTarget: String?
/// The chat the user has open (sidebar selection / pushed thread), plus the
/// sidebar's expand/collapse layout. Hoisted onto the model so they SURVIVE
/// the theme-revision view teardown: TWThemeStore bumps `revision` on any
/// settings change and RootView keys `.id(revision)` (TWTheme tokens are
/// computed statics, so the rebuild is how they re-read) — which would
/// otherwise drop the open chat + reset the sidebar. `selectedTaskId` drives
/// the iPad detail column and the iPhone `navigationDestination(item:)`.
@Published public var selectedTaskId: String?
@Published public var expandedWorkspaces: Set<String> = []
@Published public var collapsedSections: Set<String> = []
@Published public var collapsedParents: Set<String> = []
/// Settings sheet presentation — hoisted so a theme/composer/font change
/// made inside it doesn't tear the sheet down with the rest of the tree.
/// Presented from RootView (above the `.id(revision)` boundary); the sheet
/// re-themes live via its own `@ObservedObject themes`.
@Published public var settingsPresented = false
/// Deep-link target captured from a notification tap before the session is
/// established (cold launch); applied to navigationTarget on `.established`.
private var pendingDeepLinkThreadId: String?
Expand Down
Loading