feat(T02-T11): IHF Phase 1 schema, controllers, views, and helpers

- Schema: hubs, widgets, widget_versions, interaction_events (append-only
  trigger), annotations, users — single migration file
- Web layer: Types, Routes, FrontController with auth + AutoRefresh layout
- Controllers: Hubs (CRUD), Widgets (CRUD + versioning), InteractionEvents
  (JSON capture, canonical event_type validation), Annotations (threaded,
  append-only)
- Sessions controller for IHP auth
- Views: Hubs (index/show/new/edit), Widgets (index/show/new/edit),
  Annotations (index/new), Sessions (login)
- widgetEnvelope helper with full data-* governance attributes
- Integration tests: Hub CRUD, Widget versioning, event capture, append-only
  guard, annotation threading, validation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-27 01:42:43 +00:00
parent ff11913d5c
commit c560e541c7
26 changed files with 1591 additions and 12 deletions

View File

@@ -0,0 +1,56 @@
# Widget Envelope Convention
Every rendered widget in inter-hub wraps its HSX in the `widgetEnvelope` helper
from `Application.Helper.View`. This injects a stable set of `data-*` attributes
that enable client-side event capture without coupling to implementation details.
## Usage
```haskell
import Application.Helper.View (widgetEnvelope)
-- In any view:
widgetEnvelope widget [hsx|
<button class="...">Click me</button>
|]
```
## Emitted HTML
```html
<div
class="ihf-widget"
data-widget-id="<uuid>"
data-widget-type="chart"
data-hub-id="<uuid>"
data-capability-ref="pipeline.run"
data-view-context="ops/dashboard"
data-policy-scope="internal"
data-widget-version="3"
>
<!-- inner content -->
<div class="ihf-widget-controls">
<a href="/widgets/<uuid>/annotations" class="ihf-annotate-btn">Annotate</a>
</div>
</div>
```
## Attributes
| Attribute | Source | Purpose |
|-----------|--------|---------|
| `data-widget-id` | `widget.id` | Stable identity for event capture |
| `data-widget-type` | `widget.widgetType` | Semantic role of the widget |
| `data-hub-id` | `widget.hubId` | Which hub owns this widget |
| `data-capability-ref` | `widget.capabilityRef` | Link to hub capability |
| `data-view-context` | `widget.viewContext` | Logical location in the UI |
| `data-policy-scope` | `widget.policyScope` | Governance policy boundary |
| `data-widget-version` | `widget.version` | Version at render time |
## Rules
1. **Every interactive hub element** that participates in governance must be wrapped.
2. The `data-widget-id` is the capture key — the event capture endpoint uses it as `widget_id`.
3. Do not add or remove `data-*` attributes without updating both this convention doc and
the event capture client script.
4. The "Annotate" control is always rendered. It links to the full annotation thread for the widget.