@hyperfixi/engine 7.5.0 → 7.6.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 +124 -0
- package/README.md +109 -30
- package/dist/additions.d.ts +11 -4
- package/dist/common.js +247 -201
- package/dist/common.min.js +9 -9
- package/dist/core.js +55 -3
- package/dist/core.min.js +9 -9
- package/dist/engine.d.ts +9 -1
- package/dist/ext/socket.d.ts +32 -0
- package/dist/ext/socket.dev.js +139 -0
- package/dist/ext/socket.esm.js +127 -0
- package/dist/ext/socket.js +1 -0
- package/dist/ext/tailwind.d.ts +16 -0
- package/dist/ext/tailwind.dev.js +30 -0
- package/dist/ext/tailwind.esm.js +16 -0
- package/dist/ext/tailwind.js +1 -0
- package/dist/full.js +82 -12
- package/dist/full.min.js +15 -15
- package/dist/hyperfixi-hs.dev.js +82 -12
- package/dist/hyperfixi-hs.js +15 -15
- package/dist/index.d.ts +1 -2
- package/dist/index.js +68 -12
- package/dist/kit.d.ts +13 -0
- package/dist/minimal.js +56 -10
- package/dist/minimal.min.js +8 -8
- package/dist/parser.d.ts +15 -2
- package/dist/statements.d.ts +1 -1
- package/package.json +8 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,130 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [7.6.0] - 2026-11-10
|
|
11
|
+
|
|
12
|
+
The engine takes extensions: upstream's `tailwind` and `socket` load from a script tag or
|
|
13
|
+
`register(...)`, and the Vite plugin bundles them. Spanish and Portuguese handlers agree with their
|
|
14
|
+
event (`a la entrada`, `à mudança`), and German writes an event as the noun it is (`wenn Klick`).
|
|
15
|
+
A minor: new features, and renders that change in es, pt and de; everything written before still
|
|
16
|
+
reads.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **`@hyperfixi/vite-plugin`: `extensions: ['socket', 'tailwind']`.** The generated bundle
|
|
21
|
+
imports each `@hyperfixi/engine/ext/<name>` and registers it after the modules, and the dev
|
|
22
|
+
`everything` fallback does too. Extensions are opt-in, so the scan never adds one. A project
|
|
23
|
+
that lists an extension gets an engine bundle even when the scan found nothing else (a page
|
|
24
|
+
whose only hyperscript is a `socket`).
|
|
25
|
+
- **Which module comes with `toggle` is now read from the grammar.** The plugin used to have
|
|
26
|
+
the rule "`toggleElement` comes with `toggle`" written in; it now reads which modules add
|
|
27
|
+
a reading to a command (`Grammar.fallbacks`).
|
|
28
|
+
- **`verify:reference` checks the engine's extensions as its own.** It registers each
|
|
29
|
+
`dist/ext/<name>.esm.js`, documents their keywords like the engine's, and parses their
|
|
30
|
+
examples: `socket`'s hover example is now checked, where it was skipped as "upstream's".
|
|
31
|
+
|
|
32
|
+
- **`@hyperfixi/engine`: extensions that a script tag can load.** After `hyperfixi-hs.js`, a page
|
|
33
|
+
can load `ext/<name>.js`, which registers its module with the page's engine. Or, from ESM,
|
|
34
|
+
`register(...everything, tailwind)` with `@hyperfixi/engine/ext/tailwind`.
|
|
35
|
+
- **The first extension is upstream's `tailwind`** (the `twDisplay`, `twVisibility` and
|
|
36
|
+
`twOpacity` hide/show strategies), and upstream's 12 tests for it are now in the engine's
|
|
37
|
+
gate: 1,434 of 1,498.
|
|
38
|
+
- **The public object gains `register` and `extensions`, and the script-tag bundle's also gains
|
|
39
|
+
`kit`.** `register` adds modules. `extensions` lists the extension files the page has
|
|
40
|
+
loaded. `kit` holds what a module may call: the same names the library entry exports for
|
|
41
|
+
module authors. Only `hyperfixi-hs.js` carries the kit, so a bundle built from ESM (the Vite
|
|
42
|
+
plugin's) keeps only the parts of the engine it uses; it registers extensions from ESM, and an
|
|
43
|
+
extension file loaded beside it says so.
|
|
44
|
+
- **`register` skips a module it has already added.** When it does add one, it reads again
|
|
45
|
+
the elements whose scripts failed to parse, so an `async` or late-loaded extension still
|
|
46
|
+
applies.
|
|
47
|
+
- **Upstream's own `ext/tailwind.js` also runs on the engine unchanged**, through `use`.
|
|
48
|
+
- **`@hyperfixi/engine/ext/socket`: upstream's `socket` feature.** `socket Chat ws://… [with
|
|
49
|
+
timeout n] [on message [as JSON] …] end` defines `Chat`, a WebSocket with `raw`,
|
|
50
|
+
`dispatchEvent` (`send ping to Chat` sends the event as JSON) and an `rpc` proxy
|
|
51
|
+
(`Chat.rpc.f(…)` returns a promise for the reply).
|
|
52
|
+
- **Upstream's 16 socket tests pass:** the suite is 1,450 of 1,498, with 48 known failures.
|
|
53
|
+
- **Two differences from upstream:** the socket opens when the feature is installed, not
|
|
54
|
+
when it is parsed, so a parse alone opens nothing; and with `as JSON` a message that is
|
|
55
|
+
JSON `0`, `false` or `null` is that value, where upstream rejects it.
|
|
56
|
+
- **The kit grows to what a feature needs:** `dotOrColonPath`, `urlOrExpression`,
|
|
57
|
+
`evalStatic`, `commandList`, `makeContext`, `runList` and `assignToNamespace`. Each is
|
|
58
|
+
exported from `@hyperfixi/engine` and carried on `hyperfixi-hs.js`'s `_hyperscript.kit`.
|
|
59
|
+
|
|
60
|
+
### Changed
|
|
61
|
+
|
|
62
|
+
- **`@lokascript/semantic`: a Spanish or Portuguese handler's head agrees with its event, and
|
|
63
|
+
German writes an event as a noun.** Renders only; everything written before still reads.
|
|
64
|
+
- **es, pt:** `al` and `ao` hold the masculine article, and were written before every event
|
|
65
|
+
(`al entrada agregar .a`, `ao mudança de #search …`). A feminine event takes `a la` and `à`:
|
|
66
|
+
`a la entrada agregar .a`, `à mudança de #search disparar envio em #form`. The first event of
|
|
67
|
+
an `or` decides; an infinitive (`al redimensionar`) and a custom name (`al my:event`) keep the
|
|
68
|
+
masculine. Each profile lists its feminine event words (`eventHandler.feminineEventMarker`).
|
|
69
|
+
es reads `a la` as the head only before one of them, so `poner x a la` is still a put into a
|
|
70
|
+
variable named `la`. pt's `à` joins its letters and prepositions.
|
|
71
|
+
- **de:** an event's word takes its capital wherever a command names one: `wenn Klick`,
|
|
72
|
+
`wenn Änderung von #search auslösen Absenden auf #form`, `warten Änderung`. An event with no
|
|
73
|
+
German word stays code (`wenn keyup`). The event table lists each one-word name both ways, so
|
|
74
|
+
`wenn klick` and `wenn doppelklick` read as before.
|
|
75
|
+
|
|
76
|
+
- **`@hyperfixi/engine`: a module can add a reading to a command without changing what the
|
|
77
|
+
command reads (`Grammar.fallbacks`).** A fallback registered for a keyword is tried, from just
|
|
78
|
+
after the keyword, only when the command's own rule fails to parse, and it counts only if it
|
|
79
|
+
reads at least as far as that rule got. `toggle <element>` (the `toggleElement` module) is now
|
|
80
|
+
one. Every `toggle` that upstream reads now goes through upstream's rule exactly (registering
|
|
81
|
+
`toggleElement` used to send every `toggle <target>` through a different expression rule).
|
|
82
|
+
An unfinished upstream `toggle` now reports upstream's error: `toggle #d as` used to say
|
|
83
|
+
`Expected 'modal'`, and now says `Expected dotOrColonPath`, as upstream does. The
|
|
84
|
+
`Grammar.toggleElement` slot is gone; a module that set it registers a fallback instead.
|
|
85
|
+
`Parser` gains `mark()` and `reset()`.
|
|
86
|
+
|
|
87
|
+
### Fixed
|
|
88
|
+
|
|
89
|
+
- **`@lokascript/semantic`: a translated `socket` comes back as the socket upstream reads.**
|
|
90
|
+
A socket's `on message [as JSON] …` is its own clause, closed by the socket's one `end` (or
|
|
91
|
+
by the end of the script). Semantic read it as an event handler, eventsource's shape, so:
|
|
92
|
+
- a clause closed by `end` came back with a second `end`, in every language. The engine
|
|
93
|
+
rejects that, so a translated socket failed to parse on the page;
|
|
94
|
+
- a clause with no `end` (upstream's tests write them that way) lost its body, and a
|
|
95
|
+
clause's `as JSON` was read as leftover input. `translate` refused both;
|
|
96
|
+
- a dotted name came back split: `socket MyApp.chat …` became `socket MyApp .chat …`, a
|
|
97
|
+
different socket.
|
|
98
|
+
|
|
99
|
+
`as JSON` is now written with the language's own word for `as`, as a conversion is (es
|
|
100
|
+
`como JSON`, ja `として JSON`). Text semantic wrote before, with two `end`s, still reads as
|
|
101
|
+
the one socket it was.
|
|
102
|
+
|
|
103
|
+
Where a language puts its handler word after the event (ja `messageで`, tr `message da`), a
|
|
104
|
+
socket or eventsource whose body starts with a word that looks like an event (ja
|
|
105
|
+
`messageで messageを 記録`, "on message log message") now reads. The handler word used to
|
|
106
|
+
be taken as the start of the body.
|
|
107
|
+
- **Measured by the command-shape gate, which now reads extension syntax.** Its engine
|
|
108
|
+
registers every extension the engine builds. Its harvest reads upstream's `ext/` tests,
|
|
109
|
+
and the scripts a test writes as `textContent` (how upstream's socket tests write theirs).
|
|
110
|
+
That adds 33 cases (1,168 in all), and each passes in all 24 lanes. Before this fix, 4 of
|
|
111
|
+
the 16 socket cases were refused in every language and 1 was silent in every language.
|
|
112
|
+
|
|
113
|
+
## [7.5.1] - 2026-10-23
|
|
114
|
+
|
|
115
|
+
The last two silent misreads of hand-written non-English hyperscript that the 7.3.0 live test
|
|
116
|
+
found: one is now refused, the other reads right.
|
|
117
|
+
|
|
118
|
+
### Fixed
|
|
119
|
+
|
|
120
|
+
- **ko: an event word followed by its object particle is the verb's object, not a handler.** At
|
|
121
|
+
top level, hand-written `로드 를 대기` (wait for load) read as `on load wait 를`: a handler on
|
|
122
|
+
`load` waiting on a variable named `를`, and translate() did not refuse it. Top level is now
|
|
123
|
+
refused, as ja `ロード を 待つ` and tr `yükleme i bekle` already were; inside a handler,
|
|
124
|
+
`클릭 할 때 로드 를 대기` still reads `wait for load`. ko's own render of
|
|
125
|
+
`trigger click on #btn …` (`#btn에 클릭을 트리거 …`) had been refused for the same reason and now
|
|
126
|
+
translates.
|
|
127
|
+
- **es `eso`, `esto` and a detached `lo` read as `it`, as do pt `isto` and fr `cela`/`ceci`.**
|
|
128
|
+
`poner eso en #o` read `put eso into #o`, a variable nobody set, with no error. Most profiles
|
|
129
|
+
already write `it` as a demonstrative (pt `isso`, fr `ça`, ru `это`, he `זה`, ja `それ`). What
|
|
130
|
+
`it` renders to is unchanged. A variable spelled like one of these words now reads as `it` in
|
|
131
|
+
that language, as one named `isso` already did in pt; MCP `translate_code` reports it
|
|
132
|
+
(`NAME_COLLISION`) with a rename.
|
|
133
|
+
|
|
10
134
|
## [7.5.0] - 2026-10-22
|
|
11
135
|
|
|
12
136
|
What a live test of 7.3.0 found users hitting. The Vite plugin loads in a CommonJS project and
|
package/README.md
CHANGED
|
@@ -39,15 +39,22 @@ feature, or an optional expression kind. A rule that is not registered does not
|
|
|
39
39
|
script that uses it fails with a parse error that names the token. `src/bundles/full.ts` is the
|
|
40
40
|
list of every module.
|
|
41
41
|
|
|
42
|
+
A module can also add a reading to a command another module defines, for syntax upstream does
|
|
43
|
+
not have: a fallback (`(g.fallbacks.toggle ??= []).push(rule)`). It is tried from just after the
|
|
44
|
+
keyword, and only when the command's own rule fails to parse. Its reading counts only if it
|
|
45
|
+
reads at least as far as that rule got before failing; otherwise the rule's error stands. So a
|
|
46
|
+
fallback cannot change what the command reads. `toggle <element>` (`src/additions.ts`) is one.
|
|
47
|
+
|
|
42
48
|
A module can live in another package. `@hyperfixi/speech` is one (upstream's `speak`): it imports
|
|
43
49
|
`expr` and the `Grammar` / `Cmd` / `Expr` types from here, sets `g.commands.speak` to a rule that
|
|
44
50
|
reads its operands off the `Parser` it is handed, and returns a node whose `run` may return a
|
|
45
|
-
promise. `register(...everything, speak)` builds an engine with it.
|
|
46
|
-
|
|
51
|
+
promise. `register(...everything, speak)` builds an engine with it. What a module may call is
|
|
52
|
+
`src/kit.ts` (below); `register` skips a module it has already added.
|
|
47
53
|
|
|
48
54
|
The public object is shaped like upstream's `_hyperscript` (`evaluate`, `parse`, `process`,
|
|
49
55
|
`config`, `use(plugin)`, `addBeforeProcessHook`), so a plugin written for upstream's public API
|
|
50
|
-
can be used on it; the multilingual adapter is
|
|
56
|
+
can be used on it; the multilingual adapter is, and so is upstream's own `ext/tailwind.js`,
|
|
57
|
+
unchanged. It adds a hook upstream lacks:
|
|
51
58
|
|
|
52
59
|
```ts
|
|
53
60
|
_hyperscript.addSourceTransform((source, element) => english);
|
|
@@ -55,7 +62,55 @@ _hyperscript.addSourceTransform((source, element) => english);
|
|
|
55
62
|
|
|
56
63
|
A script is rewritten as it is read. The element keeps the text its author wrote, and a parse
|
|
57
64
|
error in the rewritten script carries that text (`error.written`). `@lokascript/hyperscript-adapter`
|
|
58
|
-
uses the hook when the host has it.
|
|
65
|
+
uses the hook when the host has it. It also adds `register` and `extensions`, and in the
|
|
66
|
+
script-tag bundle `kit`, for extensions (next).
|
|
67
|
+
|
|
68
|
+
### Extensions
|
|
69
|
+
|
|
70
|
+
An extension is a module that is not in `everything`. It is added with `register`: from ESM, or
|
|
71
|
+
on a page, after the script-tag bundle, by its own file:
|
|
72
|
+
|
|
73
|
+
```html
|
|
74
|
+
<script src="hyperfixi-hs.js"></script>
|
|
75
|
+
<script src="ext/tailwind.js"></script>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { register, everything } from '@hyperfixi/engine';
|
|
80
|
+
import { tailwind } from '@hyperfixi/engine/ext/tailwind';
|
|
81
|
+
register(...everything, tailwind);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
With `@hyperfixi/vite-plugin`: `hyperfixi({ extensions: ['tailwind'] })`. The plugin's scan
|
|
85
|
+
never adds an extension; a project lists the ones it uses.
|
|
86
|
+
|
|
87
|
+
- **What a module may call is `src/kit.ts`**: for parsing, `expr`, `dotOrColonPath`,
|
|
88
|
+
`urlOrExpression`, `evalStatic` and `commandList`; for running, `makeContext`, `runList` and
|
|
89
|
+
`assignToNamespace`; and `config`. ESM gets
|
|
90
|
+
it from the library entry; the script-tag bundle (`hyperfixi-hs.js`) carries it as
|
|
91
|
+
`_hyperscript.kit`, so an extension file calls the page's engine instead of carrying a copy
|
|
92
|
+
of its parser. Only that bundle carries it: on every bundle's `api` it would keep the kit's
|
|
93
|
+
functions in a bundle that uses none of them (~0.25 KB gzipped on a small one, measured), so
|
|
94
|
+
a bundle built from ESM (the Vite plugin's) registers extensions from ESM. An extension's value
|
|
95
|
+
imports come from `../kit` only, and the build fails on any other. A name is added to the
|
|
96
|
+
kit when an extension needs it, as a change to the public API.
|
|
97
|
+
- **An extension file** (`dist/ext/<name>.js`, built from `src/ext/<name>.ts`) registers its
|
|
98
|
+
module once per page (it records its name in `_hyperscript.extensions`, so a script htmx
|
|
99
|
+
boost runs again does not register twice), and says so on the console when no engine, or
|
|
100
|
+
one without the kit, was loaded before it. `dist/ext/<name>.esm.js` is the same module for ESM
|
|
101
|
+
(`@hyperfixi/engine/ext/<name>`), taking the kit from `@hyperfixi/engine`.
|
|
102
|
+
- **Loaded late** (`async`, a lazy import, a page's own `register` call), an extension still
|
|
103
|
+
applies: `register` reads again, and only again, the elements whose scripts failed to parse.
|
|
104
|
+
An extension that claims only what upstream rejects cannot change a script that parsed. A
|
|
105
|
+
dialect (an extension that changes what a parsed script means) has to be registered before
|
|
106
|
+
the document is read.
|
|
107
|
+
- **Which extensions there are**, both upstream's and each held to upstream's own tests for it:
|
|
108
|
+
`tailwind` (the `twDisplay`, `twVisibility` and `twOpacity` hide/show strategies) and
|
|
109
|
+
`socket` (`socket Chat ws://… [on message [as JSON] …] end`: a WebSocket as a named object
|
|
110
|
+
with `raw`, `dispatchEvent` and an `rpc` proxy). Here the socket opens when the feature is
|
|
111
|
+
installed; upstream's opens when it is parsed (`tests/extensions.js` holds the difference). An extension ported
|
|
112
|
+
from upstream is held to upstream's tests; one of this repository's own will be held to
|
|
113
|
+
`check:claims`, as the additions are.
|
|
59
114
|
|
|
60
115
|
### htmx and fixi
|
|
61
116
|
|
|
@@ -85,10 +140,12 @@ whenever htmx loads.
|
|
|
85
140
|
| `src/reactivity.ts`, `src/templates.ts`, `src/live-templates.ts` | Optional: `when` / `live` / `bind`; `render` and template text; live templates |
|
|
86
141
|
| `src/commands/*` | 52 command keywords, one module export each |
|
|
87
142
|
| `src/additions.ts` | The two forms upstream does not have: `new X()` and `toggle <element>` |
|
|
143
|
+
| `src/kit.ts`, `src/ext/*` | What an extension module may call; the extensions, each built to `dist/ext/<name>.js` (script tag) and `.esm.js` |
|
|
88
144
|
| `src/bundles/*` | Browser bundle entries. `hyperfixi-hs` is the script-tag bundle; `full` is the same modules, for the gate; `core`, `minimal`, `common` exist to measure size |
|
|
89
145
|
| `upstream-suite/` | The acceptance gate: upstream's Playwright suite (vendored) against a bundle |
|
|
90
|
-
| `tests/` | This package's own tests (`src/additions.ts`, and regressions upstream's tests did not show), in the form of upstream's and on the same fixtures
|
|
146
|
+
| `tests/` | This package's own tests (`src/additions.ts`, extensions, and regressions upstream's tests did not show), in the form of upstream's and on the same fixtures |
|
|
91
147
|
| `tools/probe.mts` | One source on this engine and on upstream, side by side, on the value matrix's fixture |
|
|
148
|
+
| `tools/claims.mts`, `tests/claims.json` | What each addition claims, read and run on upstream: the tier each addition may have |
|
|
92
149
|
|
|
93
150
|
A node is typed data plus the closure that runs it, bound when the node is parsed: an
|
|
94
151
|
expression has `ev`, a command has `run`, a feature has `install`. Control flow is a value a
|
|
@@ -101,6 +158,7 @@ npm run build --prefix packages/engine # dist/: library, bundles, decla
|
|
|
101
158
|
npm run typecheck --prefix packages/engine
|
|
102
159
|
npm run test:upstream --prefix packages/engine # the gate (needs a build; about 30 s)
|
|
103
160
|
npm run test:own --prefix packages/engine # additions and regressions (needs a build; 2 s)
|
|
161
|
+
npm run check:claims --prefix packages/engine # what each addition claims, on upstream (3 s)
|
|
104
162
|
npm run cost --prefix packages/engine # what each module costs in the full bundle
|
|
105
163
|
|
|
106
164
|
# Any engine bundle can be scored on the same suite:
|
|
@@ -120,15 +178,32 @@ release 0.9.93, the one this repository's other gates use) against `dist/full.js
|
|
|
120
178
|
that fail must be exactly the ones listed in `upstream-suite/known-failures.json`. A new failure
|
|
121
179
|
fails the gate. So does a listed test that now passes: prune it with
|
|
122
180
|
`npm run test:upstream:update` in the same change, so the list stays what does not pass. CI runs
|
|
123
|
-
it in the `browser-tests` job.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
upstream
|
|
127
|
-
|
|
128
|
-
|
|
181
|
+
it in the `browser-tests` job. The engine the tests run carries the extensions this engine
|
|
182
|
+
builds (`src/ext/`), as upstream's own test bundle carries its extensions (`test/entry.js`):
|
|
183
|
+
the bundle, then each `ext/<name>.js` next to it, as one file. (For upstream's bundle those
|
|
184
|
+
are upstream's own files.) The package's own tests run on the engine alone.
|
|
185
|
+
|
|
186
|
+
**`test:upstream:update` does not retry**, so a timing flake in its run becomes a listed
|
|
187
|
+
failure. Diff the `failing` list it writes before committing it.
|
|
188
|
+
|
|
189
|
+
**The package's own tests.** `test:own` runs `tests/` against the same bundle, for what
|
|
190
|
+
upstream has no tests for: the two forms, and the extension tier (`tests/extensions.js`). Every one must pass. On upstream's own bundle 3 of `additions.js`'s 27 pass (the
|
|
191
|
+
three that check an upstream form still reads, or fails, as it did), which is what shows the
|
|
192
|
+
rest test the additions. `tests/regressions.js` holds the other kind: places where this engine differed from
|
|
129
193
|
upstream and upstream's tests did not show it (each of those passes on upstream). CI runs both
|
|
130
194
|
after the upstream suite.
|
|
131
195
|
|
|
196
|
+
**The claims.** `check:claims` (`tools/claims.mts`) asks what each addition takes from upstream.
|
|
197
|
+
`tests/claims.json` lists, per addition, the sources it gives a meaning to; each is read and
|
|
198
|
+
run on upstream, and the worst outcome sets the tier the addition may be listed with:
|
|
199
|
+
an **addition** claims only what upstream rejects, a **trap repair** also what upstream reads
|
|
200
|
+
but cannot run, a **dialect** something that runs on upstream. The listed tier must be the
|
|
201
|
+
measured one, the engine must read and run every claim, and the engine without the addition
|
|
202
|
+
must read each claim as upstream does (otherwise the claim is not the addition's). A claim
|
|
203
|
+
whose run needs an API jsdom lacks (`showModal`, `togglePopover`) is only read, with the
|
|
204
|
+
reason listed; `tests/additions.js` runs it in Chromium. `--report` prints every claim and
|
|
205
|
+
what each reader did with it.
|
|
206
|
+
|
|
132
207
|
**The multilingual gates**, in the packages that own them:
|
|
133
208
|
|
|
134
209
|
- The value matrix (`packages/testing-framework`, `value-matrix.ts`) has an `eng` lane, the
|
|
@@ -200,14 +275,16 @@ engine want `increment #count's textContent`. The examples write the second, whi
|
|
|
200
275
|
bundle runs to the same count (the bundle matrix's Counter row).
|
|
201
276
|
|
|
202
277
|
Since 2026-10-21 the gate also runs upstream's htmx tests (`integration/htmx.js` and
|
|
203
|
-
`ext/hs-include.js`, 19), which the first vendoring left out
|
|
204
|
-
|
|
278
|
+
`ext/hs-include.js`, 19), which the first vendoring left out. The engine failed 10 of them until
|
|
279
|
+
it had upstream's htmx integration; it passes all 19. Since 2026-10-25 it also runs upstream's
|
|
280
|
+
tests for each extension this engine builds (`ext/tailwind.js`, 12; see Extensions), so the
|
|
281
|
+
whole suite is 1,498; and the socket tests (`features/socket.js`, 16), vendored from the
|
|
282
|
+
start, pass since the engine builds `socket`.
|
|
205
283
|
|
|
206
|
-
The
|
|
207
|
-
`evalStatically`, source info, error collection: 45),
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
absent.
|
|
284
|
+
The 48 known failures: upstream's internal-API surface (`internals.tokenizer`,
|
|
285
|
+
`evalStatically`, source info, error collection: 45), the worker feature (1: upstream's
|
|
286
|
+
extension, not built), and two collection-expression tests that need upstream's component
|
|
287
|
+
extension. Upstream's one failure is a test that expects its worker extension to be absent.
|
|
211
288
|
|
|
212
289
|
As a host for the multilingual text path (semantic renders a translation, the adapter's
|
|
213
290
|
`preprocess` turns it back into English, the engine runs the English):
|
|
@@ -262,30 +339,32 @@ two values and inserts nothing. The comparison that counts is the DOM a handler
|
|
|
262
339
|
|
|
263
340
|
## Differences from upstream
|
|
264
341
|
|
|
265
|
-
|
|
342
|
+
In syntax, two, both recorded here and nowhere silent.
|
|
266
343
|
|
|
267
344
|
**Two additions** (`src/additions.ts`, decided 2026-10-01), forms `packages/core` has and the
|
|
268
|
-
shipped examples use. Each is its own module
|
|
269
|
-
|
|
345
|
+
shipped examples use. Each is its own module; upstream's suite passes the same tests with them
|
|
346
|
+
registered as without, and `check:claims` measures what each takes from upstream (above).
|
|
270
347
|
|
|
271
348
|
- `new Date()`, `new Intl.NumberFormat('en')`, `(new Date()).getDay()`: a constructor call as
|
|
272
349
|
an expression. `new` is still an ordinary name unless a constructor call follows it. Upstream
|
|
273
|
-
has the `make a Date` command and no expression. 0.20 KB.
|
|
350
|
+
has the `make a Date` command and no expression. 0.20 KB. Measured a **dialect**: upstream
|
|
351
|
+
reads `set d to new Date()` as `set d to new` (an unset variable) and a second command,
|
|
352
|
+
`Date()`, which runs and leaves `d` null. With a class (`new Map()`) or a call after it
|
|
353
|
+
(`new Date().getDay()`, the form the shipped examples use), upstream's reading fails when
|
|
354
|
+
run instead; inside `put … into` or parentheses, upstream rejects it.
|
|
274
355
|
- `toggle #dialog`, `toggle #dialog modal` (or `as modal`), `toggle #details`: open what is
|
|
275
356
|
closed and close what is open, for a dialog, a details element (or its summary), a popover
|
|
276
357
|
and a select. Upstream rejects `toggle <expression>` unless `between` follows; that form is
|
|
277
|
-
unchanged. 0.21 KB.
|
|
358
|
+
unchanged. 0.21 KB. Measured an **addition**: upstream rejects every form it claims. It is
|
|
359
|
+
a fallback for `toggle` (above), so it reads only what upstream's rule rejects.
|
|
278
360
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
does; the possessive form `#a's @title as String` works on both. This engine continues the
|
|
283
|
-
chain. It is kept because the stricter reading looks like an upstream slip and a shipped
|
|
284
|
-
example uses the form (`its @data-stock as Number > 0`).
|
|
361
|
+
An attribute read through `my` / `its` / `your` takes nothing after it here, as on upstream:
|
|
362
|
+
both reject `my @id as String` (upstream's attribute-access rule does not continue the
|
|
363
|
+
expression chain); the possessive `#a's @title as String` works on both.
|
|
285
364
|
|
|
286
365
|
Considered and not added: core's `swap` strategies (upstream's `swap` means something else),
|
|
287
366
|
`copy`, a `? :` conditional, `.debounce(n)`, `has`, prefix `unless`, `set @a to v on <target>`,
|
|
288
|
-
`sorted by … desc
|
|
367
|
+
`sorted by … desc`, `push url` / `replace url` (pages write `call history.pushState`).
|
|
289
368
|
|
|
290
369
|
## Where the bytes are
|
|
291
370
|
|
package/dist/additions.d.ts
CHANGED
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
* Syntax upstream `_hyperscript` does not have. Everything else in this engine
|
|
3
3
|
* follows upstream's source; these two forms come from `packages/core`, are used
|
|
4
4
|
* by the examples this repository ships, and were kept by decision (2026-10-01).
|
|
5
|
-
* Each is an optional module
|
|
5
|
+
* Each is an optional module. What each takes from upstream is measured, claim by
|
|
6
|
+
* claim, by `npm run check:claims` (`tests/claims.json`):
|
|
6
7
|
*
|
|
7
8
|
* - `construct`: `new Date()`, `new Intl.NumberFormat('en')`. Upstream has the
|
|
8
9
|
* `make a Date` command and no expression; there, `set t to new Date().getDay()`
|
|
9
|
-
* parses (`new` is a variable, the call a second command) and fails when it runs
|
|
10
|
+
* parses (`new` is a variable, the call a second command) and fails when it runs,
|
|
11
|
+
* and `set d to new Date()` runs and leaves `d` null. A dialect, kept in every
|
|
12
|
+
* bundle by decision (2026-10-25).
|
|
10
13
|
* - `toggleElement`: `toggle #dialog`, `toggle #dialog modal`, `toggle #details`.
|
|
11
|
-
* Upstream rejects `toggle <expression>` without `between`.
|
|
14
|
+
* Upstream rejects `toggle <expression>` without `between`. A fallback for
|
|
15
|
+
* `toggle` (`Grammar.fallbacks`), so it reads only what that rule rejects.
|
|
12
16
|
*/
|
|
13
17
|
import type { Cmd, Expr } from './ast';
|
|
14
18
|
import type { Grammar } from './parser';
|
|
@@ -26,5 +30,8 @@ export interface ToggleElementNode extends Cmd {
|
|
|
26
30
|
/** `toggle #d modal` / `toggle #d as modal`: open with `showModal()`. */
|
|
27
31
|
modal: boolean;
|
|
28
32
|
}
|
|
29
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* `toggle <element> [modal | as modal]`: a fallback for `toggle`, so it reads only what
|
|
35
|
+
* upstream's rule rejects. It declines a target `between` follows: that is upstream's form.
|
|
36
|
+
*/
|
|
30
37
|
export declare function toggleElement(g: Grammar): void;
|