@factoidal/core 0.34.0 → 0.35.1
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 +23 -0
- package/bin/factoidal.mjs +3 -1
- package/bin/foafnaut.mjs +41 -7
- package/l4-assets/l4factoidal.js +1 -1
- package/l4-assets/l4factoidal.wasm +0 -0
- package/l4-assets/version.json +6 -6
- package/lib/version.mjs +1 -1
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.35.1 (2026-11-11)
|
|
4
|
+
|
|
5
|
+
- Foafnaut (macOS): a heavy page no longer freezes the app. The host keeps one frame of GPU work in flight for heavy frames (three for light ones); after three frames over 250 ms it slows the page's animation and shows "SLOWED: HEAVY PAGE". Quitting or closing the window stops GPU work, so the app quits within seconds (before, up to 209 s on the Spinal Tap page at 2x). `--gpu-frames-in-flight N` sets the limit.
|
|
6
|
+
- Foafnaut: Arabic letters join again on a page that loaded Noto Sans Math first; a font whose GSUB covers the script is preferred. Chinese, Japanese and Korean text breaks between characters, so long runs wrap instead of pushing right-to-left lines out of their cells.
|
|
7
|
+
- BubblePopJS: conversions between byte arrays and the engine's values are faster (Pako about 14% faster on the native engine), with proofs that the fast forms give the same results.
|
|
8
|
+
- XMPP: xmpp-serve logs each connection by its address tag, never the address (--quiet stops it).
|
|
9
|
+
|
|
10
|
+
## 0.35.0 (2026-11-10)
|
|
11
|
+
|
|
12
|
+
- Foafnaut: partial frames. A frame compiles again only the primitives that changed, keeps a damage rectangle, and redraws only that part of a layer. On the Spinal Tap page, one animated frame went from 351 ms to 3.9 ms of GPU and JS time at 1x (1225 ms to 4.0 ms at 2x); engine time per frame 28.5 ms to 10.9 ms. `retained:false` still gives the full path, and tests check that both give the same bytes.
|
|
13
|
+
- Foafnaut: BubblePopJS, the built-in JavaScript engine, runs page scripts by default everywhere, also on the Hub. QuickJS-NG stays in the builds for comparison. Fixes: `r.exec(s)` in a loop no longer pays for the length of `s` on each call; a block with `let` or `const` may hold an `await`.
|
|
14
|
+
- Foafnaut: a shader gallery page (WebGPU, WGSL), the same results on both JavaScript engines.
|
|
15
|
+
- Foafnaut: the Hub starts faster, and layout is faster on large pages.
|
|
16
|
+
- Foafnaut: on a Retina Mac the page is drawn at full resolution.
|
|
17
|
+
- Foafnaut: iframes; form submission in the page's encoding; colour emoji (COLR/CPAL) in text, SVG text and canvas text; more fonts and SMIL animation.
|
|
18
|
+
- Foafnaut: a click is no longer lost when the engine is busy; pointer input runs before queued engine work, and script hit tests use the frame on screen.
|
|
19
|
+
- Foafnaut (macOS): the app links Lean's runtime without GNU MP. The releases that still contain GNU MP carry its notice.
|
|
20
|
+
- `foafnaut` command: with no channel, it starts the newer of the installed release and the local bleeding-edge build, and says which. `--bleeding-edge` says the commit and age of the local build and warns when it is more than 6 hours old.
|
|
21
|
+
- LWS client: `verifyNotification` accepts a notification whose `Signature-Input` covers the derived component `@query` (RFC 9421 section 2.2.7; https://github.com/danbri/factoidal-core/issues/379). The value comes from the inbox URL: `?` and the query, or `?` alone when the URL has no query. Before, the client read `@query` as a header field and refused the notification with "a covered component is missing", so `createNotificationReceiver` answered 401 to every notification from a storage that signs `@query` (sparq-lws-core; seen with 0.26.0 and 0.34.0). The client derives `@method`, `@scheme`, `@authority`, `@path` and `@query` only. Another name that starts with `@` (`@target-uri`, `@request-target`, `@query-param`) is refused and is not read as a header field. The decision is in Lean (`inboxBase` and `urlQuery`, `L4Factoidal/LWS/Client/Machine.lean`), through the wasm op `lwsClientStep`.
|
|
22
|
+
- `foafnaut` command: `--version` prints the commit and the build time of the app that would run. The app gives the answer, from the record that its build wrote. An app that is too old to answer gives the line "this build is too old to answer --version".
|
|
23
|
+
- `foafnaut` command: a page given as `file:name.html`, with no `//`, is a path relative to the current directory. A `file://` URL is read as a URL, so percent-encoded characters in its path are decoded.
|
|
24
|
+
- XMPP: every IQ result the server sends has a `from` (RFC 6120 section 8.1.2.1): the address asked, or the account's bare JID when the request had no `to`. Theorems `handleIq_send_has_from` and `handleIq_domain_from`.
|
|
25
|
+
|
|
3
26
|
## 0.34.0 (2026-11-08)
|
|
4
27
|
|
|
5
28
|
- `foafnaut` command: `--url` and `--html` open a page or a file (`file:` URLs too), and other options are passed to the app.
|
package/bin/factoidal.mjs
CHANGED
|
@@ -2998,7 +2998,9 @@ async function commandXmppServe (positional, options) {
|
|
|
2998
2998
|
dir: positional[0],
|
|
2999
2999
|
port: servePort(options, useTls && !startTls ? 5223 : 5222),
|
|
3000
3000
|
host: serveHost(options),
|
|
3001
|
-
onError: (error) => { if (!quiet) err(`xmpp-serve: ${error.message}`) }
|
|
3001
|
+
onError: (error) => { if (!quiet) err(`xmpp-serve: ${error.message}`) },
|
|
3002
|
+
// Connection events: an address tag (never an address), closes at a timeout.
|
|
3003
|
+
onLog: (line) => { if (!quiet) err(`xmpp-serve: ${line}`) }
|
|
3002
3004
|
}
|
|
3003
3005
|
if (typeof options.domain === 'string') settings.domain = options.domain
|
|
3004
3006
|
if (typeof options.accounts === 'string') {
|
package/bin/foafnaut.mjs
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
// current -> versions/<commit>, config.json {"bleedingEdge":"/path/to/App.app"}.
|
|
6
6
|
import { spawn, spawnSync } from 'node:child_process';
|
|
7
7
|
import { createHash } from 'node:crypto';
|
|
8
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
9
9
|
import { homedir } from 'node:os';
|
|
10
|
-
import { basename, join, resolve } from 'node:path';
|
|
11
|
-
import { pathToFileURL } from 'node:url';
|
|
10
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
11
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
12
12
|
|
|
13
13
|
const HELP = `foafnaut [--latest|--bleeding-edge] [PAGE | --url URL | --html FILE] [APP_OPTIONS] [-- APP_ARGS]
|
|
14
14
|
|
|
@@ -23,6 +23,7 @@ const HELP = `foafnaut [--latest|--bleeding-edge] [PAGE | --url URL | --html FIL
|
|
|
23
23
|
--install-latest install the newest published build and stop
|
|
24
24
|
--list list installed versions
|
|
25
25
|
--which print the app that would run
|
|
26
|
+
--version print the commit and build time of that app (the app answers)
|
|
26
27
|
--help this text
|
|
27
28
|
|
|
28
29
|
App options, passed to the app as they are:
|
|
@@ -33,6 +34,9 @@ App options, passed to the app as they are:
|
|
|
33
34
|
--font FILE load this font file
|
|
34
35
|
--bitmap-font use the baked-in ASCII font only
|
|
35
36
|
--follow-links open links that a page asks for at load
|
|
37
|
+
--be-liberal-in-what-we-accept draw a page without the features this browser may skip
|
|
38
|
+
(module scripts, unreadable media conditions, stylesheets that did not
|
|
39
|
+
load) and list them in the console (Option-Command-J); the default refuses
|
|
36
40
|
|
|
37
41
|
Also: foafnaut-latest, foafnaut-bleedingedge. Environment: FOAFNAUT_HOME, and
|
|
38
42
|
FOAFNAUT_APP, which chooses the app outright. Page data goes to the app. Its trace
|
|
@@ -108,13 +112,15 @@ async function installLatest() {
|
|
|
108
112
|
}
|
|
109
113
|
|
|
110
114
|
// App options that take no value, and those that take one.
|
|
111
|
-
const APP_FLAGS = ['--perf-summary', '--check-pixels', '--wasm-engine', '--bitmap-font', '--follow-links', '--idle-checks-only', '--trace-input'];
|
|
115
|
+
const APP_FLAGS = ['--perf-summary', '--check-pixels', '--wasm-engine', '--bitmap-font', '--follow-links', '--idle-checks-only', '--trace-input', '--be-liberal-in-what-we-accept'];
|
|
112
116
|
const APP_VALUES = ['--native-engine', '--font'];
|
|
113
117
|
|
|
114
118
|
function pageArgs(page) {
|
|
115
119
|
if (page === undefined) return [];
|
|
116
120
|
if (/^https?:\/\//i.test(page)) return ['--url', page];
|
|
117
|
-
|
|
121
|
+
// file:///abs/path is a URL; file:name.html has no // and is relative to here.
|
|
122
|
+
if (/^file:\/\//i.test(page)) page = fileURLToPath(page);
|
|
123
|
+
else if (/^file:/i.test(page)) page = decodeURIComponent(page.slice(5));
|
|
118
124
|
const path = resolve(page);
|
|
119
125
|
if (!existsSync(path)) fail(`no such file: ${page}`);
|
|
120
126
|
return ['--html', path];
|
|
@@ -124,7 +130,7 @@ export async function main(argv, channel = 'current') {
|
|
|
124
130
|
const split = argv.indexOf('--');
|
|
125
131
|
const options = split < 0 ? argv : argv.slice(0, split);
|
|
126
132
|
const extra = split < 0 ? [] : argv.slice(split + 1);
|
|
127
|
-
let page, which = false, verbose = false, detach = false;
|
|
133
|
+
let page, which = false, verbose = false, detach = false, version = false;
|
|
128
134
|
const app_ = [];
|
|
129
135
|
const setPage = (value) => { if (page !== undefined) fail('give one page'); if (value === undefined) fail('a page option needs a value'); page = value; };
|
|
130
136
|
// Each of these picks the app or the action. Two of them have no one meaning.
|
|
@@ -136,6 +142,7 @@ export async function main(argv, channel = 'current') {
|
|
|
136
142
|
else if (arg === '--latest') channel = 'latest';
|
|
137
143
|
else if (arg === '--bleeding-edge') channel = 'bleeding-edge';
|
|
138
144
|
else if (arg === '--which') which = true;
|
|
145
|
+
else if (arg === '--version') version = true;
|
|
139
146
|
else if (arg === '--verbose' || arg === '-v') verbose = true;
|
|
140
147
|
else if (arg === '--detach') detach = true;
|
|
141
148
|
else if (arg === '--install-latest') { await installLatest(); return; }
|
|
@@ -157,9 +164,36 @@ export async function main(argv, channel = 'current') {
|
|
|
157
164
|
let app = process.env.FOAFNAUT_APP;
|
|
158
165
|
if (!app && channel === 'latest') app = await installLatest();
|
|
159
166
|
if (!app && channel === 'bleeding-edge') app = bleedingEdgeApp();
|
|
160
|
-
|
|
167
|
+
// With no channel named: the newer of the installed release and the bleeding-edge build
|
|
168
|
+
// (owner, 2026-11-09: plain foafnaut started a release of 6 November, older than the
|
|
169
|
+
// local build, with nothing to say so). Each is dated by its files on disk.
|
|
170
|
+
if (!app) {
|
|
171
|
+
const release = currentApp(), local = existsSync(bleedingEdgeApp()) ? bleedingEdgeApp() : undefined;
|
|
172
|
+
const age = (path) => statSync(path).mtimeMs;
|
|
173
|
+
app = release && local ? (age(local) >= age(release) ? local : release) : (release ?? local ?? bleedingEdgeApp());
|
|
174
|
+
if (release && app === release)
|
|
175
|
+
console.error(`foafnaut: the installed release ${basename(realpathSync(join(home(), 'current')))} (newer than the bleeding-edge build)`);
|
|
176
|
+
else if (release) console.error('foafnaut: the bleeding-edge build (newer than the installed release; --latest for the release)');
|
|
177
|
+
}
|
|
161
178
|
if (!existsSync(app)) fail(`no app at ${app}. Run foafnaut --install-latest.`);
|
|
179
|
+
// The bleeding-edge build says what it was built from (BUILD-INFO.json beside the app,
|
|
180
|
+
// written when the build is swapped in); an old build is named, so it is not taken for main.
|
|
181
|
+
if (app === bleedingEdgeApp()) {
|
|
182
|
+
try {
|
|
183
|
+
const info = JSON.parse(readFileSync(join(dirname(app), 'BUILD-INFO.json'), 'utf8'));
|
|
184
|
+
const hours = (Date.now() - Date.parse(info.builtAt)) / 3.6e6;
|
|
185
|
+
console.error(`foafnaut: bleeding edge built from ${info.commit}${info.dirty ? " with uncommitted changes" : ""} at ${info.builtAt} (${hours.toFixed(1)} h ago)` +
|
|
186
|
+
(hours > 6 ? '; it may lack newer work on main' : ''));
|
|
187
|
+
} catch { console.error('foafnaut: bleeding edge has no BUILD-INFO.json; its commit is not known'); }
|
|
188
|
+
}
|
|
162
189
|
if (which) { console.log(app); return; }
|
|
190
|
+
// The app answers for itself. `open` shows no output, so run its program directly.
|
|
191
|
+
if (version) {
|
|
192
|
+
const run = spawnSync(join(dirname(app), 'native-webgpu-html-darwin-arm64'), ['--version'], { encoding: 'utf8' });
|
|
193
|
+
if (run.status === 0) process.stdout.write(run.stdout);
|
|
194
|
+
else console.log(`foafnaut: this build is too old to answer --version (${app})`);
|
|
195
|
+
process.exit(0);
|
|
196
|
+
}
|
|
163
197
|
const args = ['-n', ...(detach ? [] : ['-W']), app, '--args', ...pageArgs(page), ...(verbose ? ['--trace-input'] : []), ...app_, ...extra];
|
|
164
198
|
const log = join(homedir(), 'Library', 'Logs', 'foafnaut', 'trace.jsonl');
|
|
165
199
|
if (verbose) console.error(`foafnaut: app ${app}\nfoafnaut: open ${args.join(' ')}\nfoafnaut: trace ${log}`);
|
package/l4-assets/l4factoidal.js
CHANGED
|
Binary file
|
package/l4-assets/version.json
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
"engine": "lean4",
|
|
3
3
|
"note": "The Lean 4 (L4Factoidal) engine build provenance and claims: the only engine of @factoidal/core since 0.9.0 (issue 713). This committed file is a placeholder. tools/fetch-builds.sh --set web (through tools/stamp-l4-version.mjs) writes the provenance members from the danbri/factoidal-builds release that built the wasm; formal/lean4/Wasm/build-wasm.sh step 9 writes them for a local build. Do not commit a stamped copy.",
|
|
4
4
|
"stamped": true,
|
|
5
|
-
"release": "danbri/factoidal-builds build-
|
|
6
|
-
"releaseRun": "https://github.com/danbri/factoidal-builds/actions/runs/
|
|
7
|
-
"gitSha": "
|
|
8
|
-
"builtAt": "2026-11-
|
|
5
|
+
"release": "danbri/factoidal-builds build-6f57afba14e0-web",
|
|
6
|
+
"releaseRun": "https://github.com/danbri/factoidal-builds/actions/runs/42968513371",
|
|
7
|
+
"gitSha": "6f57afba14e04ed648358e4693790edff69a6cd3",
|
|
8
|
+
"builtAt": "2026-11-11T07:07:29Z",
|
|
9
9
|
"leanToolchain": "leanprover/lean4:v4.33.1",
|
|
10
10
|
"emscripten": "6.0.8",
|
|
11
11
|
"abiVersion": "1",
|
|
12
12
|
"engineVersion": "l4factoidal-wasm 0.2.0 (dispatch: RDF parse/serialize, SPARQL query+update, RDFS/OWL-RL closures, RDFC-1.0, CL/IKL)",
|
|
13
13
|
"version": "l4factoidal-wasm 0.2.0 (dispatch: RDF parse/serialize, SPARQL query+update, RDFS/OWL-RL closures, RDFC-1.0, CL/IKL)",
|
|
14
|
-
"wasmSha256": "
|
|
15
|
-
"wasmBytes":
|
|
14
|
+
"wasmSha256": "2e241365bd7906474320610a65af99641474534fa0fe0e744ed3d9edeef4ee41",
|
|
15
|
+
"wasmBytes": 32703824,
|
|
16
16
|
"claims": {
|
|
17
17
|
"source": "formal/lean4 (L4Factoidal): no sorry, no user axioms, no native_decide; W3C behaviour pinned by build-time #guard",
|
|
18
18
|
"suitesAtBuildSha": "see docs/test-results and formal/lean4/PORT_NOTES.md at gitSha"
|
package/lib/version.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@factoidal/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.1",
|
|
4
4
|
"description": "EXPERIMENTAL UNDER-REVIEWED CODE: do not run public services on it without careful independent expert review. RDF parsing and writing (Turtle, TriG, N-Triples, N-Quads, RDF/XML, JSON-LD), SPARQL 1.1 and 1.2, RDFS and OWL reasoning, SHACL and ShEx validation, CSVW and RML mapping, RDFC-1.0, and SPARQL, LWS and Solid servers, for Node, Deno and browsers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sparql",
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"author": "Dan Brickley <danbri@danbri.org>",
|
|
29
29
|
"repository": {
|
|
30
30
|
"type": "git",
|
|
31
|
-
"url": "git+https://github.com/danbri/factoidal
|
|
31
|
+
"url": "git+https://github.com/danbri/factoidal.git",
|
|
32
|
+
"directory": "npm/factoidal"
|
|
32
33
|
},
|
|
33
34
|
"main": "./index.js",
|
|
34
35
|
"module": "./index.mjs",
|