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
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ struct ChatFirstShell: View {
{
PageGlassLane(
selectedIndex: ChatFirstPageGlassLanePolicy.pageGlassLaneIndex(for: navigation.route),
memoryDestinationRawValue: memoryDestinationRawValue
memoryDestinationRawValue: memoryDestinationRawValue,
homeOwnsItsPanels: HomeDesignPresentation.queryShellOwnsItsPanels(
useLegacyHomeDesign: true,
forceModernPresentation: true)
) {
routeDestination
}
Expand Down
13 changes: 7 additions & 6 deletions desktop/macos/Desktop/Sources/MainWindow/DesktopHomeView.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import AppKit

Check warning on line 1 in desktop/macos/Desktop/Sources/MainWindow/DesktopHomeView.swift

View workflow job for this annotation

GitHub Actions / PR Metadata Preflight

Large changed file

desktop/macos/Desktop/Sources/MainWindow/DesktopHomeView.swift is 1637 lines; consider splitting files over 800 lines.

Check warning on line 1 in desktop/macos/Desktop/Sources/MainWindow/DesktopHomeView.swift

View workflow job for this annotation

GitHub Actions / Hygiene

Large changed file

desktop/macos/Desktop/Sources/MainWindow/DesktopHomeView.swift is 1637 lines; consider splitting files over 800 lines.
import OmiTheme
import SwiftUI

Expand Down Expand Up @@ -100,6 +100,8 @@
selectedIndex == SidebarNavItem.settings.rawValue
}

private var homeOwnsItsPanels: Bool { !useLegacyHomeDesign }

private var shouldShowAuthEntryShell: Bool {
authState.isRestoringAuth || authState.sessionPhase == .recoveryRequired || !authState.isSignedIn
|| !hasCompletedOnboardingAtAuthorityRead
Expand Down Expand Up @@ -529,13 +531,10 @@
}
}

/// Pins the hugged glass: not smaller than the destinations, not wider than the
/// readable lane plus its page margins. Height stays display-limited.
/// Pins the shell between the destination minimum and the visible display frame.
private static func pinShellWindowSizeLimits(_ window: NSWindow, resizeFrame: Bool = true) {
let minimumContentSize = DesktopWindowLayoutPolicy.minimumContentSize
let maximumContentSize = NSSize(
width: DesktopWindowLayoutPolicy.maximumContentWidth,
height: 10_000)
let maximumContentSize = DesktopWindowLayoutPolicy.maximumContentSize(for: window)
Comment on lines +534 to +537

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 Recompute the display-sized maximum when the window moves

The maximum is now derived from window.screen, but it is only sampled during initial enforcement, activation/navigation, and didResize; moving an already-wide window from a large monitor to a smaller one emits didMove/screen-change notifications without necessarily resizing it. The window therefore retains the larger display's maxSize and can remain wider or taller than the new visible frame, while the existing stranded-frame recovery accepts it as long as 320×240 points overlap a screen. Re-pin and clamp on window-screen/display-parameter changes as well as resize.

Useful? React with 👍 / 👎.

window.contentMinSize = minimumContentSize
window.minSize = window.frameRect(forContentRect: NSRect(origin: .zero, size: minimumContentSize)).size
window.contentMaxSize = maximumContentSize
Expand Down Expand Up @@ -1386,7 +1385,9 @@
// One panel per destination — see `PageGlassLane`. Settings' own section list rides inside it
// so the page is one object rather than a panel with its nav stranded on the wallpaper.
PageGlassLane(
selectedIndex: selectedIndex, memoryDestinationRawValue: memoryDestinationRawValue
selectedIndex: selectedIndex,
memoryDestinationRawValue: memoryDestinationRawValue,
homeOwnsItsPanels: homeOwnsItsPanels
) {
HStack(spacing: 0) {
if isInSettings && !showsPrimarySidebar { settingsSidebar }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,32 @@ enum DesktopShellPresentationPolicy {
}
}

enum HomeDesignPresentation: Equatable {
case queryShell
case redesignedHub
case oldestLegacy

static func resolve(
useLegacyHomeDesign: Bool,
useOldestHomeDesign: Bool,
forceModernPresentation: Bool
) -> Self {
guard !forceModernPresentation, useLegacyHomeDesign else { return .queryShell }
return useOldestHomeDesign ? .oldestLegacy : .redesignedHub
}

static func queryShellOwnsItsPanels(
useLegacyHomeDesign: Bool,
forceModernPresentation: Bool
) -> Bool {
resolve(
useLegacyHomeDesign: useLegacyHomeDesign,
useOldestHomeDesign: false,
forceModernPresentation: forceModernPresentation
) == .queryShell
}
}

@MainActor
enum FloatingPrimaryTextInputRouting {
private(set) static var routesToMainApp = false
Expand Down
6 changes: 3 additions & 3 deletions desktop/macos/Desktop/Sources/MainWindow/DesktopTopBar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,9 @@ enum TopNavigationLayoutMetrics {
/// `PageGlassLaneLayout.laneWidth` both delegate here, so the bar and whatever is under it cannot
/// drift apart.
///
/// The lane fills the window. The 900 pt readable cap is a window-max
/// (`DesktopWindowLayoutPolicy.maximumContentWidth`), not an internal inset: capping here
/// inside a larger window is what drew the invisible click border around the glass.
/// The lane fills the window. The 900 pt readable cap belongs to content inside the lane, not to
/// the window itself: capping here inside a larger window is what drew the invisible click border
/// around the glass.
static func contentLaneWidth(for availableWidth: CGFloat) -> CGFloat {
max(0, availableWidth - (DesktopWindowLayoutPolicy.windowInset * 2))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ enum DesktopWindowLayoutPolicy {
/// gutter. Chat's own `pageMargin` is interior layout and is not this inset.
static let windowInset: CGFloat = 0

/// Readable lane plus `windowInset` on each side.
///
/// Extra width beyond this is empty wallpaper that still belongs to the window
/// until AppKit clamps the frame. Height stays display-limited: a taller panel
/// is still hugged glass, not a gutter.
static let maximumContentWidth =
ChatComposerLayout.contentLaneMaxWidth + windowInset * 2
@MainActor
static func maximumContentSize(for window: NSWindow) -> NSSize {
guard let visibleFrame = (window.screen ?? NSScreen.main)?.visibleFrame else {
return NSSize(width: 10_000, height: 10_000)
}
return window.contentRect(forFrameRect: visibleFrame).size
}
}
33 changes: 23 additions & 10 deletions desktop/macos/Desktop/Sources/MainWindow/PageGlassLane.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
// wallpaper. That is the whole point — panels sit *on* the desktop rather than on a full-bleed slab
// of glass the window painted for them.
//
// Home and Rewind were already built that way: each is a set of glass objects with real air between
// them (`QueryShellHome`, `RewindSearchLayout`). **Every other destination was drawn assuming the
// window's ground was underneath it**, so without one they render type and controls straight onto the
// wallpaper. This file is where they get a surface, and it is one surface for all of them: a page that
// invents its own panel is the drift `InkGlass` exists to stop.
// QueryShell Home and Rewind were already built that way: each is a set of glass objects with real air
// between them (`QueryShellHome`, `RewindSearchLayout`). The two older Home surfaces are the exception:
// `DashboardPage` is laned when it mounts either one, so without this file they render straight onto
// the wallpaper. **Every other destination was drawn assuming the window's ground was underneath it**,
// so this file is where they get a surface, and it is one surface for all of them: a page that invents
// its own panel is the drift `InkGlass` exists to stop.
//
// ## One tall panel, not a header plus a body
//
Expand Down Expand Up @@ -50,10 +51,17 @@ enum PageGlassLanePolicy {
///
/// It takes the raw index rather than a `SidebarNavItem` because the router's `switch` sends every
/// unrecognised index to Home through its `default:` branch. Resolving an unknown index to anything
/// else here would wrap Home in a second panel on exactly the routes nobody tests.
static func ownsItsPanels(selectedIndex: Int, memoryDestinationRawValue: Int? = nil) -> Bool {
/// else here would wrap Home in a second panel on exactly the routes nobody tests. The router passes
/// the already-resolved Home surface decision instead of making this lane read a settings key.
static func ownsItsPanels(
selectedIndex: Int,
memoryDestinationRawValue: Int? = nil,
homeOwnsItsPanels: Bool
) -> Bool {
switch SidebarNavItem(rawValue: selectedIndex) ?? .dashboard {
case .dashboard, .rewind:
case .dashboard:
return homeOwnsItsPanels
case .rewind:
return true
case .conversations:
// **Only this index is the Memory hub.** It is one rail slot wearing four different pages, and
Expand Down Expand Up @@ -106,18 +114,23 @@ enum PageGlassLaneLayout {
///
/// It wraps the router's whole page switch rather than each page in turn, so there is exactly one
/// place that decides what a destination's surface is. A page inside it paints no background of its
/// own (`glassContent()`); the panel is the ground.
/// own (`glassContent()`); the panel is the ground. Home and Rewind are handed their own-glass answer
/// by the router, while the older `DashboardPage` surfaces are handed `false` and use this panel.
struct PageGlassLane<Content: View>: View {
/// The route being rendered, used only to ask `PageGlassLanePolicy` whether it already has glass.
let selectedIndex: Int
/// The hub page being rendered when `selectedIndex` is the Memory hub's rail index. Nil for every
/// other destination, whose glass does not depend on a sub-page.
var memoryDestinationRawValue: Int? = nil
/// Whether the Home surface selected by the router owns its own glass.
let homeOwnsItsPanels: Bool
@ViewBuilder var content: () -> Content

var body: some View {
if PageGlassLanePolicy.ownsItsPanels(
selectedIndex: selectedIndex, memoryDestinationRawValue: memoryDestinationRawValue)
selectedIndex: selectedIndex,
memoryDestinationRawValue: memoryDestinationRawValue,
homeOwnsItsPanels: homeOwnsItsPanels)
{
// Handed the whole content area, so a modal dim mounted inside it has to take the lane rather
// than the surface it was given — see `ShellModalScrim`. Published here rather than chosen at
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import AppKit

Check warning on line 1 in desktop/macos/Desktop/Sources/MainWindow/Pages/DashboardPage.swift

View workflow job for this annotation

GitHub Actions / PR Metadata Preflight

Large changed file

desktop/macos/Desktop/Sources/MainWindow/Pages/DashboardPage.swift is 4297 lines; consider splitting files over 800 lines.

Check warning on line 1 in desktop/macos/Desktop/Sources/MainWindow/Pages/DashboardPage.swift

View workflow job for this annotation

GitHub Actions / Hygiene

Large changed file

desktop/macos/Desktop/Sources/MainWindow/Pages/DashboardPage.swift is 4297 lines; consider splitting files over 800 lines.
import Combine
import OmiTheme
import SwiftUI
Expand Down Expand Up @@ -256,6 +256,7 @@
@AppStorage(AssistantSettings.audioRecordingModeDefaultsKey) private var audioRecordingModeRaw =
AssistantSettings.AudioRecordingMode.onlyMeetings.rawValue
@AppStorage("useLegacyHomeDesign") private var useLegacyHomeDesign = false
@AppStorage("useOldestHomeDesign") private var useOldestHomeDesign = false
@State private var homeMode: HomeStageMode = .hub
@State private var didReportChatFirstTranscriptPage = false
@FocusState private var homeAskFieldFocused: Bool
Expand Down Expand Up @@ -379,14 +380,15 @@

private var homeSurface: some View {
Group {
if useLegacyHomeDesign && !routesChatToPrimaryShell {
if useLegacyHomeDesign && useOldestHomeDesign && !routesChatToPrimaryShell {
legacyHome
} else {
redesignedHome
Comment on lines +383 to 386

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Use the resolved Home presentation for behavior guards

When “Use old Home design” is enabled and the new “oldest” toggle remains at its default false, this branch now renders redesignedHome while its behavior still treats any useLegacyHomeDesign == true state as the widgets-based Home. Consequently consumePendingMainChatOpenRequest() consumes and discards “Continue in Omi” requests, the homeStage* automation actions return early, homeMode is reported as unavailable, and focusing the hub ask field does not transition to chat. Derive these guards from the selected HomeDesignPresentation (or an isOldestLegacy value) rather than the outer preference flag.

AGENTS.md reference: desktop/macos/AGENTS.md:L350-L352

Useful? React with 👍 / 👎.

Comment on lines +383 to 386

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 Gate legacy sheets on the resolved Home presentation

When “Use old Home design” is enabled but “Use oldest Home theme” is not, this branch renders redesignedHome, whose app and connector actions present homeConnectSheetOverlay; however, the three legacySelected* bindings still become non-nil whenever useLegacyHomeDesign is true, so the same selection also presents a dismissableSheet. Opening an app, import connector, or export destination therefore mounts two modal implementations over each other, causing duplicate content/scrims and duplicate onAppear side effects such as app-detail analytics. Gate the legacy bindings with the resolved .oldestLegacy presentation rather than the outer preference.

AGENTS.md reference: AGENTS.md:L46-L46

Useful? React with 👍 / 👎.

}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(useLegacyHomeDesign ? Color.clear : HomePalette.paper)
// `PageGlassLane.panel` supplies the ground for older Home surfaces; keep this clear to match it.
.background(Color.clear)
}

private func applyHomeSheets<Content: View>(to content: Content) -> some View {
Expand Down Expand Up @@ -456,7 +458,7 @@
.overlay {
if isLoadingCitation {
ZStack {
// Home fills the content area, so this dim was window-wide too — the sweep missed it.
// The lane publishes the modal bounds for this legacy Home surface.
ShellModalScrim()
VStack(spacing: OmiSpacing.md) {
ProgressView()
Expand Down Expand Up @@ -1442,9 +1444,7 @@
) -> some View {
ZStack {
if isShowingAppsPopup {
// Home owns its panels, so this page is handed the whole content area — a full-bleed dim
// here reaches the window's edges, and the window is transparent. `ShellModalScrim` reads
// that from `PageGlassLane` and puts the dim on the lane Home's own panels take.
// `PageGlassLane` supplies legacy Home ground and bounds this scrim; do not re-derive the preference.
ShellModalScrim(onTap: dismissAppsPopup)
.transition(.opacity)
.zIndex(2)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import OmiTheme

Check warning on line 1 in desktop/macos/Desktop/Sources/MainWindow/Pages/Settings/Sections/SettingsContentView+Assistants.swift

View workflow job for this annotation

GitHub Actions / PR Metadata Preflight

Large changed file

desktop/macos/Desktop/Sources/MainWindow/Pages/Settings/Sections/SettingsContentView+Assistants.swift is 1261 lines; consider splitting files over 800 lines.

Check warning on line 1 in desktop/macos/Desktop/Sources/MainWindow/Pages/Settings/Sections/SettingsContentView+Assistants.swift

View workflow job for this annotation

GitHub Actions / Hygiene

Large changed file

desktop/macos/Desktop/Sources/MainWindow/Pages/Settings/Sections/SettingsContentView+Assistants.swift is 1261 lines; consider splitting files over 800 lines.
import Sparkle
import SwiftUI
import UniformTypeIdentifiers
Expand Down Expand Up @@ -897,6 +897,33 @@
}
}

if useLegacyHomeDesign {
settingsCard(settingId: "advanced.preferences.oldesthome") {
HStack(spacing: OmiSpacing.lg) {
Image(systemName: "rectangle.stack")
.scaledFont(size: OmiType.subheading)
.foregroundColor(Ink.secondary)
.frame(width: 24, height: 24)

VStack(alignment: .leading, spacing: OmiSpacing.xxs) {
Text("Use oldest Home theme")
.scaledFont(size: OmiType.subheading, weight: .semibold)
.foregroundColor(Ink.primary)

Text("Show the original widgets-and-chat Home")
.scaledFont(size: OmiType.body)
.foregroundColor(Ink.secondary)
}

Spacer()

Toggle("", isOn: $useOldestHomeDesign)
.toggleStyle(OmiToggleStyle())
.labelsHidden()
}
}
}

settingsCard(settingId: "advanced.preferences.speaknotifications") {
HStack(spacing: OmiSpacing.lg) {
Image(systemName: "speaker.wave.2")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ struct SettingsContentView: View {
@AppStorage("multiChatEnabled") var multiChatEnabled = false
@AppStorage("conversationsCompactView") var conversationsCompactView = true
@AppStorage("useLegacyHomeDesign") var useLegacyHomeDesign = false
@AppStorage("useOldestHomeDesign") var useOldestHomeDesign = false
@AppStorage("speakNotificationsAloud") var speakNotificationsAloud = false
@AppStorage(DefaultsKey.integrationNudgesEnabled.rawValue) var integrationNudgesEnabled = true

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ struct QueryShellHome: View {
@State private var caretClaims = 0

private var usesLegacyPresentation: Bool {
useLegacyHomeDesign && !forceModernPresentation
!HomeDesignPresentation.queryShellOwnsItsPanels(
useLegacyHomeDesign: useLegacyHomeDesign,
forceModernPresentation: forceModernPresentation)
}

var body: some View {
Expand Down
4 changes: 2 additions & 2 deletions desktop/macos/Desktop/Sources/MainWindow/ShellSummon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ enum ShellSummonPlacement {
/// It stays above `DesktopWindowLayoutPolicy.minimumContentSize`, which is the floor the
/// destinations lay out to.
static let defaultSize = NSSize(
width: DesktopWindowLayoutPolicy.maximumContentWidth, height: 700)
width: ChatComposerLayout.contentLaneMaxWidth, height: 700)

/// Where the shell lands on a given display.
///
Expand Down Expand Up @@ -123,7 +123,7 @@ enum ShellSummonPlacement {
static func fitted(
_ size: NSSize,
in visibleFrame: NSRect,
maxWidth: CGFloat = DesktopWindowLayoutPolicy.maximumContentWidth
maxWidth: CGFloat = ChatComposerLayout.contentLaneMaxWidth
) -> NSSize {
NSSize(
width: min(size.width, visibleFrame.width, maxWidth),
Comment on lines +126 to 129

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 newly allowed wide shell frames on summon

After this change permits resizing the shell up to the display width, any user-selected width above the 900-point chat lane is still reduced to 900 here when the remembered frame is used. Thus widening the redesigned or oldest Home is lost after dismissing and summoning the shell, moving it through a summon to another display, or recreating the window, despite ShellFrameMemory recording the wider frame and landingFrame explicitly promising to preserve a user-widened size. The obsolete pre-hug clamp needs to distinguish migrated frames from widths that are now valid, or be removed.

Useful? React with 👍 / 👎.

Expand Down
20 changes: 20 additions & 0 deletions desktop/macos/Desktop/Tests/DesktopWindowLayoutPolicyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import XCTest

@testable import Omi_Computer

@MainActor
final class DesktopWindowLayoutPolicyTests: XCTestCase {
func testMainWindowMinimumFitsA1024PointWideDisplay() {
XCTAssertLessThanOrEqual(
Expand All @@ -12,4 +13,23 @@ final class DesktopWindowLayoutPolicyTests: XCTestCase {
)
XCTAssertEqual(DesktopWindowLayoutPolicy.minimumContentSize.height, 680)
}

func testMaximumContentSizeUsesTheVisibleDisplayInsteadOfTheContentLane() throws {
guard let screen = NSScreen.main else { throw XCTSkip("No display is available") }
guard screen.visibleFrame.width > ChatComposerLayout.contentLaneMaxWidth else {
throw XCTSkip("The test display is not wider than the content lane")
}

let window = NSWindow(
contentRect: screen.visibleFrame,
styleMask: .borderless,
backing: .buffered,
defer: true)
let maximum = DesktopWindowLayoutPolicy.maximumContentSize(for: window)

let expected = window.contentRect(forFrameRect: screen.visibleFrame).size
XCTAssertEqual(maximum.width, expected.width, accuracy: 0.01)
XCTAssertEqual(maximum.height, expected.height, accuracy: 0.01)
XCTAssertGreaterThan(maximum.width, ChatComposerLayout.contentLaneMaxWidth)
}
}
Loading
Loading