+
+
+
+ Vendors
+
+
+ Model Types
+
+
+ Models
+
+
+
+
+ {{ tab.tab }}
+
+
+
+
+
+```
+
+**Key behavior:**
+- Routes registered via `router.addRoute()` persist for the session
+- Components lazy-load when the tab is clicked (code-splitting preserved)
+- Tabs only appear if the plugin is enabled and staged
+
+## Consequences
+
+### Benefits
+
+**Lean builds work:** A build with `SITE_PLUGINS=machines,printers` doesn't stage the computers plugin, so its component doesn't exist. The frontend fetches zero routes from `/api/plugins/settings-routes`, no tabs appear, and no imports fail.
+
+**Plugins self-contain their UI:** Adding a settings page to a plugin no longer requires touching core code. The plugin declares the route; the framework discovers it.
+
+**Runtime flexibility:** Enabling/disabling a plugin (via `/api/plugins/{name}` PUT) changes what routes are returned. After an app restart, the tabs appear or disappear automatically.
+
+**Composability:** Multiple plugins can contribute to the same hub. Model Catalog could have tabs from `computers` (Operating Systems), `printers` (Printer Models), `machines` (Machine Types), etc., with no conflicts.
+
+### Trade-offs
+
+**Route registration is async:** The tabs appear after mount, not at router initialization. In practice this is <100ms and visually seamless, but the router doesn't know about plugin routes at build time.
+
+**Component paths are strings:** The `component` field is a string path that gets interpolated into `import()`. This prevents static analysis tools from tracing the dependency, though Vite's glob imports during the plugin staging step ensure the files are bundled.
+
+**No SSR for plugin tabs:** Server-side rendering can't evaluate `router.addRoute()` at build time. ShopDB doesn't use SSR, so this is not a concern, but it would block SSR adoption without refactoring.
+
+**Hub pages need updates:** Every settings hub that wants to support plugin tabs must fetch `/api/plugins/settings-routes` and register them. The pattern is copy-paste (see ModelCatalogHub), but it's not automatic.
+
+## Alternatives Considered
+
+### 1. Conditional imports at build time
+
+Use Vite's conditional imports based on `SITE_PLUGINS`:
+
+```javascript
+const routes = [
+ { path: 'vendors', component: () => import('../../views/settings/VendorsList.vue') },
+ { path: 'types', component: () => import('../../views/settings/ModelTypesList.vue') },
+]
+
+if (import.meta.env.VITE_SITE_PLUGINS.includes('computers')) {
+ routes.push({
+ path: 'operatingsystems',
+ component: () => import('../../.plugins-staged/computers/views/OperatingSystemsList.vue')
+ })
+}
+```
+
+**Rejected:** Still requires core code to know about every plugin's routes. Not composable - adding a new plugin means editing core.
+
+### 2. Plugin manifests drive build-time codegen
+
+Each plugin has a `manifest.json` with routes. The build reads all manifests and generates a `plugin-routes.js` that imports components and exports merged routes.
+
+**Rejected:** Build-time coupling is tighter than runtime. Changing a plugin's routes requires rebuilding the entire frontend. Dynamic enable/disable doesn't work without a rebuild.
+
+### 3. Frontend plugin discovery via import.meta.glob
+
+Use Vite's glob imports to discover all plugin components:
+
+```javascript
+const pluginComponents = import.meta.glob('../.plugins-staged/*/views/*.vue')
+```
+
+**Rejected:** Discovery is too broad. Not every Vue file in a plugin is a settings page, and the hub doesn't know what tab label to show or where to mount it without metadata. You'd still need a manifest or a naming convention, which is equivalent to declaring the routes explicitly.
+
+## Implementation
+
+**Commits:**
+- `fe09d2745e` - Plugin dynamic settings routes architecture (backend hook + frontend registration)
+- `d59d0c3194` - Add `/api/plugins/settings-routes` to OpenAPI spec
+
+**Files changed:**
+- `shopdb/plugins/base.py` - Added `get_settings_routes()` hook
+- `plugins/computers/plugin.py` - Implemented hook for Operating Systems tab
+- `shopdb/core/api/plugins.py` - Added `/api/plugins/settings-routes` endpoint
+- `frontend/src/views/settings/ModelCatalogHub.vue` - Fetch and register plugin routes
+- `frontend/src/router/routes/core.js` - Removed hardcoded computers plugin import
+- `docs/openapi.json` - Documented new endpoint
+
+**Tests:** Gate tests pass (`test_openapi_spec.py`, `test_public_read_surface.py`).
+
+## Future Extensions
+
+**Other settings hubs:** AssetConfigHub, SecurityHub, NetworkHub, etc. can adopt the same pattern. The fetch/register logic is hub-specific (filter by `hub` field), so each hub only sees its own tabs.
+
+**Nested routes:** A plugin could return routes with `children: [...]` for nested settings pages. Router supports this via `addRoute(parentName, childRoute)`.
+
+**Permissions:** The `tab` could include a `permission` field (e.g., `"computers.manage_os"`), and the frontend filters out tabs the user can't access. The backend endpoint is anonymous, so permission checks would be client-side only (acceptable for UI gating; the API endpoint enforces permission anyway).
+
+**Icons:** Add an `icon` field to the route metadata (e.g., `"mdi-microsoft-windows"`), and the tab renders the icon next to the label.
+
+**Plugin-specific hubs:** A plugin could declare `hub: "my-plugin-hub"` and render its own hub page, not just tabs within core hubs. The router would need to discover and register hub pages too.
+
+## References
+
+- ADR-001: Asset as platform contract (plugin architecture principles)
+- ADR-013: Plugin catalog and lean builds (problem context)
+- PR #XXX: Plugin dynamic settings routes (implementation)
+- Issue #XXX: Lean builds fail with hardcoded plugin imports
diff --git a/docs/wiki-drafts/Architectural-Findings-2026-09-11.md b/docs/wiki-drafts/Architectural-Findings-2026-09-11.md
new file mode 100644
index 00000000..0b611847
--- /dev/null
+++ b/docs/wiki-drafts/Architectural-Findings-2026-09-11.md
@@ -0,0 +1,443 @@
+# Architectural Findings - September 11, 2026
+
+**Scope:** Full codebase analysis via Gortex semantic code intelligence
+**Severity:** 22 findings (6 critical, 6 high, 10 medium)
+**Status:** Documented for remediation planning
+
+## Executive Summary
+
+Gortex semantic analysis identified architectural issues in plugin boundaries, dependency management, and data schema consistency. Most critical: circular dependencies between plugins violate the plugin isolation model, and schema drift between SQLAlchemy models and Alembic migrations creates deployment risks.
+
+**Key theme:** Plugin architecture (ADR-001, ADR-008) is well-designed but not consistently enforced. Code grew organically with cross-plugin imports, shared state leakage, and implicit coupling that violates the self-contained plugin contract.
+
+## Critical Findings (P0)
+
+### 1. Circular Dependencies Between Plugins
+
+**Risk:** Plugin load order matters. Enabling/disabling plugins can break others.
+
+**Evidence:**
+- `computers` plugin imports from `machines` plugin: `from plugins.machines.models import Machine`
+- `machines` plugin imports from `computers` plugin: `from plugins.computers.models import Computer`
+- `printers` plugin imports from `computers` plugin
+- `endpoints` plugin imports from `computers`, `printers`, and `usb` plugins
+
+**Impact:**
+- Violates plugin isolation (ADR-001: plugins are independent extensions)
+- Disabling `computers` breaks `machines`, `printers`, `endpoints`
+- Plugin dependency chain not declared in plugin metadata
+- Import errors at runtime if plugins loaded in wrong order
+
+**Root cause:** Plugins use each other's models for relationships (e.g., Computer controls Machine).
+
+**Proper pattern:** Relationship via `Asset` base class
+```python
+# BAD: Direct plugin-to-plugin import
+from plugins.machines.models import Machine
+
+# GOOD: Relationship via Asset ID, resolve at runtime
+machine_asset_id = db.Column(db.Integer, db.ForeignKey('asset.id'))
+machine = relationship('Asset', foreign_keys=[machine_asset_id])
+
+# In plugin manifest, declare dependency
+dependencies = ['machines'] # Explicit, not implicit
+```
+
+**Remediation:**
+1. Audit all `from plugins.