Skip to content

Commit c771a08

Browse files
hyperpolymathclaude
andcommitted
chore(ui): migrate Justfile to deno task + update ReScript UI layer
- Justfile: replace all `npm run` / `npm install` calls with `deno task` in build-ui, watch-ui, dev-ui, serve-ui, gui targets (npm is banned). - Server.res: move from root of src/rescript/ into src/ (canonical location for compiled .bs.js entry point). - deno.json: update imports map — @rescript/react 0.13.1, add rescript-webapi and @rescript/runtime bindings, add @std/* entries. - package.json: align scripts with new Server.bs.js path + 0.13.1 dep. - deno.lock: add reproducible Deno lock file. - ReScript sources: migrate Js.Promise -> Promise, update API bindings. - .gitignore: exclude package-lock.json, yarn.lock, lib/ocaml/ artefacts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c3fc0b2 commit c771a08

14 files changed

Lines changed: 329 additions & 184 deletions

File tree

Justfile

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,48 @@ update:
7676
audit:
7777
cargo audit
7878

79+
# ── UI Management ──────────────────────────────────────────
80+
81+
# Build ReScript frontend (Deno caches npm: deps automatically — no install step)
82+
build-ui:
83+
cd src/rescript && deno task res:build && deno task build
84+
85+
# Start ReScript compiler in watch mode
86+
watch-ui:
87+
cd src/rescript && deno task res:dev
88+
89+
# Start Deno dev server for UI
90+
dev-ui:
91+
cd src/rescript && deno task dev
92+
93+
# Serve production UI build
94+
serve-ui:
95+
cd src/rescript && deno task serve
96+
97+
# Launch full ECHIDNA GUI (Backend + UI + Browser)
98+
gui:
99+
#!/usr/bin/env bash
100+
set -euo pipefail
101+
echo "Launching ECHIDNA GUI..."
102+
# Start backend
103+
cargo run -- server --port 8081 --cors > /dev/null 2>&1 & BACKEND_PID=$!
104+
echo "Started backend (PID $BACKEND_PID)"
105+
# Start UI server
106+
cd src/rescript && deno task serve > /dev/null 2>&1 & UI_PID=$!
107+
echo "Started UI server (PID $UI_PID)"
108+
# Cleanup on exit
109+
trap "kill $BACKEND_PID $UI_PID 2>/dev/null || true" EXIT
110+
# Wait for servers to start
111+
echo "Waiting for servers to initialize..."
112+
sleep 2
113+
# Open browser
114+
URL="http://localhost:3000"
115+
echo "Opening $URL..."
116+
xdg-open "$URL" > /dev/null 2>&1 || open "$URL" > /dev/null 2>&1 || echo "Please open $URL in your browser"
117+
echo "ECHIDNA GUI is running. Press Ctrl+C in this terminal to stop."
118+
# Wait for background processes
119+
wait
120+
79121
# All checks before commit
80122
pre-commit: fmt-check lint test
81123
@echo "All checks passed!"

src/rescript/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
node_modules/
66
.pnp
77
.pnp.js
8+
package-lock.json
9+
yarn.lock
10+
11+
# OCaml compilation artefacts (rescript compiler output)
12+
lib/ocaml/
813

914
# ReScript build output
1015
*.bs.js

src/rescript/deno.json

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@
1010
"jsx": "react-jsx",
1111
"jsxImportSource": "react"
1212
},
13-
"importMap": "./import_map.json",
1413
"tasks": {
15-
"dev": "deno run --allow-net --allow-read --allow-env --watch server.ts",
16-
"serve": "deno run --allow-net --allow-read --allow-env server.ts",
14+
"dev": "deno run --allow-net --allow-read --allow-env --watch src/Server.bs.js",
15+
"serve": "deno run --allow-net --allow-read --allow-env src/Server.bs.js",
1716
"build": "deno run --allow-read --allow-write --allow-env build.ts",
1817
"res:build": "rescript build",
1918
"res:dev": "rescript build -w",
@@ -52,9 +51,15 @@
5251
"imports": {
5352
"react": "npm:react@^18.2.0",
5453
"react-dom": "npm:react-dom@^18.2.0",
54+
"react-dom/client": "npm:react-dom@^18.2.0/client",
55+
"react/jsx-runtime": "npm:react@^18.2.0/jsx-runtime",
5556
"rescript-webapi": "npm:rescript-webapi@^0.10.0",
5657
"@rescript/core": "npm:@rescript/core@^1.2.0",
57-
"@rescript/react": "npm:@rescript/react@^0.12.1",
58-
"rescript": "^12.0.0"
58+
"@rescript/react": "npm:@rescript/react@^0.13.1",
59+
"@rescript/runtime/": "npm:@rescript/runtime@^12.2.0/",
60+
"rescript": "npm:rescript@^12.2.0",
61+
"@std/http": "jsr:@std/http@^1.0.0",
62+
"@std/path": "jsr:@std/path@^1.0.0",
63+
"@std/media-types": "jsr:@std/media-types@^1.0.0"
5964
}
60-
}
65+
}

src/rescript/deno.lock

Lines changed: 72 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/rescript/index.html

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,22 @@
44
<meta charset="UTF-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1.0">
66
<title>ECHIDNA - Neurosymbolic Theorem Proving</title>
7+
<!-- Tailwind CSS CDN -->
8+
<script src="https://cdn.tailwindcss.com"></script>
9+
<script type="importmap">
10+
{
11+
"imports": {
12+
"react": "https://esm.sh/react@18.2.0",
13+
"react-dom": "https://esm.sh/react-dom@18.2.0",
14+
"react-dom/client": "https://esm.sh/react-dom@18.2.0/client",
15+
"react/jsx-runtime": "https://esm.sh/react@18.2.0/jsx-runtime",
16+
"rescript-webapi/": "https://esm.sh/rescript-webapi@0.10.0/",
17+
"@rescript/core": "https://esm.sh/@rescript/core@1.2.0",
18+
"@rescript/react": "https://esm.sh/@rescript/react@0.13.1",
19+
"@rescript/runtime/": "https://esm.sh/@rescript/runtime@12.2.0/"
20+
}
21+
}
22+
</script>
723
<style>
824
* {
925
margin: 0;

src/rescript/lib/rescript.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1499647
1+
2381604

src/rescript/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
"res:build": "rescript build",
99
"res:dev": "rescript build -w",
1010
"res:clean": "rescript clean",
11-
"dev": "deno run --allow-net --allow-read --watch server.ts",
12-
"build": "rescript build && deno bundle src/Main.bs.js dist/bundle.js",
13-
"serve": "deno run --allow-net --allow-read server.ts",
11+
"dev": "deno run --allow-net --allow-read --allow-env --watch src/Server.bs.js",
12+
"build": "rescript build",
13+
"serve": "deno run --allow-net --allow-read --allow-env src/Server.bs.js",
1414
"check": "rescript build && deno check src/Main.bs.js"
1515
},
1616
"keywords": [
@@ -22,7 +22,7 @@
2222
],
2323
"devDependencies": {
2424
"@rescript/core": "^1.2.0",
25-
"@rescript/react": "^0.12.1",
25+
"@rescript/react": "^0.13.1",
2626
"rescript": "^12.0.0"
2727
},
2828
"dependencies": {

src/rescript/src/Main.res

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ let make = () => {
2121

2222
// Load aspect tags on mount
2323
React.useEffect(() => {
24-
let _ = Client.getAspectTags()|> Js.Promise.then_(result => {
24+
let _ = Client.getAspectTags()->Promise.then(result => {
2525
switch result {
2626
| Ok(tags) => {
2727
dispatch(UpdateProofState({
@@ -40,28 +40,28 @@ let make = () => {
4040
completed: false,
4141
}),
4242
)
43-
Js.Promise.resolve()
43+
Promise.resolve()
4444
}
45-
| Error(_) => Js.Promise.resolve()
45+
| Error(_) => Promise.resolve()
4646
}
4747
})
4848
None
4949
}, [])
5050

5151
let handleSelectProver = prover => {
5252
dispatch(SetLoading(true))
53-
let _ = Client.initProofSession(prover)|> Js.Promise.then_(result => {
53+
let _ = Client.initProofSession(prover)->Promise.then(result => {
5454
switch result {
5555
| Ok(proofState) => {
5656
dispatch(UpdateProofState(proofState))
5757
dispatch(SetLoading(false))
5858
setCurrentView(_ => ProofSession)
59-
Js.Promise.resolve()
59+
Promise.resolve()
6060
}
6161
| Error(err) => {
6262
dispatch(SetError(Some(err)))
6363
dispatch(SetLoading(false))
64-
Js.Promise.resolve()
64+
Promise.resolve()
6565
}
6666
}
6767
})
@@ -73,17 +73,17 @@ let make = () => {
7373
dispatch(SetLoading(true))
7474
dispatch(ApplyTactic(tactic))
7575

76-
let _ = Client.applyTactic(tactic, goalId)|> Js.Promise.then_(result => {
76+
let _ = Client.applyTactic(tactic, goalId)->Promise.then(result => {
7777
switch result {
7878
| Ok(newState) => {
7979
dispatch(UpdateProofState(newState))
8080
dispatch(SetLoading(false))
81-
Js.Promise.resolve()
81+
Promise.resolve()
8282
}
8383
| Error(err) => {
8484
dispatch(SetError(Some(err)))
8585
dispatch(SetLoading(false))
86-
Js.Promise.resolve()
86+
Promise.resolve()
8787
}
8888
}
8989
})
Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ external envGet: string => option<string> = "get"
1313
/** FFI: Deno.serve */
1414
type serveOptions = {port: int}
1515
@scope("Deno") @val
16-
external serve: (serveOptions, Fetch.request => promise<Fetch.response>) => unit = "serve"
16+
external serve: (serveOptions, Webapi.Fetch.request => promise<Webapi.Fetch.response>) => unit = "serve"
1717

1818
/** FFI: console.log */
1919
@scope("console") @val
@@ -25,7 +25,7 @@ external consoleError: (string, 'a) => unit = "error"
2525

2626
/** FFI: fetch */
2727
@val
28-
external fetch: (string, {"method": string, "headers": Fetch.headers, "body": option<Fetch.readableStream>}) => promise<Fetch.response> = "fetch"
28+
external fetch: (string, {"method": string, "headers": Webapi.Fetch.headers, "body": option<Webapi.Fetch.readableStream>}) => promise<Webapi.Fetch.response> = "fetch"
2929

3030
/** FFI: URL constructor */
3131
type url = {
@@ -36,13 +36,13 @@ type url = {
3636
@new external makeUrlFromString: string => url = "URL"
3737

3838
/** FFI: Request fields */
39-
@get external getUrl: Fetch.request => string = "url"
40-
@get external getMethod: Fetch.request => string = "method"
41-
@get external getHeaders: Fetch.request => Fetch.headers = "headers"
42-
@get external getBody: Fetch.request => option<Fetch.readableStream> = "body"
39+
@get external getUrl: Webapi.Fetch.request => string = "url"
40+
@get external getMethod: Webapi.Fetch.request => string = "method"
41+
@get external getHeaders: Webapi.Fetch.request => Webapi.Fetch.headers = "headers"
42+
@get external getBody: Webapi.Fetch.request => option<Webapi.Fetch.readableStream> = "body"
4343

4444
/** FFI: Response constructors */
45-
@new external makeResponse: (string, {"status": int, "headers": Js.Dict.t<string>}) => Fetch.response = "Response"
45+
@new external makeResponse: (string, {"status": int, "headers": Dict.t<string>}) => Webapi.Fetch.response = "Response"
4646

4747
/** FFI: serveDir from std */
4848
type serveDirOptions = {
@@ -53,39 +53,42 @@ type serveDirOptions = {
5353
enableCors: bool,
5454
}
5555
@module("https://deno.land/std@0.211.0/http/file_server.ts")
56-
external serveDir: (Fetch.request, serveDirOptions) => promise<Fetch.response> = "serveDir"
56+
external serveDir: (Webapi.Fetch.request, serveDirOptions) => promise<Webapi.Fetch.response> = "serveDir"
5757

5858
/** Server port */
59-
let port = 8081
59+
let port = switch envGet("PORT") {
60+
| Some(p) => p->Int.fromString->Option.getOr(3000)
61+
| None => 3000
62+
}
6063

6164
/** API backend URL (configurable via ECHIDNA_API_URL env var) */
6265
let apiBackend = switch envGet("ECHIDNA_API_URL") {
6366
| Some(url) => url
64-
| None => "http://localhost:3000"
67+
| None => "http://localhost:8081"
6568
}
6669

6770
/** Request handler — proxies /api/ to Rust backend, serves static files otherwise */
68-
let handler = async (req: Fetch.request): Fetch.response => {
69-
let reqUrl = getUrl(req)
71+
let handler = async (req: Webapi.Fetch.request): Webapi.Fetch.response => {
72+
let reqUrl = req->getUrl
7073
let url = makeUrlFromString(reqUrl)
7174

7275
// API proxy to Rust backend
73-
if Js.String2.startsWith(url.pathname, "/api/") {
76+
if url.pathname->String.startsWith("/api/") {
7477
try {
7578
let apiUrl = makeUrl(url.pathname, apiBackend)
7679
let targetUrl = apiUrl.pathname ++ url.search
7780

7881
let response = await fetch(targetUrl, {
79-
"method": getMethod(req),
80-
"headers": getHeaders(req),
81-
"body": getBody(req),
82+
"method": req->getMethod,
83+
"headers": req->getHeaders,
84+
"body": req->getBody,
8285
})
8386

8487
response
8588
} catch {
8689
| exn =>
8790
consoleError("API proxy error:", exn)
88-
let headers = Js.Dict.fromArray([("Content-Type", "application/json")])
91+
let headers = Dict.fromArray([("Content-Type", "application/json")])
8992
makeResponse(`{"error": "Backend unavailable"}`, {"status": 502, "headers": headers})
9093
}
9194
} else {
@@ -102,7 +105,7 @@ let handler = async (req: Fetch.request): Fetch.response => {
102105

103106
/** Start the server */
104107
let start = () => {
105-
log(`ECHIDNA UI Server starting on port ${Belt.Int.toString(port)}`)
108+
log(`ECHIDNA UI Server starting on port ${port->Int.toString}`)
106109
log(`API Backend: ${apiBackend}`)
107110
serve({port: port}, handler)
108111
}

0 commit comments

Comments
 (0)