summaryrefslogtreecommitdiffhomepage
path: root/src/features/surface-host/logic/plan.ts
blob: 5b4530b7672a828a5d54328ef9bf790499a8cf4f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import type { InvokeMessage, SurfaceSpec } from "@dispatch/ui-contract";
import type { FieldView, SurfaceRenderPlan } from "./types";

const KNOWN_KINDS = new Set(["toggle", "progress", "selector", "stat", "button"]);

/**
 * Validate and normalise a SurfaceSpec into a renderable plan.
 * Keeps known field kinds in order; drops unknown kinds and `custom` fields
 * (no renderer registry yet — graceful skip, never throw).
 */
export function planSurface(spec: SurfaceSpec): SurfaceRenderPlan {
	const fields: FieldView[] = [];
	for (const field of spec.fields) {
		if (!KNOWN_KINDS.has(field.kind)) continue;
		switch (field.kind) {
			case "toggle":
				fields.push({
					kind: "toggle",
					label: field.label,
					value: field.value,
					action: field.action,
				});
				break;
			case "progress":
				fields.push({
					kind: "progress",
					label: field.label,
					value: field.value,
				});
				break;
			case "selector":
				fields.push({
					kind: "selector",
					label: field.label,
					value: field.value,
					options: field.options,
					action: field.action,
				});
				break;
			case "stat":
				fields.push({
					kind: "stat",
					label: field.label,
					value: field.value,
				});
				break;
			case "button":
				fields.push({
					kind: "button",
					label: field.label,
					action: field.action,
				});
				break;
		}
	}
	return { fields };
}

/**
 * Construct a typed `invoke` client message for an actionable field.
 * For toggle the payload is the new boolean; for selector the chosen value;
 * for button the payload is omitted.
 */
export function buildInvoke(
	surfaceId: string,
	field: Extract<FieldView, { action: unknown }>,
	value?: unknown,
): InvokeMessage {
	const base = { type: "invoke" as const, surfaceId, actionId: field.action.actionId };
	if (value !== undefined) {
		return { ...base, payload: value };
	}
	return base;
}