prolog-notebook 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -0
- package/README.md +15 -7
- package/bin/prolog-notebook.mjs +92 -20
- package/package.json +1 -1
- package/src/build-info.json +2 -2
- package/src/build.js +14 -7
- package/src/notebook.css +37 -3
- package/src/notebook.js +14 -1
- package/src/render.js +21 -3
- package/src/version.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,71 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.3] — 2026-08-30
|
|
4
|
+
|
|
5
|
+
`prolog-notebook build lists.prolog.md --check-update` answered *unknown option* — on a flag the
|
|
6
|
+
help listed under no command, exactly as though it were global.
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- **`--check-update` works with every command**, and forces the check past the daily TTL, which
|
|
11
|
+
is the whole point of asking. It was accepted by `execute` alone.
|
|
12
|
+
|
|
13
|
+
- **…and it answers down a pipe.** The check was skipped entirely without a terminal, so an
|
|
14
|
+
explicit check in a script reported nothing — indistinguishable from *you are up to date*,
|
|
15
|
+
which is the one thing an update notice must never look like. A terminal is needed to **offer**
|
|
16
|
+
an upgrade, never to **report** one.
|
|
17
|
+
|
|
18
|
+
- **`build --port 90` was accepted and ignored.** `view` and `build` share a parser, so each
|
|
19
|
+
quietly took the other's flags. A flag that is read and thrown away is worse than one refused:
|
|
20
|
+
it looks like it worked.
|
|
21
|
+
|
|
22
|
+
- **`clear` never looked for an update.** 0.5.1 established that every command doing real work
|
|
23
|
+
goes through one door, before the work; `clear` shipped afterwards and missed it, while
|
|
24
|
+
rewriting the author's notebook.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- **The help lists each option under the command that takes it.** Only `--check-update`,
|
|
29
|
+
`--version` and `-h` are listed as working anywhere, because only those three do.
|
|
30
|
+
|
|
31
|
+
- **A misplaced option says where it lives** — `--limit belongs to execute, not to view` —
|
|
32
|
+
rather than `unknown option` about a flag that is real and two lines further up.
|
|
33
|
+
|
|
34
|
+
## [0.6.2] — 2026-08-30
|
|
35
|
+
|
|
36
|
+
From a field report: a built chapter opened in Chrome and Safari showed no controls and a Run
|
|
37
|
+
button that did nothing, while the same directory worked in VS Code's browser.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- **A page that could not run looked exactly like one that could.** Opening `index.html` from
|
|
42
|
+
disk means `file://`, where browsers refuse ES modules, so none of the runtime loads. That
|
|
43
|
+
part is the browser doing its job; the page then lied about it twice.
|
|
44
|
+
|
|
45
|
+
The warning that exists for this sat **after the whole chapter** — line 540 of 551 — several
|
|
46
|
+
screens below the fold, which is the one place it could not do its job. It is now first in
|
|
47
|
+
the document and a fixed banner, revealed by a CSS `animation-delay` rather than a timer,
|
|
48
|
+
because script is precisely what is broken when it matters. `mount()` removes it long before
|
|
49
|
+
the delay elapses, so a page that boots never shows it and nothing jolts.
|
|
50
|
+
|
|
51
|
+
And **Run and Consult shipped enabled**, so the two controls a reader reaches for first were
|
|
52
|
+
full colour and inert — a screenshot of the failure was indistinguishable from success. They
|
|
53
|
+
now ship disabled and `mount()` enables them, which makes "this button works" and "something
|
|
54
|
+
is here to work it" one fact rather than two that can disagree. It also closes a real race on
|
|
55
|
+
a slow page.
|
|
56
|
+
|
|
57
|
+
- **Every prediction box had the same id.** The ordinal is counted correctly and was dropped on
|
|
58
|
+
the last hop into `renderPredict`, which took its default every time. The test asserted the
|
|
59
|
+
shape of the id, which three identical ones satisfy; it now asserts uniqueness.
|
|
60
|
+
|
|
61
|
+
- **The version picker had neither an id nor a name.** It only exists on a page that boots,
|
|
62
|
+
which is why the earlier console sweep never saw it. A served chapter's console is now empty.
|
|
63
|
+
|
|
64
|
+
### Changed
|
|
65
|
+
|
|
66
|
+
- `build` says that opening the directory from disk will not run, rather than mentioning HTTP
|
|
67
|
+
in passing. It is the point at which somebody is about to double-click the thing.
|
|
68
|
+
|
|
3
69
|
## [0.6.1] — 2026-08-30
|
|
4
70
|
|
|
5
71
|
### Fixed
|
package/README.md
CHANGED
|
@@ -132,16 +132,24 @@ chapter.prolog.md: 4 answers removed
|
|
|
132
132
|
`clear` empties every output block and touches nothing else; `execute` fills them in again from
|
|
133
133
|
the engine. A chapter with no answers is a valid chapter — one that has not been executed yet.
|
|
134
134
|
|
|
135
|
+
Each option belongs to a command, and typing one under the wrong command tells you which:
|
|
136
|
+
|
|
137
|
+
| flag | on | |
|
|
138
|
+
|---|---|---|
|
|
139
|
+
| `--limit <n>` | `execute` | solutions to take from one query before stopping. Default 100. |
|
|
140
|
+
| `--stdout` | `execute`, `clear` | print the result instead of writing the file |
|
|
141
|
+
| `--quiet` | `execute`, `clear` | report only failures |
|
|
142
|
+
| `--out <dir>` | `build` | where it writes. Default `<file>-site` |
|
|
143
|
+
| `--port <n>` | `view` | what it listens on. Default 8777, and it takes another if that one is busy |
|
|
144
|
+
| `--no-open` | `view` | print the URL instead of opening a browser |
|
|
145
|
+
|
|
146
|
+
Three work anywhere, because they are about the tool rather than about a notebook:
|
|
147
|
+
|
|
135
148
|
| flag | |
|
|
136
149
|
|---|---|
|
|
137
|
-
| `--limit <n>` | solutions to take from one query before stopping. Default 100. |
|
|
138
|
-
| `--stdout` | print the result instead of writing the file |
|
|
139
|
-
| `--quiet` | report only failures |
|
|
140
|
-
| `--out <dir>` | where `build` writes. Default `<file>-site` |
|
|
141
|
-
| `--port <n>` | what `view` listens on. Default 8777, and it takes another if that one is busy |
|
|
142
|
-
| `--no-open` | `view` prints the URL instead of opening a browser |
|
|
143
150
|
| `--version` | the tool's version, **the SWI-Prolog version it will run your chapters with**, and the copyright |
|
|
144
|
-
| `--check-update` | ask npm whether a newer one exists, and say so either way |
|
|
151
|
+
| `--check-update` | ask npm whether a newer one exists, and say so either way — forced, whatever the daily check thinks, and answered even down a pipe |
|
|
152
|
+
| `-h`, `--help` | the same list, from the command itself |
|
|
145
153
|
|
|
146
154
|
```sh
|
|
147
155
|
prolog-notebook upgrade # fetch the latest
|
package/bin/prolog-notebook.mjs
CHANGED
|
@@ -37,29 +37,66 @@ for (const stream of [process.stdout, process.stderr]) {
|
|
|
37
37
|
// src/version.js, where a page can import it too.
|
|
38
38
|
const require = createRequire(import.meta.url);
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* EACH OPTION UNDER THE COMMAND THAT TAKES IT (869erqra0).
|
|
42
|
+
*
|
|
43
|
+
* They were listed in one flat `Options` block with no owner, which reads as a
|
|
44
|
+
* promise that every one of them works everywhere — so `build --check-update`
|
|
45
|
+
* was a reasonable thing to type and `unknown option` was a strange thing to be
|
|
46
|
+
* told. Only three of them are actually the tool's rather than a command's, and
|
|
47
|
+
* now only those three are listed as such.
|
|
48
|
+
*/
|
|
40
49
|
const USAGE = `prolog-notebook — Jupyter-style notebooks for Prolog
|
|
41
50
|
|
|
42
51
|
prolog-notebook view <file.prolog.md> read it in a browser, cells and all
|
|
52
|
+
--port <n> what it listens on (default 8777)
|
|
53
|
+
--no-open print the URL instead of opening a browser
|
|
54
|
+
|
|
43
55
|
prolog-notebook build <file.prolog.md> write a page you can host or send
|
|
56
|
+
--out <dir> where it writes (default: <file>-site)
|
|
57
|
+
|
|
44
58
|
prolog-notebook execute <file.prolog.md>... run every query, write the answers in
|
|
59
|
+
--limit <n> solutions to take from one query before stopping (default ${DEFAULT_LIMIT})
|
|
60
|
+
--stdout print the result instead of writing the file
|
|
61
|
+
--quiet report only failures
|
|
62
|
+
|
|
45
63
|
prolog-notebook clear <file.prolog.md>... take the answers back out
|
|
64
|
+
--stdout print the result instead of writing the file
|
|
65
|
+
--quiet report only failures
|
|
66
|
+
|
|
46
67
|
prolog-notebook upgrade fetch the latest version
|
|
47
68
|
|
|
48
|
-
|
|
49
|
-
--limit <n> solutions to take from one query before stopping (default ${DEFAULT_LIMIT})
|
|
50
|
-
--stdout print the result instead of writing the file
|
|
51
|
-
--quiet report only failures
|
|
52
|
-
--out <dir> where build writes (default: <file>-site)
|
|
53
|
-
--port <n> what view listens on (default 8777)
|
|
54
|
-
--no-open view prints the URL instead of opening a browser
|
|
55
|
-
--version version, engine and copyright
|
|
69
|
+
Anywhere
|
|
56
70
|
--check-update ask npm whether a newer one exists, and say so either way
|
|
71
|
+
--version version, engine and copyright
|
|
57
72
|
-h, --help this
|
|
58
73
|
|
|
59
74
|
A query that stops at the limit is written without a terminator, which is the
|
|
60
75
|
format's way of saying the search was never exhausted. Nothing is invented.
|
|
61
76
|
`;
|
|
62
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Which command each option belongs to, so a misplaced one can say where it lives.
|
|
80
|
+
*
|
|
81
|
+
* `unknown option "--limit"` is true and unhelpful when the flag is real and two
|
|
82
|
+
* lines further up the same help. This costs a lookup table and saves a re-read.
|
|
83
|
+
*/
|
|
84
|
+
const BELONGS_TO = {
|
|
85
|
+
'--limit': 'execute',
|
|
86
|
+
'--stdout': 'execute and clear',
|
|
87
|
+
'--quiet': 'execute and clear',
|
|
88
|
+
'--out': 'build',
|
|
89
|
+
'--port': 'view',
|
|
90
|
+
'--no-open': 'view',
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
function unknownOption(arg, command) {
|
|
94
|
+
const home = BELONGS_TO[arg];
|
|
95
|
+
return home && home !== command
|
|
96
|
+
? `${arg} belongs to ${home}, not to ${command}\n`
|
|
97
|
+
: `unknown option "${arg}"\n`;
|
|
98
|
+
}
|
|
99
|
+
|
|
63
100
|
/**
|
|
64
101
|
* A runaway goal hangs this process — the engine is in-process here, so there is
|
|
65
102
|
* no thread left to notice (869ejgyax). Stated rather than implied, because the
|
|
@@ -158,11 +195,18 @@ function canAsk() {
|
|
|
158
195
|
* to a newer version, null to carry on here.
|
|
159
196
|
*/
|
|
160
197
|
async function upgradeFirst({ quiet = false, asked = false } = {}) {
|
|
161
|
-
|
|
198
|
+
// ASKED OUTRIGHT ALWAYS ANSWERS (869erqra0). A terminal is needed to OFFER the
|
|
199
|
+
// upgrade, never to report one: `--check-update` down a pipe used to check
|
|
200
|
+
// nothing and say nothing, which is indistinguishable from "you are up to
|
|
201
|
+
// date" — the one thing this must never look like.
|
|
202
|
+
if (!asked && (!canAsk() || quiet)) return null;
|
|
162
203
|
const ahead = await updateNotice({ version: VERSION, force: asked })
|
|
163
204
|
.catch(() => ({ message: null, newer: null }));
|
|
164
205
|
if (ahead.message) process.stderr.write(`${ahead.message}\n`);
|
|
165
|
-
|
|
206
|
+
// The notice is printed above whatever happens next; only the question needs
|
|
207
|
+
// somebody at the other end to answer it.
|
|
208
|
+
if (!ahead.newer || !canAsk()) return null;
|
|
209
|
+
if (!(await confirm('Update and continue on the new version?'))) return null;
|
|
166
210
|
if ((await upgrade(ahead.newer)) !== 0) {
|
|
167
211
|
process.stderr.write('Carrying on with the version you have.\n');
|
|
168
212
|
return null;
|
|
@@ -213,8 +257,8 @@ async function main(argv) {
|
|
|
213
257
|
}
|
|
214
258
|
|
|
215
259
|
const command = args.shift();
|
|
216
|
-
if (command === 'view' || command === 'build') return page(command, args);
|
|
217
|
-
if (command === 'clear') return clear(args);
|
|
260
|
+
if (command === 'view' || command === 'build') return page(command, args, asked);
|
|
261
|
+
if (command === 'clear') return clear(args, asked);
|
|
218
262
|
if (command === 'upgrade') {
|
|
219
263
|
const { message, newer } = await updateNotice({ version: VERSION, force: true });
|
|
220
264
|
if (message) process.stderr.write(`${message}\n`);
|
|
@@ -246,7 +290,7 @@ async function main(argv) {
|
|
|
246
290
|
else if (arg === '--quiet') options.quiet = true;
|
|
247
291
|
else if (arg === '--check-update') { /* handled above, and not a file */ }
|
|
248
292
|
else if (arg.startsWith('-')) {
|
|
249
|
-
process.stderr.write(
|
|
293
|
+
process.stderr.write(unknownOption(arg, 'execute'));
|
|
250
294
|
return 2;
|
|
251
295
|
} else files.push(arg);
|
|
252
296
|
}
|
|
@@ -259,7 +303,9 @@ async function main(argv) {
|
|
|
259
303
|
|
|
260
304
|
const jump = await upgradeFirst({ quiet: options.quiet, asked });
|
|
261
305
|
if (jump !== null) return jump;
|
|
262
|
-
|
|
306
|
+
// Whatever upgradeFirst has just reported must not be reported again below.
|
|
307
|
+
// Asked outright, it always reports now, terminal or not.
|
|
308
|
+
checked = asked || (canAsk() && !options.quiet);
|
|
263
309
|
|
|
264
310
|
// STARTED NOW, READ AT THE END. The registry is somebody else's machine on
|
|
265
311
|
// somebody else's network, and none of that should stand between the reader
|
|
@@ -309,14 +355,16 @@ async function main(argv) {
|
|
|
309
355
|
* No engine, no network and no update check: this is a text operation on a file
|
|
310
356
|
* the reader already has.
|
|
311
357
|
*/
|
|
312
|
-
async function clear(args) {
|
|
358
|
+
async function clear(args, asked = false) {
|
|
313
359
|
const options = { stdout: false, quiet: false };
|
|
314
360
|
const files = [];
|
|
315
361
|
for (const arg of args) {
|
|
316
362
|
if (arg === '--stdout') options.stdout = true;
|
|
317
363
|
else if (arg === '--quiet') options.quiet = true;
|
|
364
|
+
// Handled by the caller, and not a file.
|
|
365
|
+
else if (arg === '--check-update') { /* global */ }
|
|
318
366
|
else if (arg.startsWith('-')) {
|
|
319
|
-
process.stderr.write(
|
|
367
|
+
process.stderr.write(unknownOption(arg, 'clear'));
|
|
320
368
|
return 2;
|
|
321
369
|
} else files.push(arg);
|
|
322
370
|
}
|
|
@@ -325,6 +373,13 @@ async function clear(args) {
|
|
|
325
373
|
return 2;
|
|
326
374
|
}
|
|
327
375
|
|
|
376
|
+
// THE SAME DOOR AS EVERY OTHER COMMAND THAT DOES REAL WORK (869erqra0). 0.5.1
|
|
377
|
+
// put `view` and `build` through it and this command shipped afterwards, so it
|
|
378
|
+
// never looked. Emptying somebody's notebook is as real as work gets here, and
|
|
379
|
+
// before the work is the only point at which a newer version changes anything.
|
|
380
|
+
const jump = await upgradeFirst({ quiet: options.quiet, asked });
|
|
381
|
+
if (jump !== null) return jump;
|
|
382
|
+
|
|
328
383
|
let status = 0;
|
|
329
384
|
for (const file of files) {
|
|
330
385
|
let source;
|
|
@@ -361,11 +416,20 @@ async function clear(args) {
|
|
|
361
416
|
* whole reason a built page is readable before any engine arrives. Use `run` to
|
|
362
417
|
* put them there.
|
|
363
418
|
*/
|
|
364
|
-
async function page(command, args) {
|
|
419
|
+
async function page(command, args, asked = false) {
|
|
365
420
|
const options = { out: null, port: 8777, open: true };
|
|
366
421
|
const files = [];
|
|
367
422
|
while (args.length) {
|
|
368
423
|
const arg = args.shift();
|
|
424
|
+
// ONE PARSER, TWO COMMANDS, AND THEY DO NOT TAKE THE SAME FLAGS. `view` and
|
|
425
|
+
// `build` share this function, so `build --port 90` was quietly accepted and
|
|
426
|
+
// ignored while the help said --port was view's (869erqra0). A flag that is
|
|
427
|
+
// read and thrown away is worse than one that is refused: it looks like it
|
|
428
|
+
// worked.
|
|
429
|
+
if (BELONGS_TO[arg] && BELONGS_TO[arg] !== command) {
|
|
430
|
+
process.stderr.write(unknownOption(arg, command));
|
|
431
|
+
return 2;
|
|
432
|
+
}
|
|
369
433
|
if (arg === '--out') options.out = args.shift();
|
|
370
434
|
else if (arg === '--port') {
|
|
371
435
|
options.port = Number(args.shift());
|
|
@@ -374,8 +438,10 @@ async function page(command, args) {
|
|
|
374
438
|
return 2;
|
|
375
439
|
}
|
|
376
440
|
} else if (arg === '--no-open') options.open = false;
|
|
441
|
+
// Handled by the caller, and not a file.
|
|
442
|
+
else if (arg === '--check-update') { /* global */ }
|
|
377
443
|
else if (arg.startsWith('-')) {
|
|
378
|
-
process.stderr.write(
|
|
444
|
+
process.stderr.write(unknownOption(arg, command));
|
|
379
445
|
return 2;
|
|
380
446
|
} else files.push(arg);
|
|
381
447
|
}
|
|
@@ -387,7 +453,7 @@ async function page(command, args) {
|
|
|
387
453
|
// The same offer the execute path makes, and for the same reason: a server about to
|
|
388
454
|
// start, or a directory about to be written, is work that a newer version
|
|
389
455
|
// should be doing.
|
|
390
|
-
const jump = await upgradeFirst();
|
|
456
|
+
const jump = await upgradeFirst({ asked });
|
|
391
457
|
if (jump !== null) return jump;
|
|
392
458
|
|
|
393
459
|
const file = files[0];
|
|
@@ -416,7 +482,13 @@ async function page(command, args) {
|
|
|
416
482
|
else copyFileSync(entry.copy, target);
|
|
417
483
|
}
|
|
418
484
|
process.stderr.write(`${out}: ${built.size} files\n`);
|
|
419
|
-
|
|
485
|
+
// SAID HERE BECAUSE THIS IS WHERE IT IS ACTED ON. The obvious next move is to
|
|
486
|
+
// double-click index.html, and that is the one thing that cannot work:
|
|
487
|
+
// browsers refuse ES modules over file:// and the engine cannot be fetched
|
|
488
|
+
// there either (869erqq1u). The page says so too, but by then somebody is
|
|
489
|
+
// already looking at a chapter whose buttons do nothing.
|
|
490
|
+
process.stderr.write(`Host ${out} over HTTP — opening ${join(out, 'index.html')} from disk`
|
|
491
|
+
+ ' will not run.\n');
|
|
420
492
|
return 0;
|
|
421
493
|
}
|
|
422
494
|
|
package/package.json
CHANGED
package/src/build-info.json
CHANGED
package/src/build.js
CHANGED
|
@@ -176,19 +176,26 @@ function page(notebook) {
|
|
|
176
176
|
</head>
|
|
177
177
|
<body>
|
|
178
178
|
<main>
|
|
179
|
-
${renderNotebook(notebook)}
|
|
180
179
|
<!--
|
|
181
|
-
|
|
182
|
-
|
|
180
|
+
FIRST IN THE DOCUMENT, AND IT COSTS A READER NOTHING (869erqq1u). mount()
|
|
181
|
+
removes this element, and the stylesheet holds it invisible for a second and a
|
|
182
|
+
half before revealing it — so a page that boots never shows it, and a page that
|
|
183
|
+
cannot boot says so where somebody staring at a dead button will see it. It
|
|
184
|
+
used to sit AFTER the chapter, several screens below the fold, which is the one
|
|
185
|
+
place it could not do its job.
|
|
186
|
+
|
|
187
|
+
The delay is CSS rather than a timer, for the obvious reason: script is exactly
|
|
188
|
+
what is broken when this matters.
|
|
183
189
|
-->
|
|
184
190
|
<div id="boot-warning">
|
|
185
191
|
<strong>This notebook is not running.</strong>
|
|
186
192
|
The page loaded but its JavaScript did not. The usual cause is opening
|
|
187
|
-
<code>index.html</code> straight from disk — browsers
|
|
188
|
-
<code>file://</code
|
|
189
|
-
<code>prolog-notebook view</code> on the notebook
|
|
190
|
-
The chapter is readable either way; only the buttons need this.
|
|
193
|
+
<code>index.html</code> straight from disk — browsers refuse ES modules over
|
|
194
|
+
<code>file://</code>, and the engine cannot be fetched there either. Serve this
|
|
195
|
+
directory over HTTP, or run <code>prolog-notebook view</code> on the notebook
|
|
196
|
+
itself. The chapter is readable either way; only the buttons need this.
|
|
191
197
|
</div>
|
|
198
|
+
${renderNotebook(notebook)}
|
|
192
199
|
</main>
|
|
193
200
|
<script type="module" src="app.js"></script>
|
|
194
201
|
</body>
|
package/src/notebook.css
CHANGED
|
@@ -502,13 +502,47 @@ button.primary:hover { background: #98380f; }
|
|
|
502
502
|
}
|
|
503
503
|
|
|
504
504
|
/* Shown until mount() runs; see notebook.js. Its presence means the page is inert. */
|
|
505
|
+
/* THE PAGE THAT CANNOT RUN HAS TO SAY SO, WHERE IT WILL BE READ (869erqq1u).
|
|
506
|
+
|
|
507
|
+
Two properties, and both are the point:
|
|
508
|
+
|
|
509
|
+
FIXED, so it takes no layout. A working page carries this element for the
|
|
510
|
+
fraction of a second before mount() removes it, and a banner in the flow would
|
|
511
|
+
push the whole chapter down and then let it snap back — a visible jolt on every
|
|
512
|
+
single load, to warn about something that is not happening.
|
|
513
|
+
|
|
514
|
+
REVEALED ON A DELAY, in CSS. mount() removes it long before the animation
|
|
515
|
+
fires, so a page that boots never shows it at all; a page that cannot boot
|
|
516
|
+
shows it a second and a half in. It cannot be a timer, because script is the
|
|
517
|
+
thing that is broken. `forwards` holds the final frame rather than snapping
|
|
518
|
+
back to invisible. */
|
|
505
519
|
#boot-warning {
|
|
506
|
-
|
|
520
|
+
position: fixed;
|
|
521
|
+
top: 0;
|
|
522
|
+
left: 0;
|
|
523
|
+
right: 0;
|
|
524
|
+
z-index: 100;
|
|
525
|
+
margin: 0;
|
|
507
526
|
padding: 1rem 1.2rem;
|
|
508
|
-
border: 2px solid var(--err);
|
|
509
|
-
border-radius: 6px;
|
|
527
|
+
border-bottom: 2px solid var(--err);
|
|
510
528
|
background: #fff5f3;
|
|
511
529
|
font-size: .95rem;
|
|
530
|
+
opacity: 0;
|
|
531
|
+
animation: boot-warning .25s ease 1.5s forwards;
|
|
532
|
+
}
|
|
533
|
+
@keyframes boot-warning { to { opacity: 1; } }
|
|
534
|
+
/* ROOM FOR IT, made on the same delay and only while it is there. A fixed banner
|
|
535
|
+
would otherwise sit over the chapter's title — the page is broken, but it
|
|
536
|
+
should not also look broken. `:has` is what keeps this honest: the moment
|
|
537
|
+
mount() removes the element the selector stops matching, so a page that boots
|
|
538
|
+
never reserves the space, and a browser too old for `:has` merely goes back to
|
|
539
|
+
the banner overlapping. */
|
|
540
|
+
body:has(#boot-warning) { animation: boot-warning-room .25s ease 1.5s forwards; }
|
|
541
|
+
@keyframes boot-warning-room { to { padding-top: 7rem; } }
|
|
542
|
+
/* Somebody who has asked not to be moved still gets the message — only the fade
|
|
543
|
+
goes. Without this the whole rule is skipped and the warning never appears. */
|
|
544
|
+
@media (prefers-reduced-motion: reduce) {
|
|
545
|
+
#boot-warning { animation-duration: 0s; }
|
|
512
546
|
}
|
|
513
547
|
#boot-warning strong { color: var(--err); }
|
|
514
548
|
#boot-warning pre {
|
package/src/notebook.js
CHANGED
|
@@ -595,7 +595,12 @@ export function offerDownload(root, options = {}) {
|
|
|
595
595
|
// their version IS the chapter, and a menu with one item is a control
|
|
596
596
|
// pretending to offer something.
|
|
597
597
|
unit.innerHTML = '<span class="state notebook-state"><span class="only"></span>'
|
|
598
|
-
|
|
598
|
+
// `name` rather than `id`, because a page may hold more than one notebook
|
|
599
|
+
// (v0.4) and two ids would collide where two names cannot — this select is in
|
|
600
|
+
// no form. Without either, the issues panel flags it, which is the same red
|
|
601
|
+
// console 869ernmxe was about; it only shows up on a page that BOOTS, so the
|
|
602
|
+
// earlier sweep never saw it.
|
|
603
|
+
+ '<span class="picker" hidden><select name="notebook-version" aria-label="which version to download">'
|
|
599
604
|
+ '<option value="mine">Your version</option>'
|
|
600
605
|
+ '<option value="published">As published</option>'
|
|
601
606
|
+ `</select>${icon('chevron')}</span></span>`
|
|
@@ -677,6 +682,10 @@ function mountProgram(cell, options, bus) {
|
|
|
677
682
|
const button = cell.querySelector('[data-act="consult"]') ?? cell.querySelector('button');
|
|
678
683
|
const resetBtn = cell.querySelector('[data-act="reset"]');
|
|
679
684
|
const status = cell.querySelector('.status');
|
|
685
|
+
// THE BUTTON IS LIVE BECAUSE THIS RAN, and not a moment before (869erqq1u).
|
|
686
|
+
// It ships disabled so that a page whose runtime never arrived looks like what
|
|
687
|
+
// it is, rather than offering a control nothing is listening to.
|
|
688
|
+
button.disabled = false;
|
|
680
689
|
|
|
681
690
|
/**
|
|
682
691
|
* The one cell whose state a re-consult does not undo (format §8).
|
|
@@ -1482,6 +1491,10 @@ function mountQuery(cell, options, bus, { above = [], below = [], prediction = n
|
|
|
1482
1491
|
}
|
|
1483
1492
|
|
|
1484
1493
|
decorateSaved();
|
|
1494
|
+
// Same as a program cell's Consult: enabled here, by the thing that makes it
|
|
1495
|
+
// do something, so the page cannot show a live-looking Run with no runtime
|
|
1496
|
+
// behind it (869erqq1u).
|
|
1497
|
+
runBtn.disabled = false;
|
|
1485
1498
|
refresh();
|
|
1486
1499
|
|
|
1487
1500
|
return {
|
package/src/render.js
CHANGED
|
@@ -100,7 +100,7 @@ export function renderContainer(cell, ordinal = 1) {
|
|
|
100
100
|
return `<div class="aside">\n${renderProse(joinHeadAndBody(cell))}\n</div>`;
|
|
101
101
|
|
|
102
102
|
case 'predict':
|
|
103
|
-
return renderPredict(cell);
|
|
103
|
+
return renderPredict(cell, ordinal);
|
|
104
104
|
|
|
105
105
|
case 'bullets':
|
|
106
106
|
return `<div class="bullets">\n${cell.title ? `<h2>${renderInline(cell.title)}</h2>\n` : ''}${renderProse(cell.body)}\n</div>`;
|
|
@@ -164,6 +164,24 @@ function splitReveal(body) {
|
|
|
164
164
|
};
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
/**
|
|
168
|
+
* EVERY BUTTON SHIPS DISABLED, AND THE RUNTIME TURNS IT ON (869erqq1u).
|
|
169
|
+
*
|
|
170
|
+
* A page whose JavaScript never arrives is a page where none of these can work —
|
|
171
|
+
* opened from disk, where browsers refuse ES modules; behind a CSP that blocks
|
|
172
|
+
* the script; on a connection that dropped it. What that page must not do is look
|
|
173
|
+
* exactly like a working one. Consult and Run used to ship enabled, so the two
|
|
174
|
+
* controls a reader reaches for first were full colour and did nothing, and a
|
|
175
|
+
* screenshot of the failure was indistinguishable from success.
|
|
176
|
+
*
|
|
177
|
+
* mount() enables them, which makes "this button works" and "something is here to
|
|
178
|
+
* work it" the same fact rather than two that can disagree. It also closes a real
|
|
179
|
+
* race on a slow page: they were clickable before anything was wired to them.
|
|
180
|
+
*
|
|
181
|
+
* `; next`, `all` and `stop` were already disabled for the ordinary reason — there
|
|
182
|
+
* is no query open yet — and now the whole bar tells one story.
|
|
183
|
+
*/
|
|
184
|
+
|
|
167
185
|
/**
|
|
168
186
|
* A program cell: the Prolog, and a button that loads it.
|
|
169
187
|
*
|
|
@@ -180,7 +198,7 @@ export function renderProgram(cell) {
|
|
|
180
198
|
return `<div class="cell program" data-cell="${escapeHtml(cell.id)}">
|
|
181
199
|
<div class="bar">program<span class="spacer"></span><span class="status"></span>
|
|
182
200
|
<button data-act="reset" disabled>reset</button>
|
|
183
|
-
<button class="primary" data-act="consult">Consult</button></div>
|
|
201
|
+
<button class="primary" data-act="consult" disabled>Consult</button></div>
|
|
184
202
|
<textarea id="src-${escapeHtml(cell.id)}" spellcheck="false">${escapeHtml(cell.source)}</textarea>
|
|
185
203
|
</div>`;
|
|
186
204
|
}
|
|
@@ -217,7 +235,7 @@ export function renderQuery(cell, options = {}) {
|
|
|
217
235
|
return `<div class="cell query" data-cell="${escapeHtml(cell.id)}"${hold}${auto}${wasStale}>
|
|
218
236
|
<div class="bar">query<span class="spacer"></span><span class="status"></span>
|
|
219
237
|
<button data-act="reset" disabled>reset</button>
|
|
220
|
-
<button class="primary" data-act="run">Run</button>
|
|
238
|
+
<button class="primary" data-act="run" disabled>Run</button>
|
|
221
239
|
<button data-act="next" disabled>; next</button>
|
|
222
240
|
<button data-act="all" disabled>all</button>
|
|
223
241
|
<button data-act="stop" disabled>stop</button></div>
|
package/src/version.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
export const NAME = 'Prolog Notebook';
|
|
11
11
|
|
|
12
12
|
/** Must equal package.json's `version` — test/run.test.mjs enforces it. */
|
|
13
|
-
export const VERSION = '0.6.
|
|
13
|
+
export const VERSION = '0.6.3';
|
|
14
14
|
|
|
15
15
|
/** The two facts a licence notice is actually made of. */
|
|
16
16
|
export const YEAR = '2026';
|