@effect.healthcare/messenger 0.3.1 → 0.4.2

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.
@@ -1,596 +1,596 @@
1
- # Operation
2
-
3
- This document defines an implementation policy for **component operations**, then illustrates how specifications are implemented by applying this policy.
4
-
5
- This document is intentionally **operations** only, but part of a set of implementation policies which together defines the policies which should be followed to transform specifications into an implementation which is predictable, maintainable, and production-ready at scale.
6
-
7
- ---
8
-
9
- # How to Read This Policy
10
-
11
- Rules in this policy fall into three strengths. Read each rule with its strength in mind:
12
-
13
- - **Principles** are non-negotiable. They derive from framework correctness or TDD discipline and hold for every component. A conflict with a principle is a defect.
14
- - **Templates** are invariant skeletons with explicit variation points. The skeleton applies to every component; specific steps activate or deactivate based on the component's declared classification (e.g., attribute-backed vs non-attribute, readonly vs writable, payload-bearing vs payload-free). Variation is part of the template, not a deviation from it.
15
- - **Conventions** are canonical defaults that keep implementations readable across components. A second component may deviate when its shape genuinely warrants it; the deviation should be explained at review time, not forbidden by the policy.
16
-
17
- When applying this policy to a new component, classify each rule before reconciling conflicts. A conflict with a principle is a defect; a conflict with a template means a variation point needs to be made explicit; a conflict with a convention is a documented deviation.
18
-
19
- ---
20
-
21
- # 1) Operation Contract Must Be Declared in the Specification
22
-
23
- Each component specification must declare its operation contract under `Operations`.
24
-
25
- `Operations` must be an object whose property names are the canonical operation names.
26
-
27
- Each operation definition must include:
28
-
29
- - `description`
30
- - `parameters`
31
- - `returns`
32
-
33
- Interpretation:
34
-
35
- - `description` defines the public action intent
36
- - `parameters` defines the public invocation contract
37
- - `returns` defines the public result contract
38
- - if `parameters` is empty, the operation takes no arguments
39
- - if `returns` is `"void"`, the operation does not return a value
40
-
41
- Example specification:
42
-
43
- ```json
44
- "toggle": {
45
- "description": "Toggles the 'status' between 'pinned' and 'unpinned'.",
46
- "parameters": [],
47
- "returns": "void"
48
- }
49
- ```
50
-
51
- This defines the public action contract that the implementation must honor.
52
-
53
- # Template Variation Points
54
-
55
- Operations vary by their relationship to component state and data. The classification determines which sub-rules activate:
56
-
57
- - **Primitive operations** — delegate to a single authoritative state mutation path (Section 7). Their semantic event is emitted by the accepted state transition, not by the operation (Section 8). Examples: `hide`, `show`, `pin`, `unpin`.
58
- - **Composite operations** — derive the next target from current component context and delegate through the same authoritative mutation path (Section 11). They typically have no own event because the underlying state transition emits the existing event vocabulary. Example: `toggle`.
59
- - **Coordinating operations** — orchestrate multiple internal steps as one public action and may dispatch their own semantic events directly when the operation itself is the semantic unit (Section 10). Examples: `save`, `cancel`.
60
- - **Data-bearing operations** — accept or construct a data value and delegate through the authoritative data setter (Section 13). Examples: `load`, `create`, `update`, `reset`.
61
-
62
- Each operation must be classifiable as exactly one type. A new operation type is a signal that this list needs to be extended, not that the operation deviates from the template.
63
-
64
- # 2) Operations Must Represent Public Actions
65
-
66
- This framework distinguishes:
67
-
68
- - `Operations`: the set of callable public actions exposed by the component
69
-
70
- Policy:
71
-
72
- - operations describe callable public actions
73
- - operations must not be used to declare state
74
- - operations must not be used to declare gestures
75
- - operations must not be used to declare events
76
-
77
- Examples of valid operations:
78
-
79
- - `hide`
80
- - `show`
81
- - `pin`
82
- - `unpin`
83
- - `toggle`
84
-
85
- Examples that must not be modeled as operations:
86
-
87
- - `visibility`
88
- - `status`
89
- - `click`
90
- - `hover`
91
- - `onpin`
92
-
93
- These belong to other framework concerns.
94
-
95
- # 3) Public Operation Names Must Be Declared in Metadata
96
-
97
- For every operation declared in the specification, the implementation must define corresponding metadata in `<component>.meta.ts`.
98
-
99
- This metadata defines the canonical operation vocabulary for the component.
100
-
101
- Policy:
102
-
103
- - every declared public operation must have a key in `Operation`
104
- - `Operation` contains only declared public operation names
105
- - the implementation must use operation metadata consistently as the canonical operation vocabulary
106
-
107
- Example pattern:
108
-
109
- ```typescript
110
- export const Operation = {
111
- HIDE: "hide",
112
- SHOW: "show",
113
- PIN: "pin",
114
- UNPIN: "unpin",
115
- TOGGLE: "toggle",
116
- } as const;
117
-
118
- export type Operation = (typeof Operation)[keyof typeof Operation];
119
- ```
120
-
121
- # 4) Every Declared Public Operation Must Be Implemented as a Public Arrow Function Class Property
122
-
123
- For every operation declared in the specification, the component class must implement a corresponding public arrow function class property.
124
-
125
- Policy:
126
-
127
- - every operation declared in `Operations` must have a corresponding public arrow function class property on the component class
128
- - the property name must match the canonical operation name
129
- - the property signature must honor the declared `parameters` and `returns` contract
130
- - public operations form part of the component's imperative API
131
-
132
- Operations are implemented as arrow function class properties — not prototype methods — because this guarantees `this` binding at the point of definition. This makes operations safe to pass as callbacks without `.bind()`, which is important for gesture handlers and external consumers.
133
-
134
- Example pattern:
135
-
136
- ```typescript
137
- public hide = () => (this.visibility = Visibility.HIDDEN);
138
- public show = () => (this.visibility = Visibility.VISIBLE);
139
- public pin = () => (this.status = Status.PINNED);
140
- public unpin = () => (this.status = Status.UNPINNED);
141
- public toggle = () =>
142
- (this.status =
143
- this.status === Status.PINNED ? Status.UNPINNED : Status.PINNED);
144
- ```
145
-
146
- This gives the component a stable imperative API:
147
-
148
- ```typescript
149
- pin.hide();
150
- pin.show();
151
- pin.pin();
152
- pin.unpin();
153
- pin.toggle();
154
- ```
155
-
156
- # 5) Operations Form the Public Imperative Surface of the Component
157
-
158
- Operations form the public imperative surface of the component.
159
-
160
- Policy:
161
-
162
- - public operations define the callable action surface exposed to external consumers
163
- - public operations must be intentionally named according to the declared operation contract
164
- - public operations must provide a stable imperative entry for the behaviors they represent
165
- - where a declared public operation exists for a behavior, it should be reusable by both imperative callers and runtime gesture handlers
166
-
167
- Example imperative usage:
168
-
169
- ```typescript
170
- pin.toggle();
171
- pin.pin();
172
- ```
173
-
174
- Example runtime reuse:
175
-
176
- ```typescript
177
- private _handleClick = () => this.toggle();
178
- ```
179
-
180
- # 6) Public Operation Methods Must Honor the Declared Return Contract
181
-
182
- The default return contract for action-oriented operations is `void`.
183
-
184
- If an operation returns a value, that return contract must be declared explicitly in the specification.
185
-
186
- Policy:
187
-
188
- - the public operation property must honor the `returns` contract declared in the specification
189
- - `void` is the default return contract for action-oriented operations
190
- - non-`void` return contracts are valid only when explicitly declared in the specification
191
-
192
- Pin follows the default pattern: all declared operations return `void`.
193
-
194
- # 7) Operations Must Remain Thin and Delegate to the Authoritative Mutation Path
195
-
196
- Operations must not duplicate mutation logic internally.
197
-
198
- When an operation produces its effect by mutating a component-owned construct, it must delegate through the authoritative mutation path of that construct or through a controlled internal transition.
199
-
200
- Policy:
201
-
202
- - operations express action intent
203
- - operations must not reimplement validation, guard logic, reflection, transition acceptance, or other mutation behavior that belongs to an authoritative construct mutation path
204
- - when the operation maps to a writable public construct, it should normally delegate to that construct's public mutation path
205
- - when the operation requires a controlled internal transition or coordinated internal workflow, that path must remain authoritative
206
- - operations may act on state, data, or other internally owned constructs, provided they preserve the authoritative ownership of mutation behavior
207
-
208
- Example:
209
-
210
- ```typescript
211
- public hide = () => (this.visibility = Visibility.HIDDEN);
212
- public pin = () => (this.status = Status.PINNED);
213
- ```
214
-
215
- In this example, `hide()` and `pin()` remain thin because they express public action intent while delegating mutation behavior to the authoritative `visibility` and `status` state setters. The operation does not become a second mutation system.
216
-
217
- # 8) State-Outcome Semantic Events Must Be Emitted by Accepted State Transitions
218
-
219
- If an operation produces a semantic outcome by changing state, the operation must not dispatch the semantic event directly.
220
-
221
- Instead, the operation must delegate to state mutation, and the event must be emitted by the accepted state transition.
222
-
223
- Preferred flow:
224
-
225
- ```text
226
- operation
227
- ↓
228
- state mutation
229
- ↓
230
- accepted state transition
231
- ↓
232
- event dispatch
233
- ```
234
-
235
- Policy:
236
-
237
- - operations must not dispatch semantic events directly when those events represent state outcomes
238
- - accepted state transition is the single semantic source of truth for state-outcome event emission
239
- - if the requested state transition is not accepted, the semantic event must not be emitted
240
-
241
- This rule applies only to semantic events whose meaning is an accepted state outcome. Operations may dispatch semantic events directly when the operation itself is the semantic unit and the event does not merely restate a state transition.
242
-
243
- Example:
244
-
245
- ```typescript
246
- public pin = () => (this.status = Status.PINNED);
247
-
248
- public set status(status: Status) {
249
- status = Validate.status(status);
250
-
251
- if (this._status === status) return;
252
-
253
- this._status = status;
254
- this.setAttribute(Attributes.STATUS, status);
255
-
256
- const event = { detail: { status } };
257
-
258
- status === Status.PINNED && this._dispatchEvent(Event.ON_PIN, event);
259
- status === Status.UNPINNED && this._dispatchEvent(Event.ON_UNPIN, event);
260
- }
261
- ```
262
-
263
- # 9) Operations Must Be Safe to Call Repeatedly
264
-
265
- Operations must be safe to call repeatedly.
266
-
267
- Where the requested outcome is already true, the operation must resolve through the normal authoritative mutation path without producing a duplicate accepted outcome or duplicate semantic side effects.
268
-
269
- Policy:
270
-
271
- - repeated invocation of an operation must not create invalid transitions
272
- - where the requested outcome is already satisfied, the operation must not produce a duplicate accepted outcome
273
- - repeated safety should normally be achieved through the authoritative mutation path, not by duplicating guard logic in the operation itself
274
- - if no accepted outcome occurs, no duplicate semantic side effects should be produced
275
- - when the operation delegates through state, data, or another internally owned construct, repeat safety must be preserved by that construct's authoritative mutation path or controlled internal workflow
276
-
277
- Example:
278
-
279
- ```typescript
280
- public show = () => (this.visibility = Visibility.VISIBLE);
281
-
282
- public set visibility(visibility: Visibility) {
283
- visibility = visibility ?? Visibility.VISIBLE;
284
- visibility = Validate.visibility(visibility);
285
-
286
- if (this._visibility === visibility) return;
287
-
288
- this._visibility = visibility;
289
- visibility === Visibility.VISIBLE && this.removeAttribute(Attributes.VISIBILITY);
290
- visibility === Visibility.HIDDEN && this.setAttribute(Attributes.VISIBILITY, visibility);
291
- }
292
- ```
293
-
294
- In this example, repeated safety is illustrated by calling `show()` when the component is already in the visible state `Visibility.VISIBLE`. The setter's guard clause detects that no accepted outcome is needed and returns without mutation, reflection, or event dispatch.
295
-
296
- # 10) Coordinating Operations May Orchestrate Multiple Internal Steps as One Public Action
297
-
298
- Some public operations represent a coordinated component workflow rather than a single direct mutation.
299
-
300
- These are valid public operations.
301
-
302
- Policy:
303
-
304
- - a coordinating operation may mutate more than one component-owned construct
305
- - a coordinating operation may call other public operations or controlled internal helpers
306
- - a coordinating operation must preserve authoritative ownership of each affected construct
307
- - a coordinating operation must not duplicate mutation logic that already belongs to another authoritative mutation path
308
- - where semantic events belong to the operation itself rather than to a single state outcome, the operation may dispatch those events directly
309
-
310
- # 11) Composite Operations May Derive the Next Target from Current Component Context
311
-
312
- Some operations do not map to a single fixed target value. Instead, they derive the next target from current component context.
313
-
314
- These are valid public operations.
315
-
316
- Policy:
317
-
318
- - a composite operation may read current state or other internally owned component constructs to determine the next target
319
- - a composite operation must remain small and deterministic
320
- - a composite operation must not reimplement mutation logic that belongs to an authoritative mutation path
321
- - the derived next target must still be applied through the authoritative mutation path of the affected construct or through a controlled internal workflow
322
-
323
- Example:
324
-
325
- ```typescript
326
- public toggle = () =>
327
- (this.status =
328
- this.status === Status.PINNED ? Status.UNPINNED : Status.PINNED);
329
- ```
330
-
331
- In this example, `toggle()` reads the current `status`, computes the opposite allowed `status`, and then delegates through the normal `status` setter. The operation remains thin because the setter still owns transition acceptance, reflection, and semantic event emission.
332
-
333
- # 12) Runtime Gestures Must Route Through the Canonical Behavior Entry
334
-
335
- When a runtime gesture triggers behavior that is declared as a public operation, the gesture handler must invoke that operation.
336
-
337
- If the behavior is not declared as a public operation, the gesture handler must delegate through the authoritative mutation path of the relevant construct.
338
-
339
- Policy:
340
-
341
- - if a behavior is declared in `Operations`, runtime gesture handlers must invoke that operation
342
- - gesture handlers must not reimplement declared public action logic inline
343
- - if no public operation exists for the behavior, the gesture handler must use the authoritative mutation path of the affected construct
344
- - operations remain the canonical public action surface for both imperative callers and runtime gesture handlers
345
-
346
- Example:
347
-
348
- ```typescript
349
- private _handleClick = () => this.toggle();
350
- ```
351
-
352
- Preferred flow when the behavior is declared as an operation:
353
-
354
- ```text
355
- gesture
356
- ↓
357
- handler
358
- ↓
359
- operation
360
- ↓
361
- construct mutation
362
- ↓
363
- accepted outcome
364
- ↓
365
- event dispatch
366
- ```
367
-
368
- In this example, the click handler does not implement toggle behavior itself. It delegates to `toggle()`, keeping the operation layer as the single reusable action entry point for both user-driven and imperative behavior.
369
-
370
- ---
371
-
372
- # Policy Checklist
373
-
374
- 1. Declare every public action under `Operations` in the specification with `description`, `parameters`, and `returns`.
375
- 2. Ensure operations represent callable public actions only — not state, gestures, or events.
376
- 3. Add a key to `Operation` in `<component>.meta.ts` for every declared operation.
377
- 4. Implement every declared operation as a public arrow function class property whose name matches the canonical operation name.
378
- 5. Ensure the property signature honors the declared `parameters` and `returns` contract.
379
- 6. Keep every operation thin — it must not reimplement validation, guard logic, reflection, or transition logic that belongs to the authoritative mutation path.
380
- 7. For operations that change state, delegate to the authoritative state setter. Do not dispatch semantic events from the operation itself.
381
- 8. Ensure repeated calls are safe — repeat safety must be owned by the authoritative mutation path, not duplicated in the operation.
382
- 9. For composite operations (e.g., `toggle`), derive the next target from current component context and delegate through the same authoritative mutation path.
383
- 10. For coordinating operations that orchestrate multiple steps, preserve authoritative ownership of each affected construct.
384
- 11. Ensure runtime gesture handlers invoke the declared public operation rather than reimplementing the behavior inline.
385
-
386
- ---
387
-
388
- # Policy Applied to Specification
389
-
390
- Specification fragment:
391
-
392
- ```json
393
- {
394
- "Operations": {
395
- "hide": {
396
- "description": "Sets 'visibility' state to 'hidden' and hides the pin.",
397
- "parameters": [],
398
- "returns": "void"
399
- },
400
- "show": {
401
- "description": "Sets 'visibility' state to 'visible' and shows the pin.",
402
- "parameters": [],
403
- "returns": "void"
404
- },
405
- "pin": {
406
- "description": "Sets the 'status' to 'pinned', and pins the pin.",
407
- "parameters": [],
408
- "returns": "void"
409
- },
410
- "unpin": {
411
- "description": "Sets the 'status' to 'unpinned', and unpins the pin.",
412
- "parameters": [],
413
- "returns": "void"
414
- },
415
- "toggle": {
416
- "description": "Toggles the 'status' between 'pinned' and 'unpinned'.",
417
- "parameters": [],
418
- "returns": "void"
419
- }
420
- }
421
- }
422
- ```
423
-
424
- How this specification is implemented:
425
-
426
- - every declared operation is represented in metadata as part of the canonical `Operation` vocabulary
427
- - every declared operation is implemented as a corresponding public arrow function class property
428
- - `hide`
429
- - public operation
430
- - delegates to the authoritative `visibility` state mutation path with `Visibility.HIDDEN`
431
- - `show`
432
- - public operation
433
- - delegates to the authoritative `visibility` state mutation path with `Visibility.VISIBLE`
434
- - `pin`
435
- - public operation
436
- - delegates to the authoritative `status` state mutation path with `Status.PINNED`
437
- - `unpin`
438
- - public operation
439
- - delegates to the authoritative `status` state mutation path with `Status.UNPINNED`
440
- - `toggle`
441
- - public composite operation
442
- - reads the current `status`
443
- - computes the next allowed `status`
444
- - delegates the derived value through the same authoritative `status` mutation path as the direct operations
445
-
446
- Implementation:
447
-
448
- ```typescript
449
- public hide = () => (this.visibility = Visibility.HIDDEN);
450
-
451
- public show = () => (this.visibility = Visibility.VISIBLE);
452
-
453
- public pin = () => (this.status = Status.PINNED);
454
-
455
- public unpin = () => (this.status = Status.UNPINNED);
456
-
457
- public toggle = () =>
458
- (this.status =
459
- this.status === Status.PINNED ? Status.UNPINNED : Status.PINNED);
460
- ```
461
-
462
- How the implementation applies the policy:
463
-
464
- - the specification defines the public operation contract under `Operations`
465
- - metadata defines the canonical operation vocabulary
466
- - the component class implements each declared operation as a public arrow function class property with the same canonical name
467
- - operations remain thin and do not duplicate authoritative mutation logic
468
- - in this specification, direct operations delegate through the authoritative public state mutation path
469
- - composite operations may derive the next target state from current state, but still delegate through the same authoritative mutation path
470
- - semantic events that represent state outcomes are emitted by accepted state transitions, not by the operations directly
471
- - repeated calls remain safe because the delegated state setters guard and no-op when no real transition is accepted
472
- - runtime gestures invoke operations as the canonical public behavior entry for declared actions
473
-
474
- In this specification, all declared operations have an empty `parameters` contract and a `void` return contract, so each implementation is a nullary public arrow function class property whose effect is expressed through delegated state transition rather than through returned data.
475
-
476
- # Component Operation Specification Schema
477
-
478
- JSON Schema:
479
-
480
- ```json
481
- {
482
- "$schema": "https://json-schema.org/draft/2020-12/schema",
483
- "$id": "https://scalable.software/schema/component-operations.schema.json",
484
- "title": "Component Operations",
485
- "description": "Schema for the Operations object in a component specification.",
486
- "type": "object",
487
- "propertyNames": {
488
- "type": "string",
489
- "pattern": "^[a-z][a-zA-Z0-9]*$",
490
- "description": "Canonical operation name in lowerCamelCase, for example: hide, show, pin, unpin, toggle."
491
- },
492
- "additionalProperties": {
493
- "type": "object",
494
- "description": "An operation definition.",
495
- "required": ["description", "parameters", "returns"],
496
- "additionalProperties": false,
497
- "properties": {
498
- "description": {
499
- "type": "string",
500
- "minLength": 1,
501
- "description": "Human-readable description of the public action performed by the operation."
502
- },
503
- "parameters": {
504
- "type": "array",
505
- "description": "Ordered list of public invocation parameters for the operation. Use an empty array when the operation takes no arguments.",
506
- "items": {
507
- "type": "object",
508
- "required": ["name", "type", "description"],
509
- "additionalProperties": false,
510
- "properties": {
511
- "name": {
512
- "type": "string",
513
- "pattern": "^[a-z][a-zA-Z0-9]*$",
514
- "description": "Canonical parameter name in lowerCamelCase."
515
- },
516
- "type": {
517
- "type": "string",
518
- "minLength": 1,
519
- "description": "Declared parameter type, for example: Metadata, string, boolean."
520
- },
521
- "description": {
522
- "type": "string",
523
- "minLength": 1,
524
- "description": "Human-readable description of the parameter."
525
- }
526
- }
527
- }
528
- },
529
- "returns": {
530
- "type": "string",
531
- "minLength": 1,
532
- "description": "Declared return contract for the operation. Use \"void\" for action-oriented operations that do not return a value."
533
- }
534
- }
535
- }
536
- }
537
- ```
538
-
539
- Example JSON:
540
-
541
- ```json
542
- {
543
- "Operations": {
544
- "hide": {
545
- "description": "Sets 'visibility' state to 'hidden' and hides the pin.",
546
- "parameters": [],
547
- "returns": "void"
548
- },
549
- "show": {
550
- "description": "Sets 'visibility' state to 'visible' and shows the pin.",
551
- "parameters": [],
552
- "returns": "void"
553
- },
554
- "pin": {
555
- "description": "Sets the 'status' to 'pinned', and pins the pin.",
556
- "parameters": [],
557
- "returns": "void"
558
- },
559
- "unpin": {
560
- "description": "Sets the 'status' to 'unpinned', and unpins the pin.",
561
- "parameters": [],
562
- "returns": "void"
563
- },
564
- "toggle": {
565
- "description": "Toggles the 'status' between 'pinned' and 'unpinned'.",
566
- "parameters": [],
567
- "returns": "void"
568
- }
569
- }
570
- }
571
- ```
572
-
573
- Example JSON with parameters:
574
-
575
- ```json
576
- {
577
- "Operations": {
578
- "load": {
579
- "description": "Load metadata into the component.",
580
- "parameters": [
581
- {
582
- "name": "metadata",
583
- "type": "Metadata",
584
- "description": "The metadata instance to load into the component."
585
- }
586
- ],
587
- "returns": "void"
588
- },
589
- "create": {
590
- "description": "Create a new metadata instance for the component.",
591
- "parameters": [],
592
- "returns": "void"
593
- }
594
- }
595
- }
596
- ```
1
+ # Operation
2
+
3
+ This document defines an implementation policy for **component operations**, then illustrates how specifications are implemented by applying this policy.
4
+
5
+ This document is intentionally **operations** only, but part of a set of implementation policies which together defines the policies which should be followed to transform specifications into an implementation which is predictable, maintainable, and production-ready at scale.
6
+
7
+ ---
8
+
9
+ # How to Read This Policy
10
+
11
+ Rules in this policy fall into three strengths. Read each rule with its strength in mind:
12
+
13
+ - **Principles** are non-negotiable. They derive from framework correctness or TDD discipline and hold for every component. A conflict with a principle is a defect.
14
+ - **Templates** are invariant skeletons with explicit variation points. The skeleton applies to every component; specific steps activate or deactivate based on the component's declared classification (e.g., attribute-backed vs non-attribute, readonly vs writable, payload-bearing vs payload-free). Variation is part of the template, not a deviation from it.
15
+ - **Conventions** are canonical defaults that keep implementations readable across components. A second component may deviate when its shape genuinely warrants it; the deviation should be explained at review time, not forbidden by the policy.
16
+
17
+ When applying this policy to a new component, classify each rule before reconciling conflicts. A conflict with a principle is a defect; a conflict with a template means a variation point needs to be made explicit; a conflict with a convention is a documented deviation.
18
+
19
+ ---
20
+
21
+ # 1) Operation Contract Must Be Declared in the Specification
22
+
23
+ Each component specification must declare its operation contract under `Operations`.
24
+
25
+ `Operations` must be an object whose property names are the canonical operation names.
26
+
27
+ Each operation definition must include:
28
+
29
+ - `description`
30
+ - `parameters`
31
+ - `returns`
32
+
33
+ Interpretation:
34
+
35
+ - `description` defines the public action intent
36
+ - `parameters` defines the public invocation contract
37
+ - `returns` defines the public result contract
38
+ - if `parameters` is empty, the operation takes no arguments
39
+ - if `returns` is `"void"`, the operation does not return a value
40
+
41
+ Example specification:
42
+
43
+ ```json
44
+ "toggle": {
45
+ "description": "Toggles the 'status' between 'pinned' and 'unpinned'.",
46
+ "parameters": [],
47
+ "returns": "void"
48
+ }
49
+ ```
50
+
51
+ This defines the public action contract that the implementation must honor.
52
+
53
+ # Template Variation Points
54
+
55
+ Operations vary by their relationship to component state and data. The classification determines which sub-rules activate:
56
+
57
+ - **Primitive operations** — delegate to a single authoritative state mutation path (Section 7). Their semantic event is emitted by the accepted state transition, not by the operation (Section 8). Examples: `hide`, `show`, `pin`, `unpin`.
58
+ - **Composite operations** — derive the next target from current component context and delegate through the same authoritative mutation path (Section 11). They typically have no own event because the underlying state transition emits the existing event vocabulary. Example: `toggle`.
59
+ - **Coordinating operations** — orchestrate multiple internal steps as one public action and may dispatch their own semantic events directly when the operation itself is the semantic unit (Section 10). Examples: `save`, `cancel`.
60
+ - **Data-bearing operations** — accept or construct a data value and delegate through the authoritative data setter (Section 13). Examples: `load`, `create`, `update`, `reset`.
61
+
62
+ Each operation must be classifiable as exactly one type. A new operation type is a signal that this list needs to be extended, not that the operation deviates from the template.
63
+
64
+ # 2) Operations Must Represent Public Actions
65
+
66
+ This framework distinguishes:
67
+
68
+ - `Operations`: the set of callable public actions exposed by the component
69
+
70
+ Policy:
71
+
72
+ - operations describe callable public actions
73
+ - operations must not be used to declare state
74
+ - operations must not be used to declare gestures
75
+ - operations must not be used to declare events
76
+
77
+ Examples of valid operations:
78
+
79
+ - `hide`
80
+ - `show`
81
+ - `pin`
82
+ - `unpin`
83
+ - `toggle`
84
+
85
+ Examples that must not be modeled as operations:
86
+
87
+ - `visibility`
88
+ - `status`
89
+ - `click`
90
+ - `hover`
91
+ - `onpin`
92
+
93
+ These belong to other framework concerns.
94
+
95
+ # 3) Public Operation Names Must Be Declared in Metadata
96
+
97
+ For every operation declared in the specification, the implementation must define corresponding metadata in `<component>.meta.ts`.
98
+
99
+ This metadata defines the canonical operation vocabulary for the component.
100
+
101
+ Policy:
102
+
103
+ - every declared public operation must have a key in `Operation`
104
+ - `Operation` contains only declared public operation names
105
+ - the implementation must use operation metadata consistently as the canonical operation vocabulary
106
+
107
+ Example pattern:
108
+
109
+ ```typescript
110
+ export const Operation = {
111
+ HIDE: "hide",
112
+ SHOW: "show",
113
+ PIN: "pin",
114
+ UNPIN: "unpin",
115
+ TOGGLE: "toggle",
116
+ } as const;
117
+
118
+ export type Operation = (typeof Operation)[keyof typeof Operation];
119
+ ```
120
+
121
+ # 4) Every Declared Public Operation Must Be Implemented as a Public Arrow Function Class Property
122
+
123
+ For every operation declared in the specification, the component class must implement a corresponding public arrow function class property.
124
+
125
+ Policy:
126
+
127
+ - every operation declared in `Operations` must have a corresponding public arrow function class property on the component class
128
+ - the property name must match the canonical operation name
129
+ - the property signature must honor the declared `parameters` and `returns` contract
130
+ - public operations form part of the component's imperative API
131
+
132
+ Operations are implemented as arrow function class properties — not prototype methods — because this guarantees `this` binding at the point of definition. This makes operations safe to pass as callbacks without `.bind()`, which is important for gesture handlers and external consumers.
133
+
134
+ Example pattern:
135
+
136
+ ```typescript
137
+ public hide = () => (this.visibility = Visibility.HIDDEN);
138
+ public show = () => (this.visibility = Visibility.VISIBLE);
139
+ public pin = () => (this.status = Status.PINNED);
140
+ public unpin = () => (this.status = Status.UNPINNED);
141
+ public toggle = () =>
142
+ (this.status =
143
+ this.status === Status.PINNED ? Status.UNPINNED : Status.PINNED);
144
+ ```
145
+
146
+ This gives the component a stable imperative API:
147
+
148
+ ```typescript
149
+ pin.hide();
150
+ pin.show();
151
+ pin.pin();
152
+ pin.unpin();
153
+ pin.toggle();
154
+ ```
155
+
156
+ # 5) Operations Form the Public Imperative Surface of the Component
157
+
158
+ Operations form the public imperative surface of the component.
159
+
160
+ Policy:
161
+
162
+ - public operations define the callable action surface exposed to external consumers
163
+ - public operations must be intentionally named according to the declared operation contract
164
+ - public operations must provide a stable imperative entry for the behaviors they represent
165
+ - where a declared public operation exists for a behavior, it should be reusable by both imperative callers and runtime gesture handlers
166
+
167
+ Example imperative usage:
168
+
169
+ ```typescript
170
+ pin.toggle();
171
+ pin.pin();
172
+ ```
173
+
174
+ Example runtime reuse:
175
+
176
+ ```typescript
177
+ private _handleClick = () => this.toggle();
178
+ ```
179
+
180
+ # 6) Public Operation Methods Must Honor the Declared Return Contract
181
+
182
+ The default return contract for action-oriented operations is `void`.
183
+
184
+ If an operation returns a value, that return contract must be declared explicitly in the specification.
185
+
186
+ Policy:
187
+
188
+ - the public operation property must honor the `returns` contract declared in the specification
189
+ - `void` is the default return contract for action-oriented operations
190
+ - non-`void` return contracts are valid only when explicitly declared in the specification
191
+
192
+ Pin follows the default pattern: all declared operations return `void`.
193
+
194
+ # 7) Operations Must Remain Thin and Delegate to the Authoritative Mutation Path
195
+
196
+ Operations must not duplicate mutation logic internally.
197
+
198
+ When an operation produces its effect by mutating a component-owned construct, it must delegate through the authoritative mutation path of that construct or through a controlled internal transition.
199
+
200
+ Policy:
201
+
202
+ - operations express action intent
203
+ - operations must not reimplement validation, guard logic, reflection, transition acceptance, or other mutation behavior that belongs to an authoritative construct mutation path
204
+ - when the operation maps to a writable public construct, it should normally delegate to that construct's public mutation path
205
+ - when the operation requires a controlled internal transition or coordinated internal workflow, that path must remain authoritative
206
+ - operations may act on state, data, or other internally owned constructs, provided they preserve the authoritative ownership of mutation behavior
207
+
208
+ Example:
209
+
210
+ ```typescript
211
+ public hide = () => (this.visibility = Visibility.HIDDEN);
212
+ public pin = () => (this.status = Status.PINNED);
213
+ ```
214
+
215
+ In this example, `hide()` and `pin()` remain thin because they express public action intent while delegating mutation behavior to the authoritative `visibility` and `status` state setters. The operation does not become a second mutation system.
216
+
217
+ # 8) State-Outcome Semantic Events Must Be Emitted by Accepted State Transitions
218
+
219
+ If an operation produces a semantic outcome by changing state, the operation must not dispatch the semantic event directly.
220
+
221
+ Instead, the operation must delegate to state mutation, and the event must be emitted by the accepted state transition.
222
+
223
+ Preferred flow:
224
+
225
+ ```text
226
+ operation
227
+ ↓
228
+ state mutation
229
+ ↓
230
+ accepted state transition
231
+ ↓
232
+ event dispatch
233
+ ```
234
+
235
+ Policy:
236
+
237
+ - operations must not dispatch semantic events directly when those events represent state outcomes
238
+ - accepted state transition is the single semantic source of truth for state-outcome event emission
239
+ - if the requested state transition is not accepted, the semantic event must not be emitted
240
+
241
+ This rule applies only to semantic events whose meaning is an accepted state outcome. Operations may dispatch semantic events directly when the operation itself is the semantic unit and the event does not merely restate a state transition.
242
+
243
+ Example:
244
+
245
+ ```typescript
246
+ public pin = () => (this.status = Status.PINNED);
247
+
248
+ public set status(status: Status) {
249
+ status = Validate.status(status);
250
+
251
+ if (this._status === status) return;
252
+
253
+ this._status = status;
254
+ this.setAttribute(Attributes.STATUS, status);
255
+
256
+ const event = { detail: { status } };
257
+
258
+ status === Status.PINNED && this._dispatchEvent(Event.ON_PIN, event);
259
+ status === Status.UNPINNED && this._dispatchEvent(Event.ON_UNPIN, event);
260
+ }
261
+ ```
262
+
263
+ # 9) Operations Must Be Safe to Call Repeatedly
264
+
265
+ Operations must be safe to call repeatedly.
266
+
267
+ Where the requested outcome is already true, the operation must resolve through the normal authoritative mutation path without producing a duplicate accepted outcome or duplicate semantic side effects.
268
+
269
+ Policy:
270
+
271
+ - repeated invocation of an operation must not create invalid transitions
272
+ - where the requested outcome is already satisfied, the operation must not produce a duplicate accepted outcome
273
+ - repeated safety should normally be achieved through the authoritative mutation path, not by duplicating guard logic in the operation itself
274
+ - if no accepted outcome occurs, no duplicate semantic side effects should be produced
275
+ - when the operation delegates through state, data, or another internally owned construct, repeat safety must be preserved by that construct's authoritative mutation path or controlled internal workflow
276
+
277
+ Example:
278
+
279
+ ```typescript
280
+ public show = () => (this.visibility = Visibility.VISIBLE);
281
+
282
+ public set visibility(visibility: Visibility) {
283
+ visibility = visibility ?? Visibility.VISIBLE;
284
+ visibility = Validate.visibility(visibility);
285
+
286
+ if (this._visibility === visibility) return;
287
+
288
+ this._visibility = visibility;
289
+ visibility === Visibility.VISIBLE && this.removeAttribute(Attributes.VISIBILITY);
290
+ visibility === Visibility.HIDDEN && this.setAttribute(Attributes.VISIBILITY, visibility);
291
+ }
292
+ ```
293
+
294
+ In this example, repeated safety is illustrated by calling `show()` when the component is already in the visible state `Visibility.VISIBLE`. The setter's guard clause detects that no accepted outcome is needed and returns without mutation, reflection, or event dispatch.
295
+
296
+ # 10) Coordinating Operations May Orchestrate Multiple Internal Steps as One Public Action
297
+
298
+ Some public operations represent a coordinated component workflow rather than a single direct mutation.
299
+
300
+ These are valid public operations.
301
+
302
+ Policy:
303
+
304
+ - a coordinating operation may mutate more than one component-owned construct
305
+ - a coordinating operation may call other public operations or controlled internal helpers
306
+ - a coordinating operation must preserve authoritative ownership of each affected construct
307
+ - a coordinating operation must not duplicate mutation logic that already belongs to another authoritative mutation path
308
+ - where semantic events belong to the operation itself rather than to a single state outcome, the operation may dispatch those events directly
309
+
310
+ # 11) Composite Operations May Derive the Next Target from Current Component Context
311
+
312
+ Some operations do not map to a single fixed target value. Instead, they derive the next target from current component context.
313
+
314
+ These are valid public operations.
315
+
316
+ Policy:
317
+
318
+ - a composite operation may read current state or other internally owned component constructs to determine the next target
319
+ - a composite operation must remain small and deterministic
320
+ - a composite operation must not reimplement mutation logic that belongs to an authoritative mutation path
321
+ - the derived next target must still be applied through the authoritative mutation path of the affected construct or through a controlled internal workflow
322
+
323
+ Example:
324
+
325
+ ```typescript
326
+ public toggle = () =>
327
+ (this.status =
328
+ this.status === Status.PINNED ? Status.UNPINNED : Status.PINNED);
329
+ ```
330
+
331
+ In this example, `toggle()` reads the current `status`, computes the opposite allowed `status`, and then delegates through the normal `status` setter. The operation remains thin because the setter still owns transition acceptance, reflection, and semantic event emission.
332
+
333
+ # 12) Runtime Gestures Must Route Through the Canonical Behavior Entry
334
+
335
+ When a runtime gesture triggers behavior that is declared as a public operation, the gesture handler must invoke that operation.
336
+
337
+ If the behavior is not declared as a public operation, the gesture handler must delegate through the authoritative mutation path of the relevant construct.
338
+
339
+ Policy:
340
+
341
+ - if a behavior is declared in `Operations`, runtime gesture handlers must invoke that operation
342
+ - gesture handlers must not reimplement declared public action logic inline
343
+ - if no public operation exists for the behavior, the gesture handler must use the authoritative mutation path of the affected construct
344
+ - operations remain the canonical public action surface for both imperative callers and runtime gesture handlers
345
+
346
+ Example:
347
+
348
+ ```typescript
349
+ private _handleClick = () => this.toggle();
350
+ ```
351
+
352
+ Preferred flow when the behavior is declared as an operation:
353
+
354
+ ```text
355
+ gesture
356
+ ↓
357
+ handler
358
+ ↓
359
+ operation
360
+ ↓
361
+ construct mutation
362
+ ↓
363
+ accepted outcome
364
+ ↓
365
+ event dispatch
366
+ ```
367
+
368
+ In this example, the click handler does not implement toggle behavior itself. It delegates to `toggle()`, keeping the operation layer as the single reusable action entry point for both user-driven and imperative behavior.
369
+
370
+ ---
371
+
372
+ # Policy Checklist
373
+
374
+ 1. Declare every public action under `Operations` in the specification with `description`, `parameters`, and `returns`.
375
+ 2. Ensure operations represent callable public actions only — not state, gestures, or events.
376
+ 3. Add a key to `Operation` in `<component>.meta.ts` for every declared operation.
377
+ 4. Implement every declared operation as a public arrow function class property whose name matches the canonical operation name.
378
+ 5. Ensure the property signature honors the declared `parameters` and `returns` contract.
379
+ 6. Keep every operation thin — it must not reimplement validation, guard logic, reflection, or transition logic that belongs to the authoritative mutation path.
380
+ 7. For operations that change state, delegate to the authoritative state setter. Do not dispatch semantic events from the operation itself.
381
+ 8. Ensure repeated calls are safe — repeat safety must be owned by the authoritative mutation path, not duplicated in the operation.
382
+ 9. For composite operations (e.g., `toggle`), derive the next target from current component context and delegate through the same authoritative mutation path.
383
+ 10. For coordinating operations that orchestrate multiple steps, preserve authoritative ownership of each affected construct.
384
+ 11. Ensure runtime gesture handlers invoke the declared public operation rather than reimplementing the behavior inline.
385
+
386
+ ---
387
+
388
+ # Policy Applied to Specification
389
+
390
+ Specification fragment:
391
+
392
+ ```json
393
+ {
394
+ "Operations": {
395
+ "hide": {
396
+ "description": "Sets 'visibility' state to 'hidden' and hides the pin.",
397
+ "parameters": [],
398
+ "returns": "void"
399
+ },
400
+ "show": {
401
+ "description": "Sets 'visibility' state to 'visible' and shows the pin.",
402
+ "parameters": [],
403
+ "returns": "void"
404
+ },
405
+ "pin": {
406
+ "description": "Sets the 'status' to 'pinned', and pins the pin.",
407
+ "parameters": [],
408
+ "returns": "void"
409
+ },
410
+ "unpin": {
411
+ "description": "Sets the 'status' to 'unpinned', and unpins the pin.",
412
+ "parameters": [],
413
+ "returns": "void"
414
+ },
415
+ "toggle": {
416
+ "description": "Toggles the 'status' between 'pinned' and 'unpinned'.",
417
+ "parameters": [],
418
+ "returns": "void"
419
+ }
420
+ }
421
+ }
422
+ ```
423
+
424
+ How this specification is implemented:
425
+
426
+ - every declared operation is represented in metadata as part of the canonical `Operation` vocabulary
427
+ - every declared operation is implemented as a corresponding public arrow function class property
428
+ - `hide`
429
+ - public operation
430
+ - delegates to the authoritative `visibility` state mutation path with `Visibility.HIDDEN`
431
+ - `show`
432
+ - public operation
433
+ - delegates to the authoritative `visibility` state mutation path with `Visibility.VISIBLE`
434
+ - `pin`
435
+ - public operation
436
+ - delegates to the authoritative `status` state mutation path with `Status.PINNED`
437
+ - `unpin`
438
+ - public operation
439
+ - delegates to the authoritative `status` state mutation path with `Status.UNPINNED`
440
+ - `toggle`
441
+ - public composite operation
442
+ - reads the current `status`
443
+ - computes the next allowed `status`
444
+ - delegates the derived value through the same authoritative `status` mutation path as the direct operations
445
+
446
+ Implementation:
447
+
448
+ ```typescript
449
+ public hide = () => (this.visibility = Visibility.HIDDEN);
450
+
451
+ public show = () => (this.visibility = Visibility.VISIBLE);
452
+
453
+ public pin = () => (this.status = Status.PINNED);
454
+
455
+ public unpin = () => (this.status = Status.UNPINNED);
456
+
457
+ public toggle = () =>
458
+ (this.status =
459
+ this.status === Status.PINNED ? Status.UNPINNED : Status.PINNED);
460
+ ```
461
+
462
+ How the implementation applies the policy:
463
+
464
+ - the specification defines the public operation contract under `Operations`
465
+ - metadata defines the canonical operation vocabulary
466
+ - the component class implements each declared operation as a public arrow function class property with the same canonical name
467
+ - operations remain thin and do not duplicate authoritative mutation logic
468
+ - in this specification, direct operations delegate through the authoritative public state mutation path
469
+ - composite operations may derive the next target state from current state, but still delegate through the same authoritative mutation path
470
+ - semantic events that represent state outcomes are emitted by accepted state transitions, not by the operations directly
471
+ - repeated calls remain safe because the delegated state setters guard and no-op when no real transition is accepted
472
+ - runtime gestures invoke operations as the canonical public behavior entry for declared actions
473
+
474
+ In this specification, all declared operations have an empty `parameters` contract and a `void` return contract, so each implementation is a nullary public arrow function class property whose effect is expressed through delegated state transition rather than through returned data.
475
+
476
+ # Component Operation Specification Schema
477
+
478
+ JSON Schema:
479
+
480
+ ```json
481
+ {
482
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
483
+ "$id": "https://scalable.software/schema/component-operations.schema.json",
484
+ "title": "Component Operations",
485
+ "description": "Schema for the Operations object in a component specification.",
486
+ "type": "object",
487
+ "propertyNames": {
488
+ "type": "string",
489
+ "pattern": "^[a-z][a-zA-Z0-9]*$",
490
+ "description": "Canonical operation name in lowerCamelCase, for example: hide, show, pin, unpin, toggle."
491
+ },
492
+ "additionalProperties": {
493
+ "type": "object",
494
+ "description": "An operation definition.",
495
+ "required": ["description", "parameters", "returns"],
496
+ "additionalProperties": false,
497
+ "properties": {
498
+ "description": {
499
+ "type": "string",
500
+ "minLength": 1,
501
+ "description": "Human-readable description of the public action performed by the operation."
502
+ },
503
+ "parameters": {
504
+ "type": "array",
505
+ "description": "Ordered list of public invocation parameters for the operation. Use an empty array when the operation takes no arguments.",
506
+ "items": {
507
+ "type": "object",
508
+ "required": ["name", "type", "description"],
509
+ "additionalProperties": false,
510
+ "properties": {
511
+ "name": {
512
+ "type": "string",
513
+ "pattern": "^[a-z][a-zA-Z0-9]*$",
514
+ "description": "Canonical parameter name in lowerCamelCase."
515
+ },
516
+ "type": {
517
+ "type": "string",
518
+ "minLength": 1,
519
+ "description": "Declared parameter type, for example: Metadata, string, boolean."
520
+ },
521
+ "description": {
522
+ "type": "string",
523
+ "minLength": 1,
524
+ "description": "Human-readable description of the parameter."
525
+ }
526
+ }
527
+ }
528
+ },
529
+ "returns": {
530
+ "type": "string",
531
+ "minLength": 1,
532
+ "description": "Declared return contract for the operation. Use \"void\" for action-oriented operations that do not return a value."
533
+ }
534
+ }
535
+ }
536
+ }
537
+ ```
538
+
539
+ Example JSON:
540
+
541
+ ```json
542
+ {
543
+ "Operations": {
544
+ "hide": {
545
+ "description": "Sets 'visibility' state to 'hidden' and hides the pin.",
546
+ "parameters": [],
547
+ "returns": "void"
548
+ },
549
+ "show": {
550
+ "description": "Sets 'visibility' state to 'visible' and shows the pin.",
551
+ "parameters": [],
552
+ "returns": "void"
553
+ },
554
+ "pin": {
555
+ "description": "Sets the 'status' to 'pinned', and pins the pin.",
556
+ "parameters": [],
557
+ "returns": "void"
558
+ },
559
+ "unpin": {
560
+ "description": "Sets the 'status' to 'unpinned', and unpins the pin.",
561
+ "parameters": [],
562
+ "returns": "void"
563
+ },
564
+ "toggle": {
565
+ "description": "Toggles the 'status' between 'pinned' and 'unpinned'.",
566
+ "parameters": [],
567
+ "returns": "void"
568
+ }
569
+ }
570
+ }
571
+ ```
572
+
573
+ Example JSON with parameters:
574
+
575
+ ```json
576
+ {
577
+ "Operations": {
578
+ "load": {
579
+ "description": "Load metadata into the component.",
580
+ "parameters": [
581
+ {
582
+ "name": "metadata",
583
+ "type": "Metadata",
584
+ "description": "The metadata instance to load into the component."
585
+ }
586
+ ],
587
+ "returns": "void"
588
+ },
589
+ "create": {
590
+ "description": "Create a new metadata instance for the component.",
591
+ "parameters": [],
592
+ "returns": "void"
593
+ }
594
+ }
595
+ }
596
+ ```