prolog-notebook 0.6.4 → 0.6.5

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,34 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.5] — 2026-08-31
4
+
5
+ Both from one field report on a real chapter.
6
+
7
+ ### Fixed
8
+
9
+ - **`Attempt to access not innermost query` on a perfectly ordinary Run.** Loading the program
10
+ cells above a query emits `consulted`, which is exactly what `rerun="auto"` waits for — so an
11
+ auto cell's re-run was queued and ran while the manual run was still inside its awaits. It
12
+ asked whether anybody was mid-sequence, saw nobody, and opened its query underneath the one
13
+ about to be opened. One engine allows one open query, and whichever is not innermost cannot
14
+ be stepped, so the reader — who had pressed Run on one cell and touched nothing else — got
15
+ SWI's own words for a stack they are never supposed to meet.
16
+
17
+ A cell now claims the engine when its run **starts**, not when it succeeds. Two supporting
18
+ changes close the same class: the engine's version probe is awaited during boot rather than
19
+ fired beside the reader's first Run, and `all()` holds the session's one slot until the
20
+ worker has answered rather than releasing it up front.
21
+
22
+ - **No way to clear your own answers in a chapter that shipped without any.** The outputs row
23
+ removed itself when the FILE had no saved answers — but that argument dies the moment a
24
+ reader presses Run, and a chapter published unrun is most chapters while an author is still
25
+ writing. The row now exists whenever there is a query cell, and counts what is on the page
26
+ rather than what is in the file: *No outputs yet*, *3 outputs on this page*, *3 outputs
27
+ cleared*.
28
+
29
+ The hide row above it stays keyed to the file, and correctly — hiding is for the chapter's
30
+ saved answers, and a reader's own run is not a spoiler.
31
+
3
32
  ## [0.6.4] — 2026-08-31
4
33
 
5
34
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prolog-notebook",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
4
4
  "description": "Jupyter-style notebooks for Prolog. Runs in the browser, installs nothing.",
5
5
  "type": "module",
6
6
  "main": "./src/node.js",
package/src/browser.js CHANGED
@@ -229,8 +229,16 @@ class WorkerQuery {
229
229
  async all(limit) {
230
230
  if (this.done) return { solutions: [], truncated: false };
231
231
  const qid = await this.#open();
232
- this.#finish();
233
- return this.session.send('all', { qid, limit });
232
+ try {
233
+ return await this.session.send('all', { qid, limit });
234
+ } finally {
235
+ // AFTER THE WORKER HAS ANSWERED, not before. Releasing the session's one
236
+ // slot up front said "nothing is open here" while the engine was still
237
+ // inside the goal, so anything that opened next nested inside a frame the
238
+ // session had already forgotten (869erqvzu). The slot is the claim that
239
+ // this query is the innermost one, and that stays true until it is done.
240
+ this.#finish();
241
+ }
234
242
  }
235
243
 
236
244
  async close({ superseded = false } = {}) {
@@ -1,4 +1,4 @@
1
1
  {
2
- "commit": "9973408",
3
- "built": "2026-08-30 23:05:20 UTC"
2
+ "commit": "60e27bc",
3
+ "built": "2026-08-30 23:44:28 UTC"
4
4
  }
package/src/notebook.js CHANGED
@@ -478,11 +478,18 @@ function mountPageBar(root, options, bus, programs, queries) {
478
478
  const outputsUnit = bar.querySelector('.unit.outputs');
479
479
  const outputsState = bar.querySelector('.outputs-state');
480
480
  let refreshOutputs = () => {};
481
- // Nothing published to clear, and nothing a restore could give back. The row
482
- // goes, exactly as the one above it does — an unrun chapter is the CLI's
483
- // business, and a control whose only possible effect is on the reader's own
484
- // run is a control offering to undo the thing they just asked for.
485
- if (!spoilers.length) {
481
+ // ANY QUERY CELL AT ALL, not just a chapter that shipped answers.
482
+ //
483
+ // This used to go when the FILE had none, on the argument that an unrun
484
+ // chapter is the CLI's business. That argument was wrong the moment a reader
485
+ // pressed Run: a chapter published without answers — which is most of them
486
+ // while an author is still writing — gave them no way to clear the answers
487
+ // they had just produced, and a panel with the row missing reads as a control
488
+ // that has broken rather than one that had nothing to do (869erqw08).
489
+ //
490
+ // The row above stays keyed to the FILE, and correctly: hiding is for the
491
+ // chapter's saved answers, and a reader's own run is not a spoiler.
492
+ if (!queries.length) {
486
493
  outputsUnit.remove();
487
494
  } else {
488
495
  const wipe = document.createElement('button');
@@ -503,12 +510,14 @@ function mountPageBar(root, options, bus, programs, queries) {
503
510
  // to give back, so a page of only those leaves restore with no work.
504
511
  const restorable = gone.filter((q) => q.hasSaved).length;
505
512
  wipe.disabled = back ? restorable === 0 : left.length === 0;
506
- // COUNTED FROM THE FILE while nothing is cleared, because that is the fact
507
- // the reader does not have: how much of this chapter is answers. After
508
- // that it counts what they did, which is the fact they want confirmed.
509
- outputsState.textContent = gone.length === 0
510
- ? `${plural(spoilers.length, 'output')} in this chapter`
511
- : `${plural(gone.length, 'output')} cleared`;
513
+ // COUNTED FROM THE PAGE, which is the only count that stays true for both
514
+ // kinds of chapter: one published with its answers in it, and one still
515
+ // being written where every output on screen is the reader's own.
516
+ outputsState.textContent = gone.length > 0
517
+ ? `${plural(gone.length, 'output')} cleared`
518
+ : left.length > 0
519
+ ? `${plural(left.length, 'output')} on this page`
520
+ : 'No outputs yet';
512
521
  label(wipe, back ? 'restore' : 'erase',
513
522
  back ? 'Restore outputs' : 'Clear all outputs');
514
523
  wipe.title = back
@@ -667,12 +676,22 @@ async function boot(options, bus, status) {
667
676
  bus.booted = true;
668
677
  if (!wasBooted) {
669
678
  bus.emit({ kind: 'started', at: clock() });
670
- // Asked for, not waited for. The light going green is the answer to "did it
671
- // start", and holding that back for a round trip about a version number
672
- // would be the page reporting the less interesting fact first.
673
- prologVersion(session)
674
- .then((version) => version && bus.emit({ kind: 'engine-version', version }))
675
- .catch(() => {});
679
+ // AWAITED, AND THAT IS A CORRECTNESS MATTER RATHER THAN A PREFERENCE.
680
+ //
681
+ // This was fired and forgotten, on the argument that the light going green
682
+ // answers "did it start" and a version number should not hold it back. But
683
+ // asking for the version IS A QUERY, and one engine allows ONE OPEN QUERY
684
+ // (869epzqpc). Firing it alongside the reader's own Run put two opens into
685
+ // the worker's queue, and whichever arrived second nested inside the first —
686
+ // so the cell that started the engine got `Attempt to access not innermost
687
+ // query` while the panel cheerfully displayed the version that had won the
688
+ // race (869erqvzu).
689
+ //
690
+ // The light still goes green first: the event above is emitted before this
691
+ // waits. What it now costs is one flag lookup on the first boot only, and
692
+ // what it buys is that no cell query can ever be in flight beside it.
693
+ const version = await prologVersion(session).catch(() => null);
694
+ if (version) bus.emit({ kind: 'engine-version', version });
676
695
  }
677
696
  return session;
678
697
  }
@@ -1227,6 +1246,20 @@ function mountQuery(cell, options, bus, { above = [], below = [], prediction = n
1227
1246
  : whose, 'from');
1228
1247
  write(`?- ${goal}.`, 'echo');
1229
1248
  setRunning(true);
1249
+ // THE ENGINE IS CLAIMED HERE, BEFORE THE CONSULTS — not after the query is
1250
+ // opened, which is where this used to be (869erqvzu).
1251
+ //
1252
+ // Loading the cells above emits `consulted`, and that is exactly what an
1253
+ // auto cell waits for. Its re-run is queued and runs on the next turn, while
1254
+ // this run is still somewhere inside its awaits — so it looked at a page
1255
+ // where nobody was stepping, saw a free engine, and opened its own query
1256
+ // underneath the one this cell was about to open. The reader, who had
1257
+ // pressed Run on one cell and touched nothing else, got `Attempt to access
1258
+ // not innermost query`.
1259
+ //
1260
+ // A cell is using the engine from the moment it starts, not from the moment
1261
+ // it succeeds. finish() lets go, on every path out of here.
1262
+ bus.stepping.add(cell);
1230
1263
  try {
1231
1264
  if (!bus.booted) write('starting SWI-Prolog (5.9 MB, first time only)…', 'done');
1232
1265
  session = await boot(options, bus);
@@ -1243,7 +1276,6 @@ function mountQuery(cell, options, bus, { above = [], below = [], prediction = n
1243
1276
  return;
1244
1277
  }
1245
1278
  query = session.query(goal);
1246
- bus.stepping.add(cell);
1247
1279
  // A sequence ends when another one starts — one engine, one open query
1248
1280
  // (869epzqpc). The reader hears it here, in the cell it happened to, with
1249
1281
  // the solutions they did take still under it and Run still lit. Silence was
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.4';
13
+ export const VERSION = '0.6.5';
14
14
 
15
15
  /** The two facts a licence notice is actually made of. */
16
16
  export const YEAR = '2026';