T08 gate: integration tests for all Phase 6 artifacts (EnvelopeEmissionContract, InteractionReportingContract, WidgetAdapterSpec, adapter assignment, dashboard coverage logic); SCOPE.md updated to Phase 6 complete; docs/phase6-summary.md written (contract model, adapter pattern, known limitations, Phase 7 readiness); workplan IHUB-WP-0006 marked done. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5.8 KiB
IHF Phase 6 Summary — Cross-Framework UI Adaptation Layer
What Was Built
Phase 6 ensures that widget identity, interaction capture, and annotation capability are preserved when UI components are authored outside of IHP HSX — React, Vue, or any JS-based component — without bypassing the IHF core.
Contract Artifacts
EnvelopeEmissionContract (envelope_emission_contracts)
- Formalises the widget envelope as a versioned, immutable contract
- v1.0 seed: required
["data-widget-id", "data-view-context", "data-hub-id"], optional["data-policy-scope", "data-widget-version"] widgetEnvelopehelper updated to validate required attributes at render time (non-crashing warning)- Read-only controller + index/show pages under "Contracts" nav
InteractionReportingContract (interaction_reporting_contracts)
- Standardised REST interface contract for external event and annotation submission
- v1.0 seed: endpoint
/api/v1/interaction-events, accepted event types["clicked","viewed","submitted","dismissed","errored"], required fields["widget_id","hub_id","event_type","occurred_at"] - Read-only controller + index/show pages
WidgetAdapterSpec (widget_adapter_specs)
- Registry of supported UI framework adapters; links to envelope and reporting contracts
- Status lifecycle:
draft → active → deprecated - Full CRUD controller (no delete — audit artifact)
- Widgets gain an optional
adapter_spec_idFK (null = native IHP widget)
REST API
POST /api/v1/interaction-events — JSON body, Bearer token auth (per-hub api_key):
- Validates payload against active
InteractionReportingContract - Creates
InteractionEventrecord - Returns
201 Createdwith{id, widget_id, event_type, occurred_at}or422with errors - Custom
CanRoute/HasPathinstances (not AutoRoute) for the/api/v1/path prefix
JavaScript Client
static/js/ihf-annotation-launcher.js — vanilla JS IIFE (no framework dependency):
- Scans DOM for
[data-widget-id]onDOMContentLoaded - Injects inline "annotate" trigger adjacent to each enrolled element
- On click: opens an inline form (textarea + category select) and POSTs to existing
/widgets/:widgetId/annotationsendpoint - Works in React-rendered pages where IHP does not own the DOM
- Feature-flagged via
IHP_ANNOTATION_LAUNCHER=trueenv var (AnnotationLauncherEnabledtyped config)
static/js/ihf-react-adapter.js — ESM module for React 18+:
useWidgetEnvelope(widgetId, hubId, viewContext)— returnsdata-*props conforming to envelope v1.0withWidgetEnvelope(WrappedComponent, widgetId, hubId, viewContext)— HOC wrapping root elementuseInteractionReporter(widgetId, hubId)— returnsreportEvent(type)POSTing to/api/v1/interaction-events
static/ihf-react-test.html — static test fixture with CDN React 18, a React-rendered widget, and an IHP-rendered widget on the same page, both enrolled with the annotation launcher.
Dashboards and Navigation
Adapter Compatibility Dashboard (AdapterCompatibilityDashboardAction { hubId }):
- Panel 1: Adapter spec counts by status (active / draft / deprecated)
- Panel 2: Widget coverage — native IHP vs adapter-backed with percentage bar and per-spec breakdown
- Panel 3: Active contract versions in use (envelope + reporting)
- Panel 4: Unassigned widgets (no
adapter_spec_id) - Panel 5: Active adapter specs table with widget counts
autoRefresh— live-updates on any DB change- Linked from hub Show page and global nav ("Adapters")
Widget show page renders an adapter badge (purple, links to spec) when adapter_spec_id is set.
Contract Model
Contracts are immutable once active. A new version supersedes the old; old versions remain readable for audit. The status lifecycle is:
draft → active → superseded (EnvelopeEmissionContract, InteractionReportingContract)
draft → active → deprecated (WidgetAdapterSpec)
This prevents retroactive mutation of the rules that governed historical interaction events.
Adapter Pattern
Any UI technology participates by honouring three obligations:
- Envelope — root DOM element carries
data-widget-id,data-view-context,data-hub-id - Event reporting — interactions submitted to
POST /api/v1/interaction-eventswith bearer auth - Annotation — annotation launcher picks up
data-widget-idautomatically; no adapter-specific code required
Native IHP widgets are unaffected. adapter_spec_id is nullable. The widgetEnvelope helper continues to work unchanged.
Known Limitations
- No local JS build toolchain.
ihf-react-adapter.jsis a plain ESM module; consumers must bundle it themselves (e.g. Vite, esbuild). Phase 6 does not add npm/webpack to the IHP project. - Bearer token scope. The per-hub
api_keyis a simple secret stored inhubs.api_key. Phase 8 (federated) should replace this with OAuth or a proper token management service. - Annotation launcher requires no CSP violations. The inline form approach works in permissive CSP environments; strict
script-srcpolicies may require nonce injection. - Cross-machine consistency checks. The State Hub consistency checker runs on the server and cannot reach the local repo filesystem; C-01/C-07 errors in the check output are path false-positives for this deployment topology.
Phase 7 Readiness
Phase 6 leaves the IHF core stable and extensible:
- All Phase 6 contracts are versioned and immutable — Phase 7 can introduce v1.1 contracts without breaking existing adapters
- Widget
adapter_spec_idis nullable and indexed — adapter assignment is opt-in - The REST API surface is defined by the
InteractionReportingContractrecord — contract evolution is data-driven - The JS adapter is a thin ESM module — framework-specific adapters for Vue, Web Components, etc. follow the same pattern as
ihf-react-adapter.js