From 37c764ba8debbb11b0a0fd9e5440991821144e5b Mon Sep 17 00:00:00 2001 From: cproudlock Date: Sat, 18 Jul 2026 23:26:17 -0400 Subject: [PATCH] ADR-013 Phase 4: move hardcoded plugin top-level routes into plugin route files Core-router surgery (the Phase 4 prerequisite for lean builds): index.js hardcoded six plugin-owned full-screen routes (parts-kiosk, TV, printer-qr x2, usb-labels, printedparts-labels), so pruning any of those plugins broke the SPA build on an unresolvable import. The router now also collects a `toplevel` named export from each plugin route file (alongside the existing default = AppLayout children) and spreads it into the top-level routes. Each of the six routes moved into its owning plugin's route file (printedparts, printers, usb, slides); index.js keeps only the core print pages that span asset types (machine-badge, asset-label, asset-label-batch). index.js now references zero plugin view components. Verified: all six route paths are present in the built bundle and the moved routes resolve exactly like the unchanged core print routes. Build + vitest + naming green. --- frontend/src/router/index.js | 52 +++++----------------- frontend/src/router/routes/printedparts.js | 20 +++++++++ frontend/src/router/routes/printers.js | 15 +++++++ frontend/src/router/routes/slides.js | 9 ++++ frontend/src/router/routes/usb.js | 9 ++++ 5 files changed, 63 insertions(+), 42 deletions(-) create mode 100644 frontend/src/router/routes/slides.js diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 9d60bb9..ba99a3e 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -7,9 +7,13 @@ import { loadEnabledPlugins, isPluginEnabled } from '../composables/enabledPlugi import { useToast } from '../composables/toast' import { getFacilityName } from '../utils/siteSettings' -// Auto-discover all route modules from routes/ directory +// Auto-discover all route modules from routes/ directory. A module's default +// export is AppLayout child routes; an optional `toplevel` export is full-screen +// routes (kiosk, print pages) that live OUTSIDE AppLayout. Both let a plugin own +// its routes, so pruning the plugin removes them with no core edit (ADR-013). const routeModules = import.meta.glob('./routes/*.js', { eager: true }) const rawChildren = Object.values(routeModules).flatMap(m => m.default) +const pluginTopLevel = Object.values(routeModules).flatMap(m => m.toplevel || []) // Gather the settings pages (spread across plugin route files) and nest them // under a single two-pane shell so the grouped rail stays put while the right @@ -66,21 +70,11 @@ const routes = [ name: 'shopfloor', component: () => import('../views/ShopfloorDashboard.vue') }, - { - // Touch kiosk for taking 3D-printed parts: scan bin, scan badge, keypad. - // Open on purpose - see the decision record in the printedparts proposal. - path: '/parts-kiosk', - name: 'parts-kiosk', - component: () => import('../views/printedparts/PartsKiosk.vue'), - meta: { plugin: 'printedparts' } - }, - { - path: '/tv', - name: 'tv', - component: () => import('../views/TVDashboard.vue'), - meta: { plugin: 'slides' } - }, - // Print pages (standalone, no sidebar/header) + // Plugin-owned full-screen routes (kiosk, TV, plugin print pages) are + // contributed by each plugin's route file via its `toplevel` export. + ...pluginTopLevel, + // Core print pages (standalone, no sidebar/header) - span multiple asset + // types or are core-owned, so they stay here. { path: '/print/machine-badge/:id', name: 'print-machine-badge', @@ -100,32 +94,6 @@ const routes = [ name: 'print-asset-label-batch', component: () => import('../views/print/AssetLabelBatch.vue') }, - { - path: '/print/printer-qr', - name: 'print-printer-qr-batch', - component: () => import('../views/print/PrinterQRBatch.vue'), - meta: { plugin: 'printers' } - }, - { - path: '/print/printer-qr/:id', - name: 'print-printer-qr-single', - component: () => import('../views/print/PrinterQRSingle.vue'), - meta: { plugin: 'printers' } - }, - { - path: '/print/usb-labels', - name: 'print-usb-labels', - component: () => import('../views/print/USBLabelBatch.vue'), - meta: { plugin: 'usb' } - }, - { - // Unlike the other print pages this one requires login: it lists the - // whole catalog, which is printedparts.view-gated at the API. - path: '/print/printedparts-labels', - name: 'print-printedparts-labels', - component: () => import('../views/print/PrintedPartsLabels.vue'), - meta: { requiresAuth: true, plugin: 'printedparts' } - }, { path: '/', component: AppLayout, diff --git a/frontend/src/router/routes/printedparts.js b/frontend/src/router/routes/printedparts.js index 8b19b25..91f6066 100644 --- a/frontend/src/router/routes/printedparts.js +++ b/frontend/src/router/routes/printedparts.js @@ -39,3 +39,23 @@ export default [ meta: { requiresAuth: true, requiresAdmin: true, plugin: 'printedparts' } } ] + +// Full-screen routes (outside AppLayout), collected by the router's `toplevel` +// pass so pruning printedparts removes them without a core edit (ADR-013). +export const toplevel = [ + { + // Touch kiosk for taking 3D-printed parts: scan bin, scan badge, keypad. + // Open on purpose - see the decision record in the printedparts proposal. + path: '/parts-kiosk', + name: 'parts-kiosk', + component: () => import('../../views/printedparts/PartsKiosk.vue'), + meta: { plugin: 'printedparts' } + }, + { + // Requires login: lists the whole catalog, printedparts.view-gated at the API. + path: '/print/printedparts-labels', + name: 'print-printedparts-labels', + component: () => import('../../views/print/PrintedPartsLabels.vue'), + meta: { requiresAuth: true, plugin: 'printedparts' } + } +] diff --git a/frontend/src/router/routes/printers.js b/frontend/src/router/routes/printers.js index 5bb001d..d58122a 100644 --- a/frontend/src/router/routes/printers.js +++ b/frontend/src/router/routes/printers.js @@ -40,3 +40,18 @@ export default [ meta: { requiresAuth: true, requiresAdmin: true, plugin: 'printers' } } ] + +export const toplevel = [ + { + path: '/print/printer-qr', + name: 'print-printer-qr-batch', + component: () => import('../../views/print/PrinterQRBatch.vue'), + meta: { plugin: 'printers' } + }, + { + path: '/print/printer-qr/:id', + name: 'print-printer-qr-single', + component: () => import('../../views/print/PrinterQRSingle.vue'), + meta: { plugin: 'printers' } + } +] diff --git a/frontend/src/router/routes/slides.js b/frontend/src/router/routes/slides.js new file mode 100644 index 0000000..110ffbf --- /dev/null +++ b/frontend/src/router/routes/slides.js @@ -0,0 +1,9 @@ + +export const toplevel = [ + { + path: '/tv', + name: 'tv', + component: () => import('../../views/TVDashboard.vue'), + meta: { plugin: 'slides' } + } +] diff --git a/frontend/src/router/routes/usb.js b/frontend/src/router/routes/usb.js index 3558327..5f56eb6 100644 --- a/frontend/src/router/routes/usb.js +++ b/frontend/src/router/routes/usb.js @@ -27,3 +27,12 @@ export default [ meta: { requiresAuth: true, plugin: 'usb' } } ] + +export const toplevel = [ + { + path: '/print/usb-labels', + name: 'print-usb-labels', + component: () => import('../../views/print/USBLabelBatch.vue'), + meta: { plugin: 'usb' } + } +]