Files
fbc926f525 The app is @hanzo/rooms 0.1.55's App, at paths under /chat and /dev, behind the organization gate
_web.tsx draws rooms' App inside rooms' `Orgs`, as every room is: a reader
with no organization makes one, one with several picks one, and a team on Free
is asked for the Team plan before the app is drawn; a personal organization on
Free opens it. The app reads its place from the path: /chat and /chat/<id> are
Chat, a path under /dev is Dev. /?chat=<id> and /chat?thread=<id> move to
/chat/<id>, /?at= to /dev?at=, whose place the door replaces with its path, as
it does the builder's grammar rooms 0.1.35-0.1.38 wrote under /dev.

The gates read the app at its paths and the rooms' shell at /home, with new
Linux baselines for it; the limit banner, the model chip and the side panel
are held to what rooms 0.1.55 draws; the landing's budget is 445 KB, since
rooms' lib/host carries @hanzo/build's route grammar. app/gui.css is what the
build writes.


Co-authored-by: Hanzo Dev <[email protected]>
2026-10-16 15:05:24 -07:00

82 lines
3.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test, expect } from '@playwright/test'
import { serveExport } from './export'
import { signIn } from '../session'
import { rail } from './rail'
/**
* THE CORNER FITS, AND SAYS TWO THINGS.
*
* The bar's leading cluster carries the sheet's glyph, and the search field is
* centred between it and the room's controls; the workspace's name leads the
* sheet the glyph opens. A cluster
* that outgrows its budget pushes the field off a phone — which is why the
* budget is a maximum rather than a width — so this measures the row rather
* than trusting it.
*
* Four widths, because the failure is a width: 390 is a phone, 834 a tablet,
* 1440 a laptop, 1920 a desktop.
*/
const SIZES = [
{ name: 'phone', w: 390, h: 844 },
{ name: 'tablet', w: 834, h: 1112 },
{ name: 'laptop', w: 1440, h: 900 },
{ name: 'desktop', w: 1920, h: 1080 },
]
test.describe('the bar’s corner', () => {
for (const s of SIZES) {
test(`fits and opens the sheet at ${s.name}`, async ({ page }) => {
await signIn(page)
const site = await serveExport()
try {
await page.setViewportSize({ width: s.w, height: s.h })
await page.goto(`${site.url}/home`)
// ONE GLYPH FOR THE SHEET, and it is the control it claims to be.
const handle = page.locator('header').getByRole('button', { name: /Open sidebar|Close sidebar/ })
await expect(handle).toBeVisible({ timeout: 20000 })
// HALF THIS ROW IS THE CLIENT'S, so wait for the client. The glyph
// above is in the prerendered bytes and is there on sight; the corner
// beside it names the workspace, which the shell reads off the session
// after it mounts. The shell says when that happened, and saying so is
// the whole reason it does — so the wait is that mark and not a guess
// at how long a loaded machine takes to boot React.
await expect(
page.locator('[data-slot="workspace"][data-hydrated="true"]').first(),
).toBeAttached({ timeout: 20000 })
// WHOSE WORKSPACE THIS IS leads the sidebar — out as a column on a
// laptop, a sheet the corner opens on a phone. IAM's switcher draws a
// native <select>, which is why this reads the text rather than a role.
await rail(page, '[data-slot="list"]')
const switcher = page.locator('[data-slot="workspace-switcher"]')
await expect(switcher.locator('select')).toHaveCount(1)
await expect(switcher).toContainText('hanzo')
const bar = page.locator('header').first()
const box = await bar.boundingBox()
expect(box, 'the bar has no box').not.toBeNull()
expect(
Math.round(box!.width),
`the bar is wider than the window at ${s.name}`,
).toBeLessThanOrEqual(s.w)
// Nothing inside it reaches past the window either — a cluster that
// grows pushes the row, and a row that overflows is read as a bar that
// simply lost its search field.
const over = await bar.evaluate((el, width) => {
const out: string[] = []
for (const kid of Array.from(el.querySelectorAll('*'))) {
const r = kid.getBoundingClientRect()
if (r.width > 0 && r.right > width + 1) out.push(kid.className || kid.tagName)
}
return out.slice(0, 3)
}, s.w)
expect(over, `something in the bar reaches past ${s.name}`).toEqual([])
} finally {
await site.close()
}
})
}
})