Forge public-site UI kit — themeable Askama templates + typed render contexts (chrome/brand/i18n/jsonld/hero) over forge-cms PageView. Consumers shadow templates via askama.toml dirs precedence and rebrand via CSS custom-property tokens.
| Tier | application |
| Role | unclassified (baselined) |
| Path | crates/application/public-ui |
| Edition | 2021 |
| Targets | application_public_ui |
| Public items | 15 across 3 modules |
| Tests | 60 |
What it is for
# application-public-ui
A reusable, themeable public-site UI kit: Askama base layout + page templates + the typed render contexts they consume, over the application-cms PageView render contract.
What the kit ships
- Templates (under
templates/forge/): a base layout
forge/_layout.html with override blocks (head_extra/header/hero/hero_cta/content/cta/footer/ decoration/body_class) and page templates forge/pages/{home,services,service,sectors,projects,about,contact, default}.html. The contact template exposes a contact_form block so a consumer supplies its own bespoke form.
- Render contexts:
BrandContext,ChromeContext(+
ChromeBuilder), HeroContext, the I18n helper, and the jsonld builders.
- A default token stylesheet (
static/application-public-ui.css) built
on CSS custom properties — a consumer rebrands by overriding tokens.
- The
application-cmstyped view-models the page templates iterate
(ServiceCard, Sector, Project, Faq, ContactMethod, TeamMember), re-exported so consumers depend on one crate.
- A mountable `public_router` (the zero-config "A-path") — the kit
owns compiled #derive(Template) structs over its bundled forge/pages/*.html, so a new consumer mounts a working public site over a PublicPageReader with no template wiring. Registry-safe.
Two consumption paths (A vs B)
- A — `public_router` (zero-config, registry-safe). The kit compiles
its own templates (driven by this crate's askama.toml) and mounts them behind a PublicPageReader fetch seam + PublicSiteConfig. No markup override — config + CSS tokens only. Use this from a registry dependency.
- B — `dirs`-precedence shadow (full markup override, monorepo-only).
The consumer adds the kit's template dir to its own askama.toml dirs and writes its own page structs, overriding any template by a same-named file. Askama joins dirs to the compiling crate's CARGO_MANIFEST_DIR with no OUT_DIR/env support, so B only works when the kit is a path/Git dep at a stable relative path. See CONTRACT.md §5 for the full distribution matrix and the "vendor the templates" escape hatch for registry consumers who need override.
The override mechanism (Mechanism B — dirs precedence)
Askama compiles templates at build from askama.toml dirs, each resolved relative to the compiling crate's CARGO_MANIFEST_DIR. A consumer adds the kit's template dir to its own dirs AFTER its own templates:
# consumer askama.toml
general
dirs =
"templates",
"../rust-primitives-main/crates/platform/public-ui/templates",
Then a consumer page-template struct does #template(path = "forge/pages/home.html") (or {% extends "forge/_layout.html" %} from its own template). Because dirs is searched first-match-wins, the consumer overrides any kit template by placing a same-named file under its own templates/forge/….
The render contract
The kit's templates and the consumer's Rust struct are coupled by: 1. Context fields — the page struct must expose page: ChromeContext plus the per-page fields documented in CONTRACT.md. 2. CSS class names — the kit markup emits a fixed set of classes the theme stylesheet (kit default or consumer override) styles. They are listed in CONTRACT.md. A shadowed template that drops a class silently breaks styling, so the contract is the checklist for any override.
Capabilities
I18n
Path-prefix i18n helper — the mechanism lifted from the consumer's
| Item |
|---|
pub struct I18n |
I18n :: fn new(secondary_prefix : impl Into <String>, slug_map : & 'static SlugMap) -> Self |
I18n :: fn to_canonical(& self, secondary_path : & str) -> String |
I18n :: fn to_secondary(& self, primary_path : & str) -> String |
I18n :: fn localized_path(& self, canonical_path : & str, is_secondary : bool) -> String |
SlugMap
Path-prefix i18n helper — the mechanism lifted from the consumer's
| Item |
|---|
pub type SlugMap: (& 'static str, & 'static str) |
jsonld (other)
schema.org JSON-LD builders over BrandContext.
| Item |
|---|
fn organization(brand : & BrandContext, site_origin : & str, area_served : Option <& str>) -> Value |
fn website_entity(brand : & BrandContext, site_origin : & str, languages : & & str) -> Value |
fn home_page(brand : & BrandContext, site_origin : & str, area_served : Option <& str>, languages : & & str,) -> Vec <Value> |
fn generic_page(brand : & BrandContext, site_origin : & str, area_served : Option <& str>, canonical_url : & str, name : & str,) -> Vec <Value> |
fn serialize(graph : Vec <Value>) -> String |
preview (other)
Sprint 0.75 — gated template preview render (the live half of the New-Page
| Item |
|---|
fn render_preview(cfg : & PublicSiteConfig, template_key : & str, metadata : & serde_json::Value,) -> Option <Result <String, askama::Error>> |
fn render_block_sample(cfg : & PublicSiteConfig, block_key : & str) -> Option <String> |
fn render_widget_sample(_cfg : & PublicSiteConfig, widget_key : & str) -> Option <String> |
fn preview_router <S>(cfg : PublicSiteConfig) -> Router <S> where S : Clone + Send + Sync + 'static, |
How to use it
No examples/ target and no doctest in this crate's rustdoc. The tests listed under Verification are the closest executable usage.
Module structure
application_public_ui
i18njsonldpreview
flowchart TD n_application_public_ui["application_public_ui"] n_application_public_ui --> n_i18n["i18n"] n_application_public_ui --> n_jsonld["jsonld"] n_application_public_ui --> n_preview["preview"]
Public surface
`i18n`
| Item | What it is |
|---|---|
pub type SlugMap: (& 'static str, & 'static str) | ES↔EN (primary↔secondary) slug pairs |
pub struct I18n | Path-prefix i18n configuration + helpers. |
I18n :: fn new(secondary_prefix : impl Into <String>, slug_map : & 'static SlugMap) -> Self | Construct from a secondary-locale prefix + the consumer's slug map. |
I18n :: fn to_canonical(& self, secondary_path : & str) -> String | Translate a secondary-locale path (already stripped of the prefix) to its canonical primary form |
I18n :: fn to_secondary(& self, primary_path : & str) -> String | Translate a canonical primary path to its secondary-locale form (without the prefix) |
I18n :: fn localized_path(& self, canonical_path : & str, is_secondary : bool) -> String | Build the public URL path for canonical_path in the requested locale |
`jsonld`
| Item | What it is |
|---|---|
fn organization(brand : & BrandContext, site_origin : & str, area_served : Option <& str>) -> Value | — |
fn website_entity(brand : & BrandContext, site_origin : & str, languages : & & str) -> Value | WebSite entity — home page only |
fn home_page(brand : & BrandContext, site_origin : & str, area_served : Option <& str>, languages : & & str,) -> Vec <Value> | Home-page graph: org + website. |
fn generic_page(brand : & BrandContext, site_origin : & str, area_served : Option <& str>, canonical_url : & str, name : & str,) -> Vec <Value> | A generic page graph: org + a WebPage referencing it by @id. |
fn serialize(graph : Vec <Value>) -> String | Serialize a graph as a <script>-ready string wrapped in @context + @graph. |
`preview`
| Item | What it is |
|---|---|
fn render_preview(cfg : & PublicSiteConfig, template_key : & str, metadata : & serde_json::Value,) -> Option <Result <String, askama::Error>> | Public render-from-(key, metadata) entry (Sprint 0.75, T4) |
fn render_block_sample(cfg : & PublicSiteConfig, block_key : & str) -> Option <String> | Render one block from its registry sample, through the public-tier resolver (Sprint 0.76 T4) — the live half of the block palette, the block analogue of render_preview |
fn render_widget_sample(_cfg : & PublicSiteConfig, widget_key : & str) -> Option <String> | Render one widget from its registry sample, through the public-tier resolver (Sprint 0.76 T7) — the live half of the widget palette, the widget analogue of render_block_sample |
fn preview_router <S>(cfg : PublicSiteConfig) -> Router <S> where S : Clone + Send + Sync + 'static, | The gated preview router (Sprint 0.75, T3) |
Re-exports. Exported here, defined elsewhere.
| Export | Defined in |
|---|---|
{AltLink,ChromeBuilder,ChromeContext,NavItem,SwitcherLink} | chrome::{AltLink,ChromeBuilder,ChromeContext,NavItem,SwitcherLink} |
{BrandContext,ContactInfo,SocialLinks} | brand::{BrandContext,ContactInfo,SocialLinks} |
{ContactMethod,Faq,PageView,Project,Sector,ServiceCard,TeamMember} | application_cms::{ContactMethod,Faq,PageView,Project,Sector,ServiceCard,TeamMember} |
{I18n,SlugMap} | i18n::{I18n,SlugMap} |
{PostgresPublicPageReader,PublicPageContent,PublicPageReader} | application_cms::{PostgresPublicPageReader,PublicPageContent,PublicPageReader} |
{preview_router,render_block_sample,render_preview} | preview::{preview_router,render_block_sample,render_preview} |
{public_router,NavSpec,PublicSiteConfig} | router::{public_router,NavSpec,PublicSiteConfig} |
{render_block_preview,render_blocks_html,GalleryImage} | blocks::{render_block_preview,render_blocks_html,GalleryImage} |
{render_slot_html,render_widget_preview,SocialLink} | widgets::{render_slot_html,render_widget_preview,SocialLink} |
{render_style_block,HeroContext} | hero::{render_style_block,HeroContext} |
Boundary
Depends on no other workspace tier.
Shares tier application with 120 other crates: application-agreements, application-ai, application-analytics, application-approvals, application-assessments, application-audit-log, application-auth, application-billing, … (120 total).
_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._
Where it sits
| Tier (ontology) | application |
| Architectural role (taxonomy) | unclassified (baselined) |
| Location | crates/application/public-ui |
| Vocabulary in force (lexicon) | current |
Dependencies
Runtime, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `application-cms` | application | no | always |
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
askama | ^0.12 | — | no | always |
askama_axum | ^0.4 | — | no | always |
async-trait | ^0.1 | — | no | always |
axum | ^0.7 | multipart | no | always |
serde | ^1 | derive | no | always |
serde_json | ^1 | — | no | always |
Development, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
tokio | ^1 | full | no | always |
tower | ^0.5 | — | no | always |
Build. None.
Depended on by. Nothing in this workspace.
Signal flow — what reaches this crate, and what it reaches.
flowchart LR SELF["application-public-ui"] SELF -->|runtime| n_application_cms["application-cms"] classDef self fill:#1f883d,stroke:#1f883d,color:#fff; class SELF self;
Feature flags
No Cargo features are defined: every capability is unconditional, so no consumer can receive a half-wired crate.
Targets
| Kind | Name | Source |
|---|---|---|
| lib | application_public_ui | `src/lib.rs` |
Error model
No public error type was detected: no public item declares a type named *Error, and no public signature returns one.
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | none detected |
| async runtime | none detected |
| database access | none detected |
| network I/O | yes |
| unsafe code | none detected |
| environment variables | none detected |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
No environment variable is read with a literal name anywhere in this crate. A variable whose key is computed at run time cannot be listed here, and is not claimed to be absent.
Related capabilities
No workspace crate depends on this one.
Verification
| Kind | Count |
|---|---|
| Unit tests | 60 |
| Integration tests | 0 |
| Examples | 0 |
| Doctests | 0 |
Evidence by module. How often each public module is named by something executable.
| Module | Tests | Examples | Consumers |
|---|---|---|---|
i18n | 2 | 0 | 0 |
jsonld | 5 | 0 | 0 |
preview | 4 | 0 | 0 |
What the tests establish, by name:
callout_applies_variant_class_failsafes_unknown_and_sanitizes—src/blocks.rscard_list_layout_failsafe_and_image_alt—src/blocks.rscard_list_link_url_scheme_is_sanitized—src/blocks.rsfeature_list_renders_icon_and_is_tolerant—src/blocks.rssteps_renders_ordered_list_for_positional_numbering—src/blocks.rshours_fall_back_to_primary—src/brand.rsis_secondary_only_for_non_primary—src/brand.rsname_and_tagline_are_locale_matched—src/brand.rssecondary_falls_back_to_primary_when_blank—src/brand.rssocial_entries_skip_unset—src/brand.rscustom_hreflang_set_is_honoured—src/chrome.rsemits_primary_secondary_xdefault_alternates—src/chrome.rsset_content_replaces_placeholders_and_title_match—src/chrome.rssets_locale_and_canonical—src/chrome.rsswitcher_targets_the_other_locale_equivalent—src/chrome.rscss_escapes_inner_quote_and_strips_newlines—src/hero.rsempty_url_yields_empty_block—src/hero.rslandscape_only_emits_single_rule—src/hero.rslandscape_plus_portrait_emits_media_query—src/hero.rsportrait_equal_to_landscape_not_duplicated—src/hero.rslocalized_path_round_trips—src/i18n.rsmost_specific_pair_wins—src/i18n.rsto_canonical_preserves_deep_cms_slug—src/i18n.rsto_canonical_translates_known_and_passes_unknown—src/i18n.rsgeneric_page_references_org_by_id—src/jsonld.rshome_page_has_org_and_website—src/jsonld.rsorganization_has_required_fields_and_optional_area—src/jsonld.rsserialize_wraps_context_and_graph—src/jsonld.rsblock_preview_gallery_renders_public_sample_images—src/preview.rsblock_preview_renders_known_block—src/preview.rs- _… 30 more_
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 14 | 15 |
Public modules with a //! block | 3 | 3 |
pie showData
title Public items with rustdoc
"Documented" : 14
"No rustdoc detected" : 1
Metrics
| Metric | Value |
|---|---|
| Rust source files | 10 |
| Source lines | 3473 |
| Code lines | 2551 |
| Public API items | 15 |
| Public modules | 3 |
| Tests | 60 |
| Examples | 0 |
| Cargo features | 0 |
| Direct runtime dependencies | 7 |
| Workspace reverse dependencies | 0 |
pie showData
title Public API by kind
"function" : 9
"method" : 4
"struct" : 1
"type alias" : 1
pie showData
title Rust source composition
"Code" : 2551
"Blank or comment" : 922
Generation
Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.