M4+: mirror the desktop design language onto mobile (Home/Projects/To-Dos/Sessions)
Design system: Palette (teal accent + the Mac's categorical status colors, status→color refined by turn disposition), StatusStyle glyphs/labels, a card surface modifier, app-wide teal tint. Fixes the reported bug: Sessions now hides archived chats and uses disposition to split NEEDS YOU from DONE (a finished-the-work turn no longer shows as needing you). Rows gain status dots, favorite star, approval-count badge, diffstat; swipe to favorite/archive/delete. New surfaces, full control-scope parity with the Mac: - Home: greeting + day-streak flame, GitHub-style activity grid, stat cards, quick to-dos, and a start-chat composer (project picker + Auto). - Projects: per-project session counts → project detail with a scoped composer. - To-dos: capture, group by project, complete/dispatch/delete. - Session detail: a control menu (rename, favorite, interrupt, integrate merge/squash/rebase, archive, delete). RemoteStore handles the dashboard event + exposes all control intents; claims control scope. A NUCLEIC_DEMO/NUCLEIC_TAB env seam seeds mock state for offline UI preview. iOS app builds for the iOS 27 simulator; Home + Sessions verified rendering via screenshots. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Projects")
|
||||
.refreshable { store.refreshSessions() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ProjectDetailView: View {
|
||||
@EnvironmentObject var store: RemoteStore
|
||||
let project: WireProject
|
||||
@State private var draft = ""
|
||||
|
||||
private var sessions: [WireSessionSummary] {
|
||||
store.liveSessions
|
||||
.filter { $0.projectID == project.id.rawValue }
|
||||
.sorted { StatusStyle.sortRank($0) < StatusStyle.sortRank($1) }
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section {
|
||||
HStack(alignment: .bottom, spacing: 8) {
|
||||
TextField("Start a chat in \(project.name)…", text: $draft, axis: .vertical)
|
||||
.textFieldStyle(.roundedBorder).lineLimit(1...4)
|
||||
Button {
|
||||
store.startChat(in: project.id, message: draft)
|
||||
draft = ""
|
||||
} label: { Image(systemName: "arrow.up.circle.fill").font(.title2) }
|
||||
.disabled(draft.trimmingCharacters(in: .whitespaces).isEmpty || !store.canControl)
|
||||
}
|
||||
}
|
||||
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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(project.name)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user