Skip to content

Commit 2e12aad

Browse files
fix(desktop): enable Return for onboarding actions
Failure-Class: none
1 parent 2decc17 commit 2e12aad

6 files changed

Lines changed: 73 additions & 9 deletions

File tree

desktop/macos/Desktop/Sources/FileIndexing/FileIndexingView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ struct FileIndexingView: View {
215215
.shadow(color: OmiColors.accent.opacity(0.4), radius: 16, x: 0, y: 4)
216216
}
217217
.buttonStyle(.plain)
218+
.keyboardShortcut(.defaultAction)
218219
.padding(.bottom, OmiSpacing.page)
219220
}
220221

desktop/macos/Desktop/Sources/MainWindow/SecondBrain/SBComponents.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ struct SBInkButton: View {
9191
var size: CGFloat = 14
9292
var horizontalPadding: CGFloat = 18
9393
var verticalPadding: CGFloat = 9
94+
var isDefaultAction = false
9495
let action: () -> Void
9596

9697
var body: some View {
@@ -105,6 +106,20 @@ struct SBInkButton: View {
105106
)
106107
}
107108
.buttonStyle(.plain)
109+
.modifier(SBDefaultActionKeyboardShortcut(enabled: isDefaultAction))
110+
}
111+
}
112+
113+
private struct SBDefaultActionKeyboardShortcut: ViewModifier {
114+
let enabled: Bool
115+
116+
@ViewBuilder
117+
func body(content: Content) -> some View {
118+
if enabled {
119+
content.keyboardShortcut(.defaultAction)
120+
} else {
121+
content
122+
}
108123
}
109124
}
110125

desktop/macos/Desktop/Sources/Onboarding/OnboardingChatView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ struct OnboardingChatView: View {
336336
.background(OmiColors.accent)
337337
.cornerRadius(OmiChrome.smallControlRadius)
338338
}
339-
.buttonStyle(.plain)
339+
.buttonStyle(.plain).keyboardShortcut(.defaultAction)
340340
.padding(.top, OmiSpacing.md)
341341
}
342342

desktop/macos/Desktop/Sources/Onboarding/SecondBrain/SBOnboardingView.swift

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ struct SBOnboardingView: View {
198198
trustRow("YOURS") { Text("Pause me from the notch. Delete anything, forever.") }
199199
}
200200
.overlay(RoundedRectangle(cornerRadius: 13).stroke(sb.ink(.w1), lineWidth: 1))
201-
SBInkButton(title: "Set up Omi →") { model.answerPromise() }
201+
SBInkButton(title: "Set up Omi →", isDefaultAction: true) { model.answerPromise() }
202202
}
203203
}
204204

@@ -241,7 +241,7 @@ struct SBOnboardingView: View {
241241
Text(draft).geist(size: 17, weight: .medium).foregroundStyle(sb.ink)
242242
Text("· detected").geist(size: 12.5).foregroundStyle(sb.ink(.w4))
243243
}
244-
SBInkButton(title: "Continue") {
244+
SBInkButton(title: "Continue", isDefaultAction: true) {
245245
if let m = all.first(where: { $0.name.lowercased() == draft.lowercased() }) {
246246
model.pickLanguage(code: m.code, name: m.name)
247247
} else {
@@ -338,6 +338,7 @@ struct SBOnboardingView: View {
338338
.contentShape(Rectangle())
339339
}
340340
.buttonStyle(.plain)
341+
.keyboardShortcut(.defaultAction)
341342
} else {
342343
Button {
343344
if state == .ask { model.requestPerm(key) }
@@ -439,7 +440,9 @@ struct SBOnboardingView: View {
439440
// just a quiet Skip so the user is never stuck.
440441
Group {
441442
if model.shortcutPressed {
442-
SBInkButton(title: "Continue") { isTalk ? model.answerShortcutTalk() : model.answerShortcutOpen() }
443+
SBInkButton(title: "Continue", isDefaultAction: true) {
444+
isTalk ? model.answerShortcutTalk() : model.answerShortcutOpen()
445+
}
443446
} else {
444447
Button {
445448
isTalk ? model.answerShortcutTalk() : model.answerShortcutOpen()
@@ -472,7 +475,7 @@ struct SBOnboardingView: View {
472475
// that, a quiet "Skip for now" so the user doesn't blow past the live demo.
473476
Group {
474477
if model.screenDemoDone {
475-
SBInkButton(title: "Continue") { model.answerScreenDemo() }
478+
SBInkButton(title: "Continue", isDefaultAction: true) { model.answerScreenDemo() }
476479
} else {
477480
Button {
478481
model.answerScreenDemo()
@@ -501,7 +504,7 @@ struct SBOnboardingView: View {
501504
}
502505
.padding(.horizontal, 14)
503506
.overlay(RoundedRectangle(cornerRadius: 13).stroke(sb.ink(.w1), lineWidth: 1))
504-
SBInkButton(title: "Continue") { model.answerAgents() }
507+
SBInkButton(title: "Continue", isDefaultAction: true) { model.answerAgents() }
505508
}
506509
.frame(maxWidth: 380, alignment: .leading)
507510
}
@@ -518,7 +521,7 @@ struct SBOnboardingView: View {
518521
}
519522
.padding(.horizontal, 14)
520523
.overlay(RoundedRectangle(cornerRadius: 13).stroke(sb.ink(.w1), lineWidth: 1))
521-
SBInkButton(title: "Continue") { model.answerContext() }
524+
SBInkButton(title: "Continue", isDefaultAction: true) { model.answerContext() }
522525
}
523526
.frame(maxWidth: 380, alignment: .leading)
524527
}

desktop/macos/Desktop/Tests/OnboardingFlowTests.swift

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,44 @@ final class OnboardingFlowTests: XCTestCase {
387387
}
388388
}
389389

390+
/// Static tripwire: keyboard shortcut registration is a SwiftUI wiring
391+
/// contract, so assert every visible onboarding proceed action remains the
392+
/// default action without trying to synthesize AppKit key events in a unit test.
393+
func testOnboardingProceedActionsUseDefaultActionKeyboardShortcut() throws {
394+
// omi-test-quality: source-inspection -- static contract: verifies SwiftUI default-action wiring on every visible onboarding proceed control
395+
let chatSource = try desktopSourceFile("Onboarding/OnboardingChatView.swift")
396+
XCTAssertTrue(
397+
chatSource.contains("handleOnboardingComplete()\n })")
398+
&& chatSource.contains(".keyboardShortcut(.defaultAction)\n .padding(.top"),
399+
"the conversational onboarding Continue button must accept Return")
400+
401+
let fileIndexingSource = try desktopSourceFile("FileIndexing/FileIndexingView.swift")
402+
XCTAssertTrue(
403+
fileIndexingSource.contains("onComplete(totalFilesScanned)")
404+
&& fileIndexingSource.contains(".keyboardShortcut(.defaultAction)\n .padding(.bottom"),
405+
"the file-index onboarding Continue button must accept Return")
406+
407+
let secondBrainSource = try desktopSourceFile("Onboarding/SecondBrain/SBOnboardingView.swift")
408+
for title in ["Set up Omi →", "Continue"] {
409+
XCTAssertTrue(
410+
secondBrainSource.contains("SBInkButton(title: \"\\(title)\", isDefaultAction: true)"),
411+
"the second-brain \(title) action must accept Return")
412+
}
413+
XCTAssertEqual(
414+
secondBrainSource.components(separatedBy: "isDefaultAction: true").count - 1,
415+
6,
416+
"every visible second-brain proceed action must register Return")
417+
XCTAssertTrue(
418+
secondBrainSource.contains("Text(\"Continue →\")")
419+
&& secondBrainSource.contains(".keyboardShortcut(.defaultAction)\n } else {"),
420+
"the granted-permission Continue action must accept Return")
421+
422+
let componentsSource = try desktopSourceFile("MainWindow/SecondBrain/SBComponents.swift")
423+
XCTAssertTrue(
424+
componentsSource.contains("content.keyboardShortcut(.defaultAction)"),
425+
"SBInkButton must wire opted-in proceed actions to Return")
426+
}
427+
390428
// Regression: arrow navigation must be computed from persisted step state and
391429
// applied by the mounted view — the NSEvent monitor's captured view copy drops
392430
// @AppStorage writes on some macOS versions. These cover the extracted
@@ -440,11 +478,15 @@ final class OnboardingFlowTests: XCTestCase {
440478
}
441479

442480
private func onboardingSourceFile(_ name: String) throws -> String {
481+
try desktopSourceFile("Onboarding/\(name)")
482+
}
483+
484+
private func desktopSourceFile(_ relativePath: String) throws -> String {
443485
let sourceURL = URL(fileURLWithPath: #filePath)
444486
.deletingLastPathComponent()
445487
.deletingLastPathComponent()
446-
.appendingPathComponent("Sources/Onboarding")
447-
.appendingPathComponent(name)
488+
.appendingPathComponent("Sources")
489+
.appendingPathComponent(relativePath)
448490
// omi-test-quality: source-inspection -- static contract: forbids uncancellable deferred-advance patterns in step views
449491
return try String(contentsOf: sourceURL, encoding: .utf8)
450492
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"change": "Added Return-key support for onboarding Continue and setup actions"
3+
}

0 commit comments

Comments
 (0)