From 4b679a6543bc11749d217b1d2f03d3272642b9b5 Mon Sep 17 00:00:00 2001 From: Elijah Friedman Date: Fri, 25 Sep 2026 13:10:22 -0400 Subject: [PATCH 1/3] Extract layout options row and remove hint --- Sources/CodexBar/MenuBarLayoutEditor.swift | 111 +++++++++++------- .../Resources/ar.lproj/Localizable.strings | 1 - .../Resources/ca.lproj/Localizable.strings | 1 - .../Resources/de.lproj/Localizable.strings | 1 - .../Resources/en.lproj/Localizable.strings | 1 - .../Resources/es.lproj/Localizable.strings | 1 - .../Resources/fa.lproj/Localizable.strings | 1 - .../Resources/fr.lproj/Localizable.strings | 1 - .../Resources/gl.lproj/Localizable.strings | 1 - .../Resources/id.lproj/Localizable.strings | 1 - .../Resources/it.lproj/Localizable.strings | 1 - .../Resources/ja.lproj/Localizable.strings | 1 - .../Resources/ko.lproj/Localizable.strings | 1 - .../Resources/nl.lproj/Localizable.strings | 1 - .../Resources/pl.lproj/Localizable.strings | 1 - .../Resources/pt-BR.lproj/Localizable.strings | 1 - .../Resources/ru.lproj/Localizable.strings | 1 - .../Resources/sv.lproj/Localizable.strings | 1 - .../Resources/th.lproj/Localizable.strings | 1 - .../Resources/tr.lproj/Localizable.strings | 1 - .../Resources/uk.lproj/Localizable.strings | 1 - .../Resources/vi.lproj/Localizable.strings | 1 - .../zh-Hans.lproj/Localizable.strings | 1 - .../zh-Hant.lproj/Localizable.strings | 1 - .../MenuBarLayoutDisplayOptionsTests.swift | 78 ++++++++++++ 25 files changed, 147 insertions(+), 65 deletions(-) create mode 100644 Tests/CodexBarTests/MenuBarLayoutDisplayOptionsTests.swift diff --git a/Sources/CodexBar/MenuBarLayoutEditor.swift b/Sources/CodexBar/MenuBarLayoutEditor.swift index 2c91c15a67..35fc265440 100644 --- a/Sources/CodexBar/MenuBarLayoutEditor.swift +++ b/Sources/CodexBar/MenuBarLayoutEditor.swift @@ -716,48 +716,10 @@ struct MenuBarLayoutEditor: View { } private var displayOptions: some View { - HStack(spacing: 18) { - Picker(L("menu_bar_layout_size"), selection: self.sizeBinding) { - ForEach(MenuBarLayoutSize.allCases) { size in - Text(size.label).tag(size) - } - } - .pickerStyle(.menu) - - Picker(L("menu_bar_layout_gap"), selection: self.gapBinding) { - ForEach(MenuBarLayoutGap.allCases) { gap in - Text(gap.label).tag(gap) - } - } - .pickerStyle(.menu) - - HStack(spacing: 8) { - Text(L("menu_bar_layout_vertical_adjustment")) - .lineLimit(1) - .fixedSize() - - TextField( - "", - value: self.$settings.menuBarLayoutVerticalAdjustment, - format: .number) - .labelsHidden() - .textFieldStyle(.roundedBorder) - .multilineTextAlignment(.trailing) - .monospacedDigit() - .frame(width: 44) - - Stepper(value: self.$settings.menuBarLayoutVerticalAdjustment, in: -20...20, step: 1) { - EmptyView() - } - .labelsHidden() - } - - Spacer() - - Text(L("menu_bar_layout_keyboard_hint")) - .font(.caption) - .foregroundStyle(.tertiary) - } + MenuBarLayoutDisplayOptions( + size: self.sizeBinding, + gap: self.gapBinding, + verticalAdjustment: self.$settings.menuBarLayoutVerticalAdjustment) } private func applyPreset(_ preset: MenuBarLayoutPreset) { @@ -1345,3 +1307,68 @@ extension MenuBarLayoutToken { } } } + +/// Size, gap, and vertical-offset controls. Pickers stay at their ideal width so the +/// current selection is always visible without opening the menu. +struct MenuBarLayoutDisplayOptions: View { + @Binding var size: MenuBarLayoutSize + @Binding var gap: MenuBarLayoutGap + @Binding var verticalAdjustment: Int + + var body: some View { + HStack(spacing: 18) { + HStack(spacing: 8) { + Text(L("menu_bar_layout_size")) + .lineLimit(1) + .fixedSize() + + Picker(L("menu_bar_layout_size"), selection: self.$size) { + ForEach(MenuBarLayoutSize.allCases) { size in + Text(size.label).tag(size) + } + } + .labelsHidden() + .pickerStyle(.menu) + .fixedSize() + } + + HStack(spacing: 8) { + Text(L("menu_bar_layout_gap")) + .lineLimit(1) + .fixedSize() + + Picker(L("menu_bar_layout_gap"), selection: self.$gap) { + ForEach(MenuBarLayoutGap.allCases) { gap in + Text(gap.label).tag(gap) + } + } + .labelsHidden() + .pickerStyle(.menu) + .fixedSize() + } + + HStack(spacing: 8) { + Text(L("menu_bar_layout_vertical_adjustment")) + .lineLimit(1) + .fixedSize() + + TextField( + "", + value: self.$verticalAdjustment, + format: .number) + .labelsHidden() + .textFieldStyle(.roundedBorder) + .multilineTextAlignment(.trailing) + .monospacedDigit() + .frame(width: 44) + + Stepper(value: self.$verticalAdjustment, in: -20...20, step: 1) { + EmptyView() + } + .labelsHidden() + } + + Spacer() + } + } +} diff --git a/Sources/CodexBar/Resources/ar.lproj/Localizable.strings b/Sources/CodexBar/Resources/ar.lproj/Localizable.strings index 9fcb0e04cd..d7d5fe7e62 100644 --- a/Sources/CodexBar/Resources/ar.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ar.lproj/Localizable.strings @@ -1416,7 +1416,6 @@ "menu_bar_layout_gap_tight" = "ضيّق"; "menu_bar_layout_gap_regular" = "عادي"; "menu_bar_layout_vertical_adjustment" = "الضبط العمودي"; -"menu_bar_layout_keyboard_hint" = "يحذف Delete العنصر المحدد"; "menu_bar_layout_sample_account" = "حساب"; "menu_bar_layout_sample_runs_out" = "ينفد الجمعة"; "menu_bar_layout_token_icon" = "الأيقونة"; diff --git a/Sources/CodexBar/Resources/ca.lproj/Localizable.strings b/Sources/CodexBar/Resources/ca.lproj/Localizable.strings index 8f7a81de5d..8792ca2ad6 100644 --- a/Sources/CodexBar/Resources/ca.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ca.lproj/Localizable.strings @@ -1415,7 +1415,6 @@ "menu_bar_layout_gap_tight" = "Estret"; "menu_bar_layout_gap_regular" = "Normal"; "menu_bar_layout_vertical_adjustment" = "Ajust vertical"; -"menu_bar_layout_keyboard_hint" = "La tecla Supr elimina la fitxa seleccionada"; "menu_bar_layout_sample_account" = "compte"; "menu_bar_layout_sample_runs_out" = "s'esgota dv."; "menu_bar_layout_token_icon" = "Icona"; diff --git a/Sources/CodexBar/Resources/de.lproj/Localizable.strings b/Sources/CodexBar/Resources/de.lproj/Localizable.strings index 380afb4157..cc6463a19e 100644 --- a/Sources/CodexBar/Resources/de.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/de.lproj/Localizable.strings @@ -1413,7 +1413,6 @@ "menu_bar_layout_gap_tight" = "Eng"; "menu_bar_layout_gap_regular" = "Normal"; "menu_bar_layout_vertical_adjustment" = "Vertikale Ausrichtung"; -"menu_bar_layout_keyboard_hint" = "Die Löschtaste entfernt den ausgewählten Baustein"; "menu_bar_layout_sample_account" = "Konto"; "menu_bar_layout_sample_runs_out" = "reicht bis Fr."; "menu_bar_layout_token_icon" = "Symbol"; diff --git a/Sources/CodexBar/Resources/en.lproj/Localizable.strings b/Sources/CodexBar/Resources/en.lproj/Localizable.strings index 3a0f407993..1dfbf7d9d9 100644 --- a/Sources/CodexBar/Resources/en.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/en.lproj/Localizable.strings @@ -1421,7 +1421,6 @@ "menu_bar_layout_gap_tight" = "Tight"; "menu_bar_layout_gap_regular" = "Regular"; "menu_bar_layout_vertical_adjustment" = "Vertical"; -"menu_bar_layout_keyboard_hint" = "Delete removes the selected token"; "menu_bar_layout_sample_account" = "account"; "menu_bar_layout_sample_runs_out" = "runs out Fri"; "menu_bar_layout_token_icon" = "Icon"; diff --git a/Sources/CodexBar/Resources/es.lproj/Localizable.strings b/Sources/CodexBar/Resources/es.lproj/Localizable.strings index 984216a82c..d6b77c3f02 100644 --- a/Sources/CodexBar/Resources/es.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/es.lproj/Localizable.strings @@ -1411,7 +1411,6 @@ "menu_bar_layout_gap_tight" = "Estrecha"; "menu_bar_layout_gap_regular" = "Normal"; "menu_bar_layout_vertical_adjustment" = "Ajuste vertical"; -"menu_bar_layout_keyboard_hint" = "Suprimir quita la ficha seleccionada"; "menu_bar_layout_sample_account" = "cuenta"; "menu_bar_layout_sample_runs_out" = "se agota vie."; "menu_bar_layout_token_icon" = "Icono"; diff --git a/Sources/CodexBar/Resources/fa.lproj/Localizable.strings b/Sources/CodexBar/Resources/fa.lproj/Localizable.strings index 7136f09b48..4de94b2de4 100644 --- a/Sources/CodexBar/Resources/fa.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/fa.lproj/Localizable.strings @@ -1416,7 +1416,6 @@ "menu_bar_layout_gap_tight" = "فشرده"; "menu_bar_layout_gap_regular" = "معمولی"; "menu_bar_layout_vertical_adjustment" = "تنظیم عمودی"; -"menu_bar_layout_keyboard_hint" = "Delete نشانهٔ انتخاب‌شده را حذف می‌کند"; "menu_bar_layout_sample_account" = "حساب"; "menu_bar_layout_sample_runs_out" = "جمعه تمام می‌شود"; "menu_bar_layout_token_icon" = "آیکون"; diff --git a/Sources/CodexBar/Resources/fr.lproj/Localizable.strings b/Sources/CodexBar/Resources/fr.lproj/Localizable.strings index e908970e98..db8bbb4163 100644 --- a/Sources/CodexBar/Resources/fr.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/fr.lproj/Localizable.strings @@ -1412,7 +1412,6 @@ "menu_bar_layout_gap_tight" = "Serré"; "menu_bar_layout_gap_regular" = "Normale"; "menu_bar_layout_vertical_adjustment" = "Ajustement vertical"; -"menu_bar_layout_keyboard_hint" = "Supprimer retire le jeton sélectionné"; "menu_bar_layout_sample_account" = "compte"; "menu_bar_layout_sample_runs_out" = "épuisé ven."; "menu_bar_layout_token_icon" = "Icône"; diff --git a/Sources/CodexBar/Resources/gl.lproj/Localizable.strings b/Sources/CodexBar/Resources/gl.lproj/Localizable.strings index 8775b0fa39..64bd5376e1 100644 --- a/Sources/CodexBar/Resources/gl.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/gl.lproj/Localizable.strings @@ -1412,7 +1412,6 @@ "menu_bar_layout_gap_tight" = "Estreita"; "menu_bar_layout_gap_regular" = "Normal"; "menu_bar_layout_vertical_adjustment" = "Axuste vertical"; -"menu_bar_layout_keyboard_hint" = "Suprimir retira a ficha seleccionada"; "menu_bar_layout_sample_account" = "conta"; "menu_bar_layout_sample_runs_out" = "esgótase ven."; "menu_bar_layout_token_icon" = "Icona"; diff --git a/Sources/CodexBar/Resources/id.lproj/Localizable.strings b/Sources/CodexBar/Resources/id.lproj/Localizable.strings index cd3ba69f0c..344fc890db 100644 --- a/Sources/CodexBar/Resources/id.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/id.lproj/Localizable.strings @@ -1416,7 +1416,6 @@ "menu_bar_layout_gap_tight" = "Rapat"; "menu_bar_layout_gap_regular" = "Reguler"; "menu_bar_layout_vertical_adjustment" = "Penyesuaian Vertikal"; -"menu_bar_layout_keyboard_hint" = "Delete menghapus token yang dipilih"; "menu_bar_layout_sample_account" = "akun"; "menu_bar_layout_sample_runs_out" = "habis Jum."; "menu_bar_layout_token_icon" = "Ikon"; diff --git a/Sources/CodexBar/Resources/it.lproj/Localizable.strings b/Sources/CodexBar/Resources/it.lproj/Localizable.strings index 52215300e4..df94da7e57 100644 --- a/Sources/CodexBar/Resources/it.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/it.lproj/Localizable.strings @@ -1416,7 +1416,6 @@ "menu_bar_layout_gap_tight" = "Stretta"; "menu_bar_layout_gap_regular" = "Normale"; "menu_bar_layout_vertical_adjustment" = "Regolazione verticale"; -"menu_bar_layout_keyboard_hint" = "Canc rimuove il token selezionato"; "menu_bar_layout_sample_account" = "account"; "menu_bar_layout_sample_runs_out" = "termina ven."; "menu_bar_layout_token_icon" = "Icona"; diff --git a/Sources/CodexBar/Resources/ja.lproj/Localizable.strings b/Sources/CodexBar/Resources/ja.lproj/Localizable.strings index afa70e760f..9ecc273924 100644 --- a/Sources/CodexBar/Resources/ja.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ja.lproj/Localizable.strings @@ -1413,7 +1413,6 @@ "menu_bar_layout_gap_tight" = "狭い"; "menu_bar_layout_gap_regular" = "標準"; "menu_bar_layout_vertical_adjustment" = "垂直調整"; -"menu_bar_layout_keyboard_hint" = "Delete キーで選択したトークンを削除"; "menu_bar_layout_sample_account" = "アカウント"; "menu_bar_layout_sample_runs_out" = "金曜に使い切る"; "menu_bar_layout_token_icon" = "アイコン"; diff --git a/Sources/CodexBar/Resources/ko.lproj/Localizable.strings b/Sources/CodexBar/Resources/ko.lproj/Localizable.strings index 0fa5a13a24..302af01d0b 100644 --- a/Sources/CodexBar/Resources/ko.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ko.lproj/Localizable.strings @@ -1382,7 +1382,6 @@ "menu_bar_layout_gap_tight" = "좁게"; "menu_bar_layout_gap_regular" = "보통"; "menu_bar_layout_vertical_adjustment" = "세로 조정"; -"menu_bar_layout_keyboard_hint" = "Delete 키로 선택한 토큰 제거"; "menu_bar_layout_sample_account" = "계정"; "menu_bar_layout_sample_runs_out" = "금요일 소진"; "menu_bar_layout_token_icon" = "아이콘"; diff --git a/Sources/CodexBar/Resources/nl.lproj/Localizable.strings b/Sources/CodexBar/Resources/nl.lproj/Localizable.strings index 097814f59e..93613262fe 100644 --- a/Sources/CodexBar/Resources/nl.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/nl.lproj/Localizable.strings @@ -1412,7 +1412,6 @@ "menu_bar_layout_gap_tight" = "Krap"; "menu_bar_layout_gap_regular" = "Normaal"; "menu_bar_layout_vertical_adjustment" = "Verticale aanpassing"; -"menu_bar_layout_keyboard_hint" = "Delete verwijdert het geselecteerde token"; "menu_bar_layout_sample_account" = "account"; "menu_bar_layout_sample_runs_out" = "op vr."; "menu_bar_layout_token_icon" = "Pictogram"; diff --git a/Sources/CodexBar/Resources/pl.lproj/Localizable.strings b/Sources/CodexBar/Resources/pl.lproj/Localizable.strings index a9a3688d90..17c6c9f8b4 100644 --- a/Sources/CodexBar/Resources/pl.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/pl.lproj/Localizable.strings @@ -1416,7 +1416,6 @@ "menu_bar_layout_gap_tight" = "Wąski"; "menu_bar_layout_gap_regular" = "Zwykły"; "menu_bar_layout_vertical_adjustment" = "Regulacja pionowa"; -"menu_bar_layout_keyboard_hint" = "Delete usuwa zaznaczony element"; "menu_bar_layout_sample_account" = "konto"; "menu_bar_layout_sample_runs_out" = "wyczerpie się pt."; "menu_bar_layout_token_icon" = "Ikona"; diff --git a/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings b/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings index 6308ee913c..01718aad3f 100644 --- a/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings @@ -1413,7 +1413,6 @@ "menu_bar_layout_gap_tight" = "Apertado"; "menu_bar_layout_gap_regular" = "Normal"; "menu_bar_layout_vertical_adjustment" = "Ajuste vertical"; -"menu_bar_layout_keyboard_hint" = "Delete remove o item selecionado"; "menu_bar_layout_sample_account" = "conta"; "menu_bar_layout_sample_runs_out" = "acaba sex."; "menu_bar_layout_token_icon" = "Ícone"; diff --git a/Sources/CodexBar/Resources/ru.lproj/Localizable.strings b/Sources/CodexBar/Resources/ru.lproj/Localizable.strings index 6006715817..e5f4f4d039 100644 --- a/Sources/CodexBar/Resources/ru.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ru.lproj/Localizable.strings @@ -1414,7 +1414,6 @@ "menu_bar_layout_gap_tight" = "Узкий"; "menu_bar_layout_gap_regular" = "Обычный"; "menu_bar_layout_vertical_adjustment" = "Вертикальная настройка"; -"menu_bar_layout_keyboard_hint" = "Delete удаляет выбранный элемент"; "menu_bar_layout_sample_account" = "аккаунт"; "menu_bar_layout_sample_runs_out" = "закончится пт."; "menu_bar_layout_token_icon" = "Значок"; diff --git a/Sources/CodexBar/Resources/sv.lproj/Localizable.strings b/Sources/CodexBar/Resources/sv.lproj/Localizable.strings index e044f970d4..44012a8163 100644 --- a/Sources/CodexBar/Resources/sv.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/sv.lproj/Localizable.strings @@ -1411,7 +1411,6 @@ "menu_bar_layout_gap_tight" = "Tätt"; "menu_bar_layout_gap_regular" = "Normal"; "menu_bar_layout_vertical_adjustment" = "Vertikal justering"; -"menu_bar_layout_keyboard_hint" = "Delete tar bort den markerade brickan"; "menu_bar_layout_sample_account" = "konto"; "menu_bar_layout_sample_runs_out" = "tar slut fre."; "menu_bar_layout_token_icon" = "Ikon"; diff --git a/Sources/CodexBar/Resources/th.lproj/Localizable.strings b/Sources/CodexBar/Resources/th.lproj/Localizable.strings index b656802fd1..daee07e5d2 100644 --- a/Sources/CodexBar/Resources/th.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/th.lproj/Localizable.strings @@ -1416,7 +1416,6 @@ "menu_bar_layout_gap_tight" = "ชิด"; "menu_bar_layout_gap_regular" = "ปกติ"; "menu_bar_layout_vertical_adjustment" = "ปรับแนวตั้ง"; -"menu_bar_layout_keyboard_hint" = "Delete ลบโทเค็นที่เลือก"; "menu_bar_layout_sample_account" = "บัญชี"; "menu_bar_layout_sample_runs_out" = "หมดวันศุกร์"; "menu_bar_layout_token_icon" = "ไอคอน"; diff --git a/Sources/CodexBar/Resources/tr.lproj/Localizable.strings b/Sources/CodexBar/Resources/tr.lproj/Localizable.strings index 5e3c6cf58c..c12b353cab 100644 --- a/Sources/CodexBar/Resources/tr.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/tr.lproj/Localizable.strings @@ -1414,7 +1414,6 @@ "menu_bar_layout_gap_tight" = "Dar"; "menu_bar_layout_gap_regular" = "Normal"; "menu_bar_layout_vertical_adjustment" = "Dikey ayar"; -"menu_bar_layout_keyboard_hint" = "Delete seçili öğeyi kaldırır"; "menu_bar_layout_sample_account" = "hesap"; "menu_bar_layout_sample_runs_out" = "Cuma biter"; "menu_bar_layout_token_icon" = "Simge"; diff --git a/Sources/CodexBar/Resources/uk.lproj/Localizable.strings b/Sources/CodexBar/Resources/uk.lproj/Localizable.strings index d59e2af160..ed739b772d 100644 --- a/Sources/CodexBar/Resources/uk.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/uk.lproj/Localizable.strings @@ -1412,7 +1412,6 @@ "menu_bar_layout_gap_tight" = "Вузький"; "menu_bar_layout_gap_regular" = "Звичайний"; "menu_bar_layout_vertical_adjustment" = "Вертикальне налаштування"; -"menu_bar_layout_keyboard_hint" = "Delete видаляє вибраний елемент"; "menu_bar_layout_sample_account" = "обліковий запис"; "menu_bar_layout_sample_runs_out" = "закінчиться пт."; "menu_bar_layout_token_icon" = "Значок"; diff --git a/Sources/CodexBar/Resources/vi.lproj/Localizable.strings b/Sources/CodexBar/Resources/vi.lproj/Localizable.strings index 19a4d9c467..46eb24c3fe 100644 --- a/Sources/CodexBar/Resources/vi.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/vi.lproj/Localizable.strings @@ -1413,7 +1413,6 @@ "menu_bar_layout_gap_tight" = "Hẹp"; "menu_bar_layout_gap_regular" = "Thường"; "menu_bar_layout_vertical_adjustment" = "Điều chỉnh dọc"; -"menu_bar_layout_keyboard_hint" = "Delete xóa thẻ đã chọn"; "menu_bar_layout_sample_account" = "tài khoản"; "menu_bar_layout_sample_runs_out" = "hết vào T6"; "menu_bar_layout_token_icon" = "Biểu tượng"; diff --git a/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings b/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings index 403f5719a1..7a1b015834 100644 --- a/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings @@ -1416,7 +1416,6 @@ "menu_bar_layout_gap_tight" = "紧凑"; "menu_bar_layout_gap_regular" = "常规"; "menu_bar_layout_vertical_adjustment" = "垂直调整"; -"menu_bar_layout_keyboard_hint" = "Delete 键会移除所选项目"; "menu_bar_layout_sample_account" = "帐户"; "menu_bar_layout_sample_runs_out" = "周五用尽"; "menu_bar_layout_token_icon" = "图标"; diff --git a/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings b/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings index e640718e89..7f75ce08ff 100644 --- a/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings @@ -1443,7 +1443,6 @@ "menu_bar_layout_gap_tight" = "緊湊"; "menu_bar_layout_gap_regular" = "一般"; "menu_bar_layout_vertical_adjustment" = "垂直調整"; -"menu_bar_layout_keyboard_hint" = "Delete 鍵會移除所選項目"; "menu_bar_layout_sample_account" = "帳號"; "menu_bar_layout_sample_runs_out" = "週五用盡"; "menu_bar_layout_token_icon" = "圖示"; diff --git a/Tests/CodexBarTests/MenuBarLayoutDisplayOptionsTests.swift b/Tests/CodexBarTests/MenuBarLayoutDisplayOptionsTests.swift new file mode 100644 index 0000000000..369886100c --- /dev/null +++ b/Tests/CodexBarTests/MenuBarLayoutDisplayOptionsTests.swift @@ -0,0 +1,78 @@ +import AppKit +import SwiftUI +import Testing +@testable import CodexBar + +@MainActor +struct MenuBarLayoutDisplayOptionsTests { + @Test + func `size and gap pickers show their selection in a narrow row`() throws { + let hosting = Self.hostingView(width: 480, size: .small, gap: .tight) + let popups = Self.popUpButtons(in: hosting) + + #expect(popups.count == 2) + let titles = popups.map(\.titleOfSelectedItem) + #expect(titles.contains(MenuBarLayoutSize.small.label)) + #expect(titles.contains(MenuBarLayoutGap.tight.label)) + for popup in popups { + // A compressed popup collapses to its chevron and hides the selected title. + #expect(popup.frame.width >= popup.intrinsicContentSize.width - 1) + } + } + + @Test + func `row no longer shows the delete keyboard hint`() { + let hosting = Self.hostingView(width: 720, size: .regular, gap: .regular) + let hint = "Delete removes the selected token" + + #expect(!Self.texts(in: hosting).contains { $0.contains(hint) }) + #expect(L("menu_bar_layout_keyboard_hint") != hint) + } + + @Test + func `synthetic display options screenshot`() throws { + guard let directory = ProcessInfo.processInfo.environment["CODEXBAR_LAYOUT_OPTIONS_SCREENSHOT_DIR"] + else { return } + for appearance in [NSAppearance.Name.aqua, .darkAqua] { + let hosting = Self.hostingView(width: 520, size: .small, gap: .tight, appearance: appearance) + let bitmap = try #require(hosting.bitmapImageRepForCachingDisplay(in: hosting.bounds)) + hosting.cacheDisplay(in: hosting.bounds, to: bitmap) + let png = try #require(bitmap.representation(using: .png, properties: [:])) + let name = appearance == .aqua ? "light" : "dark" + try png.write(to: URL(fileURLWithPath: directory) + .appendingPathComponent("menu-bar-layout-display-options-\(name).png")) + } + } + + private static func hostingView( + width: CGFloat, + size: MenuBarLayoutSize, + gap: MenuBarLayoutGap, + appearance: NSAppearance.Name = .aqua) + -> NSHostingView + { + let view = MenuBarLayoutDisplayOptions( + size: .constant(size), + gap: .constant(gap), + verticalAdjustment: .constant(0)) + .padding(16) + .frame(width: width) + .background(Color(nsColor: .windowBackgroundColor)) + let hosting = NSHostingView(rootView: view) + hosting.appearance = NSAppearance(named: appearance) + hosting.frame = CGRect(origin: .zero, size: hosting.fittingSize) + hosting.layoutSubtreeIfNeeded() + return hosting + } + + private static func popUpButtons(in view: NSView) -> [NSPopUpButton] { + let own = (view as? NSPopUpButton).map { [$0] } ?? [] + return own + view.subviews.flatMap { self.popUpButtons(in: $0) } + } + + private static func texts(in view: NSView) -> [String] { + let own = (view as? NSTextField).map { [$0.stringValue] } ?? [] + let accessibility = [view.accessibilityLabel(), view.accessibilityValue() as? String].compactMap(\.self) + return own + accessibility + view.subviews.flatMap { self.texts(in: $0) } + } +} From 6d4a2fba8da70baf9ac24712d9745d2c78dcd8df Mon Sep 17 00:00:00 2001 From: Elijah Friedman Date: Fri, 25 Sep 2026 14:28:24 -0400 Subject: [PATCH 2/3] Remove unnecessary throws from layout test --- Tests/CodexBarTests/MenuBarLayoutDisplayOptionsTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/CodexBarTests/MenuBarLayoutDisplayOptionsTests.swift b/Tests/CodexBarTests/MenuBarLayoutDisplayOptionsTests.swift index 369886100c..d36b6e50f5 100644 --- a/Tests/CodexBarTests/MenuBarLayoutDisplayOptionsTests.swift +++ b/Tests/CodexBarTests/MenuBarLayoutDisplayOptionsTests.swift @@ -6,7 +6,7 @@ import Testing @MainActor struct MenuBarLayoutDisplayOptionsTests { @Test - func `size and gap pickers show their selection in a narrow row`() throws { + func `size and gap pickers show their selection in a narrow row`() { let hosting = Self.hostingView(width: 480, size: .small, gap: .tight) let popups = Self.popUpButtons(in: hosting) From f6de52d8154f931834d9e6014c94d88dd25a153c Mon Sep 17 00:00:00 2001 From: Elijah Friedman Date: Fri, 25 Sep 2026 16:05:36 -0400 Subject: [PATCH 3/3] Update gatekeeper test line references --- Tests/CodexBarTests/ProviderArchitectureGatekeeperTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/CodexBarTests/ProviderArchitectureGatekeeperTests.swift b/Tests/CodexBarTests/ProviderArchitectureGatekeeperTests.swift index 1916b674f4..b223e6462b 100644 --- a/Tests/CodexBarTests/ProviderArchitectureGatekeeperTests.swift +++ b/Tests/CodexBarTests/ProviderArchitectureGatekeeperTests.swift @@ -1830,7 +1830,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: 932, + line: 894, anchor: "let provider = self.provider ?? .codex", expectedProviderIDs: ["codex"], expectedReferenceCount: 1, @@ -1838,7 +1838,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: 963, + line: 925, anchor: "if provider == .codex,", expectedProviderIDs: ["codex"], expectedReferenceCount: 1,