Say what a plugin settings tab can actually do, and catch a declaration that cannot work
Some checks failed
CI / backend (push) Has been cancelled
CI / naming (push) Has been cancelled
CI / gates (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / lean-build (push) Has been cancelled

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:
cproudlock
2026-09-16 09:57:51 -04:00
parent 066a19052b
commit 1a6ac67cab
8 changed files with 380 additions and 14 deletions

View File

@@ -59,6 +59,12 @@ export default [
// Plugin settings routes (e.g., Operating Systems from computers plugin) are added
// dynamically by ModelCatalogHub via router.addRoute() from /api/plugins/settings-routes.
// This allows plugins to extend settings hubs without breaking lean builds.
// This hub is the ONLY one that does it: no other hub page below fetches
// or registers plugin routes, so a plugin declaring their hub id gets a
// tab nobody draws. The server knows the same list
// (SETTINGS_HUBS_WITH_PLUGIN_TABS in shopdb/plugins/base.py) and drops
// anything outside it; adding a hub means writing the code into that
// hub's .vue file AND adding it there. See ADR-019.
]
},
{

View File

@@ -56,6 +56,17 @@ const route = useRoute()
const router = useRouter()
const pluginTabs = ref([])
// Plugin tabs (ADR-019). Two things this does not do, so nobody plans around
// them. The routes exist only after this hub has mounted, so a plugin tab is
// not deep-linkable and does not survive a refresh. And the import below has no
// static file extension, so vite:dynamic-import-vars refuses to rewrite it (it
// says so on every build) and the built chunk asks for a .vue file that dist
// does not ship: the tab loads under `npm run dev` and 404s in production. The
// pages behind these tabs are therefore expected to carry a static route of
// their own in the plugin's routes.js, which is what a site actually uses.
// Copying this block into another hub is what makes that hub support plugin
// tabs - and it must then be added to SETTINGS_HUBS_WITH_PLUGIN_TABS in
// shopdb/plugins/base.py, which is what the server filters on.
onMounted(async () => {
// Fetch plugin-contributed settings routes
try {