Files
inter-hub/workplans/IHUB-WP-0006-ihf-phase6-cross-framework-ui-adaptation.md
Bernd Worsch b1d1f5066b feat(P6): IHF Phase 6 complete — agent-assisted distillation
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>
2026-03-29 21:27:10 +00:00

371 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 15 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 15 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 (T01T03) 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 (T01T07) 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.