ADR 20: File Explorer Frontend Service Layer (Interfaces, Singletons, RxJS)
Status: Accepted
Date: 2026-04-27
Context
The Web File Explorer (ADR 15) in apps/file-explorer coordinates session checks, gateway presence, drive listings, tree navigation, uploads, and route-level gates. That logic had accumulated in route resolvers with mutable module-level state, ad-hoc fetch calls, and types split between resolver files and callers. That shape made it harder to:
- keep one place for side effects and shared state updates;
- align browser calls with the OpenAPI-derived client (ADR 11);
- evolve toward testable boundaries without rewriting routing.
We want a small, explicit service-oriented front-end architecture that stays compatible with Mithril route onmatch flows and does not pretend to be a second backend.
Decision
Service layer: Introduce
apps/file-explorer/src/services/as the home for application services (session, gateway presence, drives, tree, upload, auth, telemetry). Each capability exposes a TypeScript interface (contracts incontracts.ts) and a concrete class implementation.Singleton composition: Export one instance per service from
apps/file-explorer/src/services/index.ts. Resolvers and other entry points import those singletons rather than constructing services per route. This matches the current Mithril bootstrap (no global DI container yet) while keeping construction in one file.Resolvers as adapters: Keep
apps/file-explorer/src/resolvers/as thin route adapters that call services and preserve existing function signatures and route outcomes used byrouting.tsx. Where legacy exports (session,gatewayStatus,explorerRouteData,explorerTreeData) remain, they delegate to service snapshots so the UI does not churn in one step.RxJS for shared state: Services that own cross-route UI state expose
BehaviorSubjectinternally, plusstate$andgetSnapshot(). Command methods (resolveSession,resolveExplorerDrives, etc.) update subjects. Components may still read snapshots synchronously during migration; prefer subscribing at layout boundaries when moving to stream-driven redraws.API URLs and telemetry: Do not duplicate gateway URL construction in explorer-only config types. Use
get*Urlhelpers from@substratum/api-client(generated.ts) so paths stay aligned with utoipa / OpenAPI on the gateway. Optional or debug traffic (e.g. UI telemetry ingest) is still declared on the gateway and generated like any other route.Twelve-factor alignment (frontend slice): Runtime behavior continues to depend on environment and reverse-proxy wiring (e.g. Vite
/apiproxy, nginx edge) per ADR 13. The explorer does not introduce a parallel “config server”; the API spec and client are the source of truth for HTTP paths.
Consequences
- Positive: Clear boundaries for session, connectivity, and explorer data; easier refactors and tests against interfaces; state transitions centralized; HTTP paths stay consistent with the gateway OpenAPI dump.
- Negative: Singletons are global process state in the browser tab (acceptable for this app size, but not a full DI story); RxJS adds a dependency and a learning curve for contributors; resolver shims remain until all readers move to services or streams.
- Action category split (panel vs immediate): Context-menu actions now carry a
categoryofpanel(Properties, Share, Move, Rename, Delete — opens a route-driven side panel) orimmediate(Open — executes synchronously, e.g. navigates a folder or opens a file in a new tab viagetGetDriveContentUrl).ExplorerWorkspace.onContextActionbranches oncategoryso immediate actions never participate in route panel state, while panel actions continue to usesetPanel. TheContextActionServicenow returns a discriminated{ supported, ok, message }result so the workspace can surface real gateway errors instead of a generic "not available yet" message.
Related
- ADR 08: Hosting and Frontend Stack — Mithril and dashboard context.
- ADR 11: Cross-Boundary Strategies — OpenAPI and generated client.
- ADR 13: Twelve-Factor App — config and runtime expectations.
- ADR 15: Web File Explorer — explorer UX and data-loading principles.
- ADR 44: Explorer Workspace Decomposition — orchestrator vs service vs
lib/boundaries forExplorerWorkspace. - Operational detail:
apps/file-explorer/AGENTS.md