Skip to content

facade: optional view-config resolver contract#71

Merged
msmakouz merged 2 commits into
masterfrom
feat/view-config-resolver
Jul 3, 2026
Merged

facade: optional view-config resolver contract#71
msmakouz merged 2 commits into
masterfrom
feat/view-config-resolver

Conversation

@wolfy-j

@wolfy-j wolfy-j commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

What

Adds an optional wippy.facade:resolver contract that lets a consuming application override facade requirement values (css_variables, custom_css, app_title, app_icon, theme_mode, …) at request time — driving the view config from runtime state (settings, per-tenant branding) instead of only static ns.requirement defaults.

Fully backward-compatible: no binding ⇒ no behavior change. With nothing bound the config and CSS-vars handlers return exactly the static-requirement output they do today.

The contract

resolver (kind: contract.definition, namespace wippy.facade) with a single resolve method:

  • input: optional context object (reserved; empty {} today)
  • output: a map of { requirement_name -> override value string }. Values use the same format as the static requirement default (JSON-encoded for JSON requirements such as css_variables); omitted keys fall back to the static default.

How the handlers use it

theming_helpers gains two functions:

  • resolve_overrides() — optionally opens a bound wippy.facade:resolver and returns its {name -> value} map, else {}.
    • Structural gate: "is a resolver bound?" is decided by :implementations() (empty list ⇒ not bound ⇒ {}). This is deliberate — the contract-open sentinel is a plain errors.New with no Kind, so it surfaces as kind=Unknown and can't be distinguished from a real failure by kind. The binding count is the correct signal; no error-string/kind matching.
    • Ambient actor: opened with :open() (no with_actor/with_scope) so the resolver runs as the request's caller.
    • pcall-hardened: the config/CSS endpoints are load-bearing, so every failure path (and any unexpected throw) degrades to {} = static defaults. An absent resolver is silent; a bound-but-failing one is logged.
  • requirement(name, overrides) — prefers an override string, else the static wippy.facade:<name> default.

config_handler and css_vars_handler now resolve overrides once per request and route every requirement read through the override-aware getter (the get_req closure over the resolved overrides). The now-unused direct registry require was dropped from both handlers since reads go through theming_helpers.

theming_helpers library entry gains the registry, contract, logger module grants it now needs.

Integration note (consuming app)

The upstream handler security policy is left unchanged (config_read_runtime + content_fs_runtime). A consuming app that wants to actually bind a resolver must grant the config/CSS handlers contract.get / contract.open / contract.call on wippy.facade:resolver (and its binding), e.g. via an app-side policy added to those handlers. Without that grant, resolve_overrides() simply degrades to {} (static defaults) — it never breaks the endpoints. The default upstream policy is intentionally not weakened.

Compatibility

No binding ⇒ resolve_overrides() returns {} ⇒ output is byte-identical to today's static-requirement behavior.

wolfy-j added 2 commits July 3, 2026 11:42
Add an optional wippy.facade:resolver contract.definition that lets an
application override facade requirement values (css_variables, custom_css,
app_title, ...) at request time. When no implementation is bound the config
and CSS-vars handlers fall back to the static ns.requirement defaults, so
behavior is unchanged for existing deployments.

theming_helpers gains resolve_overrides() (structural :implementations()
gate, ambient-actor open, pcall-hardened to {} on any failure) and
requirement() (override string wins over the static default). Both handlers
resolve overrides once per request and route every requirement read through
the override-aware getter.
The endpoints degrade to static on any resolver failure, so unbound and errored
are indistinguishable and need no distinguishing; open() fails cleanly when
nothing is bound. Removes the implementations() round-trip and the unused logger.
@wolfy-j

wolfy-j commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Updated the branch: dropped the :implementations() probe (open() alone suffices — the endpoints degrade to static on any resolver failure, so "unbound" and "errored" need no distinguishing) and removed the now-unused logger.

Facade suite runs offline and passes: cd src/facade/test && wippy run test2/2 (config_handler_test, security_policy_test), exit 0.

@msmakouz msmakouz merged commit 480eb14 into master Jul 3, 2026
18 checks passed
@msmakouz msmakouz deleted the feat/view-config-resolver branch July 3, 2026 17:12
@wolfy-j wolfy-j restored the feat/view-config-resolver branch July 8, 2026 02:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants