summaryrefslogtreecommitdiffhomepage
path: root/.dispatch/ui-contract.reference.md
blob: 3962fc1e3c66df317ab921e73c4dbd2634a841b7 (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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# `@dispatch/ui-contract` — in-repo reference (read THIS, not node_modules)

> This MIRRORS the backend's `@dispatch/ui-contract` package source so headless FE agents can
> read the surface contract WITHOUT following the `file:` dep symlink out of this repo (which
> hangs on a permission prompt). Your CODE still imports `@dispatch/ui-contract` normally — this
> file is for READING only.
>
> **Orchestrator:** this is a SNAPSHOT — regenerate it whenever `ui-contract` changes.

```ts
/**
 * UI contract — the frontend-agnostic vocabulary for backend-declared "surfaces".
 *
 * A SURFACE is a "data transportation surface": a typed description of what data an
 * extension exposes, its semantics, and the actions that can act on it — NOT UI.
 * Any client renders a surface in its own idiom (web/Svelte, CLI, future TUI/mobile).
 * Types-only, zero runtime, zero `@dispatch/*` deps.
 */

/** Where a surface mounts — a coarse, semantic placement hint, NOT layout/CSS. Open string. */
export type Region = string;

/** A typed reference to a backend action a field can invoke (client posts payload back). */
export interface ActionRef {
	readonly actionId: string;
}

/** One selectable option in a `selector` field. */
export interface SurfaceOption {
	readonly value: string;
	readonly label: string;
}

/** A field within a surface — a SEMANTIC value, not a widget. `kind` is the discriminant. */
export type SurfaceField =
	| ToggleField
	| ProgressField
	| SelectorField
	| StatField
	| ButtonField
	| CustomField;

/** A boolean setting plus the action that flips it. */
export interface ToggleField {
	readonly kind: "toggle";
	readonly label: string;
	readonly value: boolean;
	readonly action: ActionRef;
}

/** A bounded ratio in [0, 1] with a label. Read-only. */
export interface ProgressField {
	readonly kind: "progress";
	readonly label: string;
	readonly value: number;
}

/** An enum choice: the current value, the options, and the action that sets it. */
export interface SelectorField {
	readonly kind: "selector";
	readonly label: string;
	readonly value: string;
	readonly options: readonly SurfaceOption[];
	readonly action: ActionRef;
}

/** A read-only labelled scalar readout. */
export interface StatField {
	readonly kind: "stat";
	readonly label: string;
	readonly value: string;
}

/** A labelled action trigger. */
export interface ButtonField {
	readonly kind: "button";
	readonly label: string;
	readonly action: ActionRef;
}

/**
 * The escape hatch: data that fits no semantic field kind. Opaque `payload` + a
 * `rendererId`; clients WITH a renderer for that id show it, others GRACEFULLY SKIP.
 */
export interface CustomField {
	readonly kind: "custom";
	readonly rendererId: string;
	readonly payload: unknown;
}

/** A surface: an ordered set of fields mounted in a region, with a title. */
export interface SurfaceSpec {
	readonly id: string;
	readonly region: Region;
	readonly title: string;
	readonly fields: readonly SurfaceField[];
}

/** A surface-catalog entry — discovery metadata only (no field data). */
export interface SurfaceCatalogEntry {
	readonly id: string;
	readonly region: Region;
	readonly title: string;
}

/** The surface catalog: the list of available surfaces a client can choose to show. */
export type SurfaceCatalog = readonly SurfaceCatalogEntry[];

/** A live update for a subscribed surface. v1 carries the full new spec. */
export interface SurfaceUpdate {
	readonly surfaceId: string;
	readonly spec: SurfaceSpec;
}

// ── Surface WebSocket protocol (slice 1: surfaces only) ──────────────────────

/** A client → server message on the surface channel. */
export type SurfaceClientMessage = SubscribeMessage | UnsubscribeMessage | InvokeMessage;

export interface SubscribeMessage {
	readonly type: "subscribe";
	readonly surfaceId: string;
}

export interface UnsubscribeMessage {
	readonly type: "unsubscribe";
	readonly surfaceId: string;
}

/** Invoke a field's action; `payload` is the new value (e.g. a toggle's boolean). */
export interface InvokeMessage {
	readonly type: "invoke";
	readonly surfaceId: string;
	readonly actionId: string;
	readonly payload?: unknown;
}

/** A server → client message on the surface channel. */
export type SurfaceServerMessage =
	| CatalogMessage
	| SurfaceMessage
	| SurfaceUpdateMessage
	| SurfaceErrorMessage;

/** The current surface catalog (sent on connect and whenever it changes). */
export interface CatalogMessage {
	readonly type: "catalog";
	readonly catalog: SurfaceCatalog;
}

/** The full current spec for a surface the client just subscribed to. */
export interface SurfaceMessage {
	readonly type: "surface";
	readonly spec: SurfaceSpec;
}

/** A live update for a subscribed surface. */
export interface SurfaceUpdateMessage {
	readonly type: "update";
	readonly update: SurfaceUpdate;
}

/** A surface-scoped error. */
export interface SurfaceErrorMessage {
	readonly type: "error";
	readonly surfaceId?: string;
	readonly message: string;
}
```