Skip to content

Commit 8397dfa

Browse files
committed
fix(desktop): clear the traffic lights on every auth-shell surface
Only /login reserved the macOS traffic-light lane, so signup drew its logo underneath the lights — and so did reset-password, sso, verify, the CLI auth handoff, and the invite pages. The pre-paint script marks the lane on every desktop route, so any surface that did not reserve it overlapped. Ownership moves to `AuthShell`, which is the single source of truth for the frame all of these wear, and it now reserves unconditionally. Per-route gating was the wrong shape rather than merely incomplete: `/invite/[id]` is a dynamic segment, so no route list could have covered it. `supportsDesktopTitleBar` therefore drops its pathname argument — the caller mounting the controller is the signal, and only `AuthShell` mounts it. Workspace routes never render it and keep their existing `WorkspaceChrome`-owned listener, so the two never contend for the attribute. Off the desktop shell `--desktop-title-bar-height` is `0px`, so the reservation and the drag strip collapse to nothing and `.desktop-title-bar-page` is exactly the `min-h-screen` these surfaces had before — web is unchanged. With the prop gone the client auth layout was a bare passthrough, so the route layout renders the shell directly and the passthrough is deleted. Measured in the Electron renderer over CDP across /signup, /login, /reset-password and /cli/auth: lane 40px, logo top 56px, zero overflow on each. /invite/[id] redirects to login when signed out and was not measured directly. The surface audit gains a `stripComments` helper that every negative assertion runs through. These files document the shapes they avoid, so a bare `not.toContain` was matching the prose explaining the fix and failing on correct code.
1 parent 7798e83 commit 8397dfa

6 files changed

Lines changed: 63 additions & 66 deletions

File tree

apps/sim/app/(auth)/auth-layout-client.tsx

Lines changed: 0 additions & 16 deletions
This file was deleted.

apps/sim/app/(auth)/components/auth-shell.tsx

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import type { ReactNode } from 'react'
2-
import { cn } from '@sim/emcn'
32
import Link from 'next/link'
3+
import { DesktopTitleBarController } from '@/app/_shell/desktop-title-bar'
44
import { LogoMark, SimWordmark } from '@/app/(landing)/components/navbar/components'
55

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

1513
/**
@@ -21,18 +19,20 @@ interface AuthShellProps {
2119
* the canvas/`--text-primary` surface, and renders a logo-only header that reuses
2220
* the landing {@link LogoMark} + {@link SimWordmark} at the same nav gutters. The
2321
* single content column is centered and capped for a calm single-form layout.
22+
*
23+
* The shell also owns the macOS traffic-light lane, unconditionally — every surface that
24+
* wears it (the `(auth)` routes, the CLI auth handoff, the invite pages) sits outside
25+
* workspace chrome and draws its logo where the lights are. Gating this per route left
26+
* whichever surface was overlooked drawing underneath them, and a route list could not
27+
* cover a dynamic segment like `/invite/[id]` anyway. Off the desktop shell
28+
* `--desktop-title-bar-height` is `0px`, so the reservation and the drag strip both
29+
* collapse to nothing and `.desktop-title-bar-page` is exactly `min-h-screen`.
2430
*/
25-
export function AuthShell({ children, footer, reserveDesktopTitleBar = false }: AuthShellProps) {
31+
export function AuthShell({ children, footer }: AuthShellProps) {
2632
return (
27-
<div
28-
className={cn(
29-
'light relative flex flex-col bg-[var(--bg)] text-[var(--text-primary)]',
30-
reserveDesktopTitleBar ? 'desktop-title-bar-page' : 'min-h-screen'
31-
)}
32-
>
33-
{reserveDesktopTitleBar && (
34-
<div aria-hidden className='desktop-login-window-drag-region desktop-window-drag-region' />
35-
)}
33+
<div className='light desktop-title-bar-page relative flex flex-col bg-[var(--bg)] text-[var(--text-primary)]'>
34+
<DesktopTitleBarController />
35+
<div aria-hidden className='desktop-login-window-drag-region desktop-window-drag-region' />
3636
<header>
3737
<nav className='mx-auto flex w-full max-w-[1446px] items-center px-12 py-4 max-sm:px-5 max-lg:px-8'>
3838
<Link href='/' aria-label='Sim home' className='flex h-[30px] items-center'>

apps/sim/app/(auth)/layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import type { Metadata } from 'next'
2-
import AuthLayoutClient from '@/app/(auth)/auth-layout-client'
2+
import { AuthShell } from '@/app/(auth)/components'
33

44
export const metadata: Metadata = {
55
robots: { index: false, follow: false },
66
}
77

88
export default function AuthLayout({ children }: { children: React.ReactNode }) {
9-
return <AuthLayoutClient>{children}</AuthLayoutClient>
9+
return <AuthShell>{children}</AuthShell>
1010
}

apps/sim/app/_shell/desktop-title-bar-surfaces.test.ts

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,18 @@ import { describe, expect, it } from 'vitest'
77
/** Anchored to this file, not `process.cwd()`, which only resolves from `apps/sim`. */
88
const read = (relativePath: string) => readFileSync(new URL(relativePath, import.meta.url), 'utf8')
99

10-
const authLayout = read('../(auth)/auth-layout-client.tsx')
10+
/**
11+
* Source with comments removed.
12+
*
13+
* Every negative assertion here must run through this. These files document the shapes
14+
* they deliberately avoid, so a bare `not.toContain('min-h-screen')` matches the prose
15+
* explaining why `min-h-screen` is gone and fails on correct code. The mirror case is
16+
* worse: prose containing a wanted token makes a positive assertion pass on broken code.
17+
*/
18+
const stripComments = (source: string) =>
19+
source.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '')
20+
21+
const authLayout = read('../(auth)/layout.tsx')
1122
const authShell = read('../(auth)/components/auth-shell.tsx')
1223
const workspaceChrome = read(
1324
'../workspace/[workspaceId]/components/workspace-chrome/workspace-chrome.tsx'
@@ -23,12 +34,16 @@ const mothershipView = read(
2334
)
2435

2536
describe('desktop title-bar surface audit', () => {
26-
it('applies the safe-area shell only when the auth route is login', () => {
27-
expect(authLayout).toContain("usePathname() === '/login'")
28-
expect(authLayout).toContain('reserveDesktopTitleBar={isLogin}')
29-
expect(authShell).toContain(
30-
"reserveDesktopTitleBar ? 'desktop-title-bar-page' : 'min-h-screen'"
31-
)
37+
it('reserves the lane for every surface wearing the auth shell', () => {
38+
// Signup, reset-password, sso, verify, the CLI handoff and the invite pages all wear
39+
// this shell and sit under the same traffic lights. Reserving only on /login left the
40+
// rest drawing their logo beneath them; per-route gating could not cover
41+
// `/invite/[id]` either, so the shell owns the lane unconditionally.
42+
expect(authShell).toContain('desktop-title-bar-page')
43+
expect(authShell).toContain('<DesktopTitleBarController />')
44+
expect(stripComments(authShell)).not.toContain('reserveDesktopTitleBar')
45+
expect(stripComments(authShell)).not.toContain('min-h-screen')
46+
expect(authLayout).toContain('<AuthShell>')
3247
})
3348

3449
it('mounts a real drag surface across login and workspace title-bar lanes', () => {
@@ -93,20 +108,15 @@ describe('desktop title-bar surface audit', () => {
93108
// tall empty slab over the content. It now hugs its content and caps at the pane
94109
// height less the lane and the bottom gutter, so a long list still scrolls.
95110
expect(workspaceChrome).toContain('max-h-[calc(100%-var(--desktop-title-bar-height)-8px)]')
96-
expect(workspaceChrome).not.toMatch(/PEEK_CARD_CHROME[\s\S]{0,240}?bottom-2/)
111+
expect(stripComments(workspaceChrome)).not.toMatch(/PEEK_CARD_CHROME[\s\S]{0,240}?bottom-2/)
97112
})
98113

99114
it('reserves the login lane inside the box, never as a collapsing margin', () => {
100115
// `body` carries `min-height: 100vh`, and a `margin-top` here collapses through it
101116
// (body is a plain block box, so it opens no BFC) and displaces body itself. The
102117
// document then measured one full lane taller than the viewport, which is what made
103118
// the desktop login page scroll. Verified live: 40px of overflow, now 0.
104-
// Comments are stripped first: the rule documents why `margin-top` is wrong, and a
105-
// raw `not.toContain` would match that prose instead of a declaration.
106-
const rule = (globalStyles.match(/\.desktop-title-bar-page \{[^}]*\}/)?.[0] ?? '').replace(
107-
/\/\*[\s\S]*?\*\//g,
108-
''
109-
)
119+
const rule = stripComments(globalStyles.match(/\.desktop-title-bar-page \{[^}]*\}/)?.[0] ?? '')
110120
expect(rule).toContain('padding-top: var(--desktop-title-bar-height)')
111121
expect(rule).toContain('min-height: 100vh')
112122
expect(rule).not.toContain('margin-top')

apps/sim/app/_shell/desktop-title-bar.test.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@ import { describe, expect, it } from 'vitest'
55
import { applyDesktopTitleBarMode, supportsDesktopTitleBar } from '@/app/_shell/desktop-title-bar'
66

77
describe('desktop title bar', () => {
8-
it('reserves traffic-light space only for desktop login on macOS', () => {
9-
expect(supportsDesktopTitleBar('/login', 'Macintosh', true)).toBe(true)
10-
expect(supportsDesktopTitleBar('/workspace/ws/home', 'Macintosh', true)).toBe(false)
11-
expect(supportsDesktopTitleBar('/signup', 'Macintosh', true)).toBe(false)
12-
expect(supportsDesktopTitleBar('/desktop/connect', 'Macintosh', true)).toBe(false)
13-
expect(supportsDesktopTitleBar('/login', 'Windows NT 10.0', true)).toBe(false)
14-
expect(supportsDesktopTitleBar('/login', 'Macintosh', false)).toBe(false)
8+
it('reserves traffic-light space on macOS desktop, and nowhere else', () => {
9+
// No route check by design — mounting the controller is the signal, and only
10+
// `AuthShell` mounts it. A route list could not cover `/invite/[id]`.
11+
expect(supportsDesktopTitleBar('Macintosh', true)).toBe(true)
12+
expect(supportsDesktopTitleBar('Windows NT 10.0', true)).toBe(false)
13+
expect(supportsDesktopTitleBar('Macintosh', false)).toBe(false)
1514
})
1615

1716
it('sets, updates, and removes the shared document marker', () => {

apps/sim/app/_shell/desktop-title-bar.tsx

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
'use client'
22

33
import { useEffect } from 'react'
4-
import { usePathname } from 'next/navigation'
54
import { getDesktopBridge } from '@/lib/desktop'
65

76
export type DesktopTitleBarMode = 'fullscreen' | 'inset' | null
87

9-
/** Only the macOS desktop login route reserves space outside workspace chrome. */
10-
export function supportsDesktopTitleBar(
11-
pathname: string,
12-
userAgent: string,
13-
hasDesktopBridge: boolean
14-
): boolean {
15-
return hasDesktopBridge && /Mac/i.test(userAgent) && pathname === '/login'
8+
/**
9+
* Whether this surface reserves the macOS traffic-light lane itself.
10+
*
11+
* There is no route check: the caller mounting {@link DesktopTitleBarController} IS the
12+
* signal. Only `AuthShell` mounts it, and every surface wearing that shell — the `(auth)`
13+
* routes, the CLI auth handoff, the invite pages — sits outside workspace chrome and must
14+
* clear the lights. Workspace routes never render it; `WorkspaceChrome` owns the lane
15+
* there through its own listener, and two owners would fight over the attribute.
16+
*
17+
* A route list was the previous shape and could not survive `/invite/[id]`.
18+
*/
19+
export function supportsDesktopTitleBar(userAgent: string, hasDesktopBridge: boolean): boolean {
20+
return hasDesktopBridge && /Mac/i.test(userAgent)
1621
}
1722

1823
export function applyDesktopTitleBarMode(
@@ -27,16 +32,15 @@ export function applyDesktopTitleBarMode(
2732
}
2833

2934
/**
30-
* Keeps the macOS login inset correct across native fullscreen transitions.
31-
* Workspace routes retain their existing WorkspaceChrome-owned listener.
35+
* Keeps the inset correct across native fullscreen transitions, where the traffic lights
36+
* disappear and the lane must collapse. Rendered by `AuthShell`; workspace routes retain
37+
* their existing WorkspaceChrome-owned listener.
3238
*/
3339
export function DesktopTitleBarController() {
34-
const pathname = usePathname()
35-
3640
useEffect(() => {
3741
const bridge = getDesktopBridge()
3842
const root = document.documentElement
39-
if (!supportsDesktopTitleBar(pathname, navigator.userAgent, Boolean(bridge))) {
43+
if (!supportsDesktopTitleBar(navigator.userAgent, Boolean(bridge))) {
4044
applyDesktopTitleBarMode(root, null)
4145
return
4246
}
@@ -61,7 +65,7 @@ export function DesktopTitleBarController() {
6165
disposed = true
6266
unsubscribe()
6367
}
64-
}, [pathname])
68+
}, [])
6569

6670
return null
6771
}

0 commit comments

Comments
 (0)