diff options
| author | Adam Malczewski <[email protected]> | 2026-06-10 16:29:01 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-10 16:29:01 +0900 |
| commit | 871957b930203c019e631c4606cfdf8266d222fa (patch) | |
| tree | 50c522018c3ce4127ffa76f4b3b6c7843e90db43 /src/features/surface-host/ui/SurfaceView.svelte | |
| parent | 7b345f132763fa6405ae858b74e46229629c19d9 (diff) | |
| download | dispatch-web-871957b930203c019e631c4606cfdf8266d222fa.tar.gz dispatch-web-871957b930203c019e631c4606cfdf8266d222fa.zip | |
feat(views,surface-host): Extensions sidebar view — auto-expanded surfaces + tables
views (new feature):
- pure panel-stack reducer + thin generic ViewSidebar (dropdown picker + add/remove),
switches on view KIND, never a surface id
Extensions view (composition root):
- folds frontend modules + backend surfaces into one "Extensions" view
- frontend module list AGGREGATED from each feature's public `manifest` export
(can't drift); no per-module version (FE features are internal to dispatch-web)
- surfaces are AUTO-SUBSCRIBED on catalog + rendered expanded (no catalog buttons)
surface-host:
- consecutive `stat` fields coalesce into one aligned label/value table (StatTable)
- generic custom-field renderer: dispatch on rendererId === "table" → SurfaceTable
(pure parseTablePayload), so a backend `custom`/table field renders generically
- shared presentational components/Table.svelte (used by both, neither feature
depends on the other)
store:
- auto-subscribe every catalog entry, unsubscribe vanished ones, re-subscribe all
on reconnect; expose all received specs via `surfaces` (drops single-selection)
backend-handoff: CR-1 — emit Loaded Extensions as a custom/table field; notes
what's already covered FE-side (renderer shipped, stat-table fallback works).
Diffstat (limited to 'src/features/surface-host/ui/SurfaceView.svelte')
| -rw-r--r-- | src/features/surface-host/ui/SurfaceView.svelte | 36 |
1 files changed, 23 insertions, 13 deletions
diff --git a/src/features/surface-host/ui/SurfaceView.svelte b/src/features/surface-host/ui/SurfaceView.svelte index 4207913..5210e8c 100644 --- a/src/features/surface-host/ui/SurfaceView.svelte +++ b/src/features/surface-host/ui/SurfaceView.svelte @@ -1,10 +1,11 @@ <script lang="ts"> import type { InvokeMessage, SurfaceSpec } from "@dispatch/ui-contract"; - import { planSurface } from "../logic/plan"; + import { groupRenderFields, planSurface } from "../logic/plan"; import Button from "./Button.svelte"; import Progress from "./Progress.svelte"; import Selector from "./Selector.svelte"; - import Stat from "./Stat.svelte"; + import StatTable from "./StatTable.svelte"; + import SurfaceTable from "./SurfaceTable.svelte"; import Toggle from "./Toggle.svelte"; let { @@ -13,21 +14,30 @@ }: { spec: SurfaceSpec; onInvoke: (msg: InvokeMessage) => void } = $props(); const plan = $derived(planSurface(spec)); + // Consecutive stats render together as one aligned table; everything else is + // a standalone widget. Grouping keys on field KIND only — never the surface id. + const groups = $derived(groupRenderFields(plan.fields)); </script> <article> <h2>{spec.title}</h2> - {#each plan.fields as field (field)} - {#if field.kind === "toggle"} - <Toggle {field} surfaceId={spec.id} {onInvoke} /> - {:else if field.kind === "progress"} - <Progress {field} /> - {:else if field.kind === "selector"} - <Selector {field} surfaceId={spec.id} {onInvoke} /> - {:else if field.kind === "stat"} - <Stat {field} /> - {:else if field.kind === "button"} - <Button {field} surfaceId={spec.id} {onInvoke} /> + {#each groups as group, i (i)} + {#if group.type === "stats"} + <StatTable stats={group.stats} /> + {:else if group.field.kind === "toggle"} + <Toggle field={group.field} surfaceId={spec.id} {onInvoke} /> + {:else if group.field.kind === "progress"} + <Progress field={group.field} /> + {:else if group.field.kind === "selector"} + <Selector field={group.field} surfaceId={spec.id} {onInvoke} /> + {:else if group.field.kind === "button"} + <Button field={group.field} surfaceId={spec.id} {onInvoke} /> + {:else if group.field.kind === "custom"} + <!-- Dispatch on rendererId (a renderer KIND, never a surface id); + unknown ids gracefully render nothing. --> + {#if group.field.rendererId === "table"} + <SurfaceTable payload={group.field.payload} /> + {/if} {/if} {/each} </article> |
