Skip to content

Commit e105e07

Browse files
authored
v0.9.3: home page alignment, confluence search, dead feature flag removals, library additions
2 parents bcb348f + 6d77ae1 commit e105e07

22 files changed

Lines changed: 1016 additions & 525 deletions

File tree

‎apps/desktop/e2e/browser-chrome.spec.ts‎

Lines changed: 133 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ mountBrowserChromeFixture(useBrowserPanelOcclusion);`,
5555
response.end(
5656
path.endsWith('.js')
5757
? bundle.outputFiles.find((file) => file.path.endsWith('.js'))?.text
58-
: css.css
58+
: `${css.css}\n${bundle.outputFiles.find((file) => file.path.endsWith('.css'))?.text ?? ''}`
5959
)
6060
return
6161
}
@@ -75,7 +75,7 @@ mountBrowserChromeFixture(useBrowserPanelOcclusion);`,
7575
response.end(
7676
path === '/page'
7777
? '<!doctype html><html><body style="background:#192b40;color:white;font:24px system-ui;padding:25px"><h1>Browser fixture</h1><p>A live page behind the application chrome.</p><button>Page action</button></body></html>'
78-
: '<!doctype html><html class="dark"><head><link rel="stylesheet" href="/fixture.css"></head><body style="margin:0;background:#191919;color:#eee"><div id="root"></div><script src="/fixture.js"></script></body></html>'
78+
: '<!doctype html><html class="dark"><head><link rel="stylesheet" href="/fixture.css"></head><body style="margin:0;background:var(--bg);color:var(--text-primary)"><div id="root"></div><script src="/fixture.js"></script></body></html>'
7979
)
8080
})
8181
await new Promise<void>((resolve) => server?.listen(0, resolve))
@@ -116,31 +116,150 @@ mountBrowserChromeFixture(useBrowserPanelOcclusion);`,
116116
return { width: bounds.width, right: bounds.right }
117117
})
118118
)
119-
expect(geometry.every((tab) => tab.width >= 64 && tab.width < 160)).toBe(true)
119+
expect(geometry.every((tab) => tab.width < 160)).toBe(true)
120120
expect(geometry.at(-1)?.right).toBeLessThan(1070)
121121
await page.screenshot({ path: testInfo.outputPath('tabs.png') })
122122
})
123-
await test.step('Short labels keep their compact intrinsic width', async () => {
124-
await page.locator('#short-tabs').click()
125-
const widths = await page
126-
.locator('[data-tab-strip-item]')
127-
.evaluateAll((tabs) => tabs.map((tab) => tab.getBoundingClientRect().width))
128-
expect(widths.every((width) => width >= 64 && width < 96)).toBe(true)
129-
await page.locator('#eight-tabs').click()
130-
})
131-
await test.step('Crowded tabs preserve controls and scroll', async () => {
123+
await test.step('Crowded tabs keep readable labels when selected, hovered, and focused', async () => {
132124
await page.locator('#many-tabs').click()
133125
await expect(page.locator('[data-tab-strip-item]')).toHaveCount(18)
134126
const overflow = await page
135127
.locator('[data-tab-strip-item]')
136128
.first()
137129
.evaluate((tab) => ({
138-
width: tab.getBoundingClientRect().width,
139130
scrollWidth: tab.parentElement?.scrollWidth ?? 0,
140131
clientWidth: tab.parentElement?.clientWidth ?? 0,
141132
}))
142-
expect(overflow.width).toBeGreaterThanOrEqual(64)
143133
expect(overflow.scrollWidth).toBeGreaterThan(overflow.clientWidth)
134+
const active = page.getByRole('tab', { selected: true })
135+
const label = active.locator('[data-overflow-text]')
136+
await expect
137+
.poll(() => label.evaluate((element) => element.getBoundingClientRect().width))
138+
.toBeGreaterThanOrEqual(48)
139+
const neighbor = page.getByRole('tab').nth(2)
140+
const beforeHover = await neighbor.evaluate((element: HTMLElement) => ({
141+
left: element.offsetLeft,
142+
width: element.offsetWidth,
143+
}))
144+
await neighbor.hover()
145+
await expect
146+
.poll(() =>
147+
neighbor
148+
.locator('[data-overflow-text]')
149+
.evaluate((element) => element.getBoundingClientRect().width)
150+
)
151+
.toBeGreaterThanOrEqual(48)
152+
expect(
153+
await neighbor.evaluate((element: HTMLElement) => ({
154+
left: element.offsetLeft,
155+
width: element.offsetWidth,
156+
}))
157+
).toEqual(beforeHover)
158+
await neighbor.click()
159+
await expect(neighbor).toHaveAttribute('aria-selected', 'true')
160+
await page.keyboard.press('End')
161+
const last = page.getByRole('tab').last()
162+
await expect(last).toBeFocused()
163+
await expect(last).toHaveAttribute('aria-selected', 'true')
164+
await expect(last).toBeInViewport({ ratio: 1 })
165+
await expect
166+
.poll(() => label.evaluate((element) => element.getBoundingClientRect().width))
167+
.toBeGreaterThanOrEqual(48)
168+
await page.mouse.move(200, 180)
169+
await page.screenshot({
170+
path: testInfo.outputPath('crowded-tabs.png'),
171+
animations: 'disabled',
172+
})
173+
await page.evaluate(() => document.documentElement.classList.remove('dark'))
174+
await page.screenshot({
175+
path: testInfo.outputPath('crowded-tabs-light.png'),
176+
animations: 'disabled',
177+
})
178+
await page.evaluate(() => document.documentElement.classList.add('dark'))
179+
await page.keyboard.press('Home')
180+
await expect(page.getByRole('tab').first()).toBeInViewport({ ratio: 1 })
181+
await page.locator('#eight-tabs').click()
182+
})
183+
await test.step('Touch tabs leave room for both attention and close controls', async () => {
184+
const session = await page.context().newCDPSession(page)
185+
try {
186+
await session.send('Emulation.setTouchEmulationEnabled', { enabled: true })
187+
expect(await page.evaluate(() => matchMedia('(any-pointer: coarse)').matches)).toBe(true)
188+
await page.locator('#many-tabs').click()
189+
const attention = page.getByRole('tab').nth(1)
190+
await expect
191+
.poll(() =>
192+
attention
193+
.locator('[data-overflow-text]')
194+
.evaluate((element) => element.getBoundingClientRect().width)
195+
)
196+
.toBeGreaterThanOrEqual(48)
197+
const attentionItem = page.locator('[data-tab-strip-item="tab-1"]')
198+
const indicator = attentionItem.locator('[data-row-action-indicator]')
199+
const controls = attentionItem.locator('[data-row-action-controls]')
200+
await expect(indicator).toBeVisible()
201+
await expect(indicator).toHaveCSS('opacity', '1')
202+
await expect(controls).toHaveCSS('opacity', '1')
203+
await attentionItem.getByRole('button', { name: /^Close / }).click({ trial: true })
204+
expect(
205+
await attentionItem.evaluate((element) => {
206+
const title = element.querySelector('[data-overflow-text]')?.getBoundingClientRect()
207+
const indicator = element
208+
.querySelector('[data-row-action-indicator]')
209+
?.getBoundingClientRect()
210+
const close = element.querySelector('[aria-label^="Close "]')?.getBoundingClientRect()
211+
return (
212+
title &&
213+
indicator &&
214+
close &&
215+
title.right <= indicator.left &&
216+
indicator.right <= close.left &&
217+
close.right <= element.getBoundingClientRect().right
218+
)
219+
})
220+
).toBe(true)
221+
await page.screenshot({ path: testInfo.outputPath('crowded-tabs-touch.png') })
222+
const beforeSelection = await attention.evaluate(
223+
(element: HTMLElement) => element.offsetWidth
224+
)
225+
await attention.click()
226+
await expect(attention).toHaveAttribute('aria-selected', 'true')
227+
expect(await attention.evaluate((element: HTMLElement) => element.offsetWidth)).toBe(
228+
beforeSelection
229+
)
230+
await page.locator('#toggle-activity').click()
231+
expect(await attention.evaluate((element: HTMLElement) => element.offsetWidth)).toBe(
232+
beforeSelection
233+
)
234+
await page.locator('#toggle-activity').click()
235+
await page.locator('#medium-tabs').click()
236+
await page.getByRole('tab').first().click()
237+
const intrinsicWidth = await attention.evaluate(
238+
(element: HTMLElement) => element.offsetWidth
239+
)
240+
expect(intrinsicWidth).toBeGreaterThan(144)
241+
expect(intrinsicWidth).toBeLessThan(200)
242+
await attention.click()
243+
expect(await attention.evaluate((element: HTMLElement) => element.offsetWidth)).toBe(
244+
intrinsicWidth
245+
)
246+
await page.locator('#toggle-activity').click()
247+
expect(await attention.evaluate((element: HTMLElement) => element.offsetWidth)).toBe(
248+
intrinsicWidth
249+
)
250+
await page.locator('#toggle-activity').click()
251+
} finally {
252+
await session.send('Emulation.setTouchEmulationEnabled', { enabled: false })
253+
await session.detach()
254+
}
255+
await page.locator('#eight-tabs').click()
256+
})
257+
await test.step('Short labels stay below the maximum tab width', async () => {
258+
await page.locator('#short-tabs').click()
259+
const widths = await page
260+
.locator('[data-tab-strip-item]')
261+
.evaluateAll((tabs) => tabs.map((tab) => tab.getBoundingClientRect().width))
262+
expect(widths.every((width) => width < 120)).toBe(true)
144263
await page.locator('#eight-tabs').click()
145264
})
146265
await test.step('An open menu recovers when no native page was available for its initial capture', async () => {

‎apps/desktop/e2e/fixtures/browser-chrome.tsx‎

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ function BrowserChromeFixture({ useOcclusion }: BrowserChromeFixtureProps) {
4646
const [activeTabId, setActiveTabId] = useState<string | null>(null)
4747
const [selected, setSelected] = useState('tab-0')
4848
const [tabCount, setTabCount] = useState(8)
49-
const [shortTitles, setShortTitles] = useState(false)
49+
const [titleLength, setTitleLength] = useState<'short' | 'medium' | 'long'>('long')
50+
const [attention, setAttention] = useState(true)
5051
const [error, setError] = useState<string | null>(null)
5152
const api = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi }).simDesktop
5253
const { snapshot, snapshotLayer, onSnapshotError } = useOcclusion(
@@ -93,25 +94,43 @@ function BrowserChromeFixture({ useOcclusion }: BrowserChromeFixtureProps) {
9394
id='eight-tabs'
9495
onClick={() => {
9596
setTabCount(8)
96-
setShortTitles(false)
97+
setTitleLength('long')
9798
}}
9899
>
99100
Eight tabs
100101
</Button>
101102
<Button id='many-tabs' onClick={() => setTabCount(18)}>
102103
Many tabs
103104
</Button>
104-
<Button id='short-tabs' onClick={() => setShortTitles(true)}>
105+
<Button id='short-tabs' onClick={() => setTitleLength('short')}>
105106
Short titles
106107
</Button>
108+
<Button
109+
id='medium-tabs'
110+
onClick={() => {
111+
setTitleLength('medium')
112+
setTabCount(4)
113+
}}
114+
>
115+
Medium titles
116+
</Button>
117+
<Button id='toggle-activity' onClick={() => setAttention((value) => !value)}>
118+
Toggle activity
119+
</Button>
107120
</div>
108121
{error && <p role='alert'>{error}</p>}
109122
<TabStrip
110123
tabs={Array.from({ length: tabCount }, (_, index) => ({
111124
id: `tab-${index}`,
112-
title: shortTitles ? 'A' : `Example resource ${index + 1} with a descriptive title`,
125+
title:
126+
titleLength === 'short'
127+
? 'A'
128+
: titleLength === 'medium'
129+
? 'Medium title'
130+
: `Example resource ${index + 1} with a descriptive title`,
113131
icon: <File className='size-[16px] shrink-0' />,
114132
active: selected === `tab-${index}`,
133+
attention: attention && index === 1,
115134
}))}
116135
variant='floating'
117136
onSelect={setSelected}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { ReactNode } from 'react'
2+
import { cn, pageHeadingClassName } from '@sim/emcn'
3+
4+
interface OrganizationLandingProps {
5+
heading: string
6+
children: ReactNode
7+
}
8+
9+
/** Keeps the Home and Search composers centered independently of the shortcuts below them. */
10+
export function OrganizationLanding({ heading, children }: OrganizationLandingProps) {
11+
return (
12+
<div className='min-h-0 flex-1 overflow-y-auto [scrollbar-gutter:stable_both-edges]'>
13+
<div className='flex min-h-full flex-col items-center justify-center px-6 pt-[2vh] pb-[22vh]'>
14+
<h1 className={cn(pageHeadingClassName, 'mb-7 max-w-chat')}>{heading}</h1>
15+
<div className='relative w-full max-w-chat'>{children}</div>
16+
</div>
17+
</div>
18+
)
19+
}

0 commit comments

Comments
 (0)