prolog-notebook 0.6.1 → 0.6.2

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 CHANGED
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.2] — 2026-08-30
4
+
5
+ From a field report: a built chapter opened in Chrome and Safari showed no controls and a Run
6
+ button that did nothing, while the same directory worked in VS Code's browser.
7
+
8
+ ### Fixed
9
+
10
+ - **A page that could not run looked exactly like one that could.** Opening `index.html` from
11
+ disk means `file://`, where browsers refuse ES modules, so none of the runtime loads. That
12
+ part is the browser doing its job; the page then lied about it twice.
13
+
14
+ The warning that exists for this sat **after the whole chapter** — line 540 of 551 — several
15
+ screens below the fold, which is the one place it could not do its job. It is now first in
16
+ the document and a fixed banner, revealed by a CSS `animation-delay` rather than a timer,
17
+ because script is precisely what is broken when it matters. `mount()` removes it long before
18
+ the delay elapses, so a page that boots never shows it and nothing jolts.
19
+
20
+ And **Run and Consult shipped enabled**, so the two controls a reader reaches for first were
21
+ full colour and inert — a screenshot of the failure was indistinguishable from success. They
22
+ now ship disabled and `mount()` enables them, which makes "this button works" and "something
23
+ is here to work it" one fact rather than two that can disagree. It also closes a real race on
24
+ a slow page.
25
+
26
+ - **Every prediction box had the same id.** The ordinal is counted correctly and was dropped on
27
+ the last hop into `renderPredict`, which took its default every time. The test asserted the
28
+ shape of the id, which three identical ones satisfy; it now asserts uniqueness.
29
+
30
+ - **The version picker had neither an id nor a name.** It only exists on a page that boots,
31
+ which is why the earlier console sweep never saw it. A served chapter's console is now empty.
32
+
33
+ ### Changed
34
+
35
+ - `build` says that opening the directory from disk will not run, rather than mentioning HTTP
36
+ in passing. It is the point at which somebody is about to double-click the thing.
37
+
3
38
  ## [0.6.1] — 2026-08-30
4
39
 
5
40
  ### Fixed
@@ -416,7 +416,13 @@ async function page(command, args) {
416
416
  else copyFileSync(entry.copy, target);
417
417
  }
418
418
  process.stderr.write(`${out}: ${built.size} files\n`);
419
- process.stderr.write(`Open ${join(out, 'index.html')} over HTTP, or host the directory.\n`);
419
+ // SAID HERE BECAUSE THIS IS WHERE IT IS ACTED ON. The obvious next move is to
420
+ // double-click index.html, and that is the one thing that cannot work:
421
+ // browsers refuse ES modules over file:// and the engine cannot be fetched
422
+ // there either (869erqq1u). The page says so too, but by then somebody is
423
+ // already looking at a chapter whose buttons do nothing.
424
+ process.stderr.write(`Host ${out} over HTTP — opening ${join(out, 'index.html')} from disk`
425
+ + ' will not run.\n');
420
426
  return 0;
421
427
  }
422
428
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prolog-notebook",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "Jupyter-style notebooks for Prolog. Runs in the browser, installs nothing.",
5
5
  "type": "module",
6
6
  "main": "./src/node.js",
@@ -1,4 +1,4 @@
1
1
  {
2
- "commit": "3abbaef",
3
- "built": "2026-08-30 19:27:20 UTC"
2
+ "commit": "bdbce1b",
3
+ "built": "2026-08-30 22:41:37 UTC"
4
4
  }
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
- THE CHAPTER ABOVE IS ALREADY READABLE. Everything below is the runtime that
182
- makes it runnable, and none of it is needed to read a word.
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 block ES modules over
188
- <code>file://</code>. Serve it over HTTP instead, or run
189
- <code>prolog-notebook view</code> on the notebook itself.
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
- margin: 0 0 2rem;
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
- + '<span class="picker" hidden><select aria-label="which version to download">'
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.1';
13
+ export const VERSION = '0.6.2';
14
14
 
15
15
  /** The two facts a licence notice is actually made of. */
16
16
  export const YEAR = '2026';