From 77ec26b7fbc2601edbe4ceae5228ac9b6603aaed Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 18 Aug 2026 16:02:03 -0700 Subject: [PATCH 1/2] improvement(ui): align the two full-screen takeovers on one design-system layer Both the session-expired screen and the desktop minimum-version gate are full-screen takeovers, but they disagreed on every piece of chrome: an ad-hoc z-[9999] against a z-50 that sat below --z-dropdown, --bg against --surface-1, a legacy Button against a Chip, and muted grey on their failure copy. The z-index was a real bug, not just an inconsistency. At z-50 the session-expired takeover rendered underneath the desktop browser panel's replacement snapshot, which paints at calc(var(--z-modal) - 1). Adds --z-takeover to the z-scale as the layer above every popper, points both takeovers at it, and aligns their background, primary action, body-copy and error tokens. Also corrects the z-scale in the design-review skill, which listed --z-toast at 500 when it has long been 150. --- .agents/skills/emcn-design-review/SKILL.md | 2 +- apps/sim/app/_shell/desktop-update-gate.tsx | 14 +++++++------- apps/sim/app/_styles/globals.css | 3 +++ .../components/session-expired/session-expired.tsx | 11 ++++++++--- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/.agents/skills/emcn-design-review/SKILL.md b/.agents/skills/emcn-design-review/SKILL.md index 78253e5e772..c3a260606d4 100644 --- a/.agents/skills/emcn-design-review/SKILL.md +++ b/.agents/skills/emcn-design-review/SKILL.md @@ -39,7 +39,7 @@ Use CSS variable pattern (`text-[var(--text-primary)]`), never Tailwind semantic **Surfaces**: `--bg`, `--surface-1` through `--surface-7`, `--surface-hover`, `--surface-active` **Borders**: `--border`, `--border-1`, `--border-muted` **Brand/accent**: `--brand-secondary`, `--brand-accent` -**Z-Index**: `--z-dropdown` (100), `--z-modal` (200), `--z-popover` (300), `--z-tooltip` (400), `--z-toast` (500) +**Z-Index**: `--z-dropdown` (100), `--z-toast` (150), `--z-modal` (200), `--z-popover` (300), `--z-tooltip` (400), `--z-takeover` (500) **Shadows**: `shadow-subtle`, `shadow-medium`, `shadow-overlay`, `shadow-card` **Badges**: `--badge-*` semantic families (success/error/gray/blue/purple/orange/amber/teal/cyan/pink, each with `-bg`/`-text`) diff --git a/apps/sim/app/_shell/desktop-update-gate.tsx b/apps/sim/app/_shell/desktop-update-gate.tsx index f9c114cfc43..22df089c7ea 100644 --- a/apps/sim/app/_shell/desktop-update-gate.tsx +++ b/apps/sim/app/_shell/desktop-update-gate.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react' import type { DesktopUpdateState } from '@sim/desktop-bridge' -import { Button, useNativeSurfaceOcclusionReady } from '@sim/emcn' +import { Chip, useNativeSurfaceOcclusionReady } from '@sim/emcn' import { getDesktopBridge, getDesktopShellVersion, getDesktopUpdates } from '@/lib/desktop' import { isShellOutdated } from '@/lib/desktop/min-version' @@ -41,11 +41,11 @@ function gateActionFor(state: DesktopUpdateState): GateAction { } switch (state.status) { case 'checking': - return { label: 'Checking for updates...', disabled: true, onClick: () => {} } + return { label: 'Checking for updates…', disabled: true, onClick: () => {} } case 'downloading': return { label: - state.percent !== undefined ? `Downloading ${state.percent}%` : 'Downloading update...', + state.percent !== undefined ? `Downloading ${state.percent}%` : 'Downloading update…', disabled: true, onClick: () => {}, } @@ -97,7 +97,7 @@ export function DesktopUpdateGate() { return (
@@ -108,11 +108,11 @@ export function DesktopUpdateGate() { the update to keep going.

- + {updateState.status === 'error' && ( -

+

The update could not be downloaded. Check your connection and try again.

)} diff --git a/apps/sim/app/_styles/globals.css b/apps/sim/app/_styles/globals.css index ae53a936065..057dde4efef 100644 --- a/apps/sim/app/_styles/globals.css +++ b/apps/sim/app/_styles/globals.css @@ -58,6 +58,9 @@ --z-modal: 200; --z-popover: 300; --z-tooltip: 400; + /* Full-screen takeovers (session expired, minimum-shell-version gate) replace + the app entirely, so they sit above every other layer including poppers. */ + --z-takeover: 500; /* Shadow scale */ --shadow-subtle: 0 2px 4px 0 rgba(0, 0, 0, 0.08); diff --git a/apps/sim/app/workspace/[workspaceId]/components/session-expired/session-expired.tsx b/apps/sim/app/workspace/[workspaceId]/components/session-expired/session-expired.tsx index 03b8b416467..47afff00be6 100644 --- a/apps/sim/app/workspace/[workspaceId]/components/session-expired/session-expired.tsx +++ b/apps/sim/app/workspace/[workspaceId]/components/session-expired/session-expired.tsx @@ -1,7 +1,7 @@ 'use client' import { useCallback, useEffect, useRef, useState } from 'react' -import { Chip, useNativeSurfaceOcclusionReady } from '@sim/emcn' +import { Chip, cn, useNativeSurfaceOcclusionReady } from '@sim/emcn' import { useSession } from '@/lib/auth/auth-client' import { recoverFromStaleSession } from '@/lib/auth/stale-session-recovery' @@ -64,11 +64,16 @@ export function SessionExpired() { return (
-

+

{failed ? `${subject}, but signing out failed.` : `${subject}. Signing you out…`}

{failed && ( From c809b1dcefe38c6a15af3431dc27c1574f34bd43 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 18 Aug 2026 16:12:56 -0700 Subject: [PATCH 2/2] fix(ui): keep the shell gate above in-app takeovers and stop pre-paint click capture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tying both takeovers to one layer let document order decide which wins, and the desktop update gate mounts earlier than the session-expired screen, so the session overlay silently started covering it — reversing the precedence the old z-[9999] vs z-50 pair had. Names the precedence instead: --z-shell-gate sits above --z-takeover, because an incompatible shell invalidates everything the web app renders inside it. Both takeovers also hid their pre-paint state with opacity, which still hit-tests, so a full-viewport surface could swallow clicks before it painted. Switches them to the visibility toggle ModalContent already uses for the same handshake. --- .agents/skills/emcn-design-review/SKILL.md | 2 +- apps/sim/app/_shell/desktop-update-gate.tsx | 4 ++-- apps/sim/app/_styles/globals.css | 8 ++++++-- .../components/session-expired/session-expired.tsx | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.agents/skills/emcn-design-review/SKILL.md b/.agents/skills/emcn-design-review/SKILL.md index c3a260606d4..09a9932d4b1 100644 --- a/.agents/skills/emcn-design-review/SKILL.md +++ b/.agents/skills/emcn-design-review/SKILL.md @@ -39,7 +39,7 @@ Use CSS variable pattern (`text-[var(--text-primary)]`), never Tailwind semantic **Surfaces**: `--bg`, `--surface-1` through `--surface-7`, `--surface-hover`, `--surface-active` **Borders**: `--border`, `--border-1`, `--border-muted` **Brand/accent**: `--brand-secondary`, `--brand-accent` -**Z-Index**: `--z-dropdown` (100), `--z-toast` (150), `--z-modal` (200), `--z-popover` (300), `--z-tooltip` (400), `--z-takeover` (500) +**Z-Index**: `--z-dropdown` (100), `--z-toast` (150), `--z-modal` (200), `--z-popover` (300), `--z-tooltip` (400), `--z-takeover` (500), `--z-shell-gate` (600) **Shadows**: `shadow-subtle`, `shadow-medium`, `shadow-overlay`, `shadow-card` **Badges**: `--badge-*` semantic families (success/error/gray/blue/purple/orange/amber/teal/cyan/pink, each with `-bg`/`-text`) diff --git a/apps/sim/app/_shell/desktop-update-gate.tsx b/apps/sim/app/_shell/desktop-update-gate.tsx index 22df089c7ea..a48cc3b2de8 100644 --- a/apps/sim/app/_shell/desktop-update-gate.tsx +++ b/apps/sim/app/_shell/desktop-update-gate.tsx @@ -97,8 +97,8 @@ export function DesktopUpdateGate() { return (
diff --git a/apps/sim/app/_styles/globals.css b/apps/sim/app/_styles/globals.css index 057dde4efef..62cb18c353f 100644 --- a/apps/sim/app/_styles/globals.css +++ b/apps/sim/app/_styles/globals.css @@ -58,9 +58,13 @@ --z-modal: 200; --z-popover: 300; --z-tooltip: 400; - /* Full-screen takeovers (session expired, minimum-shell-version gate) replace - the app entirely, so they sit above every other layer including poppers. */ + /* Full-screen takeovers replace the app entirely, so they sit above every + other layer including poppers. The desktop shell gate is the outermost of + them: an incompatible shell invalidates everything the web app renders + inside it, so it outranks in-app takeovers rather than tying with them and + letting document order decide. */ --z-takeover: 500; + --z-shell-gate: 600; /* Shadow scale */ --shadow-subtle: 0 2px 4px 0 rgba(0, 0, 0, 0.08); diff --git a/apps/sim/app/workspace/[workspaceId]/components/session-expired/session-expired.tsx b/apps/sim/app/workspace/[workspaceId]/components/session-expired/session-expired.tsx index 47afff00be6..788951a6cd5 100644 --- a/apps/sim/app/workspace/[workspaceId]/components/session-expired/session-expired.tsx +++ b/apps/sim/app/workspace/[workspaceId]/components/session-expired/session-expired.tsx @@ -65,7 +65,7 @@ export function SessionExpired() { return (