import SwiftUI struct WorkspaceSwitcherView: View { @ObservedObject var screen: ScreenContext let orchestrator: NotchOrchestrator @ObservedObject private var screenRegistry = ScreenRegistry.shared @ObservedObject private var workspaceRegistry = WorkspaceRegistry.shared @State private var isRenameAlertPresented = false @State private var isDeleteConfirmationPresented = false @State private var renameDraft = "" private var currentWorkspaceSummary: WorkspaceSummary { workspaceRegistry.summary(for: screen.workspaceID) ?? workspaceRegistry.allWorkspaceSummaries().first ?? WorkspaceSummary(id: screen.workspaceID, name: "Workspace") } private var deletionFallbackSummary: WorkspaceSummary? { guard let fallbackWorkspaceID = workspaceRegistry.deletionFallbackWorkspaceID( forDeleting: screen.workspaceID, preferredFallback: workspaceRegistry.defaultWorkspaceID ) else { return nil } return workspaceRegistry.summary(for: fallbackWorkspaceID) } private var assignedScreenCount: Int { screenRegistry.assignedScreenCount(to: screen.workspaceID) } var body: some View { Menu { ForEach(workspaceRegistry.workspaceSummaries) { summary in Button { selectWorkspace(summary.id) } label: { if summary.id == screen.workspaceID { Label(summary.name, systemImage: "checkmark") } else { Text(summary.name) } } } Divider() Button("New Workspace") { let workspaceID = workspaceRegistry.createWorkspace() selectWorkspace(workspaceID) } Button("Rename Current Workspace") { renameDraft = currentWorkspaceSummary.name syncFocusLossSuppression(renamePresented: true, deletePresented: isDeleteConfirmationPresented) isRenameAlertPresented = true } Button("Delete Current Workspace", role: .destructive) { syncFocusLossSuppression(renamePresented: isRenameAlertPresented, deletePresented: true) isDeleteConfirmationPresented = true } .disabled(!workspaceRegistry.canDeleteWorkspace(id: screen.workspaceID)) } label: { switcherLabel } .menuStyle(.borderlessButton) .accessibilityIdentifier("notch.workspace-switcher") .accessibilityLabel("Workspace Switcher") .accessibilityValue(currentWorkspaceSummary.name) .fixedSize(horizontal: false, vertical: true) .help("Switch workspace for this screen") .alert("Rename Workspace", isPresented: $isRenameAlertPresented) { TextField("Workspace name", text: $renameDraft) Button("Cancel", role: .cancel) {} Button("Save") { workspaceRegistry.renameWorkspace(id: screen.workspaceID, to: renameDraft) } } message: { Text("This only renames the shared workspace. Screens assigned to it keep following the new name.") } .confirmationDialog("Delete Workspace", isPresented: $isDeleteConfirmationPresented, titleVisibility: .visible) { Button("Delete Workspace", role: .destructive) { deleteCurrentWorkspace() } } message: { Text(deleteMessage) } .onChange(of: isRenameAlertPresented) { _, isPresented in syncFocusLossSuppression(renamePresented: isPresented, deletePresented: isDeleteConfirmationPresented) } .onChange(of: isDeleteConfirmationPresented) { _, isPresented in syncFocusLossSuppression(renamePresented: isRenameAlertPresented, deletePresented: isPresented) } .onDisappear { screen.setCloseOnFocusLossSuppressed(false) } } private var deleteMessage: String { if let fallback = deletionFallbackSummary { return "This reassigns \(assignedScreenCount) screen\(assignedScreenCount == 1 ? "" : "s") to \(fallback.name) and closes this workspace." } return "At least one workspace must remain." } private var switcherLabel: some View { HStack(spacing: 6) { Image(systemName: "rectangle.3.group") .font(.system(size: 11, weight: .medium)) Text(currentWorkspaceSummary.name) .font(.system(size: 11, weight: .medium)) .lineLimit(1) Image(systemName: "chevron.down") .font(.system(size: 9, weight: .semibold)) } .foregroundStyle(.white.opacity(0.7)) .padding(.horizontal, 8) .padding(.vertical, 4) .background( RoundedRectangle(cornerRadius: 8) .fill(Color.white.opacity(0.08)) ) .contentShape(Rectangle()) .accessibilityElement(children: .ignore) .accessibilityLabel("Workspace Switcher") .accessibilityValue(currentWorkspaceSummary.name) .accessibilityIdentifier("notch.workspace-switcher") } private func selectWorkspace(_ workspaceID: WorkspaceID) { screenRegistry.assignWorkspace(workspaceID, to: screen.id) if screen.notchState == .open { orchestrator.open(screenID: screen.id) } } private func deleteCurrentWorkspace() { guard let fallback = screenRegistry.deleteWorkspace( screen.workspaceID, preferredFallback: workspaceRegistry.defaultWorkspaceID ) else { return } screenRegistry.assignWorkspace(fallback, to: screen.id) if screen.notchState == .open { orchestrator.open(screenID: screen.id) } else { screen.requestTerminalFocus?() } } private func syncFocusLossSuppression(renamePresented: Bool, deletePresented: Bool) { screen.setCloseOnFocusLossSuppressed(renamePresented || deletePresented) } }