Redesign Hope iOS UI, harden playback, and document project handoff

This commit is contained in:
snowspeeder committed 2026-09-09 20:23:44 -04:00
1 parent 40aaf4fea7
commit 24f95639dc
24 files changed
+1470 -594

No files matched your search

+47 -43
View File
@@ -5,10 +5,12 @@ struct TaskPanelView: View {
var body: some View {
ScrollView {
VStack(spacing: 12) {
LazyVStack(alignment: .leading, spacing: 20) {
if taskStore.sortedTasks.isEmpty {
emptyState
} else {
Text(taskStore.activeCount > 0 ? "Hope is working on \(taskStore.activeCount) task\(taskStore.activeCount == 1 ? "" : "s"). You can keep talking." : "Results from the things you’ve asked Hope to work on.")
.font(.subheadline).foregroundStyle(.secondary)
ForEach(taskStore.sortedTasks) { task in
TaskCardView(task: task)
}
@@ -16,57 +18,48 @@ struct TaskPanelView: View {
}
.padding()
}
.navigationTitle("Background tasks")
#if DEBUG
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Sample", action: { taskStore.injectSampleEvents() })
.font(.caption)
}
}
#endif
.navigationTitle("Activity")
.background(Color(.systemGroupedBackground))
}
private var emptyState: some View {
VStack(spacing: 8) {
Text("Nothing running")
.font(.headline)
Text("Ask Hope to research or compare something.")
.font(.subheadline)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
ContentUnavailableView {
Label("A little help, in the background", systemImage: "checklist")
} description: {
Text("Ask Hope to research a topic or compare your options. Follow the progress and read the results here.")
}
.frame(maxWidth: .infinity)
.padding(.vertical, 48)
.padding(.top, 48)
}
}
struct TaskCardView: View {
let task: TaskItem
@State private var showSteps = false
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 8) {
statusDot
Text(task.description)
.font(.subheadline.weight(.semibold))
.lineLimit(2)
VStack(alignment: .leading, spacing: 14) {
HStack {
Label(statusTitle, systemImage: statusSymbol)
.font(.subheadline.weight(.medium))
.foregroundStyle(dotColor)
Spacer()
Text(task.createdAt, style: .time).font(.caption).foregroundStyle(.secondary)
}
Text(task.description).font(.headline).fixedSize(horizontal: false, vertical: true)
if !task.steps.isEmpty {
VStack(alignment: .leading, spacing: 4) {
DisclosureGroup("\(task.steps.count) progress updates", isExpanded: $showSteps) {
VStack(alignment: .leading, spacing: 12) {
ForEach(task.steps) { step in
HStack(alignment: .top, spacing: 6) {
VStack(alignment: .leading, spacing: 4) {
Text(stepLabel(step.role))
.font(.caption2.weight(.semibold))
.foregroundStyle(stepColor(step.role))
.font(.caption.weight(.semibold)).foregroundStyle(.secondary)
Text(step.content ?? "")
.font(.caption)
.foregroundStyle(stepColor(step.role))
.lineLimit(3)
.font(.subheadline).textSelection(.enabled)
}
}
}.padding(.top, 8)
}
.font(.subheadline)
}
if let result = task.result {
block(result, color: .green, background: Color.green.opacity(0.12))
@@ -75,21 +68,33 @@ struct TaskCardView: View {
block(error, color: .red, background: Color.red.opacity(0.12))
}
}
.padding(12)
.padding(16)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color(.secondarySystemBackground), in: RoundedRectangle(cornerRadius: 14))
.background(Color(.secondarySystemGroupedBackground), in: RoundedRectangle(cornerRadius: 16))
}
private var statusDot: some View {
Circle()
.fill(dotColor)
.frame(width: 9, height: 9)
private var statusTitle: String {
switch task.status {
case .pending: "Queued"
case .running: "In progress"
case .completed: "Complete"
case .failed: "Needs attention"
}
}
private var statusSymbol: String {
switch task.status {
case .pending: "clock"
case .running: "arrow.triangle.2.circlepath"
case .completed: "checkmark.circle.fill"
case .failed: "exclamationmark.circle"
}
}
private var dotColor: Color {
switch task.status {
case .pending: return .gray
case .running: return .blue
case .running: return HopeStyle.tint
case .completed: return .green
case .failed: return .red
}
@@ -97,11 +102,10 @@ struct TaskCardView: View {
private func block(_ text: String, color: Color, background: Color) -> some View {
Text(text)
.font(.subheadline)
.foregroundStyle(color)
.font(.body)
.foregroundStyle(.primary)
.textSelection(.enabled)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(10)
.background(background, in: RoundedRectangle(cornerRadius: 10))
}
private func stepLabel(_ role: String?) -> String {