Files
inter-hub/static/ihf-react-test.html
Bernd Worsch 803376b09a feat(P6/T06): React adapter specification and reference example
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>
2026-03-29 21:19:26 +00:00

136 lines
5.0 KiB
HTML
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.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>IHF React Adapter — Test Fixture</title>
<style>
body { font-family: sans-serif; padding: 24px; background: #f9fafb; }
h1 { font-size: 1.4rem; margin-bottom: 4px; }
p { color: #6b7280; font-size: 0.9rem; margin-bottom: 24px; }
.card {
background: #fff; border: 1px solid #e5e7eb; border-radius: 8px;
padding: 16px; margin-bottom: 16px; max-width: 480px;
}
.card h2 { font-size: 1rem; margin: 0 0 8px; }
.label { font-size: 0.7rem; color: #9ca3af; margin-bottom: 4px; }
button {
background: #4f46e5; color: #fff; border: none; border-radius: 4px;
padding: 6px 14px; font-size: 0.85rem; cursor: pointer;
}
button:hover { background: #4338ca; }
pre { background: #f3f4f6; border-radius: 4px; padding: 8px; font-size: 0.75rem; overflow: auto; }
.badge {
display: inline-block; font-size: 0.65rem; font-weight: 600;
padding: 2px 6px; border-radius: 4px; margin-left: 6px;
}
.badge-react { background: #dbeafe; color: #1d4ed8; }
.badge-native { background: #dcfce7; color: #15803d; }
</style>
</head>
<body>
<h1>IHF React Adapter — Test Fixture</h1>
<p>
Demonstrates a React-backed widget alongside a native IHP widget on the same page.
Both carry <code>data-widget-id</code> attributes — the annotation launcher
and reporting contract work identically for both.
</p>
<!-- ============================================================
NATIVE IHP WIDGET (simulated — normally rendered by IHP HSX)
============================================================ -->
<div class="card"
data-widget-id="00000000-0000-0000-0000-000000000001"
data-hub-id="00000000-0000-0000-0000-000000000099"
data-view-context="test-fixture"
data-policy-scope="internal"
data-widget-version="1">
<div class="label">Native IHP widget <span class="badge badge-native">IHP</span></div>
<h2>System Health Panel</h2>
<p style="font-size:0.85rem;color:#374151;margin:0">
This div was emitted by <code>widgetEnvelope</code> in Haskell HSX.
All <code>data-*</code> attributes are present per EnvelopeEmissionContract v1.0.
</p>
</div>
<!-- ============================================================
REACT WIDGET — mounted by the script below
============================================================ -->
<div id="react-widget-root"></div>
<!-- ============================================================
EVENT LOG
============================================================ -->
<div class="card" style="max-width:480px">
<h2>Interaction Event Log</h2>
<pre id="event-log">No events yet.</pre>
</div>
<!--
In a real integration React would come from your bundle.
Here we load it from a CDN for the standalone test.
-->
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script type="module">
import { useWidgetEnvelope, useInteractionReporter } from '/js/ihf-react-adapter.js';
const { createElement: h, useState } = React;
const WIDGET_ID = '00000000-0000-0000-0000-000000000002';
const HUB_ID = '00000000-0000-0000-0000-000000000099';
const API_KEY = 'test-key'; // replace with real hub api_key in integration
function log(msg) {
var el = document.getElementById('event-log');
var ts = new Date().toISOString().substr(11, 12);
el.textContent = '[' + ts + '] ' + msg + '\n' + el.textContent;
}
function ReactMetricsWidget() {
const [count, setCount] = useState(0);
const { envelopeProps } = useWidgetEnvelope(WIDGET_ID, HUB_ID, 'test-fixture');
const { reportEvent } = useInteractionReporter(WIDGET_ID, HUB_ID, API_KEY);
async function handleClick() {
setCount(c => c + 1);
try {
await reportEvent('clicked');
log('reportEvent("clicked") → success');
} catch (e) {
log('reportEvent("clicked") → ' + e.message);
}
}
return h('div', Object.assign({ className: 'card' }, envelopeProps),
h('div', { className: 'label' },
'React widget',
h('span', { className: 'badge badge-react' }, 'React'),
),
h('h2', null, 'Live Metrics Panel'),
h('p', { style: { fontSize: '0.85rem', color: '#374151', margin: '0 0 12px' } },
'This component uses useWidgetEnvelope + useInteractionReporter.',
h('br', null),
'Button clicks are 14reported to /api/v1/interaction-events.'
),
h('button', { onClick: handleClick }, 'Click me (clicked × ' + count + ')'),
h('pre', { style: { marginTop: '10px' } },
JSON.stringify(envelopeProps, null, 2)
),
);
}
ReactDOM.createRoot(document.getElementById('react-widget-root'))
.render(h(ReactMetricsWidget));
log('React widget mounted — widget_id=' + WIDGET_ID);
</script>
<!-- Annotation launcher — normally injected by IHP when IHP_ANNOTATION_LAUNCHER=true -->
<script src="/js/ihf-annotation-launcher.js"></script>
</body>
</html>