From f901a683c7cfcea9f39c527fb04aa444b86f9409 Mon Sep 17 00:00:00 2001 From: William Mitchell Date: Wed, 19 Aug 2026 12:19:39 +0100 Subject: [PATCH 01/11] Add conditional tokens to menu bar layout --- Sources/CodexBar/MenuBarLayout.swift | 102 ++++++++ .../MenuBarLayoutConditionalEditor.swift | 244 ++++++++++++++++++ Sources/CodexBar/MenuBarLayoutEditor.swift | 128 ++++++++- Sources/CodexBar/MenuBarLayoutRenderer.swift | 108 +++++--- .../Resources/ar.lproj/Localizable.strings | 14 + .../Resources/ca.lproj/Localizable.strings | 14 + .../Resources/de.lproj/Localizable.strings | 13 + .../Resources/en.lproj/Localizable.strings | 13 + .../Resources/es.lproj/Localizable.strings | 13 + .../Resources/fa.lproj/Localizable.strings | 14 + .../Resources/fr.lproj/Localizable.strings | 13 + .../Resources/gl.lproj/Localizable.strings | 13 + .../Resources/id.lproj/Localizable.strings | 13 + .../Resources/it.lproj/Localizable.strings | 13 + .../Resources/ja.lproj/Localizable.strings | 13 + .../Resources/ko.lproj/Localizable.strings | 13 + .../Resources/nl.lproj/Localizable.strings | 13 + .../Resources/pl.lproj/Localizable.strings | 13 + .../Resources/pt-BR.lproj/Localizable.strings | 13 + .../Resources/ru.lproj/Localizable.strings | 13 + .../Resources/sv.lproj/Localizable.strings | 14 + .../Resources/th.lproj/Localizable.strings | 14 + .../Resources/tr.lproj/Localizable.strings | 14 + .../Resources/uk.lproj/Localizable.strings | 14 + .../Resources/vi.lproj/Localizable.strings | 14 + .../zh-Hans.lproj/Localizable.strings | 14 + .../zh-Hant.lproj/Localizable.strings | 14 + Sources/CodexBar/SettingsStore+Defaults.swift | 13 + Sources/CodexBar/SettingsStore.swift | 7 + Sources/CodexBar/SettingsStoreState.swift | 1 + .../StatusItemController+Animation.swift | 3 +- ...tatusItemController+CountdownRefresh.swift | 4 +- ...StatusItemController+IconObservation.swift | 8 +- .../MenuBarLayoutEditorTests.swift | 41 +++ .../MenuBarLayoutRendererTests.swift | 227 ++++++++++++++++ Tests/CodexBarTests/MenuBarLayoutTests.swift | 97 +++++++ 36 files changed, 1244 insertions(+), 48 deletions(-) create mode 100644 Sources/CodexBar/MenuBarLayoutConditionalEditor.swift diff --git a/Sources/CodexBar/MenuBarLayout.swift b/Sources/CodexBar/MenuBarLayout.swift index 1011940023..023df293a0 100644 --- a/Sources/CodexBar/MenuBarLayout.swift +++ b/Sources/CodexBar/MenuBarLayout.swift @@ -8,6 +8,85 @@ enum PercentWindow: String, CaseIterable, Codable, Hashable, Sendable { case automatic } +enum MenuBarConditionalMetric: String, CaseIterable, Codable, Hashable, Sendable { + case session + case weekly + case scopedWeekly + case automatic +} + +enum MenuBarConditionalComparison: String, CaseIterable, Codable, Hashable, Sendable { + case greaterThan + case greaterThanOrEqual + case lessThan + case lessThanOrEqual + + var symbol: String { + switch self { + case .greaterThan: ">" + case .greaterThanOrEqual: ">=" + case .lessThan: "<" + case .lessThanOrEqual: "<=" + } + } + + func evaluate(_ value: Double, _ threshold: Double) -> Bool { + switch self { + case .greaterThan: value > threshold + case .greaterThanOrEqual: value >= threshold + case .lessThan: value < threshold + case .lessThanOrEqual: value <= threshold + } + } +} + +enum MenuBarConditionalCombinator: String, CaseIterable, Codable, Hashable, Sendable { + case and + case or +} + +struct MenuBarConditionalPredicate: Codable, Hashable, Sendable { + var metric: MenuBarConditionalMetric + var comparison: MenuBarConditionalComparison + var threshold: Double +} + +struct MenuBarConditionalClause: Codable, Hashable, Sendable { + /// nil for the first clause; ignored-on-eval if set on the first. + var combinator: MenuBarConditionalCombinator? + var predicate: MenuBarConditionalPredicate +} + +struct MenuBarLayoutConditional: Codable, Hashable, Sendable { + var clauses: [MenuBarConditionalClause] // 1...4 after normalization + var thenToken: MenuBarLayoutToken + var elseToken: MenuBarLayoutToken + + init(clauses: [MenuBarConditionalClause], thenToken: MenuBarLayoutToken, elseToken: MenuBarLayoutToken) { + var normalized = clauses.prefix(4).map { clause in + var clause = clause + clause.predicate.threshold = min(max(clause.predicate.threshold, 0), 100) + return clause + } + if normalized.isEmpty { + normalized = [MenuBarConditionalClause( + combinator: nil, + predicate: MenuBarConditionalPredicate(metric: .session, comparison: .greaterThan, threshold: 0))] + } + normalized[0].combinator = nil + self.clauses = Array(normalized) + self.thenToken = thenToken + self.elseToken = elseToken + } + + static let defaultValue = MenuBarLayoutConditional( + clauses: [MenuBarConditionalClause( + combinator: nil, + predicate: MenuBarConditionalPredicate(metric: .session, comparison: .greaterThan, threshold: 0))], + thenToken: .percent(window: .session), + elseToken: .resetCountdown) +} + enum MenuBarLayoutToken: Codable, Hashable, Sendable { case icon case providerName @@ -26,6 +105,7 @@ enum MenuBarLayoutToken: Codable, Hashable, Sendable { case cost30d case separatorDot case space + indirect case conditional(MenuBarLayoutConditional) } enum MenuBarLayoutSemanticWindowResolver { @@ -284,3 +364,25 @@ extension MenuBarLayout { } } } + +extension MenuBarLayout { + /// Every token in the layout plus all tokens reachable through conditional branches (depth-capped). + var flattenedTokens: [MenuBarLayoutToken] { + var tokens: [MenuBarLayoutToken] = [] + for token in self.lines.joined() { + token.appendFlattened(into: &tokens, depth: 0) + } + return tokens + } +} + +extension MenuBarLayoutToken { + static let maxConditionalDepth = 8 + + func appendFlattened(into tokens: inout [MenuBarLayoutToken], depth: Int) { + tokens.append(self) + guard depth < Self.maxConditionalDepth, case let .conditional(conditional) = self else { return } + conditional.thenToken.appendFlattened(into: &tokens, depth: depth + 1) + conditional.elseToken.appendFlattened(into: &tokens, depth: depth + 1) + } +} diff --git a/Sources/CodexBar/MenuBarLayoutConditionalEditor.swift b/Sources/CodexBar/MenuBarLayoutConditionalEditor.swift new file mode 100644 index 0000000000..71caf40535 --- /dev/null +++ b/Sources/CodexBar/MenuBarLayoutConditionalEditor.swift @@ -0,0 +1,244 @@ +import CodexBarCore +import SwiftUI + +struct MenuBarLayoutConditionalDraft: Identifiable { + enum Mode: Hashable { + case create + case editLibrary(Int) + case editPlaced(MenuBarLayoutPosition) + } + + let id: UUID + let mode: Mode + var conditional: MenuBarLayoutConditional + + init(mode: Mode, conditional: MenuBarLayoutConditional) { + self.id = UUID() + self.mode = mode + self.conditional = conditional + } +} + +@MainActor +struct MenuBarLayoutConditionalEditorSheet: View { + @Environment(\.dismiss) private var dismiss + @State private var conditional: MenuBarLayoutConditional + + let draft: MenuBarLayoutConditionalDraft + let provider: UsageProvider? + let onSave: (MenuBarLayoutConditionalDraft) -> Void + + init( + draft: MenuBarLayoutConditionalDraft, + provider: UsageProvider?, + onSave: @escaping (MenuBarLayoutConditionalDraft) -> Void) + { + self.draft = draft + self.provider = provider + self.onSave = onSave + self._conditional = State(initialValue: draft.conditional) + } + + var body: some View { + VStack(alignment: .leading, spacing: 12) { + Text(L("menu_bar_layout_conditional_if")) + .font(.caption) + .fontWeight(.semibold) + .foregroundStyle(.secondary) + + ForEach(self.conditional.clauses.indices, id: \.self) { index in + HStack(spacing: 6) { + if index > 0 { + Picker("", selection: self.combinatorBinding(index)) { + Text(L("menu_bar_layout_conditional_and")).tag(MenuBarConditionalCombinator.and) + Text(L("menu_bar_layout_conditional_or")).tag(MenuBarConditionalCombinator.or) + } + .labelsHidden() + .fixedSize() + } + + Picker("", selection: self.metricBinding(index)) { + ForEach(MenuBarConditionalMetric.allCases, id: \.self) { metric in + Text(metric.editorLabel).tag(metric) + } + } + .labelsHidden() + + Picker("", selection: self.comparisonBinding(index)) { + ForEach(MenuBarConditionalComparison.allCases, id: \.self) { comparison in + Text(comparison.symbol).tag(comparison) + } + } + .labelsHidden() + + TextField("", value: self.thresholdBinding(index), format: .number) + .textFieldStyle(.roundedBorder) + .frame(width: 44) + .monospacedDigit() + Stepper(value: self.thresholdBinding(index), in: 0...100, step: 1) { + EmptyView() + } + .labelsHidden() + Text("%") + + if self.conditional.clauses.count > 1 { + Button { + self.conditional.clauses.remove(at: index) + } label: { + Image(systemName: "minus.circle") + } + .buttonStyle(.plain) + } + } + } + + Button(L("menu_bar_layout_conditional_add_condition")) { + self.conditional.clauses.append( + MenuBarConditionalClause( + combinator: .and, + predicate: MenuBarConditionalPredicate( + metric: .automatic, + comparison: .greaterThan, + threshold: 0))) + } + .disabled(self.conditional.clauses.count >= 4) + .buttonStyle(.link) + + HStack { + Text(L("menu_bar_layout_conditional_then")) + self.tokenMenu(selection: self.thenBinding) + } + HStack { + Text(L("menu_bar_layout_conditional_else")) + self.tokenMenu(selection: self.elseBinding) + } + + Divider() + + HStack { + Spacer() + Button(L("Cancel"), role: .cancel) { + self.dismiss() + } + Button(L("menu_bar_layout_conditional_save")) { + self.onSave( + MenuBarLayoutConditionalDraft( + mode: self.draft.mode, + conditional: self.conditional)) + self.dismiss() + } + .keyboardShortcut(.defaultAction) + } + } + .padding(16) + .frame(width: 460) + } + + private func combinatorBinding(_ index: Int) -> Binding { + Binding( + get: { self.conditional.clauses[index].combinator ?? .and }, + set: { self.conditional.clauses[index].combinator = $0 }) + } + + private func metricBinding(_ index: Int) -> Binding { + Binding( + get: { self.conditional.clauses[index].predicate.metric }, + set: { self.conditional.clauses[index].predicate.metric = $0 }) + } + + private func comparisonBinding(_ index: Int) -> Binding { + Binding( + get: { self.conditional.clauses[index].predicate.comparison }, + set: { self.conditional.clauses[index].predicate.comparison = $0 }) + } + + private func thresholdBinding(_ index: Int) -> Binding { + Binding( + get: { self.conditional.clauses[index].predicate.threshold }, + set: { self.conditional.clauses[index].predicate.threshold = min(max($0, 0), 100) }) + } + + private var thenBinding: Binding { + Binding( + get: { self.conditional.thenToken }, + set: { self.conditional.thenToken = $0 }) + } + + private var elseBinding: Binding { + Binding( + get: { self.conditional.elseToken }, + set: { self.conditional.elseToken = $0 }) + } + + private func tokenMenu(selection: Binding) -> some View { + Menu { + ForEach(Self.selectableTokens, id: \.self) { token in + Button { + selection.wrappedValue = token + } label: { + Label( + token.editorLabel(provider: self.provider), + systemImage: token.editorSystemImage) + } + } + } label: { + MenuBarLayoutChipLabel( + title: selection.wrappedValue.editorLabel(provider: self.provider), + systemImage: selection.wrappedValue.editorSystemImage, + isSelected: false) + } + } + + private static let selectableTokens: [MenuBarLayoutToken] = [ + .icon, + .providerName, + .accountLabel, + .percent(window: .session), + .percent(window: .weekly), + .percent(window: .scopedWeekly), + .percent(window: .automatic), + .usageBar, + .pace(window: .session), + .pace(window: .weekly), + .pace(window: .automatic), + .resetCountdown, + .resetAbsolute, + .runsOut, + .runsOutCompact, + .balance, + .costToday, + .cost30d, + .separatorDot, + .space, + ] +} + +extension MenuBarLayoutConditional { + func editorSummary(provider: UsageProvider?) -> String { + let condition = self.clauses.enumerated().map { index, clause -> String in + let predicate = "\(clause.predicate.metric.editorLabel) \(clause.predicate.comparison.symbol) " + + "\(Int(clause.predicate.threshold.rounded()))%" + guard index > 0, let combinator = clause.combinator else { return predicate } + let joiner = combinator == .and + ? L("menu_bar_layout_conditional_and") + : L("menu_bar_layout_conditional_or") + return "\(joiner) \(predicate)" + }.joined(separator: " ") + return L( + "menu_bar_layout_conditional_summary", + condition, + self.thenToken.editorLabel(provider: provider), + self.elseToken.editorLabel(provider: provider)) + } +} + +extension MenuBarConditionalMetric { + var editorLabel: String { + switch self { + case .session: L("menu_bar_layout_token_session") + case .weekly: L("menu_bar_layout_token_weekly") + case .scopedWeekly: L("menu_bar_layout_token_scoped_weekly") + case .automatic: L("menu_bar_layout_token_auto") + } + } +} diff --git a/Sources/CodexBar/MenuBarLayoutEditor.swift b/Sources/CodexBar/MenuBarLayoutEditor.swift index e7f295179d..785399fc37 100644 --- a/Sources/CodexBar/MenuBarLayoutEditor.swift +++ b/Sources/CodexBar/MenuBarLayoutEditor.swift @@ -55,6 +55,20 @@ enum MenuBarLayoutEditorMutations { return MenuBarLayout(lines: lines) } + static func replace( + at position: MenuBarLayoutPosition, + with token: MenuBarLayoutToken, + in layout: MenuBarLayout) + -> MenuBarLayout + { + guard layout.lines.indices.contains(position.line), + layout.lines[position.line].indices.contains(position.index) + else { return layout } + var lines = layout.lines + lines[position.line][position.index] = token + return MenuBarLayout(lines: lines) + } + static func insert( _ item: MenuBarLayoutDragItem, at target: MenuBarLayoutPosition, @@ -179,6 +193,7 @@ struct MenuBarLayoutEditor: View { @State private var scope: MenuBarLayoutEditorScope = .all @State private var selectedPosition: MenuBarLayoutPosition? + @State private var conditionalDraft: MenuBarLayoutConditionalDraft? private var layout: MenuBarLayout { switch self.scope { @@ -287,6 +302,8 @@ struct MenuBarLayoutEditor: View { self.palette(group) } + self.conditionalsPalette + Divider() self.displayOptions @@ -295,6 +312,13 @@ struct MenuBarLayoutEditor: View { .onDeleteCommand { self.removeSelectedToken() } + .sheet(item: self.$conditionalDraft) { draft in + MenuBarLayoutConditionalEditorSheet( + draft: draft, + provider: self.persistenceProvider, + onSave: self.saveConditionalDraft) + } + .onChange(of: self.scope) { _, _ in self.selectedPosition = nil } @@ -428,6 +452,16 @@ struct MenuBarLayoutEditor: View { self.insert(items.first, at: position) } .accessibilityLabel(token.editorAccessibilityLabel(provider: self.persistenceProvider)) + .contextMenu { + if case let .conditional(conditional) = token { + Button(L("menu_bar_layout_conditional_edit")) { + self.conditionalDraft = MenuBarLayoutConditionalDraft( + mode: .editPlaced(position), + conditional: conditional) + } + } + } + .accessibilityHint(L("menu_bar_layout_chip_hint")) .accessibilityAction(named: L("Remove")) { self.remove(at: position) @@ -536,6 +570,65 @@ struct MenuBarLayoutEditor: View { } } + private var conditionalsPalette: some View { + VStack(alignment: .leading, spacing: 6) { + Text(L("menu_bar_layout_group_conditionals")) + .font(.caption) + .fontWeight(.semibold) + .foregroundStyle(.secondary) + + LazyVGrid(columns: [GridItem(.adaptive(minimum: 88), spacing: 6)], alignment: .leading, spacing: 6) { + ForEach( + Array(self.settings.menuBarLayoutConditionals.enumerated()), + id: \.offset) + { index, conditional in + Button { + self.write(MenuBarLayoutEditorMutations.append(.conditional(conditional), to: self.layout)) + } label: { + MenuBarLayoutChipLabel( + title: conditional.editorSummary(provider: self.persistenceProvider), + systemImage: "switch.2", + isSelected: false) + } + .buttonStyle(.plain) + .draggable(MenuBarLayoutDragItem.palette(.conditional(conditional))) + .focusable() + .onKeyPress(keys: [.space, .return], phases: [.down]) { _ in + self.write(MenuBarLayoutEditorMutations.append(.conditional(conditional), to: self.layout)) + return .handled + } + .accessibilityLabel(conditional.editorSummary(provider: self.persistenceProvider)) + .contextMenu { + Button(L("menu_bar_layout_conditional_edit")) { + self.conditionalDraft = MenuBarLayoutConditionalDraft( + mode: .editLibrary(index), + conditional: conditional) + } + Button(L("menu_bar_layout_conditional_remove"), role: .destructive) { + self.settings.menuBarLayoutConditionals.remove(at: index) + } + } + } + + Button { + self.conditionalDraft = MenuBarLayoutConditionalDraft(mode: .create, conditional: .defaultValue) + } label: { + MenuBarLayoutChipLabel( + title: L("menu_bar_layout_conditional_add"), + systemImage: "plus.circle", + isSelected: false) + } + .buttonStyle(.plain) + .focusable() + .onKeyPress(keys: [.space, .return], phases: [.down]) { _ in + self.conditionalDraft = MenuBarLayoutConditionalDraft(mode: .create, conditional: .defaultValue) + return .handled + } + .accessibilityLabel(L("menu_bar_layout_conditional_add")) + } + } + } + private var displayOptions: some View { HStack(spacing: 18) { Picker(L("menu_bar_layout_size"), selection: self.sizeBinding) { @@ -608,6 +701,22 @@ struct MenuBarLayoutEditor: View { self.selectedPosition = nil } + private func saveConditionalDraft(_ draft: MenuBarLayoutConditionalDraft) { + switch draft.mode { + case .create: + self.settings.menuBarLayoutConditionals.append(draft.conditional) + case let .editLibrary(index): + guard self.settings.menuBarLayoutConditionals.indices.contains(index) else { return } + self.settings.menuBarLayoutConditionals[index] = draft.conditional + case let .editPlaced(position): + self.write(MenuBarLayoutEditorMutations.replace( + at: position, + with: .conditional(draft.conditional), + in: self.layout)) + self.selectedPosition = nil + } + } + private func write(_ layout: MenuBarLayout) { MenuBarLayoutEditorPersistence.activate( layout, @@ -616,7 +725,7 @@ struct MenuBarLayoutEditor: View { } } -private struct MenuBarLayoutChipLabel: View { +struct MenuBarLayoutChipLabel: View { let title: String let systemImage: String let isSelected: Bool @@ -693,7 +802,6 @@ struct MenuBarLayoutPreview: View { snapshot: snapshot) session = semanticWindows.session weekly = semanticWindows.weekly - // Provider-specific by design: Mistral's automatic lane can explicitly select its Monthly Plan window. let automaticPreference = provider == .mistral ? self.settings.menuBarMetricPreference(for: provider, snapshot: snapshot) : .automatic @@ -732,23 +840,16 @@ struct MenuBarLayoutPreview: View { scopedWeekly: MenuBarLayoutRenderWindow(scopedNamed?.window), scopedWeeklyTitle: scopedNamed?.title, automatic: automaticRenderWindow, - // Provider-specific by design: Mistral uses spend text when its automatic lane has no percentage window. automaticText: provider == .mistral && automaticRenderWindow == nil ? StatusItemController.mistralSpendDisplayText(snapshot: snapshot) : nil, - sessionPace: self.store.menuBarLayoutPaceText( - provider: provider, - window: session, - now: now), + sessionPace: self.store.menuBarLayoutPaceText(provider: provider, window: session, now: now), weeklyPace: self.store.menuBarLayoutPaceText( provider: provider, window: weekly, now: now, minimumElapsedPercent: 1), - automaticPace: self.store.menuBarLayoutPaceText( - provider: provider, - window: automatic, - now: now), + automaticPace: self.store.menuBarLayoutPaceText(provider: provider, window: automatic, now: now), runsOut: runsOut, balance: MenuBarLayoutBalanceResolver.balance(provider: provider, snapshot: snapshot), costToday: costToday.map { @@ -876,6 +977,9 @@ extension MenuBarLayoutGap { extension MenuBarLayoutToken { func editorLabel(provider: UsageProvider?) -> String { + if case let .conditional(conditional) = self { + return conditional.editorSummary(provider: provider) + } if let providerLabel = self.providerEditorLabel(provider: provider) { return providerLabel } @@ -918,6 +1022,7 @@ extension MenuBarLayoutToken { case .cost30d: L("menu_bar_layout_token_cost_30d") case .separatorDot: "·" case .space: L("menu_bar_layout_token_space") + case .conditional: L("menu_bar_layout_token_conditional") } } @@ -944,6 +1049,7 @@ extension MenuBarLayoutToken { case .cost30d: "calendar.badge.clock" case .separatorDot: "smallcircle.filled.circle" case .space: "space" + case .conditional: "switch.2" } } } diff --git a/Sources/CodexBar/MenuBarLayoutRenderer.swift b/Sources/CodexBar/MenuBarLayoutRenderer.swift index 49c7ab144c..8cef333399 100644 --- a/Sources/CodexBar/MenuBarLayoutRenderer.swift +++ b/Sources/CodexBar/MenuBarLayoutRenderer.swift @@ -301,7 +301,8 @@ final class MenuBarLayoutRenderer { data: MenuBarLayoutRenderData, icon: NSImage?, style: TokenStyle, - options: MenuBarLayoutRenderOptions) + options: MenuBarLayoutRenderOptions, + depth: Int = 0) -> (value: NSAttributedString, accessibilityText: String?) { switch item { @@ -335,34 +336,7 @@ final class MenuBarLayoutRenderer { unavailableLabel: L("Account unavailable"), attributes: style.attributes) case let .percent(window): - let rateWindow = Self.window(window, data: data) - let resolvedValue = Self.percentValue( - window: window, - rateWindow: rateWindow, - automaticText: data.automaticText, - showUsed: options.showUsed) - let prefix: String - let accessibilityPrefix: String - switch window { - case .session: - prefix = Self.sessionPrefix(rateWindow) - accessibilityPrefix = L("Session") - case .weekly: - let secondaryLabel = Self.secondaryLabel(data: data) - prefix = secondaryLabel.flatMap(\.first).map { String($0).uppercased() } ?? "W" - accessibilityPrefix = secondaryLabel ?? L("Weekly") - case .scopedWeekly: - prefix = data.scopedWeeklyTitle.map { String($0.prefix(1)).uppercased() } ?? "F" - accessibilityPrefix = data.scopedWeeklyTitle ?? L("Scoped weekly") - case .automatic: - prefix = "" - accessibilityPrefix = L("Usage") - } - let display = prefix.isEmpty ? resolvedValue.text : "\(prefix) \(resolvedValue.text)" - let accessibility = resolvedValue.isAvailable - ? L("%@ %@", accessibilityPrefix, resolvedValue.text) - : L("%@ unavailable", accessibilityPrefix) - return self.textToken(display, accessibilityText: accessibility, attributes: style.attributes) + return self.renderPercent(window, data: data, style: style, options: options) case let .pace(window): let accessibilityPrefix = Self.paceAccessibilityPrefix(window, data: data) return self.optionalTextToken( @@ -422,9 +396,55 @@ final class MenuBarLayoutRenderer { return self.textToken("·", accessibilityText: nil, attributes: style.attributes) case .space: return self.textToken(" ", accessibilityText: nil, attributes: style.attributes) + case let .conditional(conditional): + guard depth < MenuBarLayoutToken.maxConditionalDepth else { + return self.textToken( + self.missingValue, + accessibilityText: L("Conditional unavailable"), + attributes: style.attributes) + } + let branch = conditional.evaluatesTrue(data: data) ? conditional.thenToken : conditional.elseToken + return self.renderItem(branch, data: data, icon: icon, style: style, options: options, depth: depth + 1) } } + private static func renderPercent( + _ window: PercentWindow, + data: MenuBarLayoutRenderData, + style: TokenStyle, + options: MenuBarLayoutRenderOptions) + -> (value: NSAttributedString, accessibilityText: String?) + { + let rateWindow = Self.window(window, data: data) + let resolvedValue = Self.percentValue( + window: window, + rateWindow: rateWindow, + automaticText: data.automaticText, + showUsed: options.showUsed) + let prefix: String + let accessibilityPrefix: String + switch window { + case .session: + prefix = Self.sessionPrefix(rateWindow) + accessibilityPrefix = L("Session") + case .weekly: + let secondaryLabel = Self.secondaryLabel(data: data) + prefix = secondaryLabel.flatMap(\.first).map { String($0).uppercased() } ?? "W" + accessibilityPrefix = secondaryLabel ?? L("Weekly") + case .scopedWeekly: + prefix = data.scopedWeeklyTitle.map { String($0.prefix(1)).uppercased() } ?? "F" + accessibilityPrefix = data.scopedWeeklyTitle ?? L("Scoped weekly") + case .automatic: + prefix = "" + accessibilityPrefix = L("Usage") + } + let display = prefix.isEmpty ? resolvedValue.text : "\(prefix) \(resolvedValue.text)" + let accessibility = resolvedValue.isAvailable + ? L("%@ %@", accessibilityPrefix, resolvedValue.text) + : L("%@ unavailable", accessibilityPrefix) + return self.textToken(display, accessibilityText: accessibility, attributes: style.attributes) + } + private static func iconAccessibilityText(data: MenuBarLayoutRenderData) -> String { L("%@ icon", data.providerName ?? L("Provider")) } @@ -611,3 +631,33 @@ final class MenuBarLayoutRenderer { return size == .small ? 14 : 18 } } + +extension MenuBarLayoutConditional { + /// Left-fold over clauses; a predicate on a missing window (nil) evaluates false. + func evaluatesTrue(data: MenuBarLayoutRenderData) -> Bool { + guard let first = self.clauses.first else { return false } + var result = Self.test(first.predicate, data: data) + for clause in self.clauses.dropFirst() { + let value = Self.test(clause.predicate, data: data) + switch clause.combinator { + case .or: result = result || value + case .and, .none: result = result && value + } + } + return result + } + + private static func test(_ predicate: MenuBarConditionalPredicate, data: MenuBarLayoutRenderData) -> Bool { + guard let value = Self.value(for: predicate.metric, in: data) else { return false } + return predicate.comparison.evaluate(value, predicate.threshold) + } + + private static func value(for metric: MenuBarConditionalMetric, in data: MenuBarLayoutRenderData) -> Double? { + switch metric { + case .session: data.session?.usedPercent + case .weekly: data.weekly?.usedPercent + case .scopedWeekly: data.scopedWeekly?.usedPercent + case .automatic: data.automatic?.usedPercent + } + } +} diff --git a/Sources/CodexBar/Resources/ar.lproj/Localizable.strings b/Sources/CodexBar/Resources/ar.lproj/Localizable.strings index 5dc4c0ffd5..23c7b51188 100644 --- a/Sources/CodexBar/Resources/ar.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ar.lproj/Localizable.strings @@ -1489,3 +1489,17 @@ "Export JSON" = "Export JSON"; "Sources" = "Sources"; "OpenCodex" = "OpenCodex"; + +"menu_bar_layout_token_conditional" = "شرطي"; +"menu_bar_layout_group_conditionals" = "الشروط"; +"menu_bar_layout_conditional_add" = "شرطي جديد"; +"menu_bar_layout_conditional_edit" = "تحرير الشرطي"; +"menu_bar_layout_conditional_remove" = "إزالة من المكتبة"; +"menu_bar_layout_conditional_save" = "حفظ"; +"menu_bar_layout_conditional_if" = "إذا كان"; +"menu_bar_layout_conditional_then" = "فعندئذ اعرض"; +"menu_bar_layout_conditional_else" = "وإلا اعرض"; +"menu_bar_layout_conditional_and" = "و"; +"menu_bar_layout_conditional_or" = "أو"; +"menu_bar_layout_conditional_add_condition" = "إضافة شرط"; +"menu_bar_layout_conditional_summary" = "إذا كان %1$@ فعندئذ %2$@ وإلا %3$@"; diff --git a/Sources/CodexBar/Resources/ca.lproj/Localizable.strings b/Sources/CodexBar/Resources/ca.lproj/Localizable.strings index 75a520fda7..22574e0caf 100644 --- a/Sources/CodexBar/Resources/ca.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ca.lproj/Localizable.strings @@ -1488,3 +1488,17 @@ "Export JSON" = "Export JSON"; "Sources" = "Sources"; "OpenCodex" = "OpenCodex"; + +"menu_bar_layout_token_conditional" = "Condicional"; +"menu_bar_layout_group_conditionals" = "Condicionals"; +"menu_bar_layout_conditional_add" = "Condicional nou"; +"menu_bar_layout_conditional_edit" = "Edita el condicional"; +"menu_bar_layout_conditional_remove" = "Elimina de la biblioteca"; +"menu_bar_layout_conditional_save" = "Desa"; +"menu_bar_layout_conditional_if" = "Si"; +"menu_bar_layout_conditional_then" = "Mostra llavors"; +"menu_bar_layout_conditional_else" = "Mostra en cas contrari"; +"menu_bar_layout_conditional_and" = "i"; +"menu_bar_layout_conditional_or" = "o"; +"menu_bar_layout_conditional_add_condition" = "Afegeix una condició"; +"menu_bar_layout_conditional_summary" = "Si %1$@ llavors %2$@ en cas contrari %3$@"; diff --git a/Sources/CodexBar/Resources/de.lproj/Localizable.strings b/Sources/CodexBar/Resources/de.lproj/Localizable.strings index 5fc0a92474..2b95bbadeb 100644 --- a/Sources/CodexBar/Resources/de.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/de.lproj/Localizable.strings @@ -1486,3 +1486,16 @@ "Export JSON" = "Export JSON"; "Sources" = "Sources"; "OpenCodex" = "OpenCodex"; +"menu_bar_layout_token_conditional" = "Bedingt"; +"menu_bar_layout_group_conditionals" = "Bedingungen"; +"menu_bar_layout_conditional_add" = "Neue Bedingung"; +"menu_bar_layout_conditional_edit" = "Bedingung bearbeiten"; +"menu_bar_layout_conditional_remove" = "Aus der Bibliothek entfernen"; +"menu_bar_layout_conditional_save" = "Speichern"; +"menu_bar_layout_conditional_if" = "Wenn"; +"menu_bar_layout_conditional_then" = "Dann anzeigen"; +"menu_bar_layout_conditional_else" = "Sonst anzeigen"; +"menu_bar_layout_conditional_and" = "und"; +"menu_bar_layout_conditional_or" = "oder"; +"menu_bar_layout_conditional_add_condition" = "Bedingung hinzufügen"; +"menu_bar_layout_conditional_summary" = "Wenn %1$@ dann %2$@ sonst %3$@"; diff --git a/Sources/CodexBar/Resources/en.lproj/Localizable.strings b/Sources/CodexBar/Resources/en.lproj/Localizable.strings index 9be7db8a76..89d04ed720 100644 --- a/Sources/CodexBar/Resources/en.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/en.lproj/Localizable.strings @@ -1489,3 +1489,16 @@ "5-hour usage" = "5-hour usage"; "7-day usage" = "7-day usage"; "Total usage" = "Total usage"; +"menu_bar_layout_token_conditional" = "Conditional"; +"menu_bar_layout_group_conditionals" = "Conditionals"; +"menu_bar_layout_conditional_add" = "New Conditional"; +"menu_bar_layout_conditional_edit" = "Edit Conditional"; +"menu_bar_layout_conditional_remove" = "Remove from Library"; +"menu_bar_layout_conditional_save" = "Save"; +"menu_bar_layout_conditional_if" = "If"; +"menu_bar_layout_conditional_then" = "Then show"; +"menu_bar_layout_conditional_else" = "Else show"; +"menu_bar_layout_conditional_and" = "and"; +"menu_bar_layout_conditional_or" = "or"; +"menu_bar_layout_conditional_add_condition" = "Add Condition"; +"menu_bar_layout_conditional_summary" = "If %1$@ then %2$@ else %3$@"; diff --git a/Sources/CodexBar/Resources/es.lproj/Localizable.strings b/Sources/CodexBar/Resources/es.lproj/Localizable.strings index a9affd827a..2ae8b9597f 100644 --- a/Sources/CodexBar/Resources/es.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/es.lproj/Localizable.strings @@ -1484,3 +1484,16 @@ "Export JSON" = "Export JSON"; "Sources" = "Sources"; "OpenCodex" = "OpenCodex"; +"menu_bar_layout_token_conditional" = "Condicional"; +"menu_bar_layout_group_conditionals" = "Condicionales"; +"menu_bar_layout_conditional_add" = "Nueva condición"; +"menu_bar_layout_conditional_edit" = "Editar condición"; +"menu_bar_layout_conditional_remove" = "Quitar de la biblioteca"; +"menu_bar_layout_conditional_save" = "Guardar"; +"menu_bar_layout_conditional_if" = "Si"; +"menu_bar_layout_conditional_then" = "Mostrar entonces"; +"menu_bar_layout_conditional_else" = "Si no, mostrar"; +"menu_bar_layout_conditional_and" = "y"; +"menu_bar_layout_conditional_or" = "o"; +"menu_bar_layout_conditional_add_condition" = "Añadir condición"; +"menu_bar_layout_conditional_summary" = "Si %1$@ entonces %2$@ si no %3$@"; diff --git a/Sources/CodexBar/Resources/fa.lproj/Localizable.strings b/Sources/CodexBar/Resources/fa.lproj/Localizable.strings index fcea722368..7e28e6f40c 100644 --- a/Sources/CodexBar/Resources/fa.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/fa.lproj/Localizable.strings @@ -1489,3 +1489,17 @@ "Export JSON" = "Export JSON"; "Sources" = "Sources"; "OpenCodex" = "OpenCodex"; + +"menu_bar_layout_token_conditional" = "شرطی"; +"menu_bar_layout_group_conditionals" = "شروط"; +"menu_bar_layout_conditional_add" = "شرطی جدید"; +"menu_bar_layout_conditional_edit" = "ویرایش شرطی"; +"menu_bar_layout_conditional_remove" = "حذف از کتابخانه"; +"menu_bar_layout_conditional_save" = "ذخیره"; +"menu_bar_layout_conditional_if" = "اگر"; +"menu_bar_layout_conditional_then" = "سپس نمایش بده"; +"menu_bar_layout_conditional_else" = "در غیر این صورت نمایش بده"; +"menu_bar_layout_conditional_and" = "و"; +"menu_bar_layout_conditional_or" = "یا"; +"menu_bar_layout_conditional_add_condition" = "افزودن شرط"; +"menu_bar_layout_conditional_summary" = "اگر %1$@ سپس %2$@ در غیر این صورت %3$@"; diff --git a/Sources/CodexBar/Resources/fr.lproj/Localizable.strings b/Sources/CodexBar/Resources/fr.lproj/Localizable.strings index 45039d5c79..9bbdc67132 100644 --- a/Sources/CodexBar/Resources/fr.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/fr.lproj/Localizable.strings @@ -1485,3 +1485,16 @@ "Export JSON" = "Export JSON"; "Sources" = "Sources"; "OpenCodex" = "OpenCodex"; +"menu_bar_layout_token_conditional" = "Conditionnel"; +"menu_bar_layout_group_conditionals" = "Conditionnels"; +"menu_bar_layout_conditional_add" = "Nouveau conditionnel"; +"menu_bar_layout_conditional_edit" = "Modifier le conditionnel"; +"menu_bar_layout_conditional_remove" = "Retirer de la bibliothèque"; +"menu_bar_layout_conditional_save" = "Enregistrer"; +"menu_bar_layout_conditional_if" = "Si"; +"menu_bar_layout_conditional_then" = "Alors afficher"; +"menu_bar_layout_conditional_else" = "Sinon afficher"; +"menu_bar_layout_conditional_and" = "et"; +"menu_bar_layout_conditional_or" = "ou"; +"menu_bar_layout_conditional_add_condition" = "Ajouter une condition"; +"menu_bar_layout_conditional_summary" = "Si %1$@ alors %2$@ sinon %3$@"; diff --git a/Sources/CodexBar/Resources/gl.lproj/Localizable.strings b/Sources/CodexBar/Resources/gl.lproj/Localizable.strings index c263a6a0a6..ede8214a89 100644 --- a/Sources/CodexBar/Resources/gl.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/gl.lproj/Localizable.strings @@ -1485,3 +1485,16 @@ "Export JSON" = "Export JSON"; "Sources" = "Sources"; "OpenCodex" = "OpenCodex"; +"menu_bar_layout_token_conditional" = "Condicional"; +"menu_bar_layout_group_conditionals" = "Condicionais"; +"menu_bar_layout_conditional_add" = "Nova condición"; +"menu_bar_layout_conditional_edit" = "Editar a condición"; +"menu_bar_layout_conditional_remove" = "Retirar da biblioteca"; +"menu_bar_layout_conditional_save" = "Gardar"; +"menu_bar_layout_conditional_if" = "Se"; +"menu_bar_layout_conditional_then" = "Entón mostrar"; +"menu_bar_layout_conditional_else" = "Se non, mostrar"; +"menu_bar_layout_conditional_and" = "e"; +"menu_bar_layout_conditional_or" = "ou"; +"menu_bar_layout_conditional_add_condition" = "Engadir condición"; +"menu_bar_layout_conditional_summary" = "Se %1$@ entón %2$@ se non %3$@"; diff --git a/Sources/CodexBar/Resources/id.lproj/Localizable.strings b/Sources/CodexBar/Resources/id.lproj/Localizable.strings index e05041081a..fabce12e39 100644 --- a/Sources/CodexBar/Resources/id.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/id.lproj/Localizable.strings @@ -1489,3 +1489,16 @@ "Export JSON" = "Export JSON"; "Sources" = "Sources"; "OpenCodex" = "OpenCodex"; +"menu_bar_layout_token_conditional" = "Bersyarat"; +"menu_bar_layout_group_conditionals" = "Kondisional"; +"menu_bar_layout_conditional_add" = "Kondisional Baru"; +"menu_bar_layout_conditional_edit" = "Edit Kondisional"; +"menu_bar_layout_conditional_remove" = "Hapus dari Perpustakaan"; +"menu_bar_layout_conditional_save" = "Simpan"; +"menu_bar_layout_conditional_if" = "Jika"; +"menu_bar_layout_conditional_then" = "Lalu tampilkan"; +"menu_bar_layout_conditional_else" = "Jika tidak, tampilkan"; +"menu_bar_layout_conditional_and" = "dan"; +"menu_bar_layout_conditional_or" = "atau"; +"menu_bar_layout_conditional_add_condition" = "Tambah Kondisi"; +"menu_bar_layout_conditional_summary" = "Jika %1$@ maka %2$@, jika tidak %3$@"; diff --git a/Sources/CodexBar/Resources/it.lproj/Localizable.strings b/Sources/CodexBar/Resources/it.lproj/Localizable.strings index 1458147b7c..335df6c4aa 100644 --- a/Sources/CodexBar/Resources/it.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/it.lproj/Localizable.strings @@ -1489,3 +1489,16 @@ "Export JSON" = "Esporta JSON"; "Sources" = "Origini"; "OpenCodex" = "OpenCodex"; +"menu_bar_layout_token_conditional" = "Condizionale"; +"menu_bar_layout_group_conditionals" = "Condizionali"; +"menu_bar_layout_conditional_add" = "Nuovo condizionale"; +"menu_bar_layout_conditional_edit" = "Modifica condizionale"; +"menu_bar_layout_conditional_remove" = "Rimuovi dalla libreria"; +"menu_bar_layout_conditional_save" = "Salva"; +"menu_bar_layout_conditional_if" = "Se"; +"menu_bar_layout_conditional_then" = "Allora mostra"; +"menu_bar_layout_conditional_else" = "Altrimenti mostra"; +"menu_bar_layout_conditional_and" = "e"; +"menu_bar_layout_conditional_or" = "o"; +"menu_bar_layout_conditional_add_condition" = "Aggiungi condizione"; +"menu_bar_layout_conditional_summary" = "Se %1$@ allora %2$@ altrimenti %3$@"; diff --git a/Sources/CodexBar/Resources/ja.lproj/Localizable.strings b/Sources/CodexBar/Resources/ja.lproj/Localizable.strings index 85db70223a..f1ba83656e 100644 --- a/Sources/CodexBar/Resources/ja.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ja.lproj/Localizable.strings @@ -1486,3 +1486,16 @@ "Export JSON" = "Export JSON"; "Sources" = "Sources"; "OpenCodex" = "OpenCodex"; +"menu_bar_layout_token_conditional" = "条件付き"; +"menu_bar_layout_group_conditionals" = "条件"; +"menu_bar_layout_conditional_add" = "新しい条件"; +"menu_bar_layout_conditional_edit" = "条件を編集"; +"menu_bar_layout_conditional_remove" = "ライブラリから削除"; +"menu_bar_layout_conditional_save" = "保存"; +"menu_bar_layout_conditional_if" = "もし"; +"menu_bar_layout_conditional_then" = "表示"; +"menu_bar_layout_conditional_else" = "それ以外は表示"; +"menu_bar_layout_conditional_and" = "かつ"; +"menu_bar_layout_conditional_or" = "または"; +"menu_bar_layout_conditional_add_condition" = "条件を追加"; +"menu_bar_layout_conditional_summary" = "もし %1$@ なら %2$@ を表示し、それ以外は %3$@ を表示"; diff --git a/Sources/CodexBar/Resources/ko.lproj/Localizable.strings b/Sources/CodexBar/Resources/ko.lproj/Localizable.strings index 875beef0fa..b817ef9806 100644 --- a/Sources/CodexBar/Resources/ko.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ko.lproj/Localizable.strings @@ -1453,3 +1453,16 @@ "Export JSON" = "Export JSON"; "Sources" = "Sources"; "OpenCodex" = "OpenCodex"; +"menu_bar_layout_token_conditional" = "조건부"; +"menu_bar_layout_group_conditionals" = "조건"; +"menu_bar_layout_conditional_add" = "새 조건"; +"menu_bar_layout_conditional_edit" = "조건 편집"; +"menu_bar_layout_conditional_remove" = "라이브러리에서 제거"; +"menu_bar_layout_conditional_save" = "저장"; +"menu_bar_layout_conditional_if" = "만약"; +"menu_bar_layout_conditional_then" = "표시"; +"menu_bar_layout_conditional_else" = "그 외에는 표시"; +"menu_bar_layout_conditional_and" = "그리고"; +"menu_bar_layout_conditional_or" = "또는"; +"menu_bar_layout_conditional_add_condition" = "조건 추가"; +"menu_bar_layout_conditional_summary" = "만약 %1$@이면 %2$@을 표시하고, 그렇지 않으면 %3$@을 표시"; diff --git a/Sources/CodexBar/Resources/nl.lproj/Localizable.strings b/Sources/CodexBar/Resources/nl.lproj/Localizable.strings index 7c91c2ead5..7917d24154 100644 --- a/Sources/CodexBar/Resources/nl.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/nl.lproj/Localizable.strings @@ -1485,3 +1485,16 @@ "Export JSON" = "Export JSON"; "Sources" = "Sources"; "OpenCodex" = "OpenCodex"; +"menu_bar_layout_token_conditional" = "Voorwaardelijk"; +"menu_bar_layout_group_conditionals" = "Voorwaarden"; +"menu_bar_layout_conditional_add" = "Nieuwe voorwaarde"; +"menu_bar_layout_conditional_edit" = "Voorwaarde bewerken"; +"menu_bar_layout_conditional_remove" = "Verwijder uit bibliotheek"; +"menu_bar_layout_conditional_save" = "Bewaar"; +"menu_bar_layout_conditional_if" = "Als"; +"menu_bar_layout_conditional_then" = "Toon dan"; +"menu_bar_layout_conditional_else" = "Anders tonen"; +"menu_bar_layout_conditional_and" = "en"; +"menu_bar_layout_conditional_or" = "of"; +"menu_bar_layout_conditional_add_condition" = "Voorwaarde toevoegen"; +"menu_bar_layout_conditional_summary" = "Als %1$@ dan %2$@ anders %3$@"; diff --git a/Sources/CodexBar/Resources/pl.lproj/Localizable.strings b/Sources/CodexBar/Resources/pl.lproj/Localizable.strings index dac43187b7..9b3fa068e8 100644 --- a/Sources/CodexBar/Resources/pl.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/pl.lproj/Localizable.strings @@ -1489,3 +1489,16 @@ "Export JSON" = "Export JSON"; "Sources" = "Sources"; "OpenCodex" = "OpenCodex"; +"menu_bar_layout_token_conditional" = "Warunkowy"; +"menu_bar_layout_group_conditionals" = "Warunki"; +"menu_bar_layout_conditional_add" = "Nowy warunek"; +"menu_bar_layout_conditional_edit" = "Edytuj warunek"; +"menu_bar_layout_conditional_remove" = "Usuń z biblioteki"; +"menu_bar_layout_conditional_save" = "Zapisz"; +"menu_bar_layout_conditional_if" = "Jeśli"; +"menu_bar_layout_conditional_then" = "Pokaż"; +"menu_bar_layout_conditional_else" = "W przeciwnym razie pokaż"; +"menu_bar_layout_conditional_and" = "i"; +"menu_bar_layout_conditional_or" = "lub"; +"menu_bar_layout_conditional_add_condition" = "Dodaj warunek"; +"menu_bar_layout_conditional_summary" = "Jeśli %1$@, to %2$@, w przeciwnym razie %3$@"; diff --git a/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings b/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings index e90e08e6b7..6d1c988d10 100644 --- a/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings @@ -1486,3 +1486,16 @@ "Export JSON" = "Export JSON"; "Sources" = "Sources"; "OpenCodex" = "OpenCodex"; +"menu_bar_layout_token_conditional" = "Condicional"; +"menu_bar_layout_group_conditionals" = "Condicionais"; +"menu_bar_layout_conditional_add" = "Novo condicional"; +"menu_bar_layout_conditional_edit" = "Editar condicional"; +"menu_bar_layout_conditional_remove" = "Remover da biblioteca"; +"menu_bar_layout_conditional_save" = "Salvar"; +"menu_bar_layout_conditional_if" = "Se"; +"menu_bar_layout_conditional_then" = "Então mostrar"; +"menu_bar_layout_conditional_else" = "Caso contrário mostrar"; +"menu_bar_layout_conditional_and" = "e"; +"menu_bar_layout_conditional_or" = "ou"; +"menu_bar_layout_conditional_add_condition" = "Adicionar condição"; +"menu_bar_layout_conditional_summary" = "Se %1$@ então %2$@, senão %3$@"; diff --git a/Sources/CodexBar/Resources/ru.lproj/Localizable.strings b/Sources/CodexBar/Resources/ru.lproj/Localizable.strings index 2decbbfc40..2fda09d9fe 100644 --- a/Sources/CodexBar/Resources/ru.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ru.lproj/Localizable.strings @@ -1487,3 +1487,16 @@ "Export JSON" = "Export JSON"; "Sources" = "Sources"; "OpenCodex" = "OpenCodex"; +"menu_bar_layout_token_conditional" = "Условный"; +"menu_bar_layout_group_conditionals" = "Условия"; +"menu_bar_layout_conditional_add" = "Новое условие"; +"menu_bar_layout_conditional_edit" = "Изменить условие"; +"menu_bar_layout_conditional_remove" = "Удалить из библиотеки"; +"menu_bar_layout_conditional_save" = "Сохранить"; +"menu_bar_layout_conditional_if" = "Если"; +"menu_bar_layout_conditional_then" = "Показать"; +"menu_bar_layout_conditional_else" = "Иначе показать"; +"menu_bar_layout_conditional_and" = "и"; +"menu_bar_layout_conditional_or" = "или"; +"menu_bar_layout_conditional_add_condition" = "Добавить условие"; +"menu_bar_layout_conditional_summary" = "Если %1$@, то показать %2$@, иначе показать %3$@"; diff --git a/Sources/CodexBar/Resources/sv.lproj/Localizable.strings b/Sources/CodexBar/Resources/sv.lproj/Localizable.strings index a62f9bfe83..2dd043ba41 100644 --- a/Sources/CodexBar/Resources/sv.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/sv.lproj/Localizable.strings @@ -1484,3 +1484,17 @@ "Export JSON" = "Export JSON"; "Sources" = "Sources"; "OpenCodex" = "OpenCodex"; + +"menu_bar_layout_token_conditional" = "Villkor"; +"menu_bar_layout_group_conditionals" = "Villkor"; +"menu_bar_layout_conditional_add" = "Nytt villkor"; +"menu_bar_layout_conditional_edit" = "Redigera villkor"; +"menu_bar_layout_conditional_remove" = "Ta bort från bibliotek"; +"menu_bar_layout_conditional_save" = "Spara"; +"menu_bar_layout_conditional_if" = "Om"; +"menu_bar_layout_conditional_then" = "Visa då"; +"menu_bar_layout_conditional_else" = "Visa annars"; +"menu_bar_layout_conditional_and" = "och"; +"menu_bar_layout_conditional_or" = "eller"; +"menu_bar_layout_conditional_add_condition" = "Lägg till villkor"; +"menu_bar_layout_conditional_summary" = "Om %1$@ visa %2$@ annars %3$@"; diff --git a/Sources/CodexBar/Resources/th.lproj/Localizable.strings b/Sources/CodexBar/Resources/th.lproj/Localizable.strings index ad5b01c1ff..0241981340 100644 --- a/Sources/CodexBar/Resources/th.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/th.lproj/Localizable.strings @@ -1489,3 +1489,17 @@ "Export JSON" = "Export JSON"; "Sources" = "Sources"; "OpenCodex" = "OpenCodex"; + +"menu_bar_layout_token_conditional" = "ตามเงื่อนไข"; +"menu_bar_layout_group_conditionals" = "เงื่อนไข"; +"menu_bar_layout_conditional_add" = "เงื่อนไขใหม่"; +"menu_bar_layout_conditional_edit" = "แก้ไขเงื่อนไข"; +"menu_bar_layout_conditional_remove" = "ลบออกจากไลบรารี"; +"menu_bar_layout_conditional_save" = "บันทึก"; +"menu_bar_layout_conditional_if" = "ถ้า"; +"menu_bar_layout_conditional_then" = "จากนั้นแสดง"; +"menu_bar_layout_conditional_else" = "มิฉะนั้นแสดง"; +"menu_bar_layout_conditional_and" = "และ"; +"menu_bar_layout_conditional_or" = "หรือ"; +"menu_bar_layout_conditional_add_condition" = "เพิ่มเงื่อนไข"; +"menu_bar_layout_conditional_summary" = "ถ้า %1$@ จากนั้น %2$@ มิฉะนั้น %3$@"; diff --git a/Sources/CodexBar/Resources/tr.lproj/Localizable.strings b/Sources/CodexBar/Resources/tr.lproj/Localizable.strings index 1c2e4ef4c1..ec2c837e6c 100644 --- a/Sources/CodexBar/Resources/tr.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/tr.lproj/Localizable.strings @@ -1487,3 +1487,17 @@ "Export JSON" = "Export JSON"; "Sources" = "Sources"; "OpenCodex" = "OpenCodex"; + +"menu_bar_layout_token_conditional" = "Koşullu"; +"menu_bar_layout_group_conditionals" = "Koşullular"; +"menu_bar_layout_conditional_add" = "Yeni Koşullu"; +"menu_bar_layout_conditional_edit" = "Koşulluyu Düzenle"; +"menu_bar_layout_conditional_remove" = "Kitaplıktan Kaldır"; +"menu_bar_layout_conditional_save" = "Kaydet"; +"menu_bar_layout_conditional_if" = "Eğer"; +"menu_bar_layout_conditional_then" = "ise göster"; +"menu_bar_layout_conditional_else" = "değilse göster"; +"menu_bar_layout_conditional_and" = "ve"; +"menu_bar_layout_conditional_or" = "veya"; +"menu_bar_layout_conditional_add_condition" = "Koşul Ekle"; +"menu_bar_layout_conditional_summary" = "Eğer %1$@ ise %2$@, değilse %3$@ göster"; diff --git a/Sources/CodexBar/Resources/uk.lproj/Localizable.strings b/Sources/CodexBar/Resources/uk.lproj/Localizable.strings index 65448ff73e..eb9a737f9e 100644 --- a/Sources/CodexBar/Resources/uk.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/uk.lproj/Localizable.strings @@ -1485,3 +1485,17 @@ "Export JSON" = "Export JSON"; "Sources" = "Sources"; "OpenCodex" = "OpenCodex"; + +"menu_bar_layout_token_conditional" = "Умовний"; +"menu_bar_layout_group_conditionals" = "Умовні"; +"menu_bar_layout_conditional_add" = "Нова умова"; +"menu_bar_layout_conditional_edit" = "Редагувати умову"; +"menu_bar_layout_conditional_remove" = "Видалити з бібліотеки"; +"menu_bar_layout_conditional_save" = "Зберегти"; +"menu_bar_layout_conditional_if" = "Якщо"; +"menu_bar_layout_conditional_then" = "Тоді показати"; +"menu_bar_layout_conditional_else" = "Інакше показати"; +"menu_bar_layout_conditional_and" = "і"; +"menu_bar_layout_conditional_or" = "або"; +"menu_bar_layout_conditional_add_condition" = "Додати умову"; +"menu_bar_layout_conditional_summary" = "Якщо %1$@ тоді %2$@ інакше %3$@"; diff --git a/Sources/CodexBar/Resources/vi.lproj/Localizable.strings b/Sources/CodexBar/Resources/vi.lproj/Localizable.strings index 6c7e669fa8..d134ed986e 100644 --- a/Sources/CodexBar/Resources/vi.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/vi.lproj/Localizable.strings @@ -1486,3 +1486,17 @@ "Export JSON" = "Export JSON"; "Sources" = "Sources"; "OpenCodex" = "OpenCodex"; + +"menu_bar_layout_token_conditional" = "Điều kiện"; +"menu_bar_layout_group_conditionals" = "Điều kiện"; +"menu_bar_layout_conditional_add" = "Điều kiện mới"; +"menu_bar_layout_conditional_edit" = "Sửa điều kiện"; +"menu_bar_layout_conditional_remove" = "Xóa khỏi thư viện"; +"menu_bar_layout_conditional_save" = "Lưu"; +"menu_bar_layout_conditional_if" = "Nếu"; +"menu_bar_layout_conditional_then" = "Thì hiển thị"; +"menu_bar_layout_conditional_else" = "Ngược lại hiển thị"; +"menu_bar_layout_conditional_and" = "và"; +"menu_bar_layout_conditional_or" = "hoặc"; +"menu_bar_layout_conditional_add_condition" = "Thêm điều kiện"; +"menu_bar_layout_conditional_summary" = "Nếu %1$@ thì %2$@ ngược lại %3$@"; diff --git a/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings b/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings index 94dd167507..8b9593fa5f 100644 --- a/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings @@ -1463,3 +1463,17 @@ "Total usage" = "总用量"; "claude_oauth_keychain_access_revoked" = "Claude Code 轮换令牌后撤销了对 Claude 钥匙串的访问权限。点击“刷新”以重新授权,或将 Claude 用量来源切换为 CLI/Web。"; "claude_showing_last_known_usage" = "正在显示 %@ 采集的最后已知用量。"; + +"menu_bar_layout_token_conditional" = "条件"; +"menu_bar_layout_group_conditionals" = "条件"; +"menu_bar_layout_conditional_add" = "新建条件"; +"menu_bar_layout_conditional_edit" = "编辑条件"; +"menu_bar_layout_conditional_remove" = "从库中移除"; +"menu_bar_layout_conditional_save" = "保存"; +"menu_bar_layout_conditional_if" = "如果"; +"menu_bar_layout_conditional_then" = "则显示"; +"menu_bar_layout_conditional_else" = "否则显示"; +"menu_bar_layout_conditional_and" = "和"; +"menu_bar_layout_conditional_or" = "或"; +"menu_bar_layout_conditional_add_condition" = "添加条件"; +"menu_bar_layout_conditional_summary" = "如果 %1$@ 则 %2$@ 否则 %3$@"; diff --git a/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings b/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings index 2eb83c29e8..e86baab8c3 100644 --- a/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings @@ -1516,3 +1516,17 @@ "Export JSON" = "Export JSON"; "Sources" = "Sources"; "OpenCodex" = "OpenCodex"; + +"menu_bar_layout_token_conditional" = "條件"; +"menu_bar_layout_group_conditionals" = "條件"; +"menu_bar_layout_conditional_add" = "新增條件"; +"menu_bar_layout_conditional_edit" = "編輯條件"; +"menu_bar_layout_conditional_remove" = "從庫中移除"; +"menu_bar_layout_conditional_save" = "儲存"; +"menu_bar_layout_conditional_if" = "如果"; +"menu_bar_layout_conditional_then" = "則顯示"; +"menu_bar_layout_conditional_else" = "否則顯示"; +"menu_bar_layout_conditional_and" = "和"; +"menu_bar_layout_conditional_or" = "或"; +"menu_bar_layout_conditional_add_condition" = "新增條件"; +"menu_bar_layout_conditional_summary" = "如果 %1$@ 則 %2$@ 否則 %3$@"; diff --git a/Sources/CodexBar/SettingsStore+Defaults.swift b/Sources/CodexBar/SettingsStore+Defaults.swift index e5fe14eb9b..4550e6f19a 100644 --- a/Sources/CodexBar/SettingsStore+Defaults.swift +++ b/Sources/CodexBar/SettingsStore+Defaults.swift @@ -412,6 +412,14 @@ extension SettingsStore { } } + var menuBarLayoutConditionals: [MenuBarLayoutConditional] { + get { self.defaultsState.menuBarLayoutConditionals } + set { + self.defaultsState.menuBarLayoutConditionals = newValue + self.persistMenuBarLayoutConditionals() + } + } + var hasStoredMenuBarLayout: Bool { self.defaultsState.storedMenuBarLayout != nil } @@ -495,6 +503,11 @@ extension SettingsStore { self.userDefaults.set(data, forKey: key) } + private func persistMenuBarLayoutConditionals() { + guard let data = try? JSONEncoder().encode(self.defaultsState.menuBarLayoutConditionals) else { return } + self.userDefaults.set(data, forKey: "menuBarLayoutConditionals") + } + private func persistMenuBarLayoutOverrides() { guard let data = try? JSONEncoder().encode(self.defaultsState.menuBarLayoutOverridesRaw) else { return } self.userDefaults.set(data, forKey: "menuBarLayoutOverrides") diff --git a/Sources/CodexBar/SettingsStore.swift b/Sources/CodexBar/SettingsStore.swift index 93a8bc56e8..7a8c90770b 100644 --- a/Sources/CodexBar/SettingsStore.swift +++ b/Sources/CodexBar/SettingsStore.swift @@ -532,6 +532,7 @@ extension SettingsStore { let multiAccountMenuLayoutRaw = Self.loadMultiAccountMenuLayoutRaw(userDefaults: userDefaults) let resolvedPreferences = Self.loadMenuBarMetricPreferences(userDefaults: userDefaults) let storedMenuBarLayout = Self.loadMenuBarLayout(userDefaults: userDefaults, key: "menuBarLayout") + let menuBarLayoutConditionals = Self.loadMenuBarLayoutConditionals(userDefaults: userDefaults) let menuBarLayoutOverridesRaw = Self.loadMenuBarLayoutOverrides(userDefaults: userDefaults) let menuBarLayoutSizeRaw = userDefaults.string(forKey: "menuBarLayoutSize") ?? MenuBarLayoutSize.regular.rawValue @@ -675,6 +676,7 @@ extension SettingsStore { multiAccountMenuLayoutRaw: multiAccountMenuLayoutRaw, menuBarMetricPreferencesRaw: resolvedPreferences, storedMenuBarLayout: storedMenuBarLayout, + menuBarLayoutConditionals: menuBarLayoutConditionals, menuBarLayoutOverridesRaw: menuBarLayoutOverridesRaw, menuBarLayoutSizeRaw: menuBarLayoutSizeRaw, menuBarLayoutGapRaw: menuBarLayoutGapRaw, @@ -864,6 +866,11 @@ extension SettingsStore { return try? JSONDecoder().decode(MenuBarLayout.self, from: data) } + private static func loadMenuBarLayoutConditionals(userDefaults: UserDefaults) -> [MenuBarLayoutConditional] { + guard let data = userDefaults.data(forKey: "menuBarLayoutConditionals") else { return [] } + return (try? JSONDecoder().decode([MenuBarLayoutConditional].self, from: data)) ?? [] + } + private static func loadMenuBarLayoutOverrides(userDefaults: UserDefaults) -> [String: MenuBarLayout] { guard let data = userDefaults.data(forKey: "menuBarLayoutOverrides") else { return [:] } return (try? JSONDecoder().decode([String: MenuBarLayout].self, from: data)) ?? [:] diff --git a/Sources/CodexBar/SettingsStoreState.swift b/Sources/CodexBar/SettingsStoreState.swift index b1547135c2..2148bebffe 100644 --- a/Sources/CodexBar/SettingsStoreState.swift +++ b/Sources/CodexBar/SettingsStoreState.swift @@ -38,6 +38,7 @@ struct SettingsDefaultsState { var multiAccountMenuLayoutRaw: String var menuBarMetricPreferencesRaw: [String: String] var storedMenuBarLayout: MenuBarLayout? + var menuBarLayoutConditionals: [MenuBarLayoutConditional] var menuBarLayoutOverridesRaw: [String: MenuBarLayout] var menuBarLayoutSizeRaw: String var menuBarLayoutGapRaw: String diff --git a/Sources/CodexBar/StatusItemController+Animation.swift b/Sources/CodexBar/StatusItemController+Animation.swift index 3ad23d1d7a..2b1ae57354 100644 --- a/Sources/CodexBar/StatusItemController+Animation.swift +++ b/Sources/CodexBar/StatusItemController+Animation.swift @@ -1254,8 +1254,7 @@ extension StatusItemController { if !layoutResolution.usesLegacyRendering, self.settings.menuBarIconStyle == .iconAndPercent { - let showsReset = layoutResolution.layout.lines - .joined() + let showsReset = layoutResolution.layout.flattenedTokens .contains { $0 == .resetCountdown || $0 == .resetAbsolute } guard showsReset else { return [] } let window = self.menuBarLayoutWindows(provider: provider, snapshot: snapshot, now: now).automatic diff --git a/Sources/CodexBar/StatusItemController+CountdownRefresh.swift b/Sources/CodexBar/StatusItemController+CountdownRefresh.swift index 89de2699c2..d0c0d5cf8a 100644 --- a/Sources/CodexBar/StatusItemController+CountdownRefresh.swift +++ b/Sources/CodexBar/StatusItemController+CountdownRefresh.swift @@ -23,7 +23,7 @@ extension StatusItemController { if !resolution.usesLegacyRendering, self.settings.menuBarIconStyle == .iconAndPercent { - let tokens = resolution.layout.lines.joined() + let tokens = resolution.layout.flattenedTokens if tokens.contains(.resetCountdown) { countdownResetDates.append(contentsOf: resetDates) } @@ -152,7 +152,7 @@ extension StatusItemController { let resolution = self.settings.menuBarLayoutResolution(for: provider) guard !resolution.usesLegacyRendering, self.settings.menuBarIconStyle == .iconAndPercent, - resolution.layout.lines.joined().contains(where: { + resolution.layout.flattenedTokens.contains(where: { if case .pace(window: .weekly) = $0 { return true } return false }) diff --git a/Sources/CodexBar/StatusItemController+IconObservation.swift b/Sources/CodexBar/StatusItemController+IconObservation.swift index d91e71e0b6..263f3c1a91 100644 --- a/Sources/CodexBar/StatusItemController+IconObservation.swift +++ b/Sources/CodexBar/StatusItemController+IconObservation.swift @@ -85,7 +85,7 @@ extension StatusItemController { { let resolution = self.settings.menuBarLayoutResolution(for: provider) guard !resolution.usesLegacyRendering, - resolution.layout.lines.joined().contains(.accountLabel), + resolution.layout.flattenedTokens.contains(.accountLabel), let accountLabel = self.menuBarLayoutAccountLabel(provider: provider, snapshot: snapshot) else { return nil } @@ -98,7 +98,7 @@ extension StatusItemController { let resolution = self.settings.menuBarLayoutResolution(for: provider) guard !resolution.usesLegacyRendering else { return nil } - let tokens = resolution.layout.lines.joined() + let tokens = resolution.layout.flattenedTokens let showsToday = tokens.contains(.costToday) let showsLast30Days = tokens.contains(.cost30d) guard showsToday || showsLast30Days else { return nil } @@ -117,7 +117,7 @@ extension StatusItemController { { let resolution = self.settings.menuBarLayoutResolution(for: provider) guard !resolution.usesLegacyRendering, - resolution.layout.lines.joined().contains(.balance) + resolution.layout.flattenedTokens.contains(.balance) else { return nil } return MenuBarLayoutBalanceResolver.balance(provider: provider, snapshot: snapshot) } @@ -134,7 +134,7 @@ extension StatusItemController { let resolution = self.settings.menuBarLayoutResolution(for: provider) guard !resolution.usesLegacyRendering else { return nil } - let paceWindows = Set(resolution.layout.lines.joined().compactMap { token -> PercentWindow? in + let paceWindows = Set(resolution.layout.flattenedTokens.compactMap { token -> PercentWindow? in guard case let .pace(window) = token else { return nil } return window }) diff --git a/Tests/CodexBarTests/MenuBarLayoutEditorTests.swift b/Tests/CodexBarTests/MenuBarLayoutEditorTests.swift index b309af992d..88203bc7cd 100644 --- a/Tests/CodexBarTests/MenuBarLayoutEditorTests.swift +++ b/Tests/CodexBarTests/MenuBarLayoutEditorTests.swift @@ -175,6 +175,47 @@ struct MenuBarLayoutEditorTests { #expect(decoded == payload) } + @Test + func `replace mutation swaps a placed token`() { + let initial = MenuBarLayout(lines: [[.icon, .providerName]]) + + let replaced = MenuBarLayoutEditorMutations.replace( + at: MenuBarLayoutPosition(line: 0, index: 1), + with: .conditional(MenuBarLayoutConditional.defaultValue), + in: initial) + #expect(replaced.lines == [[.icon, .conditional(MenuBarLayoutConditional.defaultValue)]]) + + let outOfBoundsLine = MenuBarLayoutEditorMutations.replace( + at: MenuBarLayoutPosition(line: 3, index: 0), + with: .conditional(MenuBarLayoutConditional.defaultValue), + in: initial) + #expect(outOfBoundsLine == initial) + + let outOfBoundsIndex = MenuBarLayoutEditorMutations.replace( + at: MenuBarLayoutPosition(line: 0, index: 5), + with: .conditional(MenuBarLayoutConditional.defaultValue), + in: initial) + #expect(outOfBoundsIndex == initial) + } + + @Test + func `conditional drag payload round trips`() throws { + let payload = MenuBarLayoutDragItem.palette(.conditional(MenuBarLayoutConditional.defaultValue)) + + let data = try JSONEncoder().encode(payload) + #expect(try JSONDecoder().decode(MenuBarLayoutDragItem.self, from: data) == payload) + } + + @Test + func `conditional token appends like palette tokens`() { + let initial = MenuBarLayout(lines: [[.icon, .resetCountdown]]) + + let appended = MenuBarLayoutEditorMutations.append( + .conditional(MenuBarLayoutConditional.defaultValue), + to: initial) + #expect(appended.lines == [[.icon, .resetCountdown, .conditional(MenuBarLayoutConditional.defaultValue)]]) + } + @Test func `balance token is provider aware`() throws { let row = try ProviderDetailSection.Row(label: "Remaining", value: "$12.34") diff --git a/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift b/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift index 5cf81e6a2c..3daab6df34 100644 --- a/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift +++ b/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift @@ -556,6 +556,233 @@ struct MenuBarLayoutRendererTests { .attribute(.foregroundColor, at: 0, effectiveRange: nil) as? NSColor == .controlTextColor) } + @Test + func `conditional renders then branch when predicate true`() { + let renderer = MenuBarLayoutRenderer() + let conditional = MenuBarLayoutConditional( + clauses: [self.clause(metric: .session, comparison: .greaterThan, threshold: 0)], + thenToken: .percent(window: .session), + elseToken: .resetCountdown) + let data = self.data() + + let output = renderer.render( + layout: MenuBarLayout(lines: [[.conditional(conditional)]]), + data: data, + icon: nil, + options: self.options()) + let control = renderer.render( + layout: MenuBarLayout(lines: [[.percent(window: .session)]]), + data: data, + icon: nil, + options: self.options()) + + #expect(output.attributedTitle.string == control.attributedTitle.string) + #expect(output.attributedTitle.string == "5h 25%") + } + + @Test + func `conditional renders else branch when predicate false`() { + let renderer = MenuBarLayoutRenderer() + // Session is 25%, so a > 50 threshold fails and the else branch must win. + let conditional = MenuBarLayoutConditional( + clauses: [self.clause(metric: .session, comparison: .greaterThan, threshold: 50)], + thenToken: .percent(window: .session), + elseToken: .resetCountdown) + let data = self.data() + + let output = renderer.render( + layout: MenuBarLayout(lines: [[.conditional(conditional)]]), + data: data, + icon: nil, + options: self.options()) + let control = renderer.render( + layout: MenuBarLayout(lines: [[.resetCountdown]]), + data: data, + icon: nil, + options: self.options()) + + #expect(output.attributedTitle.string == control.attributedTitle.string) + #expect(output.attributedTitle.string == "in 2h") + } + + @Test + func `conditional and requires all predicates`() { + let renderer = MenuBarLayoutRenderer() + let data = self.data() + + // Session 25% > 0 (true) AND weekly 60% > 70 (false) -> whole clause false. + let falseConditional = MenuBarLayoutConditional( + clauses: [ + self.clause(metric: .session, comparison: .greaterThan, threshold: 0), + self.clause(metric: .weekly, comparison: .greaterThan, threshold: 70, combinator: .and), + ], + thenToken: .percent(window: .session), + elseToken: .resetCountdown) + let falseOutput = renderer.render( + layout: MenuBarLayout(lines: [[.conditional(falseConditional)]]), + data: data, + icon: nil, + options: self.options()) + let falseControl = renderer.render( + layout: MenuBarLayout(lines: [[.resetCountdown]]), + data: data, + icon: nil, + options: self.options()) + #expect(falseOutput.attributedTitle.string == falseControl.attributedTitle.string) + + // Session 25% > 0 (true) AND weekly 60% > 50 (true) -> all predicates pass. + let trueConditional = MenuBarLayoutConditional( + clauses: [ + self.clause(metric: .session, comparison: .greaterThan, threshold: 0), + self.clause(metric: .weekly, comparison: .greaterThan, threshold: 50, combinator: .and), + ], + thenToken: .percent(window: .session), + elseToken: .resetCountdown) + let trueOutput = renderer.render( + layout: MenuBarLayout(lines: [[.conditional(trueConditional)]]), + data: data, + icon: nil, + options: self.options()) + let trueControl = renderer.render( + layout: MenuBarLayout(lines: [[.percent(window: .session)]]), + data: data, + icon: nil, + options: self.options()) + #expect(trueOutput.attributedTitle.string == trueControl.attributedTitle.string) + #expect(trueOutput.attributedTitle.string == "5h 25%") + } + + @Test + func `conditional or accepts any predicate`() { + let renderer = MenuBarLayoutRenderer() + // Weekly 60% > 70 (false) OR session 25% > 0 (true) -> whole clause true. + let conditional = MenuBarLayoutConditional( + clauses: [ + self.clause(metric: .weekly, comparison: .greaterThan, threshold: 70), + self.clause(metric: .session, comparison: .greaterThan, threshold: 0, combinator: .or), + ], + thenToken: .percent(window: .session), + elseToken: .resetCountdown) + let data = self.data() + + let output = renderer.render( + layout: MenuBarLayout(lines: [[.conditional(conditional)]]), + data: data, + icon: nil, + options: self.options()) + let control = renderer.render( + layout: MenuBarLayout(lines: [[.percent(window: .session)]]), + data: data, + icon: nil, + options: self.options()) + + #expect(output.attributedTitle.string == control.attributedTitle.string) + #expect(output.attributedTitle.string == "5h 25%") + } + + @Test + func `conditional with missing metric window falls to else`() { + let renderer = MenuBarLayoutRenderer() + // Session window is nil, so the session predicate evaluates false (not "0 > 0"). + let conditional = MenuBarLayoutConditional( + clauses: [self.clause(metric: .session, comparison: .greaterThan, threshold: 0)], + thenToken: .percent(window: .session), + elseToken: .resetCountdown) + let data = MenuBarLayoutRenderData( + provider: .codex, + iconKey: "missing", + providerName: nil, + accountLabel: nil, + session: nil, + weekly: nil, + scopedWeekly: nil, + scopedWeeklyTitle: nil, + automatic: nil, + automaticText: nil, + sessionPace: nil, + weeklyPace: nil, + automaticPace: nil, + runsOut: nil, + balance: nil, + costToday: nil, + cost30d: nil) + + let output = renderer.render( + layout: MenuBarLayout(lines: [[.conditional(conditional)]]), + data: data, + icon: nil, + options: self.options()) + let control = renderer.render( + layout: MenuBarLayout(lines: [[.resetCountdown]]), + data: data, + icon: nil, + options: self.options()) + + #expect(output.attributedTitle.string == control.attributedTitle.string) + #expect(output.attributedTitle.string == "–") + } + + @Test + func `nested conditional depth cap renders placeholder`() { + let renderer = MenuBarLayoutRenderer() + // Build a chain 10 levels deep; the renderer caps traversal at maxConditionalDepth. + var inner = MenuBarLayoutConditional( + clauses: [self.clause(metric: .session, comparison: .greaterThan, threshold: 0)], + thenToken: .percent(window: .session), + elseToken: .space) + for _ in 0..<9 { + inner = MenuBarLayoutConditional( + clauses: [self.clause(metric: .session, comparison: .greaterThan, threshold: 0)], + thenToken: .conditional(inner), + elseToken: .space) + } + let data = self.data() + // The cap triggers before any branch is evaluated, independent of live values, + // so the missing-value placeholder is the expected title. + let output = renderer.render( + layout: MenuBarLayout(lines: [[.conditional(inner)]]), + data: data, + icon: nil, + options: self.options()) + + #expect(output.attributedTitle.string == "–") + } + + @Test + func `conditional accessibility announces the chosen branch`() { + let renderer = MenuBarLayoutRenderer() + let conditional = MenuBarLayoutConditional( + clauses: [self.clause(metric: .session, comparison: .greaterThan, threshold: 0)], + thenToken: .percent(window: .session), + elseToken: .resetCountdown) + let data = self.data() + + let output = renderer.render( + layout: MenuBarLayout(lines: [[.conditional(conditional)]]), + data: data, + icon: nil, + options: self.options()) + let control = renderer.render( + layout: MenuBarLayout(lines: [[.percent(window: .session)]]), + data: data, + icon: nil, + options: self.options()) + + #expect(output.accessibilityLabel == control.accessibilityLabel) + #expect(output.accessibilityLabel == L("%@ %@", L("Session"), "25%")) + } + + private func clause( + metric: MenuBarConditionalMetric, + comparison: MenuBarConditionalComparison, + threshold: Double, + combinator: MenuBarConditionalCombinator? = nil) -> MenuBarConditionalClause + { + MenuBarConditionalClause( + combinator: combinator, + predicate: MenuBarConditionalPredicate(metric: metric, comparison: comparison, threshold: threshold)) + } + private func data( automaticUsedPercent: Double = 50, provider: UsageProvider = .codex, diff --git a/Tests/CodexBarTests/MenuBarLayoutTests.swift b/Tests/CodexBarTests/MenuBarLayoutTests.swift index 03f9ae1c88..7a3bad2a5d 100644 --- a/Tests/CodexBarTests/MenuBarLayoutTests.swift +++ b/Tests/CodexBarTests/MenuBarLayoutTests.swift @@ -52,6 +52,103 @@ struct MenuBarLayoutTests { #expect(try JSONDecoder().decode(MenuBarLayoutToken.self, from: labeled) == .runsOut) } + @Test + func `conditional token codable round trips`() throws { + let conditional = MenuBarLayoutConditional( + clauses: [MenuBarConditionalClause( + combinator: nil, + predicate: MenuBarConditionalPredicate(metric: .session, comparison: .greaterThan, threshold: 0))], + thenToken: .percent(window: .session), + elseToken: .resetCountdown) + let layout = MenuBarLayout(lines: [[.icon, .conditional(conditional)]]) + + let data = try JSONEncoder().encode(layout) + let decoded = try JSONDecoder().decode(MenuBarLayout.self, from: data) + + #expect(decoded == layout) + #expect(String(bytes: data, encoding: .utf8)?.contains("conditional") == true) + } + + @Test + func `flattened tokens include conditional branches`() { + let conditional = MenuBarLayoutConditional( + clauses: [MenuBarConditionalClause( + combinator: nil, + predicate: MenuBarConditionalPredicate(metric: .session, comparison: .greaterThan, threshold: 0))], + thenToken: .percent(window: .session), + elseToken: .resetCountdown) + let layout = MenuBarLayout(lines: [[.icon, .conditional(conditional)]]) + + let flattened = layout.flattenedTokens + + #expect(flattened.contains(.icon)) + #expect(flattened.contains(.conditional(conditional))) + #expect(flattened.contains(.percent(window: .session))) + #expect(flattened.contains(.resetCountdown)) + } + + @Test + func `conditional normalization clamps thresholds and clause count`() { + let predicate = MenuBarConditionalPredicate(metric: .session, comparison: .greaterThan, threshold: 0) + let manyClauses = (0..<6).map { index in + MenuBarConditionalClause( + combinator: index == 0 ? .or : .and, + predicate: MenuBarConditionalPredicate( + metric: .session, + comparison: .greaterThan, + threshold: 250)) + } + let conditional = MenuBarLayoutConditional( + clauses: manyClauses, + thenToken: .percent(window: .session), + elseToken: .resetCountdown) + + #expect(conditional.clauses.count == 4) + #expect(conditional.clauses.allSatisfy { $0.predicate.threshold == 100 }) + + let empty = MenuBarLayoutConditional( + clauses: [], + thenToken: .percent(window: .session), + elseToken: .resetCountdown) + #expect(empty.clauses.count == 1) + #expect(empty.clauses[0].combinator == nil) + #expect(empty.clauses[0].predicate.metric == .session) + #expect(empty.clauses[0].predicate.comparison == .greaterThan) + #expect(empty.clauses[0].predicate.threshold == 0) + + let forcedFirst = MenuBarLayoutConditional( + clauses: [MenuBarConditionalClause( + combinator: .or, + predicate: MenuBarConditionalPredicate( + metric: .weekly, + comparison: .greaterThanOrEqual, + threshold: 5))], + thenToken: .percent(window: .session), + elseToken: .resetCountdown) + #expect(forcedFirst.clauses[0].combinator == nil) + } + + @Test + @MainActor + func `conditionals library and layout persist across reload`() { + let suite = "MenuBarLayoutTests-conditional-persistence" + let settings = testSettingsStore(suiteName: suite) + let conditional = MenuBarLayoutConditional( + clauses: [MenuBarConditionalClause( + combinator: nil, + predicate: MenuBarConditionalPredicate(metric: .session, comparison: .greaterThan, threshold: 30))], + thenToken: .percent(window: .session), + elseToken: .resetCountdown) + let layout = MenuBarLayout(lines: [[.icon, .conditional(conditional)]]) + + settings.menuBarLayoutConditionals = [conditional, .defaultValue] + settings.setMenuBarLayout(layout, for: nil) + + let reloaded = Self.reloadSettingsStore(settings) + #expect(reloaded.menuBarLayoutConditionals == [conditional, .defaultValue]) + #expect(reloaded.menuBarLayout == layout) + } + @Test func `decoding normalizes empty and extra lines`() throws { let emptyData = try JSONEncoder().encode(UnnormalizedLayout(lines: [])) From 3520e502293011793e333615e3e8efaa5a343f21 Mon Sep 17 00:00:00 2001 From: William Mitchell Date: Wed, 19 Aug 2026 16:10:14 +0100 Subject: [PATCH 02/11] Add named conditionals with hidden branch and duplicate - Add required `name` field to `MenuBarLayoutConditional` with backward-compatible Codable (legacy data decodes to empty string) - Add `.hidden` token that renders nothing, used as a conditional branch value to suppress output - Chips in the palette now show the conditional's display name instead of the full summary - Conditional editor enforces a non-empty unique name with inline validation - Add Duplicate action to conditional context menu with auto-generated unique names - Refactor palette section: add button moved to header row, empty-state placeholder when no conditionals exist, wider chip grid - Update all 20 locale files with new string keys - Add renderer and model tests for the new features --- Sources/CodexBar/MenuBarLayout.swift | 47 +++++- .../MenuBarLayoutConditionalEditor.swift | 55 ++++++- Sources/CodexBar/MenuBarLayoutEditor.swift | 138 ++++++++++++------ Sources/CodexBar/MenuBarLayoutRenderer.swift | 2 + .../Resources/ar.lproj/Localizable.strings | 6 + .../Resources/ca.lproj/Localizable.strings | 6 + .../Resources/de.lproj/Localizable.strings | 6 + .../Resources/en.lproj/Localizable.strings | 6 + .../Resources/es.lproj/Localizable.strings | 6 + .../Resources/fa.lproj/Localizable.strings | 6 + .../Resources/fr.lproj/Localizable.strings | 6 + .../Resources/gl.lproj/Localizable.strings | 6 + .../Resources/id.lproj/Localizable.strings | 6 + .../Resources/it.lproj/Localizable.strings | 6 + .../Resources/ja.lproj/Localizable.strings | 6 + .../Resources/ko.lproj/Localizable.strings | 6 + .../Resources/nl.lproj/Localizable.strings | 6 + .../Resources/pl.lproj/Localizable.strings | 6 + .../Resources/pt-BR.lproj/Localizable.strings | 6 + .../Resources/ru.lproj/Localizable.strings | 6 + .../Resources/sv.lproj/Localizable.strings | 6 + .../Resources/th.lproj/Localizable.strings | 6 + .../Resources/tr.lproj/Localizable.strings | 6 + .../Resources/uk.lproj/Localizable.strings | 6 + .../Resources/vi.lproj/Localizable.strings | 6 + .../zh-Hans.lproj/Localizable.strings | 6 + .../zh-Hant.lproj/Localizable.strings | 6 + .../MenuBarLayoutRendererTests.swift | 17 +++ Tests/CodexBarTests/MenuBarLayoutTests.swift | 42 ++++++ 29 files changed, 383 insertions(+), 56 deletions(-) diff --git a/Sources/CodexBar/MenuBarLayout.swift b/Sources/CodexBar/MenuBarLayout.swift index 023df293a0..755fa86923 100644 --- a/Sources/CodexBar/MenuBarLayout.swift +++ b/Sources/CodexBar/MenuBarLayout.swift @@ -58,12 +58,26 @@ struct MenuBarConditionalClause: Codable, Hashable, Sendable { } struct MenuBarLayoutConditional: Codable, Hashable, Sendable { + var name: String var clauses: [MenuBarConditionalClause] // 1...4 after normalization var thenToken: MenuBarLayoutToken var elseToken: MenuBarLayoutToken - init(clauses: [MenuBarConditionalClause], thenToken: MenuBarLayoutToken, elseToken: MenuBarLayoutToken) { - var normalized = clauses.prefix(4).map { clause in + init( + name: String = "", + clauses: [MenuBarConditionalClause], + thenToken: MenuBarLayoutToken, + elseToken: MenuBarLayoutToken) + { + self.name = name + self.clauses = clauses + self.thenToken = thenToken + self.elseToken = elseToken + self.normalize() + } + + private mutating func normalize() { + var normalized = self.clauses.prefix(4).map { clause in var clause = clause clause.predicate.threshold = min(max(clause.predicate.threshold, 0), 100) return clause @@ -75,8 +89,28 @@ struct MenuBarLayoutConditional: Codable, Hashable, Sendable { } normalized[0].combinator = nil self.clauses = Array(normalized) - self.thenToken = thenToken - self.elseToken = elseToken + } + + /// Custom Codable so older persisted conditionals without a `name` still decode (→ ""). + private enum CodingKeys: String, CodingKey { + case name, clauses, thenToken, elseToken + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "" + self.clauses = try container.decode([MenuBarConditionalClause].self, forKey: .clauses) + self.thenToken = try container.decode(MenuBarLayoutToken.self, forKey: .thenToken) + self.elseToken = try container.decode(MenuBarLayoutToken.self, forKey: .elseToken) + self.normalize() + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.name, forKey: .name) + try container.encode(self.clauses, forKey: .clauses) + try container.encode(self.thenToken, forKey: .thenToken) + try container.encode(self.elseToken, forKey: .elseToken) } static let defaultValue = MenuBarLayoutConditional( @@ -84,7 +118,7 @@ struct MenuBarLayoutConditional: Codable, Hashable, Sendable { combinator: nil, predicate: MenuBarConditionalPredicate(metric: .session, comparison: .greaterThan, threshold: 0))], thenToken: .percent(window: .session), - elseToken: .resetCountdown) + elseToken: .hidden) } enum MenuBarLayoutToken: Codable, Hashable, Sendable { @@ -105,6 +139,8 @@ enum MenuBarLayoutToken: Codable, Hashable, Sendable { case cost30d case separatorDot case space + /// Renders nothing; used as a conditional branch value to hide output for the other case. + case hidden indirect case conditional(MenuBarLayoutConditional) } @@ -380,6 +416,7 @@ extension MenuBarLayoutToken { static let maxConditionalDepth = 8 func appendFlattened(into tokens: inout [MenuBarLayoutToken], depth: Int) { + if self == .hidden { return } tokens.append(self) guard depth < Self.maxConditionalDepth, case let .conditional(conditional) = self else { return } conditional.thenToken.appendFlattened(into: &tokens, depth: depth + 1) diff --git a/Sources/CodexBar/MenuBarLayoutConditionalEditor.swift b/Sources/CodexBar/MenuBarLayoutConditionalEditor.swift index 71caf40535..6c858300d8 100644 --- a/Sources/CodexBar/MenuBarLayoutConditionalEditor.swift +++ b/Sources/CodexBar/MenuBarLayoutConditionalEditor.swift @@ -26,21 +26,53 @@ struct MenuBarLayoutConditionalEditorSheet: View { let draft: MenuBarLayoutConditionalDraft let provider: UsageProvider? + let existingNames: Set let onSave: (MenuBarLayoutConditionalDraft) -> Void init( draft: MenuBarLayoutConditionalDraft, provider: UsageProvider?, + existingNames: Set, onSave: @escaping (MenuBarLayoutConditionalDraft) -> Void) { self.draft = draft self.provider = provider + self.existingNames = existingNames self.onSave = onSave self._conditional = State(initialValue: draft.conditional) } + private var trimmedName: String { + self.conditional.name.trimmingCharacters(in: .whitespacesAndNewlines) + } + + private var reservedNames: Set { + var names = self.existingNames + // The entry's own current name is allowed so an edit can be saved unchanged. + names.remove(self.draft.conditional.name.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()) + return names + } + + private var nameIsValid: Bool { + !self.trimmedName.isEmpty && !self.reservedNames.contains(self.trimmedName.lowercased()) + } + var body: some View { VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: 4) { + Text(L("menu_bar_layout_conditional_name")) + .font(.caption) + .fontWeight(.semibold) + .foregroundStyle(.secondary) + TextField(L("menu_bar_layout_conditional_name_placeholder"), text: self.$conditional.name) + .textFieldStyle(.roundedBorder) + if !self.nameIsValid { + Text(L("menu_bar_layout_conditional_name_error")) + .font(.caption) + .foregroundStyle(.red) + } + } + Text(L("menu_bar_layout_conditional_if")) .font(.caption) .fontWeight(.semibold) @@ -113,6 +145,11 @@ struct MenuBarLayoutConditionalEditorSheet: View { self.tokenMenu(selection: self.elseBinding) } + Text(self.conditional.editorSummary(provider: self.provider)) + .font(.caption) + .foregroundStyle(.secondary) + .lineLimit(2) + Divider() HStack { @@ -128,6 +165,7 @@ struct MenuBarLayoutConditionalEditorSheet: View { self.dismiss() } .keyboardShortcut(.defaultAction) + .disabled(!self.nameIsValid) } } .padding(16) @@ -210,12 +248,19 @@ struct MenuBarLayoutConditionalEditorSheet: View { .cost30d, .separatorDot, .space, + .hidden, ] } extension MenuBarLayoutConditional { - func editorSummary(provider: UsageProvider?) -> String { - let condition = self.clauses.enumerated().map { index, clause -> String in + /// The chip label: the required name, falling back to a generic label if somehow empty. + var displayName: String { + let trimmed = self.name.trimmingCharacters(in: .whitespacesAndNewlines) + return trimmed.isEmpty ? L("menu_bar_layout_token_conditional") : trimmed + } + + private func conditionTextLines() -> [String] { + self.clauses.enumerated().map { index, clause in let predicate = "\(clause.predicate.metric.editorLabel) \(clause.predicate.comparison.symbol) " + "\(Int(clause.predicate.threshold.rounded()))%" guard index > 0, let combinator = clause.combinator else { return predicate } @@ -223,7 +268,11 @@ extension MenuBarLayoutConditional { ? L("menu_bar_layout_conditional_and") : L("menu_bar_layout_conditional_or") return "\(joiner) \(predicate)" - }.joined(separator: " ") + } + } + + func editorSummary(provider: UsageProvider?) -> String { + let condition = self.conditionTextLines().joined(separator: " ") return L( "menu_bar_layout_conditional_summary", condition, diff --git a/Sources/CodexBar/MenuBarLayoutEditor.swift b/Sources/CodexBar/MenuBarLayoutEditor.swift index 785399fc37..9715238934 100644 --- a/Sources/CodexBar/MenuBarLayoutEditor.swift +++ b/Sources/CodexBar/MenuBarLayoutEditor.swift @@ -316,6 +316,10 @@ struct MenuBarLayoutEditor: View { MenuBarLayoutConditionalEditorSheet( draft: draft, provider: self.persistenceProvider, + existingNames: Set( + self.settings.menuBarLayoutConditionals.map { + $0.name.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() + }), onSave: self.saveConditionalDraft) } @@ -572,61 +576,97 @@ struct MenuBarLayoutEditor: View { private var conditionalsPalette: some View { VStack(alignment: .leading, spacing: 6) { - Text(L("menu_bar_layout_group_conditionals")) - .font(.caption) - .fontWeight(.semibold) - .foregroundStyle(.secondary) - - LazyVGrid(columns: [GridItem(.adaptive(minimum: 88), spacing: 6)], alignment: .leading, spacing: 6) { - ForEach( - Array(self.settings.menuBarLayoutConditionals.enumerated()), - id: \.offset) - { index, conditional in - Button { - self.write(MenuBarLayoutEditorMutations.append(.conditional(conditional), to: self.layout)) - } label: { - MenuBarLayoutChipLabel( - title: conditional.editorSummary(provider: self.persistenceProvider), - systemImage: "switch.2", - isSelected: false) - } - .buttonStyle(.plain) - .draggable(MenuBarLayoutDragItem.palette(.conditional(conditional))) - .focusable() - .onKeyPress(keys: [.space, .return], phases: [.down]) { _ in - self.write(MenuBarLayoutEditorMutations.append(.conditional(conditional), to: self.layout)) - return .handled - } - .accessibilityLabel(conditional.editorSummary(provider: self.persistenceProvider)) - .contextMenu { - Button(L("menu_bar_layout_conditional_edit")) { - self.conditionalDraft = MenuBarLayoutConditionalDraft( - mode: .editLibrary(index), - conditional: conditional) - } - Button(L("menu_bar_layout_conditional_remove"), role: .destructive) { - self.settings.menuBarLayoutConditionals.remove(at: index) - } - } - } - + HStack { + Text(L("menu_bar_layout_group_conditionals")) + .font(.caption) + .fontWeight(.semibold) + .foregroundStyle(.secondary) + Spacer() Button { self.conditionalDraft = MenuBarLayoutConditionalDraft(mode: .create, conditional: .defaultValue) } label: { - MenuBarLayoutChipLabel( - title: L("menu_bar_layout_conditional_add"), - systemImage: "plus.circle", - isSelected: false) + Image(systemName: "plus.circle") } .buttonStyle(.plain) - .focusable() - .onKeyPress(keys: [.space, .return], phases: [.down]) { _ in - self.conditionalDraft = MenuBarLayoutConditionalDraft(mode: .create, conditional: .defaultValue) - return .handled - } + .help(L("menu_bar_layout_conditional_add")) .accessibilityLabel(L("menu_bar_layout_conditional_add")) } + + if self.settings.menuBarLayoutConditionals.isEmpty { + Text(L("menu_bar_layout_conditional_none")) + .font(.caption) + .foregroundStyle(.tertiary) + } else { + LazyVGrid( + columns: [GridItem(.adaptive(minimum: 180), spacing: 6)], + alignment: .leading, + spacing: 6) + { + ForEach( + Array(self.settings.menuBarLayoutConditionals.enumerated()), + id: \.offset) + { index, conditional in + self.conditionalPaletteChip(index: index, conditional: conditional) + } + } + } + } + } + + private func conditionalPaletteChip( + index: Int, + conditional: MenuBarLayoutConditional) + -> some View + { + Button { + self.write(MenuBarLayoutEditorMutations.append(.conditional(conditional), to: self.layout)) + } label: { + MenuBarLayoutChipLabel( + title: conditional.displayName, + systemImage: "switch.2", + isSelected: false) + } + .buttonStyle(.plain) + .draggable(MenuBarLayoutDragItem.palette(.conditional(conditional))) + .focusable() + .onKeyPress(keys: [.space, .return], phases: [.down]) { _ in + self.write(MenuBarLayoutEditorMutations.append(.conditional(conditional), to: self.layout)) + return .handled + } + .accessibilityLabel(conditional.displayName) + .contextMenu { + Button(L("menu_bar_layout_conditional_edit")) { + self.conditionalDraft = MenuBarLayoutConditionalDraft( + mode: .editLibrary(index), + conditional: conditional) + } + Button(L("menu_bar_layout_conditional_duplicate")) { + self.duplicateConditional(at: index) + } + Button(L("menu_bar_layout_conditional_remove"), role: .destructive) { + self.settings.menuBarLayoutConditionals.remove(at: index) + } + } + } + + private func duplicateConditional(at index: Int) { + guard self.settings.menuBarLayoutConditionals.indices.contains(index) else { return } + var copy = self.settings.menuBarLayoutConditionals[index] + copy.name = self.uniqueConditionalName(for: copy.name) + self.settings.menuBarLayoutConditionals.append(copy) + } + + private func uniqueConditionalName(for currentName: String) -> String { + let existing = Set(self.settings.menuBarLayoutConditionals.map { $0.name.lowercased() }) + let base = currentName.trimmingCharacters(in: .whitespacesAndNewlines) + let stem = base.isEmpty ? L("menu_bar_layout_token_conditional") : base + var candidate = "\(stem) (copy)" + var n = 2 + while existing.contains(candidate.lowercased()) { + candidate = "\(stem) (copy \(n))" + n += 1 } + return candidate } private var displayOptions: some View { @@ -978,7 +1018,7 @@ extension MenuBarLayoutGap { extension MenuBarLayoutToken { func editorLabel(provider: UsageProvider?) -> String { if case let .conditional(conditional) = self { - return conditional.editorSummary(provider: provider) + return conditional.displayName } if let providerLabel = self.providerEditorLabel(provider: provider) { return providerLabel @@ -1023,6 +1063,7 @@ extension MenuBarLayoutToken { case .separatorDot: "·" case .space: L("menu_bar_layout_token_space") case .conditional: L("menu_bar_layout_token_conditional") + case .hidden: L("menu_bar_layout_conditional_hide") } } @@ -1050,6 +1091,7 @@ extension MenuBarLayoutToken { case .separatorDot: "smallcircle.filled.circle" case .space: "space" case .conditional: "switch.2" + case .hidden: "eye.slash" } } } diff --git a/Sources/CodexBar/MenuBarLayoutRenderer.swift b/Sources/CodexBar/MenuBarLayoutRenderer.swift index 8cef333399..613abf770f 100644 --- a/Sources/CodexBar/MenuBarLayoutRenderer.swift +++ b/Sources/CodexBar/MenuBarLayoutRenderer.swift @@ -396,6 +396,8 @@ final class MenuBarLayoutRenderer { return self.textToken("·", accessibilityText: nil, attributes: style.attributes) case .space: return self.textToken(" ", accessibilityText: nil, attributes: style.attributes) + case .hidden: + return self.textToken("", accessibilityText: nil, attributes: style.attributes) case let .conditional(conditional): guard depth < MenuBarLayoutToken.maxConditionalDepth else { return self.textToken( diff --git a/Sources/CodexBar/Resources/ar.lproj/Localizable.strings b/Sources/CodexBar/Resources/ar.lproj/Localizable.strings index 23c7b51188..8decce1ded 100644 --- a/Sources/CodexBar/Resources/ar.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ar.lproj/Localizable.strings @@ -1500,6 +1500,12 @@ "menu_bar_layout_conditional_then" = "فعندئذ اعرض"; "menu_bar_layout_conditional_else" = "وإلا اعرض"; "menu_bar_layout_conditional_and" = "و"; +"menu_bar_layout_conditional_none" = "لا توجد شروط بعد"; +"menu_bar_layout_conditional_hide" = "إخفاء"; +"menu_bar_layout_conditional_name" = "الاسم"; +"menu_bar_layout_conditional_name_placeholder" = "مثال: فحص الجلسة"; +"menu_bar_layout_conditional_name_error" = "أدخل اسماً فريداً"; +"menu_bar_layout_conditional_duplicate" = "تكرار"; "menu_bar_layout_conditional_or" = "أو"; "menu_bar_layout_conditional_add_condition" = "إضافة شرط"; "menu_bar_layout_conditional_summary" = "إذا كان %1$@ فعندئذ %2$@ وإلا %3$@"; diff --git a/Sources/CodexBar/Resources/ca.lproj/Localizable.strings b/Sources/CodexBar/Resources/ca.lproj/Localizable.strings index 22574e0caf..8d013263e4 100644 --- a/Sources/CodexBar/Resources/ca.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ca.lproj/Localizable.strings @@ -1499,6 +1499,12 @@ "menu_bar_layout_conditional_then" = "Mostra llavors"; "menu_bar_layout_conditional_else" = "Mostra en cas contrari"; "menu_bar_layout_conditional_and" = "i"; +"menu_bar_layout_conditional_none" = "Encara no hi ha condicionals"; +"menu_bar_layout_conditional_hide" = "Amaga"; +"menu_bar_layout_conditional_name" = "Nom"; +"menu_bar_layout_conditional_name_placeholder" = "p. ex. Comprovació de la sessió"; +"menu_bar_layout_conditional_name_error" = "Introdueix un nom únic"; +"menu_bar_layout_conditional_duplicate" = "Duplica"; "menu_bar_layout_conditional_or" = "o"; "menu_bar_layout_conditional_add_condition" = "Afegeix una condició"; "menu_bar_layout_conditional_summary" = "Si %1$@ llavors %2$@ en cas contrari %3$@"; diff --git a/Sources/CodexBar/Resources/de.lproj/Localizable.strings b/Sources/CodexBar/Resources/de.lproj/Localizable.strings index 2b95bbadeb..0398cdad37 100644 --- a/Sources/CodexBar/Resources/de.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/de.lproj/Localizable.strings @@ -1498,4 +1498,10 @@ "menu_bar_layout_conditional_and" = "und"; "menu_bar_layout_conditional_or" = "oder"; "menu_bar_layout_conditional_add_condition" = "Bedingung hinzufügen"; +"menu_bar_layout_conditional_none" = "Noch keine Bedingungen"; +"menu_bar_layout_conditional_hide" = "Ausblenden"; +"menu_bar_layout_conditional_name" = "Name"; +"menu_bar_layout_conditional_name_placeholder" = "z. B. Sitzungsprüfung"; +"menu_bar_layout_conditional_name_error" = "Geben Sie einen eindeutigen Namen ein"; +"menu_bar_layout_conditional_duplicate" = "Duplizieren"; "menu_bar_layout_conditional_summary" = "Wenn %1$@ dann %2$@ sonst %3$@"; diff --git a/Sources/CodexBar/Resources/en.lproj/Localizable.strings b/Sources/CodexBar/Resources/en.lproj/Localizable.strings index 89d04ed720..d740b2fdbe 100644 --- a/Sources/CodexBar/Resources/en.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/en.lproj/Localizable.strings @@ -1501,4 +1501,10 @@ "menu_bar_layout_conditional_and" = "and"; "menu_bar_layout_conditional_or" = "or"; "menu_bar_layout_conditional_add_condition" = "Add Condition"; +"menu_bar_layout_conditional_none" = "No conditionals yet"; +"menu_bar_layout_conditional_hide" = "Hide"; +"menu_bar_layout_conditional_duplicate" = "Duplicate"; +"menu_bar_layout_conditional_name" = "Name"; +"menu_bar_layout_conditional_name_placeholder" = "e.g. Session check"; +"menu_bar_layout_conditional_name_error" = "Enter a unique name"; "menu_bar_layout_conditional_summary" = "If %1$@ then %2$@ else %3$@"; diff --git a/Sources/CodexBar/Resources/es.lproj/Localizable.strings b/Sources/CodexBar/Resources/es.lproj/Localizable.strings index 2ae8b9597f..2aa3e6e32d 100644 --- a/Sources/CodexBar/Resources/es.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/es.lproj/Localizable.strings @@ -1496,4 +1496,10 @@ "menu_bar_layout_conditional_and" = "y"; "menu_bar_layout_conditional_or" = "o"; "menu_bar_layout_conditional_add_condition" = "Añadir condición"; +"menu_bar_layout_conditional_none" = "Aún no hay condiciones"; +"menu_bar_layout_conditional_hide" = "Ocultar"; +"menu_bar_layout_conditional_name" = "Nombre"; +"menu_bar_layout_conditional_name_placeholder" = "p. ej. Comprobación de sesión"; +"menu_bar_layout_conditional_name_error" = "Introduce un nombre único"; +"menu_bar_layout_conditional_duplicate" = "Duplicar"; "menu_bar_layout_conditional_summary" = "Si %1$@ entonces %2$@ si no %3$@"; diff --git a/Sources/CodexBar/Resources/fa.lproj/Localizable.strings b/Sources/CodexBar/Resources/fa.lproj/Localizable.strings index 7e28e6f40c..afe8c3b1d9 100644 --- a/Sources/CodexBar/Resources/fa.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/fa.lproj/Localizable.strings @@ -1500,6 +1500,12 @@ "menu_bar_layout_conditional_then" = "سپس نمایش بده"; "menu_bar_layout_conditional_else" = "در غیر این صورت نمایش بده"; "menu_bar_layout_conditional_and" = "و"; +"menu_bar_layout_conditional_none" = "هنوز شرطی وجود ندارد"; +"menu_bar_layout_conditional_hide" = "پنهان کن"; +"menu_bar_layout_conditional_name" = "نام"; +"menu_bar_layout_conditional_name_placeholder" = "مثلاً: بررسی جلسه"; +"menu_bar_layout_conditional_name_error" = "نامی منحصربهفرد وارد کنید"; +"menu_bar_layout_conditional_duplicate" = "تکرار"; "menu_bar_layout_conditional_or" = "یا"; "menu_bar_layout_conditional_add_condition" = "افزودن شرط"; "menu_bar_layout_conditional_summary" = "اگر %1$@ سپس %2$@ در غیر این صورت %3$@"; diff --git a/Sources/CodexBar/Resources/fr.lproj/Localizable.strings b/Sources/CodexBar/Resources/fr.lproj/Localizable.strings index 9bbdc67132..5e44decfea 100644 --- a/Sources/CodexBar/Resources/fr.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/fr.lproj/Localizable.strings @@ -1497,4 +1497,10 @@ "menu_bar_layout_conditional_and" = "et"; "menu_bar_layout_conditional_or" = "ou"; "menu_bar_layout_conditional_add_condition" = "Ajouter une condition"; +"menu_bar_layout_conditional_none" = "Aucun conditionnel pour l\u2019instant"; +"menu_bar_layout_conditional_hide" = "Masquer"; +"menu_bar_layout_conditional_name" = "Nom"; +"menu_bar_layout_conditional_name_placeholder" = "p. ex. Vérification de session"; +"menu_bar_layout_conditional_name_error" = "Saisissez un nom unique"; +"menu_bar_layout_conditional_duplicate" = "Dupliquer"; "menu_bar_layout_conditional_summary" = "Si %1$@ alors %2$@ sinon %3$@"; diff --git a/Sources/CodexBar/Resources/gl.lproj/Localizable.strings b/Sources/CodexBar/Resources/gl.lproj/Localizable.strings index ede8214a89..f0b4cf99fc 100644 --- a/Sources/CodexBar/Resources/gl.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/gl.lproj/Localizable.strings @@ -1497,4 +1497,10 @@ "menu_bar_layout_conditional_and" = "e"; "menu_bar_layout_conditional_or" = "ou"; "menu_bar_layout_conditional_add_condition" = "Engadir condición"; +"menu_bar_layout_conditional_none" = "Aínda non hai condicións"; +"menu_bar_layout_conditional_hide" = "Agochar"; +"menu_bar_layout_conditional_name" = "Nome"; +"menu_bar_layout_conditional_name_placeholder" = "p. ex. Comprobación de sesión"; +"menu_bar_layout_conditional_name_error" = "Introduce un nome único"; +"menu_bar_layout_conditional_duplicate" = "Duplicar"; "menu_bar_layout_conditional_summary" = "Se %1$@ entón %2$@ se non %3$@"; diff --git a/Sources/CodexBar/Resources/id.lproj/Localizable.strings b/Sources/CodexBar/Resources/id.lproj/Localizable.strings index fabce12e39..0675f6932d 100644 --- a/Sources/CodexBar/Resources/id.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/id.lproj/Localizable.strings @@ -1499,6 +1499,12 @@ "menu_bar_layout_conditional_then" = "Lalu tampilkan"; "menu_bar_layout_conditional_else" = "Jika tidak, tampilkan"; "menu_bar_layout_conditional_and" = "dan"; +"menu_bar_layout_conditional_none" = "Belum ada kondisional"; +"menu_bar_layout_conditional_hide" = "Sembunyikan"; +"menu_bar_layout_conditional_name" = "Nama"; +"menu_bar_layout_conditional_name_placeholder" = "mis. Pemeriksaan sesi"; +"menu_bar_layout_conditional_name_error" = "Masukkan nama yang unik"; +"menu_bar_layout_conditional_duplicate" = "Duplikat"; "menu_bar_layout_conditional_or" = "atau"; "menu_bar_layout_conditional_add_condition" = "Tambah Kondisi"; "menu_bar_layout_conditional_summary" = "Jika %1$@ maka %2$@, jika tidak %3$@"; diff --git a/Sources/CodexBar/Resources/it.lproj/Localizable.strings b/Sources/CodexBar/Resources/it.lproj/Localizable.strings index 335df6c4aa..160e4b0820 100644 --- a/Sources/CodexBar/Resources/it.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/it.lproj/Localizable.strings @@ -1501,4 +1501,10 @@ "menu_bar_layout_conditional_and" = "e"; "menu_bar_layout_conditional_or" = "o"; "menu_bar_layout_conditional_add_condition" = "Aggiungi condizione"; +"menu_bar_layout_conditional_none" = "Ancora nessun condizionale"; +"menu_bar_layout_conditional_hide" = "Nascondi"; +"menu_bar_layout_conditional_name" = "Nome"; +"menu_bar_layout_conditional_name_placeholder" = "ad es. Controllo sessione"; +"menu_bar_layout_conditional_name_error" = "Inserisci un nome univoco"; +"menu_bar_layout_conditional_duplicate" = "Duplica"; "menu_bar_layout_conditional_summary" = "Se %1$@ allora %2$@ altrimenti %3$@"; diff --git a/Sources/CodexBar/Resources/ja.lproj/Localizable.strings b/Sources/CodexBar/Resources/ja.lproj/Localizable.strings index f1ba83656e..0cf97f8dbe 100644 --- a/Sources/CodexBar/Resources/ja.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ja.lproj/Localizable.strings @@ -1496,6 +1496,12 @@ "menu_bar_layout_conditional_then" = "表示"; "menu_bar_layout_conditional_else" = "それ以外は表示"; "menu_bar_layout_conditional_and" = "かつ"; +"menu_bar_layout_conditional_none" = "条件はまだありません"; +"menu_bar_layout_conditional_hide" = "非表示"; +"menu_bar_layout_conditional_name" = "名前"; +"menu_bar_layout_conditional_name_placeholder" = "例:セッションチェック"; +"menu_bar_layout_conditional_name_error" = "一意の名前を入力してください"; +"menu_bar_layout_conditional_duplicate" = "複製"; "menu_bar_layout_conditional_or" = "または"; "menu_bar_layout_conditional_add_condition" = "条件を追加"; "menu_bar_layout_conditional_summary" = "もし %1$@ なら %2$@ を表示し、それ以外は %3$@ を表示"; diff --git a/Sources/CodexBar/Resources/ko.lproj/Localizable.strings b/Sources/CodexBar/Resources/ko.lproj/Localizable.strings index b817ef9806..028da949ec 100644 --- a/Sources/CodexBar/Resources/ko.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ko.lproj/Localizable.strings @@ -1465,4 +1465,10 @@ "menu_bar_layout_conditional_and" = "그리고"; "menu_bar_layout_conditional_or" = "또는"; "menu_bar_layout_conditional_add_condition" = "조건 추가"; +"menu_bar_layout_conditional_none" = "아직 조건이 없습니다"; +"menu_bar_layout_conditional_hide" = "숨기기"; +"menu_bar_layout_conditional_name" = "이름"; +"menu_bar_layout_conditional_name_placeholder" = "예: 세션 확인"; +"menu_bar_layout_conditional_name_error" = "고유한 이름을 입력하세요"; +"menu_bar_layout_conditional_duplicate" = "복제"; "menu_bar_layout_conditional_summary" = "만약 %1$@이면 %2$@을 표시하고, 그렇지 않으면 %3$@을 표시"; diff --git a/Sources/CodexBar/Resources/nl.lproj/Localizable.strings b/Sources/CodexBar/Resources/nl.lproj/Localizable.strings index 7917d24154..97c969b24a 100644 --- a/Sources/CodexBar/Resources/nl.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/nl.lproj/Localizable.strings @@ -1497,4 +1497,10 @@ "menu_bar_layout_conditional_and" = "en"; "menu_bar_layout_conditional_or" = "of"; "menu_bar_layout_conditional_add_condition" = "Voorwaarde toevoegen"; +"menu_bar_layout_conditional_none" = "Nog geen voorwaarden"; +"menu_bar_layout_conditional_hide" = "Verbergen"; +"menu_bar_layout_conditional_name" = "Naam"; +"menu_bar_layout_conditional_name_placeholder" = "bijv. Sessiecontrole"; +"menu_bar_layout_conditional_name_error" = "Voer een unieke naam in"; +"menu_bar_layout_conditional_duplicate" = "Dupliceren"; "menu_bar_layout_conditional_summary" = "Als %1$@ dan %2$@ anders %3$@"; diff --git a/Sources/CodexBar/Resources/pl.lproj/Localizable.strings b/Sources/CodexBar/Resources/pl.lproj/Localizable.strings index 9b3fa068e8..d7979b6c1c 100644 --- a/Sources/CodexBar/Resources/pl.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/pl.lproj/Localizable.strings @@ -1499,6 +1499,12 @@ "menu_bar_layout_conditional_then" = "Pokaż"; "menu_bar_layout_conditional_else" = "W przeciwnym razie pokaż"; "menu_bar_layout_conditional_and" = "i"; +"menu_bar_layout_conditional_none" = "Brak jeszcze warunków"; +"menu_bar_layout_conditional_hide" = "Ukryj"; +"menu_bar_layout_conditional_name" = "Nazwa"; +"menu_bar_layout_conditional_name_placeholder" = "np. Sprawdzenie sesji"; +"menu_bar_layout_conditional_name_error" = "Wprowadź unikalną nazwę"; +"menu_bar_layout_conditional_duplicate" = "Duplikuj"; "menu_bar_layout_conditional_or" = "lub"; "menu_bar_layout_conditional_add_condition" = "Dodaj warunek"; "menu_bar_layout_conditional_summary" = "Jeśli %1$@, to %2$@, w przeciwnym razie %3$@"; diff --git a/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings b/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings index 6d1c988d10..3f20064ee8 100644 --- a/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings @@ -1498,4 +1498,10 @@ "menu_bar_layout_conditional_and" = "e"; "menu_bar_layout_conditional_or" = "ou"; "menu_bar_layout_conditional_add_condition" = "Adicionar condição"; +"menu_bar_layout_conditional_none" = "Nenhum condicional ainda"; +"menu_bar_layout_conditional_hide" = "Ocultar"; +"menu_bar_layout_conditional_name" = "Nome"; +"menu_bar_layout_conditional_name_placeholder" = "ex.: Verificação de sessão"; +"menu_bar_layout_conditional_name_error" = "Digite um nome exclusivo"; +"menu_bar_layout_conditional_duplicate" = "Duplicar"; "menu_bar_layout_conditional_summary" = "Se %1$@ então %2$@, senão %3$@"; diff --git a/Sources/CodexBar/Resources/ru.lproj/Localizable.strings b/Sources/CodexBar/Resources/ru.lproj/Localizable.strings index 2fda09d9fe..99fc3aaf35 100644 --- a/Sources/CodexBar/Resources/ru.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ru.lproj/Localizable.strings @@ -1499,4 +1499,10 @@ "menu_bar_layout_conditional_and" = "и"; "menu_bar_layout_conditional_or" = "или"; "menu_bar_layout_conditional_add_condition" = "Добавить условие"; +"menu_bar_layout_conditional_none" = "Условий пока нет"; +"menu_bar_layout_conditional_hide" = "Скрыть"; +"menu_bar_layout_conditional_name" = "Название"; +"menu_bar_layout_conditional_name_placeholder" = "напр., Проверка сеанса"; +"menu_bar_layout_conditional_name_error" = "Введите уникальное название"; +"menu_bar_layout_conditional_duplicate" = "Дублировать"; "menu_bar_layout_conditional_summary" = "Если %1$@, то показать %2$@, иначе показать %3$@"; diff --git a/Sources/CodexBar/Resources/sv.lproj/Localizable.strings b/Sources/CodexBar/Resources/sv.lproj/Localizable.strings index 2dd043ba41..74d7189079 100644 --- a/Sources/CodexBar/Resources/sv.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/sv.lproj/Localizable.strings @@ -1497,4 +1497,10 @@ "menu_bar_layout_conditional_and" = "och"; "menu_bar_layout_conditional_or" = "eller"; "menu_bar_layout_conditional_add_condition" = "Lägg till villkor"; +"menu_bar_layout_conditional_none" = "Inga villkor ännu"; +"menu_bar_layout_conditional_hide" = "Dölj"; +"menu_bar_layout_conditional_name" = "Namn"; +"menu_bar_layout_conditional_name_placeholder" = "t.ex. Sessionskontroll"; +"menu_bar_layout_conditional_name_error" = "Ange ett unikt namn"; +"menu_bar_layout_conditional_duplicate" = "Duplicera"; "menu_bar_layout_conditional_summary" = "Om %1$@ visa %2$@ annars %3$@"; diff --git a/Sources/CodexBar/Resources/th.lproj/Localizable.strings b/Sources/CodexBar/Resources/th.lproj/Localizable.strings index 0241981340..47dd4d99ca 100644 --- a/Sources/CodexBar/Resources/th.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/th.lproj/Localizable.strings @@ -1502,4 +1502,10 @@ "menu_bar_layout_conditional_and" = "และ"; "menu_bar_layout_conditional_or" = "หรือ"; "menu_bar_layout_conditional_add_condition" = "เพิ่มเงื่อนไข"; +"menu_bar_layout_conditional_none" = "ยังไม่มีเงื่อนไข"; +"menu_bar_layout_conditional_hide" = "ซ่อน"; +"menu_bar_layout_conditional_name" = "ชื่อ"; +"menu_bar_layout_conditional_name_placeholder" = "เช่น ตรวจสอบเซสชัน"; +"menu_bar_layout_conditional_name_error" = "ป้อนชื่อที่ไม่ซ้ำกัน"; +"menu_bar_layout_conditional_duplicate" = "ทำสำเนา"; "menu_bar_layout_conditional_summary" = "ถ้า %1$@ จากนั้น %2$@ มิฉะนั้น %3$@"; diff --git a/Sources/CodexBar/Resources/tr.lproj/Localizable.strings b/Sources/CodexBar/Resources/tr.lproj/Localizable.strings index ec2c837e6c..a5beefa186 100644 --- a/Sources/CodexBar/Resources/tr.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/tr.lproj/Localizable.strings @@ -1500,4 +1500,10 @@ "menu_bar_layout_conditional_and" = "ve"; "menu_bar_layout_conditional_or" = "veya"; "menu_bar_layout_conditional_add_condition" = "Koşul Ekle"; +"menu_bar_layout_conditional_none" = "Henüz koşullu yok"; +"menu_bar_layout_conditional_hide" = "Gizle"; +"menu_bar_layout_conditional_name" = "Ad"; +"menu_bar_layout_conditional_name_placeholder" = "örn. Oturum kontrolü"; +"menu_bar_layout_conditional_name_error" = "Benzersiz bir ad girin"; +"menu_bar_layout_conditional_duplicate" = "Çoğalt"; "menu_bar_layout_conditional_summary" = "Eğer %1$@ ise %2$@, değilse %3$@ göster"; diff --git a/Sources/CodexBar/Resources/uk.lproj/Localizable.strings b/Sources/CodexBar/Resources/uk.lproj/Localizable.strings index eb9a737f9e..e12f9824b6 100644 --- a/Sources/CodexBar/Resources/uk.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/uk.lproj/Localizable.strings @@ -1498,4 +1498,10 @@ "menu_bar_layout_conditional_and" = "і"; "menu_bar_layout_conditional_or" = "або"; "menu_bar_layout_conditional_add_condition" = "Додати умову"; +"menu_bar_layout_conditional_none" = "Умов поки немає"; +"menu_bar_layout_conditional_hide" = "Приховати"; +"menu_bar_layout_conditional_name" = "Назва"; +"menu_bar_layout_conditional_name_placeholder" = "напр., Перевірка сеансу"; +"menu_bar_layout_conditional_name_error" = "Введіть унікальну назву"; +"menu_bar_layout_conditional_duplicate" = "Дублювати"; "menu_bar_layout_conditional_summary" = "Якщо %1$@ тоді %2$@ інакше %3$@"; diff --git a/Sources/CodexBar/Resources/vi.lproj/Localizable.strings b/Sources/CodexBar/Resources/vi.lproj/Localizable.strings index d134ed986e..6b0a14549b 100644 --- a/Sources/CodexBar/Resources/vi.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/vi.lproj/Localizable.strings @@ -1499,4 +1499,10 @@ "menu_bar_layout_conditional_and" = "và"; "menu_bar_layout_conditional_or" = "hoặc"; "menu_bar_layout_conditional_add_condition" = "Thêm điều kiện"; +"menu_bar_layout_conditional_none" = "Chưa có điều kiện nào"; +"menu_bar_layout_conditional_hide" = "Ẩn"; +"menu_bar_layout_conditional_name" = "Tên"; +"menu_bar_layout_conditional_name_placeholder" = "ví dụ: Kiểm tra phiên"; +"menu_bar_layout_conditional_name_error" = "Nhập tên duy nhất"; +"menu_bar_layout_conditional_duplicate" = "Nhân bản"; "menu_bar_layout_conditional_summary" = "Nếu %1$@ thì %2$@ ngược lại %3$@"; diff --git a/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings b/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings index 8b9593fa5f..6c9085fb6b 100644 --- a/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings @@ -1476,4 +1476,10 @@ "menu_bar_layout_conditional_and" = "和"; "menu_bar_layout_conditional_or" = "或"; "menu_bar_layout_conditional_add_condition" = "添加条件"; +"menu_bar_layout_conditional_none" = "暂无条件"; +"menu_bar_layout_conditional_hide" = "隐藏"; +"menu_bar_layout_conditional_name" = "名称"; +"menu_bar_layout_conditional_name_placeholder" = "例如:会话检查"; +"menu_bar_layout_conditional_name_error" = "请输入唯一名称"; +"menu_bar_layout_conditional_duplicate" = "复制"; "menu_bar_layout_conditional_summary" = "如果 %1$@ 则 %2$@ 否则 %3$@"; diff --git a/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings b/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings index e86baab8c3..a565d0b005 100644 --- a/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings @@ -1529,4 +1529,10 @@ "menu_bar_layout_conditional_and" = "和"; "menu_bar_layout_conditional_or" = "或"; "menu_bar_layout_conditional_add_condition" = "新增條件"; +"menu_bar_layout_conditional_none" = "暫無條件"; +"menu_bar_layout_conditional_hide" = "隱藏"; +"menu_bar_layout_conditional_name" = "名稱"; +"menu_bar_layout_conditional_name_placeholder" = "例如:工作階段檢查"; +"menu_bar_layout_conditional_name_error" = "請輸入唯一名稱"; +"menu_bar_layout_conditional_duplicate" = "複製"; "menu_bar_layout_conditional_summary" = "如果 %1$@ 則 %2$@ 否則 %3$@"; diff --git a/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift b/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift index 3daab6df34..48f3300eb3 100644 --- a/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift +++ b/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift @@ -605,6 +605,23 @@ struct MenuBarLayoutRendererTests { #expect(output.attributedTitle.string == "in 2h") } + @Test + func `hidden branch renders nothing`() { + let renderer = MenuBarLayoutRenderer() + // Session is 25%, so > 50 fails and the else branch (.hidden) wins, contributing nothing. + let conditional = MenuBarLayoutConditional( + clauses: [self.clause(metric: .session, comparison: .greaterThan, threshold: 50)], + thenToken: .percent(window: .session), + elseToken: .hidden) + + let output = renderer.render( + layout: MenuBarLayout(lines: [[.conditional(conditional)]]), + data: self.data(), + icon: nil, + options: self.options()) + #expect(output.attributedTitle.string.isEmpty) + } + @Test func `conditional and requires all predicates`() { let renderer = MenuBarLayoutRenderer() diff --git a/Tests/CodexBarTests/MenuBarLayoutTests.swift b/Tests/CodexBarTests/MenuBarLayoutTests.swift index 7a3bad2a5d..78aaecd80d 100644 --- a/Tests/CodexBarTests/MenuBarLayoutTests.swift +++ b/Tests/CodexBarTests/MenuBarLayoutTests.swift @@ -128,6 +128,48 @@ struct MenuBarLayoutTests { #expect(forcedFirst.clauses[0].combinator == nil) } + @Test + @MainActor + func `conditional display name falls back when unnamed then uses its name`() { + let unnamed = MenuBarLayoutConditional.defaultValue + #expect(!unnamed.displayName.isEmpty) + + let named = MenuBarLayoutConditional( + name: "Gate", + clauses: [MenuBarConditionalClause( + combinator: nil, + predicate: MenuBarConditionalPredicate(metric: .session, comparison: .greaterThan, threshold: 30))], + thenToken: .percent(window: .automatic), + elseToken: .hidden) + #expect(named.displayName == "Gate") + } + + @Test + func `conditional name survives codable round trip and legacy form decodes unnamed`() throws { + let named = MenuBarLayoutConditional( + name: " Zeroth Gate ", + clauses: [MenuBarConditionalClause( + combinator: nil, + predicate: MenuBarConditionalPredicate(metric: .session, comparison: .greaterThan, threshold: 30))], + thenToken: .percent(window: .automatic), + elseToken: .hidden) + + let data = try JSONEncoder().encode(named) + let decoded = try JSONDecoder().decode(MenuBarLayoutConditional.self, from: data) + #expect(decoded == named) + #expect(decoded.name == " Zeroth Gate ") + + // An older persisted conditional without a `name` key must decode with an empty name. + guard var json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else { + Issue.record("expected a JSON object") + return + } + json.removeValue(forKey: "name") + let legacyData = try JSONSerialization.data(withJSONObject: json) + let legacy = try JSONDecoder().decode(MenuBarLayoutConditional.self, from: legacyData) + #expect(legacy.name.isEmpty) + } + @Test @MainActor func `conditionals library and layout persist across reload`() { From 48faf424a79cc6ad6482a690a57e7ed6dd30517b Mon Sep 17 00:00:00 2001 From: William Mitchell Date: Wed, 19 Aug 2026 17:36:31 +0100 Subject: [PATCH 03/11] Store conditionals by reference and fix hidden-branch spacing Move placed conditional tokens to reference-by-ID: layouts store a conditional's UUID and the library is the single source of truth, so library edits reach every placed copy and removal strips references from all layouts. Pre-resolve conditionals in the renderer so hidden branches leave no orphaned thin-space separators, key the title cache on the library, guard the edit sheet's clause bindings against stale indices, localize duplicate naming, and parenthesize mixed AND/OR summaries to match left-fold evaluation. --- Sources/CodexBar/MenuBarLayout.swift | 66 +++++-- .../MenuBarLayoutConditionalEditor.swift | 81 +++++++-- Sources/CodexBar/MenuBarLayoutEditor.swift | 106 +++++------ Sources/CodexBar/MenuBarLayoutRenderer.swift | 63 +++++-- .../Resources/ar.lproj/Localizable.strings | 3 + .../Resources/ca.lproj/Localizable.strings | 3 + .../Resources/de.lproj/Localizable.strings | 3 + .../Resources/en.lproj/Localizable.strings | 3 + .../Resources/es.lproj/Localizable.strings | 3 + .../Resources/fa.lproj/Localizable.strings | 3 + .../Resources/fr.lproj/Localizable.strings | 3 + .../Resources/gl.lproj/Localizable.strings | 3 + .../Resources/id.lproj/Localizable.strings | 3 + .../Resources/it.lproj/Localizable.strings | 3 + .../Resources/ja.lproj/Localizable.strings | 3 + .../Resources/ko.lproj/Localizable.strings | 3 + .../Resources/nl.lproj/Localizable.strings | 3 + .../Resources/pl.lproj/Localizable.strings | 3 + .../Resources/pt-BR.lproj/Localizable.strings | 3 + .../Resources/ru.lproj/Localizable.strings | 3 + .../Resources/sv.lproj/Localizable.strings | 3 + .../Resources/th.lproj/Localizable.strings | 3 + .../Resources/tr.lproj/Localizable.strings | 3 + .../Resources/uk.lproj/Localizable.strings | 3 + .../Resources/vi.lproj/Localizable.strings | 3 + .../zh-Hans.lproj/Localizable.strings | 3 + .../zh-Hant.lproj/Localizable.strings | 3 + Sources/CodexBar/SettingsStore+Defaults.swift | 14 ++ .../StatusItemController+Animation.swift | 3 +- ...tatusItemController+CountdownRefresh.swift | 12 +- ...StatusItemController+IconObservation.swift | 20 ++- .../StatusItemController+MenuBarLayout.swift | 1 + .../MenuBarLayoutEditorTests.swift | 30 +--- .../MenuBarLayoutRendererTests.swift | 167 +++++++++++++++--- Tests/CodexBarTests/MenuBarLayoutTests.swift | 104 ++++++++++- .../MenuLayoutScreenshotRenderTests.swift | 2 + .../StatusItemBalanceDisplayTests.swift | 3 + 37 files changed, 552 insertions(+), 189 deletions(-) diff --git a/Sources/CodexBar/MenuBarLayout.swift b/Sources/CodexBar/MenuBarLayout.swift index 755fa86923..4d0d2a4590 100644 --- a/Sources/CodexBar/MenuBarLayout.swift +++ b/Sources/CodexBar/MenuBarLayout.swift @@ -8,6 +8,8 @@ enum PercentWindow: String, CaseIterable, Codable, Hashable, Sendable { case automatic } +/// Deliberately mirrors `PercentWindow` but stays a separate type so predicate persistence +/// is decoupled from render-window naming. enum MenuBarConditionalMetric: String, CaseIterable, Codable, Hashable, Sendable { case session case weekly @@ -58,17 +60,20 @@ struct MenuBarConditionalClause: Codable, Hashable, Sendable { } struct MenuBarLayoutConditional: Codable, Hashable, Sendable { + let id: UUID var name: String var clauses: [MenuBarConditionalClause] // 1...4 after normalization var thenToken: MenuBarLayoutToken var elseToken: MenuBarLayoutToken init( + id: UUID = UUID(), name: String = "", clauses: [MenuBarConditionalClause], thenToken: MenuBarLayoutToken, elseToken: MenuBarLayoutToken) { + self.id = id self.name = name self.clauses = clauses self.thenToken = thenToken @@ -91,13 +96,14 @@ struct MenuBarLayoutConditional: Codable, Hashable, Sendable { self.clauses = Array(normalized) } - /// Custom Codable so older persisted conditionals without a `name` still decode (→ ""). + /// Custom Codable so older persisted conditionals without `name` or `id` still decode. private enum CodingKeys: String, CodingKey { - case name, clauses, thenToken, elseToken + case id, name, clauses, thenToken, elseToken } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decodeIfPresent(UUID.self, forKey: .id) ?? UUID() self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "" self.clauses = try container.decode([MenuBarConditionalClause].self, forKey: .clauses) self.thenToken = try container.decode(MenuBarLayoutToken.self, forKey: .thenToken) @@ -107,18 +113,21 @@ struct MenuBarLayoutConditional: Codable, Hashable, Sendable { func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.id, forKey: .id) try container.encode(self.name, forKey: .name) try container.encode(self.clauses, forKey: .clauses) try container.encode(self.thenToken, forKey: .thenToken) try container.encode(self.elseToken, forKey: .elseToken) } - static let defaultValue = MenuBarLayoutConditional( - clauses: [MenuBarConditionalClause( - combinator: nil, - predicate: MenuBarConditionalPredicate(metric: .session, comparison: .greaterThan, threshold: 0))], - thenToken: .percent(window: .session), - elseToken: .hidden) + static func makeDefault() -> MenuBarLayoutConditional { + MenuBarLayoutConditional( + clauses: [MenuBarConditionalClause( + combinator: nil, + predicate: MenuBarConditionalPredicate(metric: .session, comparison: .greaterThan, threshold: 0))], + thenToken: .percent(window: .session), + elseToken: .hidden) + } } enum MenuBarLayoutToken: Codable, Hashable, Sendable { @@ -141,7 +150,9 @@ enum MenuBarLayoutToken: Codable, Hashable, Sendable { case space /// Renders nothing; used as a conditional branch value to hide output for the other case. case hidden - indirect case conditional(MenuBarLayoutConditional) + /// References a library conditional by UUID. The conditional's content (clauses, branches) lives in + /// the conditionals library; the layout stores only its identity. + case conditional(id: UUID) } enum MenuBarLayoutSemanticWindowResolver { @@ -403,23 +414,48 @@ extension MenuBarLayout { extension MenuBarLayout { /// Every token in the layout plus all tokens reachable through conditional branches (depth-capped). - var flattenedTokens: [MenuBarLayoutToken] { + func flattenedTokens(conditionals: [MenuBarLayoutConditional]) -> [MenuBarLayoutToken] { + let byID = Dictionary(conditionals.map { ($0.id, $0) }, uniquingKeysWith: { first, _ in first }) var tokens: [MenuBarLayoutToken] = [] for token in self.lines.joined() { - token.appendFlattened(into: &tokens, depth: 0) + token.appendFlattened(into: &tokens, conditionals: byID, depth: 0) } return tokens } + + /// Returns a layout with every `.conditional(id:)` token matching `id` removed from both lines, + /// or nil when nothing referenced it (so callers never materialize an unchanged stored layout). + func removingConditional(id: UUID) -> MenuBarLayout? { + var changed = false + let filtered = self.lines.map { line in + line.filter { token in + if case let .conditional(tokenID) = token, tokenID == id { + changed = true + return false + } + return true + } + } + guard changed else { return nil } + return MenuBarLayout(lines: filtered) + } } extension MenuBarLayoutToken { static let maxConditionalDepth = 8 - func appendFlattened(into tokens: inout [MenuBarLayoutToken], depth: Int) { + func appendFlattened( + into tokens: inout [MenuBarLayoutToken], + conditionals: [UUID: MenuBarLayoutConditional], + depth: Int) + { if self == .hidden { return } tokens.append(self) - guard depth < Self.maxConditionalDepth, case let .conditional(conditional) = self else { return } - conditional.thenToken.appendFlattened(into: &tokens, depth: depth + 1) - conditional.elseToken.appendFlattened(into: &tokens, depth: depth + 1) + guard depth < Self.maxConditionalDepth, + case let .conditional(id) = self, + let conditional = conditionals[id] + else { return } + conditional.thenToken.appendFlattened(into: &tokens, conditionals: conditionals, depth: depth + 1) + conditional.elseToken.appendFlattened(into: &tokens, conditionals: conditionals, depth: depth + 1) } } diff --git a/Sources/CodexBar/MenuBarLayoutConditionalEditor.swift b/Sources/CodexBar/MenuBarLayoutConditionalEditor.swift index 6c858300d8..d468d936dd 100644 --- a/Sources/CodexBar/MenuBarLayoutConditionalEditor.swift +++ b/Sources/CodexBar/MenuBarLayoutConditionalEditor.swift @@ -4,10 +4,10 @@ import SwiftUI struct MenuBarLayoutConditionalDraft: Identifiable { enum Mode: Hashable { case create - case editLibrary(Int) - case editPlaced(MenuBarLayoutPosition) + case edit(UUID) } + /// Sheet-presentation identity only, unrelated to `conditional.id`. let id: UUID let mode: Mode var conditional: MenuBarLayoutConditional @@ -174,26 +174,50 @@ struct MenuBarLayoutConditionalEditorSheet: View { private func combinatorBinding(_ index: Int) -> Binding { Binding( - get: { self.conditional.clauses[index].combinator ?? .and }, - set: { self.conditional.clauses[index].combinator = $0 }) + get: { + guard self.conditional.clauses.indices.contains(index) else { return .and } + return self.conditional.clauses[index].combinator ?? .and + }, + set: { + guard self.conditional.clauses.indices.contains(index) else { return } + self.conditional.clauses[index].combinator = $0 + }) } private func metricBinding(_ index: Int) -> Binding { Binding( - get: { self.conditional.clauses[index].predicate.metric }, - set: { self.conditional.clauses[index].predicate.metric = $0 }) + get: { + guard self.conditional.clauses.indices.contains(index) else { return .session } + return self.conditional.clauses[index].predicate.metric + }, + set: { + guard self.conditional.clauses.indices.contains(index) else { return } + self.conditional.clauses[index].predicate.metric = $0 + }) } private func comparisonBinding(_ index: Int) -> Binding { Binding( - get: { self.conditional.clauses[index].predicate.comparison }, - set: { self.conditional.clauses[index].predicate.comparison = $0 }) + get: { + guard self.conditional.clauses.indices.contains(index) else { return .greaterThan } + return self.conditional.clauses[index].predicate.comparison + }, + set: { + guard self.conditional.clauses.indices.contains(index) else { return } + self.conditional.clauses[index].predicate.comparison = $0 + }) } private func thresholdBinding(_ index: Int) -> Binding { Binding( - get: { self.conditional.clauses[index].predicate.threshold }, - set: { self.conditional.clauses[index].predicate.threshold = min(max($0, 0), 100) }) + get: { + guard self.conditional.clauses.indices.contains(index) else { return 0 } + return self.conditional.clauses[index].predicate.threshold + }, + set: { + guard self.conditional.clauses.indices.contains(index) else { return } + self.conditional.clauses[index].predicate.threshold = min(max($0, 0), 100) + }) } private var thenBinding: Binding { @@ -259,26 +283,47 @@ extension MenuBarLayoutConditional { return trimmed.isEmpty ? L("menu_bar_layout_token_conditional") : trimmed } - private func conditionTextLines() -> [String] { - self.clauses.enumerated().map { index, clause in - let predicate = "\(clause.predicate.metric.editorLabel) \(clause.predicate.comparison.symbol) " - + "\(Int(clause.predicate.threshold.rounded()))%" - guard index > 0, let combinator = clause.combinator else { return predicate } - let joiner = combinator == .and + /// Produces a summary string reflecting the left-fold AND/OR evaluation order. Mixed + /// combinators parenthesize their accumulator so the reading matches `evaluatesTrue`. + private func conditionText() -> String { + guard let first = self.clauses.first else { return "" } + let mixed = Set(self.clauses.dropFirst().compactMap(\.combinator)).count > 1 + var text = Self.predicateText(first.predicate) + for clause in self.clauses.dropFirst() { + let joiner = (clause.combinator ?? .and) == .and ? L("menu_bar_layout_conditional_and") : L("menu_bar_layout_conditional_or") - return "\(joiner) \(predicate)" + let pred = Self.predicateText(clause.predicate) + text = mixed ? "(\(text)) \(joiner) \(pred)" : "\(text) \(joiner) \(pred)" } + return text + } + + private static func predicateText(_ predicate: MenuBarConditionalPredicate) -> String { + "\(predicate.metric.editorLabel) \(predicate.comparison.symbol) \(Int(predicate.threshold.rounded()))%" } func editorSummary(provider: UsageProvider?) -> String { - let condition = self.conditionTextLines().joined(separator: " ") + let condition = self.conditionText() return L( "menu_bar_layout_conditional_summary", condition, self.thenToken.editorLabel(provider: provider), self.elseToken.editorLabel(provider: provider)) } + + /// Generates a unique copy name that avoids collisions with existing library entries. + static func uniqueCopyName(basedOn name: String, existingNames: Set) -> String { + let base = name.trimmingCharacters(in: .whitespacesAndNewlines) + let stem = base.isEmpty ? L("menu_bar_layout_token_conditional") : base + var candidate = L("menu_bar_layout_conditional_copy_name", stem) + var n = 2 + while existingNames.contains(candidate.lowercased()) { + candidate = L("menu_bar_layout_conditional_copy_name_numbered", stem, n) + n += 1 + } + return candidate + } } extension MenuBarConditionalMetric { diff --git a/Sources/CodexBar/MenuBarLayoutEditor.swift b/Sources/CodexBar/MenuBarLayoutEditor.swift index 9715238934..b386993ce9 100644 --- a/Sources/CodexBar/MenuBarLayoutEditor.swift +++ b/Sources/CodexBar/MenuBarLayoutEditor.swift @@ -55,20 +55,6 @@ enum MenuBarLayoutEditorMutations { return MenuBarLayout(lines: lines) } - static func replace( - at position: MenuBarLayoutPosition, - with token: MenuBarLayoutToken, - in layout: MenuBarLayout) - -> MenuBarLayout - { - guard layout.lines.indices.contains(position.line), - layout.lines[position.line].indices.contains(position.index) - else { return layout } - var lines = layout.lines - lines[position.line][position.index] = token - return MenuBarLayout(lines: lines) - } - static func insert( _ item: MenuBarLayoutDragItem, at target: MenuBarLayoutPosition, @@ -441,7 +427,7 @@ struct MenuBarLayoutEditor: View { self.selectedPosition = position } label: { MenuBarLayoutChipLabel( - title: token.editorLabel(provider: self.persistenceProvider), + title: self.chipTitle(for: token), systemImage: token.editorSystemImage, isSelected: self.selectedPosition == position) .draggable(MenuBarLayoutDragItem.placed(token, at: position, in: self.layout)) @@ -455,12 +441,15 @@ struct MenuBarLayoutEditor: View { .dropDestination(for: MenuBarLayoutDragItem.self) { items, _ in self.insert(items.first, at: position) } - .accessibilityLabel(token.editorAccessibilityLabel(provider: self.persistenceProvider)) + .accessibilityLabel(self.chipTitle(for: token)) .contextMenu { - if case let .conditional(conditional) = token { + if case let .conditional(id) = token, + let conditional = self.settings.menuBarLayoutConditionals + .first(where: { $0.id == id }) + { Button(L("menu_bar_layout_conditional_edit")) { self.conditionalDraft = MenuBarLayoutConditionalDraft( - mode: .editPlaced(position), + mode: .edit(id), conditional: conditional) } } @@ -583,7 +572,9 @@ struct MenuBarLayoutEditor: View { .foregroundStyle(.secondary) Spacer() Button { - self.conditionalDraft = MenuBarLayoutConditionalDraft(mode: .create, conditional: .defaultValue) + self.conditionalDraft = MenuBarLayoutConditionalDraft( + mode: .create, + conditional: .makeDefault()) } label: { Image(systemName: "plus.circle") } @@ -597,16 +588,9 @@ struct MenuBarLayoutEditor: View { .font(.caption) .foregroundStyle(.tertiary) } else { - LazyVGrid( - columns: [GridItem(.adaptive(minimum: 180), spacing: 6)], - alignment: .leading, - spacing: 6) - { - ForEach( - Array(self.settings.menuBarLayoutConditionals.enumerated()), - id: \.offset) - { index, conditional in - self.conditionalPaletteChip(index: index, conditional: conditional) + HStack(spacing: 6) { + ForEach(self.settings.menuBarLayoutConditionals, id: \.id) { conditional in + self.conditionalPaletteChip(conditional: conditional) } } } @@ -614,12 +598,11 @@ struct MenuBarLayoutEditor: View { } private func conditionalPaletteChip( - index: Int, conditional: MenuBarLayoutConditional) -> some View { Button { - self.write(MenuBarLayoutEditorMutations.append(.conditional(conditional), to: self.layout)) + self.write(MenuBarLayoutEditorMutations.append(.conditional(id: conditional.id), to: self.layout)) } label: { MenuBarLayoutChipLabel( title: conditional.displayName, @@ -627,48 +610,41 @@ struct MenuBarLayoutEditor: View { isSelected: false) } .buttonStyle(.plain) - .draggable(MenuBarLayoutDragItem.palette(.conditional(conditional))) + .draggable(MenuBarLayoutDragItem.palette(.conditional(id: conditional.id))) .focusable() .onKeyPress(keys: [.space, .return], phases: [.down]) { _ in - self.write(MenuBarLayoutEditorMutations.append(.conditional(conditional), to: self.layout)) + self.write(MenuBarLayoutEditorMutations.append(.conditional(id: conditional.id), to: self.layout)) return .handled } .accessibilityLabel(conditional.displayName) .contextMenu { Button(L("menu_bar_layout_conditional_edit")) { self.conditionalDraft = MenuBarLayoutConditionalDraft( - mode: .editLibrary(index), + mode: .edit(conditional.id), conditional: conditional) } Button(L("menu_bar_layout_conditional_duplicate")) { - self.duplicateConditional(at: index) + self.duplicateConditional(conditional) } Button(L("menu_bar_layout_conditional_remove"), role: .destructive) { - self.settings.menuBarLayoutConditionals.remove(at: index) + self.settings.removeMenuBarLayoutConditional(id: conditional.id) } } } - private func duplicateConditional(at index: Int) { - guard self.settings.menuBarLayoutConditionals.indices.contains(index) else { return } - var copy = self.settings.menuBarLayoutConditionals[index] - copy.name = self.uniqueConditionalName(for: copy.name) + private func duplicateConditional(_ conditional: MenuBarLayoutConditional) { + let existingNames = Set(self.settings.menuBarLayoutConditionals.map { $0.name.lowercased() }) + let copyName = MenuBarLayoutConditional.uniqueCopyName( + basedOn: conditional.name, + existingNames: existingNames) + let copy = MenuBarLayoutConditional( + name: copyName, + clauses: conditional.clauses, + thenToken: conditional.thenToken, + elseToken: conditional.elseToken) self.settings.menuBarLayoutConditionals.append(copy) } - private func uniqueConditionalName(for currentName: String) -> String { - let existing = Set(self.settings.menuBarLayoutConditionals.map { $0.name.lowercased() }) - let base = currentName.trimmingCharacters(in: .whitespacesAndNewlines) - let stem = base.isEmpty ? L("menu_bar_layout_token_conditional") : base - var candidate = "\(stem) (copy)" - var n = 2 - while existing.contains(candidate.lowercased()) { - candidate = "\(stem) (copy \(n))" - n += 1 - } - return candidate - } - private var displayOptions: some View { HStack(spacing: 18) { Picker(L("menu_bar_layout_size"), selection: self.sizeBinding) { @@ -745,18 +721,22 @@ struct MenuBarLayoutEditor: View { switch draft.mode { case .create: self.settings.menuBarLayoutConditionals.append(draft.conditional) - case let .editLibrary(index): - guard self.settings.menuBarLayoutConditionals.indices.contains(index) else { return } + case let .edit(id): + guard let index = self.settings.menuBarLayoutConditionals.firstIndex(where: { $0.id == id }) + else { return } self.settings.menuBarLayoutConditionals[index] = draft.conditional - case let .editPlaced(position): - self.write(MenuBarLayoutEditorMutations.replace( - at: position, - with: .conditional(draft.conditional), - in: self.layout)) - self.selectedPosition = nil } } + private func chipTitle(for token: MenuBarLayoutToken) -> String { + if case let .conditional(id) = token { + return self.settings.menuBarLayoutConditionals + .first(where: { $0.id == id })?.displayName + ?? L("menu_bar_layout_token_conditional") + } + return token.editorLabel(provider: self.persistenceProvider) + } + private func write(_ layout: MenuBarLayout) { MenuBarLayoutEditorPersistence.activate( layout, @@ -814,6 +794,7 @@ struct MenuBarLayoutPreview: View { size: self.settings.menuBarLayoutSize, highContrast: self.settings.menuBarHighContrastOnInactiveDisplays, showUsed: self.settings.usageBarsShowUsed, + conditionals: self.settings.menuBarLayoutConditionals, appearanceName: "preview", isDebugApp: false, now: minute, @@ -1017,9 +998,6 @@ extension MenuBarLayoutGap { extension MenuBarLayoutToken { func editorLabel(provider: UsageProvider?) -> String { - if case let .conditional(conditional) = self { - return conditional.displayName - } if let providerLabel = self.providerEditorLabel(provider: provider) { return providerLabel } diff --git a/Sources/CodexBar/MenuBarLayoutRenderer.swift b/Sources/CodexBar/MenuBarLayoutRenderer.swift index 613abf770f..d00a199d4b 100644 --- a/Sources/CodexBar/MenuBarLayoutRenderer.swift +++ b/Sources/CodexBar/MenuBarLayoutRenderer.swift @@ -51,6 +51,7 @@ struct MenuBarLayoutRenderOptions: Hashable { let size: MenuBarLayoutSize let highContrast: Bool let showUsed: Bool + let conditionals: [MenuBarLayoutConditional] let appearanceName: String let isDebugApp: Bool /// Whether the provider's latest refresh failed; when true the shown snapshot is stale @@ -67,6 +68,7 @@ struct MenuBarLayoutRenderOptions: Hashable { size: MenuBarLayoutSize, highContrast: Bool, showUsed: Bool, + conditionals: [MenuBarLayoutConditional], appearanceName: String, isDebugApp: Bool, isStale: Bool = false, @@ -76,6 +78,7 @@ struct MenuBarLayoutRenderOptions: Hashable { self.size = size self.highContrast = highContrast self.showUsed = showUsed + self.conditionals = conditionals self.appearanceName = appearanceName self.isDebugApp = isDebugApp self.isStale = isStale @@ -90,6 +93,7 @@ struct MenuBarLayoutRenderKey: Hashable { let size: MenuBarLayoutSize let highContrast: Bool let showUsed: Bool + let conditionals: [MenuBarLayoutConditional] let appearanceName: String let isDebugApp: Bool let isStale: Bool @@ -189,6 +193,7 @@ final class MenuBarLayoutRenderer { size: options.size, highContrast: options.highContrast, showUsed: options.showUsed, + conditionals: options.conditionals, appearanceName: options.appearanceName, isDebugApp: options.isDebugApp, isStale: options.isStale, @@ -236,6 +241,17 @@ final class MenuBarLayoutRenderer { attributes[.baselineOffset] = baseBaselineOffset + CGFloat(options.verticalAdjustment) let result = NSMutableAttributedString() var accessibilityLines: [String] = [] + + let conditionalsByID = Dictionary( + options.conditionals.map { ($0.id, $0) }, + uniquingKeysWith: { first, _ in first }) + + // Pre-resolve conditionals so .hidden branches vanish and adjacent-space checks see + // only the tokens that will actually render. + let resolvedLines = layout.lines.map { line in + line.compactMap { Self.resolvedDisplayToken($0, data: data, conditionals: conditionalsByID) } + } + // Only surface a leading icon via `button.image` when an actual image is available and the // high-contrast contract does not require the icon to stay inside the attributed title. // AppKit dims `button.image` on inactive displays, but high-contrast layouts keep icon + text @@ -243,18 +259,18 @@ final class MenuBarLayoutRenderer { // missing icon the token still renders its placeholder inside the title. let leadingIcon: NSImage? = if options.highContrast { nil - } else if layout.lines.first?.first == .icon, icon != nil { + } else if resolvedLines.first?.first == .icon, icon != nil { icon.map { Self.offsetLeadingIcon($0, adjustment: options.verticalAdjustment) } } else { nil } - for (lineIndex, line) in layout.lines.enumerated() { + for (lineIndex, resolvedLine) in resolvedLines.enumerated() { if lineIndex > 0 { result.append(NSAttributedString(string: "\n", attributes: attributes)) } var accessibilityParts: [String] = [] - for (tokenIndex, token) in line.enumerated() { + for (tokenIndex, token) in resolvedLine.enumerated() { // The leading icon is surfaced as `button.image` so AppKit applies the system's // active/inactive display tinting; it is not repeated inside the attributed title, // but its accessibility description must survive for VoiceOver. @@ -262,7 +278,7 @@ final class MenuBarLayoutRenderer { accessibilityParts.append(Self.iconAccessibilityText(data: data)) continue } - if tokenIndex > 0, token != .space, line[tokenIndex - 1] != .space { + if tokenIndex > 0, token != .space, resolvedLine[tokenIndex - 1] != .space { result.append(NSAttributedString(string: "\u{2009}", attributes: attributes)) } let renderedItem = Self.renderItem( @@ -296,13 +312,33 @@ final class MenuBarLayoutRenderer { leadingIcon: leadingIcon) } + /// nil == resolved to .hidden (render nothing, no separator). A returned .conditional + /// means the id is dangling or the depth cap was hit; renderItem shows the placeholder. + private static func resolvedDisplayToken( + _ token: MenuBarLayoutToken, + data: MenuBarLayoutRenderData, + conditionals: [UUID: MenuBarLayoutConditional], + depth: Int = 0) + -> MenuBarLayoutToken? + { + switch token { + case .hidden: return nil + case let .conditional(id): + guard depth < MenuBarLayoutToken.maxConditionalDepth, + let conditional = conditionals[id] + else { return token } + let branch = conditional.evaluatesTrue(data: data) ? conditional.thenToken : conditional.elseToken + return self.resolvedDisplayToken(branch, data: data, conditionals: conditionals, depth: depth + 1) + default: return token + } + } + private static func renderItem( _ item: MenuBarLayoutToken, data: MenuBarLayoutRenderData, icon: NSImage?, style: TokenStyle, - options: MenuBarLayoutRenderOptions, - depth: Int = 0) + options: MenuBarLayoutRenderOptions) -> (value: NSAttributedString, accessibilityText: String?) { switch item { @@ -398,15 +434,12 @@ final class MenuBarLayoutRenderer { return self.textToken(" ", accessibilityText: nil, attributes: style.attributes) case .hidden: return self.textToken("", accessibilityText: nil, attributes: style.attributes) - case let .conditional(conditional): - guard depth < MenuBarLayoutToken.maxConditionalDepth else { - return self.textToken( - self.missingValue, - accessibilityText: L("Conditional unavailable"), - attributes: style.attributes) - } - let branch = conditional.evaluatesTrue(data: data) ? conditional.thenToken : conditional.elseToken - return self.renderItem(branch, data: data, icon: icon, style: style, options: options, depth: depth + 1) + case .conditional: + // Reachable only for dangling id or depth cap; the resolver already expanded known conditionals. + return self.textToken( + self.missingValue, + accessibilityText: L("menu_bar_layout_conditional_unavailable"), + attributes: style.attributes) } } diff --git a/Sources/CodexBar/Resources/ar.lproj/Localizable.strings b/Sources/CodexBar/Resources/ar.lproj/Localizable.strings index 8decce1ded..b97eadaff7 100644 --- a/Sources/CodexBar/Resources/ar.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ar.lproj/Localizable.strings @@ -1509,3 +1509,6 @@ "menu_bar_layout_conditional_or" = "أو"; "menu_bar_layout_conditional_add_condition" = "إضافة شرط"; "menu_bar_layout_conditional_summary" = "إذا كان %1$@ فعندئذ %2$@ وإلا %3$@"; +"menu_bar_layout_conditional_copy_name" = "%@ (نسخة)"; +"menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (نسخة %2$d)"; +"menu_bar_layout_conditional_unavailable" = "الشرط غير متاح"; diff --git a/Sources/CodexBar/Resources/ca.lproj/Localizable.strings b/Sources/CodexBar/Resources/ca.lproj/Localizable.strings index 8d013263e4..bafd1e749e 100644 --- a/Sources/CodexBar/Resources/ca.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ca.lproj/Localizable.strings @@ -1508,3 +1508,6 @@ "menu_bar_layout_conditional_or" = "o"; "menu_bar_layout_conditional_add_condition" = "Afegeix una condició"; "menu_bar_layout_conditional_summary" = "Si %1$@ llavors %2$@ en cas contrari %3$@"; +"menu_bar_layout_conditional_copy_name" = "%@ (còpia)"; +"menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (còpia %2$d)"; +"menu_bar_layout_conditional_unavailable" = "Condicional no disponible"; diff --git a/Sources/CodexBar/Resources/de.lproj/Localizable.strings b/Sources/CodexBar/Resources/de.lproj/Localizable.strings index 0398cdad37..7e183dff5f 100644 --- a/Sources/CodexBar/Resources/de.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/de.lproj/Localizable.strings @@ -1505,3 +1505,6 @@ "menu_bar_layout_conditional_name_error" = "Geben Sie einen eindeutigen Namen ein"; "menu_bar_layout_conditional_duplicate" = "Duplizieren"; "menu_bar_layout_conditional_summary" = "Wenn %1$@ dann %2$@ sonst %3$@"; +"menu_bar_layout_conditional_copy_name" = "%@ (Kopie)"; +"menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (Kopie %2$d)"; +"menu_bar_layout_conditional_unavailable" = "Bedingung nicht verfügbar"; diff --git a/Sources/CodexBar/Resources/en.lproj/Localizable.strings b/Sources/CodexBar/Resources/en.lproj/Localizable.strings index d740b2fdbe..d98617f40e 100644 --- a/Sources/CodexBar/Resources/en.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/en.lproj/Localizable.strings @@ -1508,3 +1508,6 @@ "menu_bar_layout_conditional_name_placeholder" = "e.g. Session check"; "menu_bar_layout_conditional_name_error" = "Enter a unique name"; "menu_bar_layout_conditional_summary" = "If %1$@ then %2$@ else %3$@"; +"menu_bar_layout_conditional_copy_name" = "%@ (copy)"; +"menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (copy %2$d)"; +"menu_bar_layout_conditional_unavailable" = "Conditional unavailable"; diff --git a/Sources/CodexBar/Resources/es.lproj/Localizable.strings b/Sources/CodexBar/Resources/es.lproj/Localizable.strings index 2aa3e6e32d..cbda1a77bf 100644 --- a/Sources/CodexBar/Resources/es.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/es.lproj/Localizable.strings @@ -1503,3 +1503,6 @@ "menu_bar_layout_conditional_name_error" = "Introduce un nombre único"; "menu_bar_layout_conditional_duplicate" = "Duplicar"; "menu_bar_layout_conditional_summary" = "Si %1$@ entonces %2$@ si no %3$@"; +"menu_bar_layout_conditional_copy_name" = "%@ (copia)"; +"menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (copia %2$d)"; +"menu_bar_layout_conditional_unavailable" = "Condicional no disponible"; diff --git a/Sources/CodexBar/Resources/fa.lproj/Localizable.strings b/Sources/CodexBar/Resources/fa.lproj/Localizable.strings index afe8c3b1d9..cc65281a01 100644 --- a/Sources/CodexBar/Resources/fa.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/fa.lproj/Localizable.strings @@ -1509,3 +1509,6 @@ "menu_bar_layout_conditional_or" = "یا"; "menu_bar_layout_conditional_add_condition" = "افزودن شرط"; "menu_bar_layout_conditional_summary" = "اگر %1$@ سپس %2$@ در غیر این صورت %3$@"; +"menu_bar_layout_conditional_copy_name" = "%@ (رونوشت)"; +"menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (رونوشت %2$d)"; +"menu_bar_layout_conditional_unavailable" = "شرط در دسترس نیست"; diff --git a/Sources/CodexBar/Resources/fr.lproj/Localizable.strings b/Sources/CodexBar/Resources/fr.lproj/Localizable.strings index 5e44decfea..9e6b9ee4d2 100644 --- a/Sources/CodexBar/Resources/fr.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/fr.lproj/Localizable.strings @@ -1504,3 +1504,6 @@ "menu_bar_layout_conditional_name_error" = "Saisissez un nom unique"; "menu_bar_layout_conditional_duplicate" = "Dupliquer"; "menu_bar_layout_conditional_summary" = "Si %1$@ alors %2$@ sinon %3$@"; +"menu_bar_layout_conditional_copy_name" = "%@ (copie)"; +"menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (copie %2$d)"; +"menu_bar_layout_conditional_unavailable" = "Condition indisponible"; diff --git a/Sources/CodexBar/Resources/gl.lproj/Localizable.strings b/Sources/CodexBar/Resources/gl.lproj/Localizable.strings index f0b4cf99fc..f21cbfa8d8 100644 --- a/Sources/CodexBar/Resources/gl.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/gl.lproj/Localizable.strings @@ -1504,3 +1504,6 @@ "menu_bar_layout_conditional_name_error" = "Introduce un nome único"; "menu_bar_layout_conditional_duplicate" = "Duplicar"; "menu_bar_layout_conditional_summary" = "Se %1$@ entón %2$@ se non %3$@"; +"menu_bar_layout_conditional_copy_name" = "%@ (copia)"; +"menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (copia %2$d)"; +"menu_bar_layout_conditional_unavailable" = "Condicional non dispoñible"; diff --git a/Sources/CodexBar/Resources/id.lproj/Localizable.strings b/Sources/CodexBar/Resources/id.lproj/Localizable.strings index 0675f6932d..06870423cf 100644 --- a/Sources/CodexBar/Resources/id.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/id.lproj/Localizable.strings @@ -1508,3 +1508,6 @@ "menu_bar_layout_conditional_or" = "atau"; "menu_bar_layout_conditional_add_condition" = "Tambah Kondisi"; "menu_bar_layout_conditional_summary" = "Jika %1$@ maka %2$@, jika tidak %3$@"; +"menu_bar_layout_conditional_copy_name" = "%@ (salinan)"; +"menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (salinan %2$d)"; +"menu_bar_layout_conditional_unavailable" = "Kondisional tidak tersedia"; diff --git a/Sources/CodexBar/Resources/it.lproj/Localizable.strings b/Sources/CodexBar/Resources/it.lproj/Localizable.strings index 160e4b0820..a1ec63976c 100644 --- a/Sources/CodexBar/Resources/it.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/it.lproj/Localizable.strings @@ -1508,3 +1508,6 @@ "menu_bar_layout_conditional_name_error" = "Inserisci un nome univoco"; "menu_bar_layout_conditional_duplicate" = "Duplica"; "menu_bar_layout_conditional_summary" = "Se %1$@ allora %2$@ altrimenti %3$@"; +"menu_bar_layout_conditional_copy_name" = "%@ (copia)"; +"menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (copia %2$d)"; +"menu_bar_layout_conditional_unavailable" = "Condizionale non disponibile"; diff --git a/Sources/CodexBar/Resources/ja.lproj/Localizable.strings b/Sources/CodexBar/Resources/ja.lproj/Localizable.strings index 0cf97f8dbe..b52b65705d 100644 --- a/Sources/CodexBar/Resources/ja.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ja.lproj/Localizable.strings @@ -1505,3 +1505,6 @@ "menu_bar_layout_conditional_or" = "または"; "menu_bar_layout_conditional_add_condition" = "条件を追加"; "menu_bar_layout_conditional_summary" = "もし %1$@ なら %2$@ を表示し、それ以外は %3$@ を表示"; +"menu_bar_layout_conditional_copy_name" = "%@(コピー)"; +"menu_bar_layout_conditional_copy_name_numbered" = "%1$@(コピー %2$d)"; +"menu_bar_layout_conditional_unavailable" = "条件を利用できません"; diff --git a/Sources/CodexBar/Resources/ko.lproj/Localizable.strings b/Sources/CodexBar/Resources/ko.lproj/Localizable.strings index 028da949ec..2f471b7261 100644 --- a/Sources/CodexBar/Resources/ko.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ko.lproj/Localizable.strings @@ -1472,3 +1472,6 @@ "menu_bar_layout_conditional_name_error" = "고유한 이름을 입력하세요"; "menu_bar_layout_conditional_duplicate" = "복제"; "menu_bar_layout_conditional_summary" = "만약 %1$@이면 %2$@을 표시하고, 그렇지 않으면 %3$@을 표시"; +"menu_bar_layout_conditional_copy_name" = "%@ (복사본)"; +"menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (복사본 %2$d)"; +"menu_bar_layout_conditional_unavailable" = "조건을 사용할 수 없음"; diff --git a/Sources/CodexBar/Resources/nl.lproj/Localizable.strings b/Sources/CodexBar/Resources/nl.lproj/Localizable.strings index 97c969b24a..07521a6ed7 100644 --- a/Sources/CodexBar/Resources/nl.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/nl.lproj/Localizable.strings @@ -1504,3 +1504,6 @@ "menu_bar_layout_conditional_name_error" = "Voer een unieke naam in"; "menu_bar_layout_conditional_duplicate" = "Dupliceren"; "menu_bar_layout_conditional_summary" = "Als %1$@ dan %2$@ anders %3$@"; +"menu_bar_layout_conditional_copy_name" = "%@ (kopie)"; +"menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (kopie %2$d)"; +"menu_bar_layout_conditional_unavailable" = "Voorwaarde niet beschikbaar"; diff --git a/Sources/CodexBar/Resources/pl.lproj/Localizable.strings b/Sources/CodexBar/Resources/pl.lproj/Localizable.strings index d7979b6c1c..beb63a9a07 100644 --- a/Sources/CodexBar/Resources/pl.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/pl.lproj/Localizable.strings @@ -1508,3 +1508,6 @@ "menu_bar_layout_conditional_or" = "lub"; "menu_bar_layout_conditional_add_condition" = "Dodaj warunek"; "menu_bar_layout_conditional_summary" = "Jeśli %1$@, to %2$@, w przeciwnym razie %3$@"; +"menu_bar_layout_conditional_copy_name" = "%@ (kopia)"; +"menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (kopia %2$d)"; +"menu_bar_layout_conditional_unavailable" = "Warunek niedostępny"; diff --git a/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings b/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings index 3f20064ee8..c51d6c798c 100644 --- a/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings @@ -1505,3 +1505,6 @@ "menu_bar_layout_conditional_name_error" = "Digite um nome exclusivo"; "menu_bar_layout_conditional_duplicate" = "Duplicar"; "menu_bar_layout_conditional_summary" = "Se %1$@ então %2$@, senão %3$@"; +"menu_bar_layout_conditional_copy_name" = "%@ (cópia)"; +"menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (cópia %2$d)"; +"menu_bar_layout_conditional_unavailable" = "Condicional indisponível"; diff --git a/Sources/CodexBar/Resources/ru.lproj/Localizable.strings b/Sources/CodexBar/Resources/ru.lproj/Localizable.strings index 99fc3aaf35..9adada9ad6 100644 --- a/Sources/CodexBar/Resources/ru.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ru.lproj/Localizable.strings @@ -1506,3 +1506,6 @@ "menu_bar_layout_conditional_name_error" = "Введите уникальное название"; "menu_bar_layout_conditional_duplicate" = "Дублировать"; "menu_bar_layout_conditional_summary" = "Если %1$@, то показать %2$@, иначе показать %3$@"; +"menu_bar_layout_conditional_copy_name" = "%@ (копия)"; +"menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (копия %2$d)"; +"menu_bar_layout_conditional_unavailable" = "Условие недоступно"; diff --git a/Sources/CodexBar/Resources/sv.lproj/Localizable.strings b/Sources/CodexBar/Resources/sv.lproj/Localizable.strings index 74d7189079..9f015759ed 100644 --- a/Sources/CodexBar/Resources/sv.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/sv.lproj/Localizable.strings @@ -1504,3 +1504,6 @@ "menu_bar_layout_conditional_name_error" = "Ange ett unikt namn"; "menu_bar_layout_conditional_duplicate" = "Duplicera"; "menu_bar_layout_conditional_summary" = "Om %1$@ visa %2$@ annars %3$@"; +"menu_bar_layout_conditional_copy_name" = "%@ (kopia)"; +"menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (kopia %2$d)"; +"menu_bar_layout_conditional_unavailable" = "Villkor otillgängligt"; diff --git a/Sources/CodexBar/Resources/th.lproj/Localizable.strings b/Sources/CodexBar/Resources/th.lproj/Localizable.strings index 47dd4d99ca..5ea9b6ddfd 100644 --- a/Sources/CodexBar/Resources/th.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/th.lproj/Localizable.strings @@ -1509,3 +1509,6 @@ "menu_bar_layout_conditional_name_error" = "ป้อนชื่อที่ไม่ซ้ำกัน"; "menu_bar_layout_conditional_duplicate" = "ทำสำเนา"; "menu_bar_layout_conditional_summary" = "ถ้า %1$@ จากนั้น %2$@ มิฉะนั้น %3$@"; +"menu_bar_layout_conditional_copy_name" = "%@ (สำเนา)"; +"menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (สำเนา %2$d)"; +"menu_bar_layout_conditional_unavailable" = "เงื่อนไขไม่พร้อมใช้งาน"; diff --git a/Sources/CodexBar/Resources/tr.lproj/Localizable.strings b/Sources/CodexBar/Resources/tr.lproj/Localizable.strings index a5beefa186..efb74b628b 100644 --- a/Sources/CodexBar/Resources/tr.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/tr.lproj/Localizable.strings @@ -1507,3 +1507,6 @@ "menu_bar_layout_conditional_name_error" = "Benzersiz bir ad girin"; "menu_bar_layout_conditional_duplicate" = "Çoğalt"; "menu_bar_layout_conditional_summary" = "Eğer %1$@ ise %2$@, değilse %3$@ göster"; +"menu_bar_layout_conditional_copy_name" = "%@ (kopya)"; +"menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (kopya %2$d)"; +"menu_bar_layout_conditional_unavailable" = "Koşul kullanılamıyor"; diff --git a/Sources/CodexBar/Resources/uk.lproj/Localizable.strings b/Sources/CodexBar/Resources/uk.lproj/Localizable.strings index e12f9824b6..5a17ac9d01 100644 --- a/Sources/CodexBar/Resources/uk.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/uk.lproj/Localizable.strings @@ -1505,3 +1505,6 @@ "menu_bar_layout_conditional_name_error" = "Введіть унікальну назву"; "menu_bar_layout_conditional_duplicate" = "Дублювати"; "menu_bar_layout_conditional_summary" = "Якщо %1$@ тоді %2$@ інакше %3$@"; +"menu_bar_layout_conditional_copy_name" = "%@ (копія)"; +"menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (копія %2$d)"; +"menu_bar_layout_conditional_unavailable" = "Умова недоступна"; diff --git a/Sources/CodexBar/Resources/vi.lproj/Localizable.strings b/Sources/CodexBar/Resources/vi.lproj/Localizable.strings index 6b0a14549b..84f0a9a029 100644 --- a/Sources/CodexBar/Resources/vi.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/vi.lproj/Localizable.strings @@ -1506,3 +1506,6 @@ "menu_bar_layout_conditional_name_error" = "Nhập tên duy nhất"; "menu_bar_layout_conditional_duplicate" = "Nhân bản"; "menu_bar_layout_conditional_summary" = "Nếu %1$@ thì %2$@ ngược lại %3$@"; +"menu_bar_layout_conditional_copy_name" = "%@ (bản sao)"; +"menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (bản sao %2$d)"; +"menu_bar_layout_conditional_unavailable" = "Điều kiện không khả dụng"; diff --git a/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings b/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings index 6c9085fb6b..e2fde669ea 100644 --- a/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings @@ -1483,3 +1483,6 @@ "menu_bar_layout_conditional_name_error" = "请输入唯一名称"; "menu_bar_layout_conditional_duplicate" = "复制"; "menu_bar_layout_conditional_summary" = "如果 %1$@ 则 %2$@ 否则 %3$@"; +"menu_bar_layout_conditional_copy_name" = "%@(副本)"; +"menu_bar_layout_conditional_copy_name_numbered" = "%1$@(副本 %2$d)"; +"menu_bar_layout_conditional_unavailable" = "条件不可用"; diff --git a/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings b/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings index a565d0b005..95167f219d 100644 --- a/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings @@ -1536,3 +1536,6 @@ "menu_bar_layout_conditional_name_error" = "請輸入唯一名稱"; "menu_bar_layout_conditional_duplicate" = "複製"; "menu_bar_layout_conditional_summary" = "如果 %1$@ 則 %2$@ 否則 %3$@"; +"menu_bar_layout_conditional_copy_name" = "%@(副本)"; +"menu_bar_layout_conditional_copy_name_numbered" = "%1$@(副本 %2$d)"; +"menu_bar_layout_conditional_unavailable" = "條件不可用"; diff --git a/Sources/CodexBar/SettingsStore+Defaults.swift b/Sources/CodexBar/SettingsStore+Defaults.swift index 4550e6f19a..87a974ec99 100644 --- a/Sources/CodexBar/SettingsStore+Defaults.swift +++ b/Sources/CodexBar/SettingsStore+Defaults.swift @@ -420,6 +420,20 @@ extension SettingsStore { } } + func removeMenuBarLayoutConditional(id: UUID) { + self.menuBarLayoutConditionals.removeAll { $0.id == id } + if let stored = self.defaultsState.storedMenuBarLayout, + let stripped = stored.removingConditional(id: id) + { + self.menuBarLayout = stripped + } + for (key, layout) in self.defaultsState.menuBarLayoutOverridesRaw { + guard let stripped = layout.removingConditional(id: id) else { continue } + self.defaultsState.menuBarLayoutOverridesRaw[key] = stripped + } + self.persistMenuBarLayoutOverrides() + } + var hasStoredMenuBarLayout: Bool { self.defaultsState.storedMenuBarLayout != nil } diff --git a/Sources/CodexBar/StatusItemController+Animation.swift b/Sources/CodexBar/StatusItemController+Animation.swift index 2b1ae57354..2ad7f1b211 100644 --- a/Sources/CodexBar/StatusItemController+Animation.swift +++ b/Sources/CodexBar/StatusItemController+Animation.swift @@ -1254,7 +1254,8 @@ extension StatusItemController { if !layoutResolution.usesLegacyRendering, self.settings.menuBarIconStyle == .iconAndPercent { - let showsReset = layoutResolution.layout.flattenedTokens + let showsReset = layoutResolution.layout + .flattenedTokens(conditionals: self.settings.menuBarLayoutConditionals) .contains { $0 == .resetCountdown || $0 == .resetAbsolute } guard showsReset else { return [] } let window = self.menuBarLayoutWindows(provider: provider, snapshot: snapshot, now: now).automatic diff --git a/Sources/CodexBar/StatusItemController+CountdownRefresh.swift b/Sources/CodexBar/StatusItemController+CountdownRefresh.swift index d0c0d5cf8a..27ee6c68e0 100644 --- a/Sources/CodexBar/StatusItemController+CountdownRefresh.swift +++ b/Sources/CodexBar/StatusItemController+CountdownRefresh.swift @@ -23,7 +23,7 @@ extension StatusItemController { if !resolution.usesLegacyRendering, self.settings.menuBarIconStyle == .iconAndPercent { - let tokens = resolution.layout.flattenedTokens + let tokens = resolution.layout.flattenedTokens(conditionals: self.settings.menuBarLayoutConditionals) if tokens.contains(.resetCountdown) { countdownResetDates.append(contentsOf: resetDates) } @@ -152,10 +152,12 @@ extension StatusItemController { let resolution = self.settings.menuBarLayoutResolution(for: provider) guard !resolution.usesLegacyRendering, self.settings.menuBarIconStyle == .iconAndPercent, - resolution.layout.flattenedTokens.contains(where: { - if case .pace(window: .weekly) = $0 { return true } - return false - }) + resolution.layout + .flattenedTokens(conditionals: self.settings.menuBarLayoutConditionals) + .contains(where: { + if case .pace(window: .weekly) = $0 { return true } + return false + }) else { return nil } let snapshot = self.store.menuBarSnapshot(for: provider.instanceID) guard let window = self.menuBarLayoutWindows( diff --git a/Sources/CodexBar/StatusItemController+IconObservation.swift b/Sources/CodexBar/StatusItemController+IconObservation.swift index 263f3c1a91..82eb537885 100644 --- a/Sources/CodexBar/StatusItemController+IconObservation.swift +++ b/Sources/CodexBar/StatusItemController+IconObservation.swift @@ -85,8 +85,9 @@ extension StatusItemController { { let resolution = self.settings.menuBarLayoutResolution(for: provider) guard !resolution.usesLegacyRendering, - resolution.layout.flattenedTokens.contains(.accountLabel), - let accountLabel = self.menuBarLayoutAccountLabel(provider: provider, snapshot: snapshot) + resolution.layout.flattenedTokens(conditionals: self.settings.menuBarLayoutConditionals) + .contains(.accountLabel), + let accountLabel = self.menuBarLayoutAccountLabel(provider: provider, snapshot: snapshot) else { return nil } var hasher = Hasher() @@ -98,7 +99,7 @@ extension StatusItemController { let resolution = self.settings.menuBarLayoutResolution(for: provider) guard !resolution.usesLegacyRendering else { return nil } - let tokens = resolution.layout.flattenedTokens + let tokens = resolution.layout.flattenedTokens(conditionals: self.settings.menuBarLayoutConditionals) let showsToday = tokens.contains(.costToday) let showsLast30Days = tokens.contains(.cost30d) guard showsToday || showsLast30Days else { return nil } @@ -117,7 +118,8 @@ extension StatusItemController { { let resolution = self.settings.menuBarLayoutResolution(for: provider) guard !resolution.usesLegacyRendering, - resolution.layout.flattenedTokens.contains(.balance) + resolution.layout.flattenedTokens(conditionals: self.settings.menuBarLayoutConditionals) + .contains(.balance) else { return nil } return MenuBarLayoutBalanceResolver.balance(provider: provider, snapshot: snapshot) } @@ -134,10 +136,12 @@ extension StatusItemController { let resolution = self.settings.menuBarLayoutResolution(for: provider) guard !resolution.usesLegacyRendering else { return nil } - let paceWindows = Set(resolution.layout.flattenedTokens.compactMap { token -> PercentWindow? in - guard case let .pace(window) = token else { return nil } - return window - }) + let paceWindows = Set(resolution.layout + .flattenedTokens(conditionals: self.settings.menuBarLayoutConditionals) + .compactMap { token -> PercentWindow? in + guard case let .pace(window) = token else { return nil } + return window + }) guard !paceWindows.isEmpty else { return nil } let windows = self.menuBarLayoutWindows(provider: provider, snapshot: snapshot, now: Date()) diff --git a/Sources/CodexBar/StatusItemController+MenuBarLayout.swift b/Sources/CodexBar/StatusItemController+MenuBarLayout.swift index a125f1cc52..9fd3d03092 100644 --- a/Sources/CodexBar/StatusItemController+MenuBarLayout.swift +++ b/Sources/CodexBar/StatusItemController+MenuBarLayout.swift @@ -32,6 +32,7 @@ extension StatusItemController { size: self.settings.menuBarLayoutSize, highContrast: self.shouldUseHighContrastStatusItemContent, showUsed: self.settings.usageBarsShowUsed, + conditionals: self.settings.menuBarLayoutConditionals, appearanceName: appearanceName, isDebugApp: Self.isDebugApp(bundleIdentifier: Bundle.main.bundleIdentifier), isStale: self.store.isStale(provider: provider), diff --git a/Tests/CodexBarTests/MenuBarLayoutEditorTests.swift b/Tests/CodexBarTests/MenuBarLayoutEditorTests.swift index 88203bc7cd..2150b786f3 100644 --- a/Tests/CodexBarTests/MenuBarLayoutEditorTests.swift +++ b/Tests/CodexBarTests/MenuBarLayoutEditorTests.swift @@ -175,32 +175,9 @@ struct MenuBarLayoutEditorTests { #expect(decoded == payload) } - @Test - func `replace mutation swaps a placed token`() { - let initial = MenuBarLayout(lines: [[.icon, .providerName]]) - - let replaced = MenuBarLayoutEditorMutations.replace( - at: MenuBarLayoutPosition(line: 0, index: 1), - with: .conditional(MenuBarLayoutConditional.defaultValue), - in: initial) - #expect(replaced.lines == [[.icon, .conditional(MenuBarLayoutConditional.defaultValue)]]) - - let outOfBoundsLine = MenuBarLayoutEditorMutations.replace( - at: MenuBarLayoutPosition(line: 3, index: 0), - with: .conditional(MenuBarLayoutConditional.defaultValue), - in: initial) - #expect(outOfBoundsLine == initial) - - let outOfBoundsIndex = MenuBarLayoutEditorMutations.replace( - at: MenuBarLayoutPosition(line: 0, index: 5), - with: .conditional(MenuBarLayoutConditional.defaultValue), - in: initial) - #expect(outOfBoundsIndex == initial) - } - @Test func `conditional drag payload round trips`() throws { - let payload = MenuBarLayoutDragItem.palette(.conditional(MenuBarLayoutConditional.defaultValue)) + let payload = MenuBarLayoutDragItem.palette(.conditional(id: UUID())) let data = try JSONEncoder().encode(payload) #expect(try JSONDecoder().decode(MenuBarLayoutDragItem.self, from: data) == payload) @@ -209,11 +186,12 @@ struct MenuBarLayoutEditorTests { @Test func `conditional token appends like palette tokens`() { let initial = MenuBarLayout(lines: [[.icon, .resetCountdown]]) + let conditionalID = UUID() let appended = MenuBarLayoutEditorMutations.append( - .conditional(MenuBarLayoutConditional.defaultValue), + .conditional(id: conditionalID), to: initial) - #expect(appended.lines == [[.icon, .resetCountdown, .conditional(MenuBarLayoutConditional.defaultValue)]]) + #expect(appended.lines == [[.icon, .resetCountdown, .conditional(id: conditionalID)]]) } @Test diff --git a/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift b/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift index 48f3300eb3..952a488aba 100644 --- a/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift +++ b/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift @@ -449,6 +449,7 @@ struct MenuBarLayoutRendererTests { size: .regular, highContrast: false, showUsed: false, + conditionals: [], appearanceName: "aqua", isDebugApp: false, now: self.now)) @@ -501,6 +502,7 @@ struct MenuBarLayoutRendererTests { size: options.size, highContrast: true, showUsed: options.showUsed, + conditionals: options.conditionals, appearanceName: options.appearanceName, isDebugApp: options.isDebugApp, now: options.now) @@ -566,10 +568,10 @@ struct MenuBarLayoutRendererTests { let data = self.data() let output = renderer.render( - layout: MenuBarLayout(lines: [[.conditional(conditional)]]), + layout: MenuBarLayout(lines: [[.conditional(id: conditional.id)]]), data: data, icon: nil, - options: self.options()) + options: self.options(conditionals: [conditional])) let control = renderer.render( layout: MenuBarLayout(lines: [[.percent(window: .session)]]), data: data, @@ -591,10 +593,10 @@ struct MenuBarLayoutRendererTests { let data = self.data() let output = renderer.render( - layout: MenuBarLayout(lines: [[.conditional(conditional)]]), + layout: MenuBarLayout(lines: [[.conditional(id: conditional.id)]]), data: data, icon: nil, - options: self.options()) + options: self.options(conditionals: [conditional])) let control = renderer.render( layout: MenuBarLayout(lines: [[.resetCountdown]]), data: data, @@ -615,10 +617,10 @@ struct MenuBarLayoutRendererTests { elseToken: .hidden) let output = renderer.render( - layout: MenuBarLayout(lines: [[.conditional(conditional)]]), + layout: MenuBarLayout(lines: [[.conditional(id: conditional.id)]]), data: self.data(), icon: nil, - options: self.options()) + options: self.options(conditionals: [conditional])) #expect(output.attributedTitle.string.isEmpty) } @@ -636,10 +638,10 @@ struct MenuBarLayoutRendererTests { thenToken: .percent(window: .session), elseToken: .resetCountdown) let falseOutput = renderer.render( - layout: MenuBarLayout(lines: [[.conditional(falseConditional)]]), + layout: MenuBarLayout(lines: [[.conditional(id: falseConditional.id)]]), data: data, icon: nil, - options: self.options()) + options: self.options(conditionals: [falseConditional])) let falseControl = renderer.render( layout: MenuBarLayout(lines: [[.resetCountdown]]), data: data, @@ -656,10 +658,10 @@ struct MenuBarLayoutRendererTests { thenToken: .percent(window: .session), elseToken: .resetCountdown) let trueOutput = renderer.render( - layout: MenuBarLayout(lines: [[.conditional(trueConditional)]]), + layout: MenuBarLayout(lines: [[.conditional(id: trueConditional.id)]]), data: data, icon: nil, - options: self.options()) + options: self.options(conditionals: [trueConditional])) let trueControl = renderer.render( layout: MenuBarLayout(lines: [[.percent(window: .session)]]), data: data, @@ -683,10 +685,10 @@ struct MenuBarLayoutRendererTests { let data = self.data() let output = renderer.render( - layout: MenuBarLayout(lines: [[.conditional(conditional)]]), + layout: MenuBarLayout(lines: [[.conditional(id: conditional.id)]]), data: data, icon: nil, - options: self.options()) + options: self.options(conditionals: [conditional])) let control = renderer.render( layout: MenuBarLayout(lines: [[.percent(window: .session)]]), data: data, @@ -725,10 +727,10 @@ struct MenuBarLayoutRendererTests { cost30d: nil) let output = renderer.render( - layout: MenuBarLayout(lines: [[.conditional(conditional)]]), + layout: MenuBarLayout(lines: [[.conditional(id: conditional.id)]]), data: data, icon: nil, - options: self.options()) + options: self.options(conditionals: [conditional])) let control = renderer.render( layout: MenuBarLayout(lines: [[.resetCountdown]]), data: data, @@ -742,25 +744,23 @@ struct MenuBarLayoutRendererTests { @Test func `nested conditional depth cap renders placeholder`() { let renderer = MenuBarLayoutRenderer() - // Build a chain 10 levels deep; the renderer caps traversal at maxConditionalDepth. - var inner = MenuBarLayoutConditional( + // A conditionals entry may reference itself through its branches; the renderer caps + // traversal at maxConditionalDepth. + let selfID = UUID() + let looping = MenuBarLayoutConditional( + id: selfID, + name: "loop", clauses: [self.clause(metric: .session, comparison: .greaterThan, threshold: 0)], - thenToken: .percent(window: .session), + thenToken: .conditional(id: selfID), elseToken: .space) - for _ in 0..<9 { - inner = MenuBarLayoutConditional( - clauses: [self.clause(metric: .session, comparison: .greaterThan, threshold: 0)], - thenToken: .conditional(inner), - elseToken: .space) - } let data = self.data() // The cap triggers before any branch is evaluated, independent of live values, // so the missing-value placeholder is the expected title. let output = renderer.render( - layout: MenuBarLayout(lines: [[.conditional(inner)]]), + layout: MenuBarLayout(lines: [[.conditional(id: selfID)]]), data: data, icon: nil, - options: self.options()) + options: self.options(conditionals: [looping])) #expect(output.attributedTitle.string == "–") } @@ -775,10 +775,10 @@ struct MenuBarLayoutRendererTests { let data = self.data() let output = renderer.render( - layout: MenuBarLayout(lines: [[.conditional(conditional)]]), + layout: MenuBarLayout(lines: [[.conditional(id: conditional.id)]]), data: data, icon: nil, - options: self.options()) + options: self.options(conditionals: [conditional])) let control = renderer.render( layout: MenuBarLayout(lines: [[.percent(window: .session)]]), data: data, @@ -789,6 +789,115 @@ struct MenuBarLayoutRendererTests { #expect(output.accessibilityLabel == L("%@ %@", L("Session"), "25%")) } + @Test + func `hidden branch leaves no orphaned spacing`() { + let renderer = MenuBarLayoutRenderer() + // Session is 25%, so a > 50 threshold fails and the else branch (.hidden) wins: the + // conditional contributes nothing, and the neighbors must join without a stray separator. + let conditional = MenuBarLayoutConditional( + clauses: [self.clause(metric: .session, comparison: .greaterThan, threshold: 50)], + thenToken: .percent(window: .session), + elseToken: .hidden) + let options = self.options(conditionals: [conditional]) + + let middle = renderer.render( + layout: MenuBarLayout(lines: [[ + .percent(window: .session), + .conditional(id: conditional.id), + .percent(window: .weekly), + ]]), + data: self.data(), + icon: nil, + options: options) + #expect(middle.attributedTitle.string == "5h 25%\u{2009}W 60%") + + let trailing = renderer.render( + layout: MenuBarLayout(lines: [[.percent(window: .session), .conditional(id: conditional.id)]]), + data: self.data(), + icon: nil, + options: options) + #expect(trailing.attributedTitle.string == "5h 25%") + } + + @Test + func `dangling conditional reference renders placeholder`() { + let renderer = MenuBarLayoutRenderer() + // An id not present in the library has nothing to resolve; the renderer must show the + // missing-value placeholder instead of emitting a branch. + let output = renderer.render( + layout: MenuBarLayout(lines: [[.conditional(id: UUID())]]), + data: self.data(), + icon: nil, + options: self.options(conditionals: [])) + + #expect(output.attributedTitle.string == "–") + #expect(output.accessibilityLabel.contains(L("menu_bar_layout_conditional_unavailable"))) + } + + @Test + func `library edit changes the rendered branch without touching the layout`() { + let renderer = MenuBarLayoutRenderer() + let id = UUID() + // The layout only stores the reference; the library entry decides the branch. + let layout = MenuBarLayout(lines: [[.conditional(id: id)]]) + + let conditionTrue = MenuBarLayoutConditional( + id: id, + clauses: [self.clause(metric: .session, comparison: .greaterThan, threshold: 0)], + thenToken: .percent(window: .session), + elseToken: .resetCountdown) + let first = renderer.render( + layout: layout, + data: self.data(), + icon: nil, + options: self.options(conditionals: [conditionTrue])) + #expect(first.attributedTitle.string == "5h 25%") + + // Flip the comparison so the same layout now renders the else branch. + let conditionFalse = MenuBarLayoutConditional( + id: id, + clauses: [self.clause(metric: .session, comparison: .greaterThan, threshold: 50)], + thenToken: .percent(window: .session), + elseToken: .resetCountdown) + let second = renderer.render( + layout: layout, + data: self.data(), + icon: nil, + options: self.options(conditionals: [conditionFalse])) + #expect(second.attributedTitle.string != first.attributedTitle.string) + #expect(second.attributedTitle.string == "in 2h") + } + + @Test + func `mixed and or evaluates as a left fold`() { + let renderer = MenuBarLayoutRenderer() + // Session 25% > 0 (T) or weekly 60% > 90 (F) and automatic 50% > 90 (F). + // The left fold yields ((T ∨ F) ∧ F) = false, so the else branch wins; conventional + // precedence (T ∨ (F ∧ F) = true) would pick the then branch instead. + let conditional = MenuBarLayoutConditional( + clauses: [ + self.clause(metric: .session, comparison: .greaterThan, threshold: 0), + self.clause(metric: .weekly, comparison: .greaterThan, threshold: 90, combinator: .or), + self.clause(metric: .automatic, comparison: .greaterThan, threshold: 90, combinator: .and), + ], + thenToken: .percent(window: .session), + elseToken: .resetCountdown) + + let output = renderer.render( + layout: MenuBarLayout(lines: [[.conditional(id: conditional.id)]]), + data: self.data(), + icon: nil, + options: self.options(conditionals: [conditional])) + let control = renderer.render( + layout: MenuBarLayout(lines: [[.resetCountdown]]), + data: self.data(), + icon: nil, + options: self.options()) + + #expect(output.attributedTitle.string == control.attributedTitle.string) + #expect(output.attributedTitle.string == "in 2h") + } + private func clause( metric: MenuBarConditionalMetric, comparison: MenuBarConditionalComparison, @@ -845,12 +954,14 @@ struct MenuBarLayoutRendererTests { private func options( now: Date? = nil, verticalAdjustment: Int = 0, - isStale: Bool = false) -> MenuBarLayoutRenderOptions + isStale: Bool = false, + conditionals: [MenuBarLayoutConditional] = []) -> MenuBarLayoutRenderOptions { MenuBarLayoutRenderOptions( size: .regular, highContrast: false, showUsed: true, + conditionals: conditionals, appearanceName: "aqua", isDebugApp: false, isStale: isStale, diff --git a/Tests/CodexBarTests/MenuBarLayoutTests.swift b/Tests/CodexBarTests/MenuBarLayoutTests.swift index 78aaecd80d..066b197b94 100644 --- a/Tests/CodexBarTests/MenuBarLayoutTests.swift +++ b/Tests/CodexBarTests/MenuBarLayoutTests.swift @@ -60,13 +60,15 @@ struct MenuBarLayoutTests { predicate: MenuBarConditionalPredicate(metric: .session, comparison: .greaterThan, threshold: 0))], thenToken: .percent(window: .session), elseToken: .resetCountdown) - let layout = MenuBarLayout(lines: [[.icon, .conditional(conditional)]]) + let layout = MenuBarLayout(lines: [[.icon, .conditional(id: conditional.id)]]) let data = try JSONEncoder().encode(layout) let decoded = try JSONDecoder().decode(MenuBarLayout.self, from: data) #expect(decoded == layout) - #expect(String(bytes: data, encoding: .utf8)?.contains("conditional") == true) + let json = try #require(String(bytes: data, encoding: .utf8)) + #expect(json.contains("conditional")) + #expect(json.contains(conditional.id.uuidString)) } @Test @@ -77,12 +79,12 @@ struct MenuBarLayoutTests { predicate: MenuBarConditionalPredicate(metric: .session, comparison: .greaterThan, threshold: 0))], thenToken: .percent(window: .session), elseToken: .resetCountdown) - let layout = MenuBarLayout(lines: [[.icon, .conditional(conditional)]]) + let layout = MenuBarLayout(lines: [[.icon, .conditional(id: conditional.id)]]) - let flattened = layout.flattenedTokens + let flattened = layout.flattenedTokens(conditionals: [conditional]) #expect(flattened.contains(.icon)) - #expect(flattened.contains(.conditional(conditional))) + #expect(flattened.contains(.conditional(id: conditional.id))) #expect(flattened.contains(.percent(window: .session))) #expect(flattened.contains(.resetCountdown)) } @@ -131,7 +133,7 @@ struct MenuBarLayoutTests { @Test @MainActor func `conditional display name falls back when unnamed then uses its name`() { - let unnamed = MenuBarLayoutConditional.defaultValue + let unnamed = MenuBarLayoutConditional.makeDefault() #expect(!unnamed.displayName.isEmpty) let named = MenuBarLayoutConditional( @@ -181,16 +183,100 @@ struct MenuBarLayoutTests { predicate: MenuBarConditionalPredicate(metric: .session, comparison: .greaterThan, threshold: 30))], thenToken: .percent(window: .session), elseToken: .resetCountdown) - let layout = MenuBarLayout(lines: [[.icon, .conditional(conditional)]]) + let defaultEntry = MenuBarLayoutConditional.makeDefault() + let layout = MenuBarLayout(lines: [[.icon, .conditional(id: conditional.id)]]) - settings.menuBarLayoutConditionals = [conditional, .defaultValue] + settings.menuBarLayoutConditionals = [conditional, defaultEntry] settings.setMenuBarLayout(layout, for: nil) let reloaded = Self.reloadSettingsStore(settings) - #expect(reloaded.menuBarLayoutConditionals == [conditional, .defaultValue]) + #expect(reloaded.menuBarLayoutConditionals == [conditional, defaultEntry]) #expect(reloaded.menuBarLayout == layout) } + @Test + func `unique copy name walks numbered suffixes`() { + let existing: Set = ["gate (copy)"] + #expect(MenuBarLayoutConditional.uniqueCopyName(basedOn: "Gate", existingNames: existing) == "Gate (copy 2)") + + // An empty stem falls back to the generic conditional label rather than a bare suffix. + let generic = MenuBarLayoutConditional.uniqueCopyName(basedOn: " ", existingNames: []) + #expect(!generic.isEmpty) + } + + @Test + func `conditional summary parenthesizes mixed combinators`() { + let mixed = MenuBarLayoutConditional( + clauses: [ + MenuBarConditionalClause( + combinator: nil, + predicate: MenuBarConditionalPredicate(metric: .session, comparison: .greaterThan, threshold: 0)), + MenuBarConditionalClause( + combinator: .or, + predicate: MenuBarConditionalPredicate(metric: .weekly, comparison: .greaterThan, threshold: 50)), + MenuBarConditionalClause( + combinator: .and, + predicate: MenuBarConditionalPredicate( + metric: .automatic, + comparison: .greaterThan, + threshold: 80)), + ], + thenToken: .percent(window: .session), + elseToken: .resetCountdown) + #expect(mixed.editorSummary(provider: nil).contains("(")) + + let uniform = MenuBarLayoutConditional( + clauses: [ + MenuBarConditionalClause( + combinator: nil, + predicate: MenuBarConditionalPredicate(metric: .session, comparison: .greaterThan, threshold: 0)), + MenuBarConditionalClause( + combinator: .and, + predicate: MenuBarConditionalPredicate(metric: .weekly, comparison: .greaterThan, threshold: 50)), + ], + thenToken: .percent(window: .session), + elseToken: .resetCountdown) + #expect(!uniform.editorSummary(provider: nil).contains("(")) + } + + @Test + @MainActor + func `removing a library conditional strips references everywhere`() { + let suite = "MenuBarLayoutTests-removing-conditional" + let settings = testSettingsStore(suiteName: suite) + let conditional = MenuBarLayoutConditional( + id: UUID(), + clauses: [MenuBarConditionalClause( + combinator: nil, + predicate: MenuBarConditionalPredicate(metric: .session, comparison: .greaterThan, threshold: 30))], + thenToken: .percent(window: .session), + elseToken: .resetCountdown) + let global = MenuBarLayout(lines: [[.icon, .conditional(id: conditional.id)]]) + let override = MenuBarLayout(lines: [[.conditional(id: conditional.id), .percent(window: .weekly)]]) + + settings.menuBarLayoutConditionals = [conditional] + settings.setMenuBarLayout(global, for: nil) + settings.setMenuBarLayout(override, for: .claude) + + settings.removeMenuBarLayoutConditional(id: conditional.id) + + #expect(settings.menuBarLayoutConditionals.isEmpty) + #expect(Self.hasNoConditionalReference(settings.menuBarLayout)) + #expect(Self.hasNoConditionalReference(settings.menuBarLayout(for: .claude))) + + let reloaded = Self.reloadSettingsStore(settings) + #expect(reloaded.menuBarLayoutConditionals.isEmpty) + #expect(Self.hasNoConditionalReference(reloaded.menuBarLayout)) + #expect(Self.hasNoConditionalReference(reloaded.menuBarLayout(for: .claude))) + } + + private static func hasNoConditionalReference(_ layout: MenuBarLayout) -> Bool { + !layout.lines.flatMap(\.self).contains { token in + if case .conditional = token { return true } + return false + } + } + @Test func `decoding normalizes empty and extra lines`() throws { let emptyData = try JSONEncoder().encode(UnnormalizedLayout(lines: [])) diff --git a/Tests/CodexBarTests/MenuLayoutScreenshotRenderTests.swift b/Tests/CodexBarTests/MenuLayoutScreenshotRenderTests.swift index 5830625d27..75e205a36d 100644 --- a/Tests/CodexBarTests/MenuLayoutScreenshotRenderTests.swift +++ b/Tests/CodexBarTests/MenuLayoutScreenshotRenderTests.swift @@ -117,6 +117,7 @@ final class MenuLayoutScreenshotRenderTests: XCTestCase { size: .regular, highContrast: false, showUsed: true, + conditionals: [], appearanceName: "proof", isDebugApp: false, now: Self.now)) @@ -210,6 +211,7 @@ final class MenuLayoutScreenshotRenderTests: XCTestCase { size: .regular, highContrast: false, showUsed: true, + conditionals: [], appearanceName: "proof", isDebugApp: false, now: now)) diff --git a/Tests/CodexBarTests/StatusItemBalanceDisplayTests.swift b/Tests/CodexBarTests/StatusItemBalanceDisplayTests.swift index cde8cc7b51..e84d8456a8 100644 --- a/Tests/CodexBarTests/StatusItemBalanceDisplayTests.swift +++ b/Tests/CodexBarTests/StatusItemBalanceDisplayTests.swift @@ -259,6 +259,7 @@ struct StatusItemBalanceDisplayTests { size: .regular, highContrast: false, showUsed: true, + conditionals: [], appearanceName: "aqua", isDebugApp: false, now: Date())) @@ -998,6 +999,7 @@ extension StatusItemBalanceDisplayTests { size: .regular, highContrast: false, showUsed: true, + conditionals: [], appearanceName: "aqua", isDebugApp: false, now: Date())) @@ -1053,6 +1055,7 @@ extension StatusItemBalanceDisplayTests { size: .regular, highContrast: false, showUsed: true, + conditionals: [], appearanceName: "aqua", isDebugApp: false, now: Date())) From 5616fc931ba4becf2546c5b6f6889b454cce9743 Mon Sep 17 00:00:00 2001 From: William Mitchell Date: Wed, 19 Aug 2026 18:21:58 +0100 Subject: [PATCH 04/11] Close stray SwiftUI Settings placeholder window at launch The Settings scene exists only to own the app-menu Settings command; the real settings window is AppKit-managed. macOS could present or state-restore the scene's empty placeholder window at startup. --- Sources/CodexBar/CodexbarApp.swift | 16 ++++++++++++++++ .../MenuBarLayoutRendererTests.swift | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/Sources/CodexBar/CodexbarApp.swift b/Sources/CodexBar/CodexbarApp.swift index 26f7a0170c..686acf65a7 100644 --- a/Sources/CodexBar/CodexbarApp.swift +++ b/Sources/CodexBar/CodexbarApp.swift @@ -458,6 +458,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { self.installDebugMemoryPressureObserverIfNeeded() #endif self.ensureStatusController() + self.closeSwiftUISettingsPlaceholderWindow() self.observeSettingsApplicationMenuLanguage() self.scheduleSettingsApplicationMenuValidation( missingItemRetriesRemaining: Self.settingsMenuReadinessRetryCount, @@ -497,6 +498,21 @@ final class AppDelegate: NSObject, NSApplicationDelegate { } } + /// The SwiftUI `Settings` scene exists only to own the app-menu Settings command; the real + /// settings window is AppKit-managed (`SettingsWindowController`). macOS can still present or + /// state-restore the scene's empty placeholder window at launch — close it and keep it out of + /// state restoration so it cannot come back on the next launch. + private func closeSwiftUISettingsPlaceholderWindow() { + DispatchQueue.main.async { + for window in NSApp.windows + where window.identifier?.rawValue.hasPrefix("com_apple_SwiftUI_Settings") == true + { + window.isRestorable = false + window.close() + } + } + } + func applicationWillTerminate(_ notification: Notification) { self.cloudSyncCoordinator?.stop() self.memoryPressureMonitor.stop() diff --git a/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift b/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift index 897647a296..438d866c38 100644 --- a/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift +++ b/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift @@ -765,6 +765,10 @@ struct MenuBarLayoutRendererTests { iconKey: "missing", providerName: nil, accountLabel: nil, + laneLabels: MenuBarLayoutLaneLabels(provider: .codex, snapshot: nil), + primary: nil, + secondary: nil, + tertiary: nil, session: nil, weekly: nil, scopedWeekly: nil, From 1f91002c96ee78f93c421102185985a4fdac528e Mon Sep 17 00:00:00 2001 From: William Mitchell Date: Wed, 19 Aug 2026 19:44:31 +0100 Subject: [PATCH 05/11] Keep conditional layouts readable by older releases The dual-write path promises the legacy blob stays decodable by the 0.53.x token surface, but .conditional and .hidden have no case there, so an older decoder dropped the whole saved layout or override. Filter tokens with no legacy representation out of the projection instead of mapping them, drop a line emptied purely by that filtering, and fall back to the default layout when nothing remains. --- Sources/CodexBar/MenuBarLayout.swift | 28 +++++++- Tests/CodexBarTests/MenuBarLayoutTests.swift | 76 ++++++++++++++++++++ 2 files changed, 101 insertions(+), 3 deletions(-) diff --git a/Sources/CodexBar/MenuBarLayout.swift b/Sources/CodexBar/MenuBarLayout.swift index fe37fbcb90..1e802c31d1 100644 --- a/Sources/CodexBar/MenuBarLayout.swift +++ b/Sources/CodexBar/MenuBarLayout.swift @@ -207,6 +207,17 @@ enum MenuBarLayoutToken: Codable, Hashable, Sendable { return nil } + /// Tokens added after 0.53.x that an older decoder has no case for at all. `legacyCompatible` + /// cannot map them onto an existing case without inventing content, so the layout projection + /// drops them instead: an older release then decodes the rest of the layout rather than + /// failing the whole blob and losing the user's arrangement. + var hasLegacyRepresentation: Bool { + switch self { + case .conditional, .hidden: false + default: true + } + } + /// Maps `lanePercent` onto tokens a 0.53.x decoder already understands so a downgrade keeps a /// layout instead of dropping the whole blob. Direct lanes follow the provider's semantic /// windows: Kimi's primary is weekly, so a Kimi override does not swap 7-day and 5-hour. @@ -310,10 +321,21 @@ struct MenuBarLayout: Codable, Hashable, Sendable { Set(self.lines.joined().compactMap(\.selectedLane)) } + /// Older-readable projection of this layout. Tokens an older decoder cannot represent are + /// dropped rather than mapped; a line left empty by that filtering is dropped too, and a layout + /// with nothing left falls back to `defaultLayout` via `MenuBarLayout(lines:)` normalization. func legacyCompatible(for provider: UsageProvider? = nil) -> MenuBarLayout { - MenuBarLayout(lines: self.lines.map { line in - line.map { $0.legacyCompatible(for: provider) } - }) + let projected = self.lines.map { line in + line + .filter(\.hasLegacyRepresentation) + .map { $0.legacyCompatible(for: provider) } + } + // Keep a trailing empty line only when the layout was already stacked with an empty line, + // so an older release does not inherit a blank stacked row created purely by filtering. + let compacted = projected.enumerated().filter { index, line in + !line.isEmpty || self.lines[index].isEmpty + }.map(\.element) + return MenuBarLayout(lines: compacted) } } diff --git a/Tests/CodexBarTests/MenuBarLayoutTests.swift b/Tests/CodexBarTests/MenuBarLayoutTests.swift index e3c45a8b46..3332305c37 100644 --- a/Tests/CodexBarTests/MenuBarLayoutTests.swift +++ b/Tests/CodexBarTests/MenuBarLayoutTests.swift @@ -370,6 +370,37 @@ struct MenuBarLayoutTests { #expect(layout.legacyCompatible().selectedLanes.isEmpty) } + @Test + func `conditional and hidden tokens drop out of the older-readable projection`() { + let conditional = MenuBarLayoutConditional( + clauses: [MenuBarConditionalClause( + combinator: nil, + predicate: MenuBarConditionalPredicate(metric: .session, comparison: .greaterThan, threshold: 30))], + thenToken: .percent(window: .session), + elseToken: .hidden) + + // A 0.53.x decoder has no case for these tokens, so the projection must not contain them; + // the surrounding arrangement has to survive. + let mixed = MenuBarLayout(lines: [[ + .icon, + .conditional(id: conditional.id), + .percent(window: .session), + .hidden, + ]]) + #expect(mixed.legacyCompatible() == MenuBarLayout(lines: [[.icon, .percent(window: .session)]])) + + // A line emptied purely by filtering must not survive as a blank stacked row. + let stacked = MenuBarLayout(lines: [ + [.icon, .percent(window: .weekly)], + [.conditional(id: conditional.id)], + ]) + #expect(stacked.legacyCompatible() == MenuBarLayout(lines: [[.icon, .percent(window: .weekly)]])) + + // Nothing left to project falls back to the default layout rather than an empty title. + let conditionalOnly = MenuBarLayout(lines: [[.conditional(id: conditional.id)]]) + #expect(conditionalOnly.legacyCompatible() == .defaultLayout) + } + @Test func `legacy layout JSON without lanePercent cannot decode current lane tokens`() throws { let current = try JSONEncoder().encode(MenuBarLayout(lines: [[ @@ -727,6 +758,51 @@ struct MenuBarLayoutTests { #expect(reloaded.menuBarLayoutOverrides[.claude] == claude) } + @Test + @MainActor + func `conditional layouts dual-write an older-readable fallback`() throws { + let settings = testSettingsStore(suiteName: "MenuBarLayoutTests-conditional-downgrade") + let conditional = MenuBarLayoutConditional( + name: "Gate", + clauses: [MenuBarConditionalClause( + combinator: nil, + predicate: MenuBarConditionalPredicate(metric: .session, comparison: .greaterThan, threshold: 50))], + thenToken: .percent(window: .session), + elseToken: .hidden) + let global = MenuBarLayout(lines: [[.icon, .conditional(id: conditional.id), .percent(window: .automatic)]]) + let cursor = MenuBarLayout(lines: [[.conditional(id: conditional.id), .percent(window: .weekly)]]) + + settings.menuBarLayoutConditionals = [conditional] + settings.setMenuBarLayout(global, for: nil) + settings.setMenuBarLayout(cursor, for: .cursor) + + let decoder = JSONDecoder() + let currentGlobal = try #require(settings.userDefaults.data(forKey: MenuBarLayoutUserDefaultsKey.layoutCurrent)) + let legacyGlobal = try #require(settings.userDefaults.data(forKey: MenuBarLayoutUserDefaultsKey.layout)) + let currentOverrides = try #require( + settings.userDefaults.data(forKey: MenuBarLayoutUserDefaultsKey.overridesCurrent)) + let legacyOverrides = try #require(settings.userDefaults.data(forKey: MenuBarLayoutUserDefaultsKey.overrides)) + + // The legacy blob must stay decodable by the 0.53.x token surface, minus the new tokens. + #expect(try decoder.decode(PreLanePercentMenuBarLayout.self, from: legacyGlobal) == PreLanePercentMenuBarLayout( + lines: [[.icon, .percent(window: .automatic)]])) + #expect(throws: DecodingError.self) { + try decoder.decode(PreLanePercentMenuBarLayout.self, from: currentGlobal) + } + + let legacyMap = try decoder.decode([String: PreLanePercentMenuBarLayout].self, from: legacyOverrides) + #expect(legacyMap["cursor"] == PreLanePercentMenuBarLayout(lines: [[.percent(window: .weekly)]])) + #expect(throws: DecodingError.self) { + try decoder.decode([String: PreLanePercentMenuBarLayout].self, from: currentOverrides) + } + + // Upgrading again keeps the full-fidelity layout: the dual-write blobs agree. + let reloaded = Self.reloadSettingsStore(settings) + #expect(reloaded.menuBarLayout == global) + #expect(reloaded.menuBarLayoutOverrides[.cursor] == cursor) + #expect(reloaded.menuBarLayoutConditionals == [conditional]) + } + @Test @MainActor func `Kimi lane overrides dual-write reversed semantic windows`() throws { From 43c509732bc407b7253e9670e03e76ee72a570c6 Mon Sep 17 00:00:00 2001 From: William Mitchell Date: Wed, 19 Aug 2026 19:48:17 +0100 Subject: [PATCH 06/11] Collapse menu bar lines emptied by a hidden branch Resolving a conditional to .hidden removed the token but kept its line, so a stacked layout still emitted that line's newline, held stacked typography, and announced a blank line to VoiceOver. Drop resolved-empty lines before deriving stacking and separators, and keep the debug marker safe when every line collapses. --- Sources/CodexBar/MenuBarLayoutRenderer.swift | 41 ++++++---- .../MenuBarLayoutRendererTests.swift | 76 ++++++++++++++++++- 2 files changed, 99 insertions(+), 18 deletions(-) diff --git a/Sources/CodexBar/MenuBarLayoutRenderer.swift b/Sources/CodexBar/MenuBarLayoutRenderer.swift index 40e90409a2..1966dade0b 100644 --- a/Sources/CodexBar/MenuBarLayoutRenderer.swift +++ b/Sources/CodexBar/MenuBarLayoutRenderer.swift @@ -219,7 +219,21 @@ final class MenuBarLayoutRenderer { options: MenuBarLayoutRenderOptions) -> MenuBarLayoutRenderedTitle { - let isStacked = layout.lines.count == 2 + let conditionalsByID = Dictionary( + options.conditionals.map { ($0.id, $0) }, + uniquingKeysWith: { first, _ in first }) + + // Pre-resolve conditionals so .hidden branches vanish and adjacent-space checks see + // only the tokens that will actually render. A line left with nothing to render is dropped + // entirely: keeping it would emit a stray newline, hold the title in stacked typography, + // and announce a blank line to VoiceOver. + let renderedLines = layout.lines + .map { line in + line.compactMap { Self.resolvedDisplayToken($0, data: data, conditionals: conditionalsByID) } + } + .filter { !$0.isEmpty } + + let isStacked = renderedLines.count == 2 let font = NSFont.systemFont(ofSize: Self.fontSize(size: options.size, isStacked: isStacked)) let foregroundColor = if options.highContrast { NSColor.labelColor @@ -246,16 +260,6 @@ final class MenuBarLayoutRenderer { let result = NSMutableAttributedString() var accessibilityLines: [String] = [] - let conditionalsByID = Dictionary( - options.conditionals.map { ($0.id, $0) }, - uniquingKeysWith: { first, _ in first }) - - // Pre-resolve conditionals so .hidden branches vanish and adjacent-space checks see - // only the tokens that will actually render. - let resolvedLines = layout.lines.map { line in - line.compactMap { Self.resolvedDisplayToken($0, data: data, conditionals: conditionalsByID) } - } - // Only surface a leading icon via `button.image` when an actual image is available and the // high-contrast contract does not require the icon to stay inside the attributed title. // AppKit dims `button.image` on inactive displays, but high-contrast layouts keep icon + text @@ -263,18 +267,18 @@ final class MenuBarLayoutRenderer { // missing icon the token still renders its placeholder inside the title. let leadingIcon: NSImage? = if options.highContrast { nil - } else if resolvedLines.first?.first == .icon, icon != nil { + } else if renderedLines.first?.first == .icon, icon != nil { icon.map { Self.offsetLeadingIcon($0, adjustment: options.verticalAdjustment) } } else { nil } - for (lineIndex, resolvedLine) in resolvedLines.enumerated() { + for (lineIndex, renderedLine) in renderedLines.enumerated() { if lineIndex > 0 { result.append(NSAttributedString(string: "\n", attributes: attributes)) } var accessibilityParts: [String] = [] - for (tokenIndex, token) in resolvedLine.enumerated() { + for (tokenIndex, token) in renderedLine.enumerated() { // The leading icon is surfaced as `button.image` so AppKit applies the system's // active/inactive display tinting; it is not repeated inside the attributed title, // but its accessibility description must survive for VoiceOver. @@ -282,7 +286,7 @@ final class MenuBarLayoutRenderer { accessibilityParts.append(Self.iconAccessibilityText(data: data)) continue } - if tokenIndex > 0, token != .space, resolvedLine[tokenIndex - 1] != .space { + if tokenIndex > 0, token != .space, renderedLine[tokenIndex - 1] != .space { result.append(NSAttributedString(string: "\u{2009}", attributes: attributes)) } let renderedItem = Self.renderItem( @@ -305,7 +309,12 @@ final class MenuBarLayoutRenderer { if options.isDebugApp { result.append(NSAttributedString(string: " D", attributes: attributes)) - accessibilityLines[accessibilityLines.count - 1].append(", \(L("Debug"))") + // Every line can collapse when a conditional hides the only token in the layout. + if accessibilityLines.isEmpty { + accessibilityLines.append(L("Debug")) + } else { + accessibilityLines[accessibilityLines.count - 1].append(", \(L("Debug"))") + } } let accessibilityLabel = accessibilityLines.enumerated().map { index, line in index == 0 ? line : "\(L("menu_bar_layout_line", index + 1)), \(line)" diff --git a/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift b/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift index 438d866c38..bf30a78770 100644 --- a/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift +++ b/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift @@ -876,6 +876,77 @@ struct MenuBarLayoutRendererTests { #expect(trailing.attributedTitle.string == "5h 25%") } + @Test + func `a line emptied by a hidden branch collapses instead of rendering blank`() throws { + let renderer = MenuBarLayoutRenderer() + // Session is 25%, so > 50 fails and the else branch (.hidden) wins on the conditional line. + let conditional = MenuBarLayoutConditional( + clauses: [self.clause(metric: .session, comparison: .greaterThan, threshold: 50)], + thenToken: .percent(window: .session), + elseToken: .hidden) + let options = self.options(conditionals: [conditional]) + + // Leading line hidden: no leading newline, and no empty VoiceOver line. + let leadingHidden = renderer.render( + layout: MenuBarLayout(lines: [ + [.conditional(id: conditional.id)], + [.percent(window: .weekly)], + ]), + data: self.data(), + icon: nil, + options: options) + #expect(leadingHidden.attributedTitle.string == "W 60%") + #expect(!leadingHidden.accessibilityLabel.contains(L("menu_bar_layout_line", 2))) + + // Trailing line hidden: no trailing newline. + let trailingHidden = renderer.render( + layout: MenuBarLayout(lines: [ + [.percent(window: .weekly)], + [.conditional(id: conditional.id)], + ]), + data: self.data(), + icon: nil, + options: options) + #expect(trailingHidden.attributedTitle.string == "W 60%") + + // A collapsed layout also drops stacked typography: it matches the single-line control. + let control = renderer.render( + layout: MenuBarLayout(lines: [[.percent(window: .weekly)]]), + data: self.data(), + icon: nil, + options: options) + let collapsedOffset = try #require(self.baselineOffset(in: leadingHidden.attributedTitle, at: 0)) + let controlOffset = try #require(self.baselineOffset(in: control.attributedTitle, at: 0)) + #expect(collapsedOffset == controlOffset) + #expect(leadingHidden.accessibilityLabel == control.accessibilityLabel) + } + + @Test + func `every line hidden renders an empty title without crashing`() { + let renderer = MenuBarLayoutRenderer() + let conditional = MenuBarLayoutConditional( + clauses: [self.clause(metric: .session, comparison: .greaterThan, threshold: 50)], + thenToken: .percent(window: .session), + elseToken: .hidden) + + let output = renderer.render( + layout: MenuBarLayout(lines: [[.conditional(id: conditional.id)]]), + data: self.data(), + icon: nil, + options: self.options(conditionals: [conditional])) + #expect(output.attributedTitle.string.isEmpty) + #expect(output.accessibilityLabel.isEmpty) + + // The debug marker has no line to attach to once everything collapsed. + let debug = renderer.render( + layout: MenuBarLayout(lines: [[.conditional(id: conditional.id)]]), + data: self.data(), + icon: nil, + options: self.options(conditionals: [conditional], isDebugApp: true)) + #expect(debug.attributedTitle.string == " D") + #expect(debug.accessibilityLabel == L("Debug")) + } + @Test func `dangling conditional reference renders placeholder`() { let renderer = MenuBarLayoutRenderer() @@ -1029,7 +1100,8 @@ struct MenuBarLayoutRendererTests { now: Date? = nil, verticalAdjustment: Int = 0, isStale: Bool = false, - conditionals: [MenuBarLayoutConditional] = []) -> MenuBarLayoutRenderOptions + conditionals: [MenuBarLayoutConditional] = [], + isDebugApp: Bool = false) -> MenuBarLayoutRenderOptions { MenuBarLayoutRenderOptions( size: .regular, @@ -1037,7 +1109,7 @@ struct MenuBarLayoutRendererTests { showUsed: true, conditionals: conditionals, appearanceName: "aqua", - isDebugApp: false, + isDebugApp: isDebugApp, isStale: isStale, now: now ?? self.now, verticalAdjustment: verticalAdjustment) From e2dc2ffb70d1be6da03d23ecc9f1f88c27518942 Mon Sep 17 00:00:00 2001 From: William Mitchell Date: Wed, 19 Aug 2026 19:59:51 +0100 Subject: [PATCH 07/11] Wrap the conditionals palette instead of overflowing the pane A plain HStack pushed later chips outside the settings pane once the library grew or names got long, and an adaptive LazyVGrid stretched the chips into equal columns with large gaps. Add a leading-aligned wrapping flow layout that places each chip at its natural width, with the row packing rule covered by a test. --- Sources/CodexBar/MenuBarLayoutEditor.swift | 73 ++++++++++++++++++- .../MenuBarLayoutEditorTests.swift | 27 +++++++ 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/Sources/CodexBar/MenuBarLayoutEditor.swift b/Sources/CodexBar/MenuBarLayoutEditor.swift index d484bfddbb..b0e47fa59d 100644 --- a/Sources/CodexBar/MenuBarLayoutEditor.swift +++ b/Sources/CodexBar/MenuBarLayoutEditor.swift @@ -601,11 +601,12 @@ struct MenuBarLayoutEditor: View { .font(.caption) .foregroundStyle(.tertiary) } else { - HStack(spacing: 6) { + MenuBarLayoutChipFlowLayout(spacing: 6) { ForEach(self.settings.menuBarLayoutConditionals, id: \.id) { conditional in self.conditionalPaletteChip(conditional: conditional) } } + .frame(maxWidth: .infinity, alignment: .leading) } } } @@ -792,6 +793,76 @@ struct MenuBarLayoutChipLabel: View { } } +/// Left-aligned wrapping row layout for palette chips. +/// +/// The conditionals palette holds user-named chips of widely varying width. An adaptive +/// `LazyVGrid` would size them into equal columns and spread the leftover pane width between +/// them, and a plain `HStack` would push later chips outside the settings pane; this places each +/// chip at its natural width and wraps to the next row. +struct MenuBarLayoutChipFlowLayout: Layout { + var spacing: CGFloat = 6 + + /// Pure packing rule: subview indices grouped into rows that each stay within `maxWidth`. + /// Kept separate from `Layout` so the wrapping contract is testable without faking subviews. + /// A chip wider than `maxWidth` still occupies its own row rather than being dropped. + static func rows(widths: [CGFloat], maxWidth: CGFloat, spacing: CGFloat) -> [[Int]] { + var rows: [[Int]] = [] + var current: [Int] = [] + var currentWidth: CGFloat = 0 + for (index, width) in widths.enumerated() { + let advance = current.isEmpty ? width : spacing + width + if !current.isEmpty, currentWidth + advance > maxWidth { + rows.append(current) + current = [index] + currentWidth = width + continue + } + current.append(index) + currentWidth += advance + } + if !current.isEmpty { + rows.append(current) + } + return rows + } + + func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { + let sizes = subviews.map { $0.sizeThatFits(.unspecified) } + let rows = Self.rows( + widths: sizes.map(\.width), + maxWidth: proposal.width ?? .infinity, + spacing: self.spacing) + let rowWidths = rows.map { row in + row.reduce(CGFloat.zero) { $0 + sizes[$1].width } + + CGFloat(max(0, row.count - 1)) * self.spacing + } + let rowHeights = rows.map { row in + row.reduce(CGFloat.zero) { max($0, sizes[$1].height) } + } + return CGSize( + width: rowWidths.max() ?? 0, + height: rowHeights.reduce(0, +) + CGFloat(max(0, rows.count - 1)) * self.spacing) + } + + func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { + let sizes = subviews.map { $0.sizeThatFits(.unspecified) } + let rows = Self.rows(widths: sizes.map(\.width), maxWidth: bounds.width, spacing: self.spacing) + var y = bounds.minY + for row in rows { + let rowHeight = row.reduce(CGFloat.zero) { max($0, sizes[$1].height) } + var x = bounds.minX + for index in row { + let size = sizes[index] + subviews[index].place( + at: CGPoint(x: x, y: y + (rowHeight - size.height) / 2), + proposal: ProposedViewSize(size)) + x += size.width + self.spacing + } + y += rowHeight + self.spacing + } + } +} + @MainActor struct MenuBarLayoutPreview: View { let layout: MenuBarLayout diff --git a/Tests/CodexBarTests/MenuBarLayoutEditorTests.swift b/Tests/CodexBarTests/MenuBarLayoutEditorTests.swift index 2150b786f3..f970d3c698 100644 --- a/Tests/CodexBarTests/MenuBarLayoutEditorTests.swift +++ b/Tests/CodexBarTests/MenuBarLayoutEditorTests.swift @@ -208,4 +208,31 @@ struct MenuBarLayoutEditorTests { #expect(MenuBarLayoutBalanceResolver.balance(provider: .codex, snapshot: snapshot) == nil) #expect(MenuBarLayoutToken.balance.editorLabel(provider: .openrouter) == L("Balance")) } + + @Test + func `conditional palette chips wrap instead of overflowing the pane`() { + let spacing: CGFloat = 6 + + // Two 100pt chips fit in 220pt (100 + 6 + 100); the third has to wrap. + #expect(MenuBarLayoutChipFlowLayout.rows( + widths: [100, 100, 100], + maxWidth: 220, + spacing: spacing) == [[0, 1], [2]]) + + // Long localized names still get placed on their own row rather than dropped. + #expect(MenuBarLayoutChipFlowLayout.rows( + widths: [400], + maxWidth: 220, + spacing: spacing) == [[0]]) + #expect(MenuBarLayoutChipFlowLayout.rows( + widths: [400, 120], + maxWidth: 220, + spacing: spacing) == [[0], [1]]) + + // Chips that fit stay on one row, so a short library keeps hugging the leading edge. + #expect(MenuBarLayoutChipFlowLayout.rows( + widths: [80, 90], + maxWidth: 220, + spacing: spacing) == [[0, 1]]) + } } From c1c220460bd4be72a0506ca84e3a4951a1c8d7d7 Mon Sep 17 00:00:00 2001 From: William Mitchell Date: Wed, 19 Aug 2026 20:05:52 +0100 Subject: [PATCH 08/11] Ship a starter conditionals library A fresh install showed an empty Conditionals palette, leaving the feature undiscoverable until someone hand-built a rule. Seed five editable conditionals covering the common cases. Identities are fixed so a placed reference keeps resolving across launches, and seeding only happens when no library key exists, so an emptied library is never reseeded. --- Sources/CodexBar/MenuBarLayout.swift | 70 +++++++++++++++++++ .../Resources/ar.lproj/Localizable.strings | 5 ++ .../Resources/ca.lproj/Localizable.strings | 5 ++ .../Resources/de.lproj/Localizable.strings | 5 ++ .../Resources/en.lproj/Localizable.strings | 5 ++ .../Resources/es.lproj/Localizable.strings | 5 ++ .../Resources/fa.lproj/Localizable.strings | 5 ++ .../Resources/fr.lproj/Localizable.strings | 5 ++ .../Resources/gl.lproj/Localizable.strings | 5 ++ .../Resources/id.lproj/Localizable.strings | 5 ++ .../Resources/it.lproj/Localizable.strings | 5 ++ .../Resources/ja.lproj/Localizable.strings | 5 ++ .../Resources/ko.lproj/Localizable.strings | 5 ++ .../Resources/nl.lproj/Localizable.strings | 5 ++ .../Resources/pl.lproj/Localizable.strings | 5 ++ .../Resources/pt-BR.lproj/Localizable.strings | 5 ++ .../Resources/ru.lproj/Localizable.strings | 5 ++ .../Resources/sv.lproj/Localizable.strings | 5 ++ .../Resources/th.lproj/Localizable.strings | 5 ++ .../Resources/tr.lproj/Localizable.strings | 5 ++ .../Resources/uk.lproj/Localizable.strings | 5 ++ .../Resources/vi.lproj/Localizable.strings | 5 ++ .../zh-Hans.lproj/Localizable.strings | 5 ++ .../zh-Hant.lproj/Localizable.strings | 5 ++ Sources/CodexBar/SettingsStore.swift | 6 +- .../MenuBarLayoutRendererTests.swift | 1 + Tests/CodexBarTests/MenuBarLayoutTests.swift | 37 ++++++++++ 27 files changed, 228 insertions(+), 1 deletion(-) diff --git a/Sources/CodexBar/MenuBarLayout.swift b/Sources/CodexBar/MenuBarLayout.swift index 1e802c31d1..5ffc303540 100644 --- a/Sources/CodexBar/MenuBarLayout.swift +++ b/Sources/CodexBar/MenuBarLayout.swift @@ -151,6 +151,76 @@ struct MenuBarLayoutConditional: Codable, Hashable, Sendable { thenToken: .percent(window: .session), elseToken: .hidden) } + + /// Conditionals seeded into the library on a fresh install so the palette ships with useful, + /// editable starting points instead of an empty list. + /// + /// Identities are fixed rather than generated: a layout that places a shipped conditional keeps + /// resolving across launches, and once the user edits or clears the library the stored array wins, + /// so a deleted entry is never reseeded. + /// + /// Thresholds compare the window's **used** percentage, matching `evaluatesTrue`. + static func shippedLibrary() -> [MenuBarLayoutConditional] { + [ + MenuBarLayoutConditional( + id: self.fixedID("B715B1D1-8C1D-4E99-8050-2B5A4EF4B684"), + name: L("menu_bar_layout_conditional_default_session_busy"), + clauses: [self.clause(.session, .greaterThan, 50)], + thenToken: .percent(window: .session), + elseToken: .hidden), + MenuBarLayoutConditional( + id: self.fixedID("A1C3C131-1BB8-4248-A482-FAF3E403E6E1"), + name: L("menu_bar_layout_conditional_default_weekly_high"), + clauses: [self.clause(.weekly, .greaterThanOrEqual, 90)], + thenToken: .percent(window: .weekly), + elseToken: .hidden), + MenuBarLayoutConditional( + id: self.fixedID("DBF99E1D-D5ED-4D55-AD24-A4171479DA3A"), + name: L("menu_bar_layout_conditional_default_session_spent"), + clauses: [self.clause(.session, .greaterThanOrEqual, 95)], + thenToken: .resetCountdown, + elseToken: .percent(window: .session)), + MenuBarLayoutConditional( + id: self.fixedID("CB1EBADE-B813-4B70-A32D-0FC742DC97A6"), + name: L("menu_bar_layout_conditional_default_either_high"), + clauses: [ + self.clause(.session, .greaterThan, 80), + self.clause(.weekly, .greaterThan, 80, combinator: .or), + ], + thenToken: .resetCountdown, + elseToken: .hidden), + MenuBarLayoutConditional( + id: self.fixedID("4A4E53F8-CABC-4413-B7AA-6C6452A69FEC"), + name: L("menu_bar_layout_conditional_default_scoped_weekly"), + clauses: [self.clause(.scopedWeekly, .greaterThan, 60)], + thenToken: .percent(window: .scopedWeekly), + elseToken: .hidden), + ] + } + + private static func clause( + _ metric: MenuBarConditionalMetric, + _ comparison: MenuBarConditionalComparison, + _ threshold: Double, + combinator: MenuBarConditionalCombinator? = nil) + -> MenuBarConditionalClause + { + MenuBarConditionalClause( + combinator: combinator, + predicate: MenuBarConditionalPredicate( + metric: metric, + comparison: comparison, + threshold: threshold)) + } + + /// The shipped identities are compile-time constants, so a malformed one is a programmer error + /// rather than something to paper over with a fresh identity that would dangle placed references. + private static func fixedID(_ string: String) -> UUID { + guard let id = UUID(uuidString: string) else { + preconditionFailure("Shipped conditional identity must be a valid UUID: \(string)") + } + return id + } } struct MenuBarLayoutLaneLabels: Hashable { diff --git a/Sources/CodexBar/Resources/ar.lproj/Localizable.strings b/Sources/CodexBar/Resources/ar.lproj/Localizable.strings index 968f16f7c3..63ed2b14f8 100644 --- a/Sources/CodexBar/Resources/ar.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ar.lproj/Localizable.strings @@ -1521,3 +1521,8 @@ "menu_bar_layout_conditional_copy_name" = "%@ (نسخة)"; "menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (نسخة %2$d)"; "menu_bar_layout_conditional_unavailable" = "الشرط غير متاح"; +"menu_bar_layout_conditional_default_session_busy" = "استُخدم أكثر من 50٪ من الجلسة"; +"menu_bar_layout_conditional_default_weekly_high" = "استُخدم أكثر من 90٪ من الأسبوع"; +"menu_bar_layout_conditional_default_session_spent" = "الجلسة على وشك النفاد"; +"menu_bar_layout_conditional_default_either_high" = "الجلسة أو الأسبوع في مستوى مرتفع"; +"menu_bar_layout_conditional_default_scoped_weekly" = "نافذة الطراز الأسبوعية فوق 60٪"; diff --git a/Sources/CodexBar/Resources/ca.lproj/Localizable.strings b/Sources/CodexBar/Resources/ca.lproj/Localizable.strings index 719c867edb..87b11c2002 100644 --- a/Sources/CodexBar/Resources/ca.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ca.lproj/Localizable.strings @@ -1520,3 +1520,8 @@ "menu_bar_layout_conditional_copy_name" = "%@ (còpia)"; "menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (còpia %2$d)"; "menu_bar_layout_conditional_unavailable" = "Condicional no disponible"; +"menu_bar_layout_conditional_default_session_busy" = "Sessió per damunt del 50 % utilitzat"; +"menu_bar_layout_conditional_default_weekly_high" = "Setmana per damunt del 90 % utilitzat"; +"menu_bar_layout_conditional_default_session_spent" = "Sessió gairebé esgotada"; +"menu_bar_layout_conditional_default_either_high" = "Sessió o setmana en nivell alt"; +"menu_bar_layout_conditional_default_scoped_weekly" = "Setmana per model per damunt del 60 %"; diff --git a/Sources/CodexBar/Resources/de.lproj/Localizable.strings b/Sources/CodexBar/Resources/de.lproj/Localizable.strings index e8c81b286b..54fc476ad7 100644 --- a/Sources/CodexBar/Resources/de.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/de.lproj/Localizable.strings @@ -1517,3 +1517,8 @@ "menu_bar_layout_conditional_copy_name" = "%@ (Kopie)"; "menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (Kopie %2$d)"; "menu_bar_layout_conditional_unavailable" = "Bedingung nicht verfügbar"; +"menu_bar_layout_conditional_default_session_busy" = "Sitzung über 50 % genutzt"; +"menu_bar_layout_conditional_default_weekly_high" = "Woche über 90 % genutzt"; +"menu_bar_layout_conditional_default_session_spent" = "Sitzung fast aufgebraucht"; +"menu_bar_layout_conditional_default_either_high" = "Sitzung oder Woche läuft hoch"; +"menu_bar_layout_conditional_default_scoped_weekly" = "Modellfenster über 60 % genutzt"; diff --git a/Sources/CodexBar/Resources/en.lproj/Localizable.strings b/Sources/CodexBar/Resources/en.lproj/Localizable.strings index bf239ccfe0..50454d2fc2 100644 --- a/Sources/CodexBar/Resources/en.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/en.lproj/Localizable.strings @@ -1520,3 +1520,8 @@ "menu_bar_layout_conditional_copy_name" = "%@ (copy)"; "menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (copy %2$d)"; "menu_bar_layout_conditional_unavailable" = "Conditional unavailable"; +"menu_bar_layout_conditional_default_session_busy" = "Session over 50% used"; +"menu_bar_layout_conditional_default_weekly_high" = "Weekly over 90% used"; +"menu_bar_layout_conditional_default_session_spent" = "Session nearly spent"; +"menu_bar_layout_conditional_default_either_high" = "Session or weekly running high"; +"menu_bar_layout_conditional_default_scoped_weekly" = "Scoped weekly over 60% used"; diff --git a/Sources/CodexBar/Resources/es.lproj/Localizable.strings b/Sources/CodexBar/Resources/es.lproj/Localizable.strings index 978746ed99..2896701fd2 100644 --- a/Sources/CodexBar/Resources/es.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/es.lproj/Localizable.strings @@ -1515,3 +1515,8 @@ "menu_bar_layout_conditional_copy_name" = "%@ (copia)"; "menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (copia %2$d)"; "menu_bar_layout_conditional_unavailable" = "Condicional no disponible"; +"menu_bar_layout_conditional_default_session_busy" = "Sesión por encima del 50 % usado"; +"menu_bar_layout_conditional_default_weekly_high" = "Semana por encima del 90 % usado"; +"menu_bar_layout_conditional_default_session_spent" = "Sesión casi agotada"; +"menu_bar_layout_conditional_default_either_high" = "Sesión o semana en nivel alto"; +"menu_bar_layout_conditional_default_scoped_weekly" = "Semana por modelo por encima del 60 %"; diff --git a/Sources/CodexBar/Resources/fa.lproj/Localizable.strings b/Sources/CodexBar/Resources/fa.lproj/Localizable.strings index 2427854ef5..7e5c7ab5a2 100644 --- a/Sources/CodexBar/Resources/fa.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/fa.lproj/Localizable.strings @@ -1521,3 +1521,8 @@ "menu_bar_layout_conditional_copy_name" = "%@ (رونوشت)"; "menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (رونوشت %2$d)"; "menu_bar_layout_conditional_unavailable" = "شرط در دسترس نیست"; +"menu_bar_layout_conditional_default_session_busy" = "بیش از ۵۰٪ نشست مصرف شده"; +"menu_bar_layout_conditional_default_weekly_high" = "بیش از ۹۰٪ هفته مصرف شده"; +"menu_bar_layout_conditional_default_session_spent" = "نشست تقریباً تمام شده"; +"menu_bar_layout_conditional_default_either_high" = "نشست یا هفته در سطح بالا"; +"menu_bar_layout_conditional_default_scoped_weekly" = "پنجره هفتگی مدل بیش از ۶۰٪"; diff --git a/Sources/CodexBar/Resources/fr.lproj/Localizable.strings b/Sources/CodexBar/Resources/fr.lproj/Localizable.strings index 888721fa32..93cf7293a1 100644 --- a/Sources/CodexBar/Resources/fr.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/fr.lproj/Localizable.strings @@ -1516,3 +1516,8 @@ "menu_bar_layout_conditional_copy_name" = "%@ (copie)"; "menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (copie %2$d)"; "menu_bar_layout_conditional_unavailable" = "Condition indisponible"; +"menu_bar_layout_conditional_default_session_busy" = "Session au-delà de 50 % utilisés"; +"menu_bar_layout_conditional_default_weekly_high" = "Semaine au-delà de 90 % utilisés"; +"menu_bar_layout_conditional_default_session_spent" = "Session presque épuisée"; +"menu_bar_layout_conditional_default_either_high" = "Session ou semaine à un niveau élevé"; +"menu_bar_layout_conditional_default_scoped_weekly" = "Semaine par modèle au-delà de 60 %"; diff --git a/Sources/CodexBar/Resources/gl.lproj/Localizable.strings b/Sources/CodexBar/Resources/gl.lproj/Localizable.strings index c7bad064e4..3668989160 100644 --- a/Sources/CodexBar/Resources/gl.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/gl.lproj/Localizable.strings @@ -1516,3 +1516,8 @@ "menu_bar_layout_conditional_copy_name" = "%@ (copia)"; "menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (copia %2$d)"; "menu_bar_layout_conditional_unavailable" = "Condicional non dispoñible"; +"menu_bar_layout_conditional_default_session_busy" = "Sesión por riba do 50 % usado"; +"menu_bar_layout_conditional_default_weekly_high" = "Semana por riba do 90 % usado"; +"menu_bar_layout_conditional_default_session_spent" = "Sesión case esgotada"; +"menu_bar_layout_conditional_default_either_high" = "Sesión ou semana en nivel alto"; +"menu_bar_layout_conditional_default_scoped_weekly" = "Semana por modelo por riba do 60 %"; diff --git a/Sources/CodexBar/Resources/id.lproj/Localizable.strings b/Sources/CodexBar/Resources/id.lproj/Localizable.strings index a022cb75ee..bd2421d10b 100644 --- a/Sources/CodexBar/Resources/id.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/id.lproj/Localizable.strings @@ -1520,3 +1520,8 @@ "menu_bar_layout_conditional_copy_name" = "%@ (salinan)"; "menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (salinan %2$d)"; "menu_bar_layout_conditional_unavailable" = "Kondisional tidak tersedia"; +"menu_bar_layout_conditional_default_session_busy" = "Sesi di atas 50% terpakai"; +"menu_bar_layout_conditional_default_weekly_high" = "Mingguan di atas 90% terpakai"; +"menu_bar_layout_conditional_default_session_spent" = "Sesi hampir habis"; +"menu_bar_layout_conditional_default_either_high" = "Sesi atau mingguan sedang tinggi"; +"menu_bar_layout_conditional_default_scoped_weekly" = "Mingguan per model di atas 60%"; diff --git a/Sources/CodexBar/Resources/it.lproj/Localizable.strings b/Sources/CodexBar/Resources/it.lproj/Localizable.strings index e8339d7b2b..ebef69276e 100644 --- a/Sources/CodexBar/Resources/it.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/it.lproj/Localizable.strings @@ -1520,3 +1520,8 @@ "menu_bar_layout_conditional_copy_name" = "%@ (copia)"; "menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (copia %2$d)"; "menu_bar_layout_conditional_unavailable" = "Condizionale non disponibile"; +"menu_bar_layout_conditional_default_session_busy" = "Sessione oltre il 50% usato"; +"menu_bar_layout_conditional_default_weekly_high" = "Settimana oltre il 90% usato"; +"menu_bar_layout_conditional_default_session_spent" = "Sessione quasi esaurita"; +"menu_bar_layout_conditional_default_either_high" = "Sessione o settimana a livello alto"; +"menu_bar_layout_conditional_default_scoped_weekly" = "Settimana per modello oltre il 60%"; diff --git a/Sources/CodexBar/Resources/ja.lproj/Localizable.strings b/Sources/CodexBar/Resources/ja.lproj/Localizable.strings index 000780e193..15d61acc4f 100644 --- a/Sources/CodexBar/Resources/ja.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ja.lproj/Localizable.strings @@ -1517,3 +1517,8 @@ "menu_bar_layout_conditional_copy_name" = "%@(コピー)"; "menu_bar_layout_conditional_copy_name_numbered" = "%1$@(コピー %2$d)"; "menu_bar_layout_conditional_unavailable" = "条件を利用できません"; +"menu_bar_layout_conditional_default_session_busy" = "セッション使用率 50% 超"; +"menu_bar_layout_conditional_default_weekly_high" = "週間使用率 90% 超"; +"menu_bar_layout_conditional_default_session_spent" = "セッションがほぼ上限"; +"menu_bar_layout_conditional_default_either_high" = "セッションまたは週間が高水準"; +"menu_bar_layout_conditional_default_scoped_weekly" = "モデル別週間の使用率 60% 超"; diff --git a/Sources/CodexBar/Resources/ko.lproj/Localizable.strings b/Sources/CodexBar/Resources/ko.lproj/Localizable.strings index 299945a898..1308e5d3c1 100644 --- a/Sources/CodexBar/Resources/ko.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ko.lproj/Localizable.strings @@ -1486,3 +1486,8 @@ "menu_bar_layout_conditional_copy_name" = "%@ (복사본)"; "menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (복사본 %2$d)"; "menu_bar_layout_conditional_unavailable" = "조건을 사용할 수 없음"; +"menu_bar_layout_conditional_default_session_busy" = "세션 50% 초과 사용"; +"menu_bar_layout_conditional_default_weekly_high" = "주간 90% 초과 사용"; +"menu_bar_layout_conditional_default_session_spent" = "세션 거의 소진"; +"menu_bar_layout_conditional_default_either_high" = "세션 또는 주간 사용량 높음"; +"menu_bar_layout_conditional_default_scoped_weekly" = "모델별 주간 60% 초과 사용"; diff --git a/Sources/CodexBar/Resources/nl.lproj/Localizable.strings b/Sources/CodexBar/Resources/nl.lproj/Localizable.strings index ad0115bbb2..b10406a972 100644 --- a/Sources/CodexBar/Resources/nl.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/nl.lproj/Localizable.strings @@ -1516,3 +1516,8 @@ "menu_bar_layout_conditional_copy_name" = "%@ (kopie)"; "menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (kopie %2$d)"; "menu_bar_layout_conditional_unavailable" = "Voorwaarde niet beschikbaar"; +"menu_bar_layout_conditional_default_session_busy" = "Sessie boven 50% gebruikt"; +"menu_bar_layout_conditional_default_weekly_high" = "Week boven 90% gebruikt"; +"menu_bar_layout_conditional_default_session_spent" = "Sessie bijna verbruikt"; +"menu_bar_layout_conditional_default_either_high" = "Sessie of week loopt hoog op"; +"menu_bar_layout_conditional_default_scoped_weekly" = "Modelweek boven 60% gebruikt"; diff --git a/Sources/CodexBar/Resources/pl.lproj/Localizable.strings b/Sources/CodexBar/Resources/pl.lproj/Localizable.strings index 26ad733462..7bc8a23a12 100644 --- a/Sources/CodexBar/Resources/pl.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/pl.lproj/Localizable.strings @@ -1520,3 +1520,8 @@ "menu_bar_layout_conditional_copy_name" = "%@ (kopia)"; "menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (kopia %2$d)"; "menu_bar_layout_conditional_unavailable" = "Warunek niedostępny"; +"menu_bar_layout_conditional_default_session_busy" = "Sesja powyżej 50% zużycia"; +"menu_bar_layout_conditional_default_weekly_high" = "Tydzień powyżej 90% zużycia"; +"menu_bar_layout_conditional_default_session_spent" = "Sesja prawie wyczerpana"; +"menu_bar_layout_conditional_default_either_high" = "Sesja lub tydzień na wysokim poziomie"; +"menu_bar_layout_conditional_default_scoped_weekly" = "Tydzień modelu powyżej 60% zużycia"; diff --git a/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings b/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings index dcf4cade98..1b252cd357 100644 --- a/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings @@ -1517,3 +1517,8 @@ "menu_bar_layout_conditional_copy_name" = "%@ (cópia)"; "menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (cópia %2$d)"; "menu_bar_layout_conditional_unavailable" = "Condicional indisponível"; +"menu_bar_layout_conditional_default_session_busy" = "Sessão acima de 50% usado"; +"menu_bar_layout_conditional_default_weekly_high" = "Semana acima de 90% usado"; +"menu_bar_layout_conditional_default_session_spent" = "Sessão quase esgotada"; +"menu_bar_layout_conditional_default_either_high" = "Sessão ou semana em nível alto"; +"menu_bar_layout_conditional_default_scoped_weekly" = "Semana por modelo acima de 60%"; diff --git a/Sources/CodexBar/Resources/ru.lproj/Localizable.strings b/Sources/CodexBar/Resources/ru.lproj/Localizable.strings index 0542e76767..9552cc6ffe 100644 --- a/Sources/CodexBar/Resources/ru.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ru.lproj/Localizable.strings @@ -1518,3 +1518,8 @@ "menu_bar_layout_conditional_copy_name" = "%@ (копия)"; "menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (копия %2$d)"; "menu_bar_layout_conditional_unavailable" = "Условие недоступно"; +"menu_bar_layout_conditional_default_session_busy" = "Сессия использована более 50 %"; +"menu_bar_layout_conditional_default_weekly_high" = "Неделя использована более 90 %"; +"menu_bar_layout_conditional_default_session_spent" = "Сессия почти исчерпана"; +"menu_bar_layout_conditional_default_either_high" = "Сессия или неделя на высоком уровне"; +"menu_bar_layout_conditional_default_scoped_weekly" = "Недельное окно модели более 60 %"; diff --git a/Sources/CodexBar/Resources/sv.lproj/Localizable.strings b/Sources/CodexBar/Resources/sv.lproj/Localizable.strings index 2fc8b858fe..9dbc581e7b 100644 --- a/Sources/CodexBar/Resources/sv.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/sv.lproj/Localizable.strings @@ -1516,3 +1516,8 @@ "menu_bar_layout_conditional_copy_name" = "%@ (kopia)"; "menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (kopia %2$d)"; "menu_bar_layout_conditional_unavailable" = "Villkor otillgängligt"; +"menu_bar_layout_conditional_default_session_busy" = "Session över 50 % använt"; +"menu_bar_layout_conditional_default_weekly_high" = "Vecka över 90 % använt"; +"menu_bar_layout_conditional_default_session_spent" = "Sessionen nästan förbrukad"; +"menu_bar_layout_conditional_default_either_high" = "Session eller vecka ligger högt"; +"menu_bar_layout_conditional_default_scoped_weekly" = "Modellvecka över 60 % använt"; diff --git a/Sources/CodexBar/Resources/th.lproj/Localizable.strings b/Sources/CodexBar/Resources/th.lproj/Localizable.strings index 79be284554..b693aebb91 100644 --- a/Sources/CodexBar/Resources/th.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/th.lproj/Localizable.strings @@ -1521,3 +1521,8 @@ "menu_bar_layout_conditional_copy_name" = "%@ (สำเนา)"; "menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (สำเนา %2$d)"; "menu_bar_layout_conditional_unavailable" = "เงื่อนไขไม่พร้อมใช้งาน"; +"menu_bar_layout_conditional_default_session_busy" = "ใช้เซสชันเกิน 50%"; +"menu_bar_layout_conditional_default_weekly_high" = "ใช้รายสัปดาห์เกิน 90%"; +"menu_bar_layout_conditional_default_session_spent" = "เซสชันเกือบหมด"; +"menu_bar_layout_conditional_default_either_high" = "เซสชันหรือรายสัปดาห์อยู่ในระดับสูง"; +"menu_bar_layout_conditional_default_scoped_weekly" = "รายสัปดาห์ตามโมเดลเกิน 60%"; diff --git a/Sources/CodexBar/Resources/tr.lproj/Localizable.strings b/Sources/CodexBar/Resources/tr.lproj/Localizable.strings index 688a32a300..1c829a3ec2 100644 --- a/Sources/CodexBar/Resources/tr.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/tr.lproj/Localizable.strings @@ -1519,3 +1519,8 @@ "menu_bar_layout_conditional_copy_name" = "%@ (kopya)"; "menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (kopya %2$d)"; "menu_bar_layout_conditional_unavailable" = "Koşul kullanılamıyor"; +"menu_bar_layout_conditional_default_session_busy" = "Oturumun %50'sinden fazlası kullanıldı"; +"menu_bar_layout_conditional_default_weekly_high" = "Haftanın %90'ından fazlası kullanıldı"; +"menu_bar_layout_conditional_default_session_spent" = "Oturum neredeyse tükendi"; +"menu_bar_layout_conditional_default_either_high" = "Oturum veya hafta yükseldi"; +"menu_bar_layout_conditional_default_scoped_weekly" = "Model haftasının %60'ından fazlası kullanıldı"; diff --git a/Sources/CodexBar/Resources/uk.lproj/Localizable.strings b/Sources/CodexBar/Resources/uk.lproj/Localizable.strings index 6f7d6ab51b..6bd0d6a105 100644 --- a/Sources/CodexBar/Resources/uk.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/uk.lproj/Localizable.strings @@ -1517,3 +1517,8 @@ "menu_bar_layout_conditional_copy_name" = "%@ (копія)"; "menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (копія %2$d)"; "menu_bar_layout_conditional_unavailable" = "Умова недоступна"; +"menu_bar_layout_conditional_default_session_busy" = "Сесію використано понад 50 %"; +"menu_bar_layout_conditional_default_weekly_high" = "Тиждень використано понад 90 %"; +"menu_bar_layout_conditional_default_session_spent" = "Сесія майже вичерпана"; +"menu_bar_layout_conditional_default_either_high" = "Сесія або тиждень на високому рівні"; +"menu_bar_layout_conditional_default_scoped_weekly" = "Тижневе вікно моделі понад 60 %"; diff --git a/Sources/CodexBar/Resources/vi.lproj/Localizable.strings b/Sources/CodexBar/Resources/vi.lproj/Localizable.strings index 4133ada455..522d40b09b 100644 --- a/Sources/CodexBar/Resources/vi.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/vi.lproj/Localizable.strings @@ -1518,3 +1518,8 @@ "menu_bar_layout_conditional_copy_name" = "%@ (bản sao)"; "menu_bar_layout_conditional_copy_name_numbered" = "%1$@ (bản sao %2$d)"; "menu_bar_layout_conditional_unavailable" = "Điều kiện không khả dụng"; +"menu_bar_layout_conditional_default_session_busy" = "Phiên đã dùng trên 50%"; +"menu_bar_layout_conditional_default_weekly_high" = "Tuần đã dùng trên 90%"; +"menu_bar_layout_conditional_default_session_spent" = "Phiên gần cạn"; +"menu_bar_layout_conditional_default_either_high" = "Phiên hoặc tuần đang ở mức cao"; +"menu_bar_layout_conditional_default_scoped_weekly" = "Tuần theo mô hình đã dùng trên 60%"; diff --git a/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings b/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings index 61757340e6..3943c2abc8 100644 --- a/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings @@ -1495,3 +1495,8 @@ "menu_bar_layout_conditional_copy_name" = "%@(副本)"; "menu_bar_layout_conditional_copy_name_numbered" = "%1$@(副本 %2$d)"; "menu_bar_layout_conditional_unavailable" = "条件不可用"; +"menu_bar_layout_conditional_default_session_busy" = "会话已用超过 50%"; +"menu_bar_layout_conditional_default_weekly_high" = "每周已用超过 90%"; +"menu_bar_layout_conditional_default_session_spent" = "会话即将用尽"; +"menu_bar_layout_conditional_default_either_high" = "会话或每周用量偏高"; +"menu_bar_layout_conditional_default_scoped_weekly" = "模型专属每周已用超过 60%"; diff --git a/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings b/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings index 292403bd31..493aacdec6 100644 --- a/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings @@ -1548,3 +1548,8 @@ "menu_bar_layout_conditional_copy_name" = "%@(副本)"; "menu_bar_layout_conditional_copy_name_numbered" = "%1$@(副本 %2$d)"; "menu_bar_layout_conditional_unavailable" = "條件不可用"; +"menu_bar_layout_conditional_default_session_busy" = "工作階段已用超過 50%"; +"menu_bar_layout_conditional_default_weekly_high" = "每週已用超過 90%"; +"menu_bar_layout_conditional_default_session_spent" = "工作階段即將用盡"; +"menu_bar_layout_conditional_default_either_high" = "工作階段或每週用量偏高"; +"menu_bar_layout_conditional_default_scoped_weekly" = "模型專屬每週已用超過 60%"; diff --git a/Sources/CodexBar/SettingsStore.swift b/Sources/CodexBar/SettingsStore.swift index 7a81f8bc41..ebb6c5b120 100644 --- a/Sources/CodexBar/SettingsStore.swift +++ b/Sources/CodexBar/SettingsStore.swift @@ -875,7 +875,11 @@ extension SettingsStore { } private static func loadMenuBarLayoutConditionals(userDefaults: UserDefaults) -> [MenuBarLayoutConditional] { - guard let data = userDefaults.data(forKey: "menuBarLayoutConditionals") else { return [] } + // A missing key means a fresh install, so hand back the shipped library. Any edit, add, or + // removal writes the key, so a library the user deliberately emptied is never reseeded. + guard let data = userDefaults.data(forKey: "menuBarLayoutConditionals") else { + return MenuBarLayoutConditional.shippedLibrary() + } return (try? JSONDecoder().decode([MenuBarLayoutConditional].self, from: data)) ?? [] } diff --git a/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift b/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift index bf30a78770..260751da65 100644 --- a/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift +++ b/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift @@ -6,6 +6,7 @@ import Testing @MainActor @Suite(.serialized) +// swiftlint:disable:next type_body_length struct MenuBarLayoutRendererTests { private let now = Date(timeIntervalSince1970: 1_752_768_000) diff --git a/Tests/CodexBarTests/MenuBarLayoutTests.swift b/Tests/CodexBarTests/MenuBarLayoutTests.swift index 3332305c37..7bc0928ae7 100644 --- a/Tests/CodexBarTests/MenuBarLayoutTests.swift +++ b/Tests/CodexBarTests/MenuBarLayoutTests.swift @@ -3,6 +3,7 @@ import Foundation import Testing @testable import CodexBar +// swiftlint:disable:next type_body_length struct MenuBarLayoutTests { private struct UnnormalizedLayout: Encodable { let lines: [[MenuBarLayoutToken]] @@ -197,6 +198,42 @@ struct MenuBarLayoutTests { #expect(reloaded.menuBarLayout == layout) } + @Test + @MainActor + func `a fresh install ships an editable conditionals library`() { + let settings = testSettingsStore(suiteName: "MenuBarLayoutTests-shipped-conditionals") + + let shipped = MenuBarLayoutConditional.shippedLibrary() + #expect(!shipped.isEmpty) + #expect(settings.menuBarLayoutConditionals == shipped) + + // Identities are fixed, so a placed reference keeps resolving on the next launch. + #expect(MenuBarLayoutConditional.shippedLibrary().map(\.id) == shipped.map(\.id)) + + // Each entry has to be a usable, distinctly named starting point. + #expect(Set(shipped.map(\.id)).count == shipped.count) + #expect(Set(shipped.map { $0.name.lowercased() }).count == shipped.count) + #expect(shipped.allSatisfy { !$0.name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }) + #expect(shipped.allSatisfy { !$0.clauses.isEmpty && $0.clauses[0].combinator == nil }) + } + + @Test + @MainActor + func `clearing the shipped conditionals library survives a reload`() { + let suite = "MenuBarLayoutTests-shipped-conditionals-cleared" + let settings = testSettingsStore(suiteName: suite) + #expect(!settings.menuBarLayoutConditionals.isEmpty) + + // Removing every shipped entry is a deliberate choice; the next launch must not reseed. + for conditional in settings.menuBarLayoutConditionals { + settings.removeMenuBarLayoutConditional(id: conditional.id) + } + #expect(settings.menuBarLayoutConditionals.isEmpty) + + let reloaded = Self.reloadSettingsStore(settings) + #expect(reloaded.menuBarLayoutConditionals.isEmpty) + } + @Test func `unique copy name walks numbered suffixes`() { let existing: Set = ["gate (copy)"] From c7066b72b09a2baf9883fd9b16bee80d3dcb54f4 Mon Sep 17 00:00:00 2001 From: William Mitchell Date: Wed, 19 Aug 2026 20:12:30 +0100 Subject: [PATCH 09/11] Add runtime proof for menu bar conditionals Captures the packaged-bundle behavior behind this PR: the seeded starter library in the editor, the legacy layout blob staying free of conditional tokens, and a hidden branch collapsing its line in the rendered title. --- .../pr-proof/menubar-conditionals-editor.png | Bin 0 -> 226755 bytes .../pr-proof/menubar-conditionals-runtime.log | 33 ++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 .github/pr-proof/menubar-conditionals-editor.png create mode 100644 .github/pr-proof/menubar-conditionals-runtime.log diff --git a/.github/pr-proof/menubar-conditionals-editor.png b/.github/pr-proof/menubar-conditionals-editor.png new file mode 100644 index 0000000000000000000000000000000000000000..601988c64474a8cb58b87d4ea40302c5f25956c5 GIT binary patch literal 226755 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 z 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

?oVPgm1%L_9QWm?#uh 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 Menu Bar > Conditionals +(see menubar-conditionals-editor.png), wrapping across two rows. + +menuBarLayoutConditionals: +-> Seeding happens in memory only; nothing is written at load. A library the user + empties therefore stays empty, because any edit/remove writes the key and the + stored value wins from then on. + +=== 2. Downgrade projection keeps the legacy blob 0.53.x-decodable === +Layout under test is stacked, and line 2 holds only a conditional reference: + +menuBarLayoutV2 (current): {"lines":[[{"icon":{}},{"percent":{"window":"automatic"}}],[{"conditional":{"id":"B715B1D1-8C1D-4E99-8050-2B5A4EF4B684"}}]]} +menuBarLayout (legacy) : {"lines":[[{"icon":{}},{"percent":{"window":"automatic"}}]]} + +-> The legacy blob carries no "conditional" token, so an older decoder reads the + layout instead of failing on an unknown case and discarding the whole blob. + +=== 3. A hidden branch collapses its line === +Session usage sits below the shipped rule's 50% threshold, so line 2 resolves to +.hidden and the whole line drops out. + +status item AXTitle: Claude icon, Usage 87% + +-> One line announced. No "Line 2" segment, no blank trailing line, and the title + keeps single-line typography. The editor's Live preview shows "87%" on one row + while the Menu bar strip still lists two rows (screenshot above). From f71728499dfc3146f6d12d4440fb59b7b99c9f08 Mon Sep 17 00:00:00 2001 From: William Mitchell Date: Wed, 19 Aug 2026 21:38:29 +0100 Subject: [PATCH 10/11] Fix the provider architecture gatekeeper on this branch Two problems made the macOS test shard fail: Restore two '// Provider-specific by design:' markers for the Mistral lanes in the layout preview. The branch's first commit deleted them, so the gatekeeper saw unjustified provider-specific clusters. Realign five allowlist line anchors that this branch shifted, in MenuBarLayout, MenuBarLayoutEditor, SettingsStore, and StatusItemController+MenuBarLayout. --- Sources/CodexBar/MenuBarLayoutEditor.swift | 2 ++ .../ProviderArchitectureGatekeeperTests.swift | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Sources/CodexBar/MenuBarLayoutEditor.swift b/Sources/CodexBar/MenuBarLayoutEditor.swift index b0e47fa59d..d08bf0a4fa 100644 --- a/Sources/CodexBar/MenuBarLayoutEditor.swift +++ b/Sources/CodexBar/MenuBarLayoutEditor.swift @@ -921,6 +921,7 @@ struct MenuBarLayoutPreview: View { snapshot: snapshot) session = semanticWindows.session weekly = semanticWindows.weekly + // Provider-specific by design: Mistral's automatic lane can explicitly select its Monthly Plan window. let automaticPreference = provider == .mistral ? self.settings.menuBarMetricPreference(for: provider, snapshot: snapshot) : .automatic @@ -965,6 +966,7 @@ struct MenuBarLayoutPreview: View { scopedWeekly: MenuBarLayoutRenderWindow(scopedNamed?.window), scopedWeeklyTitle: scopedNamed?.title, automatic: automaticRenderWindow, + // Provider-specific by design: Mistral uses spend text when its automatic lane has no percentage window. automaticText: provider == .mistral && automaticRenderWindow == nil ? StatusItemController.mistralSpendDisplayText(snapshot: snapshot) : nil, diff --git a/Tests/CodexBarTests/ProviderArchitectureGatekeeperTests.swift b/Tests/CodexBarTests/ProviderArchitectureGatekeeperTests.swift index 04ace16921..0b61212f30 100644 --- a/Tests/CodexBarTests/ProviderArchitectureGatekeeperTests.swift +++ b/Tests/CodexBarTests/ProviderArchitectureGatekeeperTests.swift @@ -1741,7 +1741,7 @@ struct ProviderArchitectureGatekeeperTests { reason: "This exact shared renderer maps provider-owned presentation data into the generic UI model."), AllowedProviderConstruct( path: "Sources/CodexBar/MenuBarLayout.swift", - line: 353, + line: 572, anchor: "ProviderDescriptorRegistry.descriptor(for: provider ?? .codex).presentation.primarySemanticWindow)", expectedProviderIDs: ["codex"], expectedReferenceCount: 2, @@ -1749,7 +1749,7 @@ struct ProviderArchitectureGatekeeperTests { reason: "This exact shared construct dispatches a provider-owned capability at the generic integration boundary."), AllowedProviderConstruct( path: "Sources/CodexBar/MenuBarLayoutEditor.swift", - line: 672, + line: 876, anchor: "let provider = self.provider ?? .codex", expectedProviderIDs: ["codex"], expectedReferenceCount: 1, @@ -1757,7 +1757,7 @@ struct ProviderArchitectureGatekeeperTests { reason: "This exact shared construct dispatches a provider-owned capability at the generic integration boundary."), AllowedProviderConstruct( path: "Sources/CodexBar/MenuBarLayoutEditor.swift", - line: 701, + line: 906, anchor: "if provider == .codex,", expectedProviderIDs: ["codex"], expectedReferenceCount: 1, @@ -2269,7 +2269,7 @@ struct ProviderArchitectureGatekeeperTests { reason: "This exact app-runtime bridge coordinates provider-owned state through the shared controller."), AllowedProviderConstruct( path: "Sources/CodexBar/SettingsStore.swift", - line: 1163, + line: 1174, anchor: "if !seen.contains(.factory), let zaiIndex = ordered.firstIndex(of: .zai) {", expectedProviderIDs: ["factory", "minimax", "zai"], expectedReferenceCount: 8, @@ -2486,7 +2486,7 @@ struct ProviderArchitectureGatekeeperTests { reason: "This exact app-runtime bridge coordinates provider-owned state through the shared controller."), AllowedProviderConstruct( path: "Sources/CodexBar/StatusItemController+MenuBarLayout.swift", - line: 164, + line: 165, anchor: "if provider == .codex,", expectedProviderIDs: ["codex"], expectedReferenceCount: 1, From 3d5bf61f79e5248dab465688732815545557fac2 Mon Sep 17 00:00:00 2001 From: William Mitchell Date: Wed, 19 Aug 2026 22:44:38 +0100 Subject: [PATCH 11/11] Cover the icon and empty-line edges of conditional resolution Two behaviors this branch introduced were unproven: a conditional whose branch resolves to .icon in first position has to reach button.image the same way a literal .icon token does, since AppKit only dims that path on inactive displays; and a line the user left empty must survive the older-readable projection, unlike one emptied by token filtering. --- .../MenuBarLayoutRendererTests.swift | 32 +++++++++++++++++++ Tests/CodexBarTests/MenuBarLayoutTests.swift | 5 +++ 2 files changed, 37 insertions(+) diff --git a/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift b/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift index 260751da65..f0e373bf65 100644 --- a/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift +++ b/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift @@ -948,6 +948,38 @@ struct MenuBarLayoutRendererTests { #expect(debug.accessibilityLabel == L("Debug")) } + @Test + func `a conditional resolving to the icon surfaces it as the leading image`() throws { + let renderer = MenuBarLayoutRenderer() + let icon = NSImage(size: NSSize(width: 16, height: 16)) + icon.isTemplate = true + // Session is 25%, so > 0 passes and the then branch (.icon) wins in first position. + let conditional = MenuBarLayoutConditional( + clauses: [self.clause(metric: .session, comparison: .greaterThan, threshold: 0)], + thenToken: .icon, + elseToken: .hidden) + + let output = renderer.render( + layout: MenuBarLayout(lines: [[.conditional(id: conditional.id), .percent(window: .automatic)]]), + data: self.data(), + icon: icon, + options: self.options(conditionals: [conditional])) + let control = renderer.render( + layout: MenuBarLayout(lines: [[.icon, .percent(window: .automatic)]]), + data: self.data(), + icon: icon, + options: self.options()) + + // AppKit only dims `button.image` on inactive displays, so a resolved icon has to take the + // same path a literal `.icon` token takes rather than becoming an attributed attachment. + let resolvedIcon = try #require(output.leadingIcon) + let controlIcon = try #require(control.leadingIcon) + #expect(resolvedIcon === controlIcon) + #expect(output.attributedTitle.string == control.attributedTitle.string) + #expect(output.attributedTitle.attribute(.attachment, at: 0, effectiveRange: nil) == nil) + #expect(output.accessibilityLabel == control.accessibilityLabel) + } + @Test func `dangling conditional reference renders placeholder`() { let renderer = MenuBarLayoutRenderer() diff --git a/Tests/CodexBarTests/MenuBarLayoutTests.swift b/Tests/CodexBarTests/MenuBarLayoutTests.swift index 7bc0928ae7..3090fcdd16 100644 --- a/Tests/CodexBarTests/MenuBarLayoutTests.swift +++ b/Tests/CodexBarTests/MenuBarLayoutTests.swift @@ -436,6 +436,11 @@ struct MenuBarLayoutTests { // Nothing left to project falls back to the default layout rather than an empty title. let conditionalOnly = MenuBarLayout(lines: [[.conditional(id: conditional.id)]]) #expect(conditionalOnly.legacyCompatible() == .defaultLayout) + + // A line the user left empty (line break added, no token dropped in yet) is not the same as + // one emptied by filtering, so it survives the projection unchanged. + let pendingSecondLine = MenuBarLayout(lines: [[.icon, .conditional(id: conditional.id)], []]) + #expect(pendingSecondLine.legacyCompatible() == MenuBarLayout(lines: [[.icon], []])) } @Test