Skip to content

ADR-0003: Web admin UI as a plugin

  • Status: Accepted
  • Date: 2026-05-08
  • Deciders: Mesh-America

Context

The BBS will ship a web admin interface for sysop maintenance. The question is whether it's:

a) A first-class feature of the host binary, hard-wired into the architecture b) An optional module enabled via cargo features, but otherwise integrated like any other piece of the host c) A plugin against the same bbs-plugin-api that third-party transports and extensions use

mesh-citadel took the "first-class" path: the web UI was wired directly into the transport manager and shared significant code with the BBS core. This worked but it meant the plugin contract was theoretical - there was no real example of an extension built against it, so the contract drifted from what extensions would actually need.

Decision

The web admin UI ships as plugin (option c). It implements the same TransportEngine trait as bbs-cli and bbs-mesh, and uses the same Host interface.

The plugin lives in crates/bbs-web/. It's enabled via the admin-web cargo feature. Default: off.

Alternatives considered

Option (a) - first-class

Rejected. The plugin contract becomes vapor if there's no non-trivial example. We'd have a Plugin trait that no real plugin uses, and six months in, the trait would be subtly wrong for the first extension someone wrote.

Option (b) - opt-in but integrated

Rejected for the same reason. "Optional" doesn't validate the contract; "implemented against the contract" does.

Consequences

Positive

  • The plugin API is forced to be expressive enough to support a real, complex feature: HTTP routes, static-file mounts, authentication integration, event subscriptions, config validation, lifecycle hooks. If bbs-web can be a plugin, almost any plausible extension can be.
  • Default-off is enforced naturally. Cargo features mean operators who don't want the web UI don't even compile it in. Smaller binary, less attack surface.
  • The web UI doesn't get special privileges. It can't bypass permission checks, can't access the DB directly, can't read session tokens it shouldn't see - because it has the same Host interface as a third-party plugin would.
  • Third-party UI projects (mobile app, terminal client, alternative web frontend) consume the same OpenAPI surface the web admin uses. The reference implementation is right there.

Negative

  • Slightly more complex plugin API. The trait has to support static-file mounts and axum::Router contributions, not just raw command processing. This is the cost of forcing the API to be real.
  • Boundary discipline required. The web plugin must not reach into bbs-core private APIs as a shortcut. Code review enforces this; rustdoc visibility (pub(crate) vs. pub) helps.
  • Slower iteration in some cases. A change that affects both bbs-core and bbs-web is a coordinated change across two crates rather than one. In practice this is fine - small, stable interfaces don't require coordinated changes often.

Neutral

  • The web plugin can declare dependencies on other plugins. For example, certain admin features may require the mesh transport to be loaded (e.g., "send a test mesh DM"). The supervisor resolves these at startup and refuses to start if dependencies aren't satisfied.

Notes

This decision validates by construction. The first time we find something the web plugin needs and the plugin API doesn't support, that's a signal to extend the API - and that extension benefits every other plugin.

Released under the Apache 2.0 + Commons Clause License.