ADR 33: Frontend Modular Monolith and Package Boundaries
Status: Accepted
Date: 2026-06-02
Last Updated: 2026-06-11 (pds-portal sibling app per ADR 42)
Context
Substratum ships one primary web dashboard (apps/file-explorer) for SaaS, self-hosted edge, and cloud-connected deployments. We need environment-specific navigation and copy (SaaS plan limits vs home-server operator messaging) without adopting runtime micro-frontends (separate deployables, module federation, or iframe remotes).
The backend is already a modular monolith (ADR 01). The frontend should mirror that pattern: one build, one origin, strict internal boundaries—not multiple SPAs coordinated at runtime.
Constraints from ADR 08:
- Mithril.js for a lightweight dashboard.
- Self-hosted installs stage a single static bundle beside the edge (ADR 23).
- AT Protocol OAuth requires a stable canonical app origin per deployment.
Decision
No micro-frontend architecture (v1). We do not use runtime module federation, import-map remotes, or iframe-embedded sub-apps. We reject independent frontend deploy cadences until a documented need outweighs operational cost.
Modular frontend monolith. The web UI remains one Vite application (
apps/file-explorer) with logical feature areas expressed as workspace packages or clearsrc/boundaries, not separate hosted apps.Package separation (Nx / pnpm). Shared and feature-specific code lives in libraries; apps are thin hosts:
Package / area Responsibility @substratum/ui-kitPresentational primitives only (buttons, layout chrome, settings inputs). No routing, session, or API calls. Shared across all Mithril web apps. @substratum/api-clientGenerated OpenAPI client. apps/file-explorerCustomer dashboard on app.*: route table, resolvers, feature pages, services, i18n catalogs.apps/landingMarketing on substratum.cloud(ADR 36); ui-kit + own Lingui catalogs — not file-explorer routes.apps/adminOperator UI on admin.*(ADR 38); ui-kit + own Lingui catalogs — not file-explorer routes.apps/pds-portalIdentity UI on pds.*(ADR 42); sign-in + OAuth consent; ui-kit + own Lingui catalogs — not file-explorer routes.apps/installer-guiSeparate desktop product (Tauri); shares ui-kit and theme, not the web route graph. apps/substratum-explorerThin desktop webview shell to the self-hosted edge URL. Future extraction (optional):
libs/platform(session, bootstrap, deployment context),libs/shell(nav manifest + route registration API)—consumed byfile-explorerand/or sibling apps via workspace imports only, never by copyingapps/file-explorer/srcinto another app.Application shell (in-process). “Shell” means shared layout and navigation inside the SPA: header, sidenav, locale picker, gateway status, logout. Nav items may be filtered by deployment context from gateway bootstrap (Glossary: deployment context). This is not a micro-frontend host loading foreign bundles.
Environment customization uses:
- Server:
deployment_mode/ bootstrap payload (authoritative). - Build: optional thin entry wrappers only if branding diverges severely (default: one artifact).
- Client: nav manifest + copy keys—not separate applications.
- Server:
Alignment with backend. Feature folders follow the same bounded-context idea as ingress handlers: explorer (drives/tree/upload), account (plan/limits/settings), auth (login/signup)—as directories and services under
apps/file-explorer, not deployable MFEs.External auth surfaces (not in file-explorer). One origin applies to
apps/file-exploreronapp.*only. These are separate origins with separate login flows — link out, do not embed:Surface Origin Auth In file-explorer Customer app app.*AT Proto OAuth → substratum_sessionYes (host) Support (Discourse) support.*OIDC → id.support.*bridge → PDS (ADR 39)External link only Operator admin admin.*PDS OAuth → substratum_ops_session(ADR 38)No Operated PDS identity pds.*PDS session + OAuth consent on portal (ADR 42) No (redirect only) Marketing substratum.cloudNone / checkout No apps/file-explorerMAY show support seat status on/accountand link tosupport.*; it MUST NOT host OIDC bridge callbacks, Discourse iframes, or support SSO (ADR 39 R13).Shared ui-kit, isolated app code. Sibling web apps (
apps/landing,apps/admin, and future hosts) reuse@substratum/ui-kitand Midnight Gallery design tokens for visual consistency (ADR 14, ADR 36). Application code stays isolated:- Each app is its own Nx/Vite package: own
src/, routes, services, auth wiring, and Lingui catalogs. - Apps MAY depend on
@substratum/ui-kit,@substratum/api-client, and future sharedlibs/*— MUST NOT import fromapps/file-explorer/src(no shared route modules, no re-exporting explorer pages into admin). - App-specific composed screens (e.g. operator entitlement lookup table) live in that app; promote to ui-kit only when a second app needs the same presentational primitive.
support.*(Discourse) uses Discourse’s UI; Substratum does not fork forum chrome into file-explorer. Optional bridge error/status HTML MAY use ui-kit in a minimal isolated package if needed (ADR 39).
- Each app is its own Nx/Vite package: own
Rejected alternatives
| Alternative | Why rejected (for now) |
|---|---|
| Runtime micro-frontends (Vite federation, etc.) | Weak Mithril ecosystem for federation; OAuth origin and version skew; conflicts with single-bundle self-hosted staging. |
| iframe remotes | Poor UX, cookie/session friction, accessibility cost. |
| Copy file-explorer routes into admin/landing | Duplicates auth and coupling; violates isolated-app rule (§8). Use ui-kit + app-local pages. |
| Duplicate Button/Layout in each app | Defeats ui-kit; use @substratum/ui-kit with string props from each app’s Lingui. |
Revisit micro-frontends only if independent teams need independent release trains and the above costs are accepted explicitly in a superseding ADR.
Consequences
Positive
- Sibling apps (
landing,admin) share look and feel via ui-kit without sharing file-explorer bundles or OAuth origins. - One OAuth origin per customer web app (
file-exploreronapp.*); operator and marketing ship separate artifacts. - Self-hosted and SaaS share the same staged
dist/; installer packaging stays simple. - Clear place to grow (
libs/shell, feature subfolders) without operational sprawl.
Negative
- All web features ship in one version line until we split builds deliberately.
- Discipline required: ui-kit must not absorb app routing or gateway calls.
Neutral
- Desktop installer, landing, and
apps/adminare separate apps by origin or form factor—not a violation of the file-explorer modular monolith.
Related
- ADR 32: Account entitlements and hosting policy
- ADR 38: Support and Entitlement Admin Tooling —
apps/adminisolated host - ADR 42: Branded PDS Portal —
apps/pds-portalonpds.* - ADR 39: AT Proto OIDC bridge for Discourse — support login external to file-explorer
- ADR 36: Marketing Landing Page —
apps/landingui-kit pattern - ADR 01: Modular Monolith
- ADR 08: Hosting and Frontend Stack
- ADR 15: Web File Explorer
- ADR 20: File Explorer Service Layer
- Glossary (Ubiquitous Language)
apps/file-explorer/AGENTS.md