@lokascript/compilation-service 4.1.0 → 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 +184 -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,188 @@ 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
|
+
|
|
10
192
|
## [4.1.0] - 2026-10-06
|
|
11
193
|
|
|
12
194
|
Translations keep what the source says. `tell` blocks, swaps, view transitions, chains of
|
|
@@ -1241,7 +1423,8 @@ _Synchronized version release. See git history for details._
|
|
|
1241
1423
|
- npm access token stored in GitHub Secrets
|
|
1242
1424
|
- 2FA recommended for npm organization
|
|
1243
1425
|
|
|
1244
|
-
[Unreleased]: https://github.com/codetalcott/hyperfixi/compare/v4.
|
|
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
|
|
1245
1428
|
[4.1.0]: https://github.com/codetalcott/hyperfixi/compare/v4.0.1...v4.1.0
|
|
1246
1429
|
[4.0.1]: https://github.com/codetalcott/hyperfixi/compare/v4.0.0...v4.0.1
|
|
1247
1430
|
[4.0.0]: https://github.com/codetalcott/hyperfixi/compare/v3.3.0...v4.0.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
|