@wcstack/state 4.0.0-rc.7 → 4.0.0-rc.8

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 +28 -33
  2. package/README.md +28 -33
  3. package/dist/auto.min.js +2 -1
  4. package/dist/auto.min.js.map +1 -0
  5. package/dist/index.esm.js +2 -1
  6. package/dist/index.esm.js.map +1 -0
  7. package/dist/split/auto.js +2 -1
  8. package/dist/split/auto.js.map +1 -0
  9. package/dist/split/chunks/{chunk-V34ZHCK5.js → chunk-4NJBYXIT.js} +2 -1
  10. package/dist/split/chunks/chunk-4NJBYXIT.js.map +1 -0
  11. package/dist/split/chunks/{chunk-JMPQHCIT.js → chunk-55J45X6H.js} +2 -1
  12. package/dist/split/chunks/chunk-55J45X6H.js.map +1 -0
  13. package/dist/split/chunks/{chunk-LTBR3OV5.js → chunk-7AYOQHZH.js} +2 -1
  14. package/dist/split/chunks/chunk-7AYOQHZH.js.map +1 -0
  15. package/dist/split/chunks/chunk-EIMTT5I3.js +2 -0
  16. package/dist/split/chunks/chunk-EIMTT5I3.js.map +1 -0
  17. package/dist/split/chunks/chunk-L3PFEJP7.js +2 -0
  18. package/dist/split/chunks/chunk-L3PFEJP7.js.map +1 -0
  19. package/dist/split/chunks/chunk-LXEEVZIB.js +2 -0
  20. package/dist/split/chunks/chunk-LXEEVZIB.js.map +1 -0
  21. package/dist/split/chunks/{chunk-W5KT4PQ3.js → chunk-M5SODAAE.js} +2 -1
  22. package/dist/split/chunks/chunk-M5SODAAE.js.map +1 -0
  23. package/dist/split/chunks/{chunk-SPWZUBQS.js → chunk-NOPTR2L3.js} +2 -1
  24. package/dist/split/chunks/chunk-NOPTR2L3.js.map +1 -0
  25. package/dist/split/chunks/{chunk-ARPZVNKL.js → chunk-UD3AYVP2.js} +2 -1
  26. package/dist/split/chunks/chunk-UD3AYVP2.js.map +1 -0
  27. package/dist/split/chunks/{chunk-XF6V6DWR.js → chunk-W4KOY6GT.js} +2 -1
  28. package/dist/split/chunks/chunk-W4KOY6GT.js.map +1 -0
  29. package/dist/split/chunks/{chunk-2EGTC36Y.js → chunk-ZN52NEIB.js} +2 -1
  30. package/dist/split/chunks/chunk-ZN52NEIB.js.map +1 -0
  31. package/dist/split/core.js +2 -1
  32. package/dist/split/core.js.map +1 -0
  33. package/dist/split/features/devtools.js +2 -1
  34. package/dist/split/features/devtools.js.map +1 -0
  35. package/dist/split/features/diagnostics.js +2 -1
  36. package/dist/split/features/diagnostics.js.map +1 -0
  37. package/dist/split/features/formats.js +2 -1
  38. package/dist/split/features/formats.js.map +1 -0
  39. package/dist/split/features/list-keys.js +2 -1
  40. package/dist/split/features/list-keys.js.map +1 -0
  41. package/dist/split/features/native-commands.js +2 -1
  42. package/dist/split/features/native-commands.js.map +1 -0
  43. package/dist/split/features/recursion.js +2 -1
  44. package/dist/split/features/recursion.js.map +1 -0
  45. package/dist/split/features/scopes.js +2 -1
  46. package/dist/split/features/scopes.js.map +1 -0
  47. package/dist/split/features/ssr.js +2 -1
  48. package/dist/split/features/ssr.js.map +1 -0
  49. package/dist/split/features/temporal.js +2 -1
  50. package/dist/split/features/temporal.js.map +1 -0
  51. package/package.json +1 -1
  52. package/dist/split/chunks/chunk-27N3IPIZ.js +0 -1
  53. package/dist/split/chunks/chunk-7EWPQMA6.js +0 -1
  54. package/dist/split/chunks/chunk-ALVA5TQM.js +0 -1
package/README.ja.md CHANGED
@@ -138,7 +138,7 @@
138
138
  | **getter が動く条件** | getter は遅延評価。評価需要は live binding・`$watch`・`$stream` の `args` の 3 箇所からしか生まれない | [評価のきっかけ](#評価のきっかけdemand-root) |
139
139
  | **モジュール化** | `mount=` がモジュールを 1 本のツリーに接ぎ木し、`state: path` が部分木をコンポーネントにマウントする。個別対応付けは単一キーを繋ぎ、マウントされたコンポーネントの getter はマウント点で公開される | [ボリューム](#追加の状態をマウントするmount) · [丸ごとマウント](#丸ごとマウントstate-path) |
140
140
  | **コンポーネント** | 排他的な 2 方式 — JavaScript クラス+`bind-component` か、HTML だけの DCC か | [機構の選び方](#コンポーネント機構の選び方) |
141
- | **他要素との配線** | wc-bindable プロトコル、spread(`...: obj`)、`#init=` / `#sync=` の authority、プロパティ→属性ミラー | [バインディング authority](#バインディング-authority-init--sync) · [Spread](#spread-バインディング-) · [Inputs](#inputs-と属性ミラー) |
141
+ | **他要素との配線** | wc-bindable プロトコル、spread(`...: obj`)、`#init=` / `#sync=` の authority、settable な入力 | [バインディング authority](#バインディング-authority-init--sync) · [Spread](#spread-バインディング-) · [Inputs](#inputs-と-attribute-ヒント) |
142
142
  | **トークン** | command token が state から要素のメソッドを呼び、event token が要素のイベントを state へ戻す | [Command token](#command-tokenメソッドバインディング) · [Event token](#event-tokenイベントバインディング) |
143
143
  | **時間** | `$stream` が非同期ソースを fold し、`$watch` が headless に反応する | [時間を扱う機構の選び方](#時間を扱う機構の選び方) |
144
144
  | **初期化とライフサイクル** | state の供給は 6 通り。`$connectedCallback` 〜 `$stateReadyCallback`、`bootstrapState()` / `createState()` | [状態の初期化](#状態の初期化) · [ライフサイクルフック](#ライフサイクルフック) · [API リファレンス](#api-リファレンス) |
@@ -191,9 +191,9 @@
191
191
  <script type="importmap">
192
192
  {
193
193
  "imports": {
194
- "@wcstack/state/core": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.7/dist/split/core.js",
195
- "@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.7/dist/split/features/temporal.js",
196
- "@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.7/dist/split/features/scopes.js"
194
+ "@wcstack/state/core": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.8/dist/split/core.js",
195
+ "@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.8/dist/split/features/temporal.js",
196
+ "@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.8/dist/split/features/scopes.js"
197
197
  }
198
198
  }
199
199
  </script>
@@ -253,7 +253,7 @@
253
253
  ページが名指したアドオンを、同じビルドから読み込みます。
254
254
 
255
255
  ```html
256
- <script type="module" src="https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.7/dist/split/auto.js"></script>
256
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.8/dist/split/auto.js"></script>
257
257
  <wcs-state features="scopes diagnostics">
258
258
  <script type="module">
259
259
  export default {
@@ -2600,11 +2600,11 @@ $renderedCallback(paths) {
2600
2600
  - **マウントされた `bind-component` スコープでは実行されません** —— マウントされたコンポーネントの `$watch` 宣言は、ルート state へ誘導する `wcs/mount-dollar-declaration` の警告を 1 回出して無視されます。`$stream` と `$renderedCallback` も同様です。ボリュームは `$watch` を拒否します([`mount=`](#追加の状態をマウントするmount)): ルートにフルパス(`"cart.total"`)で宣言してください。plain な(配線なし Shadow の)子は独立ツリーを持つので宣言できます。
2601
2601
  - **SSR では実行されません** —— ハンドラの副作用がサーバーとクライアントで二重に走るためです。
2602
2602
 
2603
- ## Inputs と属性ミラー
2603
+ ## Inputs と attribute ヒント
2604
2604
 
2605
- `wcBindable.inputs` は一方向のプロパティ入力(state → 要素)を宣言します。エントリに `attribute` を設定すると、フレームワークはプロパティを書き込むたびにその値を当該 HTML 属性へも書き込むため、`attributeChangedCallback`・CSS の属性セレクタ・DevTools がすべてプロパティ値と同期し続けます。
2605
+ `wcBindable.inputs` は、状態が書いてよいメンバー(state → 要素)を宣言します。方向認識初期同期(既定 ON)の下では、メンバが **state から settable であること**を示すのが `inputs` です。settable なのに `properties` にしか宣言されていないメンバは output-only 扱いになり、state からの書き込みが抑止されます — [バインディング authority](#バインディング-authority-init--sync) を参照してください。
2606
2606
 
2607
- `inputs` は属性ミラーのためだけのメタデータではありません。方向認識初期同期(既定 ON)の下では、メンバが **state から settable であること**を示すのが `inputs` です。settable なのに `properties` にしか宣言されていないメンバは output-only 扱いになり、state からの書き込みが抑止されます — [バインディング authority](#バインディング-authority-init--sync) を参照してください。
2607
+ 状態が書くのは入力の**プロパティ**だけです。エントリの省略できる `attribute` は、マークアップでそのプロパティに対応する HTML 属性の名前(`<my-chip label-text="…">`)です。wc-bindable プロトコルの定めるとおりツールのための宣言で、状態はこの属性を書きません。プロパティを属性に反映する(`attributeChangedCallback`・CSS の属性セレクタ・DevTools のため)のは要素の仕事で、setter の中で、要素だけが知っている書き方で行います(boolean は属性の有無、`"on"` / `"off"`、JSON など):
2608
2608
 
2609
2609
  ```javascript
2610
2610
  class MyChip extends HTMLElement {
@@ -2612,41 +2612,28 @@ class MyChip extends HTMLElement {
2612
2612
  protocol: "wc-bindable", version: 1,
2613
2613
  properties: [],
2614
2614
  inputs: [
2615
- { name: "data", attribute: "data" }, // プロパティ名 === 属性名
2616
- { name: "labelText", attribute: "label-text" }, // kebab-case ミラー
2617
- { name: "internal" }, // ミラーなし、プロパティのみ
2615
+ { name: "labelText", attribute: "label-text" }, // マークアップ: <my-chip label-text="…">
2616
+ { name: "selected", attribute: "selected" }, // マークアップ: <my-chip selected>
2617
+ { name: "data" }, // プロパティだけ
2618
2618
  ],
2619
2619
  };
2620
+ get labelText() { return this.getAttribute("label-text") ?? ""; }
2621
+ set labelText(v) { v == null ? this.removeAttribute("label-text") : this.setAttribute("label-text", v); }
2622
+ get selected() { return this.hasAttribute("selected"); }
2623
+ set selected(v) { this.toggleAttribute("selected", Boolean(v)); }
2620
2624
  }
2621
2625
  ```
2622
2626
 
2623
2627
  ```html
2624
- <my-chip data-wcs="data: chip.payload; labelText: chip.title"></my-chip>
2628
+ <my-chip data-wcs="labelText: chip.title; selected: chip.on; data: chip.payload"></my-chip>
2625
2629
  ```
2626
2630
 
2627
- state が値を更新すると、プロパティと属性の両方が書き込まれます:
2628
-
2629
- ```text
2630
- chip.payload = { id: 1 } → element.data = { id: 1 } かつ setAttribute("data", '{"id":1}')
2631
- chip.title = "新着" → element.labelText = "新着" かつ setAttribute("label-text", "新着")
2632
- chip.payload = null → element.data = null かつ removeAttribute("data")
2633
- ```
2634
-
2635
- 属性値のエンコード:
2636
-
2637
- | 値の型 | ミラーされる属性 |
2638
- |---|---|
2639
- | `string` / `number` / `bigint` | `String(value)` |
2640
- | `true` | 空の属性(`""`) |
2641
- | `false` / `null` / `undefined` | 属性を削除 |
2642
- | `object` / `array` | `JSON.stringify(value)`(循環参照時は `String(value)` にフォールバック) |
2631
+ `chip.title = "新着"` は `element.labelText = "新着"` を書き、setter が `label-text` に反映します。`chip.on = false` は `element.selected = false` を書き、setter が `selected` を外します。
2643
2632
 
2644
2633
  補足:
2645
2634
 
2646
- - `attribute` を**持たない** `inputs` エントリはプロパティのみ —— 値はプロパティに書き込まれるが属性には触れない
2647
- - boolean は、有無で読む HTML の真偽属性として写す(`<wcs-wakelock active>`)。`false` は属性を外す(3.x は `"false"` と書き、そうした要素はそれを真と読んでいた)。属性が `"off"` でない限り on の入力は、この形では写せない: `attribute` を宣言せず、setter に自分で属性を書かせる(`<wcs-audio>` の `limiter` がそう)
2648
- - ミラーはベストエフォート: `setAttribute` の失敗は握りつぶされ、プロパティ書き込みをブロックしない
2649
- - ネイティブ HTML 要素は `inputs` を完全に無視する —— ミラーは `static wcBindable` を公開するカスタム要素でのみ有効になる
2635
+ - 3.x はプロパティの後に属性にも書いていました(`String(value)`、オブジェクトは JSON)。setter で反映する要素は影響を受けません。setter を持たず、binder が属性を書くことに頼っていた要素には、setter が要ります(移行ガイド §3.4)。
2636
+ - ネイティブ HTML 要素は `inputs` を完全に無視します —— 宣言が効くのは `static wcBindable` を公開するカスタム要素だけです。
2650
2637
 
2651
2638
  ## コンポーネント機構の選び方
2652
2639
 
@@ -3538,7 +3525,15 @@ bootstrapState(config?, registry?)
3538
3525
 
3539
3526
  ## パフォーマンス
3540
3527
 
3541
- 4.0 のエンジンは書き直しで、操作ごとの数値はまだリポジトリのドライバで記録していません: リリースの一部として 4.0 のビルドで計測します。開発中、公式の [js-framework-benchmark](https://github.com/krausest/js-framework-benchmark) で計測した 4.0 エンジンの CPU 加重幾何平均は 1.08〜1.11 で、[`@wcstack/signals`](../signals/) は 1.21〜1.25、`@wcstack/state` 3.3.0 は 1.51 でした(小さいほど速い)。
3528
+ 公式の [js-framework-benchmark](https://github.com/krausest/js-framework-benchmark) で、4.0.0-rc.7 を測った値です(Chrome 154、4 つを 1 回のセッションで。4.0 は全部入りの `auto.min.js`):
3529
+
3530
+ | | vanillajs | 4.0 | [`@wcstack/signals`](../signals/) | 3.5.4 |
3531
+ |---|---:|---:|---:|---:|
3532
+ | CPU(加重幾何平均) | 1.02 | **1.07** | 1.22 | 1.44 |
3533
+ | メモリ(幾何平均) | 1.00 | **1.95** | 1.41 | 4.40 |
3534
+ | 1,000 行を持ったときのメモリ(MB) | 2.03 | **2.97** | 3.72 | 6.35 |
3535
+
3536
+ 小さいほど良い値です。上の 2 行は、公式の結果表と同じく、各操作の 4 つの中の最速に対する比の幾何平均です。4.0 の JavaScript の時間は 3.5.4 の約 3 分の 1 です(script だけの幾何平均 2.03 対 6.28、vanillajs を 1)。リポジトリのドライバでの操作ごとの数値は、リリースの一部として 4.0.0 のビルドで計測します。
3542
3537
 
3543
3538
  手元のハードウェアで比べるには、`e2e/bench/` のドライバ(`jsfb-verify.mjs`・`memory-profile.mjs`)を実行してください: 標準の 1,000 / 10,000 行テーブルページを headless Chromium で、両実装を同一セッションで連続して、中央値で計測します。絶対値はマシン状態で ±20% 揺れます。
3544
3539
 
package/README.md CHANGED
@@ -138,7 +138,7 @@ Every row is a section of this README. Unless it appears under [Where the neighb
138
138
  | **What makes a getter run** | Getters are lazy. Demand comes from a live binding, a `$watch` or a `$stream` `args` — and from nowhere else | [Demand roots](#demand-roots--what-makes-a-getter-run) |
139
139
  | **Modularity** | `mount=` grafts a module onto the one tree; `state: path` mounts a subtree onto a component; the per-property form maps single keys, and a mounted component's getters are exported at its mount point | [Volumes](#mounting-additional-state-mount) · [Whole-object mount](#whole-object-mount-state-path) |
140
140
  | **Components** | Two mutually exclusive mechanisms: a JavaScript class with `bind-component`, or HTML-only DCC | [Choosing a mechanism](#choosing-a-component-mechanism) |
141
- | **Wiring to other elements** | The wc-bindable protocol, spread (`...: obj`), `#init=` / `#sync=` authority, property-to-attribute mirroring | [Binding authority](#binding-authority-init--sync) · [Spread](#spread-binding-) · [Inputs](#inputs-and-attribute-mirror) |
141
+ | **Wiring to other elements** | The wc-bindable protocol, spread (`...: obj`), `#init=` / `#sync=` authority, settable inputs | [Binding authority](#binding-authority-init--sync) · [Spread](#spread-binding-) · [Inputs](#inputs-and-the-attribute-hint) |
142
142
  | **Tokens** | Command tokens call an element's methods from state; event tokens carry the element's events back | [Command token](#command-token-method-binding) · [Event token](#event-token-event-binding) |
143
143
  | **Time** | `$stream` folds an async source, `$watch` reacts headlessly | [Choosing a time mechanism](#choosing-a-time-mechanism) |
144
144
  | **Initialization and lifecycle** | Six ways to supply the state; `$connectedCallback` … `$stateReadyCallback`; `bootstrapState()` / `createState()` | [State initialization](#state-initialization) · [Lifecycle hooks](#lifecycle-hooks) · [API reference](#api-reference) |
@@ -192,9 +192,9 @@ leaves features out, it can compose them instead:
192
192
  <script type="importmap">
193
193
  {
194
194
  "imports": {
195
- "@wcstack/state/core": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.7/dist/split/core.js",
196
- "@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.7/dist/split/features/temporal.js",
197
- "@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.7/dist/split/features/scopes.js"
195
+ "@wcstack/state/core": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.8/dist/split/core.js",
196
+ "@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.8/dist/split/features/temporal.js",
197
+ "@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.8/dist/split/features/scopes.js"
198
198
  }
199
199
  }
200
200
  </script>
@@ -254,7 +254,7 @@ receptacle the owning add-on fills on install).
254
254
  core, and the add-ons the page names from the same build.
255
255
 
256
256
  ```html
257
- <script type="module" src="https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.7/dist/split/auto.js"></script>
257
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.8/dist/split/auto.js"></script>
258
258
  <wcs-state features="scopes diagnostics">
259
259
  <script type="module">
260
260
  export default {
@@ -2610,11 +2610,11 @@ Key rules:
2610
2610
  - **Not run on a mounted `bind-component` scope** — the `$watch` declaration of a mounted component is ignored with a one-time `wcs/mount-dollar-declaration` warning that points to the root state; this applies to `$stream` and `$renderedCallback` too. A volume refuses `$watch` ([`mount=`](#mounting-additional-state-mount)): declare it on the root with the full path (`"cart.total"`). A plain (unwired Shadow) child owns an independent tree and can declare it.
2611
2611
  - **SSR does not run watches** — handler side effects would otherwise execute on both server and client.
2612
2612
 
2613
- ## Inputs and Attribute Mirror
2613
+ ## Inputs and the `attribute` Hint
2614
2614
 
2615
- `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.
2615
+ `wcBindable.inputs` declares the members state may set (state → element). Under directional initial sync (default on), it is what marks a member as **settable from state**: a member declared only in `properties` becomes output-only and state writes to it are suppressed — see [Binding Authority](#binding-authority-init--sync).
2616
2616
 
2617
- `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).
2617
+ State writes an input's **property**, and only the property. An entry's optional `attribute` names the HTML attribute that corresponds to the property in markup (`<my-chip label-text="…">`); it is a declaration for tooling, as the wc-bindable protocol defines it, and state does not write it. Reflecting a property to its attribute — for `attributeChangedCallback`, CSS attribute selectors or DevTools — is the element's job, in its setter, with the encoding only the element knows (a boolean as a present / absent attribute, `"on"` / `"off"`, JSON …):
2618
2618
 
2619
2619
  ```javascript
2620
2620
  class MyChip extends HTMLElement {
@@ -2622,41 +2622,28 @@ class MyChip extends HTMLElement {
2622
2622
  protocol: "wc-bindable", version: 1,
2623
2623
  properties: [],
2624
2624
  inputs: [
2625
- { name: "data", attribute: "data" }, // property name === attribute name
2626
- { name: "labelText", attribute: "label-text" }, // kebab-case mirror
2627
- { name: "internal" }, // no mirror, property-only
2625
+ { name: "labelText", attribute: "label-text" }, // markup: <my-chip label-text="…">
2626
+ { name: "selected", attribute: "selected" }, // markup: <my-chip selected>
2627
+ { name: "data" }, // property only
2628
2628
  ],
2629
2629
  };
2630
+ get labelText() { return this.getAttribute("label-text") ?? ""; }
2631
+ set labelText(v) { v == null ? this.removeAttribute("label-text") : this.setAttribute("label-text", v); }
2632
+ get selected() { return this.hasAttribute("selected"); }
2633
+ set selected(v) { this.toggleAttribute("selected", Boolean(v)); }
2630
2634
  }
2631
2635
  ```
2632
2636
 
2633
2637
  ```html
2634
- <my-chip data-wcs="data: chip.payload; labelText: chip.title"></my-chip>
2638
+ <my-chip data-wcs="labelText: chip.title; selected: chip.on; data: chip.payload"></my-chip>
2635
2639
  ```
2636
2640
 
2637
- When state updates the value, both the property and the attribute are written:
2638
-
2639
- ```text
2640
- chip.payload = { id: 1 } → element.data = { id: 1 } and setAttribute("data", '{"id":1}')
2641
- chip.title = "新着" → element.labelText = "新着" and setAttribute("label-text", "新着")
2642
- chip.payload = null → element.data = null and removeAttribute("data")
2643
- ```
2644
-
2645
- Attribute value encoding:
2646
-
2647
- | Value type | Mirrored attribute |
2648
- |---|---|
2649
- | `string` / `number` / `bigint` | `String(value)` |
2650
- | `true` | the attribute, empty (`""`) |
2651
- | `false` / `null` / `undefined` | attribute removed |
2652
- | `object` / `array` | `JSON.stringify(value)` (falls back to `String(value)` on circular references) |
2641
+ `chip.title = "新着"` writes `element.labelText = "新着"`; the setter puts it on `label-text`. `chip.on = false` writes `element.selected = false`; the setter removes `selected`.
2653
2642
 
2654
2643
  Notes:
2655
2644
 
2656
- - `inputs` entries **without** `attribute` are property-only — the value is written to the property but no attribute is touched
2657
- - A boolean is an HTML boolean attribute, read by its presence (`<wcs-wakelock active>`): `false` removes it. (3.x wrote `"false"`, which such an element reads as true.) An input that is on unless an attribute says `"off"` cannot be mirrored this way: leave its `attribute` out and let its setter reflect it, as `<wcs-audio>`'s `limiter` does
2658
- - Mirror is best-effort: a `setAttribute` failure is swallowed and does not block the property write
2659
- - Native HTML elements ignore `inputs` entirely — the mirror only activates for custom elements that expose `static wcBindable`
2645
+ - 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).
2646
+ - Native HTML elements ignore `inputs` entirely — the declaration only applies to custom elements that expose `static wcBindable`.
2660
2647
 
2661
2648
  ## Choosing a Component Mechanism
2662
2649
 
@@ -3552,7 +3539,15 @@ A mounted component runs an engine of its own, whose mounted keys read and write
3552
3539
 
3553
3540
  ## Performance
3554
3541
 
3555
- The 4.0 engine is a rewrite, and its per-operation numbers have not yet been recorded with the repository's drivers: they are taken with the 4.0 build as part of the release. During development, the official [js-framework-benchmark](https://github.com/krausest/js-framework-benchmark) measured the 4.0 engine's CPU-weighted geometric mean at 1.08–1.11, against 1.21–1.25 for [`@wcstack/signals`](../signals/) and 1.51 for `@wcstack/state` 3.3.0 (lower is faster).
3542
+ On the official [js-framework-benchmark](https://github.com/krausest/js-framework-benchmark), measured with 4.0.0-rc.7 (Chrome 154, all four in one session, the full `auto.min.js`):
3543
+
3544
+ | | vanillajs | 4.0 | [`@wcstack/signals`](../signals/) | 3.5.4 |
3545
+ |---|---:|---:|---:|---:|
3546
+ | CPU (weighted geometric mean) | 1.02 | **1.07** | 1.22 | 1.44 |
3547
+ | Memory (geometric mean) | 1.00 | **1.95** | 1.41 | 4.40 |
3548
+ | Run memory, 1,000 rows (MB) | 2.03 | **2.97** | 3.72 | 6.35 |
3549
+
3550
+ Lower is better. The first two rows are geometric means of each operation's factor against the fastest of the four, as in the official results table. 4.0's JavaScript time is about a third of 3.5.4's (script-only geometric mean 2.03 against 6.28, vanillajs = 1). The per-operation numbers with the repository's own drivers are taken with the 4.0.0 build as part of the release.
3556
3551
 
3557
3552
  To compare on your own hardware, run the drivers in `e2e/bench/` (`jsfb-verify.mjs`, `memory-profile.mjs`): they measure the standard 1,000 / 10,000-row table page in headless Chromium, both implementations back-to-back in the same session, as medians. Absolute values swing by ±20% with machine state.
3558
3553