A pack is a versioned frontend keyed by page id. One pack is an app UI. Many packs are installed apps or themes against the same typed contract, and Keel does not choose between them. The call site does.
Pass the pack you want
respondPage(pack, pageId, data, …) is the canonical form: open a
FrontendBundle once and pass it into render. Keel loads that pack’s page
module and CSS, and records its { id, version } as the seed’s theme, the
identity of the pack that served the page.
val harbor = FrontendBundle.fromFile(Path.of("pack/dist/harbor.feb"))
routing {
keel(harbor) {
get("/") {
call.respondPage(harbor, "harbor.home", HomePage(feed = Board.feed()))
}
}
}
route.keel(pack) { … } scopes a pack to a route subtree. Inside that scope a
bundle-less respondPage(pageId, data) resolves to the scoped pack, so a
subtree shares one pack without repeating it on every call.
KeelConfig.bundle and KeelConfig.bundles hold the host’s configured packs:
the one the pages DSL renders from, plus any additional installed packs. A
bundle-less respondPage with no scoped route resolves only when exactly one
pack is configured in total. None throws MissingPackException; several
without a scoped route throws AmbiguousPackException. Both tell the host to
pass the pack explicitly or scope the route.
The call site is the policy
- Tenant, one host process, a pack per tenant; pick the pack from the tenant record before rendering.
- A/B, choose the pack per request branch. The seed’s
theme.idrecords which one served. - Override, an admin or preview route passes a draft pack typed for the same ids.
- Admin, scope an admin subtree to an admin pack with
route.keel.
None of this is a request header or a cookie. There is no resolver and no chain: Keel never scans installed packs for one that implements the page, and packs never read a visitor theme from the browser.
What the response carries
The seed’s theme field is the serving pack’s { id, version }, alongside
the entry module and css URLs from that pack. The same values also go out
as the X-Keel-Theme and X-Keel-Version response headers. They are
responses, not request overrides, a visitor cannot select a pack with a
header.
Missing pages
If the passed pack does not implement the page id, render fails with
UnknownPageInBundleException; Keel does not substitute another installed
pack. The host maps unmatched URLs to the configured notFoundPageId, and a
pack can declare its own notFound module for those documents.
See also
- Host adapter, the
server checklist and the
KeelConfigknobs. - Pack hot reload, swap an opened pack without a JVM restart while iterating.
- Document head and SEO, how
the serving pack’s
themeflows into the document head.