Files
hanzo.ai/e2e/platform-keys.spec.ts
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

64 lines
3.2 KiB
TypeScript

// The platform affordance: where keys, usage and billing are answered.
//
// THIS FILE ASSERTED THE OPPOSITE OF THE DESIGN and had done for long enough to
// be believed. It required Settings' Keys, Usage and Billing rows to be anchors
// out to console.hanzo.ai and pay.hanzo.ai, quoting a rule the component had
// already dropped. `components/workspace/Settings.tsx` says why in its header:
// those three "used to be links out … But the surface is already packaged —
// @hanzo/usage carries the one usage panel every Hanzo product renders,
// @hanzo/iam carries the identity, and lib/plans carries the ladder — so linking
// out was not deferring to another home, it was sending a reader to a second
// copy of components this page already has."
//
// So the invariant is the other one, and it is worth just as much: a row opens
// its pane HERE, and a pane leaves only for what genuinely is not here — a card
// form, which belongs to the payment host, or the console's own key management.
// Restoring the hrefs made all three rows anchors, which also took `aria-current`
// off them and broke e2e/more.spec.ts, where pressing Usage must open that pane.
//
// BASE_URL=http://localhost:3000 pnpm exec playwright test --project=chromium e2e/platform-keys.spec.ts
import { test, expect } from '@playwright/test'
import { open, settings, shot } from './app'
test.describe.configure({ timeout: 240_000 })
test.describe('the platform affordance', () => {
test('Settings answers keys, usage and billing on its own panes', async ({ page }) => {
await open(page, '/chat')
const panel = await settings(page)
await expect(panel.getByText('Platform', { exact: true }), 'Settings must name the Platform group').toBeVisible()
// A ROW, not a link: it selects a pane in this modal. The address does not
// move, so what says it arrived is the row marking itself current.
for (const label of ['API keys', 'Usage', 'Billing'] as const) {
const row = panel.getByRole('button', { name: label, exact: true }).first()
await row.scrollIntoViewIfNeeded()
await expect(row, `${label} is a pane here, not a link out`).toBeVisible()
await row.click()
await expect(
panel.locator('[aria-current="true"]').filter({ hasText: label }).first(),
`${label} must open its own pane`,
).toBeVisible({ timeout: 15_000 })
}
await shot(page, 'settings-platform-panes')
})
test('a pane leaves only for what is genuinely elsewhere', async ({ page }) => {
await open(page, '/chat')
const panel = await settings(page)
// Key management itself lives in the console, so the Keys PANE links there.
// That is the shape the whole file is about: the pane is here, the thing the
// pane cannot own is one click away and says where it goes.
await panel.getByRole('button', { name: 'API keys', exact: true }).first().click()
const out = panel.getByRole('link', { name: /API keys/i }).first()
await expect(out, 'the Keys pane points at the console that mints them').toHaveAttribute(
'href',
'https://platform.hanzo.ai/api-keys',
)
await expect(out, 'a surface this site does not own opens in its own tab').toHaveAttribute('target', '_blank')
})
})