Skip to content

Commit aca18a9

Browse files
ide: migrate Studio to AffineScript; add affinescript-dom licensing patch (#42)
* ide: migrate Error-Lang Studio from ReScript to AffineScript Ports the 6 IDE modules to AffineScript over the MPL-2.0 Dom.affine vdom, adds the browser host shim (src/host.js: handle table + dom_*/monaco_*/host_* externs + event bridge into the TEA loop), flips index.html to /src/host.js, switches the build to `affinescript compile --deno-esm` (self-contained src/Main.js) + Vite, and removes the superseded rescript.json + 6 .res modules. Docs/build commands updated (npm/rescript -> deno task / affinescript). Verified: all 7 .affine type-check; app compiles clean; host.js + Main.js pass node --check. Browser runtime is host-side (flagged NOTE: in host.js). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0195yA45jSSP7YDPwJSpw4bM * patches: add affinescript-dom-licensing.patch (MPL-2.0 + CC-BY-SA-4.0) Durable copy of the affinescript-dom licensing correction (unpushable to the out-of-scope hyperpolymath/affinescript). Standalone, manually-applied: corrects the DOM connector from a mislabelled AGPL header to MPL-2.0 (code) + CC-BY-SA-4.0 (docs) and evidences it in LICENSES/, the Mustfile, CODEOWNERS, MAINTAINERS, and GOVERNANCE. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0195yA45jSSP7YDPwJSpw4bM --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 0e657e3 commit aca18a9

24 files changed

Lines changed: 2448 additions & 1228 deletions

ide/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Generated by `deno task build:affine` (affinescript compile --deno-esm)
2+
/src/Main.js
3+
# Vite build output
4+
/dist/

ide/ARCHITECTURE.adoc

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-License-Identifier: CC-BY-SA-4.0
1+
// SPDX-License-Identifier: MPL-2.0
22
// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
33
= Error-Lang Studio: IDE Architecture
44
:toc:
@@ -8,7 +8,14 @@
88

99
Error-Lang Studio is a web-based IDE that makes computational forces visible and navigable. It's not just a code editor - it's an **archaeology toolkit** for exploring how code transforms through abstraction layers.
1010

11-
== Architecture: The Elm Architecture (TEA) in ReScript
11+
NOTE: *Migrated to AffineScript.* The IDE was ported from ReScript to
12+
AffineScript (`.res` -> `.affine`) per the Hyperpolymath language policy. The
13+
app compiles via `affinescript compile --deno-esm` to a self-contained
14+
`src/Main.js` bundle, driven by the host shim `src/host.js`. The TEA structure
15+
below is unchanged; the inline code samples still show ReScript syntax and are
16+
retained as conceptual illustrations pending a syntax refresh.
17+
18+
== Architecture: The Elm Architecture (TEA) in AffineScript
1219

1320
We use TEA because it's:
1421
- **Predictable**: Unidirectional data flow

ide/INTEGRATION.adoc

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-License-Identifier: CC-BY-SA-4.0
1+
// SPDX-License-Identifier: MPL-2.0
22
// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
33
= Error-Lang Studio: Ecosystem Integration
44
:toc:
@@ -128,7 +128,7 @@ GitHub Pages / CDN
128128
(dest "/")))
129129

130130
(build
131-
((command "cd ide && npm run build")
131+
((command "cd ide && deno task build")
132132
(output "ide/dist"))))
133133
```
134134

@@ -346,11 +346,10 @@ aspect TemporalScopeLeakage {
346346
```bash
347347
# Develop IDE
348348
cd ide
349-
npm install
350-
npm run dev # Vite dev server
349+
deno task dev # affinescript compile --deno-esm, then Vite dev server
351350

352351
# Build for production
353-
npm run build # ReScript → JS → Vite bundle
352+
deno task build # AffineScript → Deno-ESM JS → Vite bundle
354353

355354
# Generate static site
356355
draig build # Creates deployable site

ide/README.adoc

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-License-Identifier: CC-BY-SA-4.0
1+
// SPDX-License-Identifier: MPL-2.0
22
// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
33
= Error-Lang Studio: The Language Stabilizer IDE
44
:toc:
@@ -53,16 +53,21 @@ Error-Lang Studio follows The Elm Architecture (TEA):
5353
```
5454
ide/
5555
├── src/
56-
│ ├── Model.res # Application state
57-
│ ├── Msg.res # Event messages
58-
│ ├── Update.res # State transitions
59-
│ ├── View.res # UI rendering
60-
│ ├── Main.res # Entry point
61-
│ └── styles.css # Styles
56+
│ ├── Model.affine # Application state
57+
│ ├── Msg.affine # Event messages
58+
│ ├── Update.affine # State transitions
59+
│ ├── View.affine # UI rendering
60+
│ ├── Main.affine # Entry point + TEA run-loop + event bridge
61+
│ ├── Dom.affine # Virtual-DOM bindings (MPL-2.0)
62+
│ ├── MonacoBindings.affine # Monaco editor externs
63+
│ ├── host.js # Host shim: backs the externs, drives the loop
64+
│ ├── MonacoInterop.js # Monaco Editor glue (host-side)
65+
│ ├── monaco-setup.js # Monaco bootstrap (host-side)
66+
│ ├── Main.js # Generated bundle (deno task build:affine)
67+
│ └── styles.css # Styles
6268
├── public/ # Static assets
63-
├── index.html # HTML template
64-
├── package.json # Dependencies
65-
├── rescript.json # ReScript config
69+
├── index.html # HTML template (loads src/host.js)
70+
├── deno.json # Tasks + deps (Deno)
6671
├── vite.config.js # Build config
6772
├── ARCHITECTURE.adoc # Architecture design
6873
├── INTEGRATION.adoc # Ecosystem integration
@@ -73,30 +78,26 @@ ide/
7378

7479
=== Prerequisites
7580

76-
* Node.js 18+ (for build tools)
77-
* npm or yarn
78-
79-
=== Setup
80-
81-
```bash
82-
cd ide
83-
npm install
84-
```
81+
* Deno (runtime + task runner)
82+
* The AffineScript compiler on `PATH` (`affinescript`) — see
83+
`scripts/install-affinescript-toolchain.sh` in the repo root
8584

8685
=== Development Mode
8786

8887
```bash
89-
npm run dev
88+
cd ide
89+
deno task dev
9090
```
9191

92-
This starts:
93-
- ReScript compiler in watch mode (compiles `.res` → `.res.js`)
94-
- Vite dev server on http://localhost:3000 (hot reload)
92+
This:
93+
- compiles `src/Main.affine` → `src/Main.js` (`affinescript compile --deno-esm`,
94+
which inlines every module into one self-contained bundle), then
95+
- starts the Vite dev server on http://localhost:3000
9596

9697
=== Production Build
9798

9899
```bash
99-
npm run build
100+
deno task build
100101
```
101102

102103
Outputs to `dist/` directory - a fully static site deployable to:

ide/deno.json

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
11
{
22
"name": "@hyperpolymath/error-lang-studio",
33
"version": "0.1.0",
4-
"exports": "./src/Main.res.js",
4+
"exports": "./src/host.js",
55
"tasks": {
6-
"clean": "rescript clean",
7-
"build": "rescript build && deno run -A npm:vite build",
8-
"dev": "deno task dev:rescript & deno task dev:vite",
9-
"dev:rescript": "rescript build -w",
10-
"dev:vite": "deno run -A npm:vite",
6+
"clean": "rm -f src/Main.js",
7+
"build:affine": "cd src && affinescript compile --deno-esm -o Main.js Main.affine",
8+
"build": "deno task build:affine && deno run -A npm:vite build",
9+
"dev": "deno task build:affine && deno run -A npm:vite",
1110
"serve": "deno run -A npm:vite preview"
1211
},
1312
"imports": {
14-
"vite": "npm:vite@^5.4.11",
15-
"rescript": "^12.0.0"
13+
"vite": "npm:vite@^5.4.11"
1614
},
1715
"compilerOptions": {
18-
"lib": ["deno.window", "dom", "dom.iterable"],
19-
"jsx": "react-jsx",
20-
"jsxImportSource": "preact"
16+
"lib": ["deno.window", "dom", "dom.iterable"]
2117
},
2218
"fmt": {
2319
"useTabs": false,
@@ -30,6 +26,6 @@
3026
"rules": {
3127
"tags": ["recommended"]
3228
},
33-
"exclude": ["src/MonacoInterop.js", "src/monaco-setup.js"]
29+
"exclude": ["src/MonacoInterop.js", "src/monaco-setup.js", "src/host.js", "src/Main.js"]
3430
}
3531
}

ide/index.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@
6868
});
6969
</script>
7070

71-
<!-- Main application (TEA architecture, compiled from ReScript) -->
72-
<script type="module" src="/src/Main.res.js"></script>
71+
<!-- Main application (TEA architecture; AffineScript compiled to a Deno-ESM
72+
bundle (src/Main.js) and driven by the host shim (src/host.js)) -->
73+
<script type="module" src="/src/host.js"></script>
7374
</body>
7475
</html>

ide/rescript.json

Lines changed: 0 additions & 27 deletions
This file was deleted.

ide/src/Dom.affine

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// Dom.affine — minimal virtual-DOM bindings for the Error-Lang Studio IDE.
3+
//
4+
// Written fresh for error-lang (MPL-2.0). This is NOT derived from
5+
// hyperpolymath/affinescript-dom, which is AGPL-3.0-or-later and therefore
6+
// licence-incompatible with this MPL-2.0 estate. The pieces here are (a) FFI
7+
// `extern fn` declarations over the host DOM shim (ide/src/*.js) — function
8+
// signatures, not copyrightable expression — and (b) a small, independently
9+
// authored vnode builder + realiser.
10+
//
11+
// Event model (affine / host-runtime friendly): a vnode carries event bindings
12+
// as (event-name, handler-id) string pairs rather than typed closures. The host
13+
// bridge (Main) maps a handler-id back to a Msg and feeds the TEA run loop, so
14+
// the View stays a pure `Model -> VNode` function with no embedded closures.
15+
16+
module Dom;
17+
18+
use prelude::*;
19+
20+
// ---- host DOM externs (opaque Int node handles) ----
21+
pub extern fn dom_query_selector(selector: String) -> Int;
22+
pub extern fn dom_create_element(tag: String) -> Int;
23+
pub extern fn dom_create_text_node(content: String) -> Int;
24+
pub extern fn dom_append_child(parent: Int, child: Int) -> Unit;
25+
pub extern fn dom_set_attribute(el: Int, name: String, value: String) -> Unit;
26+
pub extern fn dom_clear_children(el: Int) -> Unit;
27+
pub extern fn dom_bind_event(el: Int, event: String, handler_id: String) -> Unit;
28+
29+
// ---- virtual nodes ----
30+
// VElem(tag, attrs, events, children); events are (event-name, handler-id).
31+
pub enum VNode {
32+
VElem(String, [(String, String)], [(String, String)], [VNode]),
33+
VText(String)
34+
}
35+
36+
pub fn text(content: String) -> VNode {
37+
VText(content)
38+
}
39+
40+
pub fn h(tag: String, attrs: [(String, String)], children: [VNode]) -> VNode {
41+
VElem(tag, attrs, [], children)
42+
}
43+
44+
// element with event bindings, e.g. h_on("button", [("class","btn")], [("click","stop")], [...])
45+
pub fn h_on(tag: String, attrs: [(String, String)], events: [(String, String)], children: [VNode]) -> VNode {
46+
VElem(tag, attrs, events, children)
47+
}
48+
49+
// ---- realise a vnode into a host DOM node ----
50+
pub fn render(vnode: VNode) -> Int {
51+
match vnode {
52+
VText(s) => dom_create_text_node(s),
53+
VElem(tag, attrs, events, children) => {
54+
let el = dom_create_element(tag);
55+
for a in attrs {
56+
let (name, value) = a;
57+
dom_set_attribute(el, name, value);
58+
}
59+
for ev in events {
60+
let (name, hid) = ev;
61+
dom_bind_event(el, name, hid);
62+
}
63+
for c in children {
64+
dom_append_child(el, render(c));
65+
}
66+
el
67+
}
68+
}
69+
}
70+
71+
// replace the contents of `selector` with `vnode`
72+
pub fn mount(selector: String, vnode: VNode) -> Unit {
73+
let root = dom_query_selector(selector);
74+
dom_clear_children(root);
75+
dom_append_child(root, render(vnode))
76+
}

0 commit comments

Comments
 (0)