Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## 0.67.1 — Unreleased

### Changed

- Settings: simplify menu bar layout controls while keeping token-removal instructions in the section footer (#3999). Thanks @elijahfriedman!

## 0.67.0 — 2026-09-25

### Highlights
Expand Down
111 changes: 69 additions & 42 deletions Sources/CodexBar/MenuBarLayoutEditor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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()
}
}
}
1 change: 0 additions & 1 deletion Sources/CodexBar/Resources/ar.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1417,7 +1417,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" = "الأيقونة";
Expand Down
1 change: 0 additions & 1 deletion Sources/CodexBar/Resources/ca.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1416,7 +1416,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";
Expand Down
1 change: 0 additions & 1 deletion Sources/CodexBar/Resources/de.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1414,7 +1414,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";
Expand Down
1 change: 0 additions & 1 deletion Sources/CodexBar/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1422,7 +1422,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";
Expand Down
1 change: 0 additions & 1 deletion Sources/CodexBar/Resources/es.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1412,7 +1412,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";
Expand Down
1 change: 0 additions & 1 deletion Sources/CodexBar/Resources/fa.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1417,7 +1417,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" = "آیکون";
Expand Down
1 change: 0 additions & 1 deletion Sources/CodexBar/Resources/fr.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1413,7 +1413,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";
Expand Down
1 change: 0 additions & 1 deletion Sources/CodexBar/Resources/gl.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1413,7 +1413,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";
Expand Down
1 change: 0 additions & 1 deletion Sources/CodexBar/Resources/id.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1417,7 +1417,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";
Expand Down
1 change: 0 additions & 1 deletion Sources/CodexBar/Resources/it.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1417,7 +1417,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";
Expand Down
1 change: 0 additions & 1 deletion Sources/CodexBar/Resources/ja.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -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" = "アイコン";
Expand Down
1 change: 0 additions & 1 deletion Sources/CodexBar/Resources/ko.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1383,7 +1383,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" = "아이콘";
Expand Down
1 change: 0 additions & 1 deletion Sources/CodexBar/Resources/nl.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1413,7 +1413,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";
Expand Down
1 change: 0 additions & 1 deletion Sources/CodexBar/Resources/pl.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1417,7 +1417,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";
Expand Down
1 change: 0 additions & 1 deletion Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1414,7 +1414,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";
Expand Down
1 change: 0 additions & 1 deletion Sources/CodexBar/Resources/ru.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1415,7 +1415,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" = "Значок";
Expand Down
1 change: 0 additions & 1 deletion Sources/CodexBar/Resources/sv.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1412,7 +1412,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";
Expand Down
1 change: 0 additions & 1 deletion Sources/CodexBar/Resources/th.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1417,7 +1417,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" = "ไอคอน";
Expand Down
1 change: 0 additions & 1 deletion Sources/CodexBar/Resources/tr.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1415,7 +1415,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";
Expand Down
1 change: 0 additions & 1 deletion Sources/CodexBar/Resources/uk.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -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" = "Значок";
Expand Down
1 change: 0 additions & 1 deletion Sources/CodexBar/Resources/vi.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1414,7 +1414,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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1417,7 +1417,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" = "图标";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1444,7 +1444,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" = "圖示";
Expand Down
74 changes: 74 additions & 0 deletions Tests/CodexBarTests/MenuBarLayoutDisplayOptionsTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import AppKit
import SwiftUI
import Testing
@testable import CodexBar

@MainActor
struct MenuBarLayoutDisplayOptionsTests {
@Test(arguments: MenuBarLayoutSize.allCases, MenuBarLayoutGap.allCases)
func `size and gap selections remain accessible in a narrow row`(
size: MenuBarLayoutSize,
gap: MenuBarLayoutGap)
{
let hosting = Self.hostingView(width: 480, size: size, gap: gap)
let text = MenuLayoutScreenshotRenderTests.accessibilityText(hosting)

#expect(text.contains(L("menu_bar_layout_size")))
#expect(text.contains(L("menu_bar_layout_gap")))
#expect(text.contains(size.label))
#expect(text.contains(gap.label))
}

@Test
func `keyboard instructions remain in the footer without duplication in the row`() {
let hosting = Self.hostingView(width: 720, size: .regular, gap: .regular)
let hint = "Delete removes the selected token"
let text = MenuLayoutScreenshotRenderTests.accessibilityText(hosting)

#expect(text.contains(L("menu_bar_layout_footer")))
#expect(!text.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] {
for width: CGFloat in [480, 520] {
let hosting = Self.hostingView(width: width, 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-options-\(Int(width))-\(name).png"))
}
}
}

private static func hostingView(
width: CGFloat,
size: MenuBarLayoutSize,
gap: MenuBarLayoutGap,
appearance: NSAppearance.Name = .aqua)
-> NSHostingView<some View>
{
let view = VStack(alignment: .leading, spacing: 12) {
MenuBarLayoutDisplayOptions(
size: .constant(size),
gap: .constant(gap),
verticalAdjustment: .constant(0))
SettingsSectionFooter(L("menu_bar_layout_footer"))
}
.padding(16)
.frame(width: width)
.environment(\.accessibilityEnabled, true)
.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
}
}
Loading
Loading