From e1c8cf3257cb33457aa882c548f5195ecc0f9854 Mon Sep 17 00:00:00 2001
From: Adam Malczewski <github@tradam.dev>
Date: Sat, 6 Jun 2026 22:08:16 +0900
Subject: Slice 1: surface system + WS transport + composition root

Pure-core feature libraries assembled at the composition root:
- core/protocol: pure reducer over surface catalog/spec/error messages
- features/surface-host: generic field-kind interpreter (toggle/progress/
  selector/stat/button) + pure plan logic; no surface-id special-casing
- adapters/ws: injected WebSocket client (effects at the edge)
- app: composition root store (Svelte 5 runes over the pure reducer),
  host-relative surface WS URL resolution (resolveWsUrl), root App.svelte

Verified green: svelte-check 0/0, vitest 84 passed, biome clean, vite build ok.
---
 src/app/App.svelte | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)
 create mode 100644 src/app/App.svelte

(limited to 'src/app/App.svelte')

diff --git a/src/app/App.svelte b/src/app/App.svelte
new file mode 100644
index 0000000..2619a39
--- /dev/null
+++ b/src/app/App.svelte
@@ -0,0 +1,53 @@
+<script lang="ts">
+	import type { InvokeMessage } from "@dispatch/ui-contract";
+	import { SurfaceView } from "../features/surface-host";
+	import type { AppStore } from "./store.svelte";
+
+	let { store }: { store: AppStore } = $props();
+
+	function handleSelect(surfaceId: string) {
+		store.select(surfaceId);
+	}
+
+	function handleInvoke(msg: InvokeMessage) {
+		store.invoke(msg.surfaceId, msg.actionId, msg.payload);
+	}
+</script>
+
+<main>
+	<h1>Dispatch</h1>
+
+	{#if store.lastError}
+		<div role="alert">
+			<strong>Error:</strong>
+			{store.lastError.message}
+		</div>
+	{/if}
+
+	<section>
+		<h2>Surfaces</h2>
+		{#if store.catalog.length === 0}
+			<p>No surfaces available</p>
+		{:else}
+			<ul>
+				{#each store.catalog as entry (entry.id)}
+					<li>
+						<button
+							aria-current={entry.id === store.selectedId ? "true" : undefined}
+							onclick={() => handleSelect(entry.id)}
+						>
+							{entry.title}
+							<span>({entry.region})</span>
+						</button>
+					</li>
+				{/each}
+			</ul>
+		{/if}
+	</section>
+
+	{#if store.selectedSpec}
+		<section>
+			<SurfaceView spec={store.selectedSpec} onInvoke={handleInvoke} />
+		</section>
+	{/if}
+</main>
-- 
cgit v1.2.3

