@effect.healthcare/messenger 0.3.1 → 0.4.1

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,1036 +1,1036 @@
1
- # Data
2
-
3
- This document defines an implementation policy for **component data**, then illustrates how specifications are implemented by applying this policy.
4
-
5
- This document is intentionally **data** only, but part of a set of implementation policies which together define how specifications should be transformed into implementations which are 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) Data Contract Must Be Declared in the Specification
22
-
23
- Each component specification must declare its data contract under `Data`.
24
-
25
- `Data` must be an object whose property names are the canonical data names.
26
-
27
- Each data definition must include:
28
-
29
- - `type`
30
- - `description`
31
-
32
- A data definition may also include:
33
-
34
- - `properties`
35
- - `default`
36
- - `readonly`
37
-
38
- Interpretation:
39
-
40
- - `type` defines the public runtime shape category of the data
41
- - `description` defines the semantic purpose of the data
42
- - `properties` defines the structured fields of object-shaped data
43
- - `default` defines the default runtime value when one exists
44
- - `readonly` defines whether the data is externally writable or internally owned
45
- - if `properties` is present, the data is structured
46
- - if `properties` is absent, the data is scalar or otherwise non-structured
47
-
48
- Example specification:
49
-
50
- ```json
51
- {
52
- "Data": {
53
- "metadata": {
54
- "type": "object",
55
- "description": "The metadata of the clinical pathway.",
56
- "properties": {
57
- "id": {
58
- "type": "uuid",
59
- "description": "The unique identifier of the clinical pathway."
60
- },
61
- "name": {
62
- "type": "string",
63
- "description": "The name of the clinical pathway.",
64
- "default": "Clinical Pathway"
65
- },
66
- "type": {
67
- "type": "MapType",
68
- "description": "The type of the map.",
69
- "default": "pathway"
70
- }
71
- },
72
- "default": null,
73
- "readonly": false
74
- }
75
- }
76
- }
77
- ```
78
-
79
- This defines the public data contract that the implementation must honor.
80
-
81
- # Template Variation Points
82
-
83
- Data items vary by structure, ownership, and runtime nullability:
84
-
85
- - **Structured vs scalar** — structured data declares `properties` and uses an explicit metadata type (Section 4); scalar data has a flat runtime shape and may not require a structured type definition.
86
- - **Writable vs read-only** — writable data exposes a public setter and follows the normalize → validate → guard → mutate → synchronize mutation order (Sections 8.2, 9); read-only data exposes only a getter and is mutated internally (Section 8.1).
87
- - **Supports unloaded state vs always-present** — when the runtime contract permits an unloaded or cleared state, the public getter and setter types reflect that possibility, and validation may bypass the unloaded sentinel (Section 10). Always-present data does not exercise this variation.
88
-
89
- Data-creation operations (Section 13.1), data-loading operations (Section 13.2), workflow events (Section 15), and the editing-surface pattern (Section 17) activate only when the component's specification declares the corresponding patterns. They are valid template instantiations, not extensions of the policy.
90
-
91
- # 2) Data Must Represent Canonical Component-Owned Content
92
-
93
- This framework distinguishes:
94
-
95
- - `Data`: structured or scalar content owned by the component
96
- - `State`: finite runtime mode, visibility, status, or other UI/runtime condition
97
- - `Operations`: callable public actions
98
- - `Events`: observable semantic outcomes
99
- - `Gestures`: interaction inputs
100
-
101
- Policy:
102
-
103
- - data describes canonical component-owned content
104
- - state describes runtime condition
105
- - operations describe callable public actions
106
- - events describe semantic outcomes
107
- - gestures describe interaction input
108
- - data must not be used to declare state
109
- - data must not be used to declare operations
110
- - data must not be used to declare events
111
- - data must not be used to declare gestures
112
-
113
- Examples of valid data:
114
-
115
- - `metadata`
116
- - `record`
117
- - `document`
118
- - `selection`
119
- - `payload`
120
-
121
- Examples that must not be modeled as data:
122
-
123
- - `visibility`
124
- - `status`
125
- - `mode`
126
- - `click`
127
- - `onload`
128
- - `save`
129
-
130
- Data is the authoritative component-owned content model, not a substitute for the other constructs.
131
-
132
- # 3) Public Data Names Must Be Declared in Metadata
133
-
134
- For every data item declared in the specification, the implementation must define corresponding metadata in `<component>.meta.ts`.
135
-
136
- This metadata defines the canonical data vocabulary for the component.
137
-
138
- Policy:
139
-
140
- - every declared public data item must have a key in `Data`
141
- - `Data` contains only declared public data names
142
- - the implementation must use data metadata consistently as the canonical data vocabulary
143
-
144
- Example pattern:
145
-
146
- ```typescript
147
- export const Data = {
148
- METADATA: "metadata",
149
- } as const;
150
-
151
- export type Data = (typeof Data)[keyof typeof Data];
152
- ```
153
-
154
- This gives the implementation a stable, centralized vocabulary that prevents string duplication and keeps naming consistent across metadata, validation, component API, and data-oriented events.
155
-
156
- # 4) Data Shape Must Be Defined in Metadata
157
-
158
- For every declared data item, the implementation must define the corresponding runtime type in `<component>.meta.ts`.
159
-
160
- This type defines the allowed runtime shape of the canonical data object or value.
161
-
162
- Policy:
163
-
164
- - every declared data item must have a corresponding runtime type definition in metadata
165
- - structured data should be expressed through an explicit TypeScript type
166
- - nested constrained fields may use additional metadata value domains
167
- - the metadata type is the public runtime data contract used by the implementation
168
-
169
- Example pattern:
170
-
171
- ```typescript
172
- export const MapType = {
173
- PATHWAY: "pathway",
174
- } as const;
175
-
176
- export type MapType = (typeof MapType)[keyof typeof MapType];
177
-
178
- export type Metadata = {
179
- id: string;
180
- name: string;
181
- type: MapType;
182
- };
183
- ```
184
-
185
- This makes the data contract explicit in the same way state value domains are made explicit in metadata.
186
-
187
- # 5) Data Validation Must Be Implemented in `<component>.validation.ts`
188
-
189
- For every declared data item, the implementation must define validation logic in `<component>.validation.ts`.
190
-
191
- This validation serves as the runtime enforcement layer for the data contract declared in the specification and expressed in metadata.
192
-
193
- Policy:
194
-
195
- - validation logic for data must be implemented in `<component>.validation.ts`
196
- - validation must enforce the allowed runtime shape and constrained field domains declared in metadata
197
- - public writable mutation paths must call validation before mutation proceeds
198
- - if validation fails, mutation must not proceed
199
- - validation functions should return the validated value in its narrowed runtime type
200
-
201
- Example:
202
-
203
- ```typescript
204
- import { MapType } from "./title.meta.js";
205
- import type { Metadata } from "./title.meta.js";
206
-
207
- export class Validate {
208
- public static metadata = (value: Metadata) => {
209
- if (!value.id) {
210
- throw new Error("Invalid metadata value: id is required");
211
- }
212
-
213
- if (!Object.values(MapType).includes(value.type as MapType)) {
214
- throw new Error("Invalid metadata value: type is invalid");
215
- }
216
-
217
- return value as Metadata;
218
- };
219
- }
220
- ```
221
-
222
- This makes the contract explicit:
223
-
224
- - `<component>.meta.ts` defines the data shape
225
- - `<component>.validation.ts` enforces the data shape
226
- - `<component>.ts` uses validation in the mutation path
227
-
228
- # 6) Data Must Have a Canonical Internal Source of Truth
229
-
230
- Each declared data item must have a canonical internal source of truth on the component instance.
231
-
232
- That source may be nullable when the runtime contract supports an unloaded or cleared state.
233
-
234
- Policy:
235
-
236
- - every declared data item must have a canonical internally owned runtime source of truth
237
- - canonical data should normally be stored in a private backing field
238
- - if the runtime data contract includes an unloaded or cleared state, that state must be represented explicitly
239
- - external API access must read from the canonical internal source of truth
240
-
241
- Example:
242
-
243
- ```typescript
244
- private _metadata: Metadata | null = null;
245
-
246
- public get metadata(): Metadata | null {
247
- return this._metadata;
248
- }
249
- ```
250
-
251
- This keeps ownership clear and prevents DOM structure, transient form controls, or ad hoc derived values from becoming accidental sources of truth.
252
-
253
- # 7) Data Does Not Participate in DOM Attribute Observation by Default
254
-
255
- Unlike attribute-backed state, data is not DOM attribute-backed by default.
256
-
257
- Policy:
258
-
259
- - data must not be declared in `Attributes` unless an explicit policy for attribute-backed data exists
260
- - data does not participate in `attributeChangedCallback` by default
261
- - data must not be treated as DOM attribute state merely because it is externally readable or writable
262
- - data reflection to DOM, serialization, or persistence must be modeled explicitly rather than assumed
263
-
264
- This keeps structured content separate from the DOM attribute system used for finite state.
265
-
266
- # 8) Data Must Declare External Access Contract
267
-
268
- For each non-attribute data item, the implementation must declare its external access contract as one of:
269
-
270
- - **Read-only external**
271
- - **Writable external**
272
-
273
- ## 8.1) Read-only External Data
274
-
275
- For data with `readonly: true`:
276
-
277
- - expose a public getter
278
- - do not expose a public setter
279
- - internal logic may update the canonical value
280
-
281
- Example:
282
-
283
- ```typescript
284
- private _metadata: Metadata | null = null;
285
-
286
- public get metadata(): Metadata | null {
287
- return this._metadata;
288
- }
289
-
290
- protected _loadMetadata = (metadata: Metadata) => {
291
- this._metadata = Validate.metadata(metadata);
292
- };
293
- ```
294
-
295
- ## 8.2) Writable External Data
296
-
297
- For data with `readonly: false`:
298
-
299
- - expose a public getter
300
- - expose a public setter
301
- - public writable mutation must converge on that setter
302
-
303
- Example:
304
-
305
- ```typescript
306
- private _metadata: Metadata | null = null;
307
-
308
- public get metadata(): Metadata | null {
309
- return this._metadata;
310
- }
311
-
312
- public set metadata(metadata: Metadata | null) {
313
- this._metadata = metadata ? Validate.metadata(metadata) : null;
314
- }
315
- ```
316
-
317
- Readonly therefore does **not** mean the data never changes. It means the data is externally readable but internally owned.
318
-
319
- # 9) Public Data Mutation Path Must Be Normalize → Validate → Guard → Mutate → Synchronize
320
-
321
- For writable data, the public mutation path must follow this order:
322
-
323
- 1. **Normalize**
324
- 2. **Validate**
325
- 3. **Guard**
326
- 4. **Mutate**
327
- 5. **Synchronize**
328
-
329
- Interpretation:
330
-
331
- - **Normalize** converts incoming values into the runtime shape expected by validation
332
- - **Validate** enforces the declared data contract
333
- - **Guard** prevents duplicate accepted outcomes when no effective change is needed
334
- - **Mutate** updates the canonical internal value
335
- - **Synchronize** updates dependent projections such as state, composition, or internal helpers
336
-
337
- A mutation path may omit phases that are not needed for a given data item, but the order of any phases that are used must remain Normalize → Validate → Guard → Mutate → Synchronize.
338
-
339
- Example:
340
-
341
- ```typescript
342
- public set metadata(metadata: Metadata | null) {
343
- // Normalize
344
- metadata = metadata ?? null;
345
-
346
- // Validate
347
- metadata = metadata ? Validate.metadata(metadata) : null;
348
-
349
- // Guard
350
- if (this._metadata === metadata) return;
351
-
352
- // Mutate
353
- this._metadata = metadata;
354
-
355
- // Synchronize
356
- this._updateLabel();
357
- this._syncContent();
358
- this._syncMode();
359
- }
360
- ```
361
-
362
- For structured object data, the guard strategy may be referential, structural, semantic, or omitted when no safe equality rule is available. The implementation must choose a strategy that is predictable for the declared contract.
363
-
364
- # 10) Null or Unloaded Runtime Data State Must Be Explicitly Modeled
365
-
366
- Some components support a meaningful runtime state where data is not yet loaded, has been cleared, or has not yet been created.
367
-
368
- This is valid, but it must be modeled explicitly.
369
-
370
- Policy:
371
-
372
- - when a data item supports an unloaded or cleared runtime state, that state must be explicit in the implementation contract
373
- - the public getter and setter types must reflect that runtime possibility
374
- - validation may bypass the unloaded sentinel if that sentinel is part of the runtime access contract rather than part of the structured data shape itself
375
- - operations and events must treat the unloaded state predictably
376
-
377
- Example:
378
-
379
- ```typescript
380
- private _metadata: Metadata | null = null;
381
-
382
- public set metadata(metadata: Metadata | null) {
383
- this._metadata = metadata ? Validate.metadata(metadata) : null;
384
- }
385
- ```
386
-
387
- This keeps unloaded runtime absence distinct from invalid structured data.
388
-
389
- # 11) Data Synchronization May Update Dependent State and Composition
390
-
391
- Data often drives state projections and rendered composition.
392
-
393
- This is valid, but those dependent constructs must preserve their own authoritative mutation behavior.
394
-
395
- Policy:
396
-
397
- - data mutation may synchronize dependent component state
398
- - data mutation may synchronize composition-facing values such as labels, inputs, or cached display content
399
- - when data mutation updates writable state, it should do so through that state's authoritative mutation path
400
- - data synchronization must remain predictable and traceable from the data contract to the dependent runtime behavior
401
-
402
- Example:
403
-
404
- ```typescript
405
- private _updateLabel = () =>
406
- (this.elements.label.textContent = this.metadata?.name ?? "Untitled");
407
-
408
- private _syncContent = () =>
409
- (this.content = !this.metadata?.name ? Content.EMPTY : Content.NAME);
410
-
411
- private _syncMode = () =>
412
- (this.mode = this.content === Content.EMPTY ? Mode.EDIT : Mode.VIEW);
413
- ```
414
-
415
- In this pattern:
416
-
417
- - `metadata` is the canonical data
418
- - `content` is derived runtime state
419
- - `mode` may be further coordinated from that derived state
420
- - label text is a composition projection of the canonical data
421
-
422
- # 12) Operations Must Remain Thin or Orchestrational Around Data
423
-
424
- When an operation affects component data, it must not become a second authoritative mutation system.
425
-
426
- Policy:
427
-
428
- - operations that affect data must remain thin where possible
429
- - operations may orchestrate multi-step workflows around data
430
- - operations must not duplicate validation logic that belongs to the authoritative data mutation path
431
- - operations must not mutate scattered data fields ad hoc when a canonical data mutation path exists
432
- - operations may derive a next data value and delegate it through the authoritative data mutation path
433
-
434
- Examples:
435
-
436
- ```typescript
437
- public load = (metadata: Metadata) => {
438
- this.metadata = metadata;
439
-
440
- const event = { detail: { metadata: this.metadata } };
441
- this._dispatchEvent(Event.ON_LOAD, event);
442
- };
443
-
444
- public update = () => {
445
- if (!this.metadata) return;
446
-
447
- this.metadata = {
448
- ...this.metadata,
449
- name: this.elements.name.value.trim(),
450
- };
451
-
452
- const event = { detail: { metadata: this.metadata } };
453
- this._dispatchEvent(Event.ON_UPDATE, event);
454
- };
455
-
456
- public reset = () => {
457
- this.metadata = null;
458
- this._syncName();
459
- this._dispatchEvent(Event.ON_RESET);
460
- };
461
- ```
462
-
463
- This preserves a single authoritative mutation path for the canonical data while still allowing workflow-oriented operations.
464
-
465
- # 13) Data-Creation and Data-Loading Operations Are Valid Public Patterns
466
-
467
- Some components need to create internal default data or load external data into the component.
468
-
469
- These are valid public operation patterns.
470
-
471
- ## 13.1) Create
472
-
473
- A create operation may internally construct a valid data instance and assign it through the authoritative data mutation path.
474
-
475
- Example:
476
-
477
- ```typescript
478
- public create = () => {
479
- this.metadata = {
480
- id: crypto.randomUUID(),
481
- name: "",
482
- type: MapType.PATHWAY,
483
- };
484
-
485
- const event = { detail: { metadata: this.metadata } };
486
- this._dispatchEvent(Event.ON_CREATE, event);
487
- };
488
- ```
489
-
490
- ## 13.2) Load
491
-
492
- A load operation may accept external data and assign it through the authoritative data mutation path.
493
-
494
- Example:
495
-
496
- ```typescript
497
- public load = (metadata: Metadata) => {
498
- this.metadata = metadata;
499
-
500
- const event = { detail: { metadata: this.metadata } };
501
- this._dispatchEvent(Event.ON_LOAD, event);
502
- };
503
- ```
504
-
505
- These patterns are especially common for structured domain data and should be treated as first-class data workflows.
506
-
507
- # 14) Data Update Should Prefer Whole-Value Reassignment Over Ad Hoc Nested Mutation
508
-
509
- When public behavior changes canonical data, the implementation should prefer constructing the next accepted data value and assigning it through the authoritative data mutation path.
510
-
511
- Policy:
512
-
513
- - public data updates should prefer whole-value reassignment when practical
514
- - nested ad hoc mutation should be avoided in public workflow paths when it bypasses the authoritative mutation path
515
- - reconstructing the next data value keeps validation, guard logic, synchronization, and event behavior centralized
516
-
517
- Example:
518
-
519
- ```typescript
520
- public update = () => {
521
- if (!this.metadata) return;
522
-
523
- this.metadata = {
524
- ...this.metadata,
525
- name: this.elements.name.value.trim(),
526
- };
527
- };
528
- ```
529
-
530
- This keeps the data setter authoritative rather than letting workflow operations mutate backing fields directly.
531
-
532
- # 15) Data-Oriented Semantic Events Must Be Emitted by Accepted Data Outcomes or Declared Workflow Outcomes
533
-
534
- Public data-oriented events must represent accepted semantic outcomes.
535
-
536
- Some data events describe a direct accepted data outcome, such as load or update. Others describe a declared workflow milestone, such as save, reset, or create.
537
-
538
- Policy:
539
-
540
- - public data-oriented events must be dispatched through `_dispatchEvent(...)`
541
- - data-outcome events must not be emitted before the corresponding accepted outcome has occurred
542
- - when an event payload includes data, that payload must be emitted through `detail`
543
- - emitted data payload must match the declared event contract
544
- - operations must not dispatch data-outcome events before the authoritative data mutation path has accepted the outcome
545
-
546
- Examples:
547
-
548
- ```typescript
549
- public load = (metadata: Metadata) => {
550
- this.metadata = metadata;
551
-
552
- const event = { detail: { metadata: this.metadata } };
553
- this._dispatchEvent(Event.ON_LOAD, event);
554
- };
555
-
556
- public save = () => {
557
- if (!this.metadata) return;
558
-
559
- this.update();
560
-
561
- const event = { detail: { metadata: this.metadata } };
562
- this._dispatchEvent(Event.ON_SAVE, event);
563
-
564
- this.mode = Mode.VIEW;
565
- };
566
- ```
567
-
568
- This preserves the same event discipline already used for state-outcome events, while accommodating richer data workflows.
569
-
570
- # 16) Data Payload Contract Must Match the Specification
571
-
572
- When a public event emits data payload, the runtime payload must match the declared data contract or declared event payload contract.
573
-
574
- Policy:
575
-
576
- - data payload emitted through events must use `detail`
577
- - the emitted payload must conform to the declared runtime data shape
578
- - the implementation must not emit undeclared data payload for a declared public event
579
- - payload should reflect the accepted canonical value, not a stale or pre-validation value
580
-
581
- Example event contract:
582
-
583
- ```json
584
- {
585
- "Events": {
586
- "onload": {
587
- "description": "Dispatched when the metadata is loaded.",
588
- "parameters": [
589
- {
590
- "name": "detail",
591
- "type": "object",
592
- "properties": {
593
- "metadata": {
594
- "type": "Metadata"
595
- }
596
- }
597
- }
598
- ]
599
- }
600
- }
601
- }
602
- ```
603
-
604
- Example dispatch:
605
-
606
- ```typescript
607
- const event = { detail: { metadata: this.metadata } };
608
- this._dispatchEvent(Event.ON_LOAD, event);
609
- ```
610
-
611
- # 17) Data May Drive Temporary Editing Surfaces Without Those Surfaces Becoming Canonical
612
-
613
- Components often project canonical data into editable DOM controls such as inputs, textareas, or other form surfaces.
614
-
615
- These controls are not themselves canonical data.
616
-
617
- Policy:
618
-
619
- - DOM editing controls must not become the canonical source of truth for the component data
620
- - canonical data may be projected into the editing surface
621
- - user edits may remain tentative until accepted through an operation or authoritative update path
622
- - operations such as `edit`, `change`, `save`, and `cancel` may coordinate the relationship between tentative input and canonical data
623
-
624
- Example:
625
-
626
- ```typescript
627
- private _syncName = () =>
628
- (this.elements.name.value = this.metadata?.name ?? "");
629
-
630
- public edit = () => {
631
- this._syncName();
632
- this._focusName();
633
- this.mode = Mode.EDIT;
634
- };
635
-
636
- public save = () => {
637
- if (!this.metadata) return;
638
-
639
- this.update();
640
-
641
- const event = { detail: { metadata: this.metadata } };
642
- this._dispatchEvent(Event.ON_SAVE, event);
643
-
644
- this.mode = Mode.VIEW;
645
- };
646
-
647
- public cancel = () => {
648
- this._syncName();
649
- this.content = !this.metadata?.name ? Content.EMPTY : Content.NAME;
650
- this._dispatchEvent(Event.ON_CANCEL);
651
- this.mode = Mode.VIEW;
652
- };
653
- ```
654
-
655
- In this pattern:
656
-
657
- - the input field is an editing surface
658
- - `metadata` remains canonical
659
- - save accepts the edited value into canonical data
660
- - cancel restores the editing surface from canonical data
661
-
662
- # 18) Initialization Policy Must Be Explicit About Data Lifecycle Entry
663
-
664
- If a component requires initial data creation or default data loading during initialization, that behavior must be explicit and predictable.
665
-
666
- Policy:
667
-
668
- - initialization must not hide data lifecycle behavior
669
- - if the component creates default data on mount, that should occur through a declared operation or controlled internal helper
670
- - if callers are expected to load external data after connection, that expectation should be explicit in the implementation contract
671
- - initialization behavior must remain compatible with the declared data access contract
672
-
673
- Example:
674
-
675
- ```typescript
676
- protected _initialize = () => {
677
- this.setAttribute(Attributes.VISIBILITY, this._visibility);
678
- this.setAttribute(Attributes.STATUS, this._status);
679
- this.setAttribute(Attributes.CONTENT, this._content);
680
- this.setAttribute(Attributes.MODE, this._mode);
681
-
682
- this.create();
683
- };
684
- ```
685
-
686
- This pattern is valid because the initialization path is explicit and the creation behavior still routes through the authoritative data mutation path.
687
-
688
- # 19) Readonly Policy Must Define Internal Ownership for Data
689
-
690
- `readonly` defines whether data is externally writable.
691
-
692
- - `readonly: false`
693
- The data may participate in the component's public mutation contract.
694
-
695
- - `readonly: true`
696
- The data is externally readable but internally owned. External mutation must not be authoritative.
697
-
698
- This rule applies to all data, regardless of whether the data is scalar or structured.
699
-
700
- Readonly therefore does **not** mean the value never changes. It means the value is **not writable from the outside**. The component implementation retains ownership of mutation.
701
-
702
- # 20) Naming Must Be Predictably Transformed Across Layers
703
-
704
- Public data naming must remain consistent across the specification, metadata, component API, validation, operations, and event payloads.
705
-
706
- Policy:
707
-
708
- - the specification defines the canonical public data name
709
- - metadata must define a corresponding `Data` key whose value is that canonical data name
710
- - the component class must expose a public getter using that same canonical name
711
- - if externally writable, the component class must expose a public setter using that same canonical name
712
- - validation should use a predictably related function name
713
- - event payload properties should use the same canonical data name when they emit that data
714
-
715
- Transformation pattern:
716
-
717
- | Layer | Example |
718
- |---|---|
719
- | Specification | `metadata` |
720
- | Metadata Vocabulary | `Data.METADATA = "metadata"` |
721
- | Metadata Type | `type Metadata = { ... }` |
722
- | Validation | `Validate.metadata(...)` |
723
- | Component API | `get metadata()` / `set metadata(...)` |
724
- | Event Payload | `detail.metadata` |
725
-
726
- This predictable transformation keeps the implementation traceable back to the specification and consistent with the framework's metadata-driven design.
727
-
728
- ---
729
-
730
- # Policy Applied to Specification
731
-
732
- Specification fragment:
733
-
734
- ```json
735
- {
736
- "Data": {
737
- "metadata": {
738
- "type": "object",
739
- "description": "The metadata of the clinical pathway.",
740
- "properties": {
741
- "id": {
742
- "type": "uuid",
743
- "description": "The unique identifier of the clinical pathway."
744
- },
745
- "name": {
746
- "type": "string",
747
- "description": "The name of the clinical pathway."
748
- },
749
- "type": {
750
- "type": "MapType",
751
- "description": "The type of the map."
752
- }
753
- },
754
- "default": null,
755
- "readonly": false
756
- }
757
- }
758
- }
759
- ```
760
-
761
- How this specification is implemented:
762
-
763
- - `metadata`
764
- - canonical data item
765
- - structured object-shaped data
766
- - writable external data
767
- - represented in metadata vocabulary as `Data.METADATA`
768
- - represented in metadata type space as `type Metadata = { id, name, type }`
769
- - validated by `Validate.metadata(...)`
770
- - stored canonically in `private _metadata`
771
- - exposed through `get metadata()` and `set metadata(...)`
772
- - synchronized into dependent state and composition through helper methods
773
- - acted on through data-oriented operations such as `create`, `load`, `update`, `save`, and `reset`
774
- - emitted through semantic workflow events such as `oncreate`, `onload`, `onupdate`, `onsave`, and `onreset`
775
-
776
- Implementation:
777
-
778
- ```typescript
779
- export const Data = {
780
- METADATA: "metadata",
781
- } as const;
782
-
783
- export type Data = (typeof Data)[keyof typeof Data];
784
-
785
- export const MapType = {
786
- PATHWAY: "pathway",
787
- } as const;
788
-
789
- export type MapType = (typeof MapType)[keyof typeof MapType];
790
-
791
- export type Metadata = {
792
- id: string;
793
- name: string;
794
- type: MapType;
795
- };
796
-
797
- export class Validate {
798
- public static metadata = (value: Metadata) => {
799
- if (!value.id) {
800
- throw new Error("Invalid metadata value: id is required");
801
- }
802
-
803
- if (!Object.values(MapType).includes(value.type as MapType)) {
804
- throw new Error("Invalid metadata value: type is invalid");
805
- }
806
-
807
- return value as Metadata;
808
- };
809
- }
810
-
811
- private _metadata: Metadata | null = null;
812
-
813
- public get metadata(): Metadata | null {
814
- return this._metadata;
815
- }
816
-
817
- public set metadata(metadata: Metadata | null) {
818
- // Normalize
819
- metadata = metadata ?? null;
820
-
821
- // Validate
822
- metadata = metadata ? Validate.metadata(metadata) : null;
823
-
824
- // Guard
825
- if (this._metadata === metadata) return;
826
-
827
- // Mutate
828
- this._metadata = metadata;
829
-
830
- // Synchronize
831
- this._updateLabel();
832
- this._syncContent();
833
- this._syncMode();
834
- }
835
-
836
- public create = () => {
837
- this.metadata = {
838
- id: crypto.randomUUID(),
839
- name: "",
840
- type: MapType.PATHWAY,
841
- };
842
-
843
- const event = { detail: { metadata: this.metadata } };
844
- this._dispatchEvent(Event.ON_CREATE, event);
845
- };
846
-
847
- public load = (metadata: Metadata) => {
848
- this.metadata = metadata;
849
-
850
- const event = { detail: { metadata: this.metadata } };
851
- this._dispatchEvent(Event.ON_LOAD, event);
852
- };
853
-
854
- public update = () => {
855
- if (!this.metadata) return;
856
-
857
- this.metadata = {
858
- ...this.metadata,
859
- name: this.elements.name.value.trim(),
860
- };
861
-
862
- const event = { detail: { metadata: this.metadata } };
863
- this._dispatchEvent(Event.ON_UPDATE, event);
864
- };
865
-
866
- public reset = () => {
867
- this.metadata = null;
868
- this._syncName();
869
- this._dispatchEvent(Event.ON_RESET);
870
- };
871
- ```
872
-
873
- How the implementation applies the policy:
874
-
875
- - the specification declares the data contract under `Data`
876
- - metadata defines both the canonical data vocabulary and the runtime data shape
877
- - validation enforces the runtime data contract
878
- - the component owns the canonical data through a private backing field
879
- - the public getter and setter define the external data access contract
880
- - the setter is the authoritative public mutation path
881
- - data mutation follows the normalize → validate → guard → mutate → synchronize order
882
- - data mutation synchronizes dependent state and composition
883
- - operations remain thin or orchestrational around the authoritative data path
884
- - semantic data workflow events are emitted through the framework dispatch path
885
- - event payloads expose canonical accepted data through `detail`
886
-
887
- ---
888
-
889
- # Policy Checklist
890
-
891
- A compliant component must:
892
-
893
- 1. Declare all public data under `Data` in the specification.
894
- 2. Define each data item using the required specification contract.
895
- 3. Use `type` and `description` for every declared data item.
896
- 4. Use `properties` when the data is structured.
897
- 5. Use `default` when the runtime contract includes a default or unloaded sentinel.
898
- 6. Use `readonly` to declare external ownership rules.
899
- 7. Define all declared public data names in `<component>.meta.ts`.
900
- 8. Use `Data` metadata as the canonical data vocabulary.
901
- 9. Define the runtime data shape in `<component>.meta.ts`.
902
- 10. Implement validation for each declared data item in `<component>.validation.ts`.
903
- 11. Ensure validation enforces the declared runtime shape and constrained field domains.
904
- 12. Give each declared data item a canonical internal source of truth.
905
- 13. Expose an external access contract for each data item as read-only or writable.
906
- 14. Keep public writable data mutation paths authoritative and centralized.
907
- 15. Use the mutation order normalize → validate → guard → mutate → synchronize whenever those phases are needed.
908
- 16. Model unloaded or cleared runtime data explicitly when supported.
909
- 17. Keep data out of `Attributes` and DOM attribute observation by default.
910
- 18. Allow data mutation to synchronize dependent state and composition predictably.
911
- 19. Preserve authoritative ownership of dependent state when data updates it.
912
- 20. Keep data-oriented operations thin or orchestrational around the canonical data mutation path.
913
- 21. Prefer whole-value reassignment over ad hoc nested mutation in public workflow paths.
914
- 22. Dispatch public data-oriented events through `_dispatchEvent(...)`.
915
- 23. Emit data events only after the accepted data outcome or declared workflow outcome has occurred.
916
- 24. Emit data payload through `event.detail`.
917
- 25. Ensure emitted data payload matches the declared contract.
918
- 26. Keep DOM editing surfaces non-canonical unless the specification explicitly says otherwise.
919
- 27. Keep naming predictably aligned across specification, metadata, validation, component API, and event payloads.
920
-
921
- ---
922
-
923
- # Component Data Specification Schema
924
-
925
- JSON Schema:
926
-
927
- ```json
928
- {
929
- "$schema": "https://json-schema.org/draft/2020-12/schema",
930
- "$id": "https://scalable.software/schema/component-data.schema.json",
931
- "title": "Component Data",
932
- "description": "Schema for the Data object in a component specification.",
933
- "type": "object",
934
- "propertyNames": {
935
- "type": "string",
936
- "pattern": "^[a-z][a-zA-Z0-9]*$",
937
- "description": "Canonical data name in lowerCamelCase, for example: metadata, document, payload."
938
- },
939
- "additionalProperties": {
940
- "type": "object",
941
- "description": "A data definition.",
942
- "required": ["type", "description"],
943
- "additionalProperties": false,
944
- "properties": {
945
- "type": {
946
- "type": "string",
947
- "minLength": 1,
948
- "description": "Declared runtime shape category for the data, for example: object, array, string, Metadata."
949
- },
950
- "description": {
951
- "type": "string",
952
- "minLength": 1,
953
- "description": "Human-readable description of the component-owned data."
954
- },
955
- "properties": {
956
- "type": "object",
957
- "description": "Structured field definitions for object-shaped data.",
958
- "minProperties": 1,
959
- "propertyNames": {
960
- "type": "string",
961
- "pattern": "^[a-z][a-zA-Z0-9]*$",
962
- "description": "Canonical property name in lowerCamelCase, for example: id, name, type."
963
- },
964
- "additionalProperties": {
965
- "type": "object",
966
- "required": ["type", "description"],
967
- "additionalProperties": false,
968
- "properties": {
969
- "type": {
970
- "type": "string",
971
- "minLength": 1,
972
- "description": "Declared field type, for example: string, uuid, MapType."
973
- },
974
- "description": {
975
- "type": "string",
976
- "minLength": 1,
977
- "description": "Human-readable description of the field."
978
- },
979
- "default": {
980
- "description": "Default value for the field when applicable."
981
- },
982
- "values": {
983
- "type": "array",
984
- "minItems": 1,
985
- "description": "Allowed values when the field has a constrained domain."
986
- },
987
- "readonly": {
988
- "type": "boolean",
989
- "description": "Whether the field is externally writable. Field-level readonly is optional and may be interpreted by component-specific policy."
990
- }
991
- }
992
- }
993
- },
994
- "default": {
995
- "description": "Default runtime value for the declared data item."
996
- },
997
- "readonly": {
998
- "type": "boolean",
999
- "description": "Whether the data item is externally writable. If true, the data is externally readable but internally owned."
1000
- }
1001
- }
1002
- }
1003
- }
1004
- ```
1005
-
1006
- Example JSON:
1007
-
1008
- ```json
1009
- {
1010
- "Data": {
1011
- "metadata": {
1012
- "type": "object",
1013
- "description": "The metadata of the clinical pathway.",
1014
- "properties": {
1015
- "id": {
1016
- "type": "uuid",
1017
- "description": "The unique identifier of the clinical pathway."
1018
- },
1019
- "name": {
1020
- "type": "string",
1021
- "description": "The name of the clinical pathway.",
1022
- "default": "Clinical Pathway"
1023
- },
1024
- "type": {
1025
- "type": "MapType",
1026
- "description": "The type of the map.",
1027
- "default": "pathway",
1028
- "values": ["pathway"]
1029
- }
1030
- },
1031
- "default": null,
1032
- "readonly": false
1033
- }
1034
- }
1035
- }
1036
- ```
1
+ # Data
2
+
3
+ This document defines an implementation policy for **component data**, then illustrates how specifications are implemented by applying this policy.
4
+
5
+ This document is intentionally **data** only, but part of a set of implementation policies which together define how specifications should be transformed into implementations which are 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) Data Contract Must Be Declared in the Specification
22
+
23
+ Each component specification must declare its data contract under `Data`.
24
+
25
+ `Data` must be an object whose property names are the canonical data names.
26
+
27
+ Each data definition must include:
28
+
29
+ - `type`
30
+ - `description`
31
+
32
+ A data definition may also include:
33
+
34
+ - `properties`
35
+ - `default`
36
+ - `readonly`
37
+
38
+ Interpretation:
39
+
40
+ - `type` defines the public runtime shape category of the data
41
+ - `description` defines the semantic purpose of the data
42
+ - `properties` defines the structured fields of object-shaped data
43
+ - `default` defines the default runtime value when one exists
44
+ - `readonly` defines whether the data is externally writable or internally owned
45
+ - if `properties` is present, the data is structured
46
+ - if `properties` is absent, the data is scalar or otherwise non-structured
47
+
48
+ Example specification:
49
+
50
+ ```json
51
+ {
52
+ "Data": {
53
+ "metadata": {
54
+ "type": "object",
55
+ "description": "The metadata of the clinical pathway.",
56
+ "properties": {
57
+ "id": {
58
+ "type": "uuid",
59
+ "description": "The unique identifier of the clinical pathway."
60
+ },
61
+ "name": {
62
+ "type": "string",
63
+ "description": "The name of the clinical pathway.",
64
+ "default": "Clinical Pathway"
65
+ },
66
+ "type": {
67
+ "type": "MapType",
68
+ "description": "The type of the map.",
69
+ "default": "pathway"
70
+ }
71
+ },
72
+ "default": null,
73
+ "readonly": false
74
+ }
75
+ }
76
+ }
77
+ ```
78
+
79
+ This defines the public data contract that the implementation must honor.
80
+
81
+ # Template Variation Points
82
+
83
+ Data items vary by structure, ownership, and runtime nullability:
84
+
85
+ - **Structured vs scalar** — structured data declares `properties` and uses an explicit metadata type (Section 4); scalar data has a flat runtime shape and may not require a structured type definition.
86
+ - **Writable vs read-only** — writable data exposes a public setter and follows the normalize → validate → guard → mutate → synchronize mutation order (Sections 8.2, 9); read-only data exposes only a getter and is mutated internally (Section 8.1).
87
+ - **Supports unloaded state vs always-present** — when the runtime contract permits an unloaded or cleared state, the public getter and setter types reflect that possibility, and validation may bypass the unloaded sentinel (Section 10). Always-present data does not exercise this variation.
88
+
89
+ Data-creation operations (Section 13.1), data-loading operations (Section 13.2), workflow events (Section 15), and the editing-surface pattern (Section 17) activate only when the component's specification declares the corresponding patterns. They are valid template instantiations, not extensions of the policy.
90
+
91
+ # 2) Data Must Represent Canonical Component-Owned Content
92
+
93
+ This framework distinguishes:
94
+
95
+ - `Data`: structured or scalar content owned by the component
96
+ - `State`: finite runtime mode, visibility, status, or other UI/runtime condition
97
+ - `Operations`: callable public actions
98
+ - `Events`: observable semantic outcomes
99
+ - `Gestures`: interaction inputs
100
+
101
+ Policy:
102
+
103
+ - data describes canonical component-owned content
104
+ - state describes runtime condition
105
+ - operations describe callable public actions
106
+ - events describe semantic outcomes
107
+ - gestures describe interaction input
108
+ - data must not be used to declare state
109
+ - data must not be used to declare operations
110
+ - data must not be used to declare events
111
+ - data must not be used to declare gestures
112
+
113
+ Examples of valid data:
114
+
115
+ - `metadata`
116
+ - `record`
117
+ - `document`
118
+ - `selection`
119
+ - `payload`
120
+
121
+ Examples that must not be modeled as data:
122
+
123
+ - `visibility`
124
+ - `status`
125
+ - `mode`
126
+ - `click`
127
+ - `onload`
128
+ - `save`
129
+
130
+ Data is the authoritative component-owned content model, not a substitute for the other constructs.
131
+
132
+ # 3) Public Data Names Must Be Declared in Metadata
133
+
134
+ For every data item declared in the specification, the implementation must define corresponding metadata in `<component>.meta.ts`.
135
+
136
+ This metadata defines the canonical data vocabulary for the component.
137
+
138
+ Policy:
139
+
140
+ - every declared public data item must have a key in `Data`
141
+ - `Data` contains only declared public data names
142
+ - the implementation must use data metadata consistently as the canonical data vocabulary
143
+
144
+ Example pattern:
145
+
146
+ ```typescript
147
+ export const Data = {
148
+ METADATA: "metadata",
149
+ } as const;
150
+
151
+ export type Data = (typeof Data)[keyof typeof Data];
152
+ ```
153
+
154
+ This gives the implementation a stable, centralized vocabulary that prevents string duplication and keeps naming consistent across metadata, validation, component API, and data-oriented events.
155
+
156
+ # 4) Data Shape Must Be Defined in Metadata
157
+
158
+ For every declared data item, the implementation must define the corresponding runtime type in `<component>.meta.ts`.
159
+
160
+ This type defines the allowed runtime shape of the canonical data object or value.
161
+
162
+ Policy:
163
+
164
+ - every declared data item must have a corresponding runtime type definition in metadata
165
+ - structured data should be expressed through an explicit TypeScript type
166
+ - nested constrained fields may use additional metadata value domains
167
+ - the metadata type is the public runtime data contract used by the implementation
168
+
169
+ Example pattern:
170
+
171
+ ```typescript
172
+ export const MapType = {
173
+ PATHWAY: "pathway",
174
+ } as const;
175
+
176
+ export type MapType = (typeof MapType)[keyof typeof MapType];
177
+
178
+ export type Metadata = {
179
+ id: string;
180
+ name: string;
181
+ type: MapType;
182
+ };
183
+ ```
184
+
185
+ This makes the data contract explicit in the same way state value domains are made explicit in metadata.
186
+
187
+ # 5) Data Validation Must Be Implemented in `<component>.validation.ts`
188
+
189
+ For every declared data item, the implementation must define validation logic in `<component>.validation.ts`.
190
+
191
+ This validation serves as the runtime enforcement layer for the data contract declared in the specification and expressed in metadata.
192
+
193
+ Policy:
194
+
195
+ - validation logic for data must be implemented in `<component>.validation.ts`
196
+ - validation must enforce the allowed runtime shape and constrained field domains declared in metadata
197
+ - public writable mutation paths must call validation before mutation proceeds
198
+ - if validation fails, mutation must not proceed
199
+ - validation functions should return the validated value in its narrowed runtime type
200
+
201
+ Example:
202
+
203
+ ```typescript
204
+ import { MapType } from "./title.meta.js";
205
+ import type { Metadata } from "./title.meta.js";
206
+
207
+ export class Validate {
208
+ public static metadata = (value: Metadata) => {
209
+ if (!value.id) {
210
+ throw new Error("Invalid metadata value: id is required");
211
+ }
212
+
213
+ if (!Object.values(MapType).includes(value.type as MapType)) {
214
+ throw new Error("Invalid metadata value: type is invalid");
215
+ }
216
+
217
+ return value as Metadata;
218
+ };
219
+ }
220
+ ```
221
+
222
+ This makes the contract explicit:
223
+
224
+ - `<component>.meta.ts` defines the data shape
225
+ - `<component>.validation.ts` enforces the data shape
226
+ - `<component>.ts` uses validation in the mutation path
227
+
228
+ # 6) Data Must Have a Canonical Internal Source of Truth
229
+
230
+ Each declared data item must have a canonical internal source of truth on the component instance.
231
+
232
+ That source may be nullable when the runtime contract supports an unloaded or cleared state.
233
+
234
+ Policy:
235
+
236
+ - every declared data item must have a canonical internally owned runtime source of truth
237
+ - canonical data should normally be stored in a private backing field
238
+ - if the runtime data contract includes an unloaded or cleared state, that state must be represented explicitly
239
+ - external API access must read from the canonical internal source of truth
240
+
241
+ Example:
242
+
243
+ ```typescript
244
+ private _metadata: Metadata | null = null;
245
+
246
+ public get metadata(): Metadata | null {
247
+ return this._metadata;
248
+ }
249
+ ```
250
+
251
+ This keeps ownership clear and prevents DOM structure, transient form controls, or ad hoc derived values from becoming accidental sources of truth.
252
+
253
+ # 7) Data Does Not Participate in DOM Attribute Observation by Default
254
+
255
+ Unlike attribute-backed state, data is not DOM attribute-backed by default.
256
+
257
+ Policy:
258
+
259
+ - data must not be declared in `Attributes` unless an explicit policy for attribute-backed data exists
260
+ - data does not participate in `attributeChangedCallback` by default
261
+ - data must not be treated as DOM attribute state merely because it is externally readable or writable
262
+ - data reflection to DOM, serialization, or persistence must be modeled explicitly rather than assumed
263
+
264
+ This keeps structured content separate from the DOM attribute system used for finite state.
265
+
266
+ # 8) Data Must Declare External Access Contract
267
+
268
+ For each non-attribute data item, the implementation must declare its external access contract as one of:
269
+
270
+ - **Read-only external**
271
+ - **Writable external**
272
+
273
+ ## 8.1) Read-only External Data
274
+
275
+ For data with `readonly: true`:
276
+
277
+ - expose a public getter
278
+ - do not expose a public setter
279
+ - internal logic may update the canonical value
280
+
281
+ Example:
282
+
283
+ ```typescript
284
+ private _metadata: Metadata | null = null;
285
+
286
+ public get metadata(): Metadata | null {
287
+ return this._metadata;
288
+ }
289
+
290
+ protected _loadMetadata = (metadata: Metadata) => {
291
+ this._metadata = Validate.metadata(metadata);
292
+ };
293
+ ```
294
+
295
+ ## 8.2) Writable External Data
296
+
297
+ For data with `readonly: false`:
298
+
299
+ - expose a public getter
300
+ - expose a public setter
301
+ - public writable mutation must converge on that setter
302
+
303
+ Example:
304
+
305
+ ```typescript
306
+ private _metadata: Metadata | null = null;
307
+
308
+ public get metadata(): Metadata | null {
309
+ return this._metadata;
310
+ }
311
+
312
+ public set metadata(metadata: Metadata | null) {
313
+ this._metadata = metadata ? Validate.metadata(metadata) : null;
314
+ }
315
+ ```
316
+
317
+ Readonly therefore does **not** mean the data never changes. It means the data is externally readable but internally owned.
318
+
319
+ # 9) Public Data Mutation Path Must Be Normalize → Validate → Guard → Mutate → Synchronize
320
+
321
+ For writable data, the public mutation path must follow this order:
322
+
323
+ 1. **Normalize**
324
+ 2. **Validate**
325
+ 3. **Guard**
326
+ 4. **Mutate**
327
+ 5. **Synchronize**
328
+
329
+ Interpretation:
330
+
331
+ - **Normalize** converts incoming values into the runtime shape expected by validation
332
+ - **Validate** enforces the declared data contract
333
+ - **Guard** prevents duplicate accepted outcomes when no effective change is needed
334
+ - **Mutate** updates the canonical internal value
335
+ - **Synchronize** updates dependent projections such as state, composition, or internal helpers
336
+
337
+ A mutation path may omit phases that are not needed for a given data item, but the order of any phases that are used must remain Normalize → Validate → Guard → Mutate → Synchronize.
338
+
339
+ Example:
340
+
341
+ ```typescript
342
+ public set metadata(metadata: Metadata | null) {
343
+ // Normalize
344
+ metadata = metadata ?? null;
345
+
346
+ // Validate
347
+ metadata = metadata ? Validate.metadata(metadata) : null;
348
+
349
+ // Guard
350
+ if (this._metadata === metadata) return;
351
+
352
+ // Mutate
353
+ this._metadata = metadata;
354
+
355
+ // Synchronize
356
+ this._updateLabel();
357
+ this._syncContent();
358
+ this._syncMode();
359
+ }
360
+ ```
361
+
362
+ For structured object data, the guard strategy may be referential, structural, semantic, or omitted when no safe equality rule is available. The implementation must choose a strategy that is predictable for the declared contract.
363
+
364
+ # 10) Null or Unloaded Runtime Data State Must Be Explicitly Modeled
365
+
366
+ Some components support a meaningful runtime state where data is not yet loaded, has been cleared, or has not yet been created.
367
+
368
+ This is valid, but it must be modeled explicitly.
369
+
370
+ Policy:
371
+
372
+ - when a data item supports an unloaded or cleared runtime state, that state must be explicit in the implementation contract
373
+ - the public getter and setter types must reflect that runtime possibility
374
+ - validation may bypass the unloaded sentinel if that sentinel is part of the runtime access contract rather than part of the structured data shape itself
375
+ - operations and events must treat the unloaded state predictably
376
+
377
+ Example:
378
+
379
+ ```typescript
380
+ private _metadata: Metadata | null = null;
381
+
382
+ public set metadata(metadata: Metadata | null) {
383
+ this._metadata = metadata ? Validate.metadata(metadata) : null;
384
+ }
385
+ ```
386
+
387
+ This keeps unloaded runtime absence distinct from invalid structured data.
388
+
389
+ # 11) Data Synchronization May Update Dependent State and Composition
390
+
391
+ Data often drives state projections and rendered composition.
392
+
393
+ This is valid, but those dependent constructs must preserve their own authoritative mutation behavior.
394
+
395
+ Policy:
396
+
397
+ - data mutation may synchronize dependent component state
398
+ - data mutation may synchronize composition-facing values such as labels, inputs, or cached display content
399
+ - when data mutation updates writable state, it should do so through that state's authoritative mutation path
400
+ - data synchronization must remain predictable and traceable from the data contract to the dependent runtime behavior
401
+
402
+ Example:
403
+
404
+ ```typescript
405
+ private _updateLabel = () =>
406
+ (this.elements.label.textContent = this.metadata?.name ?? "Untitled");
407
+
408
+ private _syncContent = () =>
409
+ (this.content = !this.metadata?.name ? Content.EMPTY : Content.NAME);
410
+
411
+ private _syncMode = () =>
412
+ (this.mode = this.content === Content.EMPTY ? Mode.EDIT : Mode.VIEW);
413
+ ```
414
+
415
+ In this pattern:
416
+
417
+ - `metadata` is the canonical data
418
+ - `content` is derived runtime state
419
+ - `mode` may be further coordinated from that derived state
420
+ - label text is a composition projection of the canonical data
421
+
422
+ # 12) Operations Must Remain Thin or Orchestrational Around Data
423
+
424
+ When an operation affects component data, it must not become a second authoritative mutation system.
425
+
426
+ Policy:
427
+
428
+ - operations that affect data must remain thin where possible
429
+ - operations may orchestrate multi-step workflows around data
430
+ - operations must not duplicate validation logic that belongs to the authoritative data mutation path
431
+ - operations must not mutate scattered data fields ad hoc when a canonical data mutation path exists
432
+ - operations may derive a next data value and delegate it through the authoritative data mutation path
433
+
434
+ Examples:
435
+
436
+ ```typescript
437
+ public load = (metadata: Metadata) => {
438
+ this.metadata = metadata;
439
+
440
+ const event = { detail: { metadata: this.metadata } };
441
+ this._dispatchEvent(Event.ON_LOAD, event);
442
+ };
443
+
444
+ public update = () => {
445
+ if (!this.metadata) return;
446
+
447
+ this.metadata = {
448
+ ...this.metadata,
449
+ name: this.elements.name.value.trim(),
450
+ };
451
+
452
+ const event = { detail: { metadata: this.metadata } };
453
+ this._dispatchEvent(Event.ON_UPDATE, event);
454
+ };
455
+
456
+ public reset = () => {
457
+ this.metadata = null;
458
+ this._syncName();
459
+ this._dispatchEvent(Event.ON_RESET);
460
+ };
461
+ ```
462
+
463
+ This preserves a single authoritative mutation path for the canonical data while still allowing workflow-oriented operations.
464
+
465
+ # 13) Data-Creation and Data-Loading Operations Are Valid Public Patterns
466
+
467
+ Some components need to create internal default data or load external data into the component.
468
+
469
+ These are valid public operation patterns.
470
+
471
+ ## 13.1) Create
472
+
473
+ A create operation may internally construct a valid data instance and assign it through the authoritative data mutation path.
474
+
475
+ Example:
476
+
477
+ ```typescript
478
+ public create = () => {
479
+ this.metadata = {
480
+ id: crypto.randomUUID(),
481
+ name: "",
482
+ type: MapType.PATHWAY,
483
+ };
484
+
485
+ const event = { detail: { metadata: this.metadata } };
486
+ this._dispatchEvent(Event.ON_CREATE, event);
487
+ };
488
+ ```
489
+
490
+ ## 13.2) Load
491
+
492
+ A load operation may accept external data and assign it through the authoritative data mutation path.
493
+
494
+ Example:
495
+
496
+ ```typescript
497
+ public load = (metadata: Metadata) => {
498
+ this.metadata = metadata;
499
+
500
+ const event = { detail: { metadata: this.metadata } };
501
+ this._dispatchEvent(Event.ON_LOAD, event);
502
+ };
503
+ ```
504
+
505
+ These patterns are especially common for structured domain data and should be treated as first-class data workflows.
506
+
507
+ # 14) Data Update Should Prefer Whole-Value Reassignment Over Ad Hoc Nested Mutation
508
+
509
+ When public behavior changes canonical data, the implementation should prefer constructing the next accepted data value and assigning it through the authoritative data mutation path.
510
+
511
+ Policy:
512
+
513
+ - public data updates should prefer whole-value reassignment when practical
514
+ - nested ad hoc mutation should be avoided in public workflow paths when it bypasses the authoritative mutation path
515
+ - reconstructing the next data value keeps validation, guard logic, synchronization, and event behavior centralized
516
+
517
+ Example:
518
+
519
+ ```typescript
520
+ public update = () => {
521
+ if (!this.metadata) return;
522
+
523
+ this.metadata = {
524
+ ...this.metadata,
525
+ name: this.elements.name.value.trim(),
526
+ };
527
+ };
528
+ ```
529
+
530
+ This keeps the data setter authoritative rather than letting workflow operations mutate backing fields directly.
531
+
532
+ # 15) Data-Oriented Semantic Events Must Be Emitted by Accepted Data Outcomes or Declared Workflow Outcomes
533
+
534
+ Public data-oriented events must represent accepted semantic outcomes.
535
+
536
+ Some data events describe a direct accepted data outcome, such as load or update. Others describe a declared workflow milestone, such as save, reset, or create.
537
+
538
+ Policy:
539
+
540
+ - public data-oriented events must be dispatched through `_dispatchEvent(...)`
541
+ - data-outcome events must not be emitted before the corresponding accepted outcome has occurred
542
+ - when an event payload includes data, that payload must be emitted through `detail`
543
+ - emitted data payload must match the declared event contract
544
+ - operations must not dispatch data-outcome events before the authoritative data mutation path has accepted the outcome
545
+
546
+ Examples:
547
+
548
+ ```typescript
549
+ public load = (metadata: Metadata) => {
550
+ this.metadata = metadata;
551
+
552
+ const event = { detail: { metadata: this.metadata } };
553
+ this._dispatchEvent(Event.ON_LOAD, event);
554
+ };
555
+
556
+ public save = () => {
557
+ if (!this.metadata) return;
558
+
559
+ this.update();
560
+
561
+ const event = { detail: { metadata: this.metadata } };
562
+ this._dispatchEvent(Event.ON_SAVE, event);
563
+
564
+ this.mode = Mode.VIEW;
565
+ };
566
+ ```
567
+
568
+ This preserves the same event discipline already used for state-outcome events, while accommodating richer data workflows.
569
+
570
+ # 16) Data Payload Contract Must Match the Specification
571
+
572
+ When a public event emits data payload, the runtime payload must match the declared data contract or declared event payload contract.
573
+
574
+ Policy:
575
+
576
+ - data payload emitted through events must use `detail`
577
+ - the emitted payload must conform to the declared runtime data shape
578
+ - the implementation must not emit undeclared data payload for a declared public event
579
+ - payload should reflect the accepted canonical value, not a stale or pre-validation value
580
+
581
+ Example event contract:
582
+
583
+ ```json
584
+ {
585
+ "Events": {
586
+ "onload": {
587
+ "description": "Dispatched when the metadata is loaded.",
588
+ "parameters": [
589
+ {
590
+ "name": "detail",
591
+ "type": "object",
592
+ "properties": {
593
+ "metadata": {
594
+ "type": "Metadata"
595
+ }
596
+ }
597
+ }
598
+ ]
599
+ }
600
+ }
601
+ }
602
+ ```
603
+
604
+ Example dispatch:
605
+
606
+ ```typescript
607
+ const event = { detail: { metadata: this.metadata } };
608
+ this._dispatchEvent(Event.ON_LOAD, event);
609
+ ```
610
+
611
+ # 17) Data May Drive Temporary Editing Surfaces Without Those Surfaces Becoming Canonical
612
+
613
+ Components often project canonical data into editable DOM controls such as inputs, textareas, or other form surfaces.
614
+
615
+ These controls are not themselves canonical data.
616
+
617
+ Policy:
618
+
619
+ - DOM editing controls must not become the canonical source of truth for the component data
620
+ - canonical data may be projected into the editing surface
621
+ - user edits may remain tentative until accepted through an operation or authoritative update path
622
+ - operations such as `edit`, `change`, `save`, and `cancel` may coordinate the relationship between tentative input and canonical data
623
+
624
+ Example:
625
+
626
+ ```typescript
627
+ private _syncName = () =>
628
+ (this.elements.name.value = this.metadata?.name ?? "");
629
+
630
+ public edit = () => {
631
+ this._syncName();
632
+ this._focusName();
633
+ this.mode = Mode.EDIT;
634
+ };
635
+
636
+ public save = () => {
637
+ if (!this.metadata) return;
638
+
639
+ this.update();
640
+
641
+ const event = { detail: { metadata: this.metadata } };
642
+ this._dispatchEvent(Event.ON_SAVE, event);
643
+
644
+ this.mode = Mode.VIEW;
645
+ };
646
+
647
+ public cancel = () => {
648
+ this._syncName();
649
+ this.content = !this.metadata?.name ? Content.EMPTY : Content.NAME;
650
+ this._dispatchEvent(Event.ON_CANCEL);
651
+ this.mode = Mode.VIEW;
652
+ };
653
+ ```
654
+
655
+ In this pattern:
656
+
657
+ - the input field is an editing surface
658
+ - `metadata` remains canonical
659
+ - save accepts the edited value into canonical data
660
+ - cancel restores the editing surface from canonical data
661
+
662
+ # 18) Initialization Policy Must Be Explicit About Data Lifecycle Entry
663
+
664
+ If a component requires initial data creation or default data loading during initialization, that behavior must be explicit and predictable.
665
+
666
+ Policy:
667
+
668
+ - initialization must not hide data lifecycle behavior
669
+ - if the component creates default data on mount, that should occur through a declared operation or controlled internal helper
670
+ - if callers are expected to load external data after connection, that expectation should be explicit in the implementation contract
671
+ - initialization behavior must remain compatible with the declared data access contract
672
+
673
+ Example:
674
+
675
+ ```typescript
676
+ protected _initialize = () => {
677
+ this.setAttribute(Attributes.VISIBILITY, this._visibility);
678
+ this.setAttribute(Attributes.STATUS, this._status);
679
+ this.setAttribute(Attributes.CONTENT, this._content);
680
+ this.setAttribute(Attributes.MODE, this._mode);
681
+
682
+ this.create();
683
+ };
684
+ ```
685
+
686
+ This pattern is valid because the initialization path is explicit and the creation behavior still routes through the authoritative data mutation path.
687
+
688
+ # 19) Readonly Policy Must Define Internal Ownership for Data
689
+
690
+ `readonly` defines whether data is externally writable.
691
+
692
+ - `readonly: false`
693
+ The data may participate in the component's public mutation contract.
694
+
695
+ - `readonly: true`
696
+ The data is externally readable but internally owned. External mutation must not be authoritative.
697
+
698
+ This rule applies to all data, regardless of whether the data is scalar or structured.
699
+
700
+ Readonly therefore does **not** mean the value never changes. It means the value is **not writable from the outside**. The component implementation retains ownership of mutation.
701
+
702
+ # 20) Naming Must Be Predictably Transformed Across Layers
703
+
704
+ Public data naming must remain consistent across the specification, metadata, component API, validation, operations, and event payloads.
705
+
706
+ Policy:
707
+
708
+ - the specification defines the canonical public data name
709
+ - metadata must define a corresponding `Data` key whose value is that canonical data name
710
+ - the component class must expose a public getter using that same canonical name
711
+ - if externally writable, the component class must expose a public setter using that same canonical name
712
+ - validation should use a predictably related function name
713
+ - event payload properties should use the same canonical data name when they emit that data
714
+
715
+ Transformation pattern:
716
+
717
+ | Layer | Example |
718
+ |---|---|
719
+ | Specification | `metadata` |
720
+ | Metadata Vocabulary | `Data.METADATA = "metadata"` |
721
+ | Metadata Type | `type Metadata = { ... }` |
722
+ | Validation | `Validate.metadata(...)` |
723
+ | Component API | `get metadata()` / `set metadata(...)` |
724
+ | Event Payload | `detail.metadata` |
725
+
726
+ This predictable transformation keeps the implementation traceable back to the specification and consistent with the framework's metadata-driven design.
727
+
728
+ ---
729
+
730
+ # Policy Applied to Specification
731
+
732
+ Specification fragment:
733
+
734
+ ```json
735
+ {
736
+ "Data": {
737
+ "metadata": {
738
+ "type": "object",
739
+ "description": "The metadata of the clinical pathway.",
740
+ "properties": {
741
+ "id": {
742
+ "type": "uuid",
743
+ "description": "The unique identifier of the clinical pathway."
744
+ },
745
+ "name": {
746
+ "type": "string",
747
+ "description": "The name of the clinical pathway."
748
+ },
749
+ "type": {
750
+ "type": "MapType",
751
+ "description": "The type of the map."
752
+ }
753
+ },
754
+ "default": null,
755
+ "readonly": false
756
+ }
757
+ }
758
+ }
759
+ ```
760
+
761
+ How this specification is implemented:
762
+
763
+ - `metadata`
764
+ - canonical data item
765
+ - structured object-shaped data
766
+ - writable external data
767
+ - represented in metadata vocabulary as `Data.METADATA`
768
+ - represented in metadata type space as `type Metadata = { id, name, type }`
769
+ - validated by `Validate.metadata(...)`
770
+ - stored canonically in `private _metadata`
771
+ - exposed through `get metadata()` and `set metadata(...)`
772
+ - synchronized into dependent state and composition through helper methods
773
+ - acted on through data-oriented operations such as `create`, `load`, `update`, `save`, and `reset`
774
+ - emitted through semantic workflow events such as `oncreate`, `onload`, `onupdate`, `onsave`, and `onreset`
775
+
776
+ Implementation:
777
+
778
+ ```typescript
779
+ export const Data = {
780
+ METADATA: "metadata",
781
+ } as const;
782
+
783
+ export type Data = (typeof Data)[keyof typeof Data];
784
+
785
+ export const MapType = {
786
+ PATHWAY: "pathway",
787
+ } as const;
788
+
789
+ export type MapType = (typeof MapType)[keyof typeof MapType];
790
+
791
+ export type Metadata = {
792
+ id: string;
793
+ name: string;
794
+ type: MapType;
795
+ };
796
+
797
+ export class Validate {
798
+ public static metadata = (value: Metadata) => {
799
+ if (!value.id) {
800
+ throw new Error("Invalid metadata value: id is required");
801
+ }
802
+
803
+ if (!Object.values(MapType).includes(value.type as MapType)) {
804
+ throw new Error("Invalid metadata value: type is invalid");
805
+ }
806
+
807
+ return value as Metadata;
808
+ };
809
+ }
810
+
811
+ private _metadata: Metadata | null = null;
812
+
813
+ public get metadata(): Metadata | null {
814
+ return this._metadata;
815
+ }
816
+
817
+ public set metadata(metadata: Metadata | null) {
818
+ // Normalize
819
+ metadata = metadata ?? null;
820
+
821
+ // Validate
822
+ metadata = metadata ? Validate.metadata(metadata) : null;
823
+
824
+ // Guard
825
+ if (this._metadata === metadata) return;
826
+
827
+ // Mutate
828
+ this._metadata = metadata;
829
+
830
+ // Synchronize
831
+ this._updateLabel();
832
+ this._syncContent();
833
+ this._syncMode();
834
+ }
835
+
836
+ public create = () => {
837
+ this.metadata = {
838
+ id: crypto.randomUUID(),
839
+ name: "",
840
+ type: MapType.PATHWAY,
841
+ };
842
+
843
+ const event = { detail: { metadata: this.metadata } };
844
+ this._dispatchEvent(Event.ON_CREATE, event);
845
+ };
846
+
847
+ public load = (metadata: Metadata) => {
848
+ this.metadata = metadata;
849
+
850
+ const event = { detail: { metadata: this.metadata } };
851
+ this._dispatchEvent(Event.ON_LOAD, event);
852
+ };
853
+
854
+ public update = () => {
855
+ if (!this.metadata) return;
856
+
857
+ this.metadata = {
858
+ ...this.metadata,
859
+ name: this.elements.name.value.trim(),
860
+ };
861
+
862
+ const event = { detail: { metadata: this.metadata } };
863
+ this._dispatchEvent(Event.ON_UPDATE, event);
864
+ };
865
+
866
+ public reset = () => {
867
+ this.metadata = null;
868
+ this._syncName();
869
+ this._dispatchEvent(Event.ON_RESET);
870
+ };
871
+ ```
872
+
873
+ How the implementation applies the policy:
874
+
875
+ - the specification declares the data contract under `Data`
876
+ - metadata defines both the canonical data vocabulary and the runtime data shape
877
+ - validation enforces the runtime data contract
878
+ - the component owns the canonical data through a private backing field
879
+ - the public getter and setter define the external data access contract
880
+ - the setter is the authoritative public mutation path
881
+ - data mutation follows the normalize → validate → guard → mutate → synchronize order
882
+ - data mutation synchronizes dependent state and composition
883
+ - operations remain thin or orchestrational around the authoritative data path
884
+ - semantic data workflow events are emitted through the framework dispatch path
885
+ - event payloads expose canonical accepted data through `detail`
886
+
887
+ ---
888
+
889
+ # Policy Checklist
890
+
891
+ A compliant component must:
892
+
893
+ 1. Declare all public data under `Data` in the specification.
894
+ 2. Define each data item using the required specification contract.
895
+ 3. Use `type` and `description` for every declared data item.
896
+ 4. Use `properties` when the data is structured.
897
+ 5. Use `default` when the runtime contract includes a default or unloaded sentinel.
898
+ 6. Use `readonly` to declare external ownership rules.
899
+ 7. Define all declared public data names in `<component>.meta.ts`.
900
+ 8. Use `Data` metadata as the canonical data vocabulary.
901
+ 9. Define the runtime data shape in `<component>.meta.ts`.
902
+ 10. Implement validation for each declared data item in `<component>.validation.ts`.
903
+ 11. Ensure validation enforces the declared runtime shape and constrained field domains.
904
+ 12. Give each declared data item a canonical internal source of truth.
905
+ 13. Expose an external access contract for each data item as read-only or writable.
906
+ 14. Keep public writable data mutation paths authoritative and centralized.
907
+ 15. Use the mutation order normalize → validate → guard → mutate → synchronize whenever those phases are needed.
908
+ 16. Model unloaded or cleared runtime data explicitly when supported.
909
+ 17. Keep data out of `Attributes` and DOM attribute observation by default.
910
+ 18. Allow data mutation to synchronize dependent state and composition predictably.
911
+ 19. Preserve authoritative ownership of dependent state when data updates it.
912
+ 20. Keep data-oriented operations thin or orchestrational around the canonical data mutation path.
913
+ 21. Prefer whole-value reassignment over ad hoc nested mutation in public workflow paths.
914
+ 22. Dispatch public data-oriented events through `_dispatchEvent(...)`.
915
+ 23. Emit data events only after the accepted data outcome or declared workflow outcome has occurred.
916
+ 24. Emit data payload through `event.detail`.
917
+ 25. Ensure emitted data payload matches the declared contract.
918
+ 26. Keep DOM editing surfaces non-canonical unless the specification explicitly says otherwise.
919
+ 27. Keep naming predictably aligned across specification, metadata, validation, component API, and event payloads.
920
+
921
+ ---
922
+
923
+ # Component Data Specification Schema
924
+
925
+ JSON Schema:
926
+
927
+ ```json
928
+ {
929
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
930
+ "$id": "https://scalable.software/schema/component-data.schema.json",
931
+ "title": "Component Data",
932
+ "description": "Schema for the Data object in a component specification.",
933
+ "type": "object",
934
+ "propertyNames": {
935
+ "type": "string",
936
+ "pattern": "^[a-z][a-zA-Z0-9]*$",
937
+ "description": "Canonical data name in lowerCamelCase, for example: metadata, document, payload."
938
+ },
939
+ "additionalProperties": {
940
+ "type": "object",
941
+ "description": "A data definition.",
942
+ "required": ["type", "description"],
943
+ "additionalProperties": false,
944
+ "properties": {
945
+ "type": {
946
+ "type": "string",
947
+ "minLength": 1,
948
+ "description": "Declared runtime shape category for the data, for example: object, array, string, Metadata."
949
+ },
950
+ "description": {
951
+ "type": "string",
952
+ "minLength": 1,
953
+ "description": "Human-readable description of the component-owned data."
954
+ },
955
+ "properties": {
956
+ "type": "object",
957
+ "description": "Structured field definitions for object-shaped data.",
958
+ "minProperties": 1,
959
+ "propertyNames": {
960
+ "type": "string",
961
+ "pattern": "^[a-z][a-zA-Z0-9]*$",
962
+ "description": "Canonical property name in lowerCamelCase, for example: id, name, type."
963
+ },
964
+ "additionalProperties": {
965
+ "type": "object",
966
+ "required": ["type", "description"],
967
+ "additionalProperties": false,
968
+ "properties": {
969
+ "type": {
970
+ "type": "string",
971
+ "minLength": 1,
972
+ "description": "Declared field type, for example: string, uuid, MapType."
973
+ },
974
+ "description": {
975
+ "type": "string",
976
+ "minLength": 1,
977
+ "description": "Human-readable description of the field."
978
+ },
979
+ "default": {
980
+ "description": "Default value for the field when applicable."
981
+ },
982
+ "values": {
983
+ "type": "array",
984
+ "minItems": 1,
985
+ "description": "Allowed values when the field has a constrained domain."
986
+ },
987
+ "readonly": {
988
+ "type": "boolean",
989
+ "description": "Whether the field is externally writable. Field-level readonly is optional and may be interpreted by component-specific policy."
990
+ }
991
+ }
992
+ }
993
+ },
994
+ "default": {
995
+ "description": "Default runtime value for the declared data item."
996
+ },
997
+ "readonly": {
998
+ "type": "boolean",
999
+ "description": "Whether the data item is externally writable. If true, the data is externally readable but internally owned."
1000
+ }
1001
+ }
1002
+ }
1003
+ }
1004
+ ```
1005
+
1006
+ Example JSON:
1007
+
1008
+ ```json
1009
+ {
1010
+ "Data": {
1011
+ "metadata": {
1012
+ "type": "object",
1013
+ "description": "The metadata of the clinical pathway.",
1014
+ "properties": {
1015
+ "id": {
1016
+ "type": "uuid",
1017
+ "description": "The unique identifier of the clinical pathway."
1018
+ },
1019
+ "name": {
1020
+ "type": "string",
1021
+ "description": "The name of the clinical pathway.",
1022
+ "default": "Clinical Pathway"
1023
+ },
1024
+ "type": {
1025
+ "type": "MapType",
1026
+ "description": "The type of the map.",
1027
+ "default": "pathway",
1028
+ "values": ["pathway"]
1029
+ }
1030
+ },
1031
+ "default": null,
1032
+ "readonly": false
1033
+ }
1034
+ }
1035
+ }
1036
+ ```