Skip to content

Source code

Architecture

Ownership

@agon/atrium owns reusable presentation and interaction. Consumers own route metadata, page controllers, branding, capabilities, adapters, and domain records. The starter never imports from the showcase.

A component has three pieces: a named Alpine provider in components/, a trusted HTML partial in templates/, and SCSS in styles/. Consumers compose markup at build time. Each page exports a compiled trusted HTML fragment and optionally a register(context) function for its Alpine providers. The runtime imports the selected page, registers its providers, and initializes its single root beneath the persistent shell. It never compiles backend HTML or passes raw server content to x-html.

The showcase uses two record collections with the same table/dialog workflow. Its fixture adapter demonstrates the API shape without mixing demo data into the shared package. Replace the adapter for a real application. Retain the UI contracts.

Distribution and local application publication

The private agon-atrium root coordinates development workspaces. Installing a workspace package from the registry does not install the repository root or unrelated workspaces. @agon/atrium remains independent of the publisher and templates. Each of @agon/atrium-starter and @agon/atrium-showcase depends on the core and @agon/atrium-publisher. The publisher uses only Node built-ins and has no browser runtime role.

All four packages share a stable release version. Yarn resolves workspace:* during development and replaces it with an exact version when packing for release. Each template owns an explicit files distribution allowlist, an atriumTemplate.files application source allowlist, its manifest defaults, and a thin bin/publish.js entry point. New application assets must be included in both appropriate allowlists. Shared src/atrium/build/ contains maintained Vite helpers and belongs in the core archive. Generated dist/, dist-*/, .build/, dependencies, and repository tooling do not belong in template archives.

Installing a template makes its executable available. yarn atrium-starter publish or yarn atrium-showcase publish explicitly materializes that template into the current initialized application. The shared publisher preflights paths and file/manifest conflicts, copies missing files, merges application scripts and direct dependencies, updates generated-file ignores, and runs the pinned Yarn install. It preserves registry configuration and unrelated manifest fields. There is no installation hook. The application developer owns the resulting source and lockfile. Identical files allow retries, while divergent files cause an error. See the publisher contract for options, failure behavior, and limitations.

This local command neither uploads npm packages nor deploys a site. Hosted CI release publication uploads the four packages after both generated applications pass independent package and browser checks.

Application lifecycle

  1. Vite compiles shared shell includes into index.html and emits a separate module for every route template. Shared controller dependencies may become shared chunks.
  2. The consumer imports pages from virtual:atrium/pages and passes it with route metadata and service context to createAdmin(config).
  3. createAdmin registers Alpine CSP, Focus, Alpine Router, icons, shared stores, and providers. Call start() once per document.
  4. Alpine Router mounts a small uiPage(index) outlet for the active route. The outlet checks its capability before importing the matching page module.
  5. The loaded page’s optional register(context) receives Alpine, preferences, progress, and the consumer’s explicit dependencies. It registers providers before the outlet initializes the compiled page root.
  6. Navigation destroys the outgoing subtree, including its requests, polling timers, listeners, and Alpine effects. The sidebar, header, global dialogs, and stores remain mounted.
  7. Generation counters ignore imports that complete after navigation or permission changes. Native module requests cannot be cancelled, but abandoned pages cannot mount or steal focus.

Each route template must have exactly one root element. This is an Alpine Router requirement. Route content belongs inside this root, including its dialogs.

The showcase component index lives at /components. Each example has a separate route template under src/showcase/pages/components/, declared in src/showcase/config/components.js. Map geometry, video assets, and console fixture settings are imported only by their own page controllers. The shared shell supports a grouped parent route with one submenu level through the optional parent route property. See nested navigation.

State boundaries

StateOwnerPersistence
Color, direction, layout, locale, time zone$store.uiNamespaced, versioned local storage
Current identity and capabilities$store.sessionSupplied by consumer. Memory only
Toast queue$store.toastsMemory with owned dismissal timers
Pending application requests$store.progressMemory with per-operation ownership
Table queryTable controller and router queryURL/history
Selection, form drafts, dialogsComponent instanceMemory only
Widget order/size/visibilityGrid controllerNamespaced preferences
Resource/user/job recordsShowcase adapterDemo namespace only
CredentialsHost authentication systemNever put in preference storage

The application’s appearance.config.mjs feeds the HTML compiler and the generated virtual:atrium/appearance module passed to createAdmin. Valid saved appearance values override build defaults when persistence is enabled. Disabling the customizer removes marked controls during compilation and disables appearance storage reads and writes. Reset restores the complete build defaults. Other preference domains retain their existing storage behavior. See appearance configuration.

Storage errors and invalid JSON fall back safely. Layout hydration filters unknown/duplicate widget IDs and merges newly introduced widgets.

Router choice

The concrete Alpine Router is @shaun/alpinejs-router@1.3.4. Route metadata powers both navigation and the Vite route compiler. Runtime behavior uses $router.push, $router.path, $router.params, and $router.query. The shell supplies base-aware native URLs, titles, route announcements, and focus handling.

The browser requests only the active page and its dependencies. Revisiting a page reuses its cached module while creating a fresh component instance. Loading and failure states belong to the outlet. Retry handles recoverable initialization failures. Reload application clears browser module-failure caching after a missing or failed chunk. Deploy entry HTML and fingerprinted assets together, and retain assets needed by existing sessions.

Capabilities hide navigation/actions and display an access-denied screen. They are presentation checks. Every real backend endpoint must enforce authorization independently, including calls from hidden or directly entered routes.

HTML composition

The Vite adminHtml({ root, routes, appearance }) plugin uses PostHTML includes with JSON locals. In the entry document, include ui:startup.html inside <head> and ui:shell.html inside <body>. The early stylesheet hides uninitialized shell markup and supplies the startup state and global loading line. In route templates and nested partials, use <include src="{{ uiRoot }}/metric.html"></include> to locate the installed package. Application partial paths are relative to the application root, for example partials/editor.html.

Only trusted build-time files and locals go through this compiler. Dynamic record values belong in Alpine x-text/x-model, not in include locals. A route’s template and optional module paths are consumer-authored build configuration, relative to the application root. Pages can live in feature directories such as pages/domains/list.html and pages/domains/list.js. Templates and their includes are watched during development. Changes invalidate compiled modules and reload the application.

Build policy

adminBuild({ mode, minify }) supplies development and production output settings. Production uses Vite’s native Oxc minifier with safe local-variable mangling and preserved function/class names for JavaScript, Vite CSS minification, and html-minifier-terser for entry HTML and lazy fragments. Production HTML contains no source metadata comments, including when whitespace minification is disabled. Development defaults to readable output and source maps. See build modes for commands and overrides.

Backend boundaries

Alpine owns presentation, navigation, forms, loading/error states, and interactions. Consumer adapters own HTTP requests and normalization. Business validation, subscription limits, tenant isolation, and authorization remain on the backend, including a Django REST Framework application. Client capabilities are presentation hints.

Dependency choices

Bootstrap CSS/SCSS only is imported. Its JavaScript plugins are not initialized, so Bootstrap and Alpine do not compete for dialog, menu, or collapse state. The router declares Alpine as a dependency, but the application initializes the CSP runtime exactly once. Built-in icons retain their local SVG paths. Optional Iconify collections compile to local CSS through the shared Vite plugin and consumer-owned icons.config.mjs. The complete icon gallery loads its CSS and name metadata with its route. Repeated catalogue selector patterns are encoded once and expanded into the unchanged public records at runtime. The starter imports no optional icon CSS until the consumer needs it. Compact metric trends use SVG geometry. Analytical charts load the pinned ApexCharts dependency on demand and accept consumer-owned API adapters. No runtime CDN or chart service is required.

Native surfaces

Charts, maps, media, and remote consoles are shared package components. Consumers provide geometry, sources, and authorized session adapters. Native objects stay in closure-owned state rather than Alpine proxies. Map, terminal, and noVNC engines are imported only when their surface starts. A cancellation signal owns pending work, native resources, observers, and teardown. The showcase supplies independent offline fixtures. See the component guides.

Shared modal templates use a cancellable x-ui-trap directive backed by the pinned focus-trap library. It rejects delayed activation after closure or DOM removal and restores pre-existing inert and scroll state.

Author

Laurent Declercq l.declercq@agon-innovation.ch

License

Unless otherwise stated all source code is licensed under LGPL 2.1 and has the following copyright:

© 2026, Agon Partners Innovation AG, All rights reserved.

The design material and the “Agon Ātrium” trademark is the property of their authors. Reuse of them without prior consent of their respective authors is strictly prohibited.

Version

Version: 20260921