style: correct translation header and footer layout

This commit is contained in:
Valdemar Lorenzen
2026-10-15 13:43:40 +02:00
parent 5771a37fa8
commit 6574c97276
5 changed files with 42 additions and 48 deletions
+1 -1
View File
@@ -77,7 +77,7 @@ Currency and crypto conversions require internet access and use [currency-api](h
## Translation ## Translation
Search for **Conjure Translate** and open it. Enter English in the search bar, choose a provider, then press Return or click **Translate**. Typing alone never sends text. Copy selected Japanese output with Command-C; Back or Escape returns to the launcher. Search for **Conjure Translate** and open it. Enter English in the search bar, choose a provider, then press Return or click **Translate**. Typing alone never sends text. Copy selected Japanese output with Command-C; the copy hint appears beside the Translate action at the bottom. The Back chevron beside the input or Escape returns to the launcher and restores your search.
- **MyMemory:** No account or key required. Limits are 500 UTF-8 bytes per request and [5,000 characters per day](https://mymemory.translated.net/doc/usagelimits.php) for anonymous use. - **MyMemory:** No account or key required. Limits are 500 UTF-8 bytes per request and [5,000 characters per day](https://mymemory.translated.net/doc/usagelimits.php) for anonymous use.
- **OpenAI:** Requires an API key, access to `gpt-6-luna`, and sufficient API billing. Choose **Save Key** to store it in macOS Keychain; **Forget Key** removes it. Requests disable API response storage, but OpenAI's retention policies still apply. - **OpenAI:** Requires an API key, access to `gpt-6-luna`, and sufficient API billing. Choose **Save Key** to store it in macOS Keychain; **Forget Key** removes it. Requests disable API response storage, but OpenAI's retention policies still apply.
+23 -4
View File
@@ -4,6 +4,8 @@ import ConjureKit
@MainActor @MainActor
final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSearchFieldDelegate, NSTableViewDataSource, NSTableViewDelegate { final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSearchFieldDelegate, NSTableViewDataSource, NSTableViewDelegate {
private let searchField = NSSearchField() private let searchField = NSSearchField()
private let searchSymbol = IconView(font: .systemFont(ofSize: 20))
private let translationBack = NSButton(title: "", target: nil, action: nil)
private let tableView = ApplicationTableView() private let tableView = ApplicationTableView()
private let statusLabel = NSTextField(labelWithString: "Finding applications…") private let statusLabel = NSTextField(labelWithString: "Finding applications…")
private let emptyLabel = NSTextField(labelWithString: "Finding applications…") private let emptyLabel = NSTextField(labelWithString: "Finding applications…")
@@ -226,10 +228,20 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
searchField.delegate = self searchField.delegate = self
searchField.setAccessibilityLabel("Search applications, calculate, or convert units and currencies") searchField.setAccessibilityLabel("Search applications, calculate, or convert units and currencies")
let symbol = IconView(font: searchFont) searchSymbol.image = NSImage(systemSymbolName: "sparkles", accessibilityDescription: nil)
symbol.image = NSImage(systemSymbolName: "sparkles", accessibilityDescription: nil) searchSymbol.contentTintColor = SunsetPalette.accent
symbol.contentTintColor = SunsetPalette.accent translationBack.image = IconStyle.symbol("chevron.left", for: searchFont)
let header = NSStackView(views: [symbol, searchField]) translationBack.imageScaling = .scaleProportionallyDown
translationBack.isBordered = false
translationBack.contentTintColor = SunsetPalette.secondaryText
translationBack.setAccessibilityLabel("Back to applications")
translationBack.toolTip = "Back to applications (Escape)"
translationBack.isHidden = true
NSLayoutConstraint.activate([
translationBack.widthAnchor.constraint(equalToConstant: IconStyle.applicationSize(for: searchFont)),
translationBack.heightAnchor.constraint(equalToConstant: IconStyle.applicationSize(for: searchFont)),
])
let header = NSStackView(views: [searchSymbol, translationBack, searchField])
header.orientation = .horizontal header.orientation = .horizontal
header.alignment = .centerY header.alignment = .centerY
header.spacing = 12 header.spacing = 12
@@ -780,6 +792,9 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
guard let self, let controller else { return } guard let self, let controller else { return }
controller.view.removeFromSuperview() controller.view.removeFromSuperview()
self.translation = nil self.translation = nil
self.translationBack.isHidden = true
self.translationBack.target = nil
self.searchSymbol.isHidden = false
for view in self.launcherViews.dropFirst() { for view in self.launcherViews.dropFirst() {
view.isHidden = false view.isHidden = false
view.setAccessibilityHidden(false) view.setAccessibilityHidden(false)
@@ -795,6 +810,10 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
actions.close() actions.close()
(window as? LauncherPanel)?.resetNumberHints() (window as? LauncherPanel)?.resetNumberHints()
openButton.keyEquivalent = "" openButton.keyEquivalent = ""
searchSymbol.isHidden = true
translationBack.target = controller
translationBack.action = #selector(TranslationController.goBack)
translationBack.isHidden = false
searchField.stringValue = "" searchField.stringValue = ""
searchField.placeholderAttributedString = NSAttributedString( searchField.placeholderAttributedString = NSAttributedString(
string: "Enter English text to translate…", string: "Enter English text to translate…",
+5 -15
View File
@@ -26,16 +26,6 @@ final class TranslationController: NSViewController, NSTextFieldDelegate, NSText
let title = NSTextField(labelWithString: "Conjure Translate · English → Japanese") let title = NSTextField(labelWithString: "Conjure Translate · English → Japanese")
title.font = .systemFont(ofSize: 13, weight: .semibold) title.font = .systemFont(ofSize: 13, weight: .semibold)
title.textColor = SunsetPalette.text title.textColor = SunsetPalette.text
let back = NSButton(title: "", target: self, action: #selector(goBack))
back.image = IconStyle.symbol("chevron.left", for: title.font!)
back.imageScaling = .scaleProportionallyDown
back.isBordered = false
back.contentTintColor = SunsetPalette.secondaryText
back.setAccessibilityLabel("Back to applications")
back.toolTip = "Back to applications (Escape)"
let heading = NSStackView(views: [back, title, NSView()])
heading.alignment = .centerY
heading.spacing = 12
provider.addItems(withTitles: ["MyMemory (no key)", "OpenAI · gpt-6-luna"]) provider.addItems(withTitles: ["MyMemory (no key)", "OpenAI · gpt-6-luna"])
provider.selectItem(at: UserDefaults.standard.string(forKey: "translationProvider") == "openai" ? 1 : 0) provider.selectItem(at: UserDefaults.standard.string(forKey: "translationProvider") == "openai" ? 1 : 0)
provider.setAccessibilityLabel("Translation provider") provider.setAccessibilityLabel("Translation provider")
@@ -75,8 +65,10 @@ final class TranslationController: NSViewController, NSTextFieldDelegate, NSText
returnKey.centerYAnchor.constraint(equalTo: actionPill.centerYAnchor), returnKey.centerYAnchor.constraint(equalTo: actionPill.centerYAnchor),
returnKey.trailingAnchor.constraint(equalTo: actionPill.trailingAnchor, constant: -12), returnKey.trailingAnchor.constraint(equalTo: actionPill.trailingAnchor, constant: -12),
]) ])
let footer = NSStackView(views: [NSView(), actionPill]) let footer = NSStackView(views: [status, NSView(), actionPill])
footer.orientation = .horizontal footer.orientation = .horizontal
footer.alignment = .centerY
footer.spacing = 12
footer.translatesAutoresizingMaskIntoConstraints = false footer.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(footer) view.addSubview(footer)
output.isEditable = false output.isEditable = false
@@ -98,7 +90,8 @@ final class TranslationController: NSViewController, NSTextFieldDelegate, NSText
disclosure.setAccessibilityLabel("Translation provider, privacy, and limits") disclosure.setAccessibilityLabel("Translation provider, privacy, and limits")
status.font = .systemFont(ofSize: 12) status.font = .systemFont(ofSize: 12)
status.setAccessibilityLabel("Translation status and errors") status.setAccessibilityLabel("Translation status and errors")
let stack = NSStackView(views: [heading, provider, disclosure, credentials, scroll, status]) status.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
let stack = NSStackView(views: [title, provider, disclosure, credentials, scroll])
stack.orientation = .vertical stack.orientation = .vertical
stack.alignment = .leading stack.alignment = .leading
stack.spacing = 12 stack.spacing = 12
@@ -106,8 +99,6 @@ final class TranslationController: NSViewController, NSTextFieldDelegate, NSText
stack.translatesAutoresizingMaskIntoConstraints = false stack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stack) view.addSubview(stack)
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
back.widthAnchor.constraint(equalToConstant: 22),
back.heightAnchor.constraint(equalToConstant: 22),
stack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), stack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
stack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16), stack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
stack.topAnchor.constraint(equalTo: view.topAnchor), stack.topAnchor.constraint(equalTo: view.topAnchor),
@@ -120,7 +111,6 @@ final class TranslationController: NSViewController, NSTextFieldDelegate, NSText
disclosure.widthAnchor.constraint(equalTo: stack.widthAnchor), disclosure.widthAnchor.constraint(equalTo: stack.widthAnchor),
scroll.widthAnchor.constraint(equalTo: stack.widthAnchor), scroll.widthAnchor.constraint(equalTo: stack.widthAnchor),
scroll.heightAnchor.constraint(greaterThanOrEqualToConstant: 100), scroll.heightAnchor.constraint(greaterThanOrEqualToConstant: 100),
status.widthAnchor.constraint(equalTo: stack.widthAnchor),
]) ])
updateProvider() updateProvider()
} }
+3 -28
View File
@@ -1,28 +1,3 @@
- [ ] improve on the conjure translation command - [ ] add an emoji picker inspired raycast's emoji picker
- [ ] add a notes app inspired by raycast notes
## UI consistency audit - [ ] improve on the conjure translation command
### Navigation, context, and feedback
- [x] Add a visible, accessible Back action to Translate, matching uninstall review; retain Escape and query/focus restoration.
- [x] Keep the selected application's name visible throughout uninstall file review, not only in Actions and final confirmation.
- [x] Preserve uninstall discovery loading/error feedback when filtering; show selection counts only after successful discovery.
- [x] Standardize feedback severity: secondary text for guidance/progress and consistent error styling for actual failures. Shortcut modifier guidance should not appear red; login and translation failures should receive error treatment.
- [x] Show persistent uninstall keyboard guidance: Return toggles file inclusion; Command-Return opens confirmation. Preserve the safer shortcut distinction.
- [x] Separate Translate's persistent provider/privacy/limit disclosure from transient validation, credential, loading, success, and error feedback.
### Visual consistency
- [x] Match uninstall footer text to the launcher/Translate baseline: 12pt medium rather than 14pt; retain destructive red.
- [x] Align the uninstall footer with launcher/Translate: 8pt right/bottom insets rather than 24pt right and 16pt bottom.
- [x] Use the launcher/Translate footer's plain brown-filled view for uninstall instead of adding a popover material and orange tint.
- [x] Apply Actions' SF Symbol optical-inset handling to launcher icons. Native layout measured a 22×29pt command icon view versus 22×22pt action icon views.
- [x] Render application and command favorite markers consistently: use the configured star symbol rather than a literal ★ for commands.
- [x] Apply the existing Sunset palette to Translate's custom heading and the Settings recovery button, matching equivalent headings/actions.
### Visual verification
- [ ] Visually check footer weight, symbol alignment, heading colors, and backdrop-dependent contrast in the live app; offscreen native checks do not establish final appearance.
- [ ] Check expanded Settings states, including multiline errors, login approval, and uninstall recovery. The fixed, non-scrolling window may compress or clip content; this remains unverified.
Keep intentional differences: taller two-line uninstall rows, larger translation output, native Settings controls, and destructive confirmation shortcuts.
@@ -47,4 +47,14 @@ import Testing
_ = launcher.control(search, textView: NSTextView(), doCommandBy: #selector(NSResponder.cancelOperation(_:))) _ = launcher.control(search, textView: NSTextView(), doCommandBy: #selector(NSResponder.cancelOperation(_:)))
#expect(search.stringValue == "translate") #expect(search.stringValue == "translate")
#expect(table.numberOfRows == 1) #expect(table.numberOfRows == 1)
_ = launcher.control(search, textView: NSTextView(), doCommandBy: #selector(NSResponder.insertNewline(_:)))
let back = try #require(descendants(content).compactMap { $0 as? NSButton }.first {
!$0.isHidden && $0.accessibilityLabel() == "Back to applications"
})
search.stringValue = "Hello"
launcher.controlTextDidChange(Notification(name: NSControl.textDidChangeNotification, object: search))
back.performClick(nil)
#expect(search.stringValue == "translate")
#expect(back.isHidden)
#expect(table.numberOfRows == 1)
} }