From de50fc1a9c70f40b51bf9898a9739a046e8590eb Mon Sep 17 00:00:00 2001 From: Kinflou Date: Fri, 4 Sep 2026 04:12:14 +0800 Subject: [PATCH] feat: load editor examples from the comline-examples git dep MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drops the inline `SAMPLE_FILES` string. `comline-examples` (github:ComlineProject/examples) is a git dependency of real `.ids` projects — chat, key/value, gateway, telemetry — bundled to one JSON in its `prepare`/`prepack`. `import examples from "comline-examples"`. - an **Examples** ` + +
diff --git a/app/package.json b/app/package.json index bf92022..8ab8137 100644 --- a/app/package.json +++ b/app/package.json @@ -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", @@ -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": { diff --git a/app/src/examples.test.ts b/app/src/examples.test.ts new file mode 100644 index 0000000..0e1fb6b --- /dev/null +++ b/app/src/examples.test.ts @@ -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)}`); + }); +} diff --git a/app/src/main.ts b/app/src/main.ts index f42e365..0e41329 100644 --- a/app/src/main.ts +++ b/app/src/main.ts @@ -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 { @@ -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 $ = (sel: string) => document.querySelector(sel) as T; const statusEl = $("#status"); @@ -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(); }, }; @@ -769,13 +770,33 @@ function wireCollapse(toggleSel: string) { wireCollapse("#files-toggle"); wireCollapse("#problems-toggle"); +// ── examples picker ──────────────────────────────────────────────────── +const examplesEl = $("#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(); diff --git a/app/src/style.css b/app/src/style.css index 57dee87..e67def6 100644 --- a/app/src/style.css +++ b/app/src/style.css @@ -84,7 +84,8 @@ header h1 { background: var(--bg-2); } -#target { +#target, +#examples { font: inherit; font-size: 0.75rem; color: var(--fg); @@ -94,6 +95,9 @@ header h1 { padding: 0.15rem 0.4rem; cursor: pointer; } +#examples { + color: var(--muted); +} .status { margin-left: auto; diff --git a/app/tsconfig.json b/app/tsconfig.json index 49bea6e..4080bb6 100644 --- a/app/tsconfig.json +++ b/app/tsconfig.json @@ -4,6 +4,7 @@ "module": "ESNext", "moduleResolution": "bundler", "allowImportingTsExtensions": true, + "resolveJsonModule": true, "lib": ["ES2022", "DOM", "DOM.Iterable", "WebWorker"], "strict": true, "noUnusedLocals": true, diff --git a/app/tsconfig.test.json b/app/tsconfig.test.json index 1fc472e..e657511 100644 --- a/app/tsconfig.test.json +++ b/app/tsconfig.test.json @@ -4,6 +4,6 @@ "types": ["node"], "noEmit": true }, - "include": ["src/sim"], + "include": ["src/sim", "src/examples.test.ts"], "exclude": [] } diff --git a/app/yarn.lock b/app/yarn.lock index 33c5948..7f9b8f7 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -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:." @@ -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"