@wcstack/state 4.8.0 → 4.10.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 +38 -16
  2. package/README.md +38 -16
  3. package/dist/auto.min.js +1 -1
  4. package/dist/auto.min.js.map +1 -1
  5. package/dist/index.esm.js +1 -1
  6. package/dist/index.esm.js.map +1 -1
  7. package/dist/manifest.esm.js +29 -31
  8. package/dist/parser.esm.js +5 -2
  9. package/dist/split/auto.js +1 -1
  10. package/dist/split/chunks/chunk-65B2K2XD.js +2 -0
  11. package/dist/split/chunks/{chunk-GQTEYSRZ.js.map → chunk-65B2K2XD.js.map} +1 -1
  12. package/dist/split/chunks/{chunk-IBGHHABS.js → chunk-FFXJVUTT.js} +2 -2
  13. package/dist/split/chunks/{chunk-QMOIMPIJ.js → chunk-FT7OW7CG.js} +2 -2
  14. package/dist/split/chunks/chunk-GU7IAIWW.js +2 -0
  15. package/dist/split/chunks/chunk-GU7IAIWW.js.map +1 -0
  16. package/dist/split/chunks/chunk-IWY3I2DG.js +2 -0
  17. package/dist/split/chunks/{chunk-P3EXTBXC.js → chunk-JCQTA35B.js} +2 -2
  18. package/dist/split/chunks/chunk-MZVF2WEY.js +2 -0
  19. package/dist/split/chunks/{chunk-PKEAYPFR.js → chunk-PTFWXISY.js} +2 -2
  20. package/dist/split/chunks/{chunk-BCL75PFH.js → chunk-SJRJALR4.js} +2 -2
  21. package/dist/split/chunks/chunk-TB52U4QV.js +2 -0
  22. package/dist/split/chunks/chunk-TB52U4QV.js.map +1 -0
  23. package/dist/split/chunks/chunk-UE6JK2ZA.js +2 -0
  24. package/dist/split/chunks/chunk-UE6JK2ZA.js.map +1 -0
  25. package/dist/split/core.js +1 -1
  26. package/dist/split/features/devtools.js +1 -1
  27. package/dist/split/features/diagnostics.js +1 -1
  28. package/dist/split/features/diagnostics.js.map +1 -1
  29. package/dist/split/features/formats.js +1 -1
  30. package/dist/split/features/formats.js.map +1 -1
  31. package/dist/split/features/list-keys.js +1 -1
  32. package/dist/split/features/native-commands.js +1 -1
  33. package/dist/split/features/recursion.js +1 -1
  34. package/dist/split/features/scopes.js +1 -1
  35. package/dist/split/features/ssr.js +1 -1
  36. package/dist/split/features/temporal.js +1 -1
  37. package/dist/wcs-manifest.json +9 -6
  38. package/package.json +1 -1
  39. package/dist/split/chunks/chunk-DRSAQE4E.js +0 -2
  40. package/dist/split/chunks/chunk-DRSAQE4E.js.map +0 -1
  41. package/dist/split/chunks/chunk-GQTEYSRZ.js +0 -2
  42. package/dist/split/chunks/chunk-M3BJN6SQ.js +0 -2
  43. package/dist/split/chunks/chunk-M3BJN6SQ.js.map +0 -1
  44. package/dist/split/chunks/chunk-NITPUCPA.js +0 -2
  45. package/dist/split/chunks/chunk-T3KW5DZD.js +0 -2
  46. package/dist/split/chunks/chunk-T3KW5DZD.js.map +0 -1
  47. package/dist/split/chunks/chunk-XXVJ6J2N.js +0 -2
  48. /package/dist/split/chunks/{chunk-IBGHHABS.js.map → chunk-FFXJVUTT.js.map} +0 -0
  49. /package/dist/split/chunks/{chunk-QMOIMPIJ.js.map → chunk-FT7OW7CG.js.map} +0 -0
  50. /package/dist/split/chunks/{chunk-XXVJ6J2N.js.map → chunk-IWY3I2DG.js.map} +0 -0
  51. /package/dist/split/chunks/{chunk-P3EXTBXC.js.map → chunk-JCQTA35B.js.map} +0 -0
  52. /package/dist/split/chunks/{chunk-NITPUCPA.js.map → chunk-MZVF2WEY.js.map} +0 -0
  53. /package/dist/split/chunks/{chunk-PKEAYPFR.js.map → chunk-PTFWXISY.js.map} +0 -0
  54. /package/dist/split/chunks/{chunk-BCL75PFH.js.map → chunk-SJRJALR4.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.8.0/dist/split/core.js",
197
- "@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.8.0/dist/split/features/temporal.js",
198
- "@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.8.0/dist/split/features/scopes.js"
196
+ "@wcstack/state/core": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.10.0/dist/split/core.js",
197
+ "@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.10.0/dist/split/features/temporal.js",
198
+ "@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.10.0/dist/split/features/scopes.js"
199
199
  }
200
200
  }
201
201
  </script>
@@ -261,7 +261,7 @@
261
261
  ページが名指したアドオンを、同じビルドから読み込みます。
262
262
 
263
263
  ```html
264
- <script type="module" src="https://cdn.jsdelivr.net/npm/@wcstack/state@4.8.0/dist/split/auto.js"></script>
264
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@wcstack/state@4.10.0/dist/split/auto.js"></script>
265
265
  <wcs-state features="scopes diagnostics">
266
266
  <script type="module">
267
267
  export default {
@@ -1235,7 +1235,7 @@ getter の例外は握り潰されません。評価された場所(バイン
1235
1235
  | **setter の中の読み取りは追跡しない。** setter は命令的な代入であって派生ではないので、その中で読んだものは何の依存にもならない | 何を書くかを `this.a` を読んで決める setter は、`a` が変わっても再実行されない。再実行されるのは getter だけ |
1236
1236
  | **同値ガードはプリミティブにだけ効く。** 現在値と `Object.is` で等しいプリミティブの書き込みはキューに入る前に落とされる。オブジェクト・配列の書き込みは同じ参照でも必ず通る | 同じ文字列を再代入しても何も起きない。同じオブジェクトを再代入するとバインディングと `$watch` が再発火する(`$behavior.sameValueGuard`。`semantics: "event"` のプロパティはどちらにせよ対象外)。一覧は例外で、行が配列の要素に合わせ直されるのは**新しい**配列を代入したときだけ。同じ配列の再代入や `$postUpdate` は、ある行の下を読み直すが、行を追加・削除・置き換えしない: `cards[i] = card; this.cards = cards` は古いカードを出し続ける。新しい配列を代入する(`this.cards = cards.with(i, card)`、`[...cards, x]`)。lint はその場の変更を報告する(`wcs/array-index-assign`、`wcs/array-mutation`) |
1237
1237
 
1238
- 規則 1 は静的解析で捕まえられる唯一の規則です。getter が `this.form.name` を読んでいて、ドキュメントのどこかで `form.name` を書いている(`value:` バインド・spread・`this["form.name"] = …`)と、`wcs-validate` と VS Code 拡張が `wcs/getter-untracked-read` を報告します。ルートを丸ごと置換するだけの設計(router の params・`$stream` の fold)には出ません。
1238
+ 規則 1 は静的解析で捕まえられる唯一の規則です。getter が `this.form.name` を読んでいて、ドキュメントのどこかで `form.name` を書いている(`value:` バインド・spread・`this["form.name"] = …`)と、`wcs-validate` と VS Code 拡張が `wcs/getter-untracked-read` を報告します。ルートを丸ごと置換するだけの設計(router の params・`$stream` の fold)には出ません。4.9 から、getter が読むパスをページの束縛が書くとき — `<wcs-fetch data-wcs="...: monthFetch">` は `monthFetch.value` を書くので、`this.monthFetch.value` を読む getter は応答を見ない — は **error** で、スクリプトだけが書くときは warning のままです。
1239
1239
 
1240
1240
  `$untracked(fn)` は setter の規則を getter に意図的に適用するもので、`fn` の中の読み取りは追跡されません。`$dependOn(path)` は最初の規則に対する逃げ道です。
1241
1241
 
@@ -1764,13 +1764,27 @@ export default {
1764
1764
 
1765
1765
  | フィルタ | 説明 | 例 |
1766
1766
  |---|---|---|
1767
- | `date(loc?, style?)` | 日付フォーマット | `createdAt\|date` / `createdAt\|date(ja-JP)` / `createdAt\|date(en-US,medium)` → `"Oct 13, 2026"` |
1768
- | `time(loc?, style?)` | 時刻フォーマット | `createdAt\|time` / `createdAt\|time(short)` |
1769
- | `datetime(loc?, style?)` | 日付 + 時刻 | `createdAt\|datetime(en-US)` / `createdAt\|datetime(ja-JP,long)` |
1770
- | `ymd(sep?)` | YYYY-MM-DD(既定は `-`) | `createdAt\|ymd` / `createdAt\|ymd(/)` |
1771
- | `hms(sep?)` | HH:MM:SS(既定は `:`) | `createdAt\|hms` / `createdAt\|hms(-)` |
1767
+ | `date(loc?, style?, zone?)` | 日付フォーマット(`zone?` は 4.10 から) | `createdAt\|date` / `createdAt\|date(ja-JP)` / `createdAt\|date(en-US,medium)` → `"Oct 13, 2026"` / `opensAt\|date(ja-JP,full,Asia/Tokyo)` |
1768
+ | `time(loc?, style?, zone?)` | 時刻フォーマット(`zone?` は 4.10 から) | `createdAt\|time` / `createdAt\|time(short)` / `opensAt\|time(,short,Asia/Tokyo)` |
1769
+ | `datetime(loc?, style?, zone?)` | 日付 + 時刻(`zone?` は 4.10 から) | `createdAt\|datetime(en-US)` / `createdAt\|datetime(ja-JP,long)` / `createdAt\|datetime(,,UTC)` |
1770
+ | `ymd(sep?)` | YYYY-MM-DD(既定は `-`)。実行環境のタイムゾーンの日付 | `createdAt\|ymd` / `createdAt\|ymd(/)` |
1771
+ | `hms(sep?)` | HH:MM:SS(既定は `:`)。実行環境のタイムゾーンの時刻 | `createdAt\|hms` / `createdAt\|hms(-)` |
1772
1772
 
1773
- **5 つとも `Date` を取ります**(タイムスタンプではありません)。他の型は「requires a date value」で拒否されます。値が無いとき(`undefined` / `null`)は、4.7 から何も描きません。JSON から読み込んだ state は数値か ISO 文字列を持つので、束縛ではなく読み出し側で変換してください: `get createdAt() { return new Date(this.createdAtMs); }`。`loc?` の既定はページのロケール([ロケール](#ロケール))で、**適用のたびに**読み直されるため、束縛を組み立てた後に確定したロケールも反映されます(明示引数 `date(ja-JP)` は束縛式の一部なので組み立て時に固定されます)。`style?` は `full`・`long`・`medium`・`short`(Intl の `dateStyle` / `timeStyle`)で、無ければロケールの数字の書式(`2026/10/13`)、書式だけならページのロケールです(`date(medium)`)。`currency` の通貨は束縛を組み立てるときに確かめ、数は `loc` が無ければページのロケールで書きます。複数形などほかの書式は getter で書きます(`Intl.PluralRules`、`Intl.NumberFormat`)。
1773
+ **5 つとも `Date` を取ります**(タイムスタンプではありません)。他の型は「requires a date value」で拒否されます。値が無いとき(`undefined` / `null`)は、4.7 から何も描きません。JSON から読み込んだ state は数値か ISO 文字列を持つので、束縛ではなく読み出し側で変換してください: `get createdAt() { return new Date(this.createdAtMs); }`。日付だけの文字列は瞬間ではなく日です: `new Date("2026-10-29")` は UTC の 0 時で、グリニッジより西では前の日になります —— `new Date(y, m - 1, d)` がページの動く場所のその日です(4.9 から `#29` もそう言います)。`loc?` の既定はページのロケール([ロケール](#ロケール))で、**適用のたびに**読み直されるため、束縛を組み立てた後に確定したロケールも反映されます(明示引数 `date(ja-JP)` は束縛式の一部なので組み立て時に固定されます)。`style?` は `full`・`long`・`medium`・`short`(Intl の `dateStyle` / `timeStyle`)で、無ければロケールの数字の書式(`2026/10/13`)、書式だけならページのロケールです(`date(medium)`)。`currency` の通貨は束縛を組み立てるときに確かめ、数は `loc` が無ければページのロケールで書きます。複数形などほかの書式は getter で書きます(`Intl.PluralRules`、`Intl.NumberFormat`)。
1774
+
1775
+ **タイムゾーン。** `zone?` が無ければ、`date`・`time`・`datetime` は実行環境のタイムゾーンで書きます —— ブラウザでは**読み手の**、SSR ではサーバーのタイムゾーンです。ページのロケールとは関係ありません。ある場所の時刻は、よそから読むと違って見えます: 東京の図書館の 10 月 29 日(木)09:00〜20:00 の開館時間は、ロサンゼルスから読むと `2026年10月28日水曜日`・`17:00:00`・`4:00:00` です。4.10 から、`zone?` で書くタイムゾーンを指定できます —— IANA のタイムゾーン、Intl の `timeZone` です(`Asia/Tokyo`・`America/New_York`・`UTC`)。どこの読み手にもその場所の時刻が見えます:
1776
+
1777
+ ```html
1778
+ <html lang="ja">
1779
+ <!-- state: get opensAt() { return new Date("2026-10-29T09:00:00+09:00"); }、closesAt は同じく 20:00 -->
1780
+ <p>
1781
+ <span data-wcs="textContent: opensAt|date(,full,Asia/Tokyo)"></span>
1782
+ <span data-wcs="textContent: opensAt|time(,short,Asia/Tokyo)"></span>–<span data-wcs="textContent: closesAt|time(,short,Asia/Tokyo)"></span>
1783
+ </p>
1784
+ <!-- 東京から読んでもロサンゼルスから読んでも 2026年10月29日木曜日 9:00–20:00 -->
1785
+ ```
1786
+
1787
+ 空の引数は既定のままです: `time(,short,Asia/Tokyo)` はページのロケール、`date(ja-JP,,Asia/Tokyo)` は数字の書式、`datetime(,,Asia/Tokyo)` はその両方。タイムゾーンだけを書いたときも、`/` を含むか `UTC` なら両方とも既定です(`date(Asia/Tokyo)`、`datetime(UTC)`)。`/` の無いほかのタイムゾーンは 3 つ目に書きます(`date(,,GMT)`)—— 1 つ目に書くとロケールとして読みます。Intl の取らないタイムゾーンは束縛を組み立てるときに拒否され(`#54`)、書式の場所に書いたタイムゾーン(`date(ja-JP,Asia/Tokyo)`)は 3 つ目に書くよう言われます(`#52`)。`ymd` と `hms` はタイムゾーンを取りません: `Date` を実行環境のタイムゾーンで読む(`getFullYear()` 〜 `getSeconds()`)ので、ある場所の日付や時刻にはタイムゾーン付きの `date` / `time` か getter を使います。
1774
1788
 
1775
1789
  ### 真偽値 / デフォルト
1776
1790
 
@@ -2346,6 +2360,7 @@ $on: {
2346
2360
  - `$on` のすべてのキーは `$eventTokens` で宣言済みであること(さもなくば初期化時に throw)
2347
2361
  - 各値は関数であること
2348
2362
  - シグネチャは `(state, event, ...listIndexes)` —— まず DOM の `Event`、続いて内包するループインデックス
2363
+ - ハンドラの中の `this` は undefined なので、`this` を使うハンドラはイベントが届いたときに throw します。4.9 から、`diagnostics` の後付けが、state の宣言のときにハンドラごとに 1 回、イベントより前にそう言います(`[wcs/on-handler-this]`)。lint は error として報告します
2349
2364
 
2350
2365
  ### `eventToken.<property>:` バインディング
2351
2366
 
@@ -2609,7 +2624,7 @@ $renderedCallback(paths) {
2609
2624
  | ハンドラ間 | `$watch` の宣言順 | **宣言を並べ替える** |
2610
2625
  | 同一パスの行間 | `indexes` 昇順 | 固定 |
2611
2626
 
2612
- **機構間の層を動かす唯一のもの**が、`state` 参加者を受け付ける `<wcs-view-transition>` です。バインディング適用 —— したがって `$renderedCallback` —— がフレームで着地する一方、`$watch`・`$stream` restart は state アドレスを消費し DOM を見ないので、drain がキューされた microtask に留まります。タグがある間の順序は `$watch` → `$stream` restart → `$renderedCallback` です。この層を並べ替えるものはページ上でこれ 1 つだけです。[docs/timing-and-firing-contract.ja.md](https://github.com/wcstack/wcstack/blob/main/docs/timing-and-firing-contract.ja.md) §4.3 を参照してください。
2627
+ **機構間の層を動かす唯一のもの**が、`state` 参加者を受け付ける `<wcs-view-transition>` です。バインディング適用 —— したがって `$renderedCallback` —— がフレームで着地します。4.10 から、`$watch` と `$stream` restart はそれを待つので、順序は上のとおりのまま、1 フレーム遅れるだけです。それらが書いたもの(`$watch` が state に写した一覧など)も同じトランジションの中で当たります。4.9 までは drain がキューされた microtask に留まって先に走り(`$watch` → `$stream` restart → `$renderedCallback`)、ログインの後に画面を移す `$watch` が、ルートのガードが読むセッションを `<wcs-storage>` が保存するより先に走っていました。この層を動かすものはページ上でこれ 1 つだけです。[docs/timing-and-firing-contract.ja.md](https://github.com/wcstack/wcstack/blob/main/docs/timing-and-firing-contract.ja.md) §4.3 を参照してください。
2613
2628
 
2614
2629
  主なルール:
2615
2630
 
@@ -2953,7 +2968,7 @@ li {
2953
2968
  | 26 | | `filter <name> requires at least one option` |
2954
2969
  | 27 | | `filter <name> requires a number as option` |
2955
2970
  | 28 | | `filter <name> requires a number value, not <what it was>` (a string adds: `— put |number before |<name>, or convert it where it is read`) |
2956
- | 29 | | `filter <name> requires a date value (a Date), not <what it was>` (a string or a number adds: `— make the Date where it is read: a getter returning new Date(…)`) |
2971
+ | 29 | | `filter <name> requires a date value (a Date), not <what it was>` (a string or a number adds: `— make the Date where it is read: a getter returning new Date(…)`。4.9 から、`"2026-10-01"` のような日付だけの文字列には、`new Date("2026-10-01")` は UTC の真夜中 — グリニッジより西では前の日 — で、`new Date(y, m - 1, d)` がページのある場所のその日だ、とも言う) |
2957
2972
  | 30 | | `filter <name> requires an array value` |
2958
2973
  | 31 | | `init=/sync= modifiers require enableDirectionalInitialSync.` |
2959
2974
  | 32 | | `"<text>": the "@name" selector was removed in v2 — there is a single state tree. …` |
@@ -2976,8 +2991,9 @@ li {
2976
2991
  | 49 | | `<wcs-state src="…"> failed to initialize.` |
2977
2992
  | 50 | | `<wcs-state src="…"> $connectedCallback failed.` |
2978
2993
  | 51 | | `<wcs-state src="…"> $disconnectedCallback failed.` |
2979
- | 52 | | `filter <name> takes a style of full, long, medium or short, not "<style>"` |
2994
+ | 52 | | `filter <name> takes a style of full, long, medium or short, not "<style>"`(4.10 から、書式の場所のタイムゾーン(`/` を含むものと `UTC`)には `— a time zone is the third argument, after the locale and the style, and an empty argument keeps its default: <name>(,,<zone>), <name>(en-US,long,<zone>)` が付く) |
2980
2995
  | 53 | | `filter currency takes an ISO 4217 currency code (JPY, USD, EUR), not "<code>"` |
2996
+ | 54 | | `filter <name> takes an IANA time zone (Asia/Tokyo, America/New_York, UTC), not "<zone>"`(4.10 から) |
2981
2997
  | 101 | `binding-syntax` | `Invalid bindText: "<text>". Missing ':' separator between propPart and statePart.` |
2982
2998
  | 102 | `binding-syntax` | `"<text>": "<keyword>" takes no modifiers or filters on its left side — write "<keyword>:".` |
2983
2999
  | 103 | `binding-syntax` | `"<text>": "else" takes no value — write "else:".` |
@@ -3052,10 +3068,12 @@ dropped. Validate statically: npx @wcstack/lint <file>.
3052
3068
  判定は**過小近似**です。静的に決められない形では黙ります —— 誤検知でページを騒がせないことを優先しているためで、以下はすべて警告しません:
3053
3069
 
3054
3070
  - 親が `null` / `undefined`(初期値 `null` に後から代入する形)
3071
+ - キーの下の空のオブジェクト(`fieldErrors: {}` に後から `this.fieldErrors = { date: "…" }` を代入する形)— 種が `{}` の同じ形。4.10 から。4.9 までは、その下の束縛ごとに報告し、「updates … will be silently dropped」と言っていたが、そうではない: オブジェクトを置き換えれば更新される。lint は 4.9 からその代入を読む
3055
3072
  - どの行にも無い行フィールド —— ただし、行に近い名前がある(打ち間違いらしい。`name` の横の `.nmae`)なら警告します。行はデータで、任意のフィールド(ページが自分の行に付ける `pending` など)はほかの行にはありません。4.4 から。ほかの行にあるフィールドが最初の行に無いだけの場合も警告しません(4.3 から)
3056
3073
  - 途中の getter の戻り値のサブプロパティ
3057
3074
  - まだ読み込み中のボリュームのマウントパス配下のパス —— ボリュームが決着した時点で検査される
3058
3075
  - `$` 始まりの予約名前空間(`$command.*` など)
3076
+ - 要素が書き込むオブジェクトの下のキー —— router の `searchParams: query` は URL のキーを持つので、`query.next` は URL にあるときだけある。4.10 から。4.9 までは、そうした束縛を「silently dropped」と報告していた
3059
3077
 
3060
3078
  裏を返すと、**警告が出ない = 正しい保証にはなりません**。網羅した検査は `npx @wcstack/lint <file>` 側で行ってください。再設定(`setInitialState()`)では、すべてのパスを新しい state に対して検査し直します。
3061
3079
 
@@ -3109,7 +3127,7 @@ this.$getAll("matrix.*.*", [row]);
3109
3127
  - 読めないソース([状態の初期化](#状態の初期化))。
3110
3128
  - 不正な宣言: 削除された名前(`$scan`・`$streams`・`$updatedCallback`。`diagnostics` アドオンがあるとき)、形の誤った `$behavior` / `$features` / `$commandTokens` / `$eventTokens` / `$on`、アドオンが入っていない宣言(`[wcs/feature-not-installed]`)。
3111
3129
  - ページのバインド中に見つかったマークアップの誤り: 構文エラー、未知のフィルタや誤った引数の個数、wildcard-rank のエラー、フィルタ付きの `for:`、テンプレートの中の `outerHTML:`、クラスが定義済みの要素での wc-bindable の設定ミス。走査はそのエラーで止まります: 文書順でそれより前はバインドされ、後はバインドされません。デプロイの前に `npx @wcstack/lint` を実行してください。
3112
- - 別の `<wcs-state>` がすでにバインドしているルートの上の、2 つ目のルート `<wcs-state>`(`#47`)。state ツリーはルートごとに 1 本です。部分木は `mount=` で接ぎ木してください。
3130
+ - 別の `<wcs-state>` がすでにバインドしているルートの上の、2 つ目のルート `<wcs-state>`(`#47`)。state ツリーはルートごとに 1 本です。部分木は `mount=` で接ぎ木してください。バインドするのは、文書で先のものではなく、状態が先にそろったものです: `json=` の state はすぐにそろうので、文書で先の `src=` やインラインの state より先にルートを取り、拒まれるのは後者になり、ページは違う state でバインドされます。4.9 から、`diagnostics` の後付けが、先にバインドしたときに両方を名指しします(`[wcs/second-root]`)。
3113
3131
 
3114
3132
  `for:` / `if:` の行の中で、バインディングの取り付け時に見つかったエラー(未宣言の token、要素が宣言していないメンバ)は、初期化の失敗ではなく、そのバインディングの失敗になります: `$errorCallback` に届き、行はそのまま構築されます。state にないトップレベルのキーへのバインディングは、初期化の失敗ではなく適用の失敗です。初期化に失敗した要素は再始動できません —— `setInitialState()` は throw します(`#14`)。取り除いて新しい要素を作ってください。
3115
3133
 
@@ -3296,6 +3314,10 @@ export default {
3296
3314
  `Intl` が受け付けないタグ(`<html lang="en_US">`)は、`Intl` の中で throw させる代わりに
3297
3315
  1 回報告し(`#48`)、フィルタは `"en"` を使う。`getConfig().locale` は設定された値を返し続ける。
3298
3316
 
3317
+ ロケールはタイムゾーンではない: `<html lang="ja-JP">` のページも、日付と時刻は読み手のタイムゾーンで
3318
+ 日本語に書く。ある場所の時刻は、日付フィルタの 3 つ目にその場所のタイムゾーンを渡して書く
3319
+ (4.10 から — [日付 / 時刻](#日付--時刻))。
3320
+
3299
3321
  **ロケールを後から変えても何も再描画されない。** これは state ではなく
3300
3322
  グローバル設定なので、依存グラフに載らない。フィルタ自体はバインド構築時に
3301
3323
  取り込むのではなく**適用のたびに読む**ので、別の理由で再描画されたバインドは新しい値を
@@ -3336,7 +3358,7 @@ router 自身が `<html lang>` を読む案と、リンク単位の intercept
3336
3358
 
3337
3359
  `<wcs-state>` で組んだページは素の DOM なので、[happy-dom](https://github.com/capricorn86/happy-dom) でヘッドレスにテストできます — ブラウザ不要・ビルド不要・テスト専用 API 不要。レシピは 3 つ、いずれも書いてあるとおりに動きます。
3338
3360
 
3339
- 1 import で済ませたいなら [`@wcstack/testing`](../testing/README.ja.md) がレシピ 1 を `mount()` / `settle()` / `fire()` にまとめています(`<wcs-router>` も待ちます)。以下の素のレシピはそれ無しでも有効です。
3361
+ 1 import で済ませたいなら [`@wcstack/testing`](../testing/README.ja.md) がレシピ 1 を `mount()` / `settle()` / `fire()` にまとめています(`<wcs-router>` も待ちます)。以下の素のレシピはそれ無しでも有効です。自分のファイルにある状態(`<wcs-state src="./state.js">`)は、4.9 から `mount()` の `resolveModule` オプションで読み込めます — happy-dom の下では `src` は `http:` の URL で、Node のモジュールローダはそれを読みません。インラインの状態は、Node の下では相対モジュールを `import` できません。
3340
3362
 
3341
3363
  ### 1. vitest + happy-dom
3342
3364
 
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.8.0/dist/split/core.js",
198
- "@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.8.0/dist/split/features/temporal.js",
199
- "@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.8.0/dist/split/features/scopes.js"
197
+ "@wcstack/state/core": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.10.0/dist/split/core.js",
198
+ "@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.10.0/dist/split/features/temporal.js",
199
+ "@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.10.0/dist/split/features/scopes.js"
200
200
  }
201
201
  }
202
202
  </script>
@@ -263,7 +263,7 @@ receptacle the owning add-on fills on install).
263
263
  core, and the add-ons the page names from the same build.
264
264
 
265
265
  ```html
266
- <script type="module" src="https://cdn.jsdelivr.net/npm/@wcstack/state@4.8.0/dist/split/auto.js"></script>
266
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@wcstack/state@4.10.0/dist/split/auto.js"></script>
267
267
  <wcs-state features="scopes diagnostics">
268
268
  <script type="module">
269
269
  export default {
@@ -1243,7 +1243,7 @@ Three rules decide what the dependency graph sees. None of them matters until yo
1243
1243
  | **Reads inside a setter are not tracked.** A setter is an imperative assignment, not a derivation, so nothing it reads becomes a dependency of anything | A setter that reads `this.a` to decide what to write does not run again when `a` changes — only a getter re-runs |
1244
1244
  | **The same-value guard applies to primitives only.** A primitive write `Object.is`-equal to the current value is dropped before anything is enqueued; an object or array write always passes, even the same reference | Assigning the same string again fires nothing; assigning the same object again re-fires its bindings and `$watch` (`$behavior.sameValueGuard`; a `semantics: "event"` property is exempt either way). A list is the exception: its rows follow the array's items only when a **new** array is assigned. The same array assigned again — or `$postUpdate` — re-reads what is below the rows it has, but adds, removes or replaces no row: `cards[i] = card; this.cards = cards` keeps showing the old card. Assign a new array (`this.cards = cards.with(i, card)`, `[...cards, x]`); lint reports the in-place change (`wcs/array-index-assign`, `wcs/array-mutation`) |
1245
1245
 
1246
- The first rule is the one static analysis can catch: `wcs-validate` and the VS Code extension report `wcs/getter-untracked-read` when a getter reads `this.form.name` and the document writes `form.name` somewhere (a `value:` binding, a spread, `this["form.name"] = …`). A root that is only ever replaced wholesale — router params, a `$stream` fold — is left alone.
1246
+ The first rule is the one static analysis can catch: `wcs-validate` and the VS Code extension report `wcs/getter-untracked-read` when a getter reads `this.form.name` and the document writes `form.name` somewhere (a `value:` binding, a spread, `this["form.name"] = …`). A root that is only ever replaced wholesale — router params, a `$stream` fold — is left alone. Since 4.9 it is an **error** when a binding on the page writes the path the getter reads — `<wcs-fetch data-wcs="...: monthFetch">` writes `monthFetch.value`, so a getter reading `this.monthFetch.value` never sees the response — and stays a warning when only the script writes it.
1247
1247
 
1248
1248
  `$untracked(fn)` applies the setter rule to a getter on purpose: reads inside `fn` are not tracked. `$dependOn(path)` is the escape hatch for the first rule.
1249
1249
 
@@ -1772,13 +1772,27 @@ The names 3.x kept as aliases of the 3.2 renames (`inc`, `dec`, `fix`, `uc`, `lc
1772
1772
 
1773
1773
  | Filter | Description | Example |
1774
1774
  |---|---|---|
1775
- | `date(loc?, style?)` | Date format | `createdAt\|date` / `createdAt\|date(ja-JP)` / `createdAt\|date(en-US,medium)` → `"Oct 13, 2026"` |
1776
- | `time(loc?, style?)` | Time format | `createdAt\|time` / `createdAt\|time(short)` |
1777
- | `datetime(loc?, style?)` | Date + Time | `createdAt\|datetime(en-US)` / `createdAt\|datetime(ja-JP,long)` |
1778
- | `ymd(sep?)` | YYYY-MM-DD (default `-`) | `createdAt\|ymd` / `createdAt\|ymd(/)` |
1779
- | `hms(sep?)` | HH:MM:SS (default `:`) | `createdAt\|hms` / `createdAt\|hms(-)` |
1775
+ | `date(loc?, style?, zone?)` | Date format; `zone?` since 4.10 | `createdAt\|date` / `createdAt\|date(ja-JP)` / `createdAt\|date(en-US,medium)` → `"Oct 13, 2026"` / `opensAt\|date(ja-JP,full,Asia/Tokyo)` |
1776
+ | `time(loc?, style?, zone?)` | Time format; `zone?` since 4.10 | `createdAt\|time` / `createdAt\|time(short)` / `opensAt\|time(,short,Asia/Tokyo)` |
1777
+ | `datetime(loc?, style?, zone?)` | Date + Time; `zone?` since 4.10 | `createdAt\|datetime(en-US)` / `createdAt\|datetime(ja-JP,long)` / `createdAt\|datetime(,,UTC)` |
1778
+ | `ymd(sep?)` | YYYY-MM-DD (default `-`), in the runtime's time zone | `createdAt\|ymd` / `createdAt\|ymd(/)` |
1779
+ | `hms(sep?)` | HH:MM:SS (default `:`), in the runtime's time zone | `createdAt\|hms` / `createdAt\|hms(-)` |
1780
1780
 
1781
- **All five take a `Date`,** not a timestamp — a value of any other type is rejected with "requires a date value". An absent one (`undefined` / `null`) renders nothing since 4.7. State loaded from JSON carries a number or an ISO string, so convert it where it is read rather than in the binding: `get createdAt() { return new Date(this.createdAtMs); }`. `loc?` defaults to the page locale ([Locale](#locale)), and the default is re-read on **every** apply, so a locale settled after the bindings were planned still takes effect (an explicit `date(ja-JP)` is part of the binding expression and is fixed when it is planned). `style?` is `full`, `long`, `medium` or `short` (Intl's `dateStyle` / `timeStyle`); without it the format is the locale's numeric one (`10/13/2026`), and a style alone keeps the page locale (`date(medium)`). `currency`'s code is checked when the bindings are planned; the number is formatted in the page locale unless `loc` names one. Plurals and other formats belong in a getter (`Intl.PluralRules`, `Intl.NumberFormat`).
1781
+ **All five take a `Date`,** not a timestamp — a value of any other type is rejected with "requires a date value". An absent one (`undefined` / `null`) renders nothing since 4.7. State loaded from JSON carries a number or an ISO string, so convert it where it is read rather than in the binding: `get createdAt() { return new Date(this.createdAtMs); }`. A date-only string is a day, not a moment: `new Date("2026-10-29")` is midnight UTC, which is the day before west of Greenwich — `new Date(y, m - 1, d)` is that day where the page runs (`#29` says so since 4.9). `loc?` defaults to the page locale ([Locale](#locale)), and the default is re-read on **every** apply, so a locale settled after the bindings were planned still takes effect (an explicit `date(ja-JP)` is part of the binding expression and is fixed when it is planned). `style?` is `full`, `long`, `medium` or `short` (Intl's `dateStyle` / `timeStyle`); without it the format is the locale's numeric one (`10/13/2026`), and a style alone keeps the page locale (`date(medium)`). `currency`'s code is checked when the bindings are planned; the number is formatted in the page locale unless `loc` names one. Plurals and other formats belong in a getter (`Intl.PluralRules`, `Intl.NumberFormat`).
1782
+
1783
+ **Time zones.** Without `zone?`, `date`, `time` and `datetime` format in the time zone of the runtime — **the reader's**, in a browser, and the server's under SSR — whatever the page's locale. A time that belongs to a place reads differently elsewhere: a library in Tokyo open on Thursday 29 October from 09:00 to 20:00, read from Los Angeles, shows `2026年10月28日水曜日`, `17:00:00` and `4:00:00`. Since 4.10, `zone?` names the zone to format in — an IANA time zone, Intl's `timeZone` (`Asia/Tokyo`, `America/New_York`, `UTC`) — so every reader sees the place's time:
1784
+
1785
+ ```html
1786
+ <html lang="ja">
1787
+ <!-- the state: get opensAt() { return new Date("2026-10-29T09:00:00+09:00"); }, closesAt the same at 20:00 -->
1788
+ <p>
1789
+ <span data-wcs="textContent: opensAt|date(,full,Asia/Tokyo)"></span>
1790
+ <span data-wcs="textContent: opensAt|time(,short,Asia/Tokyo)"></span>–<span data-wcs="textContent: closesAt|time(,short,Asia/Tokyo)"></span>
1791
+ </p>
1792
+ <!-- 2026年10月29日木曜日 9:00–20:00, from Tokyo and from Los Angeles alike -->
1793
+ ```
1794
+
1795
+ An empty argument keeps its default: `time(,short,Asia/Tokyo)` keeps the page locale, `date(ja-JP,,Asia/Tokyo)` the numeric format, `datetime(,,Asia/Tokyo)` both. A zone alone keeps both too when it has a `/` or is `UTC` (`date(Asia/Tokyo)`, `datetime(UTC)`); another zone without a `/` goes third (`date(,,GMT)`), since in the first place it is read as a locale. A zone Intl does not take is refused when the bindings are planned (`#54`), and a zone in the style's place (`date(ja-JP,Asia/Tokyo)`) is told that it goes third (`#52`). `ymd` and `hms` take no zone: they read the `Date` in the runtime's zone (`getFullYear()` … `getSeconds()`), so for a place's date or time use `date` / `time` with its zone, or a getter.
1782
1796
 
1783
1797
  ### Boolean / Default
1784
1798
 
@@ -2358,6 +2372,7 @@ $on: {
2358
2372
  - Every `$on` key must be declared in `$eventTokens` (otherwise an error is thrown at initialization)
2359
2373
  - Each value must be a function
2360
2374
  - The signature is `(state, event, ...listIndexes)` — the DOM `Event` first, then any enclosing loop indexes
2375
+ - `this` is undefined in a handler, so a handler that uses it throws when the event arrives. Since 4.9 the `diagnostics` add-on says so once per handler when the state is declared, before any event (`[wcs/on-handler-this]`); lint reports it as an error
2361
2376
 
2362
2377
  ### `eventToken.<property>:` Binding
2363
2378
 
@@ -2621,7 +2636,7 @@ Firing order is defined in three layers, and only the middle one is yours to ste
2621
2636
  | Between handlers | declaration order in `$watch` | **reorder the declarations** |
2622
2637
  | Between rows of one path | ascending `indexes` | fixed |
2623
2638
 
2624
- **The one thing that moves the mechanism layer** is a `<wcs-view-transition>` that accepts the `state` participant. Binding application — and with it `$renderedCallback` — then lands on a frame, while `$watch` and the `$stream` restart stay on the microtask the drain was queued on, because they consume state addresses and not the DOM. For as long as the tag is present the order is `$watch` → `$stream` restart → `$renderedCallback`. Nothing else on the page reorders this layer; see [docs/timing-and-firing-contract.md](https://github.com/wcstack/wcstack/blob/main/docs/timing-and-firing-contract.md) §4.3.
2639
+ **The one thing that moves the mechanism layer** is a `<wcs-view-transition>` that accepts the `state` participant. Binding application — and with it `$renderedCallback` — then lands on a frame. Since 4.10 `$watch` and the `$stream` restart wait for it, so the order stays as above, a frame later, and what they write (a list a `$watch` copies into state) is applied in the same transition; up to 4.9 they stayed on the microtask the drain was queued on and ran first (`$watch` → `$stream` restart → `$renderedCallback`), so a `$watch` that navigated after a login ran before `<wcs-storage>` had saved the session its route guard reads. Nothing else on the page moves this layer; see [docs/timing-and-firing-contract.md](https://github.com/wcstack/wcstack/blob/main/docs/timing-and-firing-contract.md) §4.3.
2625
2640
 
2626
2641
  Key rules:
2627
2642
 
@@ -2965,7 +2980,7 @@ The first line is what `@wcstack/state` and `/auto` print, the second what `/cor
2965
2980
  | 26 | | `filter <name> requires at least one option` |
2966
2981
  | 27 | | `filter <name> requires a number as option` |
2967
2982
  | 28 | | `filter <name> requires a number value, not <what it was>` (a string adds: `— put |number before |<name>, or convert it where it is read`) |
2968
- | 29 | | `filter <name> requires a date value (a Date), not <what it was>` (a string or a number adds: `— make the Date where it is read: a getter returning new Date(…)`) |
2983
+ | 29 | | `filter <name> requires a date value (a Date), not <what it was>` (a string or a number adds: `— make the Date where it is read: a getter returning new Date(…)`; since 4.9, a date-only string such as `"2026-10-01"` also says that `new Date("2026-10-01")` is midnight UTC — the day before west of Greenwich — and `new Date(y, m - 1, d)` is that day where the page is) |
2969
2984
  | 30 | | `filter <name> requires an array value` |
2970
2985
  | 31 | | `init=/sync= modifiers require enableDirectionalInitialSync.` |
2971
2986
  | 32 | | `"<text>": the "@name" selector was removed in v2 — there is a single state tree. …` |
@@ -2988,8 +3003,9 @@ The first line is what `@wcstack/state` and `/auto` print, the second what `/cor
2988
3003
  | 49 | | `<wcs-state src="…"> failed to initialize.` |
2989
3004
  | 50 | | `<wcs-state src="…"> $connectedCallback failed.` |
2990
3005
  | 51 | | `<wcs-state src="…"> $disconnectedCallback failed.` |
2991
- | 52 | | `filter <name> takes a style of full, long, medium or short, not "<style>"` |
3006
+ | 52 | | `filter <name> takes a style of full, long, medium or short, not "<style>"` (since 4.10, a time zone in the style's place (one with a `/`, or `UTC`) adds: `— a time zone is the third argument, after the locale and the style, and an empty argument keeps its default: <name>(,,<zone>), <name>(en-US,long,<zone>)`) |
2992
3007
  | 53 | | `filter currency takes an ISO 4217 currency code (JPY, USD, EUR), not "<code>"` |
3008
+ | 54 | | `filter <name> takes an IANA time zone (Asia/Tokyo, America/New_York, UTC), not "<zone>"` (since 4.10) |
2993
3009
  | 101 | `binding-syntax` | `Invalid bindText: "<text>". Missing ':' separator between propPart and statePart.` |
2994
3010
  | 102 | `binding-syntax` | `"<text>": "<keyword>" takes no modifiers or filters on its left side — write "<keyword>:".` |
2995
3011
  | 103 | `binding-syntax` | `"<text>": "else" takes no value — write "else:".` |
@@ -3064,11 +3080,13 @@ dropped. Validate statically: npx @wcstack/lint <file>.
3064
3080
  The check **under-approximates**: it stays silent for anything it cannot decide statically, because a false alarm costs more than a missed one. None of these warn:
3065
3081
 
3066
3082
  - A `null` / `undefined` parent (the "seed as `null`, assign later" shape)
3083
+ - An empty object under a key (`fieldErrors: {}`, filled later with `this.fieldErrors = { date: "…" }`) — the same shape, seeded `{}`. Since 4.10; up to 4.9 each binding below it was reported, and its "updates … will be silently dropped" was not so: replacing the object updates them. The lint reads those assignments since 4.9
3067
3084
  - Row fields of a list that starts empty (the row shape is unknown)
3068
3085
  - A row field no row has, unless a row has a near name (a likely typo, `.nmae` beside `name`) — rows are data, and an optional field (a `pending` the page puts on its own rows) is absent from the others. Since 4.4; the first row lacking a field another row has does not warn either (4.3)
3069
3086
  - Sub-properties of an intermediate getter's return value
3070
3087
  - Paths under the mount path of a volume that is still loading — they are checked once it settles
3071
3088
  - Reserved `$` namespaces (`$command.*` and friends)
3089
+ - Keys under an object an element writes — the router's `searchParams: query` has the URL's keys, so `query.next` is there only when the URL has it. Since 4.10; up to 4.9 such a binding was reported as "silently dropped"
3072
3090
 
3073
3091
  So **no warning is not a proof of correctness.** For exhaustive checking, run `npx @wcstack/lint <file>`. A re-set (`setInitialState()`) checks every path again against the new state.
3074
3092
 
@@ -3122,7 +3140,7 @@ What fails initialization:
3122
3140
  - A source that cannot be read ([State Initialization](#state-initialization)).
3123
3141
  - An invalid declaration: a removed name (`$scan`, `$streams`, `$updatedCallback`; with the `diagnostics` add-on), a `$behavior` / `$features` / `$commandTokens` / `$eventTokens` / `$on` of the wrong shape, a declaration whose add-on is not installed (`[wcs/feature-not-installed]`).
3124
3142
  - A markup error found while the page is bound: a syntax error, an unknown filter or a wrong argument count, a wildcard-rank error, `for:` with filters, `outerHTML:` in a template, a wc-bindable misconfiguration on an element whose class is already defined. The walk stops at the error: what comes before it in document order is bound, what comes after it is not. Run `npx @wcstack/lint` before you deploy.
3125
- - A second root `<wcs-state>` on a root that another one already binds (`#47`). There is one state tree per root; graft a subtree with `mount=`.
3143
+ - A second root `<wcs-state>` on a root that another one already binds (`#47`). There is one state tree per root; graft a subtree with `mount=`. The one that binds is the one whose state is ready first, not the first in the document: a `json=` state is ready at once, so it takes the root before an earlier `src=` or inline state, which is then the one refused, and the page binds against the wrong state. Since 4.9 the `diagnostics` add-on names both when the first binds (`[wcs/second-root]`).
3126
3144
 
3127
3145
  Inside a `for:` / `if:` row, an error found while a binding is attached (an undeclared token, a member the element does not declare) is that binding's failure instead: it goes to `$errorCallback`, and the row is still built. A binding to a top-level key the state does not have is a failed apply, not an initialization failure. An element that failed to initialize cannot be re-armed — `setInitialState()` throws (`#14`); remove it and create a new one.
3128
3146
 
@@ -3310,6 +3328,10 @@ the module is evaluated, so write it in the markup. An explicit
3310
3328
  is reported once (`#48`) and the filters use `"en"`, rather than throwing inside `Intl`;
3311
3329
  `getConfig().locale` keeps returning what was set.
3312
3330
 
3331
+ The locale is not the time zone: a `<html lang="ja-JP">` page still writes its dates and times in
3332
+ the reader's zone, in Japanese. A time that belongs to a place takes that place's zone as the date
3333
+ filters' third argument (since 4.10 — [Date / Time](#date--time)).
3334
+
3313
3335
  **Changing the locale later does not re-render anything.** It is a global
3314
3336
  setting, not state, so it is not part of the dependency graph. The filters do
3315
3337
  read it on every application rather than capturing it when the binding is built,
@@ -3353,7 +3375,7 @@ different shape; `examples/router-i18n` is the reference layout.
3353
3375
 
3354
3376
  A page built on `<wcs-state>` is plain DOM, so it can be tested headlessly with [happy-dom](https://github.com/capricorn86/happy-dom) — no browser, no build step, no test-only API. Three recipes follow; every one of them runs as written.
3355
3377
 
3356
- Want it as one import? [`@wcstack/testing`](../testing/README.md) packages recipe 1 as `mount()` / `settle()` / `fire()` (and waits for `<wcs-router>` too). The bare recipes below stay valid without it.
3378
+ Want it as one import? [`@wcstack/testing`](../testing/README.md) packages recipe 1 as `mount()` / `settle()` / `fire()` (and waits for `<wcs-router>` too). The bare recipes below stay valid without it. A state in its own file (`<wcs-state src="./state.js">`) loads under `mount()` with its `resolveModule` option, since 4.9 — under happy-dom the `src` is an `http:` URL, which Node's module loader does not read, and an inline state cannot `import` a relative module under Node.
3357
3379
 
3358
3380
  ### 1. vitest + happy-dom
3359
3381