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
16 changes: 0 additions & 16 deletions apps/sim/app/(auth)/auth-layout-client.tsx

This file was deleted.

25 changes: 12 additions & 13 deletions apps/sim/app/(auth)/components/auth-shell.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import type { ReactNode } from 'react'
import { cn } from '@sim/emcn'
import Link from 'next/link'
import { DesktopTitleBarLane } from '@/app/_shell/desktop-title-bar'
import { LogoMark, SimWordmark } from '@/app/(landing)/components/navbar/components'

interface AuthShellProps {
/** Centered content column (the form, status copy, etc.). */
children: ReactNode
/** Optional element pinned to the bottom of the shell (e.g. the support footer). */
footer?: ReactNode
/** Reserve the native macOS title-bar lane for the desktop login route. */
reserveDesktopTitleBar?: boolean
}

/**
Expand All @@ -21,18 +19,19 @@ interface AuthShellProps {
* the canvas/`--text-primary` surface, and renders a logo-only header that reuses
* the landing {@link LogoMark} + {@link SimWordmark} at the same nav gutters. The
* single content column is centered and capped for a calm single-form layout.
*
* The shell also owns the macOS traffic-light lane, unconditionally — every surface that
* wears it (the `(auth)` routes, the CLI auth handoff, the invite pages) sits outside
* workspace chrome and draws its logo where the lights are. Gating this per route left
* whichever surface was overlooked drawing underneath them, and a route list could not
* cover a dynamic segment like `/invite/[id]` anyway. Off the desktop shell
* `--desktop-title-bar-height` is `0px`, so the reservation and the drag strip both
* collapse to nothing and `.desktop-title-bar-page` is exactly `min-h-screen`.
*/
export function AuthShell({ children, footer, reserveDesktopTitleBar = false }: AuthShellProps) {
export function AuthShell({ children, footer }: AuthShellProps) {
return (
<div
className={cn(
'light relative flex flex-col bg-[var(--bg)] text-[var(--text-primary)]',
reserveDesktopTitleBar ? 'desktop-title-bar-page' : 'min-h-screen'
)}
>
{reserveDesktopTitleBar && (
<div aria-hidden className='desktop-login-window-drag-region desktop-window-drag-region' />
)}
<div className='light desktop-title-bar-page relative flex flex-col bg-[var(--bg)] text-[var(--text-primary)]'>
<DesktopTitleBarLane />
<header>
<nav className='mx-auto flex w-full max-w-[1446px] items-center px-12 py-4 max-sm:px-5 max-lg:px-8'>
<Link href='/' aria-label='Sim home' className='flex h-[30px] items-center'>
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/(auth)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { Metadata } from 'next'
import AuthLayoutClient from '@/app/(auth)/auth-layout-client'
import { AuthShell } from '@/app/(auth)/components'

export const metadata: Metadata = {
robots: { index: false, follow: false },
}

export default function AuthLayout({ children }: { children: React.ReactNode }) {
return <AuthLayoutClient>{children}</AuthLayoutClient>
return <AuthShell>{children}</AuthShell>
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ function ResetPasswordContent() {

export default function ResetPasswordPage() {
return (
<Suspense fallback={<div className='flex h-screen items-center justify-center'>Loading…</div>}>
<Suspense
fallback={<div className='flex min-h-[320px] items-center justify-center'>Loading…</div>}
>
<ResetPasswordContent />
</Suspense>
)
Expand Down
4 changes: 3 additions & 1 deletion apps/sim/app/(auth)/signup/signup-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,9 @@ export default function SignupPage({
emailSignupEnabled,
}: SignupFormProps) {
return (
<Suspense fallback={<div className='flex h-screen items-center justify-center'>Loading…</div>}>
<Suspense
fallback={<div className='flex min-h-[320px] items-center justify-center'>Loading…</div>}
>
<SignupFormContent
githubAvailable={githubAvailable}
googleAvailable={googleAvailable}
Expand Down
4 changes: 3 additions & 1 deletion apps/sim/app/(interfaces)/chat/[identifier]/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
AGENT_STREAM_PROTOCOL_HEADER,
AGENT_STREAM_PROTOCOL_V1,
} from '@/lib/workflows/streaming/agent-stream-protocol'
import { DesktopTitleBarLane } from '@/app/_shell/desktop-title-bar'
import {
ChatErrorState,
ChatHeader,
Expand Down Expand Up @@ -440,7 +441,8 @@ export default function ChatClient({ identifier }: { identifier: string }) {
}

return (
<div className='light fixed inset-0 z-[100] flex flex-col bg-[var(--bg)] text-[var(--text-primary)]'>
<div className='light desktop-title-bar-page fixed inset-0 z-[100] flex flex-col bg-[var(--bg)] text-[var(--text-primary)]'>
<DesktopTitleBarLane />
{/* Header component */}
<ChatHeader chatConfig={chatConfig} starCount={starCount} />

Expand Down
4 changes: 3 additions & 1 deletion apps/sim/app/(interfaces)/chat/[identifier]/loading.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Skeleton } from '@sim/emcn'
import { DesktopTitleBarLane } from '@/app/_shell/desktop-title-bar'

export default function ChatLoading() {
return (
<div className='light fixed inset-0 z-[100] flex flex-col bg-[var(--bg)] text-[var(--text-primary)]'>
<div className='light desktop-title-bar-page fixed inset-0 z-[100] flex flex-col bg-[var(--bg)] text-[var(--text-primary)]'>
<DesktopTitleBarLane />
<div className='border-[var(--border-1)] border-b px-4 py-3'>
<div className='mx-auto flex max-w-3xl items-center justify-between'>
<div className='flex items-center gap-[12px]'>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Skeleton } from '@sim/emcn'
import { DesktopTitleBarLane } from '@/app/_shell/desktop-title-bar'

export function ChatLoadingState() {
return (
<div className='light fixed inset-0 z-[100] flex flex-col bg-[var(--bg)]'>
<div className='light desktop-title-bar-page fixed inset-0 z-[100] flex flex-col bg-[var(--bg)]'>
<DesktopTitleBarLane />
<div className='flex flex-1 items-center justify-center px-4'>
<div className='w-full max-w-[410px]'>
<div className='flex flex-col items-center justify-center'>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
MAX_CHAT_SESSION_MS,
SAMPLE_RATE,
} from '@/lib/speech/config'
import { DesktopTitleBarLane } from '@/app/_shell/desktop-title-bar'

const ParticlesVisualization = dynamic(
() =>
Expand Down Expand Up @@ -524,10 +525,11 @@ export function VoiceInterface({
return (
<div
className={cn(
'light fixed inset-0 z-[100] flex flex-col bg-[var(--bg)] text-[var(--text-primary)]',
'light desktop-title-bar-page fixed inset-0 z-[100] flex flex-col bg-[var(--bg)] text-[var(--text-primary)]',
className
)}
>
<DesktopTitleBarLane />
<div className='flex flex-1 flex-col items-center justify-center px-8'>
<div className='relative mb-16'>
<ParticlesVisualization
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Skeleton } from '@sim/emcn'

export default function ResumeLoading() {
return (
<div className='min-h-screen bg-background'>
<div className='bg-background'>
<div className='border-b px-4 py-3'>
<div className='mx-auto flex max-w-[1200px] items-center justify-between'>
<Skeleton className='h-[24px] w-[80px] rounded-[4px]' />
Expand Down
4 changes: 3 additions & 1 deletion apps/sim/app/(landing)/components/logo-shell/logo-shell.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ReactNode } from 'react'
import { cn } from '@sim/emcn'
import Link from 'next/link'
import { DesktopTitleBarLane } from '@/app/_shell/desktop-title-bar'
import { LogoMark, SimWordmark } from '@/app/(landing)/components/navbar/components'

/**
Expand All @@ -26,7 +27,8 @@ interface LogoShellProps {

export function LogoShell({ children, center = false, footer }: LogoShellProps) {
return (
<div className='light relative flex min-h-screen flex-col bg-[var(--bg)] text-[var(--text-primary)]'>
<div className='light desktop-title-bar-page relative flex flex-col bg-[var(--bg)] text-[var(--text-primary)]'>
<DesktopTitleBarLane />
<header>
<nav className='mx-auto flex w-full max-w-[1460px] items-center px-20 py-4 max-sm:px-5 max-lg:px-8'>
<Link href='/' aria-label='Sim home' className='flex h-[30px] items-center'>
Expand Down
86 changes: 86 additions & 0 deletions apps/sim/app/_shell/desktop-title-bar-controller.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* @vitest-environment jsdom
*/
import { act } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

const { mockGetDesktopBridge } = vi.hoisted(() => ({ mockGetDesktopBridge: vi.fn() }))

vi.mock('@/lib/desktop', () => ({ getDesktopBridge: mockGetDesktopBridge }))

import {
DESKTOP_TITLE_BAR_ATTRIBUTE,
DesktopTitleBarController,
} from '@/app/_shell/desktop-title-bar'

let container: HTMLDivElement
let root: Root

/**
* A bridge whose `getState` never settles, which is the window this guards: the gap
* between mount and the async state arriving is exactly when the old code clobbered
* whatever mode another owner had already established.
*/
function pendingBridge() {
return {
windowState: {
onStateChange: vi.fn(() => vi.fn()),
getState: vi.fn(() => new Promise<never>(() => {})),
},
}
}

function mount() {
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
container = document.createElement('div')
document.body.appendChild(container)
root = createRoot(container)
act(() => root.render(<DesktopTitleBarController />))
}

beforeEach(() => {
vi.clearAllMocks()
document.documentElement.removeAttribute(DESKTOP_TITLE_BAR_ATTRIBUTE)
Object.defineProperty(navigator, 'userAgent', {
value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)',
configurable: true,
})
})

afterEach(() => {
act(() => root.unmount())
container.remove()
document.documentElement.removeAttribute(DESKTOP_TITLE_BAR_ATTRIBUTE)
})

describe('DesktopTitleBarController', () => {
it('leaves an established mode alone while its own state is still pending', () => {
mockGetDesktopBridge.mockReturnValue(pendingBridge())
// Native fullscreen, set by WorkspaceChrome. A lane-aware overlay mounting here used
// to seed `inset` first, snapping the traffic-light lane back on and jumping the
// content until the async state corrected it — or permanently, if `getState` rejected.
document.documentElement.setAttribute(DESKTOP_TITLE_BAR_ATTRIBUTE, 'fullscreen')

mount()

expect(document.documentElement.getAttribute(DESKTOP_TITLE_BAR_ATTRIBUTE)).toBe('fullscreen')
})

it('seeds inset when no owner has set a mode yet', () => {
mockGetDesktopBridge.mockReturnValue(pendingBridge())

mount()

expect(document.documentElement.getAttribute(DESKTOP_TITLE_BAR_ATTRIBUTE)).toBe('inset')
})

it('clears the marker off the desktop shell', () => {
mockGetDesktopBridge.mockReturnValue(null)
document.documentElement.setAttribute(DESKTOP_TITLE_BAR_ATTRIBUTE, 'inset')

mount()

expect(document.documentElement.hasAttribute(DESKTOP_TITLE_BAR_ATTRIBUTE)).toBe(false)
})
})
Loading
Loading