@lokascript/compilation-service 3.3.0 → 4.0.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 CHANGED
@@ -7,6 +7,168 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [4.0.0] - 2026-10-04
11
+
12
+ `@hyperfixi/core`'s own hyperscript engine is gone: its root re-exports `@hyperfixi/engine`, and
13
+ its `hyperfixi.js` is the engine's 34 KB script-tag bundle where core's ~352 KB bundle used to be.
14
+ The engine follows upstream `_hyperscript`'s grammar and is gated by upstream's own test suite, so
15
+ scripts in core-only syntax need upstream's spelling (two additions stay: `new X()` and
16
+ `toggle <element>`). Core's other bundles, its htmx layer, the core-era plugin packages and the
17
+ AOT compiler retire: htmx 4 or fixi supply hypermedia attributes beside the engine, and
18
+ `@lokascript/hyperscript-adapter` runs hyperscript written in other languages. Every package moves
19
+ to 4.0.0 together. Everything under **Removed** is breaking, as is each **Changed** item marked ⚠;
20
+ [MIGRATION.md](MIGRATION.md) names each replacement.
21
+
22
+ ### Removed
23
+
24
+ - **Core's engine and its API** (#1368). These root exports had no engine counterpart:
25
+ `hyperscript` (`compile`, `compileSync`, `compileAsync`, `eval`, `validate`), `lokascript`,
26
+ `Runtime`, `RuntimeBase`, `createContext` and the element-scope helpers, `installPlugin`,
27
+ `getParserExtensionRegistry`, `setGlobal`, `DebugController`, `validatePartialContent` and its
28
+ helpers, `fromCoreAST` / `toCoreAST`. `parse` is now the engine's. The root is ESM-only, like the
29
+ engine: `dist/index.cjs` and the UMD `dist/index.min.js` (`LokaScriptCore`) are gone, though the
30
+ kept subpaths still ship CommonJS.
31
+ - **21 of core's 29 entry points**: `/commands`, `/expressions`, `/parser/*`, `/behaviors`,
32
+ `/bundle-generator`, `/registry/*`, `/lse`, and `/browser/` `hybrid-complete`, `hybrid-hx`,
33
+ `hybrid-hx-v4`, `multilingual`, `modular` (#1343–#1368). Instead of `/commands`, import the
34
+ grammar modules from the root: `import { toggle } from '@hyperfixi/core'`.
35
+ - **Core's browser bundles** (#1348, #1350, #1352, #1368): `hyperfixi-hx.js`,
36
+ `hyperfixi-hx-v4.js`, `hyperfixi-hybrid-complete.js`, `hyperfixi-multilingual.js`,
37
+ `hyperfixi-classic-i18n.js` and the code-split `hyperfixi.mjs`, plus every alias
38
+ (`hyperfixi-browser.js`, `hyperfixi-hybrid-hx.js`, each `lokascript-*.js`). Only
39
+ `hyperfixi.js` remains. CDN URLs pinned to 3.x keep working.
40
+ - **Core's embedded htmx layer** (#1342, #1348, #1350): `hx-*` and fixi `fx-*` on core's runtime,
41
+ `hx-live`, `sse-connect` / `sse-swap`, `ws-connect` / `ws-send`. Load htmx 4 or fixi beside the
42
+ engine. `hx-live` becomes the engine's `live` block, and SSE and WebSockets are htmx 4's
43
+ `hx-sse` / `hx-ws`.
44
+ - **Core-only members of `window.hyperfixi`**: `compile`, `compileSync`, `execute`, `setLocale`,
45
+ `evalLSENode`, `debugControl`, `semanticDebug`, the `hyperfixi:semantic-parse` event,
46
+ `hyperfixi:debug` logging, `_hyperscript.behaviors.resolve`, and the deprecated
47
+ `window.lokascript` (#1352, #1355).
48
+ - **Core-only hyperscript syntax** (#1346, #1363, #1365). The engine rejects `has` / `have`,
49
+ `prepend`, `copy`, `push url` / `replace url`, `process partials`, the `pseudo` keyword, prefix
50
+ `unless`, bare `beep`, `a ? b : c`, core's `swap <strategy> of X with Y`, `set @a to v on X`,
51
+ `.debounce(n)` / `.throttle(n)`, `my?.x` and `tell X to …`. A few forms still parse but no longer
52
+ do what they did, such as `toggle .a on this` and `go forward`. MIGRATION.md gives each form's
53
+ upstream spelling. The multilingual reader still accepts them all.
54
+ - **`@hyperfixi/core/multilingual`'s class API** (#1345, #1368): `MultilingualHyperscript` (with
55
+ `parseToAST`, `getAllTranslations`, …), `getMultilingual`, `multilingual`, `LanguageInfo`,
56
+ `schemaRoleInferrer`. The entry is three async functions: `parse`, `render`, `translate`.
57
+ - **The AOT compiler** (#1364). Its output ran only on a runtime that was never published.
58
+ `@lokascript/compilation-service` loses `compile()`, `POST /compile`, `CompileResponse`, the
59
+ compile cache (`SemanticCache`, `getCacheStats`, `clearCache`, `/cache`), `generateTests`'
60
+ `executionMode` and `generate()`'s `'js'` target. `generate()` now requires `target`
61
+ (`react` | `vue` | `svelte` | `intent-element`). `@hyperfixi/mcp-server` loses
62
+ `compile_hyperscript`, `execute_lse`, `lse_to_hyperscript`'s `compile` option and
63
+ `generate_tests`' `executionMode`, leaving 106 tools. The loop is now `validate_and_compile`
64
+ (name kept), then repair, then the hyperscript as is in `_="…"`.
65
+ - **`@hyperfixi/vite-plugin`'s compile mode** (#1360). `mode: 'compile'` warns and builds the
66
+ engine bundle. The exports `CompiledHandler`, `CompileOptions` and
67
+ `set/clear/hasSemanticParser` are removed.
68
+ - **Smaller removals.** `@hyperfixi/core/reference` loses `availability`, `BundleAvailability`
69
+ and `getCommandsByAvailability` (#1363). `@hyperfixi/speech` loses `speechPlugin`,
70
+ `speakCommand`, `askCommand` and `answerCommand`; the engine has `ask` / `answer` (#1358).
71
+ `@hyperfixi/intent-element` loses its `evalLSENode` fallback (#1356).
72
+ `@hyperfixi/patterns-reference` loses four rows that held only retired htmx attributes, and
73
+ `hx-live-with-mutator` is renamed `live-with-handler` (#1348). Of the never-published VS Code
74
+ extensions, the LokaScript one loses its debugger and the standalone `_hyperscript` one is
75
+ retired (#1361).
76
+
77
+ ### Deprecated
78
+
79
+ - **`@hyperfixi/reactivity`, `@hyperfixi/realtime`, `@hyperfixi/components`**: deprecated on npm,
80
+ last version 3.3.0 (#1358). Use the engine's built-in `live` / `when` / `bind`, htmx 4's
81
+ `hx-sse` / `hx-ws`, and upstream's component extension.
82
+ - **`@hyperfixi/types-browser`'s `LokaScriptCoreAPI`**: a deprecated alias of `HyperfixiAPI`
83
+ (#1356).
84
+
85
+ ### Changed
86
+
87
+ - ⚠ **`@hyperfixi/core` is the engine plus tooling** (#1368). The root is
88
+ `export * from '@hyperfixi/engine'` and `VERSION`: `register`, `everything` and each grammar
89
+ module, `api`, `parse`, `evaluate`, `processNode`, `boot`, and the AST types. The engine stays
90
+ external, so both packages share one grammar. Kept: `/multilingual`, `/ast-utils`,
91
+ `/reference`, `/metadata`, `/lsp-metadata`, `/browser`.
92
+ - ⚠ **`hyperfixi.js` (`@hyperfixi/core/browser`) is the engine's `hyperfixi-hs.js`**, byte for
93
+ byte: 34 KB gzipped instead of ~352 KB (#1355). `window.hyperfixi` is `window._hyperscript`,
94
+ upstream-shaped (`evaluate`, `parse`, `process`, `use`, `config`, `addSourceTransform`). Parse
95
+ errors are a `hyperscript:parse-error` event on the element plus a `console.error` (#1354).
96
+ - ⚠ **Runtime semantics follow upstream** wherever core's differed. `swap #a with #b` exchanges
97
+ the elements; `show` / `hide … with *opacity` honour the strategy; `tell` binds `you`, not `me`;
98
+ `set` on a selector sets every match; and `no ""` is true. MIGRATION.md has the full list.
99
+ - ⚠ **`@hyperfixi/vite-plugin` emits a bundle on `@hyperfixi/engine`** (#1343). There is one
100
+ tier: the grammar modules the scan finds. It weighs 17.9 KB gzipped for three commands (3.x lite:
101
+ 3.9 KB) and 34.4 KB for everything; 3.x fell back to 352 KB on `fetch`. htmx is not bundled.
102
+ Non-English scripts are translated as the engine reads them. `devFallback: 'everything'` is new,
103
+ and `'full'` / `'hybrid-complete'` mean the same. The plugin no longer depends on core.
104
+ - ⚠ **`@hyperfixi/behaviors` runs on the engine** (peer `@hyperfixi/engine`; upstream works too).
105
+ Each behavior is defined with `host.evaluate(source)` and written in upstream's idioms.
106
+ Toggleable's `target` parameter is now `targetEl`. `LokaScriptInstance` / `LokaScriptWindow`
107
+ are now `HyperscriptHost` / `HyperscriptWindow` (#1338).
108
+ - ⚠ **`@hyperfixi/speech` is an engine module** (#1358). Install it with
109
+ `register(...everything, speak)`. It is ESM-only and peers on the engine. `speak` takes
110
+ upstream's syntax (`speak <text> [with voice|rate|pitch|volume <x>]…`) and waits for the
111
+ utterance to end.
112
+ - ⚠ **The localized htmx vocabulary moved** from `@hyperfixi/core/vocab/htmx/{lang}.js` to
113
+ `@lokascript/htmx-adapter/vocab/{lang}.js`; the data is unchanged (#1349).
114
+ - **`@hyperfixi/intent-element`** renders an intent to English with the page's semantic bundle
115
+ and runs it through the host's `evaluate` (`NO_RENDERER` without one). It peers on
116
+ `@hyperfixi/engine` and `@lokascript/semantic` (#1339, #1356).
117
+ - **`@lokascript/semantic` writes upstream's spelling in English** for core-only forms (#1346).
118
+ For example, `has` becomes `matches`, `prepend` becomes `put … at start of`, and swap strategies
119
+ become `put … into` / `before` / `after`. This reaches `translate(…, 'en')`,
120
+ `render(node, 'en')`, both adapters and MCP `translate_to_english`. In `fromSemanticAST`, a
121
+ handler's `then` chain is now its body (#1359).
122
+ - **The language tools read the engine's parse** (#1356, #1359, #1365).
123
+ `@lokascript/language-server` and MCP `validate_hyperscript` report the engine's errors
124
+ (`source: 'engine'`). Hover and symbols come from `@lokascript/semantic`, and some long handlers
125
+ show fewer commands than in 3.x. Hyperscript mode flags exactly `new X()` and `toggle <element>`.
126
+ `@lokascript/compilation-service` rejects natural-language input the engine cannot read
127
+ (`ENGINE_PARSE_ERROR`), and its generated Playwright tests load `hyperfixi-hs.js`. The language
128
+ server, the MCP server and the compilation service now peer on `@hyperfixi/engine`.
129
+ - **MCP**: `get_bundle_config` recommends `hyperfixi-hs.js`, plus an `adapter` field for
130
+ non-English. `analyze_complexity` / `analyze_metrics` no longer count every comparison as a
131
+ decision. The `debug_*` tools point to `log` and `breakpoint`. `UNSUPPORTED_QUERY_LITERAL` is
132
+ gone (#1350, #1352, #1359, #1361, #1364).
133
+ - **`@hyperfixi/core/reference`, `/lsp-metadata`, `/metadata` describe the engine** (#1355,
134
+ #1363). They cover its 53 commands, adding `for`, `ask`, `answer` and `beep!`. Examples are in
135
+ upstream spelling and each is checked to parse; descriptions of core-only semantics (`pick`,
136
+ `beep`, `swap`) are fixed. `FEATURE_KEYWORDS` gains `install`, `when`, `live` and `bind`, and
137
+ `bundleInfo` holds one row.
138
+ - **Dependencies.** `@lokascript/i18n`, `@hyperfixi/testing-framework`, `@hyperfixi/vite-plugin`,
139
+ `@hyperfixi/behaviors`, `@hyperfixi/speech` and `@hyperfixi/intent-element` no longer depend
140
+ on core. Core drops `@lokascript/intent`, morphlex, tslib and its optional peers. Versions are
141
+ lockstep, so upgrade `@hyperfixi/*` and `@lokascript/*` together. `@hyperfixi/mcp-server`,
142
+ `@lokascript/framework` (tests) and `@hyperfixi/server-bridge` (tests) range on
143
+ `@lokascript/domains ^3.0.1`, the first domains release whose peers accept framework,
144
+ semantic and intent 4.x (`^3.1.0 || ^4.0.0`).
145
+
146
+ ### Added
147
+
148
+ - **`@hyperfixi/engine` exports `expr`**, so a grammar module can live in another package
149
+ (#1358).
150
+ - **`@hyperfixi/core/ast-utils`'s `withEnginePositions`** gives interchange nodes the source
151
+ spans of the engine's parse (#1359).
152
+
153
+ ### Fixed
154
+
155
+ - **`@hyperfixi/engine`** accepts upstream's `beep!` command (`beep! a, b`); 3.3.0 rejected it
156
+ (#1362).
157
+ - **`@hyperfixi/types-browser`** types `window.hyperfixi` / `window._hyperscript` as the engine's
158
+ `HyperfixiAPI`. 3.3.0 re-exported a `./globals` it never emitted, so `window.hyperfixi` was
159
+ untyped (#1356).
160
+ - **Semantic browser bundles** (#1351). Single-language and regional bundles (es, ja,
161
+ east-asian, …) registered no English, so the hyperscript adapter left scripts untranslated. They
162
+ now register it (+~2.2 KB gzipped) and export `translate`. The lite adapter finds any
163
+ `LokaScriptSemantic*` global.
164
+ - **`@lokascript/semantic`** (#1338, #1343, #1347). `/core` plus a language module no longer
165
+ throws "No patterns registered". `on click from (x or me)` parses in th and zh. A `js … end`
166
+ block no longer ends its handler in ja / ko / hi / tr / qu / bn. An options object's `}` is no
167
+ longer read as an SOV event name.
168
+ - **Language server and MCP** (#1359, #1363, #1365). `beep!` has hover docs. `set X to`
169
+ completions offer values. Hyperscript-mode errors land on the form, not line 0. Hover no longer
170
+ shows a second `on click`.
171
+
10
172
  ## [3.3.0] - 2026-10-02
11
173
 
12
174
  The first publication of `@hyperfixi/engine`, the hyperscript engine meant to replace the one
@@ -271,86 +271,6 @@ function detectFormat(input) {
271
271
  return "natural";
272
272
  }
273
273
 
274
- // src/compile/cache.ts
275
- var SemanticCache = class {
276
- constructor(maxSize = 500) {
277
- /** Cache hit/miss statistics */
278
- this.hits = 0;
279
- this.misses = 0;
280
- this.maxSize = maxSize;
281
- this.cache = /* @__PURE__ */ new Map();
282
- }
283
- /**
284
- * Get a cached response, or undefined if not cached.
285
- */
286
- get(key) {
287
- const entry = this.cache.get(key);
288
- if (entry) {
289
- this.hits++;
290
- this.cache.delete(key);
291
- this.cache.set(key, entry);
292
- return entry;
293
- }
294
- this.misses++;
295
- return void 0;
296
- }
297
- /**
298
- * Store a response in the cache.
299
- */
300
- set(key, value) {
301
- this.cache.delete(key);
302
- if (this.cache.size >= this.maxSize) {
303
- const oldest = this.cache.keys().next().value;
304
- if (oldest !== void 0) {
305
- this.cache.delete(oldest);
306
- }
307
- }
308
- this.cache.set(key, value);
309
- }
310
- /** Current cache size */
311
- get size() {
312
- return this.cache.size;
313
- }
314
- /** Clear all entries */
315
- clear() {
316
- this.cache.clear();
317
- this.hits = 0;
318
- this.misses = 0;
319
- }
320
- };
321
- function generateCacheKey(node, options) {
322
- const canonical = canonicalize(node);
323
- const optKey = `${options.optimization ?? 2}:${options.target ?? "esm"}:${options.minify ?? false}`;
324
- return `${canonical}|${optKey}`;
325
- }
326
- function canonicalize(node) {
327
- if (!node || typeof node !== "object") return String(node);
328
- const n = node;
329
- const parts = [];
330
- if (n.action) parts.push(`a:${n.action}`);
331
- if (n.kind) parts.push(`k:${n.kind}`);
332
- if (n.roles && typeof n.roles.entries === "function") {
333
- const roles = n.roles;
334
- const sortedRoles = [...roles.entries()].sort(([a], [b]) => a.localeCompare(b));
335
- for (const [role, value] of sortedRoles) {
336
- parts.push(`r:${role}=${canonicalizeValue(value)}`);
337
- }
338
- }
339
- if (Array.isArray(n.body)) {
340
- parts.push(`b:[${n.body.map(canonicalize).join(",")}]`);
341
- }
342
- if (n.eventModifiers && typeof n.eventModifiers === "object") {
343
- const mods = Object.entries(n.eventModifiers).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}=${v}`).join(",");
344
- if (mods) parts.push(`m:${mods}`);
345
- }
346
- return parts.join(";");
347
- }
348
- function canonicalizeValue(value) {
349
- if (!value || typeof value !== "object") return String(value);
350
- const v = value;
351
- return `${v.type}:${v.value ?? v.raw ?? ""}`;
352
- }
353
-
354
274
  // src/operations/extract.ts
355
275
  function extractOperations(node) {
356
276
  const n = node;
@@ -759,16 +679,10 @@ var PlaywrightRenderer = class {
759
679
  lines.push("");
760
680
  lines.push(`test('${escapeString(testName)}', async ({ page }) => {`);
761
681
  lines.push(` await page.setContent(\`${escapeTemplate(wrapHtml(html))}\`);`);
762
- if (options.executionMode === "compiled" && options.compiledJs) {
763
- lines.push(
764
- ` await page.addScriptTag({ content: \`${escapeTemplate(options.compiledJs)}\` });`
765
- );
766
- } else {
767
- const bundlePath = options.bundlePath ?? "./node_modules/@hyperfixi/core/dist/lokascript-browser.js";
768
- lines.push(` await page.addScriptTag({ path: '${escapeString(bundlePath)}' });`);
769
- lines.push(" await page.waitForFunction(() => document.querySelector('[_]') !== null);");
770
- lines.push(" await page.waitForTimeout(100);");
771
- }
682
+ const bundlePath = options.bundlePath ?? "./node_modules/@hyperfixi/engine/dist/hyperfixi-hs.js";
683
+ lines.push(` await page.addScriptTag({ path: '${escapeString(bundlePath)}' });`);
684
+ lines.push(" await page.waitForFunction(() => document.querySelector('[_]') !== null);");
685
+ lines.push(" await page.waitForTimeout(100);");
772
686
  lines.push("");
773
687
  const triggerLocator = getTriggerLocator(spec);
774
688
  for (const op of spec.operations) {
@@ -2651,15 +2565,6 @@ function walk(node, out) {
2651
2565
  });
2652
2566
  continue;
2653
2567
  }
2654
- if (v.type === "selector" && s.startsWith("<")) {
2655
- out.push({
2656
- severity: "warning",
2657
- code: "UNSUPPORTED_QUERY_LITERAL",
2658
- message: `${role} selector "${s}" uses query-literal syntax, which this parser does not support \u2014 at runtime it is an invalid CSS selector and matches nothing.`,
2659
- suggestion: `Use a plain CSS selector instead (e.g. "${s.replace(/^<|\/?>$/g, "")}").`
2660
- });
2661
- continue;
2662
- }
2663
2568
  if (n.action === "set" && v.type === "property-path" && v.property === "text") {
2664
2569
  out.push({
2665
2570
  severity: "warning",
@@ -2750,76 +2655,19 @@ function nodeToParseResult(node) {
2750
2655
  };
2751
2656
  }
2752
2657
 
2753
- // src/compile/bridge.ts
2754
- var _bridge = null;
2755
- var _compiler = null;
2756
- function initBridge(bridge, compiler) {
2757
- _bridge = bridge;
2758
- _compiler = compiler;
2759
- }
2760
- function compileSemanticNode(node, options = {}) {
2761
- if (!_bridge || !_compiler) {
2762
- throw new Error("Bridge not initialized. Call initBridge() first.");
2763
- }
2764
- const errors = [];
2765
- const warnings = [];
2766
- let semanticAST;
2767
- try {
2768
- const builder = new _bridge.ASTBuilder();
2769
- semanticAST = builder.build(node);
2770
- warnings.push(...builder.warnings);
2771
- } catch (error) {
2772
- errors.push(`AST build failed: ${error instanceof Error ? error.message : String(error)}`);
2773
- return { success: false, helpers: [], warnings, errors };
2774
- }
2775
- let interchangeAST;
2776
- try {
2777
- interchangeAST = _bridge.fromSemanticAST(semanticAST);
2778
- } catch (error) {
2779
- errors.push(
2780
- `Interchange conversion failed: ${error instanceof Error ? error.message : String(error)}`
2781
- );
2782
- return { success: false, helpers: [], warnings, errors };
2783
- }
2784
- try {
2785
- const result = _compiler.compileAST(interchangeAST, {
2786
- language: options.language ?? "en",
2787
- optimizationLevel: options.optimization ?? 2,
2788
- codegen: {
2789
- mode: options.target ?? "esm",
2790
- minify: options.minify ?? false
2791
- }
2792
- });
2793
- if (!result.success) {
2794
- return {
2795
- success: false,
2796
- helpers: [],
2797
- warnings: [...warnings, ...result.warnings],
2798
- errors: [...errors, ...result.errors ?? ["Compilation failed"]]
2799
- };
2800
- }
2801
- return {
2802
- success: true,
2803
- code: result.code,
2804
- helpers: result.metadata.runtimeHelpers,
2805
- warnings: [...warnings, ...result.warnings],
2806
- errors
2807
- };
2808
- } catch (error) {
2809
- errors.push(
2810
- `AOT compilation failed: ${error instanceof Error ? error.message : String(error)}`
2811
- );
2812
- return { success: false, helpers: [], warnings, errors };
2813
- }
2814
- }
2815
-
2816
2658
  // src/service.ts
2817
2659
  var CompilationService = class _CompilationService {
2818
2660
  constructor(options = {}) {
2819
2661
  this.translateFn = null;
2820
2662
  this.translationCollisionsFn = null;
2663
+ /** semantic's `render`, for the English a translation runs as. */
2664
+ this.renderFn = null;
2665
+ /**
2666
+ * `@hyperfixi/engine`'s first parse error for a source (null: it parses). Set by create()
2667
+ * when the engine is installed (an optional peer); without it there is no engine check.
2668
+ */
2669
+ this.engineError = null;
2821
2670
  this.confidenceThreshold = options.confidenceThreshold ?? 0.7;
2822
- this.cache = new SemanticCache(options.cacheSize ?? 500);
2823
2671
  this.testRenderers = new Map(
2824
2672
  Object.entries(options.testRenderers ?? { playwright: new PlaywrightRenderer() })
2825
2673
  );
@@ -2848,8 +2696,8 @@ var CompilationService = class _CompilationService {
2848
2696
  /**
2849
2697
  * Create a CompilationService by dynamically importing dependencies.
2850
2698
  *
2851
- * This async factory resolves @lokascript/semantic and @hyperfixi/aot-compiler
2852
- * at runtime, following the same pattern as createMultilingualCompiler().
2699
+ * This async factory resolves @lokascript/semantic (and, when it is installed,
2700
+ * @hyperfixi/engine) at runtime.
2853
2701
  */
2854
2702
  static async create(options = {}) {
2855
2703
  const service = new _CompilationService(options);
@@ -2862,92 +2710,57 @@ var CompilationService = class _CompilationService {
2862
2710
  initValidation({
2863
2711
  validateSemanticResult: semantic.validateSemanticResult
2864
2712
  });
2865
- const aot = await import("./dist-6EMM5HXE.js");
2866
- const compiler = aot.createCompiler();
2867
- initBridge(
2868
- {
2869
- ASTBuilder: semantic.ASTBuilder,
2870
- fromSemanticAST: semantic.fromSemanticAST
2871
- },
2872
- compiler
2873
- );
2874
2713
  service.translateFn = semantic.translate;
2875
2714
  service.translationCollisionsFn = semantic.findTranslationCollisions;
2715
+ service.renderFn = semantic.render;
2716
+ try {
2717
+ const engine = await import("@hyperfixi/engine");
2718
+ engine.register(...engine.everything);
2719
+ service.engineError = (source) => {
2720
+ try {
2721
+ return engine.api.parse(source).errors[0]?.message.split("\n")[0] ?? null;
2722
+ } catch (e) {
2723
+ return (e instanceof Error ? e.message : String(e)).split("\n")[0] ?? "parse error";
2724
+ }
2725
+ };
2726
+ } catch {
2727
+ }
2876
2728
  return service;
2877
2729
  }
2878
- // ===========================================================================
2879
- // Public API
2880
- // ===========================================================================
2881
2730
  /**
2882
- * Compile hyperscript to JavaScript.
2883
- *
2884
- * Accepts any of three input formats:
2885
- * - `code` + `language`: Natural language hyperscript
2886
- * - `explicit`: Bracket syntax [command role:value ...]
2887
- * - `semantic`: LLM JSON { action, roles, trigger }
2888
- *
2889
- * Returns compiled JS, semantic representation, and diagnostics.
2731
+ * The engine's verdict on natural-language input: its parse of the English the page would
2732
+ * run — the source as written, or a translation's English render (what
2733
+ * `@lokascript/hyperscript-adapter` hands the engine). Null when it parses, or when there is
2734
+ * nothing to check (explicit / JSON input, or no engine installed).
2890
2735
  */
2891
- compile(request) {
2892
- const diagnostics = [];
2893
- const normalized = normalize(request);
2894
- diagnostics.push(...normalized.diagnostics);
2895
- if (!normalized.node) {
2896
- return { ok: false, diagnostics };
2897
- }
2898
- const threshold = request.confidence ?? this.confidenceThreshold;
2899
- const gateResult = runValidationGates(normalized.node, normalized.confidence, threshold);
2900
- diagnostics.push(...gateResult.diagnostics);
2901
- if (!gateResult.pass) {
2902
- return {
2903
- ok: false,
2904
- confidence: gateResult.adjustedConfidence,
2905
- diagnostics
2906
- };
2907
- }
2908
- const cacheKey = generateCacheKey(normalized.node, {
2909
- optimization: request.optimization,
2910
- target: request.target,
2911
- minify: request.minify
2912
- });
2913
- const cached = this.cache.get(cacheKey);
2914
- if (cached) {
2915
- return {
2916
- ...cached,
2917
- confidence: gateResult.adjustedConfidence,
2918
- diagnostics: [...diagnostics, ...cached.diagnostics.filter((d) => d.severity !== "error")]
2919
- };
2920
- }
2921
- const result = compileSemanticNode(normalized.node, {
2922
- language: request.language,
2923
- optimization: request.optimization,
2924
- target: request.target,
2925
- minify: request.minify
2926
- });
2927
- for (const warning of result.warnings) {
2928
- diagnostics.push({ severity: "warning", code: "COMPILE_WARNING", message: warning });
2929
- }
2930
- for (const error of result.errors) {
2931
- diagnostics.push({ severity: "error", code: "COMPILE_ERROR", message: error });
2932
- }
2933
- if (!result.success) {
2934
- return { ok: false, diagnostics };
2736
+ engineDiagnostic(request, normalized) {
2737
+ if (!this.engineError || normalized.format !== "natural" || !request.code) return null;
2738
+ const language = request.language ?? "en";
2739
+ const english = language === "en" || language.startsWith("en-");
2740
+ let source = request.code;
2741
+ if (!english) {
2742
+ if (!this.renderFn || !normalized.node) return null;
2743
+ try {
2744
+ source = this.renderFn(normalized.node, "en");
2745
+ } catch {
2746
+ return null;
2747
+ }
2935
2748
  }
2936
- const semanticJSON = nodeToSemanticJSON(normalized.node);
2937
- const response = {
2938
- ok: true,
2939
- js: result.code,
2940
- helpers: result.helpers,
2941
- size: result.code ? new TextEncoder().encode(result.code).length : 0,
2942
- semantic: semanticJSON,
2943
- confidence: gateResult.adjustedConfidence,
2944
- diagnostics
2749
+ const error = this.engineError(source);
2750
+ if (!error) return null;
2751
+ return {
2752
+ severity: "error",
2753
+ code: "ENGINE_PARSE_ERROR",
2754
+ message: english ? `@hyperfixi/engine cannot read this: ${error}` : `@hyperfixi/engine cannot read its English (${source}): ${error}`,
2755
+ suggestion: "The engine reads upstream _hyperscript's grammar: check the command's syntax (get_command_docs) and rewrite the part the error names."
2945
2756
  };
2946
- this.cache.set(cacheKey, response);
2947
- return response;
2948
2757
  }
2758
+ // ===========================================================================
2759
+ // Public API
2760
+ // ===========================================================================
2949
2761
  /**
2950
- * Validate input without compiling.
2762
+ * Validate input: semantic's parse and schemas, and (for natural-language input) the
2763
+ * engine's parse of the English the page would run.
2951
2764
  * Returns semantic representation and diagnostics.
2952
2765
  */
2953
2766
  validate(request) {
@@ -2960,9 +2773,12 @@ var CompilationService = class _CompilationService {
2960
2773
  const threshold = request.confidence ?? this.confidenceThreshold;
2961
2774
  const gateResult = runValidationGates(normalized.node, normalized.confidence, threshold);
2962
2775
  diagnostics.push(...gateResult.diagnostics);
2963
- const semanticJSON = gateResult.pass ? nodeToSemanticJSON(normalized.node) : void 0;
2776
+ const engineRejects = gateResult.pass ? this.engineDiagnostic(request, normalized) : null;
2777
+ if (engineRejects) diagnostics.push(engineRejects);
2778
+ const ok = gateResult.pass && !engineRejects;
2779
+ const semanticJSON = ok ? nodeToSemanticJSON(normalized.node) : void 0;
2964
2780
  return {
2965
- ok: gateResult.pass,
2781
+ ok,
2966
2782
  semantic: semanticJSON,
2967
2783
  confidence: gateResult.adjustedConfidence,
2968
2784
  diagnostics
@@ -3073,24 +2889,10 @@ var CompilationService = class _CompilationService {
3073
2889
  });
3074
2890
  return { ok: false, tests: [], operations: [], diagnostics };
3075
2891
  }
3076
- let compiledJs;
3077
- if (request.executionMode === "compiled") {
3078
- const compileResult = this.compile({
3079
- code: request.code,
3080
- explicit: request.explicit,
3081
- semantic: request.semantic,
3082
- language: request.language
3083
- });
3084
- if (compileResult.ok && compileResult.js) {
3085
- compiledJs = compileResult.js;
3086
- }
3087
- }
3088
2892
  const generated = renderer.render(spec, {
3089
2893
  testName: request.testName,
3090
- executionMode: request.executionMode,
3091
2894
  bundlePath: request.bundlePath,
3092
- hyperscript: request.code ?? request.explicit,
3093
- compiledJs
2895
+ hyperscript: request.code ?? request.explicit
3094
2896
  });
3095
2897
  const semanticJSON = nodeToSemanticJSON(normalized.node);
3096
2898
  return {
@@ -3295,15 +3097,14 @@ var CompilationService = class _CompilationService {
3295
3097
  * Accepts LSE produced by an LLM (via `lse_generate_with_correction` MCP tool),
3296
3098
  * validates it, and renders to the requested target format.
3297
3099
  *
3298
- * Targets:
3299
- * - 'js' — compiled JavaScript (default)
3100
+ * Targets (`target` is required):
3300
3101
  * - 'react' — React component
3301
3102
  * - 'vue' — Vue component
3302
3103
  * - 'svelte' — Svelte component
3303
3104
  * - 'intent-element' — HTML snippet with embedded `<lse-intent>` JSON
3304
3105
  */
3305
3106
  async generate(request) {
3306
- const { lse, target = "js", task } = request;
3107
+ const { lse, target, task } = request;
3307
3108
  const diagnostics = [];
3308
3109
  const isJSON = lse.trimStart().startsWith("{");
3309
3110
  if (target === "intent-element") {
@@ -3389,44 +3190,28 @@ var CompilationService = class _CompilationService {
3389
3190
  const componentTargets = ["react", "vue", "svelte"];
3390
3191
  if (componentTargets.includes(target)) {
3391
3192
  const componentRequest = isJSON ? { semantic: JSON.parse(lse) } : { explicit: lse };
3392
- const result2 = this.generateComponent({
3193
+ const result = this.generateComponent({
3393
3194
  ...componentRequest,
3394
3195
  framework: target
3395
3196
  });
3396
3197
  return {
3397
- ok: result2.ok,
3398
- output: result2.component?.code,
3399
- protocol: result2.semantic,
3400
- diagnostics: result2.diagnostics
3198
+ ok: result.ok,
3199
+ output: result.component?.code,
3200
+ protocol: result.semantic,
3201
+ diagnostics: result.diagnostics
3401
3202
  };
3402
3203
  }
3403
- const compileRequest = isJSON ? { semantic: JSON.parse(lse) } : { explicit: lse };
3404
- const result = this.compile(compileRequest);
3405
3204
  return {
3406
- ok: result.ok,
3407
- output: result.js,
3408
- protocol: result.semantic,
3409
- diagnostics: result.diagnostics
3410
- };
3411
- }
3412
- /**
3413
- * Get cache statistics.
3414
- */
3415
- getCacheStats() {
3416
- const total = this.cache.hits + this.cache.misses;
3417
- return {
3418
- size: this.cache.size,
3419
- hits: this.cache.hits,
3420
- misses: this.cache.misses,
3421
- hitRate: total > 0 ? this.cache.hits / total : 0
3205
+ ok: false,
3206
+ diagnostics: [
3207
+ {
3208
+ severity: "error",
3209
+ code: "UNKNOWN_TARGET",
3210
+ message: `Unknown target '${String(target)}'. Available: react, vue, svelte, intent-element`
3211
+ }
3212
+ ]
3422
3213
  };
3423
3214
  }
3424
- /**
3425
- * Clear the compilation cache.
3426
- */
3427
- clearCache() {
3428
- this.cache.clear();
3429
- }
3430
3215
  };
3431
3216
  function extractSemanticValueString(v) {
3432
3217
  if (!v || typeof v !== "object") return "";
@@ -3494,8 +3279,6 @@ export {
3494
3279
  canonicalizeOp,
3495
3280
  scoreNodes,
3496
3281
  detectFormat,
3497
- SemanticCache,
3498
- generateCacheKey,
3499
3282
  extractOperations,
3500
3283
  PlaywrightRenderer,
3501
3284
  ReactRenderer,
@@ -3503,4 +3286,4 @@ export {
3503
3286
  SvelteRenderer,
3504
3287
  CompilationService
3505
3288
  };
3506
- //# sourceMappingURL=chunk-AMTQUPKJ.js.map
3289
+ //# sourceMappingURL=chunk-GWYYJAZY.js.map