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.

BUSL-1.1 2.6k downloads
Updated 21 days ago Repository
kicksideuiadminappearance

Run

wippy run kickside/ui

kickside/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 — namespace kickside.ui. Declares the nav aggregation API, the nav_provider contract and its registry implementation, settings definitions, UI mount (fs.directory + http.static), the view.component, and a ui.settings_tab so the System page discovers the tab without any app-side edit.
  • ui/ — the Vue 3 web component. Builds to static/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 the ui.nav_item field shape plus a stable id. 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.lua opens no contract; its retained nav.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.effective resolves duplicate path / route_name first-wins, so a declared surface always beats a dynamic item that would shadow it.
  • Providers are capped at ui_types.NAV_LIMITS.PROVIDERS and run concurrently through cancellable contract futures under one short deadline. Each receives the same cooperative timeout_ms budget. 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 public nav_unavailable error, 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.collect caps each provider's items and categories and reports drops. Provenance is derived by the collector: only the registry binding can produce source: 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