Add the web interface

Plain HTML, CSS and JavaScript modules, with no build step. Hash routes
and relative URLs make it work under any path. The job list is polled:
every 2 seconds while a job runs, and less often otherwise.
This commit is contained in:
tim
2026-09-27 23:38:28 +02:00
parent 09d6b76a17
commit 2f4271314b
30 changed files with 4442 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { install, nodes, server, settle } from './flow-helpers.mjs';
install();
const { globalRulesCard, regexDocs } = await import('../../bindery/static/js/rules.js');
const SYNTAX = 'https://docs.python.org/3/library/re.html#regular-expression-syntax';
const HOWTO = 'https://docs.python.org/3/howto/regex.html';
const links = (root) => nodes(root).filter((el) => el.tagName === 'a').map((a) => [a.textContent, a.attributes.href]);
test('the rule hints link the regular expression syntax and an introduction', async (t) => {
const backend = server(t);
backend.on('GET', 'rules', { rules: [], paragraphs: null, global_paragraphs: false });
backend.on('GET', 'stories', []);
const card = globalRulesCard();
await settle();
const expected = [['Python regular expressions', SYNTAX], ['introduction', HOWTO]];
assert.deepEqual(links(card).filter(([, href]) => href.startsWith('https://docs.python.org/')), expected);
const [syntax] = regexDocs();
assert.equal(syntax.attributes.target, '_blank');
assert.equal(syntax.attributes.rel, 'noreferrer noopener');
});