@qontinui/shared-types 0.14.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qontinui/shared-types",
3
- "version": "0.14.0",
3
+ "version": "1.0.0",
4
4
  "description": "Shared TypeScript types for the Qontinui ecosystem",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -41,8 +41,55 @@ export interface UIBridgeElement {
41
41
  bbox?: ElementBbox | null;
42
42
  /**
43
43
  * Custom (application-defined) actions.
44
- */
45
- customActions?: string[] | null;
44
+ *
45
+ * **Objects, not names.** These used to be a bare `Vec<String>` of action
46
+ * names, which left an element-level custom action with nowhere to carry a
47
+ * safety class — so the walker had no way to tell a `refresh` from a
48
+ * `sendKeys` that writes raw bytes into a live agent PTY. Widening this to
49
+ * [`ElementActionInfo`] is what makes `effect` REACHABLE from an element;
50
+ * see that type's docs.
51
+ *
52
+ * Absent (`None`) means the element declares none. That is deliberately
53
+ * distinct from an empty list, which would mean "declared, and empty".
54
+ * ---
55
+ *
56
+ * **TRANSITIONAL: this field also accepts the legacy bare-NAME form on the
57
+ * way in.** `["sendKeys"]` deserializes as
58
+ * `[ElementActionInfo { id: "sendKeys", .. }]`, with every other field —
59
+ * `effect` included — left `None`, i.e. UNCLASSIFIED. See
60
+ * [`deserialize_custom_actions`].
61
+ *
62
+ * **Why a shim exists at all**, when this workspace's rule is
63
+ * delete-over-deprecate. The shape change spans three repos that cannot
64
+ * land atomically — this crate owns the type, `ui-bridge` emits, and
65
+ * `qontinui-runner` parses live SDK snapshots strictly
66
+ * (`spec-check/src/fetch.rs`, `spec_api/spec_check.rs`) while pinning a
67
+ * PUBLISHED SDK. Both two-party orderings leave a window in which the
68
+ * fleet is broken: land this crate first and the runner rejects the names
69
+ * the published SDK still emits; land the SDK first and it emits objects
70
+ * at a runner whose type is still `Vec<String>`. Only a tolerant READER
71
+ * removes the window, and there is one reader against nine emitters.
72
+ *
73
+ * This is NOT here to spare a caller a breaking change — that is never a
74
+ * reason in this workspace.
75
+ *
76
+ * **DELETE ME, and the trigger is observable rather than intentional:**
77
+ * once no emitter produces names — `ui-bridge`'s eight element
78
+ * projections plus `qontinui-runner`'s
79
+ * `src/services/background-observer-service.ts` — and the runner's SDK pin
80
+ * has moved past that release, remove [`deserialize_custom_actions`] and
81
+ * this attribute, and flip
82
+ * `ui_bridge_element_tolerates_legacy_name_custom_actions` into a test
83
+ * that asserts a bare string is REJECTED. Plan
84
+ * `2026-09-04-effect-calculus-joins-the-component-action-registry`,
85
+ * Design decision 4, step 5.
86
+ *
87
+ * The published SCHEMA is deliberately not widened to advertise the name
88
+ * form: the schema states the target contract, while the reader is
89
+ * transitionally lenient. A producer validating against the schema is
90
+ * correctly told that names are not the shape to emit.
91
+ */
92
+ customActions?: ElementActionInfo[] | null;
46
93
  /**
47
94
  * Unique element ID within the registry.
48
95
  */
@@ -94,3 +141,72 @@ export interface UIBridgeElement {
94
141
  */
95
142
  visible?: boolean | null;
96
143
  }
144
+ /**
145
+ * Information about a single custom (application-defined) action exposed by a
146
+ * UI Bridge **element**.
147
+ *
148
+ * The element-level twin of [`ComponentActionInfo`], and deliberately the same
149
+ * field vocabulary so a consumer that walks both action planes reads one shape,
150
+ * not two. One field is NOT carried over: `path`. A component action has a
151
+ * resolved per-action invocation path because the SDK annotates one
152
+ * (`/control/component/<componentId>/action/<actionId>`); an element action has
153
+ * no analogue — every element action is invoked through the single
154
+ * `/control/element/<elementId>/action` route with the action name in the body.
155
+ * A field nothing would ever populate is dead schema surface, so it is absent
156
+ * rather than perpetually `null`.
157
+ *
158
+ * ## Why this is an object rather than a name
159
+ *
160
+ * It exists so `effect` has somewhere to live. A bare list of action NAMES is
161
+ * unannotatable: there is no slot on a name for a safety class, so an
162
+ * element-level custom action could not declare itself `destructive` however
163
+ * much its author wanted to. That is not hypothetical — the runner exposes
164
+ * element custom actions that write raw bytes into a live agent PTY, and some
165
+ * of them act on a pane with no visible view.
166
+ *
167
+ * The wire projection is widened FIRST and the annotation lands on top of it.
168
+ * The reverse order ships an UNREACHABLE annotation, which is worse than an
169
+ * absent one: the author believes the delete button is marked, and the walker
170
+ * walks it anyway.
171
+ */
172
+ export interface ElementActionInfo {
173
+ /**
174
+ * Longer description of what the action does.
175
+ */
176
+ description?: string | null;
177
+ /**
178
+ * Safety class of this action: `read`, `write` or `destructive`.
179
+ *
180
+ * **A safety annotation, not a hint.** An autonomous walk MUST NOT fire an
181
+ * action annotated `destructive`; that is the same exclusion the IR already
182
+ * applies to `destructive` transitions when generating auto-regressions.
183
+ * Author-declared, because only the app author knows that a particular
184
+ * custom action sends the keystrokes that end a session.
185
+ *
186
+ * Absent means **unclassified, not safe** — an action nobody has judged
187
+ * must be treated as unknown rather than as `read`. `skip_serializing_if`
188
+ * keeps an un-annotated action ABSENT on the wire rather than defaulting to
189
+ * a class nobody chose.
190
+ */
191
+ effect?: ("read" | "write" | "destructive") | null;
192
+ /**
193
+ * Unique action identifier within the element — the key the action is
194
+ * registered under, and the name `/control/element/<id>/action` is called
195
+ * with.
196
+ */
197
+ id: string;
198
+ /**
199
+ * Human-readable label.
200
+ */
201
+ label?: string | null;
202
+ /**
203
+ * Free-form declaration of the action's parameters, author-supplied on the
204
+ * SDK side and surfaced verbatim. Conventionally a small JSON Schema
205
+ * subset, but the SDK does not constrain the shape, so it is carried
206
+ * through untyped — the same treatment `ElementActionRequest::params` and
207
+ * `ComponentActionInfo::param_schema` get.
208
+ */
209
+ paramSchema?: {
210
+ [k: string]: unknown;
211
+ };
212
+ }