Skip to content
Open
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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<key>CFBundleVersion</key>
<string>10</string>
<key>CFBundleShortVersionString</key>
<string>1.5.12</string>
<string>1.5.13-beta.1</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>LSApplicationCategoryType</key>
Expand Down
2 changes: 1 addition & 1 deletion Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 16 additions & 4 deletions Sources/Fluid/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -296,27 +296,34 @@ struct ContentView: View {

// Set up notch click callback for expanding command conversation
NotchOverlayManager.shared.onNotchClicked = {
guard NotchOverlayManager.shared.canHandleNotchCommandTap else { return }
// When notch is clicked in command mode, show expanded conversation
if !NotchContentState.shared.commandConversationHistory.isEmpty {
if NotchOverlayManager.shared.canShowExpandedCommandOutput,
!NotchContentState.shared.commandConversationHistory.isEmpty
{
NotchOverlayManager.shared.showExpandedCommandOutput()
}
}

// Set up command mode callbacks for notch
NotchOverlayManager.shared.onCommandFollowUp = { [weak commandModeService] text in
guard NotchOverlayManager.shared.allowsCommandNotchActions else { return }
await commandModeService?.processFollowUpCommand(text)
}

// Chat management callbacks
NotchOverlayManager.shared.onNewChat = { [weak commandModeService] in
guard NotchOverlayManager.shared.allowsCommandNotchActions else { return }
commandModeService?.createNewChat()
}

NotchOverlayManager.shared.onSwitchChat = { [weak commandModeService] chatID in
guard NotchOverlayManager.shared.allowsCommandNotchActions else { return }
commandModeService?.switchToChat(id: chatID)
}

NotchOverlayManager.shared.onClearChat = { [weak commandModeService] in
guard NotchOverlayManager.shared.allowsCommandNotchActions else { return }
commandModeService?.deleteCurrentChat()
}

Expand Down Expand Up @@ -1712,12 +1719,12 @@ struct ContentView: View {

self.clearActiveRecordingMode()

// Show "Transcribing..." state before calling stop() to keep overlay visible.
// Show "Transcribing" state before calling stop() to keep overlay visible.
// The asr.stop() call performs the final transcription which can take a moment
// (especially for slower models like Whisper Medium/Large).
DebugLogger.shared.debug("Showing transcription processing state", source: "ContentView")
self.menuBarManager.setProcessing(true)
NotchOverlayManager.shared.updateTranscriptionText("Transcribing...")
NotchOverlayManager.shared.updateTranscriptionText("Transcribing")

// Give SwiftUI a chance to render the processing state before we do heavier work
// (ASR finalization + optional AI post-processing).
Expand All @@ -1734,6 +1741,7 @@ struct ContentView: View {
DebugLogger.shared.debug("Transcription returned empty text", source: "ContentView")
// Hide processing state when returning early
self.menuBarManager.setProcessing(false)
NotchOverlayManager.shared.hide()
return
}

Expand Down Expand Up @@ -1807,7 +1815,7 @@ struct ContentView: View {
let postProcessingStart = Date()

// Update overlay text to show we're now refining (processing already true)
NotchOverlayManager.shared.updateTranscriptionText("Refining...")
NotchOverlayManager.shared.updateTranscriptionText("Refining")

// Ensure the status label becomes visible immediately.
await Task.yield()
Expand Down Expand Up @@ -1957,6 +1965,10 @@ struct ContentView: View {
]
)
}

if !didTypeExternally {
NotchOverlayManager.shared.hide()
}
}

private func currentDictationOutputRouteForHotkeyStop() -> DictationOutputRoute {
Expand Down
33 changes: 33 additions & 0 deletions Sources/Fluid/Persistence/SettingsStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1332,6 +1332,22 @@ final class SettingsStore: ObservableObject {
}
}

/// Internal presentation modes for the top notch overlay.
/// This is intentionally separate from bottom overlay sizing.
enum NotchPresentationMode: String, CaseIterable, Codable {
case standard
case minimal

var displayName: String {
switch self {
case .standard:
return "Standard Notch"
case .minimal:
return "Compact"
}
}
}

/// Where the recording overlay appears (default: bottom)
var overlayPosition: OverlayPosition {
get {
Expand All @@ -1348,6 +1364,22 @@ final class SettingsStore: ObservableObject {
}
}

/// Internal-only top notch presentation mode. No public settings UI yet.
var notchPresentationMode: NotchPresentationMode {
get {
guard let raw = self.defaults.string(forKey: Keys.notchPresentationMode),
let mode = NotchPresentationMode(rawValue: raw)
else {
return .standard
}
return mode
}
set {
objectWillChange.send()
self.defaults.set(newValue.rawValue, forKey: Keys.notchPresentationMode)
}
}

/// Vertical offset for the bottom overlay (distance from bottom of screen/dock)
var overlayBottomOffset: Double {
get {
Expand Down Expand Up @@ -3581,6 +3613,7 @@ private extension SettingsStore {

// Overlay Position
static let overlayPosition = "OverlayPosition"
static let notchPresentationMode = "NotchPresentationMode"
static let overlayBottomOffset = "OverlayBottomOffset"
static let overlayBottomOffsetMigratedTo50 = "OverlayBottomOffsetMigratedTo50"
static let overlaySize = "OverlaySize"
Expand Down
40 changes: 27 additions & 13 deletions Sources/Fluid/Services/CommandModeService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ final class CommandModeService: ObservableObject {
self.loadCurrentChatFromStore()
}

private var shouldSyncCommandNotchState: Bool {
self.enableNotchOutput && NotchOverlayManager.shared.shouldSyncCommandConversationToNotch
}

private func loadCurrentChatFromStore() {
if let session = chatStore.currentSession {
self.currentChatID = session.id
Expand Down Expand Up @@ -278,6 +282,11 @@ final class CommandModeService: ObservableObject {

/// Sync conversation history to NotchContentState
private func syncToNotchState() {
guard self.shouldSyncCommandNotchState else {
NotchContentState.shared.clearCommandOutput()
return
Comment on lines +285 to +287
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve command history when notch sync is temporarily disabled

When sync is disabled by presentation policy, this branch clears all notch command output state. If the user later re-enables standard notch mode, there is no automatic settings-change resync path, so expanded command output remains empty (and cannot open from existing history) until a new message or chat reload occurs. This makes mode toggling appear to lose the current conversation in notch UI.

Useful? React with πŸ‘Β / πŸ‘Ž.

}

NotchContentState.shared.clearCommandOutput()

for msg in self.conversationHistory {
Expand Down Expand Up @@ -308,7 +317,7 @@ final class CommandModeService: ObservableObject {
self.saveCurrentChat()

// Push to notch
if self.enableNotchOutput {
if self.shouldSyncCommandNotchState {
NotchContentState.shared.addCommandMessage(role: .user, content: text)
NotchContentState.shared.setCommandProcessing(true)
}
Expand All @@ -322,14 +331,18 @@ final class CommandModeService: ObservableObject {

// Add to both histories
self.conversationHistory.append(Message(role: .user, content: text))
NotchContentState.shared.addCommandMessage(role: .user, content: text)
if self.shouldSyncCommandNotchState {
NotchContentState.shared.addCommandMessage(role: .user, content: text)
}

// Auto-save after adding user message
self.saveCurrentChat()

self.isProcessing = true
self.didRequireConfirmationThisRun = false
NotchContentState.shared.setCommandProcessing(true)
if self.shouldSyncCommandNotchState {
NotchContentState.shared.setCommandProcessing(true)
}

await self.processNextTurn()
}
Expand Down Expand Up @@ -374,7 +387,7 @@ final class CommandModeService: ObservableObject {
self.captureCommandRunCompleted(success: false)

// Push to notch
if self.enableNotchOutput {
if self.shouldSyncCommandNotchState {
NotchContentState.shared.addCommandMessage(role: .assistant, content: errorMsg)
NotchContentState.shared.setCommandProcessing(false)
self.showExpandedNotchIfNeeded()
Expand All @@ -386,7 +399,7 @@ final class CommandModeService: ObservableObject {
self.currentStep = .thinking("Analyzing...")

// Push status to notch
if self.enableNotchOutput {
if self.shouldSyncCommandNotchState {
NotchContentState.shared.addCommandMessage(role: .status, content: "Thinking...")
}

Expand Down Expand Up @@ -414,7 +427,7 @@ final class CommandModeService: ObservableObject {
))

// Push step to notch
if self.enableNotchOutput {
if self.shouldSyncCommandNotchState {
let statusText = tc.purpose ?? self.stepDescription(for: stepType)
NotchContentState.shared.addCommandMessage(role: .status, content: statusText)
}
Expand All @@ -432,7 +445,7 @@ final class CommandModeService: ObservableObject {
self.currentStep = nil

// Push confirmation needed to notch
if self.enableNotchOutput {
if self.shouldSyncCommandNotchState {
NotchContentState.shared.addCommandMessage(role: .status, content: "⚠️ Confirmation needed in Command Mode window")
NotchContentState.shared.setCommandProcessing(false)
}
Expand Down Expand Up @@ -464,7 +477,7 @@ final class CommandModeService: ObservableObject {
self.captureCommandRunCompleted(success: isFinal)

// Push final response to notch and show expanded view
if self.enableNotchOutput {
if self.shouldSyncCommandNotchState {
NotchContentState.shared.updateCommandStreamingText("") // Clear streaming
NotchContentState.shared.addCommandMessage(role: .assistant, content: response.content)
NotchContentState.shared.setCommandProcessing(false)
Expand All @@ -488,7 +501,7 @@ final class CommandModeService: ObservableObject {
self.captureCommandRunCompleted(success: false)

// Push error to notch
if self.enableNotchOutput {
if self.shouldSyncCommandNotchState {
NotchContentState.shared.addCommandMessage(role: .assistant, content: errorMsg)
NotchContentState.shared.setCommandProcessing(false)
self.showExpandedNotchIfNeeded()
Expand Down Expand Up @@ -530,7 +543,8 @@ final class CommandModeService: ObservableObject {

/// Show expanded notch output if there's content to display
private func showExpandedNotchIfNeeded() {
guard self.enableNotchOutput else { return }
guard self.shouldSyncCommandNotchState else { return }
guard NotchOverlayManager.shared.canShowExpandedCommandOutput else { return }
guard !NotchContentState.shared.commandConversationHistory.isEmpty else { return }

// Show the expanded notch
Expand Down Expand Up @@ -912,7 +926,7 @@ final class CommandModeService: ObservableObject {
self.streamingText = fullContent

// Push to notch for real-time display
if self.enableNotchOutput {
if self.shouldSyncCommandNotchState {
NotchContentState.shared.updateCommandStreamingText(fullContent)
}
}
Expand All @@ -928,7 +942,7 @@ final class CommandModeService: ObservableObject {
let fullContent = self.streamingBuffer.joined()
if !fullContent.isEmpty {
self.streamingText = fullContent
if self.enableNotchOutput {
if self.shouldSyncCommandNotchState {
NotchContentState.shared.updateCommandStreamingText(fullContent)
}
}
Expand All @@ -945,7 +959,7 @@ final class CommandModeService: ObservableObject {
self.thinkingBuffer = [] // Clear thinking buffer

// Clear notch streaming text as well
if self.enableNotchOutput {
if self.shouldSyncCommandNotchState {
NotchContentState.shared.updateCommandStreamingText("")
}

Expand Down
Loading
Loading