--- id: IHUB-WP-0006 type: workplan title: "IHF Phase 6 — Cross-Framework UI Adaptation Layer" domain: inter_hub repo: inter-hub status: done owner: custodian topic_slug: inter_hub created: "2026-03-29" updated: "2026-03-29" state_hub_workstream_id: "7372340a-0fcb-4911-8831-2a55ed9069a4" --- # IHF Phase 6 — Cross-Framework UI Adaptation Layer ## Goal Ensure semantic continuity while the UI stack diversifies. Phase 5 established AI-assisted distillation within the IHP server-rendered surface. 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. All Phase 6 artifacts are formal contracts rather than free-form conventions. A widget that participates via an adapter must honour the same identity, traceability, and event-capture obligations as a native IHP widget. ## Background Phases 1–5 are complete. The IHF core (widget registry, interaction events, annotations, requirements, decisions, outcomes, agent assistance) is stable. The spec (§Phase 6) calls for: - widget protocol adapters - metadata emission standards - client-side SDKs or thin adapters - cross-framework annotation launcher - standardized interaction reporting interface Artifacts introduced: `WidgetAdapterSpec`, `InteractionReportingContract`, `EnvelopeEmissionContract`. Reference: `specs/InteractionHubFrameworkSpecification_v0.1.md` §Phase 6, `docs/ihp-overview.md`, `docs/ihp-controllers-views-forms.md`. ## Phase 6 Exit Criteria (from IHF spec §Phase 6) - New UI technologies can participate without bypassing the IHF core - Widget identity remains stable across frontend evolution - Annotations and interaction events remain compatible ## Data Artifacts Introduced (Phase 6) `WidgetAdapterSpec`, `InteractionReportingContract`, `EnvelopeEmissionContract` --- ## Tasks ### T01 — Schema: WidgetAdapterSpec, InteractionReportingContract, EnvelopeEmissionContract ```task id: IHUB-WP-0006-T01 status: done priority: high state_hub_task_id: "8d92f9d5-ec3c-4d9b-b16c-26f938a306e7" ``` Add Phase 6 tables to `Application/Schema.sql` and write migration: ```sql -- Describes how a specific UI technology (React, Vue, etc.) maps to IHF widget -- protocol obligations — identity, envelope emission, event reporting. CREATE TABLE widget_adapter_specs ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, name TEXT NOT NULL UNIQUE, -- e.g. "react-18", "vue-3", "web-component" framework TEXT NOT NULL, -- e.g. "react", "vue", "vanilla" version TEXT NOT NULL, -- adapter spec version, e.g. "1.0" envelope_contract_id UUID REFERENCES envelope_emission_contracts(id), reporting_contract_id UUID REFERENCES interaction_reporting_contracts(id), status TEXT NOT NULL DEFAULT 'draft', -- status values: draft | active | deprecated notes TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); CREATE INDEX widget_adapter_specs_framework_idx ON widget_adapter_specs (framework); CREATE INDEX widget_adapter_specs_status_idx ON widget_adapter_specs (status); -- Formalises the rules for how a widget envelope must be emitted: -- which attributes are required, their format, and version. CREATE TABLE envelope_emission_contracts ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, contract_version TEXT NOT NULL UNIQUE, -- e.g. "1.0", "1.1" required_attributes JSONB NOT NULL, -- e.g. ["data-widget-id", "data-view-context", "data-hub-id"] optional_attributes JSONB NOT NULL DEFAULT '[]', validation_rules JSONB NOT NULL DEFAULT '{}', -- machine-readable rules: format checks, presence guards description TEXT, status TEXT NOT NULL DEFAULT 'active', -- status values: draft | active | superseded created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); -- Standardised REST interface contract for external event and annotation -- submission — used by non-IHP adapters. CREATE TABLE interaction_reporting_contracts ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, contract_version TEXT NOT NULL UNIQUE, -- e.g. "1.0" endpoint_path TEXT NOT NULL, -- e.g. "/api/v1/interaction-events" accepted_event_types JSONB NOT NULL, -- e.g. ["clicked","viewed","submitted"] required_fields JSONB NOT NULL, -- minimum payload: widget_id, hub_id, event_type, occurred_at auth_scheme TEXT NOT NULL DEFAULT 'bearer', description TEXT, status TEXT NOT NULL DEFAULT 'active', created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); -- Link widgets to their adapter spec (null = native IHP widget). ALTER TABLE widgets ADD COLUMN adapter_spec_id UUID REFERENCES widget_adapter_specs(id); CREATE INDEX widgets_adapter_spec_id_idx ON widgets (adapter_spec_id); ``` **Exit criteria:** `migrate` runs cleanly; all Phase 6 types available in GHCi. --- ### T02 — EnvelopeEmissionContract: formalise widgetEnvelope as a versioned contract ```task id: IHUB-WP-0006-T02 status: done priority: high state_hub_task_id: "298af675-550b-480b-bed6-05efc79cd0c9" ``` 1. Seed the canonical v1.0 `EnvelopeEmissionContract` record in a migration: - `required_attributes: ["data-widget-id", "data-view-context", "data-hub-id"]` - `optional_attributes: ["data-policy-scope", "data-widget-version"]` - `validation_rules: {data-widget-id: "uuid", data-hub-id: "uuid"}` 2. Update the `widgetEnvelope` helper (`Web/View/Helpers.hs` or equivalent) to read the active contract version from DB (or config) and assert required attributes at render time — log a warning (not crash) if any are missing. 3. Add `EnvelopeEmissionContractsController`: - `index`: table of contract versions with status badges - `show`: full required/optional attributes and validation rules as formatted JSON panels - Read-only (contracts are immutable once active; a new version supersedes) 4. Link from global nav under "Contracts" **Exit criteria:** Active contract record exists in DB; widgetEnvelope validates against it; contract index/show pages render correctly. --- ### T03 — InteractionReportingContract: REST endpoint for external event submission ```task id: IHUB-WP-0006-T03 status: done priority: high state_hub_task_id: "f2767465-ff00-48be-b2dc-5bf3b179cca9" ``` 1. Seed the canonical v1.0 `InteractionReportingContract`: - `endpoint_path: "/api/v1/interaction-events"` - `accepted_event_types: ["clicked","viewed","submitted","dismissed","errored"]` - `required_fields: ["widget_id","hub_id","event_type","occurred_at"]` 2. Add `Api.InteractionEventsController` (separate from the web controller): - `POST /api/v1/interaction-events` — JSON body, Bearer token auth - Validate payload against the active `InteractionReportingContract` - Create `InteractionEvent` record - Return `201 Created` with `{id, widget_id, event_type}` or `422` with validation errors 3. Register the API route in `FrontController.hs` 4. Add `InteractionReportingContractsController` (read-only, same pattern as T02) **Exit criteria:** `POST /api/v1/interaction-events` with a valid payload creates an `InteractionEvent`; invalid payloads return `422`; contract show page renders. --- ### T04 — WidgetAdapterSpecsController and registry dashboard ```task id: IHUB-WP-0006-T04 status: done priority: high state_hub_task_id: "e84016d0-60c0-48cb-ad70-2c054d2530db" ``` 1. Scaffold `WidgetAdapterSpecsController`: - `index`: table of adapters — framework badge, version, status, envelope contract version, reporting contract version - `new` / `create`: register a new adapter spec - `show`: full detail — framework, version, linked contracts, notes, status - `edit` / `update`: update notes and status only (contracts are immutable once linked) - No delete — adapter specs are audit artifacts 2. Validation: - `name`, `framework`, `version` required - `status` must be `draft | active | deprecated` 3. On widget `new`/`edit` forms: optional `adapter_spec_id` select (null = native) 4. On widget show page: if `adapter_spec_id` present, show adapter badge with link to the spec **Exit criteria:** Adapter specs can be registered, listed, and viewed; widget form allows adapter assignment; widget show page renders adapter badge. --- ### T05 — Cross-framework annotation launcher (lightweight JS widget) ```task id: IHUB-WP-0006-T05 status: done priority: medium state_hub_task_id: "fea86955-d5e6-4623-b5cc-f422c266c9cf" ``` 1. Create `static/js/ihf-annotation-launcher.js` — a self-contained vanilla JS module (no framework dependency): - On `DOMContentLoaded`, scan for elements with `data-widget-id` attribute - Inject a small "annotate" trigger (button or icon) adjacent to each enrolled element - On trigger click: open a lightweight inline form (textarea + category select) and POST to `/annotations` (existing IHP endpoint) via `fetch` - On success: show a brief confirmation; on error: show inline error message - Reads `data-hub-id` from the element (or nearest ancestor) for the hub context 2. The launcher must work in React-rendered pages where IHP does not own the DOM — it relies solely on `data-widget-id` presence. 3. Include as an optional script tag in the IHP layout (`Web/View/Layout.hs`) with a feature flag (`IHP_ANNOTATION_LAUNCHER=true`) 4. Document usage in `docs/annotation-launcher.md` **Exit criteria:** Launcher script injects annotation triggers on a page with `data-widget-id` elements; annotation POST succeeds; works from a static HTML test page (not IHP-rendered). --- ### T06 — React adapter specification and reference example ```task id: IHUB-WP-0006-T06 status: done priority: medium state_hub_task_id: "023269d8-9835-40b4-a394-478a0f36eee0" ``` 1. Register a `react-18` `WidgetAdapterSpec` record (via migration seed or admin UI): - links to envelope v1.0 contract and reporting v1.0 contract - `status = active` 2. Create `static/js/ihf-react-adapter.js` — a thin React hook + HOC: - `useWidgetEnvelope(widgetId, hubId, viewContext)` — returns a `ref` and `data-*` props object conforming to the envelope contract - `withWidgetEnvelope(WrappedComponent, widgetId, hubId, viewContext)` — HOC that applies the envelope to the root DOM element - `useInteractionReporter(widgetId, hubId)` — returns a `reportEvent(type)` function that POSTs to `/api/v1/interaction-events` 3. Create `docs/react-adapter.md` with usage examples for all three exports 4. Add a test fixture page in `static/` demonstrating a React widget using the adapter alongside an IHP-rendered widget on the same page **Exit criteria:** `useWidgetEnvelope` emits correct `data-*` attributes; `reportEvent` reaches `/api/v1/interaction-events`; annotation launcher script picks up the React widget's `data-widget-id`; docs written. --- ### T07 — Adapter compatibility validation dashboard ```task id: IHUB-WP-0006-T07 status: done priority: medium state_hub_task_id: "dc8fa48a-7195-4410-a77e-717b53127c2e" ``` 1. Add `AdapterCompatibilityDashboardAction { hubId }` to `HubsController` (AutoRefresh): - **Adapter summary**: count of registered adapters by status (draft / active / deprecated) - **Widget coverage**: total widgets / native IHP / adapter-backed (per adapter spec), with percentage bars - **Contract versions in use**: which envelope and reporting contract versions are active - **Unassigned widgets**: widgets with no `adapter_spec_id` that have received events from external origins (heuristic: `user_agent` not matching IHP server) - **Stale adapters**: adapter specs with `status=active` but no widgets assigned in the last 30 days 2. Link from hub Show page alongside Triage / Governance / Antifragility / Agent dashboards 3. Add "Adapters" link to global nav **Exit criteria:** Dashboard renders all five panels; live-updates on widget or adapter changes; stale adapter detection works. --- ### T08 — Phase 6 gate: tests, consistency, docs ```task id: IHUB-WP-0006-T08 status: done priority: high state_hub_task_id: "90ea4814-7603-4016-be34-d41ae091f7e1" ``` 1. **Integration tests** (`Test/`): - EnvelopeEmissionContract create + fetch (required_attributes, validation_rules) - InteractionReportingContract create + fetch - `POST /api/v1/interaction-events` — valid payload creates InteractionEvent - `POST /api/v1/interaction-events` — missing required field returns 422 - WidgetAdapterSpec create + status transition (draft → active → deprecated) - Widget with adapter_spec_id: fetch + show renders adapter badge - Adapter compatibility dashboard: compiles and returns correct widget counts 2. **Consistency sync** via State Hub MCP: `check_repo_consistency(repo_slug="inter-hub", fix=True)` 3. **Documentation updates:** - Update `SCOPE.md` current state section: Phase 6 complete - Write `docs/phase6-summary.md`: what was built, contract model, adapter pattern, known limitations, Phase 7 readiness 4. **Smoke test checklist:** - Register a `react-18` adapter spec via UI - Assign a widget to the adapter - POST a test interaction event via `curl` to `/api/v1/interaction-events` - Verify event appears in widget show page - Open annotation launcher on a page with a React-backed widget - Confirm adapter compatibility dashboard shows correct coverage **Exit criteria:** All tests pass; consistency sync reports no errors; smoke test completed; SCOPE.md updated. --- ## Phase 6 Dependencies - Phases 1–5 schema stable (widget registry, interaction events, and annotation model required for adapter integration) - `envelope_emission_contracts` and `interaction_reporting_contracts` must exist before `widget_adapter_specs` (foreign key; T01 handles both in one migration) - Contracts (T01–T03) before adapter spec controller (T04) - Adapter spec controller (T04) before annotation launcher (T05) and React adapter (T06) — widget assignment UI depends on T04 - All feature tasks (T01–T07) before gate (T08) ## Notes - **Contracts are immutable once active.** A new version supersedes the old; old versions remain readable for audit. No in-place edits after status=active. - **Native IHP widgets are unaffected.** `adapter_spec_id` is nullable. Existing widgets continue to function exactly as before. - **The JS adapter is a thin client.** It does not embed a framework build pipeline. `ihf-react-adapter.js` is a plain ESM module; consumers bundle it themselves. - **Auth for the reporting API.** Bearer token scheme. In Phase 6 the token is a per-hub API key stored in `hubs.api_key` (add column in T01 migration). Phase 8 (federated) can layer on OAuth. - **No local JS build toolchain added.** Static JS files are served as-is. Phase 6 does not introduce npm, webpack, or esbuild into the IHP project.