# 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"]` - `widgetEnvelope` helper 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_id` FK (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 `InteractionEvent` record - Returns `201 Created` with `{id, widget_id, event_type, occurred_at}` or `422` with errors - Custom `CanRoute`/`HasPath` instances (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]` on `DOMContentLoaded` - Injects inline "annotate" trigger adjacent to each enrolled element - On click: opens an inline form (textarea + category select) and POSTs to existing `/widgets/:widgetId/annotations` endpoint - Works in React-rendered pages where IHP does not own the DOM - Feature-flagged via `IHP_ANNOTATION_LAUNCHER=true` env var (`AnnotationLauncherEnabled` typed config) **`static/js/ihf-react-adapter.js`** — ESM module for React 18+: - `useWidgetEnvelope(widgetId, hubId, viewContext)` — returns `data-*` props conforming to envelope v1.0 - `withWidgetEnvelope(WrappedComponent, widgetId, hubId, viewContext)` — HOC wrapping root element - `useInteractionReporter(widgetId, hubId)` — returns `reportEvent(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: 1. **Envelope** — root DOM element carries `data-widget-id`, `data-view-context`, `data-hub-id` 2. **Event reporting** — interactions submitted to `POST /api/v1/interaction-events` with bearer auth 3. **Annotation** — annotation launcher picks up `data-widget-id` automatically; 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.js` is 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_key` is a simple secret stored in `hubs.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-src` policies 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_id` is nullable and indexed — adapter assignment is opt-in - The REST API surface is defined by the `InteractionReportingContract` record — 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`