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.
This commit is contained in:
@@ -7,9 +7,13 @@ import { loadEnabledPlugins, isPluginEnabled } from '../composables/enabledPlugi
|
|||||||
import { useToast } from '../composables/toast'
|
import { useToast } from '../composables/toast'
|
||||||
import { getFacilityName } from '../utils/siteSettings'
|
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 routeModules = import.meta.glob('./routes/*.js', { eager: true })
|
||||||
const rawChildren = Object.values(routeModules).flatMap(m => m.default)
|
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
|
// 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
|
// under a single two-pane shell so the grouped rail stays put while the right
|
||||||
@@ -66,21 +70,11 @@ const routes = [
|
|||||||
name: 'shopfloor',
|
name: 'shopfloor',
|
||||||
component: () => import('../views/ShopfloorDashboard.vue')
|
component: () => import('../views/ShopfloorDashboard.vue')
|
||||||
},
|
},
|
||||||
{
|
// Plugin-owned full-screen routes (kiosk, TV, plugin print pages) are
|
||||||
// Touch kiosk for taking 3D-printed parts: scan bin, scan badge, keypad.
|
// contributed by each plugin's route file via its `toplevel` export.
|
||||||
// Open on purpose - see the decision record in the printedparts proposal.
|
...pluginTopLevel,
|
||||||
path: '/parts-kiosk',
|
// Core print pages (standalone, no sidebar/header) - span multiple asset
|
||||||
name: 'parts-kiosk',
|
// types or are core-owned, so they stay here.
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
path: '/print/machine-badge/:id',
|
path: '/print/machine-badge/:id',
|
||||||
name: 'print-machine-badge',
|
name: 'print-machine-badge',
|
||||||
@@ -100,32 +94,6 @@ const routes = [
|
|||||||
name: 'print-asset-label-batch',
|
name: 'print-asset-label-batch',
|
||||||
component: () => import('../views/print/AssetLabelBatch.vue')
|
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: '/',
|
path: '/',
|
||||||
component: AppLayout,
|
component: AppLayout,
|
||||||
|
|||||||
@@ -39,3 +39,23 @@ export default [
|
|||||||
meta: { requiresAuth: true, requiresAdmin: true, plugin: 'printedparts' }
|
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' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|||||||
@@ -40,3 +40,18 @@ export default [
|
|||||||
meta: { requiresAuth: true, requiresAdmin: true, plugin: 'printers' }
|
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' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|||||||
9
frontend/src/router/routes/slides.js
Normal file
9
frontend/src/router/routes/slides.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
export const toplevel = [
|
||||||
|
{
|
||||||
|
path: '/tv',
|
||||||
|
name: 'tv',
|
||||||
|
component: () => import('../../views/TVDashboard.vue'),
|
||||||
|
meta: { plugin: 'slides' }
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -27,3 +27,12 @@ export default [
|
|||||||
meta: { requiresAuth: true, plugin: 'usb' }
|
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' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user