← Files Career Command CenterARCHIVED FILE
assets/macos-app/Sources/LeadWorkspaceView.swift
28.8 KB · Oct 2, 2026 · 00:29 UTC
import SwiftUI
struct MainView: View {
@ObservedObject var store: AppStore
var body: some View {
HStack(spacing: 0) {
SidebarView(store: store)
.frame(width: 224)
Divider()
content
}
.frame(minWidth: 1200, minHeight: 720)
.background(AppTheme.canvas)
.overlay(alignment: .topTrailing) {
if !store.toastMessage.isEmpty {
Label(store.toastMessage, systemImage: "checkmark.circle.fill")
.font(.system(size: 12, weight: .semibold))
.foregroundStyle(.white)
.padding(.horizontal, 13)
.frame(height: 36)
.background(AppTheme.ink.opacity(0.92), in: RoundedRectangle(cornerRadius: 6))
.shadow(color: .black.opacity(0.15), radius: 10, y: 4)
.padding(18)
.transition(.move(edge: .top).combined(with: .opacity))
}
}
.animation(.easeOut(duration: 0.18), value: store.toastMessage)
.alert("Career Command Center", isPresented: Binding(
get: { !store.errorMessage.isEmpty },
set: { if !$0 { store.errorMessage = "" } }
)) {
Button("OK", role: .cancel) { store.errorMessage = "" }
} message: {
Text(store.errorMessage)
}
}
@ViewBuilder
private var content: some View {
switch store.selectedSection {
case .toApply, .monitor, .applied, .archive, .deleted:
HStack(spacing: 0) {
OpportunityListView(store: store)
.frame(width: 390)
Divider()
LeadDetailView(store: store)
.frame(minWidth: 580)
}
case .documents:
DocumentsView(store: store)
case .evidence:
EvidenceView(store: store)
case .questions:
QuestionsView(store: store)
case .automation:
AutomationView(store: store)
case .settings:
SettingsView(store: store)
}
}
}
struct SidebarView: View {
@ObservedObject var store: AppStore
var body: some View {
VStack(alignment: .leading, spacing: 0) {
HStack(spacing: 10) {
AppLogo(size: 34)
VStack(alignment: .leading, spacing: 1) {
Text("Career Command")
.font(.system(size: 13, weight: .semibold))
Text("Center")
.font(.system(size: 11))
.foregroundStyle(.secondary)
}
}
.padding(.horizontal, 16)
.padding(.top, 16)
.padding(.bottom, 20)
.foregroundStyle(AppTheme.ink)
sidebarLabel("PIPELINE")
sidebarButton(.toApply, count: store.count(for: .toApply))
sidebarButton(.monitor, count: store.count(for: .monitor))
sidebarButton(.applied, count: store.count(for: .applied))
sidebarButton(.archive, count: store.count(for: .archive))
sidebarButton(.deleted, count: store.count(for: .deleted))
sidebarLabel("WORKSPACE")
.padding(.top, 14)
sidebarButton(.documents)
sidebarButton(.evidence)
sidebarButton(.questions, count: store.count(for: .questions))
sidebarButton(.automation)
sidebarButton(.settings)
Spacer()
if store.config.automation.needsCodexSync {
Button {
store.selectedSection = .automation
} label: {
HStack(spacing: 8) {
Image(systemName: "exclamationmark.arrow.triangle.2.circlepath")
.foregroundStyle(AppTheme.amber)
VStack(alignment: .leading, spacing: 1) {
Text("Sync needed")
.font(.system(size: 11, weight: .semibold))
Text("Automation settings changed")
.font(.system(size: 10))
.foregroundStyle(.secondary)
}
Spacer()
}
.padding(10)
.background(AppTheme.amber.opacity(0.08), in: RoundedRectangle(cornerRadius: 6))
}
.buttonStyle(.plain)
.padding(.horizontal, 12)
.padding(.bottom, 10)
}
Button {
store.copyCodexRequest(store.runSearchRequest())
} label: {
Label("Run Search", systemImage: "play.fill")
.frame(maxWidth: .infinity)
}
.buttonStyle(PrimaryButtonStyle())
.padding(.horizontal, 12)
.padding(.bottom, 8)
HStack {
Text(store.workspaceURL.lastPathComponent)
.font(.system(size: 10))
.foregroundStyle(.secondary)
.lineLimit(1)
Spacer()
IconButton(icon: "arrow.clockwise", help: "Refresh workspace") { store.reload() }
}
.padding(.horizontal, 12)
.padding(.bottom, 12)
}
.background(AppTheme.sidebar)
.foregroundStyle(AppTheme.ink)
}
private func sidebarLabel(_ text: String) -> some View {
Text(text)
.font(.system(size: 9, weight: .bold))
.foregroundStyle(AppTheme.muted)
.padding(.horizontal, 16)
.padding(.bottom, 6)
}
private func sidebarButton(_ section: AppSection, count: Int? = nil) -> some View {
let selected = store.selectedSection == section
return Button {
store.selectedSection = section
if section.leadStatus != nil {
store.selectedLeadID = store.visibleLeads.first?.id
}
} label: {
HStack(spacing: 9) {
Image(systemName: section.icon)
.font(.system(size: 12, weight: .semibold))
.frame(width: 18)
.foregroundStyle(selected ? Color.white : AppTheme.muted)
Text(section.title)
.font(.system(size: 12, weight: selected ? .semibold : .regular))
.foregroundStyle(selected ? Color.white : AppTheme.ink)
Spacer()
if let count, count > 0 {
Text(String(count))
.font(.system(size: 10, weight: .semibold, design: .rounded))
.foregroundStyle(selected ? Color.white : AppTheme.muted)
.padding(.horizontal, 6)
.frame(height: 19)
.background(selected ? Color.white.opacity(0.17) : Color.primary.opacity(0.055), in: Capsule())
}
}
.padding(.horizontal, 12)
.frame(height: 36)
.background(
selected ? AppTheme.teal : Color.clear,
in: RoundedRectangle(cornerRadius: 6)
)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.padding(.horizontal, 8)
.padding(.vertical, 1)
}
}
struct OpportunityListView: View {
@ObservedObject var store: AppStore
@State private var typeFilter = "All"
private var leads: [LeadRecord] {
guard typeFilter != "All" else { return store.visibleLeads }
return store.visibleLeads.filter { $0.type.localizedCaseInsensitiveContains(typeFilter) }
}
var body: some View {
VStack(spacing: 0) {
VStack(alignment: .leading, spacing: 13) {
HStack(alignment: .firstTextBaseline) {
SectionTitle(title: store.selectedSection.title)
Spacer()
Text("\(leads.count)")
.font(.system(size: 12, weight: .semibold, design: .rounded))
.foregroundStyle(.secondary)
}
HStack(spacing: 8) {
HStack(spacing: 7) {
Image(systemName: "magnifyingglass")
.foregroundStyle(.secondary)
TextField("Search opportunities", text: $store.searchText)
.textFieldStyle(.plain)
}
.padding(.horizontal, 10)
.frame(height: 34)
.background(Color(nsColor: .textBackgroundColor), in: RoundedRectangle(cornerRadius: 6))
.overlay(RoundedRectangle(cornerRadius: 6).stroke(AppTheme.line))
Picker("Type", selection: $typeFilter) {
Text("All").tag("All")
Text("Jobs").tag("Job")
Text("PhDs").tag("PhD")
}
.labelsHidden()
.frame(width: 86)
}
}
.padding(18)
Divider()
if leads.isEmpty {
Spacer()
EmptyStateView(
icon: emptyIcon,
title: emptyTitle,
message: emptyMessage,
actionTitle: store.selectedSection == .toApply ? "Run Search" : nil,
action: store.selectedSection == .toApply ? { store.copyCodexRequest(store.runSearchRequest()) } : nil
)
Spacer()
} else {
ScrollView {
LazyVStack(spacing: 8) {
ForEach(leads) { lead in
LeadRow(lead: lead, selected: store.selectedLeadID == lead.id)
.onTapGesture { store.selectedLeadID = lead.id }
.contextMenu {
leadContextMenu(lead)
}
}
}
.padding(12)
}
}
}
.background(AppTheme.canvas)
}
@ViewBuilder
private func leadContextMenu(_ lead: LeadRecord) -> some View {
if store.selectedSection == .deleted {
Button("Restore") { store.restoreDeleted(lead.id) }
} else if store.selectedSection == .archive {
Button("Restore") { store.restoreArchived(lead.id) }
Button("Delete", role: .destructive) { store.deleteLead(lead.id) }
} else {
Button("Mark Applied") { store.markApplied(lead.id) }
Button("Save for Later") { store.saveForLater(lead.id) }
Divider()
Button("Archive") { store.archive(lead.id) }
Button("Delete", role: .destructive) { store.deleteLead(lead.id) }
}
}
private var emptyIcon: String {
switch store.selectedSection {
case .archive: return "archivebox"
case .deleted: return "trash"
case .applied: return "checkmark.circle"
case .monitor: return "bookmark"
default: return "tray"
}
}
private var emptyTitle: String {
switch store.selectedSection {
case .archive: return "Archive is empty"
case .deleted: return "Nothing recently deleted"
case .applied: return "No applications recorded"
case .monitor: return "No saved opportunities"
default: return "No opportunities waiting"
}
}
private var emptyMessage: String {
switch store.selectedSection {
case .archive: return "Archived opportunities stay out of the active queue and remain protected from rediscovery."
case .deleted: return "Deleted posting details appear here until they are restored or removed permanently."
case .applied: return "Mark an opportunity applied when the application has actually been submitted."
case .monitor: return "Save promising roles that are useful but not ready for immediate action."
default: return "The next verified search will place matching jobs and PhDs here."
}
}
}
struct LeadRow: View {
let lead: LeadRecord
let selected: Bool
var body: some View {
VStack(alignment: .leading, spacing: 9) {
HStack(alignment: .top, spacing: 10) {
VStack(alignment: .leading, spacing: 3) {
Text(lead.title)
.font(.system(size: 13, weight: .semibold))
.foregroundStyle(.primary)
.lineLimit(2)
Text(lead.organization)
.font(.system(size: 12, weight: .medium))
.foregroundStyle(AppTheme.teal)
.lineLimit(1)
}
Spacer(minLength: 4)
ScoreBadge(score: lead.score)
}
HStack(spacing: 11) {
MetadataLabel(icon: "mappin.and.ellipse", text: lead.location)
Spacer(minLength: 0)
Text(lead.type)
.font(.system(size: 10, weight: .semibold))
.foregroundStyle(.secondary)
}
if !lead.deadline.isEmpty {
Label(lead.deadline, systemImage: "calendar")
.font(.system(size: 10, weight: .medium))
.foregroundStyle(AppTheme.coral)
}
}
.padding(12)
.frame(maxWidth: .infinity, alignment: .leading)
.background(
selected ? AppTheme.teal.opacity(0.10) : AppTheme.panel,
in: RoundedRectangle(cornerRadius: 7)
)
.overlay(
RoundedRectangle(cornerRadius: 7)
.stroke(selected ? AppTheme.teal.opacity(0.38) : AppTheme.line)
)
.contentShape(Rectangle())
}
}
struct LeadDetailView: View {
@ObservedObject var store: AppStore
var body: some View {
if let lead = store.selectedLead {
LeadDetailContent(store: store, lead: lead)
.id(lead.id + lead.updatedAt)
} else {
EmptyStateView(
icon: "rectangle.and.text.magnifyingglass",
title: "Select an opportunity",
message: "Choose a posting from the queue to review fit, application files, and lifecycle actions."
)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(AppTheme.canvas)
}
}
}
private struct LeadDetailContent: View {
@ObservedObject var store: AppStore
let lead: LeadRecord
@State private var notes: String
@State private var confirmDelete = false
@State private var confirmPermanentDelete = false
init(store: AppStore, lead: LeadRecord) {
self.store = store
self.lead = lead
_notes = State(initialValue: lead.userNotes)
}
var body: some View {
VStack(spacing: 0) {
detailHeader
Divider()
ScrollView {
VStack(alignment: .leading, spacing: 20) {
if !lead.summary.isEmpty {
detailSection("Overview") {
ForEach(lead.summary, id: \.self) { item in
Text(item)
.font(.system(size: 13))
.foregroundStyle(.primary)
}
}
}
if lead.hasAssessment {
assessmentSection
}
if !lead.requirements.isEmpty {
detailSection("Requirements") {
bulletList(lead.requirements)
}
}
if !lead.responsibilities.isEmpty {
detailSection("Responsibilities") {
bulletList(lead.responsibilities)
}
}
if lead.hasPackage {
detailSection("Application Package") {
packageButtons
}
}
detailSection("Your Notes") {
TextEditor(text: $notes)
.font(.system(size: 13))
.scrollContentBackground(.hidden)
.padding(8)
.frame(minHeight: 90)
.background(Color(nsColor: .textBackgroundColor), in: RoundedRectangle(cornerRadius: 5))
.overlay(RoundedRectangle(cornerRadius: 5).stroke(AppTheme.line))
HStack {
Spacer()
Button("Save Notes") { store.updateUserNotes(lead.id, notes: notes) }
.buttonStyle(SecondaryButtonStyle())
}
}
sourceFooter
}
.padding(24)
.frame(maxWidth: 920, alignment: .leading)
.frame(maxWidth: .infinity, alignment: .top)
}
}
.background(AppTheme.canvas)
.confirmationDialog(
"Delete this posting?",
isPresented: $confirmDelete,
titleVisibility: .visible
) {
Button("Move to Recently Deleted", role: .destructive) { store.deleteLead(lead.id) }
Button("Cancel", role: .cancel) {}
} message: {
Text("The posting leaves every active list. Generated files are preserved, and a dedupe marker prevents the automation from adding it again.")
}
.confirmationDialog(
"Permanently remove posting details?",
isPresented: $confirmPermanentDelete,
titleVisibility: .visible
) {
Button("Delete Permanently", role: .destructive) { store.permanentlyDelete(lead.id) }
Button("Cancel", role: .cancel) {}
} message: {
Text("The posting record will be removed. A minimal dedupe marker remains so the same opportunity is not rediscovered.")
}
}
private var detailHeader: some View {
VStack(alignment: .leading, spacing: 14) {
HStack(alignment: .top, spacing: 16) {
VStack(alignment: .leading, spacing: 5) {
HStack(spacing: 8) {
StatusPill(status: lead.status)
if !lead.tier.isEmpty {
Text("Tier \(lead.tier)")
.font(.system(size: 10, weight: .semibold))
.foregroundStyle(.secondary)
}
}
Text(lead.title)
.font(.system(size: 21, weight: .semibold))
.foregroundStyle(AppTheme.ink)
.fixedSize(horizontal: false, vertical: true)
Text(lead.organization)
.font(.system(size: 13, weight: .medium))
.foregroundStyle(AppTheme.teal)
HStack(spacing: 14) {
MetadataLabel(icon: "mappin.and.ellipse", text: lead.location)
MetadataLabel(icon: "briefcase.fill", text: lead.type)
if !lead.deadline.isEmpty {
MetadataLabel(icon: "calendar", text: lead.deadline)
}
}
}
Spacer(minLength: 12)
ScoreBadge(score: lead.score)
}
actionBar
}
.padding(.horizontal, 22)
.padding(.vertical, 18)
.background(AppTheme.canvas)
}
@ViewBuilder
private var actionBar: some View {
HStack(spacing: 8) {
if lead.status == .deleted {
Button {
store.restoreDeleted(lead.id)
} label: {
Label("Restore", systemImage: "arrow.uturn.backward")
}
.buttonStyle(PrimaryButtonStyle())
Button {
confirmPermanentDelete = true
} label: {
Label("Delete Permanently", systemImage: "trash")
}
.buttonStyle(DangerButtonStyle())
} else if lead.status == .archived {
Button {
store.restoreArchived(lead.id)
} label: {
Label("Restore", systemImage: "arrow.uturn.backward")
}
.buttonStyle(PrimaryButtonStyle())
Button {
confirmDelete = true
} label: {
Label("Delete", systemImage: "trash")
}
.buttonStyle(DangerButtonStyle())
} else {
if !lead.jobURL.isEmpty {
Button {
store.openWeb(lead.jobURL)
} label: {
Label("Open Posting", systemImage: "arrow.up.right.square")
}
.buttonStyle(SecondaryButtonStyle())
}
if !lead.applyURL.isEmpty {
Button {
store.openWeb(lead.applyURL)
} label: {
Label("Apply", systemImage: "paperplane.fill")
}
.buttonStyle(PrimaryButtonStyle())
}
if lead.status != .applied {
IconButton(icon: "checkmark.circle", help: "Mark applied", tint: Color.green) {
store.markApplied(lead.id)
}
}
IconButton(icon: "archivebox", help: "Archive posting") { store.archive(lead.id) }
IconButton(icon: "trash", help: "Delete posting", tint: AppTheme.coral) { confirmDelete = true }
Menu {
Button("Move to To Apply") { store.moveToApply(lead.id) }
Button("Save for Later") { store.saveForLater(lead.id) }
if !lead.packageFolder.isEmpty {
Divider()
Button("Reveal Package") { store.reveal(path: lead.packageFolder) }
}
} label: {
Image(systemName: "ellipsis")
.frame(width: 32, height: 30)
}
.menuStyle(.borderlessButton)
.fixedSize()
.help("More actions")
}
Spacer()
}
}
private var packageButtons: some View {
FlowLayout {
if !lead.cvPath.isEmpty {
Button { store.open(path: lead.cvPath) } label: {
Label("Open CV", systemImage: "doc.text.fill")
}
.buttonStyle(SecondaryButtonStyle())
}
if !lead.coverLetterPath.isEmpty {
Button { store.open(path: lead.coverLetterPath) } label: {
Label("Open Cover Letter", systemImage: "doc.richtext.fill")
}
.buttonStyle(SecondaryButtonStyle())
}
if !lead.packageFolder.isEmpty {
Button { store.reveal(path: lead.packageFolder) } label: {
Label("Reveal Package", systemImage: "folder.fill")
}
.buttonStyle(SecondaryButtonStyle())
}
if !lead.notesPath.isEmpty {
Button { store.open(path: lead.notesPath) } label: {
Label("Tailoring Notes", systemImage: "checklist")
}
.buttonStyle(SecondaryButtonStyle())
}
}
}
private var sourceFooter: some View {
HStack(spacing: 14) {
if !lead.platformSource.isEmpty {
MetadataLabel(icon: "network", text: lead.platformSource.replacingOccurrences(of: "_", with: " "))
}
if !lead.updatedAt.isEmpty {
MetadataLabel(icon: "clock", text: "Updated \(lead.updatedAt)")
}
Spacer()
}
.padding(.top, 6)
}
private var assessmentSection: some View {
VStack(alignment: .leading, spacing: 14) {
HStack(alignment: .top, spacing: 14) {
if !lead.matchStrengths.isEmpty {
detailAssessmentBlock(
title: "Match evidence",
subtitle: "Verified experience that supports the fit score",
icon: "scope",
color: AppTheme.teal,
items: lead.matchStrengths
)
}
if !lead.fitGaps.isEmpty {
detailAssessmentBlock(
title: "Evidence gaps",
subtitle: "Role capabilities not yet demonstrated by the evidence bank",
icon: "exclamationmark.triangle.fill",
color: AppTheme.amber,
items: lead.fitGaps
)
}
}
if !lead.eligibilityConstraints.isEmpty {
detailAssessmentBlock(
title: "Eligibility and practical constraints",
subtitle: "Conditions to confirm before investing in the application",
icon: "person.badge.clock.fill",
color: AppTheme.amber,
items: lead.eligibilityConstraints
)
}
if !lead.applicationRequirements.isEmpty {
detailAssessmentBlock(
title: "Application checklist",
subtitle: "Submission tasks only. These do not count against the fit score.",
icon: "checklist",
color: AppTheme.infoBlue,
items: lead.applicationRequirements
)
}
if !lead.searchNotes.isEmpty {
detailAssessmentBlock(
title: "Search notes",
subtitle: "Context from discovery and verification",
icon: "note.text",
color: AppTheme.muted,
items: lead.searchNotes
)
}
}
}
private func detailSection<Content: View>(_ title: String, @ViewBuilder content: () -> Content) -> some View {
VStack(alignment: .leading, spacing: 10) {
Text(title.uppercased())
.font(.system(size: 10, weight: .bold))
.foregroundStyle(.secondary)
content()
}
}
private func detailAssessmentBlock(
title: String,
subtitle: String,
icon: String,
color: Color,
items: [String]
) -> some View {
HStack(alignment: .top, spacing: 10) {
Image(systemName: icon)
.foregroundStyle(color)
.padding(.top, 1)
VStack(alignment: .leading, spacing: 6) {
Text(title)
.font(.system(size: 12, weight: .semibold))
Text(subtitle)
.font(.system(size: 10))
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
VStack(alignment: .leading, spacing: 6) {
ForEach(items, id: \.self) { item in
HStack(alignment: .top, spacing: 7) {
Circle()
.fill(color)
.frame(width: 4, height: 4)
.padding(.top, 6)
Text(item)
.font(.system(size: 12))
.fixedSize(horizontal: false, vertical: true)
Spacer(minLength: 0)
}
}
}
}
Spacer(minLength: 0)
}
.padding(12)
.frame(maxWidth: .infinity, alignment: .topLeading)
.background(color.opacity(0.075), in: RoundedRectangle(cornerRadius: 6))
}
private func bulletList(_ items: [String]) -> some View {
VStack(alignment: .leading, spacing: 8) {
ForEach(items, id: \.self) { item in
HStack(alignment: .top, spacing: 8) {
Circle()
.fill(AppTheme.teal)
.frame(width: 4, height: 4)
.padding(.top, 7)
Text(item)
.font(.system(size: 12))
.fixedSize(horizontal: false, vertical: true)
Spacer(minLength: 0)
}
}
}
}
}
SHA-256: 1353db9774c7a57b1b1186d32f178ae841d453e4414ba271ebcf3e069c2eacaa