Site / Site and the Router

How it works

A node is a router plus annotations

Site.route(router, routeCase, options) keeps the router you pass and reads the tag from the route case, so one route case is one node. Nothing about the URL is reimplemented: a node's address is always node.router.build(params), so a link built through the Site parses back to the route it was built from. Nodes are frozen values, and mounting one in a tree does not change it, so the exported node builds targets and hrefs wherever it is used.

The tree is indexed once

Site.make(...roots) freezes the topology and walks it once, at definition, so a bad tree fails where it is declared. The walk keeps three things for that tree, in a WeakMap:

  • each node's parent;
  • each tag's node, refusing two nodes with one tag or one node mounted twice, since either would make "which node is this route?" ambiguous;
  • the parser: parseUrlWithFallback(oneOf(...routers)) over every node's router, in tree order (depth-first, parents first).

So Site.parse is Foldkit's parser with the list taken from the tree, and Site.routeAt(site, url) is the same parse with None instead of a fallback. Every other lookup (chainOf, titleOf, sectionOf, metaOf) is a map read and a walk up the parents.

Site.routing is the two branches, written once

It is a Bundle wiring, placed with Bundle.assemble beside the pages. It claims the click Message, so the application's own update never sees one, and handles the URL-change Message before passing it on. Routing.reduces(message) names both for the application's guard.

On a click:

UrlRequest.External                      -> load the page in full
UrlRequest.Internal, the tree holds it   -> pushUrl or replaceUrl, per Site.historyOf
UrlRequest.Internal, no node takes it    -> load the page in full

The last line is the difference from Foldkit's own split. Foldkit calls a link internal when it is on this origin. The Site calls it internal when this application draws it. A link to /docs served by another app on the same domain is a full load, not a Navigate Command that could only draw NotFound.

Site.historyOf(current, next) adds a Back step for a move to another node, or from an address the tree does not hold, so Back can return there. A move within one node replaces the current entry, unless the node's history rule says otherwise. In the example, another person is a step and another search of the people list is a replace.

On a URL change the wiring parses the address with your parse. If the result is the route the Model already shows, it returns the same Model, so nothing renders and nothing pending is cleared. Your own replaceUrl, or a URL mirror's write, comes back as exactly such a change. Otherwise it runs, in order:

  1. set the route field;
  2. each routed page (Site.placement) whose node the route belongs to is told, through the page's own Message;
  3. arrived(model, route), if you gave one, for anything else a new route does, such as moving focus.

The navigation Commands it returns are ordinary Foldkit Commands (pushUrl, replaceUrl, load) that answer with your completed Message. The wiring shares the URL-change tag with URL mirrors, which read their own slices of the address first.

The rest reads the tree and nothing else

Site.view composes functions: the deepest node's view, wrapped by each ancestor's layout, root first. A layout receives its child as an argument; there is no Outlet and no nested router. Site.sources turns each node's surface into a Surface.when on the route field, keyed by tag, so the reads a page declares start when its route is shown and stop when it is not. Site.targets runs each node's Site.paths Effect to list every address, for a static build and a sitemap.