A directory of React mounted full-bleed under the page slug.
Single-page app pages
When a view cannot express it and a component inside a block page is
still too constrained — the app needs the viewport, its own URLs, a
multi-screen flow — declare the page as a single-page app: a
directory of React beside the app definition, built at install into the
app's extension and mounted full-bleed in the workspace under the page's
slug.
app dispatch "Dispatch" {}
page board "Runs" list Run {
board Run summary [driver, stops]
}
/// The driver console: map, stop list, proof-of-delivery capture.
page console "Driver Console" spa from "console/" using sdk write, fsm, files {}
nav [board, console]
home board
console/index.tsx default-exports the page. Every .ts and .tsx
file under the directory is part of it, and files import each other with
relative paths; react, react-router and @fastyoke/sdk come from
the platform. Stylesheets and images are not bundled: use the host's
utility classes or style props, and reference assets by URL.
The page owns its URL space. It is mounted at
/workspace/app/<app>/console and everything after that is the page's:
/console/run/abc/stop/2 reaches it as subpath = "run/abc/stop/2",
deep links survive a reload, and the sidebar keeps "Driver Console"
active. The host does not impose a router. The entry receives base,
subpath and navigate, and any depth of the tree reads them with
useSpaRoute(); react-router users add one line:
import { MemoryRouter, Route, Routes } from 'react-router';
import { SpaRouterSync, useSpaRoute } from '@fastyoke/sdk';
export default function Console() {
const route = useSpaRoute();
return (
<MemoryRouter initialEntries={[`/${route.subpath}`]}>
<SpaRouterSync route={route} />
<Routes>
<Route path="/" element={<RunList />} />
<Route path="/run/:id/stop/:n" element={<Stop />} />
</Routes>
</MemoryRouter>
);
}
The grant is a component's: using sdk write, fsm gives the page a
client that reads, writes and fires transitions; no clause gives it
props and routing only. It runs on the same short-lived, scoped token
inside the tenant's session, so it cannot reach a route the grant does
not cover. A page with unsaved state calls useLeaveGuard(dirty) so the
shell asks before leaving.
The directory travels with the source like a component's file: the
command line, the Studio and the MCP server inline it before install,
and yokec check reads it next to the .yoke file, refusing a missing
index.tsx, a file it cannot bundle or an import that climbs out of the
directory. The page is a page: it appears in nav, may be home, and
comes back as spa from "console/" when the app is exported.