eyeling 1.24.14 → 1.24.16

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/HANDBOOK.md CHANGED
@@ -3731,7 +3731,9 @@ This matters because the playground is not just a text box plus a submit button.
3731
3731
 
3732
3732
  The output behavior also adapts to the kind of N3 program being run. In some cases the natural result is a streamed list of derived triples. In others, such as programs using output-oriented constructs like `log:outputString`, a rendered text result is more appropriate. The playground supports both styles.
3733
3733
 
3734
- For Markdown-oriented `log:outputString` examples, the output pane has two views: a rendered Markdown view and a Markdown source view. The rendered view is selected by default when the output is text intended for presentation, while the source view keeps the exact generated Markdown available for copying, inspection, or comparison.
3734
+ For Markdown-oriented `log:outputString` examples, the output pane has two views: a rendered Markdown view and a Markdown source view. Those tabs appear only when the actual output looks like Markdown; Turtle or other plain output stays in the source editor without the Markdown toggle. The rendered view is selected by default for Markdown output, while the source view keeps the exact generated Markdown available for copying, inspection, or comparison.
3735
+
3736
+ Repository example reports often contain relative source links that are written for the checked-in files under `examples/output/*.md`. When such an example is loaded into the playground from a raw URL, the rendered Markdown view resolves those relative links against the corresponding static output page rather than against `/playground`, so links like `../name.n3` and `../input/name.trig` continue to point to the intended example files.
3735
3737
 
3736
3738
  ### I.4 Error handling and explainability
3737
3739
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.24.14",
3
+ "version": "1.24.16",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [
@@ -24,8 +24,8 @@ const C = TTY
24
24
  : { g: '', r: '', y: '', dim: '', n: '' };
25
25
  const msTag = (ms) => `${C.dim}(${ms} ms)${C.n}`;
26
26
 
27
- const TOTAL_TESTS = 11;
28
- const idxWidth = String(TOTAL_TESTS).length;
27
+ const TOTAL_TESTS = (fs.readFileSync(__filename, 'utf8').match(/^\s*beginTest\(/gm) || []).length;
28
+ const idxWidth = String(Math.max(1, TOTAL_TESTS)).length;
29
29
  let passed = 0;
30
30
  let failed = 0;
31
31
  let currentTest = null;
@@ -535,6 +535,8 @@ async function main() {
535
535
  // Intercept CodeMirror + remote GitHub raw URLs (keep test deterministic).
536
536
  const localPkg = fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8');
537
537
  const localEyeling = fs.readFileSync(path.join(ROOT, 'eyeling.js'), 'utf8');
538
+ const localSmokeArithmetic = fs.readFileSync(path.join(ROOT, 'examples', 'smoke-arithmetic.n3'), 'utf8');
539
+ const localSmokeArithmeticTrig = fs.readFileSync(path.join(ROOT, 'examples', 'input', 'smoke-arithmetic.trig'), 'utf8');
538
540
  const localSudoku = fs.readFileSync(path.join(ROOT, 'examples', 'sudoku.n3'), 'utf8');
539
541
  const localSudokuBuiltin = fs.readFileSync(path.join(ROOT, 'examples', 'builtin', 'sudoku.js'), 'utf8');
540
542
 
@@ -566,6 +568,14 @@ async function main() {
566
568
  'https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/eyeling.js',
567
569
  { ct: 'application/javascript', body: localEyeling },
568
570
  ],
571
+ [
572
+ 'https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/examples/smoke-arithmetic.n3',
573
+ { ct: 'text/plain', body: localSmokeArithmetic },
574
+ ],
575
+ [
576
+ 'https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/examples/input/smoke-arithmetic.trig',
577
+ { ct: 'text/plain', body: localSmokeArithmeticTrig },
578
+ ],
569
579
  [
570
580
  'https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/examples/sudoku.n3',
571
581
  { ct: 'text/plain', body: localSudoku },
@@ -806,6 +816,13 @@ ${JSON.stringify(last, null, 2)}`);
806
816
  const outputStringProgram = `@prefix : <#> .
807
817
  @prefix log: <http://www.w3.org/2000/10/swap/log#> .
808
818
  :report log:outputString "## Hello from output string\n\nLine 2 with **bold** and [Eyeling](https://example.org/eyeling)\n" .
819
+ `;
820
+ const logQueryTurtleProgram = `@prefix : <#> .
821
+ @prefix log: <http://www.w3.org/2000/10/swap/log#> .
822
+
823
+ :Socrates a :Human .
824
+ { ?x a :Human } => { ?x a :Mortal } .
825
+ { ?s ?p ?o } log:query { ?s ?p ?o } .
809
826
  `;
810
827
 
811
828
  // 1) Baseline smoke test: the default program runs to completion.
@@ -907,9 +924,29 @@ ${JSON.stringify(last, null, 2)}`);
907
924
  assert.ok(compactShareUrl.length < playgroundUrl.length + encodeURIComponent(outputStringProgram).length, 'Expected compact share URL to be shorter than raw editor URL');
908
925
  endTest();
909
926
 
910
- // 6) URL-loaded examples should auto-load matching examples/input/<stem>.trig and run in RDF/TriG mode.
927
+ // 6) log:query can produce Turtle; that should stay in plain source output without Markdown tabs.
928
+ beginTest('playground hides markdown tabs for Turtle log:query output');
929
+ await setProgram(logQueryTurtleProgram);
930
+ await clickRun();
931
+ const logQueryTurtle = await waitForState(
932
+ 'log:query Turtle output completion',
933
+ (st) =>
934
+ String(st.status || '')
935
+ .trim()
936
+ .startsWith('Done') && /:Socrates\s+a\s+:Mortal\s*\./.test(String(st.output || '')),
937
+ 30000,
938
+ );
939
+ assert.match(logQueryTurtle.output, /:Socrates\s+a\s+:Human\s*\./, 'Expected Turtle-style source output');
940
+ assert.match(logQueryTurtle.output, /:Socrates\s+a\s+:Mortal\s*\./, 'Expected inferred Turtle-style source output');
941
+ assert.doesNotMatch(logQueryTurtle.output, /^#{1,6}\s+/m, 'Expected non-Markdown Turtle output');
942
+ assert.equal(logQueryTurtle.outputTabsHidden, true, 'Expected Turtle log:query output to hide Markdown tabs');
943
+ assert.equal(logQueryTurtle.renderedHidden, true, 'Expected Turtle log:query output to skip rendered Markdown panel');
944
+ assert.equal(logQueryTurtle.sourceHidden, false, 'Expected Turtle log:query output to show source directly');
945
+ endTest();
946
+
947
+ // 7) URL-loaded examples should auto-load matching examples/input/<stem>.trig and run in RDF/TriG mode.
911
948
  beginTest('playground auto-loads companion TriG sidecars and uses RDF/TriG mode');
912
- await loadUrlIntoEditor(`${started.baseUrl}/examples/smoke-arithmetic.n3`);
949
+ await loadUrlIntoEditor('https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/examples/smoke-arithmetic.n3');
913
950
  const smokeLoaded = await waitForState(
914
951
  'smoke-arithmetic URL loaded with companion TriG input',
915
952
  (st) => /companion RDF\/TriG input/i.test(String(st.status || '')) && /input\/smoke-arithmetic\.trig/i.test(String(st.backgroundStatus || '')),
@@ -926,9 +963,19 @@ ${JSON.stringify(last, null, 2)}`);
926
963
  30000,
927
964
  );
928
965
  assert.match(smoke.output, /product = 42/i, 'Expected result derived from companion TriG evidence');
966
+ assert.match(
967
+ smoke.renderedHtml,
968
+ new RegExp('href="' + started.baseUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '/examples/smoke-arithmetic\\.n3"'),
969
+ 'Expected relative Markdown source links to resolve against the static output page, not /playground',
970
+ );
971
+ assert.match(
972
+ smoke.renderedHtml,
973
+ new RegExp('href="' + started.baseUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '/examples/input/smoke-arithmetic\\.trig"'),
974
+ 'Expected relative Markdown TriG links to resolve against the static output page, not /playground',
975
+ );
929
976
  endTest();
930
977
 
931
- // 7) URL-loaded repository examples should auto-load matching examples/builtin/<stem>.js.
978
+ // 8) URL-loaded repository examples should auto-load matching examples/builtin/<stem>.js.
932
979
  beginTest('playground auto-loads a companion example builtin for URL-loaded Sudoku');
933
980
  await loadUrlIntoEditor('https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/examples/sudoku.n3');
934
981
  await waitForState(
@@ -952,12 +999,12 @@ ${JSON.stringify(last, null, 2)}`);
952
999
  assert.match(sudoku.output, /unique valid Sudoku solution/i, 'Expected Sudoku builtin-backed result');
953
1000
  endTest();
954
1001
 
955
- // Ensure no uncaught runtime exceptions.
1002
+ // 9) Ensure no uncaught runtime exceptions.
956
1003
  beginTest('playground has no uncaught runtime exceptions');
957
1004
  assert.equal(exceptions.length, 0, `Uncaught exceptions in playground.html: ${JSON.stringify(exceptions[0] || {})}`);
958
1005
  endTest();
959
1006
 
960
- // Console errors are noisy and often indicate a broken UI.
1007
+ // 10) Console errors are noisy and often indicate a broken UI.
961
1008
  // (We suppress known noise like /favicon.ico on the server.)
962
1009
  beginTest('playground has no console errors');
963
1010
  assert.equal(consoleErrors.length, 0, `Console errors in playground.html: ${JSON.stringify(consoleErrors[0] || {})}`);