recipes/logs/page/entry.tsx
One file of recipes/logs, at commit 9a5bb24.
// What the browser runs: a recorded client, the board it shares, a sign-in form, and the two
// buttons that make something worth recording. Nothing here names the backend.
import { createAuth } from '@aweftjs/auth/client';
import { createClient } from '@aweftjs/client';
import { mutable, observer } from '@aweftjs/core';
import { createLog } from '@aweftjs/logs/client';
import { Button, TextField, Theme, h, light, mount } from '@aweftjs/ui';
interface Board { title: string; note?: string; _secret?: string }
const page = globalThis as unknown as {
document: { body: unknown };
location: { protocol: string; host: string };
__log: { visit: string; flush(): Promise<void> };
};
const socket = `${page.location.protocol === 'https:' ? 'wss' : 'ws'}://${page.location.host}/ws`;
// The recorder wraps the client the rest of the page uses. The build is a fixed string here so
// the visit reads back with a build; a real page reads it from its own environment.
const log = createLog(createClient({ url: socket }), { build: 'recipe-1', flushMs: 300 });
const client = log.client;
const identity = createAuth(client);
// So the harness can read this visit back and flush on demand.
page.__log = { visit: log.visit, flush: () => log.flush() };
const title = mutable('waiting for the server');
let board: Board | undefined;
client.share<Board>('board').ready.then((shared) => {
board = shared;
observer(shared).path('title').effect((now) => { title.set(String(now)); });
});
Theme.define({
page: { minHeight: '100vh', background: '$background', color: '$foreground', fontFamily: '$font', display: 'flex', flexDirection: 'column', alignItems: 'start', gap: '$space4', padding: '$space4' },
});
const App = (): unknown => {
const email = mutable('');
const password = mutable('');
const signIn = async (): Promise<void> => { await identity.enter(email.get(), password.get()); };
// A write the server takes, and a slot the recorder must never carry a value for.
const write = (): void => { if (board !== undefined) { board.note = 'a public note'; board._secret = 'do not record me'; } };
// A removal the server refuses, so the page hears a refusal.
const remove = (): void => { if (board !== undefined) delete board.note; };
const boom = (): void => { void client.ask('board/Boom').catch(() => undefined); };
return (
<main id="page" theme="page">
<h1>Board</h1>
<p id="board-title">{title}</p>
<p id="who">{identity.user.map((who) => (typeof who === 'string' ? `signed in as ${who}` : who === null ? 'nobody' : 'asking'))}</p>
<TextField label="Email" value={email} />
<TextField label="Password" value={password} password />
<Button id="sign-in" label="Sign in" onClick={signIn} />
<Button id="write" label="Write" onClick={write} />
<Button id="remove" label="Remove" onClick={remove} />
<Button id="boom" label="Boom" onClick={boom} />
</main>
);
};
mount(page.document.body as never, <Theme value={light}><App /></Theme>);