Skip to content

Commit e959636

Browse files
committed
feat(cadre): scaffold router navigation runtime wrapper (INT-09)
1 parent 70ca15e commit e959636

5 files changed

Lines changed: 159 additions & 3 deletions

File tree

affinescript-cadre/e2e/run.sh

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: MPL-2.0
3+
# End-to-end runtime test for the CadreRouter JS wrapper.
4+
5+
set -uo pipefail
6+
cd "$(dirname "$0")"
7+
REPO="$(cd ../.. && pwd)"
8+
BIN="${AFFINESCRIPT_BIN:-$REPO/_build/default/bin/main.exe}"
9+
10+
if [ ! -x "$BIN" ]; then
11+
echo "SKIP: compiler not built ($BIN missing) — run dune build"
12+
exit 0
13+
fi
14+
15+
command -v node >/dev/null 2>&1 || { echo "SKIP: node not on PATH"; exit 0; }
16+
17+
TMP="$(mktemp -d)"
18+
trap 'rm -rf "$TMP"' EXIT
19+
20+
# Generate the Wasm router module
21+
"$BIN" router-bridge -o "$TMP/router.wasm"
22+
23+
# Run the Node test
24+
node test.mjs "$TMP/router.wasm"

affinescript-cadre/e2e/test.mjs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// e2e test for the CadreRouter JS wrapper.
3+
4+
import assert from 'node:assert/strict';
5+
import { CadreRouter } from '../src/CadreRouter.js';
6+
7+
async function main() {
8+
const wasmPath = process.argv[2] || './router.wasm';
9+
const router = await CadreRouter.create(wasmPath, { base: import.meta.url });
10+
11+
// Initial State assertions
12+
assert.equal(router.screenW, 1280, 'Initial screen width should be 1280');
13+
assert.equal(router.screenH, 720, 'Initial screen height should be 720');
14+
assert.equal(router.stackLen, 0, 'Initial stack should be empty');
15+
assert.equal(router.stackTop, -1, 'Initial stack top should be -1');
16+
assert.equal(router.popupTag, -1, 'Initial popup tag should be -1');
17+
18+
// Push a screen
19+
router.push(4); // 4 = Game
20+
assert.equal(router.stackLen, 1, 'Stack length should be 1 after push');
21+
assert.equal(router.stackTop, 4, 'Stack top should be 4 (Game)');
22+
23+
// Push another screen
24+
router.push(1); // 1 = CharacterSelect
25+
assert.equal(router.stackLen, 2, 'Stack length should be 2 after push');
26+
assert.equal(router.stackTop, 1, 'Stack top should be 1 (CharacterSelect)');
27+
28+
// Resize
29+
router.resize(1920, 1080);
30+
assert.equal(router.screenW, 1920, 'Width should be updated to 1920');
31+
assert.equal(router.screenH, 1080, 'Height should be updated to 1080');
32+
33+
// Pop
34+
router.pop();
35+
assert.equal(router.stackLen, 1, 'Stack length should be 1 after pop');
36+
assert.equal(router.stackTop, 4, 'Stack top should be 4 (Game) after pop');
37+
38+
// Popup
39+
router.presentPopup(2); // 2 = Hacking
40+
assert.equal(router.popupTag, 2, 'Popup tag should be 2 (Hacking)');
41+
42+
router.dismissPopup();
43+
assert.equal(router.popupTag, -1, 'Popup tag should be -1 after dismiss');
44+
45+
console.log('ALL ASSERTIONS PASS — CadreRouter ran end-to-end');
46+
}
47+
48+
main().catch((err) => {
49+
console.error(err);
50+
process.exit(1);
51+
});

affinescript-cadre/package.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "@hyperpolymath/affinescript-cadre",
3+
"version": "0.1.0",
4+
"description": "Router and Navigation runtime for AffineScript",
5+
"type": "module",
6+
"main": "src/CadreRouter.js",
7+
"scripts": {
8+
"test": "./e2e/run.sh"
9+
},
10+
"author": "Jonathan D.A. Jewell",
11+
"license": "MPL-2.0"
12+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// Cadre Router runtime wrapping the affine-js loader and tea_router wasm.
3+
4+
import { readBytes, buildImportObject } from '../../packages/affine-js/loader.js';
5+
6+
export class CadreRouter {
7+
constructor(instance) {
8+
this.exports = instance.exports;
9+
}
10+
11+
/**
12+
* Initialize a new CadreRouter instance from a WASM module.
13+
* @param {string | URL} wasmSource
14+
* @param {{ base?: string | URL }} options
15+
*/
16+
static async create(wasmSource, options = {}) {
17+
const bytes = await readBytes(wasmSource, options);
18+
// The router does not have imports, but we use buildImportObject for host parity
19+
const importObject = buildImportObject({}, options);
20+
const { instance } = await WebAssembly.instantiate(bytes, importObject);
21+
const router = new CadreRouter(instance);
22+
router.exports.affinescript_router_init();
23+
return router;
24+
}
25+
26+
// --- Actions ---
27+
28+
push(screenTag) {
29+
this.exports.affinescript_router_push(screenTag);
30+
}
31+
32+
pop() {
33+
this.exports.affinescript_router_pop();
34+
}
35+
36+
presentPopup(popupTag) {
37+
this.exports.affinescript_router_present_popup(popupTag);
38+
}
39+
40+
dismissPopup() {
41+
this.exports.affinescript_router_dismiss_popup();
42+
}
43+
44+
resize(w, h) {
45+
this.exports.affinescript_router_resize(w, h);
46+
}
47+
48+
// --- Getters ---
49+
50+
get screenW() {
51+
return this.exports.affinescript_router_get_screen_w();
52+
}
53+
54+
get screenH() {
55+
return this.exports.affinescript_router_get_screen_h();
56+
}
57+
58+
get stackLen() {
59+
return this.exports.affinescript_router_get_stack_len();
60+
}
61+
62+
get stackTop() {
63+
return this.exports.affinescript_router_get_stack_top();
64+
}
65+
66+
get popupTag() {
67+
return this.exports.affinescript_router_get_popup_tag();
68+
}
69+
}

docs/ECOSYSTEM.adoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,8 @@ target.
167167
Linear-msg invariant; reuses the INT-02 loader. 9 Deno tests vs the
168168
canonical `affinescript tea-bridge` + a re-entrancy fixture.
169169

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

173173
|`affinescriptiser` |adjunct |In-tree tooling/experiments;
174174
not part of the integration critical path.
@@ -269,7 +269,7 @@ the runtime is FIXED (PR #257, closed 2026-05-19); runtime VERIFIED
269269
end-to-end 2026-07-07 via `affinescript-dom/e2e/run.sh` (mount + attr
270270
patch + text update + child removal, mutation log asserted)
271271
|INT-09 |`affinescript-cadre` router/navigation runtime |ledger-only
272-
|planned (blocked by INT-07)
272+
|**CLOSED 2026-07-25** (scaffolded CadreRouter JS wrapper and E2E tests)
273273
|INT-10 |LSP distribution (`affinescript-lsp`) |#282 |unblocked —
274274
distribution decided (ADR-019: Releases + thin Deno/JSR shim, #260).
275275
Consumes the shim once #260 S2/S3 land

0 commit comments

Comments
 (0)