Accept + implement ADR-010 frontend plugin hooks (contract 0.7.0)
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:
@@ -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) |
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user