Site / foldkit-site

Common workflows

Navigation rendering. Derive the nav from the tree instead of keeping parallel tables: sections in tree order, hrefs from Site.landing, titles from Site.titleOf, key bindings from Site.shortcuts, and the current section by comparing tags.

Route changes. On a URL change, resolve the route value once, then Site.chainOf for the active chain and Site.historyOf for the history step — or hand both branches to Site.routing (with pages for the routed placements) and keep only the guard in update. What each route does with the change beyond informing its page is still the application's update.

Who may be shown a page. A route carries no authorization: the server guards what a page reads, whatever the route says. What a route may decide is presentation, sending a visitor to sign in rather than drawing an empty page. access on a node says who may be shown it and the nodes below it, from what the application knows of the reader (its Me row), and where anyone else goes (from access.test.ts):

ts
const Projects = Site.node(pipe(literal('projects'), mapTo(AppRoute.Projects)), AppRoute.Projects)
  .access<Me>({ allow: me => me.signedIn, otherwise: toSignIn })
  .node

Site.accessOf(site, route, who) decides it, root first: Allowed; Pending while who is Option.none() (the reader's Me still loading), for a loading state rather than a sign-in page flashed at someone signed in; Denied with the URL to go to (otherwise gets the address asked for, so a sign-in page can return to it). Read it with Access.match. The view draws from it, update replaces the URL on Denied after a route change or when Me arrives, and a server answers Denied with a 303 before rendering. An otherwise whose target the same rule covers throws, at any address: a sign-in page mounted under the rule it serves would otherwise send each refused reader to a longer returnTo for ever. A rule given in a node's options names the reader it reads (allow: (me: Me) => …); accessOf cannot check that its who is that reader, so pass the one the rules read.

Prefetch and SSR. Site.target plus Site.chainOf say which Surfaces a destination will activate; Site.sourcesFor(site, owner, place, target.route) is those as Sources — Data.satisfy over them, with the route set to the target's, prepares the Model before a synchronous render. Preparation stays caller-composed — there is no prepare in any plan.

Limits / when not to use it

  • A single-screen application has one node; the tree buys nothing.
  • Bundles with OutMessages, and children that are not Bundles, stay on Bundle.at and Link.child: Site.placement takes neither onOut nor a non-Bundle child.
  • A node's history function sees only its own route values; cross-node rules more subtle than "another node is a step" do not exist yet.
  • Locales are route params like any other when they arrive; themes are preferences, not routes.