Skip to content

Commit 5ca7c6f

Browse files
committed
fix(desktop): a fixed root escapes ancestor padding — chat drew under the lights
Cursor's sharpest catch, and the audit was actively hiding the bug rather than missing it. `position: fixed` resolves against the viewport, not the parent, so a lane-aware shell's `padding-top` never moves it. The chat surfaces sit inside `LogoShell` and still painted at viewport top, under the traffic lights, while the check reported them covered — and adding the correct reservation would then have tripped the nested-reservation check, so the audit pushed toward the wrong answer. Roots matching `fixed inset-0` no longer inherit coverage and are exempt from the doubled check. That reclassified seven surfaces, each decided on evidence: - chat, its loading boundary, the loading state and the voice interface are full-window roots at `z-[100]` with their own top chrome — all now reserve; - the file viewer wraps a full-bleed `<iframe>` whose content starts at viewport top — now reserves; - session-expired centres its content with nothing in the lane, and the search modal's `fixed inset-0` is only its scrim (the panel sits at `top-[15%]`) — both allowlisted with that reasoning rather than an assumption. Verified by reverting chat to a bare `fixed inset-0` inside `LogoShell`: it is flagged now and was called covered before.
1 parent 2413ec3 commit 5ca7c6f

6 files changed

Lines changed: 32 additions & 6 deletions

File tree

apps/sim/app/(interfaces)/chat/[identifier]/chat.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
AGENT_STREAM_PROTOCOL_HEADER,
99
AGENT_STREAM_PROTOCOL_V1,
1010
} from '@/lib/workflows/streaming/agent-stream-protocol'
11+
import { DesktopTitleBarLane } from '@/app/_shell/desktop-title-bar'
1112
import {
1213
ChatErrorState,
1314
ChatHeader,
@@ -440,7 +441,8 @@ export default function ChatClient({ identifier }: { identifier: string }) {
440441
}
441442

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

apps/sim/app/(interfaces)/chat/[identifier]/loading.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { Skeleton } from '@sim/emcn'
2+
import { DesktopTitleBarLane } from '@/app/_shell/desktop-title-bar'
23

34
export default function ChatLoading() {
45
return (
5-
<div className='light fixed inset-0 z-[100] flex flex-col bg-[var(--bg)] text-[var(--text-primary)]'>
6+
<div className='light desktop-title-bar-page fixed inset-0 z-[100] flex flex-col bg-[var(--bg)] text-[var(--text-primary)]'>
7+
<DesktopTitleBarLane />
68
<div className='border-[var(--border-1)] border-b px-4 py-3'>
79
<div className='mx-auto flex max-w-3xl items-center justify-between'>
810
<div className='flex items-center gap-[12px]'>

apps/sim/app/(interfaces)/chat/components/loading-state/loading-state.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { Skeleton } from '@sim/emcn'
2+
import { DesktopTitleBarLane } from '@/app/_shell/desktop-title-bar'
23

34
export function ChatLoadingState() {
45
return (
5-
<div className='light fixed inset-0 z-[100] flex flex-col bg-[var(--bg)]'>
6+
<div className='light desktop-title-bar-page fixed inset-0 z-[100] flex flex-col bg-[var(--bg)]'>
7+
<DesktopTitleBarLane />
68
<div className='flex flex-1 items-center justify-center px-4'>
79
<div className='w-full max-w-[410px]'>
810
<div className='flex flex-col items-center justify-center'>

apps/sim/app/(interfaces)/chat/components/voice-interface/voice-interface.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
MAX_CHAT_SESSION_MS,
1515
SAMPLE_RATE,
1616
} from '@/lib/speech/config'
17+
import { DesktopTitleBarLane } from '@/app/_shell/desktop-title-bar'
1718

1819
const ParticlesVisualization = dynamic(
1920
() =>
@@ -524,10 +525,11 @@ export function VoiceInterface({
524525
return (
525526
<div
526527
className={cn(
527-
'light fixed inset-0 z-[100] flex flex-col bg-[var(--bg)] text-[var(--text-primary)]',
528+
'light desktop-title-bar-page fixed inset-0 z-[100] flex flex-col bg-[var(--bg)] text-[var(--text-primary)]',
528529
className
529530
)}
530531
>
532+
<DesktopTitleBarLane />
531533
<div className='flex flex-1 flex-col items-center justify-center px-8'>
532534
<div className='relative mb-16'>
533535
<ParticlesVisualization

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,10 @@ const LANE_EXEMPT: Record<string, string> = {
193193
'Marketing chrome. Verified: every consumer lives under app/(landing)/, and the desktop shell boots to /login or a workspace with no path to those routes.',
194194
'app/playground/page.tsx':
195195
'Verified dev-only: the page calls notFound() unless NEXT_PUBLIC_ENABLE_PLAYGROUND is set.',
196+
'app/workspace/[workspaceId]/components/session-expired/session-expired.tsx':
197+
'Centres its content (`items-center justify-center`) with no chrome in the lane, and is a transient sign-out notice.',
198+
'app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/search-modal.tsx':
199+
'The `fixed inset-0` here is the scrim, which carries no content. The panel itself is separately positioned at `top-[15%]`, well clear of the lane.',
196200
'app/_shell/desktop-update-gate.tsx':
197201
'Blocking overlay that centres its content, with no chrome in the lane. The lights stay usable regardless: under `titleBarStyle: hiddenInset` macOS draws them above the web contents, so web UI cannot cover them — this bug class is app chrome sitting *under* the lights, not the reverse.',
198202
'app/workspace/[workspaceId]/w/[workflowId]/components/error/index.tsx':
@@ -214,6 +218,16 @@ const LANE_EXEMPT: Record<string, string> = {
214218
* surface this PR converted lost its `min-h-screen` token and dropped out of the check
215219
* entirely, leaving the files most likely to regress unwatched.
216220
*/
221+
/**
222+
* A root positioned against the viewport rather than its parent.
223+
*
224+
* `position: fixed` ignores an ancestor's `padding-top`, so a lane-aware shell does NOT
225+
* cover it — the chat surfaces sat inside `LogoShell` and still painted at viewport top,
226+
* under the lights, while this check called them covered. Such a root must reserve for
227+
* itself, and reserving must not then read as a double reservation.
228+
*/
229+
const ESCAPES_ANCESTOR_PADDING = /fixed inset-0/
230+
217231
const FILLS_VIEWPORT = /\b(min-h-screen|h-screen)\b|fixed inset-0|desktop-title-bar-page/
218232

219233
const LANE_AWARE_SHELL_USAGE = /<(AuthShell|LogoShell|WorkspaceChrome|InterfacesShell)\b/
@@ -290,9 +304,11 @@ function viewportRoots() {
290304
file,
291305
fillsViewport: FILLS_VIEWPORT.test(source),
292306
self: reservesLane(source),
307+
escapesPadding: ESCAPES_ANCESTOR_PADDING.test(source),
293308
inherited:
294309
!SELF_RESERVE_REQUIRED.has(file) &&
295310
!DEFINES_LANE_SHELL.test(source) &&
311+
!ESCAPES_ANCESTOR_PADDING.test(source) &&
296312
ancestorLayouts(file).some((l) => reservesLane(sourceOf(l))),
297313
}
298314
})
@@ -316,7 +332,7 @@ describe('desktop traffic-light lane coverage', () => {
316332
// drag strips and two controllers. Shipped exactly that on the resume loading skeleton,
317333
// and the file-local check could not see it.
318334
const doubled = viewportRoots()
319-
.filter((r) => r.self && r.inherited)
335+
.filter((r) => r.self && r.inherited && !r.escapesPadding)
320336
.map((r) => r.file)
321337

322338
expect(doubled).toEqual([])

apps/sim/app/workspace/[workspaceId]/files/[fileId]/view/file-viewer.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { createLogger } from '@sim/logger'
44
import { useParams } from 'next/navigation'
5+
import { DesktopTitleBarLane } from '@/app/_shell/desktop-title-bar'
56
import { useWorkspaceFileRecord } from '@/hooks/queries/workspace-files'
67

78
const logger = createLogger('FileViewer')
@@ -20,7 +21,8 @@ export function FileViewer() {
2021
const serveUrl = `/api/files/serve/${encodeURIComponent(file.key)}?context=workspace&t=${file.size}`
2122

2223
return (
23-
<div className='fixed inset-0 z-50 bg-[var(--bg)]'>
24+
<div className='desktop-title-bar-page fixed inset-0 z-50 bg-[var(--bg)]'>
25+
<DesktopTitleBarLane />
2426
<iframe
2527
src={serveUrl}
2628
className='h-full w-full border-0'

0 commit comments

Comments
 (0)