iPad: adaptive sidebar+detail shell and Mac-style diff (remote Phases 1-2)
Grow the universal NucleicRemote app into a width-adaptive shell so a regular-width iPad renders the macOS sidebar+detail IA while the iPhone keeps its TabView -- both over the same RemoteStore projection (one host authority, N renderers). Phase 1 (adaptive shell): - AdaptiveRootView branches on horizontalSizeClass: CompactRootView (the existing iPhone TabView, moved verbatim) vs SplitRootView (NavigationSplitView) on regular width. - SplitSidebar: Home/Projects/To-dos/Settings destinations + sessions grouped under their projects (attention-sorted), connection chip footer. - SplitDetail selects a destination or a session; a selected session reuses SessionDetailView keyed .id(sessionID) so switching drives open/close. - RemoteStore.closeOpen(_:) is now id-guarded so a split-view A->B switch (onAppear(B) before onDisappear(A)) can't tear down B's fresh subscription. - IdentityStore.deviceID idiom-tags the prefix (ipad-/iphone-) for new installs so the host lists a paired iPad correctly. Phase 2 (width tuning + diff): - readableColumn() caps+centers Home and the transcript on wide layouts; a no-op at phone/portrait width. - SessionDiffView switches on available width (GeometryReader): a Mac-style two-pane diff (file list + selected file's patch) on wide/landscape, the phone stack otherwise. UnifiedPatch splits the combined patch per file. Read-only, same wire, no protocol change. - Demo diff fixture now carries both files' patches. iPhone layout and behavior unchanged. Builds clean; verified in the iPad simulator (demo mode). Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
@@ -36,8 +36,6 @@ struct NucleicRemoteApp: App {
|
||||
|
||||
struct RootView: View {
|
||||
@EnvironmentObject var store: RemoteStore
|
||||
// Initial tab; overridable via NUCLEIC_TAB for offline UI previews.
|
||||
@State private var tab = Int(ProcessInfo.processInfo.environment["NUCLEIC_TAB"] ?? "") ?? 0
|
||||
// Appearance preferences — same storage keys and semantics as the Mac's Settings →
|
||||
// Appearance, so both devices honor the same choices.
|
||||
@AppStorage(AppAppearance.storageKey) private var appearanceRaw = AppAppearance.system.rawValue
|
||||
@@ -52,30 +50,15 @@ struct RootView: View {
|
||||
DemoBanner()
|
||||
Group {
|
||||
if store.isPaired {
|
||||
TabView(selection: $tab) {
|
||||
HomeView()
|
||||
.tabItem { Label("Home", systemImage: "house") }.tag(0)
|
||||
SessionsView()
|
||||
.tabItem { Label("Sessions", systemImage: "square.stack.3d.up") }
|
||||
.badge(store.needsYouCount).tag(1)
|
||||
ProjectsView()
|
||||
.tabItem { Label("Projects", systemImage: "folder") }.tag(2)
|
||||
TodosView()
|
||||
.tabItem { Label("To-dos", systemImage: "checklist") }.tag(3)
|
||||
SettingsView()
|
||||
.tabItem { Label("Settings", systemImage: "gearshape") }.tag(4)
|
||||
}
|
||||
// Width-adaptive shell: the iPhone's TabView on compact width, the Mac's
|
||||
// sidebar+detail split on a regular-width iPad (see AdaptiveRootView).
|
||||
AdaptiveRootView()
|
||||
} else {
|
||||
PairingIntroView()
|
||||
}
|
||||
}
|
||||
}
|
||||
.overlay(alignment: .bottom) { ErrorBubble() }
|
||||
// A notification tap routes to its session: jump to the Sessions tab, where
|
||||
// `SessionsView` consumes `pendingRoute` and pushes the detail.
|
||||
.onChange(of: store.pendingRoute) { _, route in
|
||||
if route != nil { tab = 1 }
|
||||
}
|
||||
.tint(Palette.accent)
|
||||
.preferredColorScheme((AppAppearance(rawValue: appearanceRaw) ?? .system).colorScheme)
|
||||
.dynamicTypeSize((AppTextSize(rawValue: textSizeRaw) ?? .medium).dynamicTypeSize)
|
||||
|
||||
Reference in New Issue
Block a user