tjs-lang 0.8.7 → 0.9.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 (75) hide show
  1. package/CLAUDE.md +12 -4
  2. package/dist/experiments/ambient/probe.d.ts +52 -0
  3. package/dist/index.js +115 -171
  4. package/dist/index.js.map +4 -4
  5. package/dist/scripts/build-editors.d.ts +19 -0
  6. package/dist/src/css/dimensions.d.ts +23 -0
  7. package/dist/src/css/index.d.ts +85 -0
  8. package/dist/src/css/predicates.d.ts +38 -0
  9. package/dist/src/css/shorthands.d.ts +31 -0
  10. package/dist/src/css/style.d.ts +48 -0
  11. package/dist/src/lang/emitters/js.d.ts +9 -1
  12. package/dist/src/lang/index.d.ts +2 -2
  13. package/dist/src/lang/parser-transforms.d.ts +9 -5
  14. package/dist/src/lang/parser.d.ts +2 -0
  15. package/dist/src/lang/predicate.d.ts +60 -0
  16. package/dist/src/lang/transpiler.d.ts +3 -2
  17. package/dist/src/lang/types.d.ts +16 -0
  18. package/dist/src/schema/index.d.ts +12 -0
  19. package/dist/tjs-browser-from-ts.js +20 -20
  20. package/dist/tjs-browser-from-ts.js.map +3 -3
  21. package/dist/tjs-browser.js +100 -90
  22. package/dist/tjs-browser.js.map +4 -4
  23. package/dist/tjs-css.js +193 -0
  24. package/dist/tjs-css.js.map +7 -0
  25. package/dist/tjs-eval.js +43 -42
  26. package/dist/tjs-eval.js.map +4 -4
  27. package/dist/tjs-from-ts.js +13 -13
  28. package/dist/tjs-from-ts.js.map +2 -2
  29. package/dist/tjs-lang.js +102 -92
  30. package/dist/tjs-lang.js.map +4 -4
  31. package/dist/tjs-runtime.js +10 -0
  32. package/dist/tjs-runtime.js.map +7 -0
  33. package/dist/tjs-schema.js +6 -0
  34. package/dist/tjs-schema.js.map +7 -0
  35. package/dist/tjs-vm.js +55 -54
  36. package/dist/tjs-vm.js.map +4 -4
  37. package/docs/ambient-contracts.md +261 -0
  38. package/editors/ace/ajs-mode.js +214 -233
  39. package/editors/codemirror/ajs-language.js +1570 -233
  40. package/editors/editors-build.test.ts +29 -0
  41. package/editors/monaco/ajs-monarch.js +239 -195
  42. package/llms.txt +4 -0
  43. package/package.json +35 -4
  44. package/src/css/css.test.ts +122 -0
  45. package/src/css/dimensions.test.ts +112 -0
  46. package/src/css/dimensions.ts +146 -0
  47. package/src/css/index.ts +243 -0
  48. package/src/css/perf.bench.test.ts +109 -0
  49. package/src/css/predicates.ts +232 -0
  50. package/src/css/property-aware.test.ts +84 -0
  51. package/src/css/shorthands.test.ts +90 -0
  52. package/src/css/shorthands.ts +113 -0
  53. package/src/css/style.test.ts +125 -0
  54. package/src/css/style.ts +134 -0
  55. package/src/index-tsfree.test.ts +58 -0
  56. package/src/lang/emit-verified-predicate.test.ts +95 -0
  57. package/src/lang/emitters/dts.test.ts +31 -0
  58. package/src/lang/emitters/dts.ts +23 -4
  59. package/src/lang/emitters/js.ts +33 -1
  60. package/src/lang/from-ts.test.ts +1 -1
  61. package/src/lang/generic-verified-predicate.test.ts +39 -0
  62. package/src/lang/index.ts +14 -5
  63. package/src/lang/parser-transforms.ts +109 -14
  64. package/src/lang/parser.ts +9 -2
  65. package/src/lang/predicate-evaluator.test.ts +49 -0
  66. package/src/lang/predicate-report.test.ts +54 -0
  67. package/src/lang/predicate.ts +268 -0
  68. package/src/lang/redos-lint.test.ts +81 -0
  69. package/src/lang/transpiler.ts +13 -0
  70. package/src/lang/type-verified-predicate.test.ts +83 -0
  71. package/src/lang/types.ts +17 -0
  72. package/src/lang/typescript-syntax.test.ts +2 -1
  73. package/src/schema/index.ts +62 -0
  74. package/src/schema/schema.test.ts +66 -0
  75. package/src/use-cases/bootstrap.test.ts +14 -4
@@ -0,0 +1,261 @@
1
+ # Ambient Contracts — probe reality, derive verified predicate contracts
2
+
3
+ > **Status: design note / idea (2026-07-03).** Not built. Captures the direction
4
+ > and a concrete first spike (CSS + DOM). Related: the predicate engine
5
+ > (`src/lang/predicate.ts`), the `tjs-lang/css` library (`src/css/`), the
6
+ > autocomplete introspection bridge (`demo/src/introspection-bridge.ts`,
7
+ > `editors/introspect-value.ts`), and the VM capability model (`src/vm/`).
8
+
9
+ ## The itch
10
+
11
+ Static types are pessimistic about ambient runtime environments (the DOM, Node
12
+ globals, `window`, host objects) in ways that create ceremony without safety.
13
+ The canonical example:
14
+
15
+ ```ts
16
+ element.addEventListener('input', (e) => {
17
+ const value = e.target.value // ❌ TS: Property 'value' does not exist on 'EventTarget'
18
+ })
19
+ ```
20
+
21
+ `e.target` is typed `EventTarget | null`, and `EventTarget` has no `.value`, so
22
+ TypeScript forces `(e.target as HTMLInputElement).value` or a `instanceof` dance.
23
+ But the honest situation is: **at runtime the value is either there or it
24
+ isn't, and if it isn't you find out immediately.** The cast doesn't make it
25
+ safer — it just silences a compiler that can't see the real object. A runtime
26
+ predicate is the truthful tool here:
27
+
28
+ ```tjs
29
+ // pure, total, verifiable — reads props, no cast, no lie
30
+ function hasValueTarget(e) {
31
+ return e != null && e.target != null && typeof e.target.value === 'string'
32
+ }
33
+ // usage: hasValueTarget(e) ? e.target.value : undefined — no ceremony
34
+ ```
35
+
36
+ That predicate is exactly what the verifier accepts (member access is pure;
37
+ `typeof` → `TypeOf`), compiles to native, and — crucially — is **serializable**.
38
+ So the thought: instead of hand-writing these, could tooling **probe a real
39
+ ambient environment and derive the predicate contracts automatically?**
40
+
41
+ ## The payoff: author against an environment you're not running in
42
+
43
+ The headline use (user, 2026-07-03): **type-checking and autocompletion against a
44
+ non-ambient runtime — editing in a Node-based toolchain while _targeting_ the
45
+ browser** (or the reverse, or Electron, or a specific Bun). Normally you have no
46
+ target environment to introspect, so you fall back to a hand-maintained
47
+ `.d.ts` (`lib.dom`) that drifts from what the engine actually does.
48
+
49
+ Because a probed contract is a **serializable predicate**, it decouples _capture_
50
+ from _consumption_ in time and space: probe the real target once (locally or in
51
+ CI), bottle the contract, and a Node-side editor/typechecker consumes it with **no
52
+ target present**. It beats a `.d.ts` on three axes:
53
+
54
+ - **Grounded in reality, not a drifting spec** — it's what the target _actually_
55
+ exposes (we watched happy-dom diverge 678 vs 1 own-keys from real Chrome; a
56
+ captured contract is the real number).
57
+ - **Target-specific and versioned** — capture from the _actual_ target (this
58
+ browser, this Electron, this Bun), selectable/pinned, not a lowest-common-
59
+ denominator lib.
60
+ - **The same artifact validates at runtime** — a `.d.ts` evaporates; a predicate
61
+ contract can stay as a boundary guard, and its enumerable leaves feed
62
+ `suggest()` completions with real values.
63
+
64
+ Why it's plausible _here_ and not a research project: the contract format already
65
+ exists (`$predicate` / `tjs-lang/schema`), the autocomplete provider already has
66
+ an introspection hook (`getMembers` — point it at a captured contract instead of a
67
+ live object), and this is exactly the metadata the pinned "argument-type-driven
68
+ completion" item wants (e.g. `createElement`'s tag→type return shapes), sourced
69
+ from reality instead of hand-authored. See [[introspection-autocomplete]].
70
+
71
+ ## Contract vs. shim — the distinction that decides feasibility
72
+
73
+ "A predicate that stands in for the DOM" can mean two very different things:
74
+
75
+ 1. **A contract / validator** — "does this value behave enough like an
76
+ `HTMLElement` / an input event / a `CSSStyleDeclaration`?" Predicates are
77
+ _pure validators_, so this is squarely in scope: shape, property presence,
78
+ method arity, pure invariants.
79
+ 2. **A behavioral substitute** — something that, when code calls
80
+ `document.createElement('div')`, actually _does_ it. Predicates **cannot** be
81
+ this (they're pure, stateless, effect-free). That's a shim — happy-dom/jsdom
82
+ territory.
83
+
84
+ The valuable, on-thesis move is **#1 as the bridge to #2**: probe the real
85
+ environment, emit a serializable predicate _contract_, and use it to **certify a
86
+ stand-in** in whatever setting. The predicate isn't the DOM — it's the portable,
87
+ verifiable spec a substitute must satisfy, auto-derived from reality and from the
88
+ surface a given program actually touches.
89
+
90
+ ## Why it fits this project
91
+
92
+ - **The introspection probe already exists.** The autocomplete work runs code in
93
+ a real iframe and reads live scope (`introspect-value.ts`,
94
+ `introspection-bridge.ts`); Claude-in-Chrome can drive a _real_ browser. That's
95
+ the "kick the tires" mechanism.
96
+ - **Predicates are the serializable contract form** — verified, ReDoS-safe,
97
+ compile-to-native, travel as data (the `$predicate` keyword already embeds them
98
+ in JSON Schema, now evaluated by `tosijs-schema` via `tjs-lang/schema`).
99
+ - **The VM already models this as capabilities.** It injects `fetch`/`store`/
100
+ `llm`; "DOM-as-capability, predicate-validated at the boundary" is the same
101
+ pattern. A headless setting swaps the stub; the predicate catches divergence.
102
+ - **"Types are examples" already** — deriving a contract from _observed_ real
103
+ values is the same philosophy as `inferTypeFromValue`, sourced from a live probe
104
+ instead of a literal.
105
+
106
+ ## The honest constraints
107
+
108
+ - **Most of the DOM's contract is _behavioral_** (layout, events, mutation),
109
+ which a pure predicate can't capture — only shape + pure invariants. So:
110
+ - **Scope to the used surface.** Trace which ambient APIs the code actually
111
+ touches (in the real iframe) and derive a contract for _that slice_, not all
112
+ of `lib.dom.d.ts`.
113
+ - **Behavioral conformance is a separate, impure harness** — run the same probe
114
+ battery against the real env and a candidate stub, diff the outputs. The
115
+ predicate is the pure shape-gate; the harness is the behavior-gate.
116
+ - **Snapshots drift.** Browsers evolve; a captured contract is a dated snapshot.
117
+ Fine — it's re-derivable, and versioning it is a feature (conformance over
118
+ time).
119
+ - **Non-determinism / side effects** must stay outside predicates (the `effects`
120
+ tag already enforces this): a predicate validates the _data_ crossing the
121
+ boundary, never performs the effect.
122
+
123
+ ## Shape of the tooling (sketch)
124
+
125
+ 1. **Probe** — in a real environment (browser via Claude-in-Chrome, or the
126
+ introspection iframe), walk an ambient object / a program's used surface and
127
+ record: property names + `typeof`, method names + `.length` (arity), and a few
128
+ sampled input→output pairs for pure-ish accessors.
129
+ 2. **Derive** — turn the probe record into a predicate cluster (shape checks +
130
+ `typeof` guards + membership sets), run it through `verifyPredicate` (so the
131
+ emitted contract is itself certified safe), and `suggest()`-mine it for the
132
+ enumerable leaves.
133
+ 3. **Emit** — a `$predicate` schema (naive validators see structure, aware ones
134
+ run the contract) + optionally a `.d.ts`-ish view for editors.
135
+ 4. **Conform** — a harness that validates a stand-in (happy-dom, a hand stub, a
136
+ VM capability) against the contract, failing loud where it diverges. This is
137
+ what would have unblocked the Phase 5 real-`tosijs`-theme measurement (blocked
138
+ because `theme.ts` needs `HTMLElement` at import).
139
+
140
+ ## First spike — CSS + DOM (the convergence point)
141
+
142
+ `element.style` (`CSSStyleDeclaration`) is the ideal first target: it's an
143
+ ambient, stateful host object, and we _already have a full CSS value grammar as
144
+ predicates_ (`tjs-lang/css`). So the loop is concrete and small:
145
+
146
+ - **`event.target` demo** — show the `hasValueTarget` predicate compiles, is
147
+ verified pure, is total, and validates a real input event while rejecting a
148
+ click on a `<div>`. The predicate TypeScript won't let you write cleanly is a
149
+ perfectly ordinary verified predicate. (This is the "asinine" antidote, and
150
+ needs no browser — synthetic event-shaped objects suffice for the unit; a real
151
+ browser confirms on live events.)
152
+ - **`CSSStyleDeclaration` probe** — introspect real `element.style` in a browser,
153
+ derive a shape contract, and diff a happy-dom `style` against it: where does the
154
+ stub lie? Leaf _values_ ride the existing CSS predicates (`isColorValue`,
155
+ `isDimension`, …), so this exercises probe → contract → conformance end-to-end
156
+ while reusing what phases 1–5 built.
157
+
158
+ ## Ask the environment directly — `CSS.supports` (tosijs-ui port finding)
159
+
160
+ The tosijs 2.0 port arrived at the same idea from the other side (see
161
+ `../tosijs/TJS-PORT-DX.md`): the strongest form of "probe reality" for CSS is to
162
+ **ask the browser's own validator** — `CSS.supports(prop, value)` is exact, always
163
+ current with the engine's real support (vendor prefixes, new features, the full
164
+ `calc()`/`var()` grammar), and zero-maintenance. It closes the exact gap the
165
+ hand-authored grammar has: `isCssProperty('align-kontent')` returns `true`
166
+ (identifier-shaped), but `CSS.supports('align-kontent', …)` in a real browser
167
+ returns `false`. Property names for autocomplete come from the same place (a
168
+ throwaway element's `.style` keys / `CSSStyleDeclaration.prototype`).
169
+
170
+ This sharpens the contract-vs-fallback split by **when** validation runs:
171
+
172
+ - **Runtime, in a browser** → `CSS.supports` is ground truth (exact, free). A
173
+ `CSS.supports`-based check is predicate-_safe to run_ but references the ambient
174
+ `CSS` object, so it can't be statically _verified_ (it's effectful/ambient).
175
+ - **Transpile-time / SSR / Node** → no `CSS`, so the hand-authored grammar
176
+ predicates (`isColor`/`isLength`/`isStyleValueFor`) are the portable fallback.
177
+
178
+ So the ideal CSS-value check prefers `CSS.supports` when `typeof CSS !== 'undefined'`
179
+ and falls back to the grammar predicate otherwise — which is exactly the
180
+ probe-reality / portable-fallback pairing this whole note is about, one leaf down.
181
+ **Caveat (verified, and it's the same "the stand-in lies" finding): happy-dom
182
+ stubs `CSS.supports` to return `true` for everything** (even `width: banana`) — so
183
+ tests can't rely on it; ground truth needs a real browser or a real CSS engine.
184
+
185
+ ## Findings from the first spike (2026-07-03)
186
+
187
+ Built in `experiments/ambient/` (`probe.ts` + two demo tests, 7 green):
188
+
189
+ - **The `event.target` predicate is ordinary.** `hasValueTarget(e)` (reads
190
+ `e.target.value`, the thing TS rejects) verifies pure/safe, compiles native, and
191
+ is total — no throw on a missing target. The cast TS demands buys nothing a
192
+ predicate doesn't. (`event-target.demo.test.ts`.)
193
+ - **Host objects hide their surface on the prototype.** Probed a real
194
+ `CSSStyleDeclaration` via happy-dom: `Object.keys(style)` is nearly empty, yet
195
+ `typeof style.color === 'string'` and `typeof style.setProperty === 'function'`.
196
+ So a naive own-enumerable probe derives an empty contract — **the probe must
197
+ walk the prototype chain** (`getOwnPropertyNames` per proto) or check a scoped
198
+ key list. `probeShape` now does both.
199
+ - **Scope to the used surface — the full proto chain is huge.** An `HTMLElement`'s
200
+ chain is hundreds of props; a whole-shape contract would be enormous and
201
+ over-specified. A `keys: [...]` scope (the surface a program actually touches,
202
+ traced from the real env) is the right input. Demonstrated: a 4-key contract for
203
+ `style` verifies safe, accepts a real happy-dom `style` and a conforming hand
204
+ stub, rejects a plain object missing the methods.
205
+ - **Reading accessors during a probe can have side effects** (`offsetWidth` forces
206
+ layout). A scoped key list bounds this; a stricter probe would read property
207
+ _descriptors_ rather than the values. Getters that throw are skipped.
208
+ - **CSS convergence holds.** Leaf values off the real object ride the existing
209
+ `tjs-lang/css` predicates (`isColorValue(style.color)`), so shape-gate (contract)
210
+ + value-grammar (css) compose — the recursive style structure from CSS phase 4
211
+ is the same shape a DOM-derived contract would target.
212
+
213
+ Net: the probe→derive→verify→conform loop works end-to-end on a real host object;
214
+ the open work is sourcing the used-surface scope (trace) and the behavioral half.
215
+
216
+ ### Real-browser findings (via haltija `/eval`, live Chrome — 2026-07-03)
217
+
218
+ Confirmed against a real browser (not happy-dom), which sharpened three points:
219
+
220
+ - **Builtins don't honor `new`.** `new HTMLDivElement()` → `TypeError: Failed to
221
+ construct 'HTMLDivElement': Illegal constructor`. You **don't need `new`** — the
222
+ shape is on the constructor's `.prototype` chain
223
+ (`HTMLDivElement → HTMLElement → Element → Node → EventTarget`), readable
224
+ statically, no instance, no side effects. Cleaner than instantiating.
225
+ - **The manager vends the type.** `document.createElement('div').constructor ===
226
+ HTMLDivElement` (and `'a' → HTMLAnchorElement`, `'input' → HTMLInputElement`).
227
+ So a factory's output gives you _both_ a live example (real default values — the
228
+ "types are examples" leaf) _and_ the type identity (`.constructor` → name +
229
+ `.constructor.prototype` → the surface). Two complementary probe sources that
230
+ cross-check.
231
+ - **Structural contracts are realm-portable; `instanceof` is not — proved.** An
232
+ `<iframe>`'s input: `instanceof HTMLInputElement` (top realm) = **false**, but
233
+ the structural predicate (`typeof x.value === 'string' && x.tagName === 'INPUT'`)
234
+ = **true**. This is the whole argument in one line: nominal DOM types are
235
+ realm-bound (the stand-in problem _is_ a realm problem), a derived structural
236
+ predicate crosses realms. (happy-dom couldn't show this — it shares one
237
+ `HTMLInputElement` across `Window`s; the real browser does.)
238
+ - **Bonus — the stand-in already diverges from reality.** Real Chrome's `style`
239
+ has **678 own keys** (CSS properties as own props); happy-dom's had ~1 (on the
240
+ proto). Same object, wildly different probe result — exactly the conformance gap
241
+ the harness exists to catch, and proof that you must probe _reality_, not a
242
+ stand-in, to derive the contract.
243
+
244
+ ### Generalizes beyond the DOM — Node/Bun internals
245
+
246
+ The probe is environment-agnostic: `process`, `Buffer`, `node:fs`, `Bun.*`,
247
+ `globalThis` are ambient objects with the same shape. Derive a used-surface
248
+ contract from the _real_ runtime and certify a stand-in — e.g. "does my `process`
249
+ usage survive in the browser polyfill?" or "does Bun's `process` satisfy the
250
+ contract my code derived from Node's?". It's the realm/stand-in problem one level
251
+ up: cross-_runtime_ portability instead of cross-_frame_. Same tool.
252
+
253
+ ## Open questions
254
+
255
+ - Where's the line between "derive automatically" and "author, then verify
256
+ against reality"? The probe likely _proposes_ a contract a human trims.
257
+ - Method behavior: can we derive useful pure invariants for methods
258
+ (idempotence, `getComputedStyle(x)` shape) or only signatures?
259
+ - Editor story: a derived `$predicate` contract as the source of both validation
260
+ _and_ autocomplete (the introspection-autocomplete north star) for host objects
261
+ that ship no good `.d.ts`.