Files
zeekayandHanzo Dev 9bbdc64ee0 One /dev: the builder is platform.hanzo.ai/dev
This export carried a second copy of the builder at /dev — the composer,
a run's transcript and a project pane (app/(app)/dev and
components/workspace/{Build,Session,project}.tsx), plus DevStudio.tsx and
enter.ts, which nothing else used. They are deleted, and dev.hanzo.ai is no
longer published from this export.

Every link to the builder is DEV or dev(ref) from lib/host.ts. dev() carries
a run id or a project slug into the path only when it is exactly one, so no
caller can make it name another address. The shell's Dev tile, Projects and
Runs open it there; /code forwards there. The edge's permanent redirects for
hanzo.ai/dev, /code, dev.hanzo.ai and hanzo.app live in universe.

Gates and specs that drove the old /dev drive /chat where they tested the
shared shell, and are removed where the builder itself was the subject.



Co-authored-by: Hanzo Dev <[email protected]>
2026-09-24 15:53:59 -07:00

70 lines
2.5 KiB
TypeScript

import { test, expect } from '@playwright/test'
/**
* PRODUCTION, ASKED DIRECTLY.
*
* Every other spec here measures `out/` — the bytes about to ship. This one
* measures what is actually serving, which is a different question and the only
* one that catches a room whose data never arrives: the export cannot know that
* `/v1/billing/plans` answers 503, and it did.
*
* It is in the `chromium` project rather than `gates`, so it never blocks a
* publish. A page can be perfectly built and still be up against an outage, and
* a build must not be held for one.
*
* Run it by hand:
* pnpm exec playwright test e2e/prod.spec.ts --project=chromium
*/
const SITE = process.env.BASE_URL || 'https://hanzo.ai'
/** Every room, plus the pages that take money or introduce the product. */
const PAGES = [
'/',
'/chat',
'/home',
'/board',
'/drive',
'/inbox',
'/work',
'/pricing',
'/integrations',
'/account',
]
for (const path of PAGES) {
test(`${path} answers, and says something`, async ({ page }) => {
test.setTimeout(60_000)
await page.setViewportSize({ width: 1440, height: 900 })
// What the platform refused, so a room that draws an empty shell can be
// told apart from one whose store is down.
const refused: string[] = []
page.on('response', (r) => {
if (r.url().includes('/v1/') && r.status() >= 400)
refused.push(`${r.status()} ${r.url().split('?')[0].replace(/^https:\/\/[^/]+/, '')}`)
})
const answer = await page.goto(`${SITE}${path}`, { waitUntil: 'load' })
expect(answer?.status(), `${path} did not answer`).toBeLessThan(400)
// A signed-out room says what it is and how to open it; it never draws a
// blank. The floor comes from the SHORTEST such room measured — /board's
// "Sign in to see the work." puts its whole page at 48 characters — rather
// than from a round number: set at 90 it failed three rooms that were
// answering correctly, which is a gate measuring its author's guess.
await expect
.poll(async () => (await page.locator('body').innerText()).trim().length, {
timeout: 20_000,
})
.toBeGreaterThan(40)
// Let the room's own reads finish before reporting what was refused: the
// page can satisfy the check above off cache while its data is still in
// flight, and a report written then says every store is healthy.
await page.waitForLoadState('networkidle').catch(() => {})
if (refused.length) console.log(`${path} — the platform refused: ${[...new Set(refused)].join(', ')}`)
})
}