feat(folio-web): web sample app + CDP spec tests (#36)

* fix(runner): allow nil connection for web platform

* fix(testrun): skip SDK handshake for web platform

* feat(folio-web): add React/Vite web sample app

* feat(folio-web): add sanderling spec

* fix(chrome): use InsertText for multi-char text input

* feat(hierarchy): add Screen field populated from sanderling-screen attr

* fix(chrome): auto-detect viewport from CSS vars, fix InputText accumulation, expose route as screen

* fix(runner): fall back to hierarchy root screen when snapshot screen is empty

* fix(folio-web): broaden loggedIn extractor to all authenticated pages
This commit is contained in:
pj authored and GitHub committed 2026-04-22 23:59:30 +07:00
1 parent eed99e58aa
commit af7b7e27b0
31 files changed
+2970 -74

No files matched your search

@@ -0,0 +1,91 @@
import { useState, type FormEvent } from 'react'
import { createAccount } from '../db'
import { back, navigate } from '../router'
import { BackButton, Header, Screen } from '../components/Screen'
export function AddAccountPage(props: { onCreated: () => void }) {
const [name, setName] = useState('')
const [err, setErr] = useState<string | null>(null)
const [busy, setBusy] = useState(false)
async function onSubmit(e: FormEvent) {
e.preventDefault()
if (busy) return
const trimmed = name.trim()
if (!trimmed) {
setErr('Account name is required')
return
}
if (trimmed.length > 40) {
setErr('Name is too long (max 40 characters)')
return
}
setBusy(true)
try {
await createAccount(trimmed)
props.onCreated()
navigate('/', { replace: true })
} catch (e) {
setErr(e instanceof Error ? e.message : 'Could not create account')
} finally {
setBusy(false)
}
}
return (
<Screen
header={
<Header
title="New account"
left={<BackButton onClick={() => back('/')} />}
/>
}
footer={
<button
id="add-account-submit"
className="btn primary"
type="submit"
form="add-account-form"
disabled={busy || name.trim().length === 0}
>
{busy ? 'Creating…' : 'Create account'}
</button>
}
>
<form
id="add-account-form"
onSubmit={onSubmit}
className="list"
style={{ gap: 14 }}
noValidate
>
<div className="field">
<label className="field-label" htmlFor="account-name">
Account name
</label>
<input
id="account-name"
className={'input' + (err ? ' invalid' : '')}
type="text"
autoFocus
autoComplete="off"
placeholder="e.g. Checking"
value={name}
maxLength={40}
onChange={(e) => {
setName(e.target.value)
if (err) setErr(null)
}}
/>
</div>
<div id="add-account-error" className="error" role="alert">
{err ?? ''}
</div>
<div className="muted" style={{ fontSize: 12 }}>
Use a short, recognizable name. You can create as many accounts as
you need.
</div>
</form>
</Screen>
)
}