@zakkster/lite-signal-decorators 0.4.0 → 1.0.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,88 @@ All notable changes to `@zakkster/lite-signal-decorators` are documented here.
4
4
  The format follows Keep a Changelog; this project adheres to Semantic
5
5
  Versioning.
6
6
 
7
+ ## [1.0.0] - 2026-08-29
8
+
9
+ The 1.0 release: docs freeze, the fleet playground, and the standing pre-publish
10
+ gate. Zero runtime changes -- the 16-export surface ships byte-identical to
11
+ 0.4.0 (the hot accessor canon is review-diffed to a zero-line diff), and this
12
+ release freezes that surface under semver: any later signature change is a major
13
+ recorded in a decision file. Dist-tag `latest`. Stage gate, measured at
14
+ closeout by the full section-10 chain (archived verbatim below): 214/214 tests
15
+ on both lanes; torture 15 scenarios (13 pass + the two forward-compat scenarios
16
+ legitimately skipping under the installed 1.5.0 peer) with 15/15 sabotage
17
+ controls breaking as required; the peer-preview lane SUITE-GREEN per tag
18
+ (15/15, forward scenarios RUNNING) against lite-signal 1.9.0-preview.6 AND
19
+ 1.9.0-canary.1; the bench sink self-test catching its sabotaged adapter; pack 7
20
+ files, no `demo/` or `Publications/`.
21
+
22
+ ### Added
23
+
24
+ - The fleet-playground demo (`demo/`, dev-only, never in `files[]`): a
25
+ single-file instrument console over a two-plane architecture -- Plane A a
26
+ decorated entity-VM fleet in a `capacityFor`-sized custom registry with
27
+ enforced ceilings, Plane B the telemetry signals in the default registry
28
+ driving five `@zakkster/lite-watch-ex` watchers (`watchUntil`,
29
+ `pausableWatch`, `watchChanged`, `watchMany`, `watchPrevious`). The DOM-free
30
+ core runs headless under the same GC-budget and dispose-storm gates the
31
+ library uses; the PD-29 registry wall is proven by a stats-delta (no watcher
32
+ ever forms an edge into the custom-registry fleet), not by assertion.
33
+ - README migration section: MobX 7 and signal-utils translation tables,
34
+ including the closing row MobX cannot write -- `disposeReactive(vm)` is one
35
+ idempotent, node-exact call after which every later touch throws by name.
36
+ - README emit-support matrix, generated from `test/fixtures/hashes.json` (9
37
+ entries) under drift-proof `EMIT-MATRIX` markers; `04-fixture-freshness`
38
+ asserts the README block equals the generator, so a re-emit that changes a
39
+ byte is loud, not silent.
40
+ - `llms.txt` per-feature forward floors documented alongside the peer range:
41
+ 1.6.0 for `createScope`, 1.9.0 for `Symbol.dispose`; the peer range floor
42
+ stays `>=1.5.0 <2.0.0`.
43
+ - `gate` script (`test/gate.mjs`): the section-10 pre-publish chain as captured
44
+ child processes -- fixtures, test, test:gc, torture (semantic + soak),
45
+ the TORTURE_BREAK control sweep, the non-blocking peer-preview lane,
46
+ the bench sink self-test, and `npm pack --dry-run` asserting exactly 7 files.
47
+ Every blocking step exits 0 or the gate exits non-zero; peer-preview is
48
+ reported, never gated.
49
+ - `Publications/` (dev-only, never shipped): per-channel release drafts and the
50
+ GitHub release notes, centered on the class-reactivity benchmark methodology
51
+ with an explicit invitation for competitor adapter PRs.
52
+
53
+ ### Changed
54
+
55
+ - Docs re-stamped to reality: Testing sections now read 214 tests across
56
+ fourteen files and 15 torture scenarios (13 run + 2 floor-gated skips), and
57
+ every numeric claim traces to a live stamp (`bench/results.txt`,
58
+ `decisions/0006`, or a gate tail). Version references across README and
59
+ llms.txt move to 1.0.0.
60
+ - The 16-export surface is frozen under semver at 1.0.0. The 0.x "frozen for
61
+ 0.x" note is retired in favor of the semver promise.
62
+
63
+ ### Fixed
64
+
65
+ - (Found in review, pass 1 charge C4) An inline-style violation in the demo
66
+ HTML -- a non-custom-property `style=` attribute against the demo CSS law --
67
+ was hoisted into the stylesheet. Custom-property `style="--var: value"` hooks
68
+ remain, per the law.
69
+ - (Found in the planner audit) The Testing and gates sections carried
70
+ 0.2.0-era numbers (171 tests, "12/12 scenarios", an 11-row file table) two
71
+ stages stale, in violation of the no-claim-without-a-stamp rule. Re-stamped
72
+ against the 1.0.0 tree.
73
+
74
+ ### Gate output (section-10 chain, archived verbatim)
75
+
76
+ ```
77
+ fixtures OK exit 0 -- emit fixtures regenerated
78
+ test OK exit 0 -- 214 pass / 0 fail
79
+ test:gc OK exit 0 -- 214 pass / 0 fail
80
+ torture OK exit 0 -- 13 passed, 2 skipped, 0 warned, 0 failed in 32.7s
81
+ torture:controls OK exit 0 -- 15 passed, 0 skipped, 0 warned, 0 failed in 1.9s
82
+ torture:peer-preview REPORTED NON-BLOCKING -- lane completed (exit 0) [preview 1.9.0-preview.6 SUITE-GREEN 15 passed, 0 skipped, 0 warned, 0 failed; canary 1.9.0-canary.1 SUITE-GREEN 15 passed, 0 skipped, 0 warned, 0 failed]
83
+ bench:selftest OK exit 0 -- ALL PASS -- 22 passed, 0 failed
84
+ pack OK exit 0 -- 7/7 files, no demo/ no Publications/
85
+ ----------------------------------------------------------------------
86
+ GATE PASS -- 7 blocking steps + 1 non-blocking (peer-preview)
87
+ ```
88
+
7
89
  ## [0.4.0] - 2026-08-26
8
90
 
9
91
  The introspection release: the surface grows 11 -> 16, every addition cold-path
@@ -276,6 +358,7 @@ Initial release -- the decorator core.
276
358
  - Torture skeleton (`@zakkster/lite-leak` + `@zakkster/lite-gc-profiler`):
277
359
  retention, conservation, lifecycle, and zero-GC lanes.
278
360
 
361
+ [1.0.0]: https://github.com/PeshoVurtoleta/lite-signal-decorators/releases/tag/v1.0.0
279
362
  [0.4.0]: https://github.com/PeshoVurtoleta/lite-signal-decorators/releases/tag/v0.4.0
280
363
  [0.3.0]: https://github.com/PeshoVurtoleta/lite-signal-decorators/releases/tag/v0.3.0
281
364
  [0.2.0]: https://github.com/PeshoVurtoleta/lite-signal-decorators/releases/tag/v0.2.0
package/README.md CHANGED
@@ -49,6 +49,7 @@ No base class to extend. No `makeObservable(this, {...})` mirror object to keep
49
49
  - [Design decisions worth knowing](#design-decisions-worth-knowing)
50
50
  - [Testing (for clients & QA)](#testing-for-clients--qa)
51
51
  - [Compatibility](#compatibility)
52
+ - [Migrating from MobX 7 & signal-utils](#migrating-from-mobx-7--signal-utils)
52
53
  - [What this is not](#what-this-is-not)
53
54
  - [Ecosystem](#ecosystem)
54
55
  - [FAQ](#faq) - [License](#license)
@@ -240,7 +241,7 @@ Symbol keys work (`Reflect.ownKeys`). A spec key colliding with an own property
240
241
  | `boxOf` | `(vm, key) => SignalBox \| ComputedBox` | The live engine box behind a `@reactive`/`@derived` member -- `.peek()`, `.subscribe()`, raw interop. Unknown key: named throw with a did-you-mean. After dispose: `ReactiveDisposedError`. |
241
242
  | `rootOf` | `(vm) => NodeDescriptor` | The instance's anchor descriptor -- feeds `forEachOwned` and lite-devtools. Throws `ReactiveDisposedError` after dispose. |
242
243
 
243
- ### Introspection & audit (0.4.0)
244
+ ### Introspection & audit (1.0.0)
244
245
 
245
246
  | Export | Signature | Behavior |
246
247
  |---|---|---|
@@ -256,7 +257,7 @@ With labels and audit off, the zero-GC budgets are byte-identical to 0.3.0 -- th
256
257
  | Export | Value |
257
258
  |---|---|
258
259
  | `ReactiveDisposedError` | `extends Error`; `name: "ReactiveDisposedError"`; fields `className`, `key`. Thrown on ANY touch of a disposed instance's surface. |
259
- | `VERSION` | `"0.4.0"` |
260
+ | `VERSION` | `"1.0.0"` |
260
261
 
261
262
  ### The rejection matrix
262
263
 
@@ -305,6 +306,27 @@ console.log(world.stats().activeNodes); // 0 -- every node returned to the pool
305
306
 
306
307
  The default registry never notices any of it: bound-registry churn leaves outside `stats()` frozen (torture-pinned). And because `boxOf` returns the *engine's* box, everything lite-signal composes with -- subscriptions, `peek`, batch, untrack, lite-raf frame effects -- composes with decorated members too.
307
308
 
309
+ ### Predicate-gated watchers (`@zakkster/lite-watch-ex`)
310
+
311
+ `lite-watch-ex` adds one-shot, pausable, and change-gated watchers over the same engine. Its sources are plain **thunks** (`() => vm.hp`, never a box handle), and every watcher creates its effect node in the **default registry** -- so wire one only to a **default-registry** instance (one with no `host.registry`), never across a custom-registry fleet, where the edge would cross a boundary the engine's default `dispose` cannot see:
312
+
313
+ ```js
314
+ import { watchUntil } from "@zakkster/lite-watch-ex";
315
+ import { defineReactive } from "@zakkster/lite-signal-decorators";
316
+
317
+ // Default-registry instance -- no host.registry, so it lives in the default graph.
318
+ const ReactivePlayer = defineReactive(class Player {}, { signals: { hp: 100 } });
319
+ const vm = new ReactivePlayer();
320
+
321
+ // Fires ONCE when hp crosses the threshold, then self-disposes:
322
+ watchUntil(() => vm.hp, (h) => h <= 25, (h) => console.log("low hp:", h));
323
+
324
+ vm.hp = 40; // predicate false -> no fire
325
+ vm.hp = 20; // predicate true -> "low hp: 20", watcher disposes itself
326
+ ```
327
+
328
+ The [`fleet-playground` demo](demo/fleet-playground.html) shows the safe split at scale: decorated entity VMs in an enforced custom registry, all watchers on a separate default-registry telemetry plane.
329
+
308
330
  ---
309
331
 
310
332
  ## The numbers
@@ -352,12 +374,12 @@ The ~7 ns over raw batch is the guarded thunk + rest-array the decorator allocat
352
374
  | `disposeReactive(vm)` | none | allocation-free success path; poison handles are prebuilt per member at decoration time |
353
375
  | `boxOf` / `rootOf` / any throw | cold path | introspection and failure paths may allocate; never on the hot path |
354
376
 
355
- The gates that hold it (run on every change, all green at 0.2.0):
377
+ The gates that hold it (run on every change, all green at 1.0.0):
356
378
 
357
- - `npm test` / `npm run test:gc` -- **171/171** on both lanes.
379
+ - `npm test` / `npm run test:gc` -- **214/214** on both lanes.
358
380
  - Suite gate (lite-leak + lite-gc-profiler): `leak=size 0/0 findings=0 warnings=0 | gc major=0 minor=0 maxMs=0.00 | ok`.
359
- - Torture: **12/12 scenarios** (zero-GC read/write lanes at `maxMajor 0, maxPauseMs 4`; 4096-cycle leak gate at 0 live / 0 findings / 0 warnings; capacity atomicity at every overflow point; a 300-seed x 20k-op oracle with zero divergences) -- plus **12/12 sabotage controls** proving each gate can actually fail.
360
- - `churn-soak`: sustained construct/use/dispose for a wall-clock budget; pools at floor and retained heap flat at every sample.
381
+ - Torture: **15 scenarios** (zero-GC read/write lanes at `maxMajor 0, maxPauseMs 4`; 4096-cycle leak gate at 0 live / 0 findings / 0 warnings; capacity atomicity at every overflow point; a 300-seed x 20k-op oracle with zero divergences) -- **13 run + 2 that skip correctly below their peer floors** (`scope-adoption` needs 1.6.0, `using-dispose` needs 1.9.0; the installed peer is 1.5.0). A skip *below* a floor is the forward-compat design working; a skip *at or above* it is a FAIL (run.mjs enforces floor-escalation). Every scenario carries a `TORTURE_BREAK` sabotage control that must exit non-zero -- **15/15 controls** prove each gate can actually fail.
382
+ - `churn-soak` + `fleet-soak`: sustained construct/use/dispose and a 10s 2k-VM fleet tick; pools at floor and retained heap flat at every sample.
361
383
 
362
384
  The cross-framework matrix lives in `bench/` (private, never shipped): six engines -- both our tiers, the hand-written `lite-raw-boxes` baseline, MobX 7, signal-utils/signal-polyfill, and a hand-rolled alien-signals class -- across seven class-shaped scenarios, checksum-verified for identical work, stamped into `bench/results.txt`. The formal verdicts are in [`decisions/0006-kill-criteria.md`](decisions/0006-kill-criteria.md): the decorated path measured **0.94x** the hand-written baseline on vm-write and **1.10x** on a 10k-instance fleet read (the 2.0x kill line cleared with margin), and **0 major + 0 minor GC over 4096 construct/use/dispose cycles** with pools at floor -- while emitting ~12.6x less transient garbage per churn run than the hand-rolled class it replaces.
363
385
 
@@ -382,18 +404,19 @@ Full rationale lives in [`decisions/`](decisions/) -- each is a numbered, dated
382
404
  ## Testing (for clients & QA)
383
405
 
384
406
  ```bash
385
- npm test # node --test, 171 tests
386
- npm run test:gc # the same 171 with --expose-gc (enables the allocation assertions)
407
+ npm test # node --test, 214 tests
408
+ npm run test:gc # the same 214 with --expose-gc (enables the allocation assertions)
409
+ npm run gate # the full pre-publish chain (section 10): fixtures -> test -> test:gc -> torture -> controls -> peer-preview (non-blocking) -> bench selftest -> pack
387
410
  ```
388
411
 
389
- **171 tests** across eleven files, all green at 0.2.0. The decorator protocol is tested three times over: against a mock Stage-3 emitter *and* against committed real TypeScript 5 and Babel `2023-11` emits, so both toolchains' codegen is pinned, not assumed.
412
+ **214 tests** across fourteen files, all green at 1.0.0. The decorator protocol is tested three times over: against a mock Stage-3 emitter *and* against committed real TypeScript 5 and Babel `2023-11` emits, so both toolchains' codegen is pinned, not assumed.
390
413
 
391
414
  | File | Tests | Covers |
392
415
  |---|---:|---|
393
416
  | `01-protocol-mock` | 30 | Decorator protocol on the mock Stage-3 emitter: wiring, values, options, rejection matrix |
394
417
  | `02-fixtures-ts` | 19 | The same laws on real TypeScript 5 emit (committed fixtures) |
395
418
  | `03-fixtures-babel` | 19 | The same laws on real Babel `2023-11` emit |
396
- | `04-fixture-freshness` | 1 | Fixture hashes match the sources (stale-emit guard) |
419
+ | `04-fixture-freshness` | 2 | Fixture hashes match the sources (stale-emit guard) + the README emit-matrix block matches its generator |
397
420
  | `05-wiring` | 5 | Anchor creation, wiring order, leaf-wires-once |
398
421
  | `06-dispose` | 6 | Cascade, idempotency, poison, `using` |
399
422
  | `07-qa-boundary` | 13 | S1 adversarial boundary pins |
@@ -401,19 +424,54 @@ npm run test:gc # the same 171 with --expose-gc (enables the allocation asse
401
424
  | `09-buildless` | 16 | `defineReactive` parity + the spec rejection matrix |
402
425
  | `10-qa-s2a-boundary` | 34 | Adversarial pins: identity guard, frozen dispose, registry heterogeneity, stacking |
403
426
  | `11-qa-s2b-boundary` | 8 | Construction-throw boundaries: init-phase drain, chain-base throws, overflow storms |
427
+ | `12-accounting` | 11 | `costOf` node/link/shape grid (double-probe, frozen + cached, fail-closed) + `capacityFor` budget sizing |
428
+ | `13-labels-audit` | 10 | `enableLabels`/`labelOf` per-registry identity + `auditReactive` leak reporting, both opt-in and default-OFF |
429
+ | `14-qa-s4-boundary` | 21 | S4 adversarial edges: stats-less facade closure, signals-only capacity floor, label/audit boundary matrix |
430
+
431
+ ### Emit-support matrix
432
+
433
+ Three fixture sources, two Stage-3 emitters, both emit lanes -- every cell below is a committed, hash-pinned fixture (the `04-fixture-freshness` guard above). The table is generated from the fixture manifest, so a re-emit that changes a byte is loud, not silent:
434
+
435
+ <!-- EMIT-MATRIX:START -->
436
+ Generated by `node test/fixtures/emit-matrix.mjs` from `test/fixtures/hashes.json` -- do not hand-edit. Toolchain pinned by the committed fixtures: **TypeScript 5.9.3**, **@babel/core 7.29.7** + **@babel/plugin-proposal-decorators 7.29.7** (`version: 2023-11`). Each `sha256` is the first 12 hex of the committed emit; `npm run fixtures` regenerates and `test/04-fixture-freshness` fails loudly on any drift.
437
+
438
+ | Source | Emitter | Emit lane | Compiled output | sha256 | At decoration time |
439
+ |---|---|---|---|---|---|
440
+ | `fixture.src.ts` | TypeScript 5 | standard 2023-11 | `ts-out/fixture.src.js` | `1a3fc0f943bf` | accepted -- full decorator surface wired + pinned green |
441
+ | `fixture.src.ts` | Babel | standard 2023-11 | `babel-out/fixture.src.js` | `eb9dfb5939b1` | accepted -- full decorator surface wired + pinned green |
442
+ | `static.src.ts` | TypeScript 5 | standard 2023-11 | `ts-out/static.src.js` | `d2a03e3d5f70` | rejected -- static member is a named throw at decoration time |
443
+ | `static.src.ts` | Babel | standard 2023-11 | `babel-out/static.src.js` | `dc936c5aa235` | rejected -- static member is a named throw at decoration time |
444
+ | `legacy.src.ts` | TypeScript 5 | legacy (experimental) | `ts-legacy-out/legacy.src.js` | `c1059b1d37b1` | rejected -- legacy emit -> named rejection at decoration time |
445
+ | `legacy.src.ts` | Babel | legacy (experimental) | `babel-legacy-out/legacy.src.js` | `1d35a02c57ce` | rejected -- legacy emit -> named rejection at decoration time |
446
+
447
+ Source hashes: `fixture.src.ts` `fb492a396340`, `static.src.ts` `81fb649965e6`, `legacy.src.ts` `30ac3dabaf7c`.
448
+ <!-- EMIT-MATRIX:END -->
404
449
 
405
450
  ### The torture suite (dev-side, never shipped)
406
451
 
407
452
  Process-isolated stress scenarios built on `@zakkster/lite-leak` + `@zakkster/lite-gc-profiler`:
408
453
 
409
454
  ```bash
410
- npm run torture # all 12 scenarios
455
+ npm run torture # all 15 scenarios (13 run + 2 floor-gated skips)
411
456
  npm run torture:semantic # the correctness lane (CI)
412
- npm run torture:soak # the wall-clock churn soak
457
+ npm run torture:soak # the wall-clock churn + fleet soaks
413
458
  npm run torture:controls # sabotage self-test: every scenario must FAIL when broken
414
459
  ```
415
460
 
416
- Twelve scenarios: emit-matrix, ordering, lifecycle, pool-conservation, zero-GC lanes, capacity atomicity (every overflow point x both construction paths), the full disposed-poison surface + resurrection storms, a 4096-cycle lite-leak gate, a **300-seed x 20k-op oracle fuzzer** (decorated vs hand-wired raw twin in lockstep: every derived value, every effect fire count, every graph opcode tally), raw/decorated interop + cross-registry + `registry.destroy()` contracts, batch/untrack semantics, and the churn soak. Every scenario carries a `TORTURE_BREAK` sabotage control that must exit non-zero -- a gate that cannot fail is not a gate. Seeded lanes replay exactly via `TORTURE_SEED`.
461
+ Fifteen scenarios: emit-matrix, ordering, lifecycle, pool-conservation, zero-GC lanes, capacity atomicity (every overflow point x both construction paths), the full disposed-poison surface + resurrection storms, a 4096-cycle lite-leak gate, a **300-seed x 20k-op oracle fuzzer** (decorated vs hand-wired raw twin in lockstep: every derived value, every effect fire count, every graph opcode tally), raw/decorated interop + cross-registry + `registry.destroy()` contracts, batch/untrack semantics, the wall-clock churn soak, and a 10s 2k-VM fleet soak -- plus two forward-compat scenarios (`scope-adoption`, `using-dispose`) that **skip correctly** while the installed peer sits below their per-feature floors (1.6.0 `createScope`, 1.9.0 `Symbol.dispose`). A skip below a floor is the design working; a skip at or above it is a FAIL. On the installed 1.5.0 peer: 13 pass, 2 skip. Every scenario carries a `TORTURE_BREAK` sabotage control that must exit non-zero -- a gate that cannot fail is not a gate. Seeded lanes replay exactly via `TORTURE_SEED`.
462
+
463
+ ### The fleet demo (dev-side, never shipped)
464
+
465
+ A single-file instrument console -- [`demo/fleet-playground.html`](demo/fleet-playground.html) -- drives a two-plane capacity fleet: decorated entity VMs in an enforced custom registry, telemetry watchers in the default registry. Its DOM-free core runs headless under the same gates the library uses:
466
+
467
+ ```bash
468
+ npm run demo:build # esbuild bundle -> demo/bundle.js + rewrite demo/bundle.sha256
469
+ npm run demo:check # verify the committed bundle matches its recorded hash
470
+ npm run demo:gc # headless GC-budget lane over the fleet core (maxMajor 0)
471
+ npm run demo:storm # headless dispose-storm retention lane (lite-leak, size 0)
472
+ ```
473
+
474
+ The `demo/` directory is dev-only -- it never enters `package.json` `files[]` and never ships to consumers.
417
475
 
418
476
  ---
419
477
 
@@ -430,12 +488,45 @@ Native (untranspiled) `@` decorators in engines: not shipped anywhere yet -- unt
430
488
 
431
489
  ---
432
490
 
491
+ ## Migrating from MobX 7 & signal-utils
492
+
493
+ The decorator vocabulary maps almost one-to-one; what changes is the lifetime story. Both libraries below leave teardown to the garbage collector -- this package makes it a single deterministic call.
494
+
495
+ ### From MobX 7
496
+
497
+ | MobX 7 | lite-signal-decorators |
498
+ |---|---|
499
+ | `@observable accessor x` | `@reactive accessor x` |
500
+ | `@computed get y()` | `@derived get y()` |
501
+ | `@action m()` | `@batched m()` |
502
+ | `makeObservable(this, {...})` | `@reactiveHost` -- one wiring site, no mirror object to keep in sync |
503
+ | `reaction(...)` / `autorun(...)` | `@reactiveEffect m()` |
504
+ | reaction disposers only; the instance itself is never disposable | **`disposeReactive(vm)` -- one call, idempotent, node-exact, and every later touch throws by name. MobX has no equivalent; its per-instance graph ends when the collector decides.** |
505
+
506
+ ### From signal-utils
507
+
508
+ Verified against the installed `signal-utils@0.21.1`: `@signal` (on accessors or getters) and `@cached` (on getters) are the two decorators in its surface.
509
+
510
+ | signal-utils 0.21 | lite-signal-decorators |
511
+ |---|---|
512
+ | `@signal accessor x` (or `@signal get x`) | `@reactive accessor x` |
513
+ | `@cached get y()` | `@derived get y()` |
514
+ | no disposal API at all | `disposeReactive(vm)` -- **and it disposes**: cascade teardown, poison swap, node-exact conservation |
515
+ | Stage-3 build required | `defineReactive(Class, spec)` -- the buildless door signal-utils has no equivalent for |
516
+
517
+ The cross-framework numbers behind this table are stamped in [`decisions/0006-kill-criteria.md`](decisions/0006-kill-criteria.md) (both engines measured through their documented class APIs at checksum-identical work).
518
+
519
+ ---
520
+
433
521
  ## What this is not
434
522
 
435
523
  - **Not a home for module-level or global signals.** That is raw `lite-signal` territory; `static` members are rejected by design.
524
+ - **Not a deep/proxy observation layer.** No `observable.deep`, no wrapped Arrays/Maps/Sets, no proxy magic -- the reactive unit is a declared member, not a traversed object graph. Collections are `@zakkster/lite-project` territory.
436
525
  - **Not a per-frame action system.** `@batched` costs a measured thunk per call -- fine for "one call per user intent", wrong inside a render loop. Per-frame hot lanes stay on plain accessor writes (and frame *scheduling* belongs to `lite-raf`).
437
526
  - **Not a framework, renderer, or component model.** It ends at the reactive view-model; DOM binding is `lite-signal-dom`'s job.
438
- - **Not a MobX API shim.** No `makeObservable`, no administration objects, no proxy magic -- and no GC-based cleanup: disposal is explicit, deterministic, and verified, because "the collector will get it eventually" is not a lifecycle.
527
+ - **Not a general meta-programming kit.** Five decorators, one wiring law -- not an open decorator toolbox. It does one thing: turn a class into a reactive view-model with a provable lifetime.
528
+ - **Not a MobX API shim.** No `makeObservable`, no administration objects -- and no GC-based cleanup: disposal is explicit, deterministic, and verified, because "the collector will get it eventually" is not a lifecycle.
529
+ - **Not a legacy-decorators consumer.** TypeScript `experimentalDecorators` emit is detected by call shape at decoration time and rejected with a named error -- never "works differently under legacy".
439
530
  - **Not usable as `@` syntax without a toolchain** -- that is exactly what `defineReactive` exists for.
440
531
 
441
532
  ---
@@ -448,6 +539,7 @@ Native (untranspiled) `@` decorators in engines: not shipped anywhere yet -- unt
448
539
  | `@zakkster/lite-signal-dom` | DOM bindings for the same engine -- where a view-model meets actual elements. |
449
540
  | `@zakkster/lite-raf` | Frame-rate scheduling for the same graph; the frame-coalescing pattern the `scheduler` option on `@reactiveEffect` exists to plug into. |
450
541
  | `@zakkster/lite-devtools` | Graph inspection; `rootOf(vm)` + `forEachOwned` is the hook it walks. |
542
+ | [`@zakkster/lite-watch-ex`](https://www.npmjs.com/package/@zakkster/lite-watch-ex) | One-shot / predicate-gated / pausable watchers over the same engine; thunk sources, default-registry effects -- see the [registry note](#composability) before pointing one at a decorated member. |
451
543
  | `@zakkster/lite-leak` + `@zakkster/lite-gc-profiler` | The dev-side harness that proves every retention and allocation claim in this README. Never shipped to consumers. |
452
544
 
453
545
  ---
@@ -484,7 +576,7 @@ Because an instance whose base-class boxes live in one pool and whose subclass b
484
576
  No, and the README says so with numbers: 22.12 ns/op vs 15.25 raw vs 11.67 unbatched on the reference rig -- a thunk + rest-array per call. Use it for actions; keep per-frame writes on plain accessors.
485
577
 
486
578
  **Where are `costOf`, labels, the audit hook, private members?**
487
- The first three landed in 0.4.0 -- `costOf`/`capacityFor` (measured capacity accounting), `enableLabels`/`labelOf` (devtools identity), and `auditReactive` (leak audit), all cold-path or opt-in with the hot canon untouched. Private-member support remains out. The `llms.txt` scope note tracks exactly what is and isn't included.
579
+ All three are part of the frozen 1.0.0 surface -- `costOf`/`capacityFor` (measured capacity accounting), `enableLabels`/`labelOf` (devtools identity), and `auditReactive` (leak audit), all cold-path or opt-in with the hot canon untouched. Private-member support remains out. The `llms.txt` scope note tracks exactly what is and isn't included.
488
580
 
489
581
  ---
490
582
 
@@ -366,4 +366,4 @@ export class ReactiveDisposedError extends Error {
366
366
  // --- Version ------------------------------------------------------------------
367
367
 
368
368
  /** Package version. Kept in lockstep with package.json and llms.txt. */
369
- export const VERSION: "0.4.0";
369
+ export const VERSION: "1.0.0";
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @zakkster/lite-signal-decorators v0.4.0
2
+ * @zakkster/lite-signal-decorators v1.0.0
3
3
  * --------------------
4
4
  * Stage-3 decorator layer over @zakkster/lite-signal. Turns a plain class into
5
5
  * a reactive view-model with measured per-instance cost and deterministic
@@ -1661,4 +1661,4 @@ export function auditReactive(on) {
1661
1661
  // --- Version ------------------------------------------------------------------
1662
1662
 
1663
1663
  /** Package version. Kept in lockstep with package.json and llms.txt. */
1664
- export const VERSION = "0.4.0";
1664
+ export const VERSION = "1.0.0";
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # @zakkster/lite-signal-decorators
2
2
 
3
- VERSION 0.4.0
3
+ VERSION 1.0.0
4
4
 
5
5
  > Stage-3 decorator layer over @zakkster/lite-signal. Turns a plain class into a
6
6
  > reactive view-model where each instance has a measured per-property cost, a
@@ -68,7 +68,7 @@ slot for a poison handle, so any later read/write throws a named
68
68
  `FinalizationRegistry` reports any instance GC'd without `disposeReactive`.
69
69
  - `ReactiveDisposedError` -- `extends Error`, `name` `"ReactiveDisposedError"`,
70
70
  fields `className` and `key`.
71
- - `VERSION` -- `"0.4.0"`.
71
+ - `VERSION` -- `"1.0.0"`.
72
72
 
73
73
  ## Registry law (one registry per host chain)
74
74
 
@@ -185,14 +185,26 @@ throw a named error.
185
185
  Requires `@zakkster/lite-signal` `>=1.5.0 <2.0.0` (peer dependency). Uses
186
186
  `signalBox`, `computedBox`, `effect`, `createRoot`, `getOwner`, `runWithOwner`,
187
187
  `dispose`, `nodeId`, `isTracking`, `batch`, and `untrack` from it (all 11 also
188
- routed per-registry via `createRegistry()`).
188
+ routed per-registry via `createRegistry()`). The RANGE floor is 1.5.0 and stays
189
+ 1.5.0 -- everything the runtime surface needs shipped in 1.5.0 stable. Two
190
+ forward-compat torture scenarios are feature-detected above that floor and SKIP
191
+ below it (a skip below the floor is correct; at or above it is a failure): the
192
+ `createScope` adoption scenario needs peer 1.6.0, and the engine
193
+ `[Symbol.dispose]`/`using` interop scenario needs peer 1.9.0. Neither is a
194
+ runtime requirement -- the shipped surface runs on 1.5.0.
189
195
 
190
196
  ## Scope note
191
197
 
192
- 0.4.0 adds the S4 introspection surface -- 16 exports: the 11 runtime
193
- exports plus `costOf`, `capacityFor`, `enableLabels`, `labelOf`, and
194
- `auditReactive` (all cold / opt-in; the hot accessor canon is byte-identical to
195
- 0.3.0). Also present since 0.3.0: the dev-side class-reactivity benchmark
196
- (`bench/`, private, never shipped) and the fleet-soak torture scenario; the
197
- formal kill-criteria verdicts live in decisions/0006-kill-criteria.md. Not yet
198
- included: private-member support.
198
+ 1.0.0 FREEZES the export surface -- 16 exports: the 11 runtime exports plus
199
+ `costOf`, `capacityFor`, `enableLabels`, `labelOf`, and `auditReactive` (all
200
+ cold / opt-in; the hot accessor canon is byte-identical to 0.3.0). The semver
201
+ promise from here: any change to an existing export's signature or behavior is a
202
+ MAJOR, recorded in a decision file; new exports are minors; the hot accessor
203
+ canon (`makeGet`/`makeSet`) does not move without a major. Also present since
204
+ 0.3.0: the dev-side class-reactivity benchmark (`bench/`, private, never
205
+ shipped) and the fleet-soak torture scenario; the formal kill-criteria verdicts
206
+ live in decisions/0006-kill-criteria.md. Still OUT of the 1.0 surface (each a
207
+ deliberate, documented exclusion, not an oversight): private (`#`) members,
208
+ static members, module/global signals (raw lite-signal territory), and any
209
+ instance reinitialization / pooling API -- an instance is single-lifetime,
210
+ disposed once, never revived.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zakkster/lite-signal-decorators",
3
- "version": "0.4.0",
3
+ "version": "1.0.0",
4
4
  "description": "Stage-3 decorator layer over @zakkster/lite-signal. The reactive class layer where an instance has a measured cost, deterministic teardown, and a churn benchmark.",
5
5
  "author": "Zahary Shinikchiev <shinikchiev@yahoo.com>",
6
6
  "license": "MIT",
@@ -67,6 +67,7 @@
67
67
  "torture:soak": "node test/torture/run.mjs --group soak",
68
68
  "torture:controls": "node test/torture/run.mjs --controls",
69
69
  "torture:peer-preview": "node test/torture/peer-preview.mjs",
70
+ "gate": "node test/gate.mjs",
70
71
  "spikes": "for f in spikes/*.mjs; do echo \"== $f ==\"; node --expose-gc \"$f\" || exit 1; done",
71
72
  "spike:emit": "node --expose-gc spikes/emit/probe.mjs",
72
73
  "spike:ownership": "node --expose-gc spikes/ownership.mjs",
@@ -74,7 +75,11 @@
74
75
  "spike:manual": "node --expose-gc spikes/manual-call.mjs",
75
76
  "spike:poison": "node --expose-gc spikes/poison.mjs",
76
77
  "spike:buildless": "node --expose-gc spikes/buildless.mjs",
77
- "fixtures:regen": "node spikes/emit/regen.mjs"
78
+ "fixtures:regen": "node spikes/emit/regen.mjs",
79
+ "demo:build": "node demo/build.mjs",
80
+ "demo:check": "node demo/build.mjs --check",
81
+ "demo:gc": "node --expose-gc demo/gc-lane.mjs",
82
+ "demo:storm": "node --expose-gc demo/storm-lane.mjs"
78
83
  },
79
84
  "peerDependencies": {
80
85
  "@zakkster/lite-signal": ">=1.5.0 <2.0.0"
@@ -87,6 +92,8 @@
87
92
  "@zakkster/lite-gc-profiler": "^1.16.0",
88
93
  "@zakkster/lite-leak": "^1.10.0",
89
94
  "@zakkster/lite-signal": "1.5.0",
95
+ "@zakkster/lite-watch-ex": "^1.1.0",
96
+ "esbuild": "0.28.2",
90
97
  "typescript": "^5.6.0"
91
98
  }
92
99
  }