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
1 change: 1 addition & 0 deletions app/.yarnrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ nodeLinker: node-modules
# allow-list and disables lifecycle scripts by default; the build needs both on.
approvedGitRepositories:
- "https://github.com/ComlineProject/simulator.git"
- "https://github.com/ComlineProject/examples.git"
enableScripts: true

# That WASM build isn't byte-reproducible (wasm-opt runs only where it's
Expand Down
3 changes: 3 additions & 0 deletions app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ <h1>Comline Playground</h1>
<button data-app="edit" class="active">edit</button>
<button data-app="simulate">simulate</button>
</div>
<select id="examples" aria-label="load an example schema">
<option value="">Examples…</option>
</select>
<div class="controls" id="gen-controls">
<div class="seg" id="mode" role="tablist" aria-label="generation mode">
<button data-mode="code" class="active">code</button>
Expand Down
3 changes: 2 additions & 1 deletion app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"dev": "yarn wasm && vite",
"build": "tsc --noEmit && vite build",
"preview": "vite preview",
"test": "tsc -p tsconfig.test.json && node --import tsx --test \"src/sim/**/*.test.ts\""
"test": "tsc -p tsconfig.test.json && node --import tsx --test \"src/**/*.test.ts\""
},
"dependencies": {
"@codemirror/autocomplete": "^6.18.0",
Expand All @@ -22,6 +22,7 @@
"@codemirror/state": "^6.4.0",
"@codemirror/view": "^6.34.0",
"@lezer/highlight": "^1.2.3",
"comline-examples": "git+https://github.com/ComlineProject/examples.git#f63894988ea2cc5db98ff97cc0fbdfceb5af6bb7",
"comline-simulator": "git+https://github.com/ComlineProject/simulator.git#eff27ce9b077ec411c299743b328a24089387215"
},
"devDependencies": {
Expand Down
38 changes: 38 additions & 0 deletions app/src/examples.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/// Every example schema (from the `comline-examples` git dependency) compiles
/// against *this* build of the editor wasm — so a `comline-core` change that
/// breaks one is caught here, not in the playground UI.

import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { strict as assert } from "node:assert";
import { test } from "node:test";

import examples from "comline-examples";

const initWasm = (await import("./wasm/comline_playground_wasm.js")).default;
const { compile_project } = await import("./wasm/comline_playground_wasm.js");
await initWasm(
readFileSync(fileURLToPath(new URL("./wasm/comline_playground_wasm_bg.wasm", import.meta.url))),
);

interface Example {
id: string;
entry: string;
files: { name: string; source: string }[];
}

for (const ex of examples as Example[]) {
test(`example "${ex.id}" compiles with no errors`, () => {
assert.ok(
ex.files.some((f) => f.name === ex.entry),
`entry "${ex.entry}" is one of the files`,
);
const res = compile_project(
ex.files.map((f) => ({ path: f.name, source: f.source })),
) as { diagnostics?: { severity?: unknown; message?: string }[] };
const errors = (res.diagnostics ?? []).filter(
(d) => d.severity === "error" || d.severity === 1 || d.severity === "Error",
);
assert.equal(errors.length, 0, `diagnostics: ${JSON.stringify(errors)}`);
});
}
89 changes: 55 additions & 34 deletions app/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,36 +23,19 @@ import type {
import { createSim, type ProjectShape, type SimView } from "./sim/index.ts";
import initSimWasm from "comline-simulator";
import simWasmUrl from "comline-simulator/pkg/comline_simulator_bg.wasm?url";
import examplesData from "comline-examples";

// ── sample: two files, one `use`ing the other ────────────────────────────
const SAMPLE_FILES: { name: string; doc: string }[] = [
{
name: "chat.ids",
doc: `use types::Message

error Rejected {
message = "rejected: {self.reason}"
reason: string
}

@framing = "jsonrpc"
protocol Chat {
/// Request/response with a raised error.
function send(text: string) -> Message ! Rejected;
/// Fire-and-forget.
function note(text: string);
}
`,
},
{
name: "types.ids",
doc: `struct Message {
body: string
seq: u64
// ── examples ────────────────────────────────────────────────────────────
// `.ids` projects from ComlineProject/examples (a git dependency; bundled to
// one JSON in its `prepare`). The first is what a fresh visit opens.
interface Example {
id: string;
title: string;
blurb: string;
entry: string;
files: { name: string; source: string }[];
}
`,
},
];
const EXAMPLES = examplesData as Example[];

// ── virtual file set ────────────────────────────────────────────────────
interface SchemaFile {
Expand All @@ -71,6 +54,23 @@ const nextId = () => `f${++uid}`;
const activeFile = () => files.find((f) => f.id === activeId)!;
const project = (): FileInput[] => files.map((f) => ({ path: f.name, source: f.doc }));

// cleared when an example is loaded, set on the first edit — gates the
// "replace your schema?" confirm in the Examples picker.
let dirty = false;

/** Replace the whole file set with an example's files. */
function setFiles(ex: Example) {
files = ex.files.map((f) => ({
id: nextId(),
name: f.name,
doc: f.source,
state: makeState(f.source, ctx),
}));
openIds = files.map((f) => f.id);
activeId = (files.find((f) => f.name === ex.entry) ?? files[0]).id;
dirty = false;
}

// ── DOM ─────────────────────────────────────────────────────────────────
const $ = <T extends HTMLElement>(sel: string) => document.querySelector(sel) as T;
const statusEl = $<HTMLSpanElement>("#status");
Expand Down Expand Up @@ -156,6 +156,7 @@ const ctx: EditorContext = {
const f = activeFile();
if (!f) return; // the blank scratch buffer shown when no file is open
f.doc = d;
dirty = true;
scheduleRefresh();
},
};
Expand Down Expand Up @@ -769,13 +770,33 @@ function wireCollapse(toggleSel: string) {
wireCollapse("#files-toggle");
wireCollapse("#problems-toggle");

// ── examples picker ────────────────────────────────────────────────────
const examplesEl = $<HTMLSelectElement>("#examples");
for (const ex of EXAMPLES) {
const o = document.createElement("option");
o.value = ex.id;
o.textContent = ex.title;
o.title = ex.blurb;
examplesEl.append(o);
}
examplesEl.addEventListener("change", () => {
const ex = EXAMPLES.find((e) => e.id === examplesEl.value);
examplesEl.value = "";
if (!ex) return;
if (dirty && !window.confirm(`Replace the current schema with the "${ex.title}" example?`)) return;
setFiles(ex);
view.setState(activeFile().state);
editorEl.classList.remove("is-hidden");
editorEmptyEl.classList.add("is-hidden");
renderTabs();
renderFileTree();
statusEl.textContent = "compiling…";
void refresh();
});

// ── boot ───────────────────────────────────────────────────────────────
for (const s of SAMPLE_FILES) {
files.push({ id: nextId(), name: s.name, doc: s.doc, state: makeState(s.doc, ctx) });
}
openIds = files.map((f) => f.id);
activeId = files[0].id;
view = mountEditor(editorEl, files[0].state);
setFiles(EXAMPLES[0]);
view = mountEditor(editorEl, activeFile().state);
renderTabs();
renderFileTree();

Expand Down
6 changes: 5 additions & 1 deletion app/src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ header h1 {
background: var(--bg-2);
}

#target {
#target,
#examples {
font: inherit;
font-size: 0.75rem;
color: var(--fg);
Expand All @@ -94,6 +95,9 @@ header h1 {
padding: 0.15rem 0.4rem;
cursor: pointer;
}
#examples {
color: var(--muted);
}

.status {
margin-left: auto;
Expand Down
1 change: 1 addition & 0 deletions app/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"module": "ESNext",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"lib": ["ES2022", "DOM", "DOM.Iterable", "WebWorker"],
"strict": true,
"noUnusedLocals": true,
Expand Down
2 changes: 1 addition & 1 deletion app/tsconfig.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
"types": ["node"],
"noEmit": true
},
"include": ["src/sim"],
"include": ["src/sim", "src/examples.test.ts"],
"exclude": []
}
8 changes: 8 additions & 0 deletions app/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,13 @@ __metadata:
languageName: node
linkType: hard

"comline-examples@git+https://github.com/ComlineProject/examples.git#f63894988ea2cc5db98ff97cc0fbdfceb5af6bb7":
version: 0.0.0
resolution: "comline-examples@https://github.com/ComlineProject/examples.git#commit=f63894988ea2cc5db98ff97cc0fbdfceb5af6bb7"
checksum: 10c0/8659fdb337f52f30bf55f4c0eb185b0937e6ea819a078ba6c07ba3bc3e00b89f8349e337e596e8d8b9410041fd538e0523bc0d7e8487e8cabb1b010e2ac02f0e
languageName: node
linkType: hard

"comline-playground@workspace:.":
version: 0.0.0-use.local
resolution: "comline-playground@workspace:."
Expand All @@ -771,6 +778,7 @@ __metadata:
"@codemirror/view": "npm:^6.34.0"
"@lezer/highlight": "npm:^1.2.3"
"@types/node": "npm:^20.19.43"
comline-examples: "git+https://github.com/ComlineProject/examples.git#f63894988ea2cc5db98ff97cc0fbdfceb5af6bb7"
comline-simulator: "git+https://github.com/ComlineProject/simulator.git#eff27ce9b077ec411c299743b328a24089387215"
linkedom: "npm:^0.18.13"
tsx: "npm:^4.23.13"
Expand Down
Loading