Skip to content

fix(client,site,gui): model RDF 1.2 triple terms; preload hashed engine wasm on /app - #6661

Merged
jeswr merged 8 commits into
mainfrom
fix/issues-js-site-wasm
Oct 6, 2026
Merged

jeswr merged 8 commits into
mainfrom
fix/issues-js-site-wasm

Conversation

@jeswr

@jeswr jeswr commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

Requested by Jesse · project thread

Summary

Two frontend fixes (JS/TS only, no Rust).

#6044 — RDF 1.2 triple terms in SparqlTerm. The engine emits SPARQL 1.2 JSON {"type":"triple","value":{"subject":…,"predicate":…,"object":…}} (crates/sparq-engine/src/json.rs), but SparqlTerm declared type: "uri"|"literal"|"bnode" with a string value. The site REPL's repl-dataset.ts::termToNT had no triple case, so storeToNQuads wrote the string undefined into the dataset snapshot. (PR #5778, which the issue cites as fixing the GUI writer, was closed unmerged, so the GUI writer had the same gap.)

  • SparqlTerm is now a discriminated union SparqlAtomicTerm | SparqlTripleTerm. This is a breaking type change for @sparq/client (a private workspace package): reading term.value as a string now requires narrowing on type.
  • New shared helpers in @sparq/client: termToNTriples (the single N-Triples/N-Quads term writer; writes <<( s p o )>>, recurses into nested terms, throws on an unknown kind instead of emitting undefined), termValue, and isTripleTerm. formatTerm and CSV/TSV export handle triple terms.
  • The site REPL snapshot writer and the GUI termToNT both delegate to termToNTriples. Every call site that read term.value as a string (site REPL cells/graph, SHACL playground, VC and Solid demos, GUI ODRL tool, SELECT graph, engine-context graph stats) now narrows or uses termValue. The SHACL Compact serialiser throws a clear error for a triple term because SCS cannot express one.
  • Small behaviour change: the GUI writer used to escape a raw tab as \t. The shared writer matches the engine serialiser and leaves it raw, which is still valid N-Triples. Inferred-fact identity compares decoded keys, so it is unaffected.

#2422 — /app downloads the engine wasm twice. The GUI layout preloaded the unhashed /wasm/sparq_wasm_bg.wasm and /wasm/sparq_wasm.js. The runtime loader resolves the engine through public/wasm/wasm-manifest.json to the content-hashed copies (sq-b66fc). On the hosted build the preload never matched, so the browser fetched the multi-MB engine twice. The hints are now resolved at build time with the same resolveWasmAssetWithManifest the loader uses (new gui/app/src/lib/wasm-preload.ts), so the two URLs match by construction. If there is no manifest (dev), both fall back to the unhashed names. I verified it with npm run build:web (stub wasm): out/index.html now has modulepreload /app/wasm/sparq_wasm-<hash>.js and preload /app/wasm/sparq_wasm_bg-<hash>.wasm. I could not load the live site from this sandbox (the proxy refuses it), so the issue's third download is not reproduced. The tier-b bundles (reason/text/rsp) already load lazily on tool open, and nothing else fetches wasm at mount. If a third copy still shows after deploy, it needs a live network trace.

No new frontend dependencies (AGENTS.md's optional-code policy): wasm-preload.ts is server-only and runs at build time.

Closes #6044
Closes #2422

Base gate (always required)

  • cargo build --workspace succeeds. (No Rust changes; left to CI.)
  • cargo clippy --workspace --exclude sparq-py --all-targets -- -D warnings is clean. (No Rust changes; left to CI.)
  • The code this PR touches is formatted (matching the surrounding committed style).
  • Tests pass for every package touched: packages/sparq-client npm test (18 pass, including the new sparql-term.test.mjs), site npm run test:unit (403 pass; the new repl-dataset triple-term test failed before the fix and passes after), gui/app npm run test:unit (126 pass, including the new wasm-preload.test.ts and a triple-term case in inferred-facts.test.ts). tsc --noEmit is clean in all three, and next lint is clean on the touched site/GUI files.

Targeted re-evaluation (check the rows that apply to your change)

  • Public API (pub item / CLI flag / HTTP route / Py/JS binding): @sparq/client is a private workspace package with no skills/ surface doc, so there was no SKILL.md to update. Its tests were re-run.

Ratchets and conventions

  • I did not lower any conformance / perf / coverage ratchet.
  • No hard-coded performance numbers added to markdown.
  • No TODO/FIXME markers added.
  • If this change makes a doc statement false, I updated that doc in the same change.

Security

  • This change does not introduce a security regression.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ScyGGohDhirnbLbrUSnA5n


Generated by Claude Code

claude added 3 commits October 5, 2026 19:10
…erm writer (#6044)

The engine emits SPARQL 1.2 JSON {"type":"triple","value":{subject,predicate,object}}
for an RDF 1.2 triple term, but SparqlTerm declared type uri|literal|bnode with a string
value. site/src/lib/repl-dataset.ts's termToNT switch had no triple case, so
storeToNQuads wrote the string "undefined" into the REPL dataset snapshot.

- SparqlTerm is now SparqlAtomicTerm | SparqlTripleTerm (discriminated on type).
- New shared helpers in @sparq/client: termToNTriples (the one N-Triples/N-Quads term
  writer, <<( s p o )>> for triple terms, throws on an unknown kind), termValue,
  isTripleTerm; formatTerm and CSV/TSV export handle triple terms.
- The site REPL snapshot writer and the GUI termToNT delegate to termToNTriples;
  every call site that read term.value as a string now narrows or uses termValue.
- Regression tests: site repl-dataset (red before, green after), client
  sparql-term suite, GUI inferred-facts key round-trip with a triple term.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ScyGGohDhirnbLbrUSnA5n
…requests (#2422)

The /app layout preloaded /wasm/sparq_wasm_bg.wasm and /wasm/sparq_wasm.js, but the
@sparq/client loader resolves the engine through public/wasm/wasm-manifest.json to the
content-hashed copies. On the hosted build the preload never matched, so the browser
downloaded the engine wasm twice (unused preload + hashed copy).

Resolve the hints at build time through the same resolveWasmAssetWithManifest the loader
uses, so the URLs are identical by construction (falls back to the unhashed names when
no manifest exists, e.g. dev). Verified with build:web: out/index.html now preloads
/app/wasm/sparq_wasm-<hash>.js and /app/wasm/sparq_wasm_bg-<hash>.wasm.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ScyGGohDhirnbLbrUSnA5n
@jeswr jeswr self-assigned this Oct 5, 2026
@jeswr
jeswr marked this pull request as ready for review October 5, 2026 19:34
@jeswr

jeswr commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator Author

🔎 Codex reviewer — gpt-6.1-sol

Automated review by the Codex reviewer (OpenAI gpt-6.1-sol via Codex CLI) of head 54f228442de9. Scope: correctness, security, soundness and design; style nits omitted. A new head gets a fresh review.

Findings

  1. [medium] Triple-term identity misclassifies asserted facts as inferred — gui/app/src/lib/inferred-facts.ts:135
    Triple-term keys compare serialized text rather than decoded components. For an asserted triple term containing a literal with a tab, the shared JavaScript writer emits a raw tab, while the Rust reasoner’s closure writer emits \t. Consequently, entailedFactsFromClosure treats the unchanged asserted fact as newly inferred, inflating the inferred count and listing it in the entailed-facts browser. SPARQL binding keys also fail to match the closure keys.

    Canonicalize triple terms recursively in both key functions using decoded component identities. Add a regression test comparing a JavaScript snapshot against the equivalent Rust closure spelling.

Verdict: Fix the inferred-fact identity regression before merging.

claude added 2 commits October 5, 2026 21:11
Inferred-fact identity keyed RDF 1.2 triple terms by their N-Triples
spelling. The shared JS writer emits a raw TAB inside a literal while the
Rust closure writer emits `\t`, so an asserted triple-term fact was
reported as inferred and SPARQL binding keys missed the closure keys.
Both key functions now build triple-term keys recursively from the
component keys (term type, value, datatype, language, direction), and
the RDF 1.2 base direction is a literal key component on both paths.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ScyGGohDhirnbLbrUSnA5n
Conflict in gui/app/package.json test:unit: kept both sides (main's
import-url and workspace-sources tests plus this branch's wasm-preload test).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ScyGGohDhirnbLbrUSnA5n
@jeswr

jeswr commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator Author

🔎 Codex reviewer — gpt-6.1-sol

Automated review by the Codex reviewer (OpenAI gpt-6.1-sol via Codex CLI) of head 4f71d51a3f4e. Scope: correctness, security, soundness and design; style nits omitted. A new head gets a fresh review.

Findings

  1. [medium] Preserve base direction inside triple terms — packages/sparq-client/src/index.ts:173
    The new recursive writer drops its:dir. For example, a triple term containing {"type":"literal","value":"hi","xml:lang":"en","its:dir":"ltr"} is serialized with "hi"@en instead of "hi"@en--ltr. These are different RDF terms. Because the site and GUI use this writer for snapshots and dataset merges, saving and restoring a newly supported triple term silently changes its identity; terms differing only by direction can collapse.

    Add its:dir to the literal type and preserve it in the language suffix. Add a snapshot round-trip test with a directional literal nested inside a triple term.

Verdict: Fix the RDF identity loss before merging.

claude added 2 commits October 5, 2026 22:14
This PR made site/src/lib/repl-dataset.ts and result-graph.ts import runtime
values (termToNTriples, formatTerm) from @sparq/client. That specifier is only
an alias in tsconfig paths and next.config, so Node failed with
ERR_MODULE_NOT_FOUND and three test files (rdf-import, repl-dataset,
result-graph) did not load. Map the bare specifier to the same source entry in
the test loader.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ScyGGohDhirnbLbrUSnA5n
…d keys

termToNTriples and formatTerm wrote a directional literal such as
{"xml:lang":"en","its:dir":"ltr"} as "hi"@en, both at the top level and
inside a triple term, so site/GUI dataset snapshots changed the term's
identity and literals differing only by direction collapsed. Add its:dir
to SparqlAtomicTerm and emit it as @lang--dir. The site and GUI graph-view
node keys now include the direction too.

Tests: client writer/formatter (top-level, nested, parse round trip), site
rowsToNQuads round trip, GUI termToNT key round trip, and graph-view node
identity in both site and GUI.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ScyGGohDhirnbLbrUSnA5n
@jeswr
jeswr force-pushed the fix/issues-js-site-wasm branch from 737b108 to 763fccf Compare October 5, 2026 22:26
@jeswr

jeswr commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator Author

🔎 Codex reviewer — gpt-6.1-sol

Automated review by the Codex reviewer (OpenAI gpt-6.1-sol via Codex CLI) of head 763fccf0f5de. Scope: correctness, security, soundness and design; style nits omitted. A new head gets a fresh review.

Findings

No correctness, security, soundness or design problems found.

Verdict: The PR is safe to merge as is.

# Conflicts:
#	site/test-support/ts-loader.mjs
@jeswr

jeswr commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator Author

🔎 Codex reviewer — gpt-6.1-sol

Automated review by the Codex reviewer (OpenAI gpt-6.1-sol via Codex CLI) of head a59bc12aa04b. Scope: correctness, security, soundness and design; style nits omitted. A new head gets a fresh review.

Findings

No correctness, security, soundness or design problems found.

Verdict: The PR is safe to merge as is.

jeswr commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator Author

Local ci-fast gate (GitHub Actions outage; Jesse approved local-gate merges). PR head a59bc12a merged with main 3e703be6:

  • clippy -D warnings (core crates, all targets): pass
  • nextest (core crates, ci profile): pass
  • doctests (core crates): pass
  • W3C SPARQL conformance 1229/1229 (ratchet 1229): pass
  • JS suites on the PR head merged with main 0268a63 (no site/, gui/, packages/, js/ or package files changed between that and 3e703be): packages/sparq-client 29/29, site unit 391/391 + lint clean, gui/app unit 145/145 + tsc clean

Squash-merging under the local-gate rule.


Generated by Claude Code

@jeswr
jeswr merged commit 7f1a1f5 into main Oct 6, 2026
27 checks passed
@jeswr
jeswr deleted the fix/issues-js-site-wasm branch October 6, 2026 00:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants