ui
Kickside UI — platform navigation API plus admin UI surfaces. Ships the kickside-appearance web component (a System tab) that edits navigation layout, categories, and per-surface overrides backed by kickside.ui settings.
Run
wippy run kickside/uikickside/ui
Admin UI surfaces for a Kickside app. Currently the Appearance editor: a
self-contained web component (kickside-appearance) that mounts as a System
tab and edits the navigation appearance.
It edits the nav layout (vertical / horizontal), custom categories, custom items,
and per-surface overrides (hide / reorder / relabel / re-icon), then persists them
to kickside.ui settings over /nav/config.
Shape
src/_index.yaml— namespacekickside.ui. Declares the nav aggregation API, thenav_providercontract and its registry implementation, settings definitions, UI mount (fs.directory+http.static), theview.component, and aui.settings_tabso the System page discovers the tab without any app-side edit.ui/— the Vue 3 web component. Builds tostatic/index.js.
Where nav items come from
Every item on /nav/list arrives through an implementation of
kickside.ui:nav_provider — including the declared ones: registry_nav_provider
scans the ui.nav_item / ui.nav_category registry entries and is always collected
first. A module whose surfaces exist only at runtime (pages per entity kind,
per-tenant sections) binds the same contract and is asked on every request, so
nothing needs to be declared statically.
list({ locale, manage, timeout_ms }) -> { items, categories }. Items use theui.nav_itemfield shape plus a stableid. A provider contributes raw fields and the core validates them (nav.normalize_provided/nav.normalize_category) — one normalizer for every source, the registry scan included, so a provider cannot inject an unsafe path or tag and a bad declaration cannot be quietly repaired into an item pointing at/.nav.luaopens no contract; its retainednav.declared()helper is a declared-only compatibility registry view, not a request-path fallback.- Providers run under the caller's actor and scope — a provider may (and should) filter its surfaces by what that user can see.
- Remaining providers run in deterministic priority order, then binding id;
nav.effectiveresolves duplicatepath/route_namefirst-wins, so a declared surface always beats a dynamic item that would shadow it. - Providers are capped at
ui_types.NAV_LIMITS.PROVIDERSand run concurrently through cancellable contract futures under one short deadline. Each receives the same cooperativetimeout_msbudget. A failed, timed-out, malformed, or overflow provider is logged with a stable diagnostic and skipped; healthy sources still render. Contract availability itself returns the stable publicnav_unavailableerror, while raw details remain logs-only. - Future cancellation is best-effort: it bounds the request's wait for normal Wippy operations but cannot preempt CPU-bound code that never yields. Installed in-process provider code is therefore privileged and reviewed like every other Wippy module; its binding metadata, returned items/categories, and errors remain untrusted input and are revalidated, capped, sanitized, and never exposed verbatim. Hostile-code isolation requires a separate runtime/process boundary and is not provided by this in-process extension contract.
nav_providers.collectcaps each provider's items and categories and reports drops. Provenance is derived by the collector: only the registry binding can producesource: declared; a provider payload cannot claim it.
Data
The editor reads /nav/list and writes /nav/config. Those compatibility routes
are owned by this module; internally, persisted navigation controls live under
the kickside.ui settings namespace.
Build
make build # cd ui && npm install && npm run build -> static/index.js
make lint # wippy lint