@wcstack/state 4.10.1 → 4.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.ja.md +27 -7
  2. package/README.md +28 -7
  3. package/dist/auto.min.js +1 -1
  4. package/dist/auto.min.js.map +1 -1
  5. package/dist/index.d.ts +5 -1
  6. package/dist/index.esm.js +1 -1
  7. package/dist/index.esm.js.map +1 -1
  8. package/dist/split/auto.js +1 -1
  9. package/dist/split/chunks/{chunk-JCQTA35B.js → chunk-AKTGQ6TC.js} +2 -2
  10. package/dist/split/chunks/chunk-CYMUD7KV.js +2 -0
  11. package/dist/split/chunks/chunk-CYMUD7KV.js.map +1 -0
  12. package/dist/split/chunks/chunk-E3XSBW55.js +2 -0
  13. package/dist/split/chunks/chunk-IYPAK7PO.js +2 -0
  14. package/dist/split/chunks/chunk-IYPAK7PO.js.map +1 -0
  15. package/dist/split/chunks/{chunk-FFXJVUTT.js → chunk-JJYM4JVZ.js} +2 -2
  16. package/dist/split/chunks/chunk-KBQKAD3I.js +2 -0
  17. package/dist/split/chunks/{chunk-PTFWXISY.js.map → chunk-KBQKAD3I.js.map} +1 -1
  18. package/dist/split/chunks/{chunk-SJRJALR4.js → chunk-LPJFTEVT.js} +2 -2
  19. package/dist/split/chunks/chunk-NAV7FBTI.js +2 -0
  20. package/dist/split/chunks/{chunk-MZVF2WEY.js.map → chunk-NAV7FBTI.js.map} +1 -1
  21. package/dist/split/chunks/{chunk-FT7OW7CG.js → chunk-R6RHIIR4.js} +2 -2
  22. package/dist/split/chunks/chunk-REB4RMZ3.js +2 -0
  23. package/dist/split/chunks/{chunk-65B2K2XD.js.map → chunk-REB4RMZ3.js.map} +1 -1
  24. package/dist/split/chunks/chunk-SJ552B3B.js +2 -0
  25. package/dist/split/chunks/{chunk-SANIIUS2.js.map → chunk-SJ552B3B.js.map} +1 -1
  26. package/dist/split/core.js +1 -1
  27. package/dist/split/features/devtools.js +1 -1
  28. package/dist/split/features/devtools.js.map +1 -1
  29. package/dist/split/features/diagnostics.js +1 -1
  30. package/dist/split/features/diagnostics.js.map +1 -1
  31. package/dist/split/features/formats.js +1 -1
  32. package/dist/split/features/list-keys.js +1 -1
  33. package/dist/split/features/native-commands.js +1 -1
  34. package/dist/split/features/recursion.js +1 -1
  35. package/dist/split/features/scopes.js +1 -1
  36. package/dist/split/features/scopes.js.map +1 -1
  37. package/dist/split/features/ssr.js +1 -1
  38. package/dist/split/features/ssr.js.map +1 -1
  39. package/dist/split/features/temporal.js +1 -1
  40. package/package.json +1 -1
  41. package/dist/split/chunks/chunk-65B2K2XD.js +0 -2
  42. package/dist/split/chunks/chunk-GU7IAIWW.js +0 -2
  43. package/dist/split/chunks/chunk-GU7IAIWW.js.map +0 -1
  44. package/dist/split/chunks/chunk-IWY3I2DG.js +0 -2
  45. package/dist/split/chunks/chunk-IWY3I2DG.js.map +0 -1
  46. package/dist/split/chunks/chunk-MZVF2WEY.js +0 -2
  47. package/dist/split/chunks/chunk-PTFWXISY.js +0 -2
  48. package/dist/split/chunks/chunk-SANIIUS2.js +0 -2
  49. package/dist/split/chunks/chunk-UE6JK2ZA.js +0 -2
  50. /package/dist/split/chunks/{chunk-JCQTA35B.js.map → chunk-AKTGQ6TC.js.map} +0 -0
  51. /package/dist/split/chunks/{chunk-UE6JK2ZA.js.map → chunk-E3XSBW55.js.map} +0 -0
  52. /package/dist/split/chunks/{chunk-FFXJVUTT.js.map → chunk-JJYM4JVZ.js.map} +0 -0
  53. /package/dist/split/chunks/{chunk-SJRJALR4.js.map → chunk-LPJFTEVT.js.map} +0 -0
  54. /package/dist/split/chunks/{chunk-FT7OW7CG.js.map → chunk-R6RHIIR4.js.map} +0 -0
package/README.ja.md CHANGED
@@ -193,9 +193,9 @@
193
193
  <script type="importmap">
194
194
  {
195
195
  "imports": {
196
- "@wcstack/state/core": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.10.1/dist/split/core.js",
197
- "@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.10.1/dist/split/features/temporal.js",
198
- "@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.10.1/dist/split/features/scopes.js"
196
+ "@wcstack/state/core": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.11.0/dist/split/core.js",
197
+ "@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.11.0/dist/split/features/temporal.js",
198
+ "@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.11.0/dist/split/features/scopes.js"
199
199
  }
200
200
  }
201
201
  </script>
@@ -232,6 +232,14 @@
232
232
  `formats` のフィルタは入っていないと報告されました。`dist/split/features/` のアドオンのファイル名には
233
233
  コンテンツハッシュが入りません。自前で配信するなら、URL に版を入れ、配備の後も古いものを残してください。
234
234
 
235
+ **1 ページにパッケージのコピーは 1 つ。** ページが先に評価したコピーが `<wcs-state>` を定義し、2 つ目の
236
+ コピーは何もしません。4.11 から、先のコピーが別の版のとき — 上げた後に新しいものの横に残った
237
+ 古い `<script>` — `/auto` と `.` の `bootstrapState` がそう言います: `[wcs/package-twice] <wcs-state> is
238
+ already defined by another copy of @wcstack/state that does not name its version; this copy,
239
+ @wcstack/state@4.11.0, does nothing. Load each package once.` 同じ版のコピー(同じリリースの `wcstack/auto` のバンドルと `@wcstack/state/auto`)は何も
240
+ 言わず、`/core` は確かめません(core のサイズの予算)。どのパッケージも同じ規則に従います:
241
+ [docs/timing-and-firing-contract.ja.md §24](../../docs/timing-and-firing-contract.ja.md)。
242
+
235
243
  | エントリ | 足されるもの |
236
244
  |---|---|
237
245
  | `@wcstack/state/core` | バインディングの本体: `data-wcs`・`{{ }}` とコメントバインディング・`for` / `if`・パス getter・イベント・`$command` / `$on`・条件の 10 個のフィルタ(`eq` `ne` `not` `lt` `le` `gt` `ge` `truthy` `falsy` `boolean`)・`bootstrapState`・`installFeatures` |
@@ -261,7 +269,7 @@
261
269
  ページが名指したアドオンを、同じビルドから読み込みます。
262
270
 
263
271
  ```html
264
- <script type="module" src="https://cdn.jsdelivr.net/npm/@wcstack/state@4.10.1/dist/split/auto.js"></script>
272
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@wcstack/state@4.11.0/dist/split/auto.js"></script>
265
273
  <wcs-state features="scopes diagnostics">
266
274
  <script type="module">
267
275
  export default {
@@ -765,7 +773,7 @@ export default {
765
773
  - spread 右辺へのフィルタ(`...: target|filter`)はエラー
766
774
  - 右辺パスの途中に `*` を含めても OK(例:`...: stores.*.fetch`)
767
775
  - 右辺は素のツリーパス(`...: fetchX`、途中の `*` も可)
768
- - カスタム要素クラスが未登録の場合、`customElements.whenDefined(tag)` 解決時に遅延展開される(autoloader による遅延ロードに対応)。`for:` / `if:` のテンプレートの中でも同じ — 行や枝はすぐに描かれ、クラスが定義されたらそれぞれの行の要素をその行の値で展開する。定義前に消えた行・閉じた枝は展開せず、何も生かし続けない。待つのはその要素を定義するレジストリ —— 要素が置かれた shadow root のスコープ付きレジストリも含む
776
+ - カスタム要素クラスが未登録の場合、`customElements.whenDefined(tag)` 解決時に遅延展開される(autoloader による遅延ロードに対応)。`for:` / `if:` のテンプレートの中でも同じ — 行や枝はすぐに描かれ、クラスが定義されたらそれぞれの行の要素をその行の値で展開する。定義前に消えた行・閉じた枝は展開せず、何も生かし続けない。待つのはその要素を定義するレジストリ —— 要素が置かれた shadow root のスコープ付きレジストリも含む。待つ時間に限りは無い: 定義が来なければ、スプレッドはエラー無しに展開されないまま —— 4.11 から、`diagnostics` アドオンがそれを報告する([カスタム要素の束縛はすべて定義を待つ](#inputs-と-attribute-ヒント))
769
777
  - `wcBindable` 宣言**のない**要素はエラー(`[wcs/spread-no-bindable]`。明示配線で書いてください)— ネイティブ要素は定義を待たずに即座にエラーになる。spread は何を展開すべきかを契約から読み取るため
770
778
 
771
779
  **Composite shell**(wc-bindable Composition Profile)はそのままサポートされます:composite shell は標準の `target.constructor.wcBindable` を通じて synthesized declaration を露出するため、`"s3.progress"` のような composed name はフラットな要素メンバーキーとして扱われます。state を composed 構造に合わせて (`{ s3: { progress: 0 } }`) 持てば、`...: pipeline` が自動的に nested state path へ展開されます。
@@ -819,6 +827,15 @@ export default {
819
827
 
820
828
  getter はコピーを返しますが、書き込みは両側に届きます。元のパスを通した書き込み(`this["items.1.name"] = "z"`)はコピー側の行に、行を通した書き込み(`firstTwo.1.name`、または行の中のバインディング)は元のリストの行に届きます。getter の配列が書いたオブジェクトを持つので、getter は評価し直されます —— 絞り込み(`todos` の未完了の要素を返す `get shown()` を `for: shown` で描画する場合)なら、チェックボックスが `.done` を書いた行は表示から抜けます。新しいオブジェクトを返す getter(`map`)は、こうした書き込みでは評価し直しません。届かない場合は [同じオブジェクトが 2 つの位置にあるとき](#resolve--明示的なインデックスでのアクセス) を参照してください。
821
829
 
830
+ **`for:` が取るのは配列です。** それ以外の値 —— 数、文字列、オブジェクト(`items` を渡すつもりだった応答 `{ items: [...] }`)—— は行を 1 つも描かず、何も失敗しません。`null` と `undefined` は意図して何も描きません: 後から届くリストです。4.11 から、`diagnostics` アドオンは、配列でも `null` / `undefined` でもない値を、リストのパスごとに 1 回警告します:
831
+
832
+ ```
833
+ [@wcstack/state] [wcs/binding-type-expectation] "for: count" needs an array, not a number (3): it renders
834
+ no rows (null and undefined render none on purpose). Validate statically: npx @wcstack/lint <file>.
835
+ ```
836
+
837
+ 見るのは、リストの最初の値、配列を置き換えた後の値、そして行の外の `for:` では後のすべての値です。行の中のリストで、`null` が後から配列でない値に置き換わったものは見ません。lint は、値の型が見えるところ —— state のリテラルなど —— で同じ誤りを同じコードで報告します(`stateSchema` があれば `wcs/path-type-mismatch`)。後から `createStateAsync` で書かれた値や fetch の値は見えません。
838
+
822
839
  #### `$listKeys` — 再取得された行の同一性
823
840
 
824
841
  行がバインディングの管理外の DOM 状態(フォーカス、IME 変換中の文字列、`<details>` の開閉、行内スクロール位置、`<canvas>` の描画内容、`<video>` の再生位置)を持つ場合、行の再構築でそれらは失われます。キーを宣言すると、リフレッシュをまたいで行を同定できます:
@@ -2171,7 +2188,7 @@ class MyFetcher extends HTMLElement {
2171
2188
  - `methodName` は `wcBindable.commands` に(`name` で)現れること(ネイティブ要素では [ネイティブ要素のコマンド](#ネイティブ要素のコマンド) の表にあること)
2172
2189
  - `<tokenName>` は `$commandTokens` で宣言されていること(`$command.typo` は `[wcs/token-undeclared]` を throw する)
2173
2190
 
2174
- カスタム要素はクラスが定義された時点で検査され、ネイティブ要素はその場で検査されます —— アドオンが入っていれば(`@wcstack/state` と `/auto` には含まれています)[native-commands](#ネイティブ要素のコマンド) の表と照合し、入っていなければ拒否します(`[wcs/token-misconfigured]` `#1202`)。ページのバインド中に見つかった違反は `<wcs-state>` の初期化を失敗させます。`for:` / `if:` の行の中で見つかった違反や、ページのバインド後にクラスが定義されて見つかった違反は、そのバインディング 1 本の失敗として `$errorCallback` に届き、行はそのまま構築されます([初期化の失敗](#初期化の失敗))。
2191
+ カスタム要素はクラスが定義された時点で検査され(それまでメソッドは購読されず、定義が来なければ購読されないままです —— [カスタム要素の束縛はすべて定義を待つ](#inputs-と-attribute-ヒント))、ネイティブ要素はその場で検査されます —— アドオンが入っていれば(`@wcstack/state` と `/auto` には含まれています)[native-commands](#ネイティブ要素のコマンド) の表と照合し、入っていなければ拒否します(`[wcs/token-misconfigured]` `#1202`)。ページのバインド中に見つかった違反は `<wcs-state>` の初期化を失敗させます。`for:` / `if:` の行の中で見つかった違反や、ページのバインド後にクラスが定義されて見つかった違反は、そのバインディング 1 本の失敗として `$errorCallback` に届き、行はそのまま構築されます([初期化の失敗](#初期化の失敗))。
2175
2192
 
2176
2193
  ### Token API
2177
2194
 
@@ -2673,6 +2690,7 @@ class MyChip extends HTMLElement {
2673
2690
 
2674
2691
  - 3.x はプロパティの後に属性にも書いていました(`String(value)`、オブジェクトは JSON)。setter で反映する要素は影響を受けません。setter を持たず、binder が属性を書くことに頼っていた要素には、setter が要ります(移行ガイド §3.4)。
2675
2692
  - ネイティブ HTML 要素は `inputs` を完全に無視します —— 宣言が効くのは `static wcBindable` を公開するカスタム要素だけです。
2693
+ - **カスタム要素の束縛は、wc-bindable のメンバーに限らず、すべてその要素の定義を待ちます**: どんな名前のプロパティの束縛も(`textContent:`、`text:`、`title:`、`value:` …)、スプレッドも、`command.` と `eventToken.` も。要素を定義するレジストリで `customElements.whenDefined(tag)` が解決するまで、どれも当たりません: 未定義の `<my-badge data-wcs="attr.cat: cat; textContent: label">` は、`cat` 属性は付き、中身は空のままです。定義の有無に関わらずすぐに当たるのは、`attr.*`、`class.*`、`style.*`、`html:` / `innerHTML:`、`radio:` / `checkbox:` と、`on*:` のリスナーです。待つ時間に限りは無いので、来ない定義 —— ページが読み込まなかったスクリプト(`@wcstack/upload/auto` の書き忘れ)、失敗した autoloader のパス —— は、それらの束縛をエラー無しに当たらないままにします。4.11 から、`diagnostics` アドオンがそれを報告します: ページがそろってから —— `load`、または state の `$connectedCallback` がそれより後ならその時点から —— 5 秒後に、`<wcs-state>` がバインドするルートを 1 回見て、そうした束縛を持ちまだ定義されていない要素を、タグごとに 1 回警告します(`[wcs/element-undefined]`)。`wcs-*` のタグには、それを定義する wcstack のパッケージ(の `/auto`)を読み込むよう言いますが、パッケージの名前は挙げません —— `wcs-<名前>` が `@wcstack/<名前>` とは限らないからです(`<wcs-ws>` は `@wcstack/websocket`、`<wcs-link>` は router)。ほかのタグには、それを定義するスクリプトを読み込むよう言います。見るのはページの要素と、その時点で描かれている `for:` / `if:` の行と枝です。後から加わった要素と、コンポーネント自身の state が描く行は見ません。
2676
2694
  - `for:` の行と `if:` の枝は、挿入する前にバインドします。その中のカスタム要素は、`connectedCallback` が走る前に入力(`element.value = …`)を受け取ります。ページの直下では、要素は先に接続されています。既定値から描く `connectedCallback` は、setter が受け取ったものを上書きします —— 今のプロパティから描いてください(外部の評価では、金額欄が、まだ採用していない入力をこれで失いました。ページを 3.x から 4.0 に上げたときです)。もう 1 つの早い書き込み、要素が定義される前に代入されたプロパティは、[I/O ノードのガイドライン](../../docs/async-io-node-guidelines.ja.md)の `upgradeProperties` の手順が扱います。
2677
2695
 
2678
2696
  ## コンポーネント機構の選び方
@@ -2893,6 +2911,8 @@ export default {
2893
2911
  ```
2894
2912
 
2895
2913
  フックはバッチの後(`$renderedCallback` の後)に走り、await されず、フック内で throw しても console に報告されるだけで drain は壊れません。DevTools にはフックの有無に関わらず全失敗が `state:binding-apply-error` として届きます。ボリューム(`<wcs-state mount>`)は実行しません(`console.warn`)。マウントされたコンポーネントのフックは、そのコンポーネント自身のバインディングの失敗を受け取ります。`$watch` ハンドラの失敗(別途隔離・報告)、throw した `$renderedCallback`(`console.error` で報告)、失敗した `$connectedCallback`([初期化の失敗](#初期化の失敗))は対象外です。
2914
+
2915
+ 4.7 から、エンジンが報告するエラーは `reportError()` を通ってページのエラーハンドラ(`window.onerror`、`error` リスナー)に届きます([本番でのエラーの報告](#本番でのエラーの報告))—— ただし `$errorCallback` を宣言した state のバインディングの失敗は別です: それはフックにだけ届き、ページのエラーハンドラには届きません。エラーの報告係にバインディングの失敗も見せたいページは、フックから渡します: `$errorCallback(error, { path }) { this.loadError = …; reportError(error); }`。(4.2 向けに書かれ、上の例のようにバインディングの失敗を描いていた state は、アップグレードの後、ページが頼っていたエラーの報告係から、その失敗をすべて遠ざけていました —— 継ぎ目アプリ `homeroom`。)
2896
2916
  - Web Component を使用している場合は、コンポーネント側に `async $stateReadyCallback(stateProp)` を定義おくことで、`bind-component` でバインドした状態が利用可能になった瞬間にフックとして呼び出されます。
2897
2917
 
2898
2918
  ## 遷移アニメーション
@@ -3168,7 +3188,7 @@ window.addEventListener("error", (e) => {
3168
3188
  });
3169
3189
  ```
3170
3190
 
3171
- `$errorCallback` は、これまでどおり state のバインディングの失敗を代わりに受けます(描く、自分で報告する)。`reportError()` の無い環境 — happy-dom、Node、`@wcstack/server` — では、これまでどおり `console.error` です。警告は `console.warn` のままです。
3191
+ `$errorCallback` は、これまでどおり state のバインディングの失敗を代わりに受けます(描く、自分で報告する —— フックの中で `reportError(error)` を呼べば、ページのエラーハンドラにも渡ります)。`reportError()` の無い環境 — happy-dom、Node、`@wcstack/server` — では、これまでどおり `console.error` です。警告は `console.warn` のままです。
3172
3192
 
3173
3193
  ### 描画中の書き込みは無限ループになりません
3174
3194
 
package/README.md CHANGED
@@ -194,9 +194,9 @@ leaves features out, it can compose them instead:
194
194
  <script type="importmap">
195
195
  {
196
196
  "imports": {
197
- "@wcstack/state/core": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.10.1/dist/split/core.js",
198
- "@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.10.1/dist/split/features/temporal.js",
199
- "@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.10.1/dist/split/features/scopes.js"
197
+ "@wcstack/state/core": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.11.0/dist/split/core.js",
198
+ "@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.11.0/dist/split/features/temporal.js",
199
+ "@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.11.0/dist/split/features/scopes.js"
200
200
  }
201
201
  }
202
202
  </script>
@@ -234,6 +234,15 @@ accepted it: the add-on filled the other copy's slots, so a `$watch` never ran,
234
234
  filter of `formats` was reported missing. The add-on files under `dist/split/features/` are not
235
235
  content-hashed: self-hosted, put the version in their URL and keep the old ones after a deploy.
236
236
 
237
+ **One copy of the package per page.** The copy a page evaluates first defines `<wcs-state>`, and a
238
+ second copy does nothing. Since 4.11, `/auto` and `.`'s `bootstrapState` say so
239
+ when the first copy is of another version — an old `<script>` left beside the new one after an
240
+ upgrade: `[wcs/package-twice] <wcs-state> is already defined by another copy of @wcstack/state that does
241
+ not name its version; this copy, @wcstack/state@4.11.0, does nothing. Load each package once.` A copy of the same version stays
242
+ silent (the `wcstack/auto` bundle beside `@wcstack/state/auto` of one release), and `/core` does not
243
+ check (the core's size budget). Every package follows the same rule:
244
+ [docs/timing-and-firing-contract.md §24](../../docs/timing-and-firing-contract.md).
245
+
237
246
  | Entry | What it adds |
238
247
  |---|---|
239
248
  | `@wcstack/state/core` | The binding engine: `data-wcs`, `{{ }}` and comment bindings, `for` / `if`, path getters, events, `$command` / `$on`, the 10 condition filters (`eq` `ne` `not` `lt` `le` `gt` `ge` `truthy` `falsy` `boolean`), `bootstrapState`, `installFeatures` |
@@ -263,7 +272,7 @@ receptacle the owning add-on fills on install).
263
272
  core, and the add-ons the page names from the same build.
264
273
 
265
274
  ```html
266
- <script type="module" src="https://cdn.jsdelivr.net/npm/@wcstack/state@4.10.1/dist/split/auto.js"></script>
275
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@wcstack/state@4.11.0/dist/split/auto.js"></script>
267
276
  <wcs-state features="scopes diagnostics">
268
277
  <script type="module">
269
278
  export default {
@@ -773,7 +782,7 @@ Runtime reads `customClass.wcBindable.properties + inputs` and expands each name
773
782
  - Filters on the spread target (`...: target|filter`) are rejected.
774
783
  - The right-hand path may contain `*` anywhere (e.g. `...: stores.*.fetch`).
775
784
  - The right-hand side is a plain tree path (`...: fetchX` or `...: stores.*.fetch`).
776
- - If the custom element class is not yet registered, expansion is deferred until `customElements.whenDefined(tag)` resolves — autoloader-style late registration is supported. This holds inside `for:` / `if:` templates too: the rows and the branch render right away, and each row's element is expanded with that row's values once the class is defined. A row removed, or a branch closed, before the definition is not expanded, and holds nothing alive. The wait is on the registry that defines the element — a scoped registry of the shadow root it is placed in included.
785
+ - If the custom element class is not yet registered, expansion is deferred until `customElements.whenDefined(tag)` resolves — autoloader-style late registration is supported. This holds inside `for:` / `if:` templates too: the rows and the branch render right away, and each row's element is expanded with that row's values once the class is defined. A row removed, or a branch closed, before the definition is not expanded, and holds nothing alive. The wait is on the registry that defines the element — a scoped registry of the shadow root it is placed in included. The wait has no limit: a definition that never comes leaves the spread unexpanded, without an error — since 4.11 the `diagnostics` add-on reports it ([every binding on a custom element waits for its definition](#inputs-and-the-attribute-hint)).
777
786
  - Elements **without** a `wcBindable` declaration are rejected (`[wcs/spread-no-bindable]`; write bindings explicitly) — a native element at once, without waiting for a definition. Spread requires the contract to know what to expand.
778
787
 
779
788
  **Composite shells** (wc-bindable Composition Profile) are supported transparently: a composite shell exposes its synthesized declaration through the standard `target.constructor.wcBindable` surface, and composed names like `"s3.progress"` are kept as flat element member keys. Mirror the composed structure in state (`{ s3: { progress: 0 } }`) and `...: pipeline` expands into nested state paths automatically.
@@ -827,6 +836,15 @@ The exception is data that arrives as **freshly created objects** — `fetch(...
827
836
 
828
837
  The getter returns a copy, and a write reaches both sides: a write through the original path (`this["items.1.name"] = "z"`) reaches the copy's row, and a write through the row (`firstTwo.1.name`, or a binding inside the row) reaches the original list's row. The getter is evaluated again, because its array holds the written object — with a filter (`get shown()` returning the undone items of `todos`, rendered with `for: shown`), a row whose checkbox writes `.done` leaves the view. A getter that returns new objects (`map`) is not evaluated again by such a write. See [One object at two positions](#resolve--access-by-explicit-index) for what is not reached.
829
838
 
839
+ **`for:` takes an array.** Any other value — a number, a string, an object (a response `{ items: [...] }` where its `items` was meant) — renders no rows, and nothing fails. `null` and `undefined` render none on purpose: a list that arrives later. Since 4.11 the `diagnostics` add-on warns once per list path about a value that is neither an array nor `null` / `undefined`:
840
+
841
+ ```
842
+ [@wcstack/state] [wcs/binding-type-expectation] "for: count" needs an array, not a number (3): it renders
843
+ no rows (null and undefined render none on purpose). Validate statically: npx @wcstack/lint <file>.
844
+ ```
845
+
846
+ It looks at a list's first value, at a later value that replaces an array, and, for a `for:` outside any row, at every later value. A list inside a row whose `null` is later replaced by something that is not an array is not looked at. The lint reports the same mistake under the same code (`wcs/path-type-mismatch` with a `stateSchema`) where it can see the value's type, as in the state's literal — not a value written later through `createStateAsync` or from a fetch.
847
+
830
848
  #### `$listKeys` — identity for refetched rows
831
849
 
832
850
  When rows carry DOM state the bindings do not own — focus, an in-flight IME composition, `<details>` open state, inner scroll position, `<canvas>` contents, `<video>` playback — rebuilding the rows loses it. Declare a key so the framework can recognize rows across a refresh:
@@ -2183,7 +2201,7 @@ Validation rules (enforced at binding time):
2183
2201
  - `methodName` must appear (by `name`) in `wcBindable.commands` (on a native element: in the table of [Native Element Commands](#native-element-commands))
2184
2202
  - `<tokenName>` must be declared in `$commandTokens` (`$command.typo` throws `[wcs/token-undeclared]`)
2185
2203
 
2186
- A custom element is checked once its class is defined; a native element at once — against the [native-commands](#native-element-commands) table where the add-on is installed (`@wcstack/state` and `/auto` include it), and refused otherwise (`[wcs/token-misconfigured]` `#1202`). A violation found while the page is bound fails the `<wcs-state>`'s initialization; one found inside a `for:` / `if:` row, or when a class is defined after the page was bound, fails that one binding, which goes to `$errorCallback`, and the row is still built ([Initialization failures](#initialization-failures)).
2204
+ A custom element is checked once its class is defined — its method is not subscribed before that, and never when no definition comes ([every binding on a custom element waits for its definition](#inputs-and-the-attribute-hint)); a native element at once — against the [native-commands](#native-element-commands) table where the add-on is installed (`@wcstack/state` and `/auto` include it), and refused otherwise (`[wcs/token-misconfigured]` `#1202`). A violation found while the page is bound fails the `<wcs-state>`'s initialization; one found inside a `for:` / `if:` row, or when a class is defined after the page was bound, fails that one binding, which goes to `$errorCallback`, and the row is still built ([Initialization failures](#initialization-failures)).
2187
2205
 
2188
2206
  ### Token API
2189
2207
 
@@ -2685,6 +2703,7 @@ Notes:
2685
2703
 
2686
2704
  - 3.x also wrote the attribute after the property (`String(value)`, JSON for an object). An element that reflects in its setter is unaffected; one that has no setter and relied on the binder to write its attribute needs a setter now (Migration guide, §3.4).
2687
2705
  - Native HTML elements ignore `inputs` entirely — the declaration only applies to custom elements that expose `static wcBindable`.
2706
+ - **Every binding on a custom element waits for its definition**, not only its wc-bindable members: a property binding of any name (`textContent:`, `text:`, `title:`, `value:` …), a spread, `command.` and `eventToken.`. None of them is applied before `customElements.whenDefined(tag)` resolves in the registry that defines the element: an undefined `<my-badge data-wcs="attr.cat: cat; textContent: label">` gets its `cat` attribute and stays empty. What is applied at once, defined or not: `attr.*`, `class.*`, `style.*`, `html:` / `innerHTML:`, `radio:` / `checkbox:`, and the `on*:` listeners. The wait has no limit, so a definition that never comes — a script the page did not load (`@wcstack/upload/auto` left out), an autoloader path that fails — leaves those bindings unapplied without an error. Since 4.11 the `diagnostics` add-on reports it: 5 s after the page was ready — its `load`, or the state's `$connectedCallback` when that is later — it looks once at the root the `<wcs-state>` binds, and warns once per tag about an element with such a binding that is still not defined (`[wcs/element-undefined]`). For a `wcs-*` tag it says to load the wcstack package that defines it (its `/auto`) without naming one — `wcs-<name>` is not always `@wcstack/<name>` (`<wcs-ws>` is `@wcstack/websocket`, `<wcs-link>` the router) — for another tag the script that defines it. It sees the page's elements and the `for:` / `if:` rows and branches rendered at that moment; an element added later, and the rows a component's own state renders, are not looked at.
2688
2707
  - A `for:` row or an `if:` branch is bound before it is inserted: a custom element in it receives its inputs (`element.value = …`) before its `connectedCallback` runs. At page level the element is connected first. A `connectedCallback` that renders from its defaults overwrites what the setters received — render from the current properties instead. (An outside evaluation's amount field lost an entry it had not accepted yet this way when its page moved from 3.x to 4.0.) The other early write, a property set before the element is defined, is the [I/O node guidelines](../../docs/async-io-node-guidelines.md)' `upgradeProperties` step.
2689
2708
 
2690
2709
  ## Choosing a Component Mechanism
@@ -2905,6 +2924,8 @@ All hooks except `$disconnectedCallback` support `async` — you can use `async/
2905
2924
  ```
2906
2925
 
2907
2926
  The hook runs after the batch (after `$renderedCallback`), is not awaited, and an exception thrown inside it is reported to the console without breaking the drain. DevTools still receives every failure as `state:binding-apply-error` whether or not the hook exists. A volume (`<wcs-state mount>`) does not run it (`console.warn`); a mounted component's receives the failures of the component's own bindings. It does not cover `$watch` handlers (isolated and reported separately), a `$renderedCallback` that throws (reported with `console.error`), or a `$connectedCallback` that fails ([Initialization failures](#initialization-failures)).
2927
+
2928
+ Since 4.7 the errors the engine reports reach the page's error handlers (`window.onerror`, an `error` listener) through `reportError()` ([Reporting errors in production](#reporting-errors-in-production)) — except a binding failure of a state that declares `$errorCallback`: it goes to the hook only, and the page's error handlers never see it. A page whose error reporter must see binding failures too hands them on from the hook: `$errorCallback(error, { path }) { this.loadError = …; reportError(error); }`. (A state written for 4.2 that rendered its binding failures this way, as above, kept every one of them from the error reporter its page relied on after the upgrade — the `homeroom` seam app.)
2908
2929
  - In Web Components, define `async $stateReadyCallback(stateProp)` to receive a hook when the bound state becomes available via `bind-component`
2909
2930
 
2910
2931
  ## Transition animations
@@ -3181,7 +3202,7 @@ window.addEventListener("error", (e) => {
3181
3202
  });
3182
3203
  ```
3183
3204
 
3184
- `$errorCallback` still takes a state's binding failures instead (render them, report them yourself). Where there is no `reportError()` — happy-dom, Node, `@wcstack/server` — the report is `console.error`, as before. Warnings stay `console.warn`.
3205
+ `$errorCallback` still takes a state's binding failures instead (render them, report them yourself — `reportError(error)` in the hook hands them to the page's error handlers too). Where there is no `reportError()` — happy-dom, Node, `@wcstack/server` — the report is `console.error`, as before. Warnings stay `console.warn`.
3185
3206
 
3186
3207
  ### A write made while rendering cannot loop forever
3187
3208