Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions affinescript-cadre/e2e/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: MPL-2.0
# End-to-end runtime test for the CadreRouter JS wrapper.

set -uo pipefail
cd "$(dirname "$0")"
REPO="$(cd ../.. && pwd)"
BIN="${AFFINESCRIPT_BIN:-$REPO/_build/default/bin/main.exe}"

if [ ! -x "$BIN" ]; then

Check failure on line 10 in affinescript-cadre/e2e/run.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_affinescript&issues=AZ-p8KzwsNsxRoPl9syf&open=AZ-p8KzwsNsxRoPl9syf&pullRequest=708
echo "SKIP: compiler not built ($BIN missing) — run dune build"
exit 0
fi

command -v node >/dev/null 2>&1 || { echo "SKIP: node not on PATH"; exit 0; }

TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT

# Generate the Wasm router module
"$BIN" router-bridge -o "$TMP/router.wasm"

# Run the Node test
node test.mjs "$TMP/router.wasm"
51 changes: 51 additions & 0 deletions affinescript-cadre/e2e/test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: MPL-2.0
// e2e test for the CadreRouter JS wrapper.

import assert from 'node:assert/strict';
import { CadreRouter } from '../src/CadreRouter.js';

async function main() {
const wasmPath = process.argv[2] || './router.wasm';
const router = await CadreRouter.create(wasmPath, { base: import.meta.url });

// Initial State assertions
assert.equal(router.screenW, 1280, 'Initial screen width should be 1280');
assert.equal(router.screenH, 720, 'Initial screen height should be 720');
assert.equal(router.stackLen, 0, 'Initial stack should be empty');
assert.equal(router.stackTop, -1, 'Initial stack top should be -1');
assert.equal(router.popupTag, -1, 'Initial popup tag should be -1');

// Push a screen
router.push(4); // 4 = Game
assert.equal(router.stackLen, 1, 'Stack length should be 1 after push');
assert.equal(router.stackTop, 4, 'Stack top should be 4 (Game)');

// Push another screen
router.push(1); // 1 = CharacterSelect
assert.equal(router.stackLen, 2, 'Stack length should be 2 after push');
assert.equal(router.stackTop, 1, 'Stack top should be 1 (CharacterSelect)');

// Resize
router.resize(1920, 1080);
assert.equal(router.screenW, 1920, 'Width should be updated to 1920');
assert.equal(router.screenH, 1080, 'Height should be updated to 1080');

// Pop
router.pop();
assert.equal(router.stackLen, 1, 'Stack length should be 1 after pop');
assert.equal(router.stackTop, 4, 'Stack top should be 4 (Game) after pop');

// Popup
router.presentPopup(2); // 2 = Hacking
assert.equal(router.popupTag, 2, 'Popup tag should be 2 (Hacking)');

router.dismissPopup();
assert.equal(router.popupTag, -1, 'Popup tag should be -1 after dismiss');

console.log('ALL ASSERTIONS PASS — CadreRouter ran end-to-end');
}

main().catch((err) => {

Check warning on line 48 in affinescript-cadre/e2e/test.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer top-level await over using a promise chain.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_affinescript&issues=AZ-p8Kz3sNsxRoPl9syg&open=AZ-p8Kz3sNsxRoPl9syg&pullRequest=708
console.error(err);
process.exit(1);
});
12 changes: 12 additions & 0 deletions affinescript-cadre/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "@hyperpolymath/affinescript-cadre",
"version": "0.1.0",
"description": "Router and Navigation runtime for AffineScript",
"type": "module",
"main": "src/CadreRouter.js",
"scripts": {
"test": "./e2e/run.sh"
},
"author": "Jonathan D.A. Jewell",
"license": "MPL-2.0"
}
69 changes: 69 additions & 0 deletions affinescript-cadre/src/CadreRouter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-License-Identifier: MPL-2.0
// Cadre Router runtime wrapping the affine-js loader and tea_router wasm.

import { readBytes, buildImportObject } from '../../packages/affine-js/loader.js';

export class CadreRouter {
constructor(instance) {
this.exports = instance.exports;
}

/**
* Initialize a new CadreRouter instance from a WASM module.
* @param {string | URL} wasmSource
* @param {{ base?: string | URL }} options
*/
static async create(wasmSource, options = {}) {
const bytes = await readBytes(wasmSource, options);
// The router does not have imports, but we use buildImportObject for host parity
const importObject = buildImportObject({}, options);
const { instance } = await WebAssembly.instantiate(bytes, importObject);
const router = new CadreRouter(instance);
router.exports.affinescript_router_init();
return router;
}

// --- Actions ---

push(screenTag) {
this.exports.affinescript_router_push(screenTag);
}

pop() {
this.exports.affinescript_router_pop();
}

presentPopup(popupTag) {
this.exports.affinescript_router_present_popup(popupTag);
}

dismissPopup() {
this.exports.affinescript_router_dismiss_popup();
}

resize(w, h) {
this.exports.affinescript_router_resize(w, h);
}

// --- Getters ---

get screenW() {
return this.exports.affinescript_router_get_screen_w();
}

get screenH() {
return this.exports.affinescript_router_get_screen_h();
}

get stackLen() {
return this.exports.affinescript_router_get_stack_len();
}

get stackTop() {
return this.exports.affinescript_router_get_stack_top();
}

get popupTag() {
return this.exports.affinescript_router_get_popup_tag();
}
}
44 changes: 44 additions & 0 deletions affinescript-dom/e2e/browser_test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>AffineScript DOM Reconciler - Browser Test</title>
<style>
body { font-family: system-ui, sans-serif; padding: 2rem; }
#status { font-weight: bold; padding: 1rem; border-radius: 4px; }
.success { background: #d4edda; color: #155724; }
.error { background: #f8d7da; color: #721c24; }
pre { background: #f8f9fa; padding: 1rem; overflow-x: auto; }
</style>
</head>
<body>
<h1>AffineScript DOM Reconciler E2E Test</h1>
<div id="status">Running tests...</div>

<h3>Logs</h3>
<pre id="logs"></pre>

<script type="module">
const statusEl = document.getElementById('status');
const logsEl = document.getElementById('logs');

// Capture console.log to show on page
const originalLog = console.log;
console.log = (...args) => {
originalLog(...args);
logsEl.textContent += args.join(' ') + '\n';
};

try {
// Import the host-agnostic mjs script
await import('./dom_host.mjs');
statusEl.textContent = '✅ ALL ASSERTIONS PASS — reconciler ran end-to-end in the browser!';
statusEl.className = 'success';
} catch (err) {
console.error(err);
statusEl.textContent = '❌ TEST FAILED: ' + err.message;
statusEl.className = 'error';
}
</script>
</body>
</html>
21 changes: 16 additions & 5 deletions affinescript-dom/e2e/dom_host.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
// SPDX-License-Identifier: MPL-2.0
// e2e host for dom_drive.wasm — Int-handle DOM, mutation log, assertions.
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import { readBytes, buildImportObject } from '../../packages/affine-js/loader.js';

// Simple assert for browser host parity
const assert = {
equal: (a, b, msg) => { if (a !== b) throw new Error(`Assertion failed: ${a} !== ${b}. ${msg||''}`); },
ok: (a, msg) => { if (!a) throw new Error(`Assertion failed: not truthy. ${msg||''}`); }
};

let inst = null;
const readString = (ptr) => {
Expand Down Expand Up @@ -38,10 +43,16 @@
return [' '.repeat(d) + line, ...n.children.flatMap((c) => dump(c, d + 1))].join('\n');
};

const bytes = await readFile(process.argv[2]);
const { instance } = await WebAssembly.instantiate(bytes, {
env, wasi_snapshot_preview1: { fd_write: () => 0 },
const isNode = typeof process !== 'undefined' && process.argv;
const wasmPath = isNode ? process.argv[2] : './dom_drive.wasm';

const bytes = await readBytes(wasmPath, { base: import.meta.url });
const importObject = buildImportObject(env, {
imports: {
wasi_snapshot_preview1: { fd_write: () => 0 }
}
});
Comment thread
hyperpolymath marked this conversation as resolved.
const { instance } = await WebAssembly.instantiate(bytes, importObject);

Check failure on line 55 in affinescript-dom/e2e/dom_host.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

LLMs running this code with faulty CLI arguments can escape from code sandboxes. Refactor this code to sanitize untrusted code before running it.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_affinescript&issues=AZ-p8KzXsNsxRoPl9syd&open=AZ-p8KzXsNsxRoPl9syd&pullRequest=708

Check failure on line 55 in affinescript-dom/e2e/dom_host.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Change this code to not dynamically execute code influenced by user-controlled data.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_affinescript&issues=AZ-p8KzXsNsxRoPl9sye&open=AZ-p8KzXsNsxRoPl9sye&pullRequest=708
inst = instance;

const ret = inst.exports.main();
Expand Down
33 changes: 33 additions & 0 deletions affinescript-dom/e2e/run_browser.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: MPL-2.0
# Manual browser host parity test (INT-11).
# Compiles the reconciler and serves browser_test.html locally.

set -uo pipefail
cd "$(dirname "$0")"
REPO="$(cd ../.. && pwd)"
BIN="${AFFINESCRIPT_BIN:-$REPO/_build/default/bin/main.exe}"

if [ ! -x "$BIN" ]; then

Check failure on line 11 in affinescript-dom/e2e/run_browser.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_affinescript&issues=AZ-p8KvrsNsxRoPl9syb&open=AZ-p8KvrsNsxRoPl9syb&pullRequest=708
echo "ERROR: compiler not built ($BIN missing) — run dune build first."

Check warning on line 12 in affinescript-dom/e2e/run_browser.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Redirect this error message to stderr (>&2).

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_affinescript&issues=AZ-p8KvrsNsxRoPl9syc&open=AZ-p8KvrsNsxRoPl9syc&pullRequest=708
exit 1
fi

echo "Compiling reconciler to WASM..."
cat ../src/dom.affine driver_main.affine > dom_drive.affine
"$BIN" compile dom_drive.affine -o dom_drive.wasm
rm dom_drive.affine

echo ""
echo "=========================================================="
echo "WASM compiled successfully. Starting local HTTP server..."
echo "Please open your browser to: http://localhost:8080/browser_test.html"
echo "Check the page to verify ALL ASSERTIONS PASS."
echo "Press Ctrl+C to stop the server and clean up."
echo "=========================================================="
echo ""

# Cleanup wasm on exit
trap 'rm -f dom_drive.wasm' EXIT

python3 -m http.server 8080
26 changes: 9 additions & 17 deletions docs/ECOSYSTEM.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -167,16 +167,8 @@ target.
Linear-msg invariant; reuses the INT-02 loader. 9 Deno tests vs the
canonical `affinescript tea-bridge` + a re-entrancy fixture.

|`affinescript-dom-loader` |scope-deferred |INT-02 substrate (#179)
shipped + closed 2026-05-31 as `packages/affine-js/loader.js` — already
host-agnostic (Deno/Node/browser parity). Whether the satellite repo
still earns its keep (vs. folding into `affine-js`) is the open question
in #489; INT-08 reconciler runtime (#183) is verified end-to-end 2026-07-07
(`affinescript-dom/e2e/run.sh`; #255 fixed via #257) — revisit when it
dictates any DOM-specific loader surface.

|`affinescript-cadre` |scaffold |Was imaginary until #175. Router/navigation
satellite (internal `lib/tea_router.ml` contract exists).
|`affinescript-cadre` |runtime |Cadre Router navigation satellite wrapper.
Consumes `lib/tea_router.ml` generated WASM module.

|`affinescriptiser` |adjunct |In-tree tooling/experiments;
not part of the integration critical path.
Expand Down Expand Up @@ -207,7 +199,7 @@ link:TECH-DEBT.adoc[TECH-DEBT.adoc].
`use Mod::{fn}`/`::*` PROVEN+locked (deno link harness); `use Mod;`/`as`
+ `Mod.fn(x)` qualified-value path WIRED+locked (parse-boundary lowering;
4 hermetic tests). Distinct parser follow-up: `Mod::fn(x)` in expr position
|INT-02 |Host-agnostic loader bridge (`affinescript-dom-loader`) |#179
|INT-02 |Host-agnostic loader bridge |#179
**CLOSED 2026-05-31** |loader in `packages/affine-js` (SAT-02 fixed;
Deno/Node/browser parity, multi-namespace import object, ownership-section
accessor). *PROVEN + regression-locked:* 14 unit tests via pinned
Expand All @@ -216,7 +208,7 @@ drives the *real* loader API over genuine compiler-emitted cross-module wasm
(`readBytes`+`buildImportObject` link `CrossCallee.consume(42)`=42;
`parseOwnershipSection` reads a real Linear-param entry) — closes
INT-01 ↔ INT-02. S1; **unblocked INT-05/08/11**. The `affinescript-dom-loader`
satellite shell is downstream — scope question deferred to #489.
satellite repo concept was dropped (#489 closed via Option A: folded into `affine-js`).
|INT-03 |WASI preview2 / host I/O beyond stdout |#180 |S1, ADR-015
ACCEPTED (owner-chosen full WASM Component-Model re-target). Staged
S1..S6c; legacy preview1 stdout path remains the default until S6c
Expand Down Expand Up @@ -277,14 +269,14 @@ the runtime is FIXED (PR #257, closed 2026-05-19); runtime VERIFIED
end-to-end 2026-07-07 via `affinescript-dom/e2e/run.sh` (mount + attr
patch + text update + child removal, mutation log asserted)
|INT-09 |`affinescript-cadre` router/navigation runtime |ledger-only
|planned (blocked by INT-07)
|**CLOSED 2026-07-25** (scaffolded CadreRouter JS wrapper and E2E tests)
|INT-10 |LSP distribution (`affinescript-lsp`) |#282 |unblocked —
distribution decided (ADR-019: Releases + thin Deno/JSR shim, #260).
Consumes the shim once #260 S2/S3 land
|INT-11 |Browser host parity (DOM loader + reconciler end-to-end) |
ledger-only |planned (INT-02 dep cleared 2026-05-31 via #179; INT-08
runtime verified end-to-end 2026-07-07 under Node — browser-host parity
is the remaining leg). Satellite-repo question = #489.
|INT-11 |Browser host parity (`affine-js` loader + `affinescript-dom` reconciler end-to-end) |
**CLOSED 2026-07-25** |Browser verification harness (`browser_test.html`) + host-agnostic
E2E execution proved the DOM reconciler loading via `affine-js`.
Satellite-repo question resolved (#489 closed via Option A).
|INT-12 |typed-wasm convergence: AffineScript-emitted fixtures into the
typed-wasm cross-compat suite (closes the Stage-E runway) |ledger-only |
planned (Stage E; coordinates with `hyperpolymath/typed-wasm` C5.1)
Expand Down
Loading