diff --git a/bin/check.sh b/bin/check.sh index 177d8ab..c060d53 100644 --- a/bin/check.sh +++ b/bin/check.sh @@ -25,6 +25,16 @@ else echo "-- skipped: interface/web/node_modules missing (npm install)" fi +echo "== frontend unit tests ==" +# The JSX/TSX transform behind the preview window is a pure module with a +# node --test suite. Nothing else in the frontend has tests, so this is cheap; +# without it the transform's silent-wrong cases go unguarded. +if [ -d interface/web/node_modules ]; then + (cd interface/web && npm test) || fail=1 +else + echo "-- skipped: interface/web/node_modules missing (npm install)" +fi + echo "== powershell parse ==" # The Windows installer has died at parse twice. Cheap to catch here if pwsh # happens to be installed on the Linux box; the ASCII guard in tests/ is the diff --git a/interface/web/package-lock.json b/interface/web/package-lock.json index cecd34e..cc301f7 100644 --- a/interface/web/package-lock.json +++ b/interface/web/package-lock.json @@ -8,6 +8,7 @@ "name": "web", "version": "1.0.0", "dependencies": { + "preact": "^10.29.8", "react": "^19.2.4", "react-dom": "^19.2.4" }, @@ -21,6 +22,9 @@ "eslint-plugin-react-refresh": "^0.5.2", "globals": "^17.4.0", "vite": "^8.0.4" + }, + "engines": { + "node": ">=20.19" } }, "node_modules/@babel/code-frame": { @@ -2233,6 +2237,24 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/preact": { + "version": "10.29.8", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.8.tgz", + "integrity": "sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + }, + "peerDependencies": { + "preact-render-to-string": ">=5" + }, + "peerDependenciesMeta": { + "preact-render-to-string": { + "optional": true + } + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", diff --git a/interface/web/package.json b/interface/web/package.json index 80940ba..d6fa21e 100644 --- a/interface/web/package.json +++ b/interface/web/package.json @@ -10,9 +10,11 @@ "dev": "vite", "build": "vite build", "lint": "eslint .", + "test": "node --test src/preview/jsx-transform.test.js", "preview": "vite preview" }, "dependencies": { + "preact": "^10.29.8", "react": "^19.2.4", "react-dom": "^19.2.4" }, diff --git a/interface/web/src/Markdown.jsx b/interface/web/src/Markdown.jsx index 127f018..35370f5 100644 --- a/interface/web/src/Markdown.jsx +++ b/interface/web/src/Markdown.jsx @@ -1,4 +1,11 @@ -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; + +// Which languages get a live sandboxed preview (RenderBlock) instead of a plain +// syntax block (CodeBlock), and how each becomes a document body, lives in +// ./preview/languages.js. A language like `js` is deliberately absent — +// auto-executing bare script isn't this feature's job (see RenderBlock's doc +// comment for the sandboxing model). +import { PREVIEW_LANGS, RENDERABLE_LANGS } from "./preview/languages.js"; // Parse content into an array of {type, value, lang, streaming} blocks. // Handles: @@ -51,11 +58,13 @@ export function Markdown({ content }) { const blocks = parseBlocks(content); return (