import SwiftUI import NucleicProtocol /// Projects overview (mirrors the Mac's per-project pages): each project with its session /// counts; tap through to that project's sessions + a scoped composer. struct ProjectsView: View { @EnvironmentObject var store: RemoteStore var body: some View { NavigationStack { Group { if store.dashboard.projects.isEmpty { ContentUnavailableView("No projects", systemImage: "folder", description: Text("Add a project on the Mac to see it here.")) } else { List(store.dashboard.projects) { project in NavigationLink { ProjectDetailView(project: project) } label: { HStack(spacing: 12) { Image(systemName: "folder.fill").foregroundStyle(Palette.accent) VStack(alignment: .leading, spacing: 2) { Text(project.name).font(.body.weight(.medium)) Text(project.defaultBranch).font(.caption).foregroundStyle(.secondary) } Spacer() if project.activeCount > 0 { Text("\(project.activeCount) active") .font(.caption2.bold()) .padding(.horizontal, 7).padding(.vertical, 3) .background(Palette.active.opacity(0.18), in: Capsule()) .foregroundStyle(Palette.active) } Text("\(project.sessionCount)").foregroundStyle(.secondary) } } } .compactTabBarInset() } } .navigationTitle("Projects") .refreshable { store.refreshSessions() } } } } struct ProjectDetailView: View { @EnvironmentObject var store: RemoteStore @Environment(\.horizontalSizeClass) private var sizeClass let project: WireProject @State private var showNewChat = false private var sessions: [WireSessionSummary] { store.liveSessions .filter { $0.projectID == project.id.rawValue } .sorted(by: StatusStyle.attentionThenRecency) } var body: some View { List { Section("Sessions") { if sessions.isEmpty { Text("No sessions yet.").foregroundStyle(.secondary) } ForEach(sessions, id: \.sessionID) { summary in NavigationLink { SessionDetailView(sessionID: summary.sessionID) } label: { SessionRow(summary: summary, showProjectName: false) } } } } .navigationTitle(project.name) .navigationBarTitleDisplayMode(.inline) .scrollDismissesKeyboard(.interactively) .compactTabBarInset() // A floating "+" scoped to this project — the glass composer opens pre-locked to it. Only // at regular width; on compact the shared tab-bar "+" (project picker) covers new chats. .newChatAffordance(project: project, isPresented: $showNewChat, enabled: sizeClass == .regular) } }