Skip to content

Commit 8bea4c7

Browse files
Merge branch 'main' into ci/coq-proof-gate
2 parents 11b5e8a + 3e34d7f commit 8bea4c7

10 files changed

Lines changed: 261 additions & 25 deletions

File tree

.github/workflows/codeql.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ jobs:
3737
- name: Checkout
3838
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
3939
- name: Initialize CodeQL
40-
uses: github/codeql-action/init@e0647621c2984b5ed2f768cb892365bf2a616ad1 # v4.37.2
40+
uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
4141
with:
4242
languages: ${{ matrix.language }}
4343
build-mode: ${{ matrix.build-mode }}
4444
- name: Perform CodeQL Analysis
45-
uses: github/codeql-action/analyze@e0647621c2984b5ed2f768cb892365bf2a616ad1 # v4.37.2
45+
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
4646
with:
4747
category: "/language:${{ matrix.language }}"

.github/workflows/semgrep.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
env:
3131
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
3232
- name: Upload SARIF
33-
uses: github/codeql-action/upload-sarif@e0647621c2984b5ed2f768cb892365bf2a616ad1 # v4.37.2
33+
uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
3434
with:
3535
sarif_file: semgrep.sarif
3636
if: always()

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+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>AffineScript DOM Reconciler - Browser Test</title>
6+
<style>
7+
body { font-family: system-ui, sans-serif; padding: 2rem; }
8+
#status { font-weight: bold; padding: 1rem; border-radius: 4px; }
9+
.success { background: #d4edda; color: #155724; }
10+
.error { background: #f8d7da; color: #721c24; }
11+
pre { background: #f8f9fa; padding: 1rem; overflow-x: auto; }
12+
</style>
13+
</head>
14+
<body>
15+
<h1>AffineScript DOM Reconciler E2E Test</h1>
16+
<div id="status">Running tests...</div>
17+
18+
<h3>Logs</h3>
19+
<pre id="logs"></pre>
20+
21+
<script type="module">
22+
const statusEl = document.getElementById('status');
23+
const logsEl = document.getElementById('logs');
24+
25+
// Capture console.log to show on page
26+
const originalLog = console.log;
27+
console.log = (...args) => {
28+
originalLog(...args);
29+
logsEl.textContent += args.join(' ') + '\n';
30+
};
31+
32+
try {
33+
// Import the host-agnostic mjs script
34+
await import('./dom_host.mjs');
35+
statusEl.textContent = '✅ ALL ASSERTIONS PASS — reconciler ran end-to-end in the browser!';
36+
statusEl.className = 'success';
37+
} catch (err) {
38+
console.error(err);
39+
statusEl.textContent = '❌ TEST FAILED: ' + err.message;
40+
statusEl.className = 'error';
41+
}
42+
</script>
43+
</body>
44+
</html>

affinescript-dom/e2e/dom_host.mjs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
// SPDX-License-Identifier: MPL-2.0
22
// e2e host for dom_drive.wasm — Int-handle DOM, mutation log, assertions.
3-
import assert from 'node:assert/strict';
4-
import { readFile } from 'node:fs/promises';
3+
import { readBytes, buildImportObject } from '../../packages/affine-js/loader.js';
4+
5+
// Simple assert for browser host parity
6+
const assert = {
7+
equal: (a, b, msg) => { if (a !== b) throw new Error(`Assertion failed: ${a} !== ${b}. ${msg||''}`); },
8+
ok: (a, msg) => { if (!a) throw new Error(`Assertion failed: not truthy. ${msg||''}`); }
9+
};
510

611
let inst = null;
712
const readString = (ptr) => {
@@ -38,10 +43,16 @@ const dump = (h, d = 0) => {
3843
return [' '.repeat(d) + line, ...n.children.flatMap((c) => dump(c, d + 1))].join('\n');
3944
};
4045

41-
const bytes = await readFile(process.argv[2]);
42-
const { instance } = await WebAssembly.instantiate(bytes, {
43-
env, wasi_snapshot_preview1: { fd_write: () => 0 },
46+
const isNode = typeof process !== 'undefined' && process.argv;
47+
const wasmPath = isNode ? process.argv[2] : './dom_drive.wasm';
48+
49+
const bytes = await readBytes(wasmPath, { base: import.meta.url });
50+
const importObject = buildImportObject(env, {
51+
imports: {
52+
wasi_snapshot_preview1: { fd_write: () => 0 }
53+
}
4454
});
55+
const { instance } = await WebAssembly.instantiate(bytes, importObject);
4556
inst = instance;
4657

4758
const ret = inst.exports.main();
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: MPL-2.0
3+
# Manual browser host parity test (INT-11).
4+
# Compiles the reconciler and serves browser_test.html locally.
5+
6+
set -uo pipefail
7+
cd "$(dirname "$0")"
8+
REPO="$(cd ../.. && pwd)"
9+
BIN="${AFFINESCRIPT_BIN:-$REPO/_build/default/bin/main.exe}"
10+
11+
if [ ! -x "$BIN" ]; then
12+
echo "ERROR: compiler not built ($BIN missing) — run dune build first."
13+
exit 1
14+
fi
15+
16+
echo "Compiling reconciler to WASM..."
17+
cat ../src/dom.affine driver_main.affine > dom_drive.affine
18+
"$BIN" compile dom_drive.affine -o dom_drive.wasm
19+
rm dom_drive.affine
20+
21+
echo ""
22+
echo "=========================================================="
23+
echo "WASM compiled successfully. Starting local HTTP server..."
24+
echo "Please open your browser to: http://localhost:8080/browser_test.html"
25+
echo "Check the page to verify ALL ASSERTIONS PASS."
26+
echo "Press Ctrl+C to stop the server and clean up."
27+
echo "=========================================================="
28+
echo ""
29+
30+
# Cleanup wasm on exit
31+
trap 'rm -f dom_drive.wasm' EXIT
32+
33+
python3 -m http.server 8080

docs/ECOSYSTEM.adoc

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -167,16 +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-dom-loader` |scope-deferred |INT-02 substrate (#179)
171-
shipped + closed 2026-05-31 as `packages/affine-js/loader.js` — already
172-
host-agnostic (Deno/Node/browser parity). Whether the satellite repo
173-
still earns its keep (vs. folding into `affine-js`) is the open question
174-
in #489; INT-08 reconciler runtime (#183) is verified end-to-end 2026-07-07
175-
(`affinescript-dom/e2e/run.sh`; #255 fixed via #257) — revisit when it
176-
dictates any DOM-specific loader surface.
177-
178-
|`affinescript-cadre` |scaffold |Was imaginary until #175. Router/navigation
179-
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.
180172

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

0 commit comments

Comments
 (0)