summaryrefslogtreecommitdiffhomepage
path: root/src/app/App.svelte
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-06 22:08:16 +0900
committerAdam Malczewski <[email protected]>2026-06-06 22:08:16 +0900
commite1c8cf3257cb33457aa882c548f5195ecc0f9854 (patch)
treed355147cdab8eb77917ad02caedf26b3d8d0be57 /src/app/App.svelte
downloaddispatch-web-e1c8cf3257cb33457aa882c548f5195ecc0f9854.tar.gz
dispatch-web-e1c8cf3257cb33457aa882c548f5195ecc0f9854.zip
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.
Diffstat (limited to 'src/app/App.svelte')
-rw-r--r--src/app/App.svelte53
1 files changed, 53 insertions, 0 deletions
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>