ihf-react-adapter.js: useWidgetEnvelope (EnvelopeEmissionContract v1.0 props), withWidgetEnvelope HOC, useInteractionReporter (POSTs to reporting contract endpoint). Plain ESM module — no IHP build toolchain required. docs/react-adapter.md with usage examples. static/ihf-react-test.html: React widget + native IHP widget on same page demonstrating annotation launcher pickup and reportEvent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
15 KiB
id, type, title, domain, repo, status, owner, topic_slug, created, updated, state_hub_workstream_id
| id | type | title | domain | repo | status | owner | topic_slug | created | updated | state_hub_workstream_id |
|---|---|---|---|---|---|---|---|---|---|---|
| IHUB-WP-0006 | workplan | IHF Phase 6 — Cross-Framework UI Adaptation Layer | inter_hub | inter-hub | todo | custodian | inter_hub | 2026-03-29 | 2026-03-29 | 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
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:
-- 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
id: IHUB-WP-0006-T02
status: done
priority: high
state_hub_task_id: "298af675-550b-480b-bed6-05efc79cd0c9"
- Seed the canonical v1.0
EnvelopeEmissionContractrecord 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"}
- Update the
widgetEnvelopehelper (Web/View/Helpers.hsor 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. - Add
EnvelopeEmissionContractsController:index: table of contract versions with status badgesshow: full required/optional attributes and validation rules as formatted JSON panels- Read-only (contracts are immutable once active; a new version supersedes)
- 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
id: IHUB-WP-0006-T03
status: done
priority: high
state_hub_task_id: "f2767465-ff00-48be-b2dc-5bf3b179cca9"
- 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"]
- 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
InteractionEventrecord - Return
201 Createdwith{id, widget_id, event_type}or422with validation errors
- Register the API route in
FrontController.hs - 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
id: IHUB-WP-0006-T04
status: done
priority: high
state_hub_task_id: "e84016d0-60c0-48cb-ad70-2c054d2530db"
- Scaffold
WidgetAdapterSpecsController:index: table of adapters — framework badge, version, status, envelope contract version, reporting contract versionnew/create: register a new adapter specshow: full detail — framework, version, linked contracts, notes, statusedit/update: update notes and status only (contracts are immutable once linked)- No delete — adapter specs are audit artifacts
- Validation:
name,framework,versionrequiredstatusmust bedraft | active | deprecated
- On widget
new/editforms: optionaladapter_spec_idselect (null = native) - On widget show page: if
adapter_spec_idpresent, 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)
id: IHUB-WP-0006-T05
status: done
priority: medium
state_hub_task_id: "fea86955-d5e6-4623-b5cc-f422c266c9cf"
- Create
static/js/ihf-annotation-launcher.js— a self-contained vanilla JS module (no framework dependency):- On
DOMContentLoaded, scan for elements withdata-widget-idattribute - 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) viafetch - On success: show a brief confirmation; on error: show inline error message
- Reads
data-hub-idfrom the element (or nearest ancestor) for the hub context
- On
- The launcher must work in React-rendered pages where IHP does not own the
DOM — it relies solely on
data-widget-idpresence. - Include as an optional script tag in the IHP layout (
Web/View/Layout.hs) with a feature flag (IHP_ANNOTATION_LAUNCHER=true) - 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
id: IHUB-WP-0006-T06
status: todo
priority: medium
state_hub_task_id: "023269d8-9835-40b4-a394-478a0f36eee0"
- Register a
react-18WidgetAdapterSpecrecord (via migration seed or admin UI):- links to envelope v1.0 contract and reporting v1.0 contract
status = active
- Create
static/js/ihf-react-adapter.js— a thin React hook + HOC:useWidgetEnvelope(widgetId, hubId, viewContext)— returns arefanddata-*props object conforming to the envelope contractwithWidgetEnvelope(WrappedComponent, widgetId, hubId, viewContext)— HOC that applies the envelope to the root DOM elementuseInteractionReporter(widgetId, hubId)— returns areportEvent(type)function that POSTs to/api/v1/interaction-events
- Create
docs/react-adapter.mdwith usage examples for all three exports - 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
id: IHUB-WP-0006-T07
status: todo
priority: medium
state_hub_task_id: "dc8fa48a-7195-4410-a77e-717b53127c2e"
- Add
AdapterCompatibilityDashboardAction { hubId }toHubsController(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_idthat have received events from external origins (heuristic:user_agentnot matching IHP server) - Stale adapters: adapter specs with
status=activebut no widgets assigned in the last 30 days
- Link from hub Show page alongside Triage / Governance / Antifragility / Agent dashboards
- 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
id: IHUB-WP-0006-T08
status: todo
priority: high
state_hub_task_id: "90ea4814-7603-4016-be34-d41ae091f7e1"
- Integration tests (
Test/):- EnvelopeEmissionContract create + fetch (required_attributes, validation_rules)
- InteractionReportingContract create + fetch
POST /api/v1/interaction-events— valid payload creates InteractionEventPOST /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
- Consistency sync via State Hub MCP:
check_repo_consistency(repo_slug="inter-hub", fix=True) - Documentation updates:
- Update
SCOPE.mdcurrent state section: Phase 6 complete - Write
docs/phase6-summary.md: what was built, contract model, adapter pattern, known limitations, Phase 7 readiness
- Update
- Smoke test checklist:
- Register a
react-18adapter spec via UI - Assign a widget to the adapter
- POST a test interaction event via
curlto/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
- Register a
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_contractsandinteraction_reporting_contractsmust exist beforewidget_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_idis 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.jsis 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.