@lokascript/compilation-service 4.0.1 → 4.2.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 +331 -1
- package/dist/{chunk-GWYYJAZY.js → chunk-QUA3XGWW.js} +22 -3
- package/dist/chunk-QUA3XGWW.js.map +1 -0
- package/dist/http.cjs +21 -2
- package/dist/http.cjs.map +1 -1
- package/dist/http.d.cts +1 -1
- package/dist/http.d.ts +1 -1
- package/dist/http.js +1 -1
- package/dist/index.cjs +21 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/{service-2lEnO5hA.d.cts → service-TXPE1KWF.d.cts} +10 -0
- package/dist/{service-2lEnO5hA.d.ts → service-TXPE1KWF.d.ts} +10 -0
- package/package.json +6 -6
- package/dist/chunk-GWYYJAZY.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,334 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [4.2.0] - 2026-10-07
|
|
11
|
+
|
|
12
|
+
A translation that would lose part of a script is now refused instead of returned short.
|
|
13
|
+
`@lokascript/semantic`'s `translate()` throws a `LossyTranslationError` unless the caller passes
|
|
14
|
+
`{ lossy: 'allow' }`, and the adapter, the Vite plugin's bundle and `@hyperscript-tools/i18n` keep
|
|
15
|
+
the author's text and warn. Upstream forms that translations used to drop now read whole in every
|
|
16
|
+
language: scripts of several features, `catch` / `finally`, conditionals and loops, `bind … and`,
|
|
17
|
+
scoped names such as `element x`, what `on` reads after its event, and clauses a command's
|
|
18
|
+
pattern does not model. On the new command-shape gate, round trips that came back silently
|
|
19
|
+
different fell from 7,614 at its first run to 174 (none in English); 23,763 of 27,240 now read as
|
|
20
|
+
their source does, and 3,303 are refused.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **`@lokascript/semantic` keeps a clause its pattern does not model, as written.** A command's
|
|
25
|
+
pattern read the start of the command and dropped the rest:
|
|
26
|
+
`add .foo to .bar when it matches .doh`, `toggle between .a and .b`,
|
|
27
|
+
`take .foo from .div for #d3`, `render #tmpl into #target`, `log me, my`. The rest is now
|
|
28
|
+
kept on the node as `verbatimClause` and written back after the command in every language, in
|
|
29
|
+
SOV and VSO word order too (ja `クリック を で .bar に .rey を 追加 when it matches .doh` reads
|
|
30
|
+
back as `on click add .rey to .bar when it matches .doh`). So a foreign render can now hold a
|
|
31
|
+
clause in English. A run that holds a command verb, a structure word (`else`, `catch`, `on`, …)
|
|
32
|
+
or a word of the source language (es `cuando`) is not a clause, and its translation is
|
|
33
|
+
refused. The parser records a `verbatim-clause` warning for each kept clause, since no role
|
|
34
|
+
reads it: `@lokascript/compilation-service` reports it as `UNCONSUMED_INPUT` with its repair
|
|
35
|
+
hint, and MCP `validate_hyperscript` lists it with the unconsumed tokens. A pattern group that
|
|
36
|
+
reads its marker and binds no role now gives the marker back, so
|
|
37
|
+
`take .foo from .div for #d3` keeps its `for` and `transition *width from 0px to 100px` its
|
|
38
|
+
`from`.
|
|
39
|
+
- **`@lokascript/semantic` keeps what upstream's `on` reads between the event and the body.**
|
|
40
|
+
`on click elsewhere`, a count (`on click 1`, `on click 1 to 2`), `on mutation of attributes`,
|
|
41
|
+
`on click in #d1`, `on foo queue first` and `on x having threshold 0.1` lost everything after
|
|
42
|
+
the event, so the handler ran on every event, or with a count, on the wrong ones. The run is now
|
|
43
|
+
kept as `EventHandlerSemanticNode.headClause`, written after the whole head in every language
|
|
44
|
+
(ja `クリック を で elsewhere .clicked を 追加`) and reported with the same `verbatim-clause`
|
|
45
|
+
warning. Outside English a count is read only in the `1 to 2` form, since a body there can open
|
|
46
|
+
with a number: a bare count in another language (`on click 1`) is refused.
|
|
47
|
+
- **`@lokascript/semantic` reads `catch` and `finally` in handlers and functions.** In a `def`, the
|
|
48
|
+
clauses ran as more of the function's body, so its error handling ran every time, in silence. In
|
|
49
|
+
a handler, `catch e …` was left unread. `EventHandlerSemanticNode` and `DefSemanticNode` now
|
|
50
|
+
carry `catchName`, `catchBody` and `finallyBody`. A `def` writes its clauses before its `end`,
|
|
51
|
+
and a handler writes them after its commands. No language has its own words for them yet, so
|
|
52
|
+
every language writes English's `catch` and `finally`. `wait a tick` now reads as `wait 0ms`, as
|
|
53
|
+
the engine runs it; every render wrote `wait tick`, a wait on a variable named `tick`.
|
|
54
|
+
- **`@hyperfixi/testing-framework`: the command-shape gate.** It takes the 1,135 scripts in
|
|
55
|
+
upstream _hyperscript's own test suite (0.9.93) and in core's reference and hover examples that
|
|
56
|
+
the engine reads, translates each into the other 23 languages and back (and English to English),
|
|
57
|
+
and has the engine parse the result and the source. A pair passes when the two parses match,
|
|
58
|
+
after a short list of named equivalences that are each checked on both engines. Otherwise it is
|
|
59
|
+
refused (`translate` threw) or silent. The baseline lists the refused and silent pairs and only
|
|
60
|
+
shrinks: the gate fails on a pair worse than listed and on one better. Every silent pair left
|
|
61
|
+
belongs to a named family with a reason (`SILENT_FAMILIES`), and none is in English.
|
|
62
|
+
- **`@hyperfixi/engine`'s parse keeps two values it held only in a closure.** `GoNode.url` holds
|
|
63
|
+
the address of `go to url <address>`, and `SymbolNode.on` the element of `^name on <element>`.
|
|
64
|
+
Two scripts that differed only there parsed to the same tree. Nothing reads them at run time.
|
|
65
|
+
|
|
66
|
+
### Changed
|
|
67
|
+
|
|
68
|
+
- **`@lokascript/semantic`'s `translate()` refuses a translation that would lose part of the
|
|
69
|
+
script.** This is a change in behavior. It used to return what it could render:
|
|
70
|
+
`log it?.dataset?.customValue` came back in Spanish as `registrar ello`, and Hindi wrote
|
|
71
|
+
`put it.name into #r` in a form that reads back as `on its.name put #r into me`. It now throws a
|
|
72
|
+
`LossyTranslationError` carrying `partial` (what it would have returned, not the whole script),
|
|
73
|
+
`loss.kind` and `loss.lost` (what it drops). It refuses when the parse leaves input unread
|
|
74
|
+
(`truncation`); when the output does not read back with the input's commands, roles and `put`
|
|
75
|
+
positions (`read-back`), as when a foreign `wait for <event>` reads back as a time wait; and when
|
|
76
|
+
a string, number, selector, attribute, style or `$` / `:` / `^` name of the input is missing from
|
|
77
|
+
the output (`invariant`). For example,
|
|
78
|
+
`translate('on click ask "Name?" then put it into me', 'en', 'es')` throws with `loss.lost`
|
|
79
|
+
`['ask "Name?"']`, since `ask` has no schema yet. Pass `{ lossy: 'allow' }` as the fourth
|
|
80
|
+
argument to get the partial output instead. The root and
|
|
81
|
+
`@lokascript/semantic/core` export `LossyTranslationError`, `findTranslationLoss` (the same
|
|
82
|
+
checks on a parse and its render) and the types `TranslateOptions`, `TranslationLoss` and
|
|
83
|
+
`TranslationLossKind`. Comments (`--`, `//`) and Bengali's polite verb forms (`টগল করুন`) are now
|
|
84
|
+
read whole, so a correct translation that holds them is not refused.
|
|
85
|
+
`@hyperfixi/core/multilingual`'s `translate` takes the same options and rethrows the refusal. It
|
|
86
|
+
used to return the input unchanged on any error; input that does not parse still comes back
|
|
87
|
+
unchanged.
|
|
88
|
+
- **`@lokascript/hyperscript-adapter`, `@hyperfixi/vite-plugin`'s bundle and
|
|
89
|
+
`@hyperscript-tools/i18n` keep the author's text when a translation would lose part of it.**
|
|
90
|
+
This is a change in behavior. The adapter's full, slim and lite plugins ran the partial English:
|
|
91
|
+
`al clic alternar .foo cuando .bar` ran as `on click toggle .foo`. They now leave the script as
|
|
92
|
+
written, so the host reports a parse error naming code the author wrote, and warn once per
|
|
93
|
+
language with what would be dropped (`cuando .bar`). `preprocess()`'s config takes `onLossy`,
|
|
94
|
+
which is called with the refusal. The Vite plugin's source transform does the same, and its
|
|
95
|
+
`translateHyperscript` rethrows the refusal, where it returned the code unchanged on any error.
|
|
96
|
+
`@hyperscript-tools/i18n`'s lenient mode kept the source text in silence. Now `translateHtml`
|
|
97
|
+
reports a refusal through `onRefused` (`'warn'`, the default, `'error'`, or a callback), the
|
|
98
|
+
Eleventy filters take `'warn'` or `'error'`, and the CLI names the file.
|
|
99
|
+
- **`@hyperfixi/mcp-server`'s translate tools report a refusal.** `translate_code` returns
|
|
100
|
+
`ok: false` with a `LOSSY_TRANSLATION` diagnostic and `loss.lost`, and the partial text under
|
|
101
|
+
`partial`, never `code`. `translate_hyperscript` and `translate_to_english` return
|
|
102
|
+
`refused: true` with `lost` and `partial`. `translate_hyperscript` used to report every error as
|
|
103
|
+
a missing package.
|
|
104
|
+
|
|
105
|
+
### Fixed
|
|
106
|
+
|
|
107
|
+
- **`@lokascript/semantic`: a script of several features reads as one program.** Upstream reads
|
|
108
|
+
each top-level feature on its own. Semantic kept only the first and dropped the rest, or chained
|
|
109
|
+
them with `then`, which neither engine reads: `def a … end def b … end` became one function,
|
|
110
|
+
`def … end on click …` lost its handler, and `bind … end live …` and `set :x to 1 on click …`
|
|
111
|
+
came out joined by `then`. A handler, a `def`, a `behavior`, a feature block, or a top-level
|
|
112
|
+
`bind`, `set`, `install` or `js` now starts a new part of the program, and a top-level `init`
|
|
113
|
+
parses, as a feature block (`FeatureAction` gains `'init'`). English writes one feature per
|
|
114
|
+
line and closes each handler with `end`. It writes a `bind`, `set` or `install` with the `end`
|
|
115
|
+
upstream allows when the next feature has no head word to split at, and joins consecutive
|
|
116
|
+
`install` and `bind` features with a space, never `then`, which the engine rejects.
|
|
117
|
+
- **`@lokascript/semantic`: a dotted name is one name, and a behavior's `end`s are optional.**
|
|
118
|
+
A dotted name is one name: `def utils.foo()` defined `utils` and left `.foo ( )` unread, and
|
|
119
|
+
`behavior App.Widgets.Clickable` did not parse as a behavior. A behavior's last handler needs no
|
|
120
|
+
`end`, nor does the behavior at the end of input: `behavior B(x) on click set @out to x` read as
|
|
121
|
+
a `behavior` command chained to a `set`. A trailing `init` block in such a behavior is its init.
|
|
122
|
+
A behavior member that is neither a handler nor `init`
|
|
123
|
+
(`behavior MarkIt set @data-marked to 'yes' end`) came out as `behavior MarkIt then set …`,
|
|
124
|
+
which the engine rejects; semantic does not model such members yet, so the translation is now
|
|
125
|
+
refused. So is `behavior A … behavior B …` with no `end` between them: upstream reads B as part
|
|
126
|
+
of A, and semantic read it as a `behavior` command in A's last handler.
|
|
127
|
+
- **`@lokascript/semantic`: `bind … and` / `with` and `install`'s arguments read.** Upstream reads
|
|
128
|
+
`bind <left> and|with|to <right>` as the same binding. Only `to` had a pattern, and its left
|
|
129
|
+
side took only a variable, so twelve of upstream's test scripts did not parse, among them
|
|
130
|
+
`bind .dark and $darkMode` and `bind my value and #slider's value`. The left side now takes
|
|
131
|
+
selectors and property paths too. `install Toggleable(cls: 'highlighted')`, the documented
|
|
132
|
+
behaviors usage, lost its arguments: an install's parentheses are arguments, not a signature.
|
|
133
|
+
- **`@lokascript/semantic`: conditionals and loops read whole.** An English pattern read every
|
|
134
|
+
top-level `if X …` as a handler for an event named `X`, so `if x log 1 else log 2 end` became
|
|
135
|
+
`on x log 1 then log 2`. Seven languages read their own `if` the same way (es and fr `si`, pt
|
|
136
|
+
`se`, zh `如果`, sw `kama` / `ikiwa`, id `jika` / `kalau` / `bila`, de `falls`), so every
|
|
137
|
+
conditional rendered in them read back as a handler. A bare `if … end` is now a conditional; de
|
|
138
|
+
`wenn` and `sobald` and id `apabila` still head a handler. An `if` on the line after `else`
|
|
139
|
+
opens its own block, as upstream reads it. Semantic chained the two, so the commands after the
|
|
140
|
+
inner `end` ran whether or not the condition held. Tokens that open a line now carry
|
|
141
|
+
`metadata.lineStart`, and English writes such an `if` on its own line. The `end` of
|
|
142
|
+
`at end of` no longer closes a conditional: in
|
|
143
|
+
`if x put 'a' at end of me end put 'b' at end of me`, the second `put` ran inside the branch. An
|
|
144
|
+
empty block (`if x then end`) stays a conditional, and a condition ends at `else`. `break` and
|
|
145
|
+
`continue` read. A loop whose test follows its body (`repeat … until x end`,
|
|
146
|
+
`repeat … while x end`) is read as one (`LoopSemanticNode.bottomTested`) and written
|
|
147
|
+
`repeat forever … until x end`, which the engine reads as the same loop; before, `repeat set x`
|
|
148
|
+
read as `repeat x times`. `repeat in X` keeps its collection, and `indexed by i` reads as
|
|
149
|
+
`index i`.
|
|
150
|
+
- **`@lokascript/semantic`: a name with its scope is one value, in every language.** Upstream reads
|
|
151
|
+
`global x`, `element x`, `element's x` (and `the element's x`), `local x` and `dom x` as one
|
|
152
|
+
variable, its idiom for a behavior's state; seven of `@hyperfixi/behaviors`' sources use it.
|
|
153
|
+
Semantic read the scope word alone, so the command around it was dropped, in English and in
|
|
154
|
+
every language: `set element x to 10`, `set global x to 10`, `init set dom count to 42`. The
|
|
155
|
+
tokenizers now read the scope word and the name as one token, and every language writes it as
|
|
156
|
+
written, as it writes `$` and `:` names (es `establecer element x a 10`). A scope word alone
|
|
157
|
+
(`set element to 5`) is still a word.
|
|
158
|
+
- **`@lokascript/semantic`: English writes upstream's postfix `unless` and drops `open`'s mode.**
|
|
159
|
+
`toggle .foo unless I match .bar` was written `toggle .foo then unless I match .bar`, and core's
|
|
160
|
+
prefix `unless C X` as it is; upstream rejects both. English now writes the guard after its
|
|
161
|
+
command, and other languages keep their own form. Upstream's `open` is modal, and the engine
|
|
162
|
+
reads `open #d as non-modal` as an expression, `(#d as non) - modal`. English now writes it
|
|
163
|
+
`call #d.show()`, and `open #d as modal` as `open #d`. The reader still accepts core's mode.
|
|
164
|
+
- **`@lokascript/semantic`: a value keeps its spelling.** A call's arguments keep their spacing:
|
|
165
|
+
`call navigator.clipboard.writeText(#input's value)` became `…(#input'svalue)`. A style block
|
|
166
|
+
kept a space between every token, so `font-family` became `font - family`, a subtraction.
|
|
167
|
+
`show`'s strategy argument is read raw, as upstream reads it: `display:inline-block` became
|
|
168
|
+
`display:inline - block`. A swap's strategy slot takes only core's strategy words (`into`,
|
|
169
|
+
`over`, `innerHTML`, …, `morph`), so `swap arr[0] with arr[2]` exchanges two values, as
|
|
170
|
+
upstream reads it; it was written `swap arr of [0] …`. An attribute with its value and a name
|
|
171
|
+
with its index are one value when nothing spaces them: `add @data-foo=baz` and
|
|
172
|
+
`remove :arr[1]` kept only their first part, and `increment arr[1]` read as
|
|
173
|
+
`increment arr by [1]`. Every language writes them as written. The words inside an index are
|
|
174
|
+
the script's: bn wrote the variable `index` in `var[(index-1)..(index+1)]` as `সূচক`.
|
|
175
|
+
- **`@lokascript/semantic`: events, waits and amounts keep what was written.** A quoted event name
|
|
176
|
+
stays quoted (`LiteralValue.quoted`): `trigger "my event"` now round-trips in all 23 languages,
|
|
177
|
+
and `send "hello" to ChatSocket` keeps its quotes. A spaced time unit is read with its number:
|
|
178
|
+
`wait 2 seconds` translated as `wait 2`, which is two milliseconds. A written `by 1` stays and
|
|
179
|
+
an implicit one is never shown: German wrote `um 1` for every increment, and English dropped
|
|
180
|
+
every written `by 1`. `measure` reads its element: `measure #other` became `measure`.
|
|
181
|
+
- **`@hyperfixi/core`'s reference and hover docs teach what the 4.x engine does.** `open`'s
|
|
182
|
+
`as modal` / `as non-modal` mode does nothing on the engine, so the docs drop it and give
|
|
183
|
+
`call #myDialog.show()` for a non-modal dialog. The `swap` example
|
|
184
|
+
`swap innerHTML of #target with result` was core's `put`, but upstream and the engine exchange
|
|
185
|
+
the two values: it is now `swap #a's value with #b's value`, and the hover text reads "Exchanges
|
|
186
|
+
two elements, or two writable values." The `fetch` example is now a template literal,
|
|
187
|
+
``fetch `/api/${id}` as json``, since upstream sends a naked URL's `${id}` as written.
|
|
188
|
+
Translation keeps core's meaning for both old forms: English writes `put result into #target`
|
|
189
|
+
and a template literal. The Japanese example in `docs/EXAMPLES.md` is written as semantic writes
|
|
190
|
+
it, `クリック で 自分 に .active を 切り替え`; the old one left `を 私` unread.
|
|
191
|
+
|
|
192
|
+
## [4.1.0] - 2026-10-06
|
|
193
|
+
|
|
194
|
+
Translations keep what the source says. `tell` blocks, swaps, view transitions, chains of
|
|
195
|
+
handlers without their `end`s, events with names of several words, and variables named like
|
|
196
|
+
events or keywords now survive `translate`, MCP `translate_code` and the adapter in every
|
|
197
|
+
language. The adapter's per-language bundles render English with semantic's renderer, and
|
|
198
|
+
`@lokascript/semantic/core` gains `registerHandcrafted`.
|
|
199
|
+
|
|
200
|
+
### Added
|
|
201
|
+
|
|
202
|
+
- **`@lokascript/semantic` reads upstream's `start view transition … end`, in all 24 languages.**
|
|
203
|
+
`on click start view transition swap #a with #b end` parsed as a `transition` command whose
|
|
204
|
+
patient was `swap`, so `translate`, MCP `translate_code` and the corpus lost the block and its
|
|
205
|
+
body (`on click transition swap`), and validation reported the rest as unconsumed input. The
|
|
206
|
+
block is now a `viewTransition` command with its `body` (`ViewTransitionSemanticNode`;
|
|
207
|
+
`isViewTransitionBlock` from the package root), nested the way a loop's body is: a command after
|
|
208
|
+
the block's `end` stays after it, and the `end` no longer closes the handler, behavior or `def`
|
|
209
|
+
around it. `start [a] view transition [using "<type>"]` is written in English in every language,
|
|
210
|
+
as core's `using view transition` tail already is (a browser API's name, which no profile
|
|
211
|
+
translates). The corpus row `swap-view-transition` is now written this way and parses on both
|
|
212
|
+
engines.
|
|
213
|
+
|
|
214
|
+
### Changed
|
|
215
|
+
|
|
216
|
+
- **Single-language bundles hold only their own language's hand-crafted patterns.** Each
|
|
217
|
+
command's pattern dispatcher in `@lokascript/semantic` named all 24 languages' hand-crafted
|
|
218
|
+
patterns, so every bundle that could build patterns held all of them (~200 KB minified). Each
|
|
219
|
+
language module now registers its own (`src/patterns/handcrafted/<lang>.ts`, through
|
|
220
|
+
`@lokascript/semantic/core`'s new `registerHandcrafted`), and bundlers drop the rest:
|
|
221
|
+
semantic's single-language bundles are 14–15 KB gzipped smaller (`browser-de` 116 KB instead
|
|
222
|
+
of 130), and a Vite or Rollup build that imports `@lokascript/semantic/core` and one
|
|
223
|
+
`languages/<lang>` gets the same saving. The patterns each language builds are unchanged
|
|
224
|
+
(hashed for all 24 before and after, from source and from the split `dist/`). The adapter's
|
|
225
|
+
bundles, which also changed renderer in this release, are measured in its entry under Fixed.
|
|
226
|
+
|
|
227
|
+
### Fixed
|
|
228
|
+
|
|
229
|
+
- **`@lokascript/semantic`: a variable named like an event or a keyword keeps its name.** Upstream
|
|
230
|
+
and the engine run `set input to "a"` on a variable named `input`, but semantic's English parse
|
|
231
|
+
dropped the whole command (an event name alone took the value as the text "input", which a
|
|
232
|
+
`set` cannot write), so `translate`, MCP `translate_code` and the adapter lost it in every
|
|
233
|
+
language; `put "a" into input` was lost in ar, de, fr, id and zh, whose tokenizers read `input`
|
|
234
|
+
as the event, and `toggle .a on input` in six. An event name alone where a value stands is now
|
|
235
|
+
the variable it spells, as a structure word or a verb already was, except where an event stands
|
|
236
|
+
(`send`, `trigger`, `repeat until event`, `wait`). And the renderer no longer writes a variable
|
|
237
|
+
in the language's own word where that word reads back as another variable: `set when to 1`
|
|
238
|
+
wrote es `establecer cuando a 1`, read back as a variable named `cuando`, in all 23 languages,
|
|
239
|
+
and bn, ms, th and tl wrote `increment i by index` with their word for `index`. A variable keeps
|
|
240
|
+
its localized word only where that word alone is the same value (es `ello` for `it`), and the
|
|
241
|
+
verified render spells one whose neighbours change it (vi `đặt giá trị`, `set`, for `put value`).
|
|
242
|
+
The value matrix now runs ten such names (`input`, `click`, `when`, `index`, …) in every
|
|
243
|
+
position: all pass on both engines. No corpus row changes.
|
|
244
|
+
- **`@lokascript/semantic`: the event a command names reads back in every language, and a
|
|
245
|
+
`wait` keeps its source for any event.** The handler head read a native event name through the
|
|
246
|
+
event table, but `send`, `trigger` and `repeat until event` read it only when the language's
|
|
247
|
+
tokenizer knew the word: es `enviar dobleclic a #x` read back `send dobleclic to #x`, so the
|
|
248
|
+
event fired as `dobleclic` (55 of the 170 localized names per command: es, pt, fr, de, ko and zh
|
|
249
|
+
coinages, and id and sw `unload`). They now read it as its English name, as the head does. A `wait` kept the source of an
|
|
250
|
+
event only from a fixed list, in English first: `wait for myEvent from #b`,
|
|
251
|
+
`wait for unload from #b` and `wait for focusin from window` lost the source in every language, and
|
|
252
|
+
`wait for click or myEvent from #b` everything after `click`. Upstream reads every leg of
|
|
253
|
+
`wait for` that is not a number as an event, so English now does, and a name followed by a
|
|
254
|
+
source is an event in every language (a time wait has none); tr and tl no longer read `focusin`
|
|
255
|
+
as `focus`. bn writes the event `send` sends with `কে`, as `trigger`'s, since
|
|
256
|
+
`#x তে স্ক্রোল তে পাঠান` read as `scroll to #x`; the old `তে` still reads. And fr's `change`
|
|
257
|
+
event, `changement`, no longer reads as the reactive `when … changes` word, which turned
|
|
258
|
+
`repeat until event change` into a `when` feature. Four corpus rows change, the bn renders of
|
|
259
|
+
`send` (`কে` for `তে`).
|
|
260
|
+
- **`@lokascript/semantic`: a hand-written event name of several words is read whole.** The event
|
|
261
|
+
table reads 20 names of several words that the renderer never writes (ar `تمرير الماوس`, de
|
|
262
|
+
`taste runter`, id `lepas tombol`, pt `pressionar tecla`, qu `mana q'away`, sw `bonyeza chini`,
|
|
263
|
+
tr `fare_bas`, zh `鼠标进入`, and twelve more), but their tokenizers split them, and the reader took
|
|
264
|
+
the first word for the event: de `wenn taste runter protokollieren 1` read `on taste log 1`, id
|
|
265
|
+
`ketika lepas tombol …` `on lepas`, sw `unapo bonyeza chini …` `on click`, qu
|
|
266
|
+
`maykama mana q'away …` `on false`, and ar `على تمرير الماوس من #b …` `on scroll`. A `wait for`,
|
|
267
|
+
`send`, `trigger` or `repeat until event` lost the event the same way. The tokenizers now read
|
|
268
|
+
each name whole; tr reads an `_`-joined word as one token when it is a keyword, as sw already did
|
|
269
|
+
(its own `tuş_bas` had never been read), and splits any other at `_` as before. No corpus row
|
|
270
|
+
changes.
|
|
271
|
+
- **`@lokascript/semantic`: an event name of several words keeps what is written after it.** ar
|
|
272
|
+
writes keydown, keyup and resize as `ضغط المفتاح`, `رفع المفتاح` and `تغيير حجم`, id writes
|
|
273
|
+
keydown as `tekan tombol`, and vi writes resize as `đổi kích thước`, but each tokenizer read only
|
|
274
|
+
the first word as the event. So `on keyup from #b log 1` read back `on keyup log 1` in ar,
|
|
275
|
+
`on resize from window` read back `on change` in ar and vi, and `send keyup to #x`,
|
|
276
|
+
`trigger keydown on #x`, `repeat until event keyup from #b` and `wait for resize` lost their
|
|
277
|
+
`to`, `on` or `from`, or waited for `change`. `translate`, MCP `translate_code` and the adapter
|
|
278
|
+
carried the loss. The three tokenizers now read each name whole, as es, fr, pt and qu already
|
|
279
|
+
did. The corpus's ar and vi `window-resize` rows, which the parser's compound repair already
|
|
280
|
+
read right, now parse at full confidence (1.0, was 0.82); no other corpus row changes.
|
|
281
|
+
- **`@lokascript/semantic`: a behavior's handlers need no `end` of their own.** Upstream ends a
|
|
282
|
+
handler's commands at the next feature, so `behavior F on click add .a on keyup log 1 end end`
|
|
283
|
+
is two handlers. The behavior parser split its body only at `end`, read both handlers as one,
|
|
284
|
+
and English wrote `behavior F then add .a then log 1`, which the engine rejects: `translate`,
|
|
285
|
+
MCP `translate_code` and the adapter broke such a behavior in every language. Each piece now goes
|
|
286
|
+
through the splitter a top-level chain of handlers already used, and parses to the same node as
|
|
287
|
+
the form with every `end` written. No corpus row changes (every one writes its `end`s).
|
|
288
|
+
- **`@lokascript/semantic`: a chain of handlers written natively without their `end`s reads as
|
|
289
|
+
written, in every language.** In 9 languages such a chain merged into one handler, at top level
|
|
290
|
+
and inside a behavior, and the result was valid English that ran wrong: de, fr, id, zh, ko, qu
|
|
291
|
+
and tr dropped the second event and ran its commands on the first (`on click add .a then log
|
|
292
|
+
1`), and hi and bn read it as a destination (`add .a to keyup`). The split knew a handler head
|
|
293
|
+
only as `on`'s forms and ja/ko's `を で` / `을 에`; it now reads each language's own: the `when`
|
|
294
|
+
word the renderer writes (de `wenn`, fr `quand`, id `ketika`, qu `maykama`), zh's `一 … 就`, and
|
|
295
|
+
the words after the event (ko `할 때`, tr `i üzerinde`). The same rule ends an `init` with no
|
|
296
|
+
`end` in SOV languages too, and he and vi no longer read a handler after `toggle .a on #x` as the
|
|
297
|
+
toggle's. hi `पर` and bn `তে` are also the destination marker, which almost every command there
|
|
298
|
+
may write first, so a split at one is never trusted: it reads as written, at a confidence below
|
|
299
|
+
the adapter's threshold, so the adapter leaves the script as written and the engine reports it.
|
|
300
|
+
No corpus row changes.
|
|
301
|
+
- **`@lokascript/semantic`: English writes core's view-transition tail as upstream's block.**
|
|
302
|
+
`swap #a with #b using view transition` (and `morph`'s tail) renders to English as
|
|
303
|
+
`start view transition swap #a with #b end`, which upstream _hyperscript and `@hyperfixi/engine`
|
|
304
|
+
both parse; the tail itself is rejected by both. Every language reads the tail, and
|
|
305
|
+
`@lokascript/hyperscript-adapter` hands the host the English render, so a page written in
|
|
306
|
+
another language with a view-transition swap failed to parse and ran nothing. Foreign renders
|
|
307
|
+
keep the tail.
|
|
308
|
+
- **`@lokascript/semantic`: a `swap` keeps both operands in every language.** Only a swap of two
|
|
309
|
+
selectors round-tripped. `swap #target with me`, `swap :x with :y` and a swap of properties
|
|
310
|
+
(`swap my textContent with #a`, `swap #a's textContent with #b's textContent`) lost an operand
|
|
311
|
+
or its property in up to 21 languages, so a translated page swapped the wrong thing or nothing.
|
|
312
|
+
Four causes: swap's roles took no reference or property path; a handler's repair re-parse was
|
|
313
|
+
vetoed when the swap's first operand moved to its real role; an optional slot that declined a
|
|
314
|
+
value by type still consumed it (any command's pattern could lose the next role that way); and
|
|
315
|
+
in pl/uk the with-word is also the of-word, so `swap el with #t` read as `#t's el`.
|
|
316
|
+
- **`@lokascript/semantic`: a `tell` block keeps its extent.** Semantic kept `tell` flat, its body
|
|
317
|
+
being every statement after it, so in English and every translation a command after the tell's
|
|
318
|
+
`end` was lost (`tell #modal show end then log 2` lost the `log`) or pulled inside it
|
|
319
|
+
(`… end log 2`), a nested tell took its parent's remaining commands, and in a behavior or `def`
|
|
320
|
+
the tell's `end` closed the enclosing block, dropping the next handler or command. The parser
|
|
321
|
+
now nests a tell's body as it nests a loop's (`BlockCommandSemanticNode`, `isBlockCommand`), and
|
|
322
|
+
reads an `end` as the tell's exactly when the engine does: before a new `on <event>`, `def`,
|
|
323
|
+
`init` or `behavior`, a tell needs none.
|
|
324
|
+
- **`@lokascript/hyperscript-adapter`'s per-language and regional bundles render English with
|
|
325
|
+
semantic's renderer.** They rendered with their own English writer, which saved English's
|
|
326
|
+
language data and had drifted far from semantic's: over the corpus's 3,772 translations, 1,242
|
|
327
|
+
rendered to valid English that did something else, and 345 to English the engine rejects (the
|
|
328
|
+
script then kept its author's text and did not run). Every `if` lost its branches,
|
|
329
|
+
`put … before`/`after` became `put … into`, `from window`, `from elsewhere`, `debounced` and
|
|
330
|
+
`or <event>` vanished, `<button/> in me` widened to the whole page, `repeat for x in …` lost
|
|
331
|
+
its binding, and every `swap #a with #b` was written `swap of #a with #b`. They now give exactly
|
|
332
|
+
what the full `hyperscript-i18n.global.js` gives. English's own module builds through
|
|
333
|
+
`@lokascript/semantic/core` (its pattern generator and repeat heads,
|
|
334
|
+
`getRepeatPatternsForLanguage` now exported there), so a bundle holds one copy. Against 4.0.1,
|
|
335
|
+
the single-language bundles are the same size (`-de` 116 KB gzipped) and `-en` is 109 KB
|
|
336
|
+
instead of 148; the regional bundles are 8–16 KB larger (`western` 141 KB instead of 125).
|
|
337
|
+
|
|
10
338
|
## [4.0.1] - 2026-10-05
|
|
11
339
|
|
|
12
340
|
Fixes for 4.0.0: the per-language adapter bundles read every language again, and the MCP server's
|
|
@@ -1095,7 +1423,9 @@ _Synchronized version release. See git history for details._
|
|
|
1095
1423
|
- npm access token stored in GitHub Secrets
|
|
1096
1424
|
- 2FA recommended for npm organization
|
|
1097
1425
|
|
|
1098
|
-
[Unreleased]: https://github.com/codetalcott/hyperfixi/compare/v4.0
|
|
1426
|
+
[Unreleased]: https://github.com/codetalcott/hyperfixi/compare/v4.2.0...HEAD
|
|
1427
|
+
[4.2.0]: https://github.com/codetalcott/hyperfixi/compare/v4.1.0...v4.2.0
|
|
1428
|
+
[4.1.0]: https://github.com/codetalcott/hyperfixi/compare/v4.0.1...v4.1.0
|
|
1099
1429
|
[4.0.1]: https://github.com/codetalcott/hyperfixi/compare/v4.0.0...v4.0.1
|
|
1100
1430
|
[4.0.0]: https://github.com/codetalcott/hyperfixi/compare/v3.3.0...v4.0.0
|
|
1101
1431
|
[3.3.0]: https://github.com/codetalcott/hyperfixi/compare/v3.2.0...v3.3.0
|
|
@@ -2427,13 +2427,14 @@ function liftNodeDiagnostics(node, diagnostics) {
|
|
|
2427
2427
|
if (!Array.isArray(nodeDiags)) return;
|
|
2428
2428
|
for (const d of nodeDiags) {
|
|
2429
2429
|
if (d.severity !== "warning" && d.severity !== "error") continue;
|
|
2430
|
+
const unread = d.code === "unconsumed-input" || d.code === "verbatim-clause";
|
|
2430
2431
|
const diag = {
|
|
2431
2432
|
severity: d.severity,
|
|
2432
2433
|
// Parser codes are kebab-case; this surface uses UPPER_SNAKE (PARSE_ERROR &c).
|
|
2433
|
-
code: (d.code ?? "PARSE_DIAGNOSTIC").replace(/-/g, "_").toUpperCase(),
|
|
2434
|
+
code: unread ? "UNCONSUMED_INPUT" : (d.code ?? "PARSE_DIAGNOSTIC").replace(/-/g, "_").toUpperCase(),
|
|
2434
2435
|
message: d.message ?? "parser diagnostic"
|
|
2435
2436
|
};
|
|
2436
|
-
if (
|
|
2437
|
+
if (unread) {
|
|
2437
2438
|
diag.suggestion = "Dropped tokens were parsed but bound to no role \u2014 usually a missing role marker (e.g. 'to'/'on'/'from' before the target), so the role fell back to a default like `me`. Compare the returned roles against your intent.";
|
|
2438
2439
|
}
|
|
2439
2440
|
diagnostics.push(diag);
|
|
@@ -2656,6 +2657,9 @@ function nodeToParseResult(node) {
|
|
|
2656
2657
|
}
|
|
2657
2658
|
|
|
2658
2659
|
// src/service.ts
|
|
2660
|
+
function isLossyTranslation(error) {
|
|
2661
|
+
return error instanceof Error && error.name === "LossyTranslationError" && typeof error.partial === "string";
|
|
2662
|
+
}
|
|
2659
2663
|
var CompilationService = class _CompilationService {
|
|
2660
2664
|
constructor(options = {}) {
|
|
2661
2665
|
this.translateFn = null;
|
|
@@ -2816,6 +2820,21 @@ var CompilationService = class _CompilationService {
|
|
|
2816
2820
|
diagnostics: this.translationCollisions(request)
|
|
2817
2821
|
};
|
|
2818
2822
|
} catch (error) {
|
|
2823
|
+
if (isLossyTranslation(error)) {
|
|
2824
|
+
return {
|
|
2825
|
+
ok: false,
|
|
2826
|
+
partial: error.partial,
|
|
2827
|
+
loss: { kind: error.loss.kind, lost: [...error.loss.lost] },
|
|
2828
|
+
diagnostics: [
|
|
2829
|
+
{
|
|
2830
|
+
severity: "error",
|
|
2831
|
+
code: "LOSSY_TRANSLATION",
|
|
2832
|
+
message: error.message,
|
|
2833
|
+
suggestion: "The translation would drop the part listed in `loss.lost`. Rewrite that part of the source, or translate the rest without it."
|
|
2834
|
+
}
|
|
2835
|
+
]
|
|
2836
|
+
};
|
|
2837
|
+
}
|
|
2819
2838
|
return {
|
|
2820
2839
|
ok: false,
|
|
2821
2840
|
diagnostics: [
|
|
@@ -3286,4 +3305,4 @@ export {
|
|
|
3286
3305
|
SvelteRenderer,
|
|
3287
3306
|
CompilationService
|
|
3288
3307
|
};
|
|
3289
|
-
//# sourceMappingURL=chunk-
|
|
3308
|
+
//# sourceMappingURL=chunk-QUA3XGWW.js.map
|