ivue 2.2.1 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,52 +10,35 @@
10
10
  <h3 align="center">Plain classes. Full reactivity.<br>Infinite scalability. One kilobyte.</h3>
11
11
 
12
12
  <p align="center">
13
- <a href="https://www.npmjs.com/package/ivue"><img src="https://img.shields.io/npm/v/ivue.svg" alt="npm"></a>
13
+ <a href="https://www.npmjs.com/package/ivue"><img src="https://img.shields.io/npm/v/ivue.svg" alt="npm version"></a>
14
+ <a href="https://www.npmjs.com/package/ivue"><img src="https://img.shields.io/npm/dm/ivue.svg" alt="npm downloads"></a>
14
15
  <a href="https://github.com/infinite-system/ivue/actions/workflows/ci.yml"><img src="https://github.com/infinite-system/ivue/actions/workflows/ci.yml/badge.svg?branch=main" alt="build status"></a>
16
+ <img src="https://img.shields.io/badge/coverage-100%25-34d399" alt="100% test coverage">
17
+ <img src="https://img.shields.io/badge/gzipped-1.1%20kB-818cf8" alt="1.1 kB gzipped">
18
+ <a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT license"></a>
15
19
  </p>
16
20
 
17
21
  <p align="center">
18
- <strong>Docs: <a href="https://ivue.dev/">ivue.dev</a></strong>
22
+ <a href="https://ivue.dev/">Docs</a> ·
23
+ <a href="https://ivue.dev/guide/getting-started">Getting Started</a> ·
24
+ <a href="https://ivue.dev/guide/standard">The Standard</a> ·
25
+ <a href="https://ivue.dev/examples/">Examples</a> ·
26
+ <a href="https://ivue.dev/guide/benchmarks">Benchmarks</a> ·
27
+ <a href="https://ivue.dev/blog/">Blog</a> ·
28
+ <a href="https://ivue.dev/community">Community</a>
19
29
  </p>
20
30
 
31
+ ---
32
+
21
33
  ivue builds Vue 3 reactivity out of plain TypeScript classes. Real
22
34
  inheritance, real encapsulation, real polymorphism — on ordinary objects,
23
- with nothing paid until first access. The whole engine is **1.1kb gzipped**
35
+ with nothing paid until first access. The whole engine is **1.1 kB gzipped**
24
36
  with zero dependencies.
25
37
 
26
- - **Native class API** — `extends`, `super`, getters, setters, private
27
- fields. Real inheritance, encapsulation and polymorphism, all reactive.
28
- - **Zero-cost creation** — instances are plain objects. A million of them
29
- take 22 ms — 6 to 132× faster than the alternatives.
30
- - **One kilobyte** — 1.1kb gzipped, zero dependencies, 100% test coverage.
31
- Stripped to the load-bearing core — an API you can hold in your head.
32
- - **Store or ViewModel** — the same class serves as a global store, a
33
- component ViewModel, or a domain model. One mental model everywhere.
34
- - **Composition API, fully compatible** — composables plug in through
35
- `$`-getters. The entire Vue ecosystem works inside your classes.
36
- - **TypeScript first** — writable ref-getters, fully typed instances,
37
- precise inference. The type system shaped the engine's design.
38
-
39
- ## Getting started
40
-
41
38
  ```sh
42
39
  npm i ivue vue
43
40
  ```
44
41
 
45
- ## Built for humans and AI
46
-
47
- ivue ships with an [Operating Manual](https://ivue.dev/guide/standard) —
48
- the complete authoring standard as annotated templates, rules, and a review
49
- checklist. It reads as documentation and works as a drop-in skill for AI
50
- coding agents, so generated code follows the same standard your team writes:
51
-
52
- ```sh
53
- npx ivue skill # installs .claude/skills/ivue/SKILL.md, version-locked
54
- npx ivue skill --all # + Codex/Cursor/Copilot where already in use
55
- ```
56
-
57
- ### Usage
58
-
59
42
  ```ts
60
43
  import { Reactive } from 'ivue';
61
44
  import { ref } from 'vue';
@@ -79,8 +62,6 @@ export namespace Counter {
79
62
  }
80
63
  ```
81
64
 
82
- In a component:
83
-
84
65
  ```vue
85
66
  <script setup lang="ts">
86
67
  import { Counter } from './Counter';
@@ -98,19 +79,20 @@ const { count } = counter;
98
79
 
99
80
  Full walkthrough: [Getting Started](https://ivue.dev/guide/getting-started).
100
81
 
101
- ## Hard problems, solved together
102
-
103
- Each of these sank earlier class-reactivity attempts. ivue ships all of them
104
- as one coherent design:
82
+ ## Why classes, why now
105
83
 
106
- - **Bound methods** — `this.method` is always correct, always the same reference.
107
- - **Reactive inheritance** deep `super.x.value` chains resolve level-safe.
108
- - **Development parity** — the same class identity, direct binding, and engine branches in development and production.
109
- - **Circular import immunity** the namespace pattern resolves mutual references in any load order.
110
- - **Writable getter types** — ref-returning getters type as writable; instances fully inferred.
111
- - **Deterministic teardown** `$watch` scopes per instance, `$stopEffects()` cleans up.
112
- - **Minimal memory footprint** — derivations are shared prototype getters, not per-instance allocations.
113
- - **Hot paths** reads hoist to native ref speed with one line where it matters.
84
+ - **Native class API** — `extends`, `super`, getters, setters, private
85
+ fields. Real inheritance, encapsulation and polymorphism, all reactive.
86
+ - **Zero-cost creation** — instances are plain objects. A million of them
87
+ take 22 ms6 to 132× faster than the alternatives.
88
+ - **One kilobyte** — 1.1 kB gzipped, zero dependencies, 100% test coverage.
89
+ Stripped to the load-bearing corean API you can hold in your head.
90
+ - **Store or ViewModel** — the same class serves as a global store, a
91
+ component ViewModel, or a domain model. One mental model everywhere.
92
+ - **Composition API, fully compatible** — composables plug in through
93
+ `$`-getters. The entire Vue ecosystem works inside your classes.
94
+ - **TypeScript first** — writable ref-getters, fully typed instances,
95
+ precise inference. The type system shaped the engine's design.
114
96
 
115
97
  ## One idea, carried through
116
98
 
@@ -120,10 +102,8 @@ plain and re-derives on every read — reactive with zero allocation. Methods
120
102
  bind themselves once, to the right `this`. Instances stay ordinary objects:
121
103
  no proxy wraps them, no work happens at construction.
122
104
 
123
- Inheritance, teardown, development parity, speed — consequences of that one move.
124
-
125
- Composables plug straight in — the entire Vue ecosystem works inside your
126
- classes:
105
+ Inheritance, teardown, development parity, speed — consequences of that one
106
+ move. And composables plug straight in:
127
107
 
128
108
  ```ts
129
109
  import { useMouse } from '@vueuse/core';
@@ -141,10 +121,73 @@ class $Pointer {
141
121
  }
142
122
  ```
143
123
 
124
+ ## Hard problems, solved together
125
+
126
+ Each of these sank earlier class-reactivity attempts. ivue ships all of them
127
+ as one coherent design:
128
+
129
+ - **Bound methods** — `this.method` is always correct, always the same reference.
130
+ - **Reactive inheritance** — deep `super.x.value` chains resolve level-safe.
131
+ - **Development parity** — the same class identity, direct binding, and engine branches in development and production.
132
+ - **[Circular import immunity](https://ivue.dev/blog/circular-imports-dissolved)** — the namespace pattern resolves mutual references in any load order.
133
+ - **Writable getter types** — ref-returning getters type as writable; instances fully inferred.
134
+ - **Deterministic teardown** — `$watch` scopes per instance, `$stopEffects()` cleans up.
135
+ - **Minimal memory footprint** — derivations are shared prototype getters, not per-instance allocations.
136
+ - **Hot paths** — reads hoist to native ref speed with one line where it matters.
137
+
138
+ ## `Static()` — capability classes <sub>(`ivue/extras`, +0.5 kB)</sub>
139
+
140
+ The same discipline for the class-level surface:
141
+ [`Static()`](https://ivue.dev/guide/static) makes static methods lazy-bound
142
+ and `$`-prefixed static getters cached **per receiver** — a lazy singleton,
143
+ an inheritance-aware store, an override seam, and a test boundary in one
144
+ declaration. It retires
145
+ [module-level state](https://ivue.dev/blog/module-level-state) outright, and
146
+ it has no Vue dependency — the identical idiom runs under Node and Bun:
147
+
148
+ ```ts
149
+ import { Static } from 'ivue/extras';
150
+
151
+ class $TextSegmentation {
152
+ protected static get $segmenter() {
153
+ return new Intl.Segmenter(undefined, { granularity: 'grapheme' });
154
+ }
155
+ }
156
+
157
+ export namespace TextSegmentation {
158
+ export const $Class = Static($TextSegmentation); // anchor — children extend this
159
+ export let Class = $Class;
160
+ }
161
+ ```
162
+
163
+ ## Proven at scale
164
+
165
+ <p align="center">
166
+ <a href="https://ivue.dev/examples/invar">
167
+ <img src="docs_v2/public/invar-editor.svg" alt="Invar — a terminal IDE built on ivue" width="720">
168
+ </a>
169
+ </p>
170
+
171
+ [**Invar**](https://ivue.dev/examples/invar) is ivue at full scale: a
172
+ complete terminal IDE — editor, workspace search, git, terminals, LSP,
173
+ agents — running on ivue classes under Bun, with no DOM and no Vue
174
+ components. **94,000 source lines, 345 classes, 35 invariant contracts,
175
+ zero import cycles**, built almost entirely by AI agents holding the
176
+ [Standard](https://ivue.dev/guide/standard) as their base discipline.
177
+
178
+ And at the other end of scale on the web: a
179
+ [1,000,000-row virtual scroller](https://ivue.dev/examples/virtual-scroller),
180
+ a [20,000,000-cell flyweight grid](https://ivue.dev/examples/flyweight-grid)
181
+ at 4.7 bytes per live cell, and
182
+ [production-grade Quasar field components](https://ivue.dev/examples/choose-field) —
183
+ all with full source on the page.
184
+
144
185
  ## The numbers
145
186
 
146
- Measured, not promised — method and live in-browser benchmarks in
147
- [the docs](https://ivue.dev/guide/benchmarks).
187
+ Measured, not promised — every number carries its method, and the
188
+ load-bearing benchmarks
189
+ [run live in your browser](https://ivue.dev/guide/benchmarks) on the
190
+ shipped engine:
148
191
 
149
192
  | creating 1,000,000 instances | time | ivue is |
150
193
  | --- | --- | --- |
@@ -164,22 +207,49 @@ Taken all the way down: a fully reactive spreadsheet model holding
164
207
  floor — because in ivue, everything costs proportional to what's *observed*,
165
208
  nothing costs proportional to what *exists*.
166
209
 
210
+ ## Built for humans and AI
211
+
212
+ ivue ships with a
213
+ [Standard Operating Manual](https://ivue.dev/guide/standard) — the complete
214
+ authoring standard as annotated templates, rules, and a review checklist. It
215
+ reads as documentation and works as a drop-in skill for AI coding agents, so
216
+ generated code follows the same standard your team writes:
217
+
218
+ ```sh
219
+ npx ivue skill # installs .claude/skills/ivue/SKILL.md, version-locked
220
+ npx ivue skill --all # + Codex/Cursor/Copilot where already in use
221
+ ```
222
+
223
+ Agents holding the Standard have
224
+ [derived correct patterns its own author never wrote](https://ivue.dev/blog/patterns-the-author-never-wrote) —
225
+ the manual is a generator, not a catalog. The wider argument:
226
+ [Reactive framework for the AI era](https://ivue.dev/blog/reactive-framework-for-the-ai-era).
227
+
228
+ ## Go deeper
229
+
230
+ - [Fundamental Principles](https://ivue.dev/guide/principles) — the design, from first ideas
231
+ - [The Engine](https://ivue.dev/engine) — how the transform works, internals annotated
232
+ - [Advanced Patterns](https://ivue.dev/guide/namespace-pattern) — Namespace, Computed Seed, Keyed Version Signals, Flyweight, Static, Backend ivue
233
+ - [The Blog](https://ivue.dev/blog/) — 30 posts of measured argument, from [circular imports](https://ivue.dev/blog/circular-imports-dissolved) to [what JavaScript becomes](https://ivue.dev/blog/what-javascript-becomes)
234
+
235
+ ## Community
236
+
237
+ Questions, ideas, bug reports — there's no ticket queue, just the people who
238
+ write the code: [Discussions](https://github.com/infinite-system/ivue/discussions) ·
239
+ [Issues](https://github.com/infinite-system/ivue/issues) ·
240
+ [Discord](https://discord.gg/8MgZNsrfv) ·
241
+ [X @evgenykalash](https://x.com/evgenykalash) ·
242
+ [Community page](https://ivue.dev/community)
243
+
167
244
  ## A note on the size
168
245
 
169
- The complete engine is **1.1kb gzipped**: lazy prototype transformation,
246
+ The complete engine is **1.1 kB gzipped**: lazy prototype transformation,
170
247
  bound methods, inheritance, lifecycle ownership, and the public utilities.
171
248
  Development uses that same engine without a second hot-update execution path.
172
249
 
173
250
  > *Perfection is achieved, not when there is nothing more to add, but when
174
251
  > there is nothing left to take away.* — Antoine de Saint-Exupéry
175
252
 
176
- ## Documentation
177
-
178
- Full guide, principles, live benchmarks, advanced examples (a 1,000,000-row
179
- virtual scroller, a 20,000,000-cell flyweight grid, production-grade Quasar
180
- field components), and the API reference:
181
- **https://ivue.dev/**
182
-
183
253
  ## License
184
254
 
185
255
  [MIT](./LICENSE)
@@ -362,6 +362,70 @@ describe('Static $-cached getters', () => {
362
362
  expect(open()).toBe(3); // detached, still bound
363
363
  });
364
364
 
365
+ it('the anchor shape: $Class = Static($X), Class = Reactive($Class)', () => {
366
+ class $Panel {
367
+ constructor(public seed: number) {}
368
+ get width() {
369
+ return ref(this.seed);
370
+ }
371
+ static get glyph() {
372
+ return '#'; // live knob
373
+ }
374
+ static get $legend() {
375
+ return { marker: this.glyph.repeat(2) };
376
+ }
377
+ static describe() {
378
+ return this.$legend.marker;
379
+ }
380
+ }
381
+
382
+ const Anchor = Static($Panel);
383
+ const Panel = Reactive(Anchor);
384
+
385
+ // instance side: full Reactive semantics through the wrapper
386
+ const panel: any = new Panel(7);
387
+ const widthCell = panel.width;
388
+ expect(widthCell.value).toBe(7);
389
+ expect(panel.width).toBe(widthCell); // cached cell
390
+ panel.$stopEffects();
391
+ expect(panel.width).not.toBe(widthCell); // teardown reset works
392
+
393
+ // static side: caches + binding live on the same published class
394
+ expect(Panel.$legend).toBe(Panel.$legend);
395
+ const describe = Panel.describe;
396
+ expect(describe()).toBe('##');
397
+
398
+ // Reactive() is identity, so anchor and published class are one object
399
+ expect(Panel).toBe(Anchor);
400
+ });
401
+
402
+ it('a bare double extending the anchor inherits working semantics', () => {
403
+ class $Momentum {
404
+ static get friction() {
405
+ return 2; // knob
406
+ }
407
+ static get $atRest() {
408
+ return { threshold: this.friction * 10 };
409
+ }
410
+ static settle() {
411
+ return this.$atRest.threshold;
412
+ }
413
+ }
414
+
415
+ const Anchor = Static($Momentum);
416
+ // the downstream case: a pinch-knob double with NO wrap of its own
417
+ class TunedMomentum extends Anchor {
418
+ static override get friction() {
419
+ return 7;
420
+ }
421
+ }
422
+
423
+ expect(Anchor.$atRest.threshold).toBe(20); // parent reads FIRST
424
+ expect(TunedMomentum.$atRest.threshold).toBe(70); // child still derives itself
425
+ const settle = TunedMomentum.settle;
426
+ expect(settle()).toBe(70); // inherited method binds to the child, detached
427
+ });
428
+
365
429
  it('walks the raw inheritance chain — ancestor $-getters cache per receiver', () => {
366
430
  class $Base {
367
431
  static get scale() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ivue",
3
- "version": "2.2.1",
3
+ "version": "2.2.2",
4
4
  "description": "Infinite Vue – Class Based Architecture for Vue 3",
5
5
  "type": "module",
6
6
  "exports": {
@@ -37,10 +37,12 @@
37
37
  "cypress:headed": "cypress run --component --browser chrome --headed --no-exit",
38
38
  "build": "tsc --version;tsc --p ./tsconfig.json && vite build",
39
39
  "build:demo": "tsc --p ./tsconfig.json && vite build demo",
40
- "build:docs": "npm run sync:examples && npm --prefix docs_v2 run build",
40
+ "build:docs": "npm run sync:examples && npm --prefix docs_v2 run build && npm run check:links",
41
+ "check:links": "node docs_v2/scripts/check-links.mjs",
41
42
  "render:og": "node docs_v2/scripts/brand-image-generator.mjs og",
42
43
  "render:form-header": "node docs_v2/scripts/brand-image-generator.mjs form-header",
43
44
  "render:banner": "node docs_v2/scripts/brand-image-generator.mjs blog",
45
+ "sync:blog-dates": "node docs_v2/scripts/blog-dates-generator.mjs",
44
46
  "preview:demo": "npm run build:demo && vite preview demo --host",
45
47
  "preview:docs": "npm --prefix docs_v2 run preview",
46
48
  "release": "npm run build && npm publish",
@@ -134,5 +136,6 @@
134
136
  "license": "MIT",
135
137
  "bin": {
136
138
  "ivue": "bin/ivue.mjs"
137
- }
139
+ },
140
+ "packageManager": "npm@11.6.2"
138
141
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: ivue
3
- description: Use when writing or editing ivue `Reactive()` classes, converting a Vue component or composable to ivue, or resolving any `.value`-in-template, `defineExpose`/`reactive()` instance-typing, `ReactiveInstance`/`Instance`, `$watch`/`$watchEffect`, or namespace-export question — the operating manual for Vue 3 class-based reactivity where state is ref-getters, derived values are plain getters, and Refs/Computeds are `.value` everywhere.
3
+ description: Use when writing or editing ivue `Reactive()` classes, converting a Vue component or composable to ivue, or resolving any `.value`-in-template, `defineExpose`/`reactive()` instance-typing, `ReactiveInstance`/`Instance`, `$watch`/`$watchEffect`, or namespace-export question, or any `Static()` capability-class / `$`-cached-static / anchor question — the operating manual for Vue 3 class-based reactivity where state is ref-getters, derived values are plain getters, and Refs/Computeds are `.value` everywhere.
4
4
  ---
5
5
 
6
6
  # ivue `Reactive`
@@ -30,6 +30,7 @@ path and skip the install; never add the dependency alongside a vendored copy.
30
30
 
31
31
  ```ts
32
32
  import { Reactive } from 'ivue'; // in this app: 'src/utils/ivue'
33
+ import { Static } from 'ivue/extras';
33
34
  import {
34
35
  ref,
35
36
  shallowRef,
@@ -42,6 +43,10 @@ import {
42
43
  import { useProjectStore } from 'src/stores/project.store';
43
44
 
44
45
  class $Box {
46
+ static get DEFAULT_HEIGHT() {
47
+ return 4;
48
+ }
49
+
45
50
  // Constructor runs SYNCHRONOUSLY where you `new` — in setup() that
46
51
  // means the constructor body IS setup code, and the whole toolbox
47
52
  // works here:
@@ -68,7 +73,7 @@ class $Box {
68
73
  // RAW: read AND write via .value. shallowRef for big structures you
69
74
  // REPLACE wholesale.
70
75
  get height() {
71
- return ref(4);
76
+ return ref((this.constructor as typeof $Box).DEFAULT_HEIGHT);
72
77
  }
73
78
  get rows() {
74
79
  return shallowRef<Row[]>([]);
@@ -146,11 +151,6 @@ class $Box {
146
151
  return this.$project.projectId;
147
152
  }
148
153
 
149
- // CONSTANTS / CONFIG — plain fields ONLY. A plain field written
150
- // from a method triggers NOTHING (no Ref/Computed, no dependency
151
- // edge). Never store mutable state here.
152
- baseWidth = 400;
153
-
154
154
  // METHODS — plain; engine-binds to raw (stable identity, safe as
155
155
  // handlers). Reactive-closure bodies above delegate HERE (the
156
156
  // thin-closure rule).
@@ -179,7 +179,7 @@ class $Box {
179
179
  }
180
180
 
181
181
  export namespace Box {
182
- export const $Class = $Box; // raw — children `extends` this
182
+ export const $Class = Static($Box); // static anchor — children `extends` this
183
183
  export let Class = Reactive($Class); // reactive — you `new` this
184
184
  // the type of every unwrapping surface (defineExpose, reactive())
185
185
  export type Instance = typeof Class.Instance;
@@ -421,6 +421,7 @@ session.dispose();
421
421
  | ✅ constructor runs init; register hooks/watchers there | ❌ add an `init()` method expecting auto-call — ivue never calls it |
422
422
  | ✅ plain `watch` in component-scoped constructors; `$watch` + a `$stopEffects` dispose path for outliving instances | ❌ default to `this.$watch` in a component-scoped class — its scope silently outlives unmount |
423
423
  | ✅ compose cleanup as an ordinary method — `dispose() { /* non-Vue cleanup */ this.$stopEffects(); }` | ❌ expect a teardown hook — ivue auto-calls NOTHING (no `init()`, no `stopEffects()`) |
424
+ | ✅ a class with static members anchors them: `const $Class = Static($X)` (`ivue/extras`) | ❌ `extends X.Class` — the mutable slot is an eager snapshot of one generation; always extend `$Class` |
424
425
 
425
426
  ## The unwrapping-surface typing invariant
426
427
 
@@ -498,6 +499,101 @@ safe without ordering discipline or `forwardRef`-style workarounds:
498
499
  stays impossible because it evaluates at load time and both parents cannot
499
500
  exist first.
500
501
 
502
+ ## `Static()` — the static-side sibling (from `ivue/extras`)
503
+
504
+ `Reactive()` owns instances. Stateless CAPABILITY classes — function bags for
505
+ files, git, parsers, clocks: never constructed, only called and swapped — use
506
+ `Static()` from the `ivue/extras` entry (separate, so core stays the engine):
507
+
508
+ - **Static methods bind lazily with stable identity** — detachable, safe as a
509
+ router/queue/listener callback, bound to the RECEIVING class.
510
+ - **Get-only statics named `$…` compute once PER RECEIVER.** The `$` prefix
511
+ promises stable identity, NOT immutability — a mutable memo table is a
512
+ legitimate `$`-cache. Non-`$` static getters stay LIVE: those are the knobs
513
+ test subclasses pinch.
514
+
515
+ THE ANCHOR RULE — a class that declares static members wraps them ONCE, at
516
+ `$Class`, so subclasses and test doubles inherit working semantics by
517
+ extending `$Class` bare:
518
+
519
+ ```ts
520
+ import { Static } from 'ivue/extras';
521
+
522
+ class $GitCommands {
523
+ static get binary() {
524
+ return 'git'; // LIVE knob — no $ prefix
525
+ }
526
+ static get $environment() {
527
+ return { LC_ALL: 'C' }; // computed once per receiver
528
+ }
529
+ static stage(path: string) {
530
+ return this.run(['add', '--', path]); // `this` = receiving class
531
+ }
532
+ }
533
+
534
+ export namespace GitCommands {
535
+ export const $Class = Static($GitCommands); // anchor — wrap HERE
536
+ export let Class = $Class; // selection — kernels/tests swap this
537
+ }
538
+ ```
539
+
540
+ Statics AND reactive instances on one class — anchor the statics, then
541
+ `Reactive()`:
542
+
543
+ ```ts
544
+ export namespace Settings {
545
+ export const $Class = Static($Settings);
546
+ export let Class = Reactive($Class); // in-place: Class === $Class
547
+ export type Instance = typeof Class.Instance;
548
+ }
549
+ ```
550
+
551
+ No static members → no wrapper: `$Class = $X`, the standard form unchanged.
552
+
553
+ ## Reading your own statics — the ladder
554
+
555
+ `Reactive(X) === X`, so a namespace's `Class` slot IS the base class. A getter
556
+ that reads statics through it therefore hard-binds to the base and silently
557
+ IGNORES a subclass override — the exact opposite of what a live (non-`$`)
558
+ static getter is for:
559
+
560
+ ```ts
561
+ // ❌ three members, a double cast, and the override never applies
562
+ protected get Tooltip() {
563
+ return Tooltip.Class as unknown as typeof $Tooltip;
564
+ }
565
+ public static get TOOLTIP_DWELL_SECONDS() { return 0.4; }
566
+ protected get tooltipDwellSeconds() {
567
+ return this.Tooltip.TOOLTIP_DWELL_SECONDS; // base value forever
568
+ }
569
+ ```
570
+
571
+ Measured: a subclass setting `0.1` still reads `0.4` through this shape.
572
+
573
+ Take the first rung that applies:
574
+
575
+ 1. **Nothing outside the instance reads it** → delete the static. A plain
576
+ instance getter is zero bytes per instance and natively overridable:
577
+ ```ts
578
+ protected get tooltipDwellSeconds() { return 0.4; }
579
+ ```
580
+ 2. **Something outside reads it** (tests pinching the knob, another class)
581
+ → keep the static and read it live off the receiver:
582
+ ```ts
583
+ protected get tooltipDwellSeconds() {
584
+ return (this.constructor as typeof $Tooltip).TOOLTIP_DWELL_SECONDS;
585
+ }
586
+ ```
587
+ `this.constructor` is the actual class: the subclass when subclassed, and
588
+ an engine class that INHERITS `$Class` for a plain reactive instance, so
589
+ statics resolve in both cases. TypeScript types `constructor` as
590
+ `Function`, so the one cast is required and is the honest cost.
591
+ 3. **Overriding must NOT happen** → name the class directly,
592
+ `$Tooltip.TOOLTIP_DWELL_SECONDS`, and let the code say so.
593
+
594
+ Never introduce a `protected get <ClassName>()` self-reference getter. It is a
595
+ cast wearing a getter costume: it looks live and is not.
596
+
501
597
  ## Generic classes (brief)
502
598
 
503
599
  `ReactiveClass<C>` cannot carry `<T>` through (no higher-kinded types), but
@@ -664,6 +760,27 @@ Contiguity says "same kind of thing"; a blank line says "the kind changes,
664
760
  or complexity rises." Spend the signal deliberately — a blanket
665
761
  newline-between-everything rule makes air mean nothing.
666
762
 
763
+ Class members use one order: static members → constructor → state getters →
764
+ prop getters → derived getters → methods. The constructor is the first
765
+ instance member. Comments and invariant annotations can precede the member
766
+ they describe.
767
+
768
+ Constants use one form per role:
769
+
770
+ | Role | Form |
771
+ | --- | --- |
772
+ | Tunable or overridable class constant | `static get SCREAMING_SNAKE_CASE()` |
773
+ | Protocol or byte constant on a hot path, never overridden | `static readonly SCREAMING_SNAKE_CASE` with a one-line hot-path comment |
774
+ | Contributor or pane identity data | Instance `readonly lowerCamelCase` field |
775
+ | Extensible constructed dependency | Field assigned from a prototype `createX()` factory method |
776
+ | Any other supposed constant | Defect: choose the real role or remove it |
777
+
778
+ Read live statics through the receiving class. JavaScript dispatches getter
779
+ and prototype method overrides while a parent constructor runs. A subclass
780
+ field initializer runs only after `super()` returns. It cannot change parent
781
+ construction. This mechanism makes getters safe tunables and `createX()`
782
+ methods safe construction seams.
783
+
667
784
  ```ts
668
785
  // state block — CONTIGUOUS: reads as the instance's STATE TABLE
669
786
  get sheet() {
@@ -708,7 +825,7 @@ convention and check it in review.
708
825
  ## Self-review checklist (run over your ivue diff)
709
826
 
710
827
  - [ ] Every mutable state member is `get x() { return ref(...) }` — no mutable plain fields.
711
- - [ ] Inside the class, every Ref/Computed read/write uses `.value`; plain fields are constants/config only.
828
+ - [ ] Inside the class, every Ref/Computed read/write uses `.value`; every plain field matches one role in the constants table.
712
829
  - [ ] Derived values are PLAIN getters; `computed()` appears only for expensive / render-suppressing / stable-handle cases.
713
830
  - [ ] Stores/composables are injected via `private get $store() { return useStore() }`, not field initializers.
714
831
  - [ ] The class is exported through the namespace (`$Class` / `Class = Reactive($Class)` / `Instance`); generics cast `Class` and hand-apply `ReactiveInstance` to `Instance<T>`.
@@ -723,4 +840,6 @@ convention and check it in review.
723
840
  - [ ] Every `computed()`/constructor-watch CALLBACK delegates to a method (`computed(() => this.recalculate())`) — no logic inlined in reactive closures; the arrow form, never `computed(this.method)`.
724
841
  - [ ] Identifiers are unfolded to domain words (`row`/`col`/`cell`/`cellValue`/`versionRef`…), loop indices and specs included — no single-letter names, no name meaning different things in different methods.
725
842
  - [ ] Keyed/sparse state uses the Map-of-refs shape (get-or-create on read, peek-only bump on write, explicit release path) — never one getter per key, never a deep `reactive()` collection.
843
+ - [ ] Static members are anchored (`const $Class = Static($X)`); `$`-prefixed static getters are compute-once-per-receiver caches, non-`$` statics stay live knobs, and inheritance extends `$Class` — never the mutable `Class`.
844
+ - [ ] Static members precede the constructor; the constructor precedes state, prop, and derived getters; methods come last.
726
845
  - [ ] Spacing carries meaning: declaration-like getters contiguous within their group; blank lines only where a doc comment / multi-line body / category boundary begins; methods always separated.