@wcstack/state 1.20.0 → 1.21.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.
package/README.ja.md CHANGED
@@ -324,6 +324,10 @@ property[#modifier]: path[@state][|filter[|filter(args)...]]
324
324
  | `#prevent` | イベントハンドラで `event.preventDefault()` を呼び出す |
325
325
  | `#stop` | イベントハンドラで `event.stopPropagation()` を呼び出す |
326
326
  | `#onchange` | 双方向バインディングで `input` の代わりに `change` イベントを使用 |
327
+ | `#init=<authority>` | バインディングの authority / 初期同期の向き — [バインディング authority](#バインディング-authority-init--sync) 参照 |
328
+ | `#sync=<timing>` | 要素スナップショットの読み取りタイミング — [バインディング authority](#バインディング-authority-init--sync) 参照 |
329
+
330
+ 複数の修飾子は 1 つの `#` の後にカンマ区切りで書きます: `value#ro,init=none: path`
327
331
 
328
332
  ### 双方向バインディング
329
333
 
@@ -338,6 +342,46 @@ property[#modifier]: path[@state][|filter[|filter(args)...]]
338
342
 
339
343
  `<input type="button">` は除外されます。`#ro` で無効化、`#onchange` でイベントを変更できます。
340
344
 
345
+ ### バインディング authority (`#init=` / `#sync=`)
346
+
347
+ `static wcBindable` を宣言したカスタム要素への prop バインディングは、**authority**(そのワイヤをどちら側が所有するか)を解決します。authority は初期値の出所だけでなく、**そのバインディングの生存期間全体で state→element 書き込みが有効かどうか**を決めます。既定はメンバの宣言位置から導出されます(`enableDirectionalInitialSync` で既定 ON):
348
+
349
+ | メンバの宣言位置 | 既定 authority | 効果 |
350
+ |---|---|---|
351
+ | `properties` のみ(output-only) | `element` | 要素の値が state に流れる。**state からこのメンバへは書き込まれない** |
352
+ | `inputs` のみ | `state` | state が要素に書き込む |
353
+ | `properties` + `inputs`(双方向) | `state` | 従来挙動 — state が先に書き、以後は要素イベントが state を更新 |
354
+ | —(`wcBindable` 非宣言・素の HTML 要素) | `state` | 従来と不変 |
355
+
356
+ > **作法:** settable なメンバは **`properties` と `inputs` の両方**に宣言してください。`properties` にしか宣言されていないメンバは output-only 扱いになり、state→element 書き込みがバインディングの生存期間ずっと抑止され、要素側の初期値が state 側のシード値を上書きします(`@wcstack` の I/O ノード Shell と DCC の `$bindables` はこの作法に従っています)。
357
+
358
+ authority はバインディング単位で `#init=` により上書きできます:
359
+
360
+ | 値 | authority | 使える宣言 |
361
+ |---|---|---|
362
+ | `init=state` | state 所有:state → element(双方向メンバは要素イベントも引き続き受信) | inputs のみ・双方向 |
363
+ | `init=element` | element 所有:要素のスナップショットとイベント → state。state からの書き込みは抑止 | output-only・双方向 |
364
+ | `init=auto` | state スロットが未初期化なら `element`、それ以外は `state` | 双方向 |
365
+ | `init=none` | 初期同期なし(event バインディングはこの値のみ許可) | すべて |
366
+
367
+ `#sync=` は element authority のバインディングで要素スナップショットを**いつ**読むかを制御します:
368
+
369
+ | 値 | 意味 |
370
+ |---|---|
371
+ | `sync=call`(既定) | バインディングの attach 時に即読み取り |
372
+ | `sync=connect` | 要素が document に接続されるまで読み取りを保留 |
373
+
374
+ ```html
375
+ <x-clock data-wcs="value#init=element: clock.now"></x-clock>
376
+ <x-input data-wcs="value#init=auto: form.name"></x-input>
377
+ <x-widget data-wcs="value#init=element,sync=connect: widget.snapshot"></x-widget>
378
+ ```
379
+
380
+ 注意:
381
+
382
+ - `enableDirectionalInitialSync: false`(opt-out)のとき `#init=`/`#sync=` を書くと throw します。
383
+ - **1.20 以前からの移行:** output-only メンバに対して state 側に都合のよい初期値(`value: []`、`query: ""` 等)をシードしないでください — 要素側の実初期値(多くは `null`/`undefined`)がシードを置き換えます。シードは要素の実初期値に合わせ、表示用の値は派生 getter で null ガードしてください。
384
+
341
385
  ### ラジオボタンバインディング
342
386
 
343
387
  `radio` でラジオボタングループを単一の状態値にバインドします:
@@ -1193,7 +1237,7 @@ class MyFetcher extends HTMLElement {
1193
1237
 
1194
1238
  検証ルール(バインディング時に強制):
1195
1239
 
1196
- - 要素は `protocol: "wc-bindable"` かつ `version: 1` `static wcBindable` を公開するカスタム要素であること
1240
+ - 要素は `protocol: "wc-bindable"` かつ整数 `version` が `1` 以上(現行プロトコルは `1`。1 以上のすべてのバージョンが core 互換)の `static wcBindable` を公開するカスタム要素であること
1197
1241
  - `methodName` は `wcBindable.commands` に(`name` で)現れること
1198
1242
  - バインドされる値は `CommandToken` であること(token 以外の値の代入は throw する —— 例えば未宣言の名前 `$command.typo` は `undefined` に解決され、ここで拒否される)
1199
1243
 
@@ -1352,7 +1396,7 @@ class MyTarget extends HTMLElement {
1352
1396
 
1353
1397
  検証ルール:
1354
1398
 
1355
- - 要素は wc-bindable なカスタム要素であること(`static wcBindable`・`protocol: "wc-bindable"`・`version: 1`)。非 wc-bindable 要素は attach 時に拒否される。
1399
+ - 要素は wc-bindable なカスタム要素であること(`static wcBindable`・`protocol: "wc-bindable"`・整数 `version` 1 以上。1 以上のすべてのバージョンが core 互換)。非 wc-bindable 要素は attach 時に拒否される。
1356
1400
  - `<property>` は `wcBindable.properties` に現れること —— **attach 時** に検証(fail-fast。クラス参照のみで足り、DOM 接続に非依存)。
1357
1401
  - `<tokenName>` は `$eventTokens` で宣言されていること —— **発火時** に検証。state はイベント発火時に要素の live root から解決されるため、attach 時にノードが detached になりうる `for` / `if` ブロック内や SSR ハイドレーション後でも機能する。
1358
1402
  - 修飾子 `#prevent` / `#stop` は通常のイベントバインディングと同様に機能する: `eventToken.error#prevent: createFailed`。
@@ -1454,6 +1498,8 @@ error 時、プロパティは直前の fold 結果を保持し、エラーは `
1454
1498
 
1455
1499
  `wcBindable.inputs` は一方向のプロパティ入力(state → 要素)を宣言します。エントリに `attribute` を設定すると、フレームワークはプロパティを書き込むたびにその値を当該 HTML 属性へも書き込むため、`attributeChangedCallback`・CSS の属性セレクタ・DevTools がすべてプロパティ値と同期し続けます。
1456
1500
 
1501
+ `inputs` は属性ミラーのためだけのメタデータではありません。方向認識初期同期(既定 ON)の下では、メンバが **state から settable であること**を示すのが `inputs` です。settable なのに `properties` にしか宣言されていないメンバは output-only 扱いになり、state からの書き込みが抑止されます — [バインディング authority](#バインディング-authority-init--sync) を参照してください。
1502
+
1457
1503
  ```javascript
1458
1504
  class MyChip extends HTMLElement {
1459
1505
  static wcBindable = {
@@ -1552,7 +1598,7 @@ export default {
1552
1598
 
1553
1599
  これにより以下が生成されます:
1554
1600
 
1555
- - クラスの `static wcBindable` — フレームワークアダプタ用のプロトコルメタデータ
1601
+ - クラスの `static wcBindable` — フレームワークアダプタ用のプロトコルメタデータ。各 `$bindables` メンバは `properties` と `inputs` の両方に宣言され(双方向)、方向認識初期同期の下でも親 state → DCC の書き込みが機能します — [バインディング authority](#バインディング-authority-init--sync) 参照
1556
1602
  - プロトタイプの getter/setter — リアクティブプロキシ経由で読み書き
1557
1603
  - `CustomEvent` のディスパッチ — 値が変更されるたびに `my-counter:count-changed` が発火
1558
1604
 
@@ -1671,6 +1717,21 @@ bootstrapState({
1671
1717
  | `locale` | `'en'` | フィルタのデフォルトロケール |
1672
1718
  | `debug` | `false` | デバッグモード |
1673
1719
  | `enableMustache` | `true` | `{{ }}` 構文の有効化 |
1720
+ | `enableDirectionalInitialSync` | `true` | 方向認識のバインディング authority(`#init=` / `#sync=` バインド modifier)— [バインディング authority](#バインディング-authority-init--sync) 参照。既定 on。`false` で opt-out |
1721
+ | `enablePropagationContext` | `true` | バインド間の因果伝播トラッキング(echo/diamond のループ防止)。既定 on。`false` で opt-out |
1722
+ | `enableContractAnalyzer` | `false` | opt-in の開発時 contract analyzer(`analyzeContract` を公開) |
1723
+
1724
+ > この 3 つは **architecture-hardening** 機能で、規範は `docs/architecture-hardening/` に
1725
+ > あります。`enablePropagationContext` は**既定 on** — write-path コストは一方向バインドで
1726
+ > ほぼゼロ(echo しうる双方向 wire のみ因果 bookkeeping を行う)で、フラグは恒久的な
1727
+ > opt-out として残します。`enableDirectionalInitialSync` も**既定 on**: プロパティ単位で
1728
+ > 初期同期の authority を割り当てます(output-only な `wcBindable` メンバは初期値を
1729
+ > element→state で読み取り、双方向 / input メンバは state→element を維持)。setup-path
1730
+ > コストは初期 render の 5% 未満(producer-value observer は echo しうる双方向 wire にのみ
1731
+ > 登録)で、フラグは恒久的な opt-out として残します。`enableContractAnalyzer` は opt-in
1732
+ > (既定 `false`・無効時ランタイムコストゼロ)で、有効な場合、公開 API `analyzeContract()`
1733
+ > が稼働中の `static wcBindable` サーフェスと sidecar manifest の drift を開発時診断として
1734
+ > 報告します。
1674
1735
 
1675
1736
  ## TypeScript サポート
1676
1737
 
package/README.md CHANGED
@@ -324,6 +324,10 @@ Multiple bindings separated by `;`:
324
324
  | `#prevent` | Calls `event.preventDefault()` on event handlers |
325
325
  | `#stop` | Calls `event.stopPropagation()` on event handlers |
326
326
  | `#onchange` | Uses `change` event instead of `input` for two-way binding |
327
+ | `#init=<authority>` | Binding authority / initial sync direction — see [Binding Authority](#binding-authority-init--sync) |
328
+ | `#sync=<timing>` | Element snapshot timing — see [Binding Authority](#binding-authority-init--sync) |
329
+
330
+ Multiple modifiers are comma-separated after a single `#`: `value#ro,init=none: path`.
327
331
 
328
332
  ### Two-Way Binding
329
333
 
@@ -338,6 +342,46 @@ Automatically enabled for:
338
342
 
339
343
  `<input type="button">` is excluded. Use `#ro` to disable, `#onchange` to change the event.
340
344
 
345
+ ### Binding Authority (`#init=` / `#sync=`)
346
+
347
+ For custom elements that declare `static wcBindable`, every prop binding resolves an **authority** — which side owns the wire. The authority decides both where the initial value comes from *and* whether state→element writes are enabled, for the binding's whole lifetime. It is derived from where the member is declared (on by default via `enableDirectionalInitialSync`):
348
+
349
+ | Member declared in | Default authority | Effect |
350
+ |---|---|---|
351
+ | `properties` only (output-only) | `element` | The element's value flows into state; **state never writes this member** |
352
+ | `inputs` only | `state` | State writes the element |
353
+ | `properties` + `inputs` (two-way) | `state` | Classic behavior — state writes first, element events update state afterwards |
354
+ | — (no `wcBindable`; plain HTML elements) | `state` | Unchanged behavior |
355
+
356
+ > **Authoring rule:** declare every settable member in **both** `properties` and `inputs`. A member declared only in `properties` is output-only — state→element writes are suppressed for the life of the binding, and the element's own initial value overwrites whatever the state seeded. (`@wcstack` I/O node Shells and DCC `$bindables` follow this rule.)
357
+
358
+ Override the authority per binding with `#init=`:
359
+
360
+ | Value | Authority | Allowed on |
361
+ |---|---|---|
362
+ | `init=state` | State-owned: state → element (two-way members still receive element events) | inputs-only, two-way |
363
+ | `init=element` | Element-owned: element snapshot and events → state; state writes are suppressed | output-only, two-way |
364
+ | `init=auto` | `element` if the state slot is uninitialized, otherwise `state` | two-way |
365
+ | `init=none` | No initial sync (event bindings accept only this value) | any |
366
+
367
+ `#sync=` controls **when** the element snapshot is read for element-authority bindings:
368
+
369
+ | Value | Meaning |
370
+ |---|---|
371
+ | `sync=call` (default) | Read immediately when the binding attaches |
372
+ | `sync=connect` | Defer the read until the element is connected to the document |
373
+
374
+ ```html
375
+ <x-clock data-wcs="value#init=element: clock.now"></x-clock>
376
+ <x-input data-wcs="value#init=auto: form.name"></x-input>
377
+ <x-widget data-wcs="value#init=element,sync=connect: widget.snapshot"></x-widget>
378
+ ```
379
+
380
+ Notes:
381
+
382
+ - With `enableDirectionalInitialSync: false` (opt-out), writing `#init=`/`#sync=` throws.
383
+ - **Migrating from ≤ 1.20:** do not seed state with placeholder values (`value: []`, `query: ""`) for output-only members — the element's real initial value (often `null`/`undefined`) replaces the seed. Match the seed to the element's actual initial value and null-guard display values with a derived getter.
384
+
341
385
  ### Radio Binding
342
386
 
343
387
  Bind a radio button group to a single state value with `radio`:
@@ -1194,7 +1238,7 @@ class MyFetcher extends HTMLElement {
1194
1238
 
1195
1239
  Validation rules (enforced at binding time):
1196
1240
 
1197
- - The element must be a custom element exposing `static wcBindable` with `protocol: "wc-bindable"` and `version: 1`
1241
+ - The element must be a custom element exposing `static wcBindable` with `protocol: "wc-bindable"` and an integer `version` of `1` or later (the current protocol version is `1`; all versions ≥ 1 are core-compatible)
1198
1242
  - `methodName` must appear (by `name`) in `wcBindable.commands`
1199
1243
  - The bound value must be a `CommandToken` (assigning a non-token value throws — for example, an undeclared name like `$command.typo` resolves to `undefined` and is rejected here)
1200
1244
 
@@ -1353,7 +1397,7 @@ class MyTarget extends HTMLElement {
1353
1397
 
1354
1398
  Validation rules:
1355
1399
 
1356
- - The element must be a wc-bindable custom element (`static wcBindable`, `protocol: "wc-bindable"`, `version: 1`). A non-wc-bindable element is rejected at attach time.
1400
+ - The element must be a wc-bindable custom element (`static wcBindable`, `protocol: "wc-bindable"`, integer `version` 1 — all versions ≥ 1 are core-compatible). A non-wc-bindable element is rejected at attach time.
1357
1401
  - `<property>` must appear in `wcBindable.properties` — checked at **attach time** (fail-fast; needs only the class, not DOM connection).
1358
1402
  - `<tokenName>` must be declared in `$eventTokens` — checked at **fire time**. State is resolved from the element's live root node when the event fires, so the binding also works inside `for` / `if` blocks and after SSR hydration, where the node may still be detached at attach time.
1359
1403
  - Modifiers `#prevent` / `#stop` work as on any event binding: `eventToken.error#prevent: createFailed`.
@@ -1455,6 +1499,8 @@ See [docs/streams.md](docs/streams.md) for the full contract — lifecycle and o
1455
1499
 
1456
1500
  `wcBindable.inputs` declares one-way property inputs (state → element). When an entry sets `attribute`, the framework writes the value to that HTML attribute every time it writes the property, so `attributeChangedCallback`, CSS attribute selectors, and DevTools all stay in sync with the property value.
1457
1501
 
1502
+ `inputs` is not just attribute-mirroring metadata: under directional initial sync (default on), it is what marks a member as **settable from state**. A settable member declared only in `properties` becomes output-only and state writes to it are suppressed — see [Binding Authority](#binding-authority-init--sync).
1503
+
1458
1504
  ```javascript
1459
1505
  class MyChip extends HTMLElement {
1460
1506
  static wcBindable = {
@@ -1553,7 +1599,7 @@ export default {
1553
1599
 
1554
1600
  This generates:
1555
1601
 
1556
- - `static wcBindable` on the class — protocol metadata for framework adapters
1602
+ - `static wcBindable` on the class — protocol metadata for framework adapters. Each `$bindables` member is declared in both `properties` and `inputs` (two-way), so parent-state → DCC writes keep working under directional initial sync — see [Binding Authority](#binding-authority-init--sync)
1557
1603
  - Getter/setter on the prototype — reads/writes go through the reactive proxy
1558
1604
  - `CustomEvent` dispatch — `my-counter:count-changed` fires on every mutation
1559
1605
 
@@ -1672,6 +1718,22 @@ All options with defaults:
1672
1718
  | `locale` | `'en'` | Default locale for filters |
1673
1719
  | `debug` | `false` | Debug mode |
1674
1720
  | `enableMustache` | `true` | Enable `{{ }}` syntax |
1721
+ | `enableDirectionalInitialSync` | `true` | Direction-aware binding authority (`#init=` / `#sync=` binding modifiers) — see [Binding Authority](#binding-authority-init--sync). Default on; set `false` to opt out |
1722
+ | `enablePropagationContext` | `true` | Causal propagation tracking across bindings (echo/diamond loop prevention). Default on; set `false` to opt out |
1723
+ | `enableContractAnalyzer` | `false` | Opt-in dev-time contract analyzer (exposes `analyzeContract`) |
1724
+
1725
+ > These three are **architecture-hardening** features; their normative reference is
1726
+ > `docs/architecture-hardening/`. `enablePropagationContext` defaults **on** — its
1727
+ > write-path cost is near-zero for one-way bindings (only echo-capable two-way
1728
+ > wires do the causal bookkeeping) — with the flag kept as a permanent opt-out.
1729
+ > `enableDirectionalInitialSync` also defaults **on**: it assigns per-property
1730
+ > initial-sync authority (an output-only `wcBindable` member reads its initial value
1731
+ > element→state; two-way / input members keep state→element). Its setup-path cost is
1732
+ > under 5% of initial render (the producer-value observer is only registered for
1733
+ > echo-capable two-way wires), and the flag is a permanent opt-out. `enableContractAnalyzer`
1734
+ > is opt-in (default `false`, zero runtime cost when off); when on, the exported
1735
+ > `analyzeContract()` API reports drift between a live `static wcBindable` surface and
1736
+ > a sidecar manifest for dev-time diagnostics.
1675
1737
 
1676
1738
  ## TypeScript Support
1677
1739
 
package/dist/index.d.ts CHANGED
@@ -1,3 +1,168 @@
1
+ /**
2
+ * Interface for hierarchical loop index management in nested loops.
3
+ * Tracks parent-child relationships, versions, and provides access to index hierarchy.
4
+ */
5
+ interface IListIndex {
6
+ readonly parentListIndex: IListIndex | null;
7
+ readonly uuid: string;
8
+ readonly position: number;
9
+ readonly length: number;
10
+ index: number;
11
+ readonly version: number;
12
+ readonly dirty: boolean;
13
+ readonly indexes: number[];
14
+ readonly listIndexes: WeakRef<IListIndex>[];
15
+ readonly varName: string;
16
+ at(position: number): IListIndex | null;
17
+ }
18
+ interface ILoopContext extends IStateAddress {
19
+ readonly pathInfo: IPathInfo;
20
+ readonly listIndex: IListIndex;
21
+ }
22
+ interface ILoopContextStack {
23
+ createLoopContext(elementStateAddress: IStateAddress, callback: (loopContext: ILoopContext) => void | Promise<void>): void | Promise<void>;
24
+ }
25
+
26
+ declare const setLoopContextAsyncSymbol: unique symbol;
27
+ declare const setLoopContextSymbol: unique symbol;
28
+ declare const getByAddressSymbol: unique symbol;
29
+ declare const hasByAddressSymbol: unique symbol;
30
+ declare const setByAddressSymbol: unique symbol;
31
+ declare const connectedCallbackSymbol: unique symbol;
32
+ declare const disconnectedCallbackSymbol: unique symbol;
33
+ declare const updatedCallbackSymbol: unique symbol;
34
+
35
+ interface IStateProxy extends IState {
36
+ [setLoopContextAsyncSymbol](loopContext: ILoopContext | null, callback: () => Promise<any>): Promise<any>;
37
+ [setLoopContextSymbol](loopContext: ILoopContext | null, callback: () => any): any;
38
+ [getByAddressSymbol](address: IStateAddress): any;
39
+ [hasByAddressSymbol](address: IStateAddress): boolean;
40
+ [setByAddressSymbol](address: IStateAddress, value: any): void;
41
+ [connectedCallbackSymbol](): Promise<void>;
42
+ [disconnectedCallbackSymbol](): void;
43
+ [updatedCallbackSymbol](updatedAbsAddressList: IAbsoluteStateAddress[]): void;
44
+ }
45
+ type Mutability = "readonly" | "writable";
46
+
47
+ interface IStateElement {
48
+ readonly name: string;
49
+ readonly initializePromise: Promise<void>;
50
+ readonly connectedCallbackPromise: Promise<void>;
51
+ readonly listPaths: Set<string>;
52
+ readonly elementPaths: Set<string>;
53
+ readonly getterPaths: Set<string>;
54
+ readonly setterPaths: Set<string>;
55
+ readonly loopContextStack: ILoopContextStack;
56
+ readonly dynamicDependency: Map<string, string[]>;
57
+ readonly staticDependency: Map<string, string[]>;
58
+ readonly version: number;
59
+ readonly rootNode: Node;
60
+ readonly boundComponentStateProp: string | null;
61
+ readonly bindableEventMap: Record<string, string>;
62
+ readonly commandTokenNames: ReadonlySet<string>;
63
+ readonly eventTokenNames: ReadonlySet<string>;
64
+ /**
65
+ * state が $updatedCallback を定義しているか。false のとき drain は更新
66
+ * アドレスの集計と最終の writable createState を丸ごとスキップできる。
67
+ * optional なのはテスト用モック互換のため(undefined は「不明=集計する」)。
68
+ */
69
+ readonly hasUpdatedCallback?: boolean;
70
+ /**
71
+ * 他行を読む getter(隣接項目参照など)が検出されたリストパスの集合。
72
+ * これらのリストは walkDependency の diff-filter 展開の対象外(全行展開)。
73
+ * optional なのはテスト用モック互換のため(undefined は「検出なし」扱い)。
74
+ */
75
+ readonly crossRowListPaths?: ReadonlySet<string>;
76
+ addCrossRowListPath?(path: string): void;
77
+ setPathInfo(path: string, bindingType: BindingType): void;
78
+ addStaticDependency(parentPath: string, childPath: string): boolean;
79
+ addDynamicDependency(fromPath: string, toPath: string): boolean;
80
+ createStateAsync(mutability: Mutability, callback: (state: IStateProxy) => Promise<void>): Promise<void>;
81
+ createState(mutability: Mutability, callback: (state: IStateProxy) => void): void;
82
+ nextVersion(): number;
83
+ bindProperty(prop: string, desc: PropertyDescriptor): void;
84
+ setInitialState(state: Record<string, any>): void;
85
+ }
86
+
87
+ interface IPathInfo {
88
+ readonly id: number;
89
+ readonly path: string;
90
+ readonly segments: string[];
91
+ readonly lastSegment: string;
92
+ readonly cumulativePaths: string[];
93
+ readonly cumulativePathSet: Set<string>;
94
+ readonly cumulativePathInfos: IPathInfo[];
95
+ readonly cumulativePathInfoSet: Set<IPathInfo>;
96
+ readonly parentPath: string | null;
97
+ readonly parentPathInfo: IPathInfo | null;
98
+ readonly wildcardPaths: string[];
99
+ readonly wildcardPathSet: Set<string>;
100
+ readonly indexByWildcardPath: Record<string, number>;
101
+ readonly wildcardPathInfos: IPathInfo[];
102
+ readonly wildcardPathInfoSet: Set<IPathInfo>;
103
+ readonly wildcardParentPaths: string[];
104
+ readonly wildcardParentPathSet: Set<string>;
105
+ readonly wildcardParentPathInfos: IPathInfo[];
106
+ readonly wildcardParentPathInfoSet: Set<IPathInfo>;
107
+ readonly wildcardPositions: number[];
108
+ readonly lastWildcardPath: string | null;
109
+ readonly lastWildcardInfo: IPathInfo | null;
110
+ readonly wildcardCount: number;
111
+ }
112
+ interface IStateAddress {
113
+ readonly pathInfo: IPathInfo;
114
+ readonly listIndex: IListIndex | null;
115
+ readonly parentAddress: IStateAddress | null;
116
+ }
117
+ interface IAbsolutePathInfo {
118
+ readonly stateName: string;
119
+ readonly stateElement: IStateElement;
120
+ readonly pathInfo: IPathInfo;
121
+ readonly parentAbsolutePathInfo: IAbsolutePathInfo | null;
122
+ }
123
+ interface IAbsoluteStateAddress {
124
+ readonly absolutePathInfo: IAbsolutePathInfo;
125
+ readonly listIndex: IListIndex | null;
126
+ readonly parentAbsoluteAddress: IAbsoluteStateAddress | null;
127
+ }
128
+
129
+ /**
130
+ * Filter/types.ts
131
+ *
132
+ * Type definition file for filter functions.
133
+ *
134
+ * Main responsibilities:
135
+ * - Defines types for filter functions (FilterFn) and filter functions with options (FilterWithOptionsFn)
136
+ * - Type-safe management of filter name-to-function mappings (FilterWithOptions) and filter function arrays (Filters)
137
+ * - Defines types for retrieving filter functions from built-in filter collections
138
+ *
139
+ * Design points:
140
+ * - Type design enabling flexible filter design and extension
141
+ * - Supports filters with options and combinations of multiple filters
142
+ */
143
+ type FilterFn<T = unknown> = (value: unknown) => T;
144
+
145
+ type BindingType = 'text' | 'prop' | 'event' | 'for' | 'if' | 'elseif' | 'else' | 'radio' | 'checkbox' | 'spread';
146
+ interface IFilterInfo {
147
+ readonly filterName: string;
148
+ readonly args: string[];
149
+ readonly filterFn: FilterFn;
150
+ }
151
+ interface IBindingInfo {
152
+ readonly propName: string;
153
+ readonly propSegments: string[];
154
+ readonly propModifiers: string[];
155
+ readonly statePathName: string;
156
+ readonly statePathInfo: IPathInfo;
157
+ readonly stateName: string;
158
+ readonly inFilters: IFilterInfo[];
159
+ readonly outFilters: IFilterInfo[];
160
+ readonly node: Node;
161
+ readonly replaceNode: Node;
162
+ readonly bindingType: BindingType;
163
+ readonly uuid?: string | null;
164
+ }
165
+
1
166
  interface IState {
2
167
  [key: string]: any;
3
168
  }
@@ -20,6 +185,25 @@ interface IConfig {
20
185
  readonly locale: string;
21
186
  readonly debug: boolean;
22
187
  readonly enableMustache: boolean;
188
+ /**
189
+ * Enables direction-aware initial synchronization (`init=` / `sync=`).
190
+ * Disabled by default while Phase 2 is evaluated against existing snapshots.
191
+ */
192
+ readonly enableDirectionalInitialSync: boolean;
193
+ /**
194
+ * Enables causal propagation tracking (transaction / edge provenance /
195
+ * write receipts). Disabled by default while Phase 3 runs as a shadow of
196
+ * the primitive same-value guard.
197
+ */
198
+ readonly enablePropagationContext: boolean;
199
+ /**
200
+ * Enables the opt-in dev-time contract analyzer (Phase 5b). When false
201
+ * (default), `analyzeContract()` is a no-op with zero cost — runtime
202
+ * behavior and allocation are unchanged. When true, it checks the actually
203
+ * loaded `static wcBindable` declarations against a supplied sidecar
204
+ * manifest and emits `contract:*` drift trace via the DevTools sink.
205
+ */
206
+ readonly enableContractAnalyzer: boolean;
23
207
  /**
24
208
  * 同値ガード(**既定 true**・標準的リアクティブ挙動・`setConfig({ sameValueGuard: false })` で opt-out 可)。
25
209
  * primitive 値の set で `Object.is` 同値なら更新を no-op にする
@@ -40,6 +224,9 @@ interface IWritableConfig {
40
224
  locale?: string;
41
225
  debug?: boolean;
42
226
  enableMustache?: boolean;
227
+ enableDirectionalInitialSync?: boolean;
228
+ enablePropagationContext?: boolean;
229
+ enableContractAnalyzer?: boolean;
43
230
  sameValueGuard?: boolean;
44
231
  }
45
232
 
@@ -52,6 +239,13 @@ declare function getConfig(): IConfig;
52
239
  */
53
240
  declare function getBindingsReady(rootNode: Node): Promise<void>;
54
241
 
242
+ /**
243
+ * Browser builds use the native HTMLElement. Headless runtimes receive an
244
+ * inert base so the public module can be imported without installing DOM
245
+ * globals; constructing components remains a browser-only operation.
246
+ */
247
+ declare const HTMLElementBase: typeof HTMLElement;
248
+
55
249
  interface ISsrElement {
56
250
  readonly name: string;
57
251
  readonly version: string;
@@ -61,7 +255,7 @@ interface ISsrElement {
61
255
  getTemplate(uuid: string): HTMLTemplateElement | null;
62
256
  verifyVersion(): boolean;
63
257
  }
64
- declare class Ssr extends HTMLElement implements ISsrElement {
258
+ declare class Ssr extends HTMLElementBase implements ISsrElement {
65
259
  private _stateData;
66
260
  private _templates;
67
261
  private _hydrateProps;
@@ -425,5 +619,141 @@ interface IWcsManifest {
425
619
  /** 機械可読な単一正本を返す。vscode-wcs はこれを消費する想定。 */
426
620
  declare function getWcsManifest(): IWcsManifest;
427
621
 
428
- export { Ssr, VERSION, WCS_MANIFEST_VERSION, bootstrapState, buildBindings, builtinFilterMeta, defineState, getBindingsReady, getConfig, getWcsManifest };
429
- export type { FilterArgType, FilterResultType, IFilterMeta, ISsrElement, IWcsManifest, IWritableConfig, IWritableTagNames, WcsPathValue, WcsPaths, WcsStateApi, WcsThis };
622
+ /**
623
+ * devtools/types.ts
624
+ *
625
+ * DevTools Hook Protocol (docs/devtools-hook-protocol.md) の型定義。
626
+ *
627
+ * イベント payload はランタイム内部オブジェクト(IAbsoluteStateAddress /
628
+ * IBindingInfo 等)への生参照を含む(同一 realm・オーバーレイ前提、protocol 原則 4)。
629
+ * 消費者はこれらを変異してはならない。
630
+ */
631
+
632
+ type DevtoolsEvent = {
633
+ readonly type: "state:element-registered";
634
+ readonly name: string;
635
+ readonly rootNode: Node;
636
+ readonly element: IStateElement;
637
+ } | {
638
+ readonly type: "state:element-unregistered";
639
+ readonly name: string;
640
+ readonly rootNode: Node;
641
+ readonly element: IStateElement;
642
+ } | {
643
+ readonly type: "state:write";
644
+ readonly absoluteAddress: IAbsoluteStateAddress;
645
+ readonly value: unknown;
646
+ /** same-value guard が既に取得していた場合のみ意味を持つ(protocol §4.2) */
647
+ readonly oldValue: unknown;
648
+ readonly hasOldValue: boolean;
649
+ } | {
650
+ readonly type: "state:update-batch";
651
+ readonly addresses: ReadonlySet<IAbsoluteStateAddress>;
652
+ } | {
653
+ readonly type: "state:binding-added";
654
+ readonly absoluteAddress: IAbsoluteStateAddress;
655
+ readonly binding: IBindingInfo;
656
+ } | {
657
+ readonly type: "state:binding-removed";
658
+ readonly absoluteAddress: IAbsoluteStateAddress;
659
+ readonly binding: IBindingInfo;
660
+ } | {
661
+ readonly type: "state:binding-cleared";
662
+ readonly absoluteAddress: IAbsoluteStateAddress;
663
+ } | {
664
+ readonly type: "state:token-emit";
665
+ readonly kind: "command" | "event";
666
+ readonly stateName: string | null;
667
+ readonly tokenName: string;
668
+ readonly args: readonly unknown[];
669
+ readonly subscriberCount: number;
670
+ } | {
671
+ readonly type: "propagation:suppressed";
672
+ readonly reason: "confirmation" | "visited-edge";
673
+ readonly transactionId: number;
674
+ readonly edgeId: number;
675
+ readonly node: Node;
676
+ readonly member: string;
677
+ } | {
678
+ readonly type: "propagation:coalesced";
679
+ readonly absoluteAddress: IAbsoluteStateAddress;
680
+ readonly droppedTransactionId: number;
681
+ readonly winnerTransactionId: number;
682
+ } | {
683
+ readonly type: "propagation:hop-limit";
684
+ readonly absoluteAddress: IAbsoluteStateAddress;
685
+ readonly transactionId: number;
686
+ readonly hop: number;
687
+ } | {
688
+ readonly type: "contract:manifest-read";
689
+ readonly tag: string;
690
+ /** 実行時に該当タグが登録済みか(未登録なら drift の起点)。 */
691
+ readonly loaded: boolean;
692
+ } | {
693
+ readonly type: "contract:unsupported-extension";
694
+ readonly namespace: string;
695
+ } | {
696
+ readonly type: "contract:drift";
697
+ readonly reason: "component-not-loaded" | "missing-member" | "event-mismatch";
698
+ readonly tag: string;
699
+ readonly member?: string;
700
+ /** event-mismatch のとき: sidecar 宣言 event / live event。 */
701
+ readonly sidecarEvent?: string;
702
+ readonly liveEvent?: string;
703
+ };
704
+ /** contract analyzer(Phase 5b)が生成しうる event だけの狭い union(公開 API の戻り型)。 */
705
+ type ContractEvent = Extract<DevtoolsEvent, {
706
+ readonly type: "contract:manifest-read" | "contract:unsupported-extension" | "contract:drift";
707
+ }>;
708
+
709
+ /**
710
+ * contract/types.ts
711
+ *
712
+ * Phase 5b(dev-time contract analyzer)が読む sidecar manifest の最小 subset。
713
+ * 完全な JSON-Schema subset 検証は CI 側(vscode-wcs の validator core)の責務であり、
714
+ * runtime analyzer は「実際に読み込まれた wcBindable 宣言との drift」照合に絞る。
715
+ *
716
+ * この型は vscode-wcs の `wcstack.types` を copy-distribution したもの(§14: ランタイム
717
+ * 依存を導入しない)。CI 側の全量型ではなく drift 照合に必要な形だけを持つ。
718
+ */
719
+ interface IContractObservable {
720
+ readonly event?: string;
721
+ }
722
+ interface IContractComponent {
723
+ readonly observables?: Readonly<Record<string, IContractObservable>>;
724
+ readonly inputs?: Readonly<Record<string, unknown>>;
725
+ readonly commands?: Readonly<Record<string, unknown>>;
726
+ }
727
+ interface IContractManifest {
728
+ readonly manifestExtensions?: {
729
+ readonly "wcstack.types"?: {
730
+ readonly components?: Readonly<Record<string, IContractComponent>>;
731
+ };
732
+ readonly [namespace: string]: unknown;
733
+ };
734
+ }
735
+
736
+ /**
737
+ * contract/contractAnalyzer.ts
738
+ *
739
+ * Phase 5b(09-remediation-design.md §5b / §7.1 dev runtime / §6 contract trace)の
740
+ * opt-in dev-time analyzer。実際に登録済みの custom element の `static wcBindable`
741
+ * 宣言(= 実行時の正本)を、利用者が渡した sidecar manifest と突き合わせ、drift を
742
+ * DevTools trace(`contract:*`)へ流す。
743
+ *
744
+ * 完了条件「無効時の runtime 挙動・cost が不変」: `analyzeContract` は
745
+ * `config.enableContractAnalyzer` が false のとき即 return し、manifest を一切走査
746
+ * しない(hot path には一切フックしない — 純粋な on-demand API)。
747
+ *
748
+ * pure な core(`analyzeManifestContract`)は宣言解決と emit を注入で受けるためテスト可能。
749
+ */
750
+
751
+ /**
752
+ * opt-in dev-time contract analysis。無効時はゼロコスト(即 return・manifest 非走査)。
753
+ * 有効時は live 宣言と manifest を突き合わせ、`contract:*` trace を返しつつ、DevTools
754
+ * sink が接続されていれば同時に流す。
755
+ */
756
+ declare function analyzeContract(manifest: IContractManifest): readonly ContractEvent[];
757
+
758
+ export { Ssr, VERSION, WCS_MANIFEST_VERSION, analyzeContract, bootstrapState, buildBindings, builtinFilterMeta, defineState, getBindingsReady, getConfig, getWcsManifest };
759
+ export type { ContractEvent, FilterArgType, FilterResultType, IContractManifest, IFilterMeta, ISsrElement, IWcsManifest, IWritableConfig, IWritableTagNames, WcsPathValue, WcsPaths, WcsStateApi, WcsThis };