# IHP: Realtime Capabilities IHP offers four distinct realtime mechanisms. Choosing the right one for a given IHF surface is important — they have very different trade-offs. --- ## Summary Comparison | Mechanism | Best For | Client Requirement | State Lives | |-----------|---------|-------------------|-------------| | AutoRefresh | Read-heavy live dashboards | Vanilla JS (morphdom) | Server / DB | | DataSync | Reactive JS components embedded in server pages | JS SDK + bundler | DB (via RLS) | | Server-Side Components | Rich interactive UI with Haskell state machine | Vanilla JS | Server (Haskell) | | HTMX | Partial page updates triggered by user actions | htmx.js | Server | --- ## 1. AutoRefresh Enables server-side views to **automatically re-render** when the underlying database changes. No client-side framework or state management required. ### How It Works 1. Wrap an action with `autoRefresh do ...` 2. IHP tracks which DB tables the action's queries touched (via PostgreSQL LISTEN/NOTIFY) 3. A WebSocket connection is established when the page loads 4. On any INSERT/UPDATE/DELETE to tracked tables, the server re-runs the action 5. If the generated HTML differs, the delta is sent over WebSocket; `morphdom` applies it to the DOM with minimal DOM mutations ```haskell action HubDashboardAction { hubId } = autoRefresh do hub <- fetch hubId widgets <- query @Widget |> filterWhere (#hubId, hubId) |> fetch recentEvents <- query @InteractionEvent |> filterWhere (#hubId, hubId) |> orderByDesc #occurredAt |> limit 20 |> fetch render HubDashboardView { .. } ``` ### Layout Requirements ```haskell defaultLayout inner = [hsx|
{autoRefreshMeta} ... |] ``` ### Custom SQL Tracking For `sqlQuery` calls that bypass the query builder, manually register table reads: ```haskell trackTableRead "interaction_events" ``` ### IHF Use Cases - **Hub operator dashboard** — live widget counts, recent interaction signals, annotation feed - **Governance ledger view** — live requirement candidate list - **Triage board** — live annotation queue AutoRefresh is the right choice for all of these: server-rendered, no JS complexity, updates arrive automatically. --- ## 2. IHP DataSync A WebSocket-based API that allows **JavaScript to query the database directly**, mirroring the Haskell query builder in JS. For reactive JS components embedded in otherwise server-rendered pages. ### JavaScript API ```javascript // One-time fetch const annotations = await query('annotations') .where('widget_id', widgetId) .orderBy('created_at', 'DESC') .fetch(); // Realtime subscription (React hook) const { records: annotations } = useQuery( query('annotations') .where('widget_id', widgetId) .orderBy('created_at', 'DESC') ); // Mutations await createRecord('annotations', { widgetId, body, category }); await updateRecord('annotations', id, { body }); await deleteRecord('annotations', id); ``` When `useQuery()` establishes a subscription, all connected clients automatically re-render when the query result changes. ### Security: Row Level Security DataSync relies on **PostgreSQL Row Level Security (RLS)**. IHP runs with two DB roles: - Privileged owner role — used by Haskell server code - `ihp_authenticated` — used by DataSync (what JS clients see) Example RLS policy: ```sql -- Users can only see their own annotations CREATE POLICY "Users can view own annotations" ON annotations FOR SELECT USING (actor_id = ihp_user_id()); -- Anyone can view annotations on public widgets CREATE POLICY "Public widget annotations are readable" ON annotations FOR SELECT USING (EXISTS ( SELECT 1 FROM widgets w WHERE w.id = annotations.widget_id AND w.policy_scope = 'public' )); ``` ### Setup Requirements Node.js/npm, a frontend bundler (esbuild recommended), DataSync JS SDK, WebSocket + REST API controllers enabled in `FrontController`. ### IHF Use Cases - **Annotation composer** — reactive annotation entry form with live thread updates - **Widget feedback inbox** — live feed of all annotations on a specific widget for the widget owner - **Cross-tenant widget embeds** — widget UIs embedded in third-party pages; RLS isolates each widget's data --- ## 3. Server-Side Components React-like components that run entirely on the server. Rich interactive UI with a Haskell state machine — no JS state management needed. ### Structure A component has: - **State** — a Haskell data type - **Actions** — a sum type - **Render** — HSX function from state to HTML - **`componentDidMount`** — hook for async data loading on connect ```haskell -- Web/Component/AnnotationComposer.hs data AnnotationComposer = AnnotationComposer { widgetId :: !(Id Widget) , body :: !Text , category :: !Text , submitted :: !Bool } data AnnotationComposerController = UpdateBodyAction { body :: !Text } | UpdateCategoryAction { category :: !Text } | SubmitAnnotationAction deriving (Eq, Show, Data) instance Component AnnotationComposer AnnotationComposerController where initialState = AnnotationComposer { widgetId = error "set by mount" , body = "" , category = "friction" , submitted = False } action state UpdateBodyAction { body } = pure state { body } action state UpdateCategoryAction { category } = pure state { category } action state SubmitAnnotationAction = do createRecord (newRecord @Annotation |> set #widgetId state.widgetId |> set #body state.body |> set #category state.category) pure state { submitted = True, body = "" } render state = [hsx|