Say what a plugin settings tab can actually do, and catch a declaration that cannot work
Finding F12 of the architectural review, checked against the code and found worse than reported: the dynamic component import cannot be rewritten by the bundler, so the built file ships the unresolved path and a plugin tab answers 404 in every production build, staged or not. It works only under the dev server. Nothing shipping is affected, because the one plugin that declares a tab also registers the same page as an ordinary route, and that is the one sites reach. - The ADR draft now carries a "Constraints as implemented" section: only the model catalog hub registers plugin tabs, registration happens when that hub mounts so a tab is not deep-linkable and does not survive a refresh, and the build finding above with the bundler's own warning. The two ways out are stated as the open decision rather than taken. - The hook's docstring reads as instructions to a plugin author, and its example no longer names a path that does not exist at build time. - A route declared for a hub that cannot render it is dropped with an error naming the plugin, the hub and the hubs that do work, and the same is logged at startup. Enabling the plugin still succeeds: a tab nobody can reach is no reason to take a working backend offline. - Tests hold the two halves together: no bundled plugin may declare a hub the frontend does not implement, and the list in the server must equal the hubs that really register routes, so the two cannot drift apart again.
This commit is contained in:
@@ -14,6 +14,8 @@ This directory contains drafted wiki pages based on work completed Q3 2026. Copy
|
||||
- Decision: Backend hook + frontend dynamic route registration
|
||||
- Implementation: BasePlugin hook, /api/plugins/settings-routes endpoint, ModelCatalogHub integration
|
||||
- Benefits: Lean builds work, plugins self-contain UI, runtime flexibility
|
||||
- Constraints as implemented: Model Catalog is the only hub that draws plugin
|
||||
tabs, the tabs are not deep-linkable, and the component is bound at build time
|
||||
|
||||
**Related commits:** fe09d2745e, d59d0c3194
|
||||
|
||||
@@ -109,12 +111,36 @@ class ComputersPlugin(BasePlugin):
|
||||
|
||||
The frontend fetches routes from `/api/plugins/settings-routes` and registers them dynamically via `router.addRoute()`.
|
||||
|
||||
### Rules for declaring one
|
||||
|
||||
1. **Declare `hub: 'model-catalog'` or declare nothing.** Model Catalog is the
|
||||
only hub whose page registers plugin tabs. A route naming any other hub
|
||||
(`asset-config`, `security`, `network`, ...) is dropped by the API with an
|
||||
error in the log, because nothing would draw it. For a settings page that
|
||||
belongs elsewhere, own a full page in `plugins/<name>/frontend/routes.js`
|
||||
instead: it is statically registered and behaves like any other route.
|
||||
2. **Your frontend must be staged into the build.** `component` is a path
|
||||
relative to `frontend/src`, so it must be the staged copy -
|
||||
`.plugins-staged/<name>/views/X.vue`, made by `scripts/stage-frontend.mjs`
|
||||
from `plugins/<name>/frontend/views/X.vue`. Enabling the plugin at runtime
|
||||
does not put the file in a bundle that was built without it; that needs a
|
||||
frontend rebuild.
|
||||
3. **Register the page in your `routes.js` too, and treat that as the real
|
||||
route.** The hub's import of your component is not rewritten by Vite (no
|
||||
static file extension in the template), so it resolves under `npm run dev`
|
||||
and 404s in a production build. The tab is a convenience on top of a normal
|
||||
plugin route, not a way to ship a page. A gate test enforces this.
|
||||
4. **Do not link directly to your tab's URL.** The hub registers the route when
|
||||
it mounts, so the URL only resolves for someone who reached it through the
|
||||
hub. A bookmark, a link in an email, or a refresh on the tab lands nowhere.
|
||||
|
||||
**Benefits:**
|
||||
- Lean builds work (no hardcoded plugin imports)
|
||||
- Plugins self-contain their UI extensions
|
||||
- Runtime flexibility (enable/disable plugin affects tabs)
|
||||
- Enable/disable changes the tabs, for a plugin already in the build
|
||||
|
||||
**Reference:** ADR-019 Plugin Dynamic Settings Routes
|
||||
**Reference:** ADR-019 Plugin Dynamic Settings Routes, section "Constraints as
|
||||
implemented"
|
||||
```
|
||||
|
||||
---
|
||||
@@ -125,9 +151,9 @@ The frontend fetches routes from `/api/plugins/settings-routes` and registers th
|
||||
```markdown
|
||||
## GET /api/plugins/settings-routes
|
||||
|
||||
Returns settings routes contributed by all enabled plugins via `get_settings_routes()` hook.
|
||||
Returns settings routes contributed by all enabled plugins via `get_settings_routes()` hook. A route naming a hub that does not draw plugin tabs is dropped, with an error in the server log naming the plugin and the hub.
|
||||
|
||||
**Auth:** None (jwt-optional, deliberately anonymous for kiosk/TV displays)
|
||||
**Auth:** `settings.view` (it names the enabled plugins, the hub layout and the internal component paths; no pre-login page calls it)
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
@@ -147,7 +173,7 @@ Returns settings routes contributed by all enabled plugins via `get_settings_rou
|
||||
}
|
||||
```
|
||||
|
||||
**Used by:** Settings hub pages (ModelCatalogHub, AssetConfigHub, etc.) to dynamically register plugin tabs.
|
||||
**Used by:** ModelCatalogHub, to register plugin tabs. It is the only hub page that does; the others draw their own tabs only.
|
||||
|
||||
**Reference:** ADR-019 Plugin Dynamic Settings Routes
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user