Accept + implement ADR-010 frontend plugin hooks (contract 0.7.0)
Some checks failed
CI / backend (push) Failing after 1m2s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s

Four data-only hooks on BasePlugin (get_settings_cards,
get_asset_panels, get_map_overlays, get_asset_presentation) with a
GET-only /api/pluginui consumer surface copying the dashboard-widgets
semantics. Pilots: warranty declares its asset panel; measuringtools
supplies its settings card, presentation, and calibration overlay -
the last hardcoded settings-nav entry is now hook-sourced. Generic
renderers for panels/overlays/presentation deferred per the ADR's
incremental adoption plan (documented in CONTRACT-STABILITY.md).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-07-11 19:38:32 -04:00
parent 9eed3745fc
commit 24f67d6ac5
17 changed files with 625 additions and 19 deletions

View File

@@ -8,7 +8,7 @@ the live code, not aspiration. The authoritative hook reference is
## Current version
The plugin contract is at **0.6.0**, declared in `shopdb/__init__.py` as
The plugin contract is at **0.7.0**, declared in `shopdb/__init__.py` as
`__contract_version__`. It is pre-1.0, which under semver means any 0.x minor
bump is allowed to break the contract, and this project has used that latitude.
@@ -25,8 +25,9 @@ Recorded in the comment block in `shopdb/__init__.py`:
| 0.3.0 | `shopdb.api` expanded to the full plugin import surface (db, cache, model bases, core models, response + pagination helpers, `employee_connection`) so plugins stop importing internal core paths | additive (minor) |
| 0.4.0 | Removed the never-implemented `get_searchable_fields` hook (search is a core concern over the asset model) and wired `get_dashboard_widgets` to a real consumer (`/api/dashboard/widgets`) | pre-1.0 contract reduction |
| 0.6.0 | Added the `get_reports` hook, consumed by `GET /api/reports` to merge plugin report cards into the Reports hub | additive optional hook (minor) |
| 0.7.0 | Added the four ADR-010 frontend-contribution hooks (`get_settings_cards`, `get_asset_panels`, `get_map_overlays`, `get_asset_presentation`), consumed by the `GET /api/pluginui/*` endpoints | additive optional hooks (minor) |
The source comment block documents 0.3.0, 0.4.0, and 0.6.0. Earlier points
The source comment block documents 0.3.0, 0.4.0, 0.6.0, and 0.7.0. Earlier points
(0.1.x / 0.2.x) predate that recorded rationale; `PluginMeta`'s fallback
`core_version` default of `>=0.2.0,<1.0.0` is the only remaining trace of the
0.2 baseline.
@@ -49,6 +50,7 @@ land with a new or amended ADR.
| `get_navigation_items` | Sidebar menu entries |
| `get_dashboard_widgets` | Dashboard widgets, consumed by `/api/dashboard/widgets` |
| `get_reports` | Report cards, consumed by `/api/reports` (added 0.6.0) |
| Frontend-contribution hooks | `get_settings_cards`, `get_asset_panels`, `get_map_overlays`, `get_asset_presentation`, consumed by `/api/pluginui/*` (added 0.7.0, [ADR-010](adr/ADR-010-frontend-plugin-hooks.md)) |
| Collector pair | `get_collector_schema` + `apply_collector_payload` per [ADR-006](adr/ADR-006-collector-contract.md) |
| Settings helpers | `get_setting` / `set_setting`, namespaced to the plugin |
| `get_provisioning_note` | Setup-wizard transparency note for extra tables |
@@ -65,7 +67,7 @@ Known-unstable areas. Building on these means expecting rework.
| Area | Status | Reference |
|------|--------|-----------|
| Frontend hook contract | Not defined yet. There is no server-side hook for asset-detail panels, map markers, or search-result rendering. A plugin that needs custom UI still hand-edits the Vue frontend. This is the single biggest gap. [ADR-010](adr/ADR-010-frontend-plugin-hooks.md) proposes the path: data-only declarative hooks (`get_settings_cards`, `get_asset_panels`, `get_map_overlays`, `get_asset_presentation`) rendered by generic core components, with build-time glob discovery deferred for real components. | [ADR-010](adr/ADR-010-frontend-plugin-hooks.md) (PROPOSED) |
| Frontend renderers (residual) | The four data-only hooks and their `/api/pluginui/*` consumers are settled (0.7.0). The generic core renderers are landing incrementally: the settings-cards rail/landing renderer ships with 0.7.0; the asset-panel, map-overlay, and search-presentation renderers are wired opt-in per the ADR adoption plan. Real component-backed panels (bespoke charts, custom overlays) remain deferred to Option C (build-time glob discovery). | [ADR-010](adr/ADR-010-frontend-plugin-hooks.md) (ACCEPTED) |
| Per-plugin migrations | Brand new. The per-plugin Alembic engine exists and every bundled plugin now carries a chain, but the pattern has one release of production mileage, not years. | [ADR-008](adr/ADR-008-plugin-migration-ownership.md) (2026-07-10) |
| Pip distribution | Deferred to v2. External plugins install by clone / submodule / symlink; there is no entry-point discovery and no automatic update path yet. | [ADR-003](adr/ADR-003-plugin-distribution.md) |

View File

@@ -9,7 +9,7 @@ The contract is locked in [ADR-001](../docs/adr/ADR-001-asset-as-platform-contra
The framework declares its contract version in `shopdb/__init__.py`:
```python
__contract_version__ = '0.6.0'
__contract_version__ = '0.7.0'
```
Each plugin's `manifest.json` declares the range of contract versions it supports:
@@ -216,6 +216,98 @@ Consumed by `GET /api/reports`, which merges plugin cards after the static core
reports sorted into category groups by the frontend (disabled plugins are
skipped; a broken plugin is isolated in prod, re-raised in dev/test).
### `get_settings_cards() -> List[Dict]`
Returns settings-catalog card definitions. Added in contract 0.7.0 (ADR-010).
Each card is merged into the settings rail and landing overview without the
plugin hand-editing the core `settingsNav.js` catalog. `icon` is a string key
mapped to a Lucide component core-side, exactly like `get_navigation_items`.
```python
class MeasuringToolsPlugin(BasePlugin):
def get_settings_cards(self):
return [{
'group': 'Measuring Tools', # rail group title (created if new)
'to': '/settings/measuringtooltypes',
'icon': 'ruler', # string key, mapped core-side
'title': 'Measuring Tool Types',
'description': 'Manage measuring-tool subtypes + map colors',
'position': 22, # order within the group
}]
```
Consumed by `GET /api/pluginui/settings-cards`, which merges enabled plugins'
cards into the core catalog (disabled plugins are skipped; a broken plugin is
isolated in prod, re-raised in dev/test).
### `get_asset_panels() -> List[Dict]`
Returns asset-detail extension-panel definitions. Added in contract 0.7.0
(ADR-010). A generic core `AssetPanel` component renders each panel on the
matching detail pages, fetching the panel's `endpoint`. This replaces
hand-composing a plugin panel component into each detail view.
```python
class WarrantyPlugin(BasePlugin):
def get_asset_panels(self):
return [{
'id': 'warranty',
'title': 'Warranty',
'assettypes': ['*'], # detail pages it appears on; ['*'] = all
'endpoint': '/api/warranty/asset/{assetid}',
'render': 'table', # 'keyvalue' | 'table' | 'badge'
'position': 30,
}]
```
Consumed by `GET /api/pluginui/asset-panels?assetid=<id>`, which returns the
panels whose `assettypes` match that asset's type (disabled plugins skipped;
broken plugin isolated in prod, re-raised in dev/test). A panel that needs
bespoke UI (a chart) is out of scope for this data-only hook.
### `get_map_overlays() -> List[Dict]`
Returns shop-floor map overlay/decoration definitions. Added in contract 0.7.0
(ADR-010). The map stays data-driven off asset types + positions; an overlay
adds decoration data (a badge or ring) plus an optional legend entry, with no
plugin-side map code.
```python
class MeasuringToolsPlugin(BasePlugin):
def get_map_overlays(self):
return [{
'id': 'calibration-due',
'label': 'Calibration due', # legend label
'endpoint': '/api/measuringtools/map-overlay', # -> [{assetid, color, label}]
'style': 'badge', # 'badge' | 'ring'
'legend': True,
}]
```
Consumed by `GET /api/pluginui/map-overlays` (disabled plugins skipped; broken
plugin isolated in prod, re-raised in dev/test).
### `get_asset_presentation() -> List[Dict]`
Returns asset-type presentation/routing definitions. Added in contract 0.7.0
(ADR-010). Declares how a plugin-owned asset type renders in global-search rows
and cross-links (which icon, which detail route), so core never hardcodes a
plugin's route or icon.
```python
class MeasuringToolsPlugin(BasePlugin):
def get_asset_presentation(self):
return [{
'assettype': 'measuring_tool', # AssetType.assettype key the plugin owns
'icon': 'ruler',
'label': 'Measuring Tool',
'route': '/measuringtools/{assetid}',
}]
```
Consumed by `GET /api/pluginui/asset-presentation` (disabled plugins skipped;
broken plugin isolated in prod, re-raised in dev/test).
### `get_provisioning_note() -> Optional[Dict]`
Transparency note the setup wizard shows the moment a site checks this plugin

View File

@@ -122,6 +122,10 @@ Override hooks on the plugin class as needed. See [PLUGIN-HOOKS.md](PLUGIN-HOOKS
| `get_navigation_items` | Plugin shows up in the sidebar nav |
| `get_dashboard_widgets` | Plugin's dashboard widget appears on the home page |
| `get_reports` | Plugin's report cards appear on the Reports hub |
| `get_settings_cards` | Plugin's card joins the settings rail + landing (no `settingsNav.js` edit) |
| `get_asset_panels` | Plugin panel renders on matching asset-detail pages |
| `get_map_overlays` | Plugin decorates shop-floor map markers + adds a legend entry |
| `get_asset_presentation` | Plugin declares its asset type's search icon + detail route |
| `get_collector_schema` + `apply_collector_payload` | Plugin accepts external pushes at `/api/collector/<name>` |
Each hook has a default that does nothing. Override only what your plugin needs.

View File

@@ -1,7 +1,8 @@
# ADR-010: Frontend plugin hook contract
- **Status:** PROPOSED
- **Status:** ACCEPTED
- **Date:** 2026-07-11
- **Accepted:** 2026-07-11
- **Deciders:** cproudlock
- **Supersedes:** none
@@ -112,7 +113,7 @@ not pursued.
## Decision
**PROPOSED:** adopt a hybrid. Add **data-only declarative hooks** (Option B) for
**DECISION:** adopt a hybrid. Add **data-only declarative hooks** (Option B) for
the four presentation surfaces a generic core renderer can serve, and keep
**file-convention glob discovery** (Option C) as the deferred mechanism for the
residual cases where a real component is unavoidable. Do not pursue runtime

View File

@@ -22,7 +22,7 @@ Each ADR captures a single architectural decision: the context, the decision its
| [007](ADR-007-product-versioning-and-releases.md) | Product versioning and releases | ACCEPTED |
| [008](ADR-008-plugin-migration-ownership.md) | Plugin migration ownership (per-plugin chains) | ACCEPTED |
| [009](ADR-009-frontend-plugin-gating.md) | Frontend plugin route gating | ACCEPTED |
| [010](ADR-010-frontend-plugin-hooks.md) | Frontend plugin hook contract | PROPOSED |
| [010](ADR-010-frontend-plugin-hooks.md) | Frontend plugin hook contract | ACCEPTED |
| [011](ADR-011-machines-rename.md) | Machines rename + modeltypes retyping | ACCEPTED |
## Authoring