Skip to content

Commit 06faa07

Browse files
improvement(auth): unify sign-in and access pages (#9461)
* feat(auth): implement isolated Paper design system * chore(auth): remove design-tool attribution * fix(auth): preserve navigation and desktop layout in redesign * fix(auth): consolidate fields and localize icons * fix(auth): resolve review findings and complete layout isolation * fix(auth): preserve invitation loading and branding boundaries --------- Co-authored-by: Waleed Latif <walif6@gmail.com>
1 parent d2f09ff commit 06faa07

120 files changed

Lines changed: 6895 additions & 1918 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/desktop/e2e/smoke.spec.ts‎

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -178,23 +178,20 @@ test.describe('desktop shell smoke', () => {
178178
const window = await app.firstWindow()
179179
await window.waitForSelector('#retry', { timeout: 30_000 })
180180
expect(window.url()).toMatch(/^sim-shell:\/\/pages\/offline\.html\?/)
181-
await expect(window.getByRole('img', { name: 'Sim', exact: true })).toBeVisible()
182-
await expect(window.getByRole('img', { name: 'Sim', exact: true })).toHaveAttribute(
183-
'aria-label',
184-
'Sim'
185-
)
181+
const home = window.getByRole('link', { name: 'Home', exact: true })
182+
await expect(home).toBeVisible()
183+
await expect(home).toHaveAttribute('href', 'https://sim.ai')
186184
await expect(window.locator('#title')).toHaveText('Can’t connect to Sim')
187-
// The recovery path for a self-hosted shell pointed at a server it cannot
188-
// reach. Exercised end to end here because it is the only coverage of the
189-
// `server:` local-page IPC gate: the bundled page reads the configuration
190-
// over the real preload bridge, and status.sim.ai is withheld because this
191-
// origin is not one of Sim's own. `toBeHidden` is load-bearing — the page's
192-
// own `button { display: inline-flex }` outranks the UA `[hidden]` rule, so
193-
// the attribute alone does not hide it.
185+
// The real preload bridge must retain server recovery while hiding cloud-only status.
194186
await expect(window.locator('#server')).toBeVisible()
195187
await expect(window.locator('#status')).toBeHidden()
196188
await expect
197-
.poll(() => window.evaluate(() => document.fonts.check('16px "Season Sans"')))
189+
.poll(() =>
190+
window.evaluate(async () => {
191+
const fonts = await document.fonts.load('14px "Sim Auth Geist"')
192+
return fonts.length > 0 && fonts.every((font) => font.status === 'loaded')
193+
})
194+
)
198195
.toBe(true)
199196
await expect(window.locator('#detail')).toHaveAttribute('role', 'status')
200197
})
@@ -205,18 +202,25 @@ test.describe('desktop shell smoke', () => {
205202
const window = await app.firstWindow()
206203
await expect(window.locator('#server')).toBeVisible()
207204
await expect(window.locator('html')).toHaveAttribute('data-sim-desktop-title-bar', 'inset')
205+
const titlebar = window.locator('[data-auth-titlebar]')
206+
await expect
207+
.poll(() => titlebar.evaluate((element) => element.getBoundingClientRect().height))
208+
.toBeGreaterThanOrEqual(38)
208209
await app.evaluate(({ BrowserWindow }) => {
209210
BrowserWindow.getAllWindows()[0].setFullScreen(true)
210211
})
211212
await expect(window.locator('html')).toHaveAttribute('data-sim-desktop-title-bar', 'fullscreen')
212213
await window.reload()
213214
await expect(window.locator('#server')).toBeVisible()
214215
await expect(window.locator('html')).toHaveAttribute('data-sim-desktop-title-bar', 'fullscreen')
215-
await expect(window.locator('.desktop-title-bar-page')).toHaveCSS('padding-top', '0px')
216+
await expect(titlebar).toHaveCSS('height', '0px')
216217
await app.evaluate(({ BrowserWindow }) => {
217218
BrowserWindow.getAllWindows()[0].setFullScreen(false)
218219
})
219220
await expect(window.locator('html')).toHaveAttribute('data-sim-desktop-title-bar', 'inset')
221+
await expect
222+
.poll(() => titlebar.evaluate((element) => element.getBoundingClientRect().height))
223+
.toBeGreaterThanOrEqual(38)
220224
})
221225

222226
test('bundled dialogs follow the app theme independently of the system theme', async () => {
@@ -310,7 +314,7 @@ test.describe('desktop shell smoke', () => {
310314
// Its page, the pre-filled value (which crosses the local-page IPC gate) and
311315
// Escape are asserted together because the packaged build once opened it as
312316
// a blank sheet with no way out.
313-
test('the offline server picker renders EMCN controls and handles validation and dismissal', async () => {
317+
test('the offline server picker handles validation and dismissal', async () => {
314318
const testInfo = test.info()
315319
app = await launchApp('http://127.0.0.1:1')
316320
const window = await app.firstWindow()
@@ -333,17 +337,21 @@ test.describe('desktop shell smoke', () => {
333337
.toBe(true)
334338
await picker.getByLabel('Server URL').fill('http://example.com')
335339
await picker.getByLabel('Server URL').press('Enter')
336-
await expect(picker.getByRole('alert')).toBeVisible()
340+
await expect(picker.locator('#server-origin-errors')).toBeVisible()
341+
await expect(picker.getByLabel('Server URL')).toHaveAccessibleDescription(
342+
/Server URL must use HTTPS/
343+
)
337344
await expect(picker.getByLabel('Server URL')).toHaveAttribute('aria-invalid', 'true')
338345
await picker.getByLabel('Server URL').fill('http://127.0.0.1:1')
339-
await expect(picker.getByRole('alert')).toHaveCount(0)
346+
await expect(picker.locator('#server-origin-errors')).toHaveCount(0)
347+
await expect(picker.getByLabel('Server URL')).not.toHaveAttribute('aria-invalid', 'true')
340348
await picker.getByRole('button', { name: 'Connect', exact: true }).click()
341349
await expect(picker.getByRole('status')).toHaveText('Already connected to this server.')
342350
await expect
343351
.poll(() =>
344352
picker
345-
.locator('[data-chip-modal-body]')
346-
.evaluate((element) => element.scrollHeight <= element.clientHeight)
353+
.getByRole('dialog')
354+
.evaluate((element) => element.scrollHeight <= globalThis.innerHeight)
347355
)
348356
.toBe(true)
349357
await picker.emulateMedia({ colorScheme: 'light' })
@@ -352,7 +360,7 @@ test.describe('desktop shell smoke', () => {
352360
animations: 'disabled',
353361
})
354362
await picker.emulateMedia({ colorScheme: 'dark' })
355-
await expect(picker.locator('html')).toHaveClass('dark')
363+
await expect(picker.locator('[data-auth-root]')).toHaveCSS('color-scheme', 'light')
356364
await picker.screenshot({
357365
path: testInfo.outputPath('server-modal-dark.png'),
358366
animations: 'disabled',

‎apps/desktop/package.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@
4343
"electron-updater": "6.8.9",
4444
"micromatch": "4.0.8",
4545
"safe-regex2": "5.1.2",
46-
"pdf-lib": "1.17.1"
46+
"pdf-lib": "1.17.1",
47+
"@sim/auth-ui": "workspace:*"
4748
},
4849
"devDependencies": {
4950
"@electron/fuses": "1.8.0",

‎apps/desktop/scripts/build.ts‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,17 @@ const renderer: BuildOptions = {
108108
{
109109
name: 'desktop-tailwind',
110110
setup(builder) {
111-
builder.onLoad({ filter: /shell\.css$/ }, async ({ path }) => {
111+
builder.onLoad({ filter: /(?:shell|utilities|styles\.module)\.css$/ }, async ({ path }) => {
112112
const config = await loadPostcssConfig({}, resolve('../sim'))
113113
const result = await postcss(config.plugins).process(readFileSync(path, 'utf8'), {
114114
from: path,
115115
})
116116
return {
117-
contents: result.css,
118-
loader: 'css',
117+
contents: result.css.replace(
118+
/url\(["']?\.\/assets\/(geist[^)"']+)["']?\)/g,
119+
'url(./$1)'
120+
),
121+
loader: path.endsWith('.module.css') ? 'local-css' : 'css',
119122
resolveDir: dirname(path),
120123
watchFiles: result.messages.flatMap((message) =>
121124
message.type === 'dependency' ? [message.file as string] : []
@@ -130,6 +133,10 @@ const renderer: BuildOptions = {
130133
async function run(): Promise<void> {
131134
execFileSync(process.execPath, ['run', 'scripts/build-native.ts'], { stdio: 'inherit' })
132135
compileComputerUse()
136+
mkdirSync('dist/renderer', { recursive: true })
137+
for (const font of ['geist-variable.woff2', 'geist-mono-variable.woff2']) {
138+
cpSync(resolve('../../packages/auth-ui/src/assets', font), join('dist/renderer', font))
139+
}
133140
if (watch) {
134141
const { context } = await import('esbuild')
135142
const rendererCtx = await context(renderer)

‎apps/desktop/src/main/local-pages.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ const LOCAL_PAGES: ReadonlySet<string> = new Set<LocalPage>(['offline.html', 'se
3737
const SERVABLE_FILES: ReadonlySet<string> = new Set([
3838
...LOCAL_PAGES,
3939
'SeasonSansUprightsVF.woff2',
40+
'geist-variable.woff2',
41+
'geist-mono-variable.woff2',
4042
'server.js',
4143
'server.css',
4244
'offline.js',

‎apps/desktop/src/renderer/auth.css‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
html,
2+
body {
3+
margin: 0;
4+
padding: 0;
5+
background: white;
6+
color-scheme: light;
7+
}
8+
9+
html {
10+
--desktop-title-bar-height: 0px;
11+
}
12+
13+
html[data-sim-desktop-title-bar="inset"] {
14+
--desktop-title-bar-height: max(env(titlebar-area-height, 38px), 38px);
15+
}

‎apps/desktop/src/renderer/offline/index.tsx‎

Lines changed: 50 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
import { useState } from 'react'
2+
import {
3+
AuthRoot,
4+
AuthShellView,
5+
AuthTextLink,
6+
AuthWordmark,
7+
AuthButton as Chip,
8+
AuthStatusContent as StatusPageContent,
9+
} from '@sim/auth-ui'
210
import { observeDesktopTitleBar, type SimDesktopApi } from '@sim/desktop-bridge'
3-
import { Chip, LogoPage, SimWordmark, StatusPageContent } from '@sim/emcn'
411
import { createRoot } from 'react-dom/client'
5-
import { initializeShellPage } from '@/renderer/shell'
6-
import '@/renderer/shell.css'
12+
import '@/renderer/auth.css'
713

814
const ERROR_COPY = {
915
offline: {
@@ -57,51 +63,55 @@ function OfflinePage({ isSimCloud }: OfflinePageProps) {
5763
}
5864

5965
return (
60-
<LogoPage
61-
center
62-
className='desktop-title-bar-page'
63-
titleBar={
64-
<div aria-hidden className='desktop-login-window-drag-region desktop-window-drag-region' />
65-
}
66-
logo={
67-
<span role='img' aria-label='Sim' className='flex h-[30px] items-center'>
68-
<SimWordmark />
69-
</span>
70-
}
71-
>
72-
<StatusPageContent
73-
titleId='title'
74-
title={copy.title}
75-
description={copy.message}
76-
detail={
77-
<p
78-
id='detail'
79-
role='status'
80-
className='max-w-full break-words text-[var(--text-muted)] text-caption'
66+
<AuthRoot>
67+
<AuthShellView
68+
homeHref='https://sim.ai'
69+
logo={<AuthWordmark />}
70+
titlebar={<div data-auth-titlebar='' aria-hidden='true' />}
71+
footer={
72+
<AuthTextLink
73+
onClick={() => {
74+
void bridge?.openExternal('mailto:support@sim.ai')
75+
}}
8176
>
82-
{actionError || detail}
83-
</p>
77+
Contact support
78+
</AuthTextLink>
8479
}
8580
>
86-
<Chip id='retry' variant='primary' onClick={() => bridge?.offlineRetry()}>
87-
Retry
88-
</Chip>
89-
{isSimCloud ? (
90-
<Chip id='status' onClick={checkStatus}>
91-
Check status
81+
<StatusPageContent
82+
titleId='title'
83+
title={copy.title}
84+
description={copy.message}
85+
detail={
86+
<p
87+
id='detail'
88+
role='status'
89+
className='au:max-w-full au:break-words au:text-[var(--auth-secondary)] au:text-caption'
90+
>
91+
{actionError || detail}
92+
</p>
93+
}
94+
>
95+
<Chip fullWidth id='retry' variant='primary' onClick={() => bridge?.offlineRetry()}>
96+
Retry
9297
</Chip>
93-
) : null}
94-
<Chip id='server' onClick={() => bridge?.server?.open()}>
95-
Change server
96-
</Chip>
97-
</StatusPageContent>
98-
</LogoPage>
98+
{isSimCloud ? (
99+
<Chip fullWidth id='status' onClick={checkStatus}>
100+
Check status
101+
</Chip>
102+
) : null}
103+
<Chip fullWidth variant='ghost' id='server' onClick={() => bridge?.server?.open()}>
104+
Change server
105+
</Chip>
106+
</StatusPageContent>
107+
</AuthShellView>
108+
</AuthRoot>
99109
)
100110
}
101111

102112
const container = document.getElementById('root')
103113
if (!container) throw new Error('Offline page root is missing')
104-
void initializeShellPage().then(() => {
114+
{
105115
observeDesktopTitleBar(document.documentElement, navigator.userAgent, bridge)
106116
const root = createRoot(container)
107117
root.render(<OfflinePage isSimCloud={false} />)
@@ -111,4 +121,4 @@ void initializeShellPage().then(() => {
111121
root.render(<OfflinePage isSimCloud={isSimCloud} />)
112122
})
113123
.catch(() => {})
114-
})
124+
}

‎apps/desktop/src/renderer/server/index.tsx‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { createRoot } from 'react-dom/client'
22
import { ServerModal } from '@/renderer/server/server-modal'
3-
import { initializeShellPage, shellWindow } from '@/renderer/shell'
4-
import '@/renderer/shell.css'
3+
import type { ShellWindowApi } from '@/shared/shell'
4+
5+
const shellWindow = (window as Window & { simShell?: ShellWindowApi }).simShell
6+
import '@/renderer/auth.css'
57

68
const container = document.getElementById('root')
79
if (!container || !shellWindow) throw new Error('Server modal host is unavailable')
@@ -17,4 +19,4 @@ async function renderServerModal() {
1719
}
1820
}
1921

20-
void initializeShellPage().then(renderServerModal)
22+
void renderServerModal()

0 commit comments

Comments
 (0)