## [6.2.2] - 2026-10-18 ### Migration - Deployments that keep a copy of `docker-compose.yml` need to re-sync it to get the new nginx limits ### Added - Headings with an `id` link to their own section - The load test suite fails a change that makes pages hit the nginx rate limits or send more requests than their baseline ### Changed - nginx queues a page's burst of requests instead of refusing it, and LinkedDataHub's own `/sparql` queries get a higher tier of limits (`NGINX_SPARQL_UI_*`) - The client retries a request answered `429` instead of failing that part of the page - The 3D graph loads a single Three.js bundle - Web-Client dependency bumped to 6.0.7 ### Fixed - A document holding an assistant result that describes the document itself renders again - RDF/XML holding an XML literal that contains RDF/XML is read correctly, so the proxy no longer answers `502` for it (works around apache/jena#4334) - The query block's Open button opens the results as a document - The 3D graph's node expansion and backlinks survive a `429` - `ldh --proxy` keeps the logical host for a proxy URI without a port ## [6.2.1] - 2026-10-17 ### Migration - The assistant is opt-in: set `COMPOSE_PROFILES=assistant` in `.env` to run the `web-algebra` service and offer the assistant in the UI - **BREAKING**: `GENERATE_SITEMAP` is replaced by `ENABLE_SITEMAP`, and the sitemap is opt-in: set `ENABLE_SITEMAP=true`; without it every `/robots.txt` disallows crawling ### Added - Object blocks can read an external value from a dataspace-local description, set with `ldh:graph` (and from the CLI) ### Changed - `/sitemap.xml` is queried per request instead of generated at startup, and past 50,000 documents it is a sitemap index over MD5 buckets of the document URIs (`?bucket=`) - Assistant answers are XHTML (lists, emphasis, code, links to the results), stored as `rdf:XMLLiteral` - Admin responses link to themselves as `lds:admin`, and `ldh admin` commands refuse a base that advertises no such link - jsoup bumped to 1.23.2, fixing memory exhaustion on deeply nested XML namespace declarations ### Fixed - `ldh --proxy` no longer gets `421` from nginx - Embedded resource cards carry the same frame and corners whether typed or not ## [6.2.0] - 2026-10-17 ### Migration - Top-level LDH stylesheet params and variables carry the `ldh:` prefix; overrides of the unprefixed names are ignored - Shared stylesheets query through `ldh:query-result()`; `ac:value-intersect()`/`ac:value-except()` are removed - **BREAKING**: the `$lds:Context` stylesheet param is removed, as it exposed service credentials to stylesheets; use `lds:context()` - Existing dataspaces need an authorization granting public read access to `/context` on both origins (new ones get it seeded) - **BREAKING**: `ldh import csv|rdf` print the URI of the created import instead of the target document's - **BREAKING**: `ldh --proxy` keeps the request's `Host`; pass the plain proxy instead of an `admin.`-prefixed one ### Added - `/context` serves the public description of every dataspace, and end-user responses link their admin application (`lds:admin`) - `ldh admin` commands take the end-user base URI - `ldh admin import ontology --file` installs the namespace ontology from a local file in one step - `ldh import csv|rdf --wait` waits for the import to finish - An assistant that turns a natural-language request into a Web-Algebra plan, runs it on Execute and shows the result as a view - Assistant conversations are stored as `ldh:Chat` blocks in the document - A `web-algebra` service in the stack, acting on the reader's behalf; the assistant writes plans only when the optional `openai_api_key` secret is declared - The agent guide is served at `/AGENTS.md` on every dataspace - An `ldh` Claude Code plugin with skills for building dataspaces, datasets, apps and packages with the CLI - A `GET` on `/sparql` without a query returns the endpoint's SPARQL Service Description ### Changed - An external HTML page with no RDF representation opens in a new tab; the proxy answers `406` instead of `502` - Every chart, map and 3D graph canvas is initialised by one template, wherever it is rendered - External `/sparql` requests are capped per client and in total by nginx (`NGINX_SPARQL_*`), answering `429` beyond the limits - Fuseki restarts on an out-of-memory error instead of running on broken - Web-Client dependency bumped to 6.0.6 ### Fixed - A malformed `Accept`-family header no longer turns an error response into a `500`; an invalid `accept` parameter is refused with `400` - Proxied responses always vary on `Accept`, so a cached RDF response is no longer served to a browser navigation - The proxy serves the boolean result of an upstream `ASK` query - A document imported under another name than its ontology's no longer takes the dataspace down - LDH's ontologies import SPIN, SP and OWL by the IRIs those ontologies declare - Nested property lists of blank nodes lay out for their own cell instead of collapsing to one word per line - Charts no longer fail on datetimes with fractional milliseconds - The version history link announces that it opens a dialog - `rdf:XMLLiteral` values are stored as Exclusive XML Canonicalization again - A block saved through its edit form labels its resource values instead of showing the URI's fragment - An ontology-defined view no longer flashes its own metadata before its results ## [6.1.0] - 2026-10-05 ### Migration - **BREAKING**: `ldh` drops `-b`/`--base`; dataspace-level commands take the base URI as their positional, defaulting to `LDH_BASE` - `Link` relation types are always quoted, e.g. `rel="timemap"` - Runtime settings are kept as a change set in `settings/dataspaces.ru`; an existing `settings/dataspaces.trig` is converted once ### Changed - `ldh add file`, `import csv`, `import rdf` and `push` derive the upload URI from the target document - `Link` headers are built with `jakarta.ws.rs.core.Link`, keeping empty fragments such as `ns#` - Release images are built natively per architecture, with a layer cache - `MAX_CONN_PER_ROUTE`, `MAX_TOTAL_CONN` and `MAX_REQUEST_RETRIES` are passed as system properties - The browser parses and serialises SPARQL with SPARQL.js alone; `SPARQLBuilder.js` is gone ### Fixed - ORCID endpoints derive from `ORCID_ISSUER` (default `https://orcid.org`) instead of the hardwired sandbox - Concurrent writes quoting the same `ETag` no longer both pass `If-Match` - Runtime settings no longer mask later edits to `config/dataspaces.trig` and `config/system.trig` - Restoring a version from a historical view no longer fails `412` - An unwritten versioned document has an empty TimeMap instead of `404` - The History dialog names every author, not only the signed-in agent - The request access dialog names the signed-in agent instead of showing their WebID - A malformed `Accept-Language` is ignored instead of causing a `500` - Views support an aliased first variable, and say when it binds no resources - The file drop overlay clears when a drag leaves the window early - CI builds `linkeddatahub-rdf` from the checkout, so version bumps no longer break the suites ## [6.0.1] - 2026-09-30 ### Added - `linkeddatahub-rdf`, a separately published library of the vocabularies and the document shapes the HTTP API accepts, so the CLI and other clients build against one description of the request bodies ### Changed - Documentation links point at `docs.linkeddatahub.com` rather than the stale static mirror under `atomgraph.github.io` ### Fixed - A burst of server-side renders could deadlock the platform against itself, each render holding a request thread while it waited for a label callback that needed another; a request to this instance's own URL is now bounded by `CLIENT_SELF_REQUEST_TIMEOUT` (5 s) and the image's client pool is sized to the connector (`MAX_CONN_PER_ROUTE=200`, `MAX_TOTAL_CONN=400`) - Every write is held to the constraints of the document as it will be written: a PUT that did not type the document was validated while the document was untyped, a POST validated only its body, and the document form's multipart PUT wrote the form's triples as the graph with no type, container, timestamps or `If-Match` - A package ontology whose descriptor named its document rather than the ontology took its dataspace down, and bundled copies shadowed `packages.linkeddatahub.com`, so every page of the packages dataspace failed with an empty label - Proxied XHTML content left path-absolute `@href`/`@src` references unresolved, so authored media (`/uploads/{sha1}`) resolved against the proxying dataspace's origin and 404'd - A block retrying a `429` re-sent the first request of its chain instead of the one its step made, so a rate-limited chart drew the RDF/XML of its query's document as the results - Chart colours came out empty in Chromium, which enumerates no custom properties: `ldh:css-token()` asks `getComputedStyle` for the property by name instead of reading the enumerated style map - `ldh` uploads `.xsl` and `.xslt` as `text/xsl`; stored as `application/octet-stream`, a stylesheet answered the platform's request with `406` ## [6.0.0] - 2026-09-29 LinkedDataHub has a new interface. The app shell, content blocks, action bar, breadcrumbs, mode lists, type badges, property lists, tables, pager, modals and forms are drawn against a design system vendored into the platform — its tokens, components and typefaces ship with LDH, and `ldh.css` is the single app layer loaded over them, the one file a dataspace stylesheet has to reckon with. IXSL templates drive the dropdowns and modals, the `msi` font draws the icons and the RDFa editor edits `rdf:XMLLiteral`, so jQuery, `bootstrap.js`, WYMEditor and the sprite sheet are gone. Bootstrap 2 is gone, and with it the class vocabulary and the `bs2:` template modes application stylesheets were written against. An override of a `bs2:` mode still compiles and never fires, and the Bootstrap classes are backed by no stylesheet, so the page renders unstyled with no diagnostic — hence the major version. Every namespace LDH defines now lives under `https://w3id.org/atomgraph/linkeddatahub`. The Linked Data Templates namespace is gone — LDH has not been an LDT implementation since the API became the Graph Store Protocol, and the template machinery was dead code — and the ontology that described an "application" now describes a dataspace, the name the rest of the platform has used since 5.1.0. Both are breaking for stored data as well as for stylesheets: every dataspace is typed in the old namespace and every document in the old document-hierarchy one. ### Migration - Legacy Bootstrap buttons (`btn`, `btn-primary`, `btn-large`, …) become the `ac-btn` intent/appearance/size vocabulary — `ac-btn in-primary ap-solid sz-md` - `$ac:langs` and `$ac:lang` stylesheet parameters become the `ac:langs()` and `ac:langs()[1]` functions; an `xsl:with-param` left behind is silently ignored - `pull-left` and `pull-right` have no replacement — the design system lays out with flex and grid - Overrides depending on `bootstrap.js`, jQuery, WYMEditor or the sprite icons need rewriting: IXSL templates drive dropdowns and modals, the `msi` font draws icons, the RDFa editor edits `rdf:XMLLiteral` - Stylesheets extending the removed vocabulary can be diffed against `ldh.css`, the one app layer LDH loads over the vendored design kits - The platform stylesheet is `xsl/layout.xsl`; an `xsl:import` of `xsl/bootstrap/2.3.2/layout.xsl` fails to compile - Every `bs2:` mode takes its design-system component name: `bs2:Row` becomes `ldh:BlockRow`, `bs2:Header` `ac:BlockHeader`, `bs2:PropertyList` `ac:PropertyEditor`, `bs2:Form` `ac:ResourceForm`, `bs2:NavBar` `ac:Header`, `bs2:Lookup` `ldh:Combobox`, `ac:ModeList` `ac:ModeSwitcher`, `ldh:LeftSidebar` `ldh:DataspaceDrawer` - `ldt:base()` becomes `lds:base()` and the `$ldt:base` parameter goes, along with the rest of the `https://www.w3.org/ns/ldt` namespace: the Java `LDT` vocabulary, the bundled `ldt.ttl`, the `rdfs:subClassOf ldt:Application` on the dataspace class, and the dead `ct:`/`c:` sibling namespaces (`lacl:requestAccess` now ranges over `foaf:Document`) - `ldt:ontology`, `ldt:service` and `ldt:base` become `lds:ontology`, `lds:service` and `lds:base`, and the `ldt#ontology` and `ldt#base` `Link` relations with them, so a deployment's `config/dataspaces.trig` and `config/system.trig` each need one prefix where they needed two - `dh:` moves off `www.w3.org` to `https://w3id.org/atomgraph/linkeddatahub/document-hierarchy#` — prefix, class names and `DH.java` unchanged — but every `dh:Container` and `dh:Item` already in a triplestore is typed in the old namespace - `lapp:` becomes `lds:` and `https://w3id.org/atomgraph/linkeddatahub/apps#` becomes `https://w3id.org/atomgraph/linkeddatahub/dataspaces#` in `lds.ttl`, renaming `lapp:Application` → `lds:Dataspace`, `lapp:EndUserApplication` → `lds:EndUserDataspace`, `lapp:AdminApplication` → `lds:AdminDataspace`, the three `*Constructor` individuals to match, and the `lapp:application` `Link` relation naming the dataspace a response belongs to → `lds:dataspace` - `lapp:Dataset`, `lapp:Package` and the remaining properties keep their local names under the new prefix, and `lds:prefix` and `lds:versioningRepository` — used in config and read in Java but never declared — are now declared - `lapp:frontendProxy` is removed with no replacement under the new prefix, having been read by nothing: the frontend (Varnish) proxy used for cache-invalidation BAN requests comes from the `ldhc:frontendProxy` servlet parameter, set from `FRONTEND_PROXY` by `platform/entrypoint.sh`, while `lds:backendProxy` stays, since a proxied `lds:Dataset` is read through it - Stylesheet parameters and functions follow the prefix — `$lapp:origin` → `$lds:origin`, `$lapp:Context` → `$lds:Context`, `lapp:origin()` → `lds:origin()`, `lapp:base()` → `lds:base()`, `lapp:application-description()` → `lds:dataspace-description()` — and a stylesheet still declaring `xmlns:lapp` compiles and silently binds nothing - Java callers move with the model: the `com.atomgraph.linkeddatahub.apps` package is `…dataspaces`, `Application`/`AdminApplication`/`EndUserApplication` are `Dataspace`/`AdminDataspace`/`EndUserDataspace`, `LAPP` is `LDS`, and the `getApplication()`/`getAdminApplication()`/`getEndUserApplication()` accessors are `getDataspace()`/`getAdminDataspace()`/`getEndUserDataspace()` - **There is no data migration.** An existing deployment re-seeds: its dataspace descriptions and every document in its stores are typed in namespaces this release no longer reads - One `fuseki` service replaces `fuseki-admin` and `fuseki-end-user`, holding an end-user and an admin TDB2 dataset per dataspace, declared in `config/fuseki/config.ttl` and persisted under `fuseki//` - Datasets are named after the dataspace origin with the deployment host dropped and the role appended: `end-user`, `admin`, `northwind-traders.demo.end-user`, … - An existing deployment moves its two TDB2 directories under the new dataset names, points every `lds:service` in `config/system.trig` at `http://fuseki:3030//` and merges the two stores' heap and `mem_limit`; `varnish-admin` and `varnish-end-user` stay - Client-side package composition needs the new `sef-compiler` service: a deployment with its own compose file adds it, the platform's `depends_on` on it and the `./sef` bind mount at `/var/www/linkeddatahub/sef` - `SEF_ROOT` and `SEF_COMPILER` are optional; without them packages compose server-side only, as in 5.10.0 - `CLIENT_STYLESHEET` names the client stylesheet the SEF is composed from (default: the platform's `client.xsl`); a deployment bootstrapping its own sets it to that stylesheet's webapp path, which must import the platform's `client.xsl` directly, and its layout prefers `ldh:client-stylesheet()` over its own SEF - A package stylesheet is composed right after `hooks.xsl`: it can specialise an open mode (`ldh:RowHook`, the tree node, the leaves in `imports/values.xsl`) and no longer replaces a sealed mode, a typed rule or a global - The ACL query fails closed on a typeless resource (see Security), so a non-owner request for a URL that does not exist is answered 403 rather than 404 - The `oauth2-login` and `oauth2-authorize` grants move from the admin store to the end-user app's `namespace-ontology.trig.template` as `acl:accessTo` on the login and callback URLs; an existing deployment re-seeds or migrates them - `ldh:container` leaves the ontology, the `ldh:ViewConstructor` form and the view query: a view's Create button determines the container from the view's own solutions - A deployment with its own compose file adds the `./packages` bind mount at `/var/www/linkeddatahub/packages` (`PACKAGE_ROOT`), where copies of imported package stylesheets survive a redeploy - A package ontology imported before this release is materialized under `admin/ontologies/` on the next request and edited there from then on; a later change to the published package reaches the application only after that document is deleted - **BREAKING**: a write to a document that already exists must be conditional — `If-Match` with the document's current `ETag`, or `If-None-Match: *` to create — and is answered `428 Precondition Required` otherwise; a client that wrote unconditionally now reads the tag first, as `ldh` does for you - An entity tag identifies a negotiated variant, so the read supplying the validator must send the same `Accept` as the write quoting it, or the write is refused `412` ### Added - Design system port: the app shell, content blocks, action bar, breadcrumbs, mode lists, type badges, property lists, pager, modals and forms all render the design system's class vocabulary - Vendored design system stylesheets and typefaces, with latin-ext subsets so Latin Extended-A no longer falls back mid-word - A long-form typography layer binds headings, `code`/`samp`/`kbd`, `pre`, `dl`, `blockquote`, `hr` and tables to the design system on the element itself, so `rdf:XMLLiteral` content is dressed everywhere - `ldh.css` is the one app layer loaded after the vendored design kits, replacing the Web-Client Bootstrap stylesheet link - `ac:langs()` returns the reader's accepted languages as deduped primary subtags with an `en` floor — from the writer-supplied parameter on the server, from `navigator.languages` in the browser - `ac:lang-rank()` ranks a value's language against that list, so property values sort by the reader's preference - `Content-Language` on HTML responses, negotiated against the languages the UI translation bundle ships - `Vary: Accept-Language` and language-sensitive entity tags on language-negotiated representations, so a shared cache cannot serve one reader's rendering to another - Every rendered literal declares its own language: lang-tagged values carry their tag, untagged values carry `lang=""`, typed non-string values inherit - Predicate labels, object links and table cells declare the language their label was chosen in - Spanish translations completed - View table columns are sortable, keyed off each column's XSD datatype through `ldh:sort-key()` - Facet toolbar, three-zone pager, chart controls grid, design-system list and grid modes - Empty blocks and empty chart result sets say so through the design system's block-state component - Package ontologies are declared as `owl:imports` instead of grafted, with an imports characterization test - `ldh packages list`, `ldh packages add` and `ldh packages remove`: the catalog read through the Linked Data proxy with imported packages marked, the `ldh:import` declaration written through `PATCH /settings` - `ldh get` reaches a versioned document's Memento roles through `--timemap`, `--version ` and `--timegate` (with `--datetime` in RFC 1123 or ISO 8601, printing the negotiated version's URI) - `ldh put` and `ldh post` take an optional `FILE` argument and recognize the RDF syntax from its extension; `-t/--content-type` stays required on stdin - Every dataspace serves its own `/sitemap.xml`, generated at startup from the public read rules of its own admin service - `/robots.txt` per dataspace: one with a sitemap names it and allows crawling, one without (an admin dataspace, or nothing public) disallows everything - `ldh push` replays a directory into the document tree it maps to — the apps' `update-folder.sh` as a CLI command: RDF files are `PUT` to the document their path spells, `root.ttl` is the target document, other files are uploaded, subdirectories recurse - `ldh push` honours a gitignore-style `.ldhignore`, prints its plan with `--dry-run`, writes one URL per line to stdout and stops at the first failed request; `tests/http/push/` covers the mapping, skips, upload and convergence - `ldh create item` and `ldh create container` take `--primary-topic`, resolved against the created document (`'#this'` for a fragment, an absolute URI for a resource described elsewhere) - The page renders in the colour scheme the reader's browser asks for: `color-scheme: light dark`, differing tokens written once as `light-dark()`, a `` against the white flash, `data-theme` only as a forcing override - A responsive axis: the shell queries `@media` at 768 and 1024, content components query `@container` at 520; the action bar wraps into two tiers below 640 and the page stops overflowing at phone width - A document's markup asserts the graph the same URL serves: value cells carry `@content` where display and lexical form differ, `ldh:DocumentMetadata` carries the document's own statements as `link`/`meta`, an XHTML block says it is `rdf:value`, and `@about` on the root pins the default subject - Package stylesheets reach the client: a wrapper importing `client.xsl` and the imported packages is compiled by `sef-compiler` and published under `/static/xsl/sef/`, announced to the page as a `Link` header; server-side composition is never withheld - `ldh:ContentColumn`, a declared slot in `ldh:ContentBody` that the platform wraps in `.ldh-content-aside` when a package fills it - The taxonomy editor package fills the content column with a concept tree rooted at the scheme and opened down to the concept being read, a polyhierarchical concept under each of its parents - The SKOS ontology gains views for `skos:hasTopConcept` and `skos:inScheme`, an orphan-concepts view, a missing-`inScheme` constraint and `rdf:langString` constructors for every natural-language property; every view carries `ldh:showWhenEmpty false`, so a leaf or root concept renders no empty Narrower or Broader block - Editable package ontologies: an imported package's ontology is materialized verbatim as a document under `admin/ontologies/`, naming it as `foaf:primaryTopic`, so its constructors, constraints and views are edited like the namespace ontology's - A document created from a view's Create button carries `foaf:primaryTopic` and a `dct:title` taken from the topic's label, so it renders with a body and pairs with its topic in list, table and grid rows - Dropping a file anywhere on a document takes it in: a fixed overlay raised on the first file `dragenter` takes the drop, RDF is imported and lands in `ReadMode`, anything else is uploaded as an attachment - An HTTP error document tells the reader what the status means: the block header becomes an inline alert with the sentence `ac:http-error-key()` picks; the error builders move to Web-Client - Every failed client request reports where it was made: `ldh:promise-failure` takes the host element and a headline, `ldh:RenderFailure` picks the shape by host, and `alert()` remains only for a host that has left the page - Keyboard operability: menus take the `role=menu` model with roving focus and Escape restoring the trigger, the RDFa annotation object switch answers the arrow keys, and the pager's previous and next become buttons - `tests/ui/`: a Playwright suite driving the running stack as owner and anonymous, with a console/page-error/alert/4xx collector, a hydration gate on Saxon-JS's listener binding, `ldh`-seeded fixtures and a stale-SEF preflight; its own CI workflow (`make ui-tests`) - Every code sample the platform renders carries a control that copies it: a `pre` in prose, a stored query's `sp:text` and an error's technical detail ### Changed - Entity tags are a SHA-256 digest of the graph URI and the graph's statements, replacing a XOR fold of per-statement hashes; every document's `ETag` changes once on upgrade, so caches revalidate and conditional requests in flight are refused - `HEAD` is answered for any access mode the agent holds rather than `acl:Read` alone, so an agent with `acl:Write` or `acl:Append` can read the validator its writes have to quote; `GET` still needs `acl:Read`, and a non-reader's `HEAD` omits `Last-Modified` - A failed precondition answers with the document's current entity tag, so a client can retry against it without a second read - `POST /clear` takes an optional `uri`: without one it empties the cache and reloads nothing, with one it also purges that URI's proxy caches and reassembles its closure before responding, and `ldh admin clear ontology --ontology` is optional to match - Ontology resolution asks the store before the bundled mappings, so an application's own graph declaring an ontology outranks the copy the platform ships - `Import ontology` and `ldh admin import ontology` keep the vocabulary in the target document beside the derived constructors, with `foaf:primaryTopic` naming it; the document is no longer typed `owl:Ontology` - An imported package's stylesheet is copied once under `PACKAGE_ROOT` and served from the application's own origin under `/static/com/linkeddatahub/packages/`, so what a running instance compiles cannot change under it - **BREAKING**: the bundled package `https://packages.linkeddatahub.com/skos/#this` becomes `https://packages.linkeddatahub.com/editor/taxonomy/#this` and its stylesheet `skos.xsl`; an `ldh:import` naming the old URI resolves to nothing and must be re-declared - **BREAKING**: `ldh` commands regroup by verb, dropping the `bin/` mirror: `create`, `add` and `remove` groups (`ldh create item`, `ldh add view`, `ldh remove block`), `import` for the composite workflows, `admin` for the admin scope; `cli/README.md` maps every old name - **BREAKING**: the `ldh` certificate option is `-c/--cert`, replacing `-f/--cert-file`; `-p/--cert-password` and both `LDH_CERT_*` variables are unchanged - `-c/--cert` accepts the agent's credential as a PKCS12 keystore or as a PEM file with the certificate and its PKCS#8 private key, recognised by content rather than by extension, so the PEM the `bin/` scripts fed `curl -E` now authenticates `ldh` as well; the password is required for a keystore and an encrypted PEM key, and omitted for an unencrypted one - A credential `ldh` cannot use says why instead of blaming the password: a certificate with no private key, a PKCS#1 or SEC1 key with the `openssl pkcs8 -topk8` conversion, and a file that is neither format each report their own error - `` is taken from the `Content-Language` the response carries, so header and document agree by construction - The language a page reports is the first accepted language the UI bundle has, so asking for German no longer puts `lang="de"` on an English page - Published language tags are the shortest the bundle justifies (`en`, not `en-US`) - Supported UI languages are derived from `translations.rdf` rather than declared in `web.xml` - Property lists and table cells order their values by the reader's language instead of hiding the ones that do not match - Object metadata merges on the RDF term rather than the lexical form, so a tagged literal is no longer collapsed into an untagged twin - Document responses pass the accepted languages into entity tag computation, so tags stop colliding across languages - Legacy Bootstrap buttons move to the `ac-btn` intent/appearance/size vocabulary - Block link columns route through the `ldh-drawer`; backlinks and copy-URI split into two placements chosen by what a click does - Charts draw with resolved design tokens; the HTML Table chart is skinned in CSS and fills the block width - One navigation leaves one history entry - CSR failures render through the design system's block states instead of `div.alert.alert-block` - Document-level map and graph modes fill the space between action bar and footer - The map marker info window is the card itself, fitted into the map viewport and panning the least it can to reveal itself - Editing forms already open on the page reconcile with the constructor after a constructor save - The edit pencil on a resource description appears only under `acl:Write` - A view's parallax row becomes a closed-by-default `
` disclosure, and the toolbar states what the query shows (filters, sort, count, mode) - A view's Create button moves out of the view toolbar into the block header beside Copy - The query block runs its stored query on render, results first, with the editor folded behind a toggle in the block head and Run as the one primary action - A view's toolbars and a chart's controls ship collapsed behind a tune button in the block header, with what the query shows stated under the title - A class with no constructor is given one by its first saved property, written into the application's own ontology - A constructor row whose object range is undeclared reads as any resource instead of demanding a class before Save - The add-data items (RDF import, CSV import, …) move into the Actions menu, offered on the Root document and on containers where `acl:Append` applies - XHTML prose blocks render without a header: the drag grip is a hover-surfaced strip on the card's edge, and links, Copy and Edit a corner cluster - An `ldh:Object` block is a chrome-less carrier with a slot bar over the embedded card, so a block has one header - Instance timestamps leave the block chrome and render as ordinary statements - A collection of resources renders as `ul`/`li` and a read-mode property list as `dl`/`dt`/`dd`, the markup the RDFa is carried on - The design system's DataTable gets table layout back (`colgroup` instead of grid tracks, no restated `role` attributes), so the SPARQL results table stops being a second table component - Paging or re-sorting a view no longer collapses the block: the pager lives in its own host and the rows stay standing, dimmed, while the query runs - The floating Edit pill retires; every block carries the edit pencil in its own control cluster, and click-anywhere-to-edit lands on that button - The last hard-coded English leaves the client: the RDFa editor's chrome, the media-type option groups, the 3D graph's panels and the block drag and delete strings read from `translations.rdf` - The RDFa annotation dialog pins the statement's subject and predicate and tabs the rest: an object tab with a Text or Link switch and a subject tab for the `@about` and `@typeof` overrides - Constraint violations decorate the affected fields with the inline message instead of stacking generic alerts - An XHTML block clicked out of unedited restores its snapshot without a save, so no version is minted for a no-op - A press outside a whole-resource edit form dismisses an untouched form and leaves an edited one to Save or Cancel; a press between blocks moves the caret instead of exiting - A grid card no longer offers an edit pin: a card is one projection of a result row, and the whole resource is one hop away - A 3D graph node's link navigates in the page, through the Linked Data proxy for an external URI, instead of opening a browser tab - The document tree's children query is a bounded `CONSTRUCT` of what a node renders, paged by `ldh:tree-page-size` (default 1000) and always including the child leading to the document being opened - The shell carries its landmark roles (`banner`, `main`, `contentinfo`), the tab strip and breadcrumbs are labelled navigations, and the address bar is a `role=search` with a `type=url` input that works without CSR - Constructor instantiation is one shared pipeline behind a dual `ldh:parse-query` (SPARQL.js in the browser, a `ParseQuery` Jena extension on the server), so the sign-up form appears on a direct page load - `sh:name` and `sh:description` labelling moves into LDH, where it outranks `dc:title` and `foaf:name` - Web-Client dependency bumped to 6.0.3, twirl to 2.0.1 - The `Makefile` becomes the file every LDH-based deployment copies, with this repo's own targets in `make/local.mk` and its settings in `make/config.mk` - The UI says dataspace where it said application: the dataspace menu, its settings modal and the access-request text ### Removed - Bootstrap 2: the framework stylesheets, `bootstrap.js`, jQuery, WYMEditor, the sprite icon layer and the `pull-left`/`pull-right` tokens - The Bootstrap 2 class vocabulary that no stylesheet backed - `$ac:langs` and `$ac:lang` parameters, replaced by `ac:langs()` and `ac:langs()[1]` at 36 call sites - `$ldt:base`, `$ldt:ontology`, `$ac:httpHeaders`, `$ac:method`, `$Referer`, `$ac:googleMapsKey`, `$doc-types`, `$main-doc` and `$acl:Agent` — parameters read by nothing or never populated; the `ldt:` prefix leaves the LDH stylesheets - `ldh:container` (see Migration) and the stored `ldh:SelectChildren` query, which the generated tree query replaces - `ac:ConstructMode` from both ontologies, its Create label becoming a catalog entry, and the `graphity.org` typeahead namespace - The `+ Constructor` button, which wrote a constructor into every graph describing the class and amplified itself on every press - The combobox panel that opened on focus, offering a document as the object of its own property - `LocalStylesheetResolver`, the bundled package stylesheet copy and its mapping, `location-mapping.ttl`, and the namespace mappings no ontology imports and no stylesheet uses - `ABS_PATH`: a dataspace serves from its origin's root, and no other value has worked since the admin app moved to a subdomain ### Fixed - A block embedded from another dataspace with `ldh:Object` ran its query against the reader's endpoint, so a chart or view embedded across dataspaces drew nothing; the object container is now stamped with the `sd:endpoint` `Link` header the loaded document answered with, and a block's query runs against the nearest such stamp, else the pane's, the built-in views embedded from the ontology getting no stamp and querying the reader's dataspace as before - `varnish-admin` never invalidated cached SPARQL query results, so a SELECT kept its pre-write answer for the full cache lifetime; writes now ban them as the end-user cache already did - Editing a class's constructors rewrote every constructor in the document at once, and two saved together could overwrite each other; each is saved on its own, in sequence, and one shared with another class is left alone - Clearing an ontology discards every cached graph and assembled closure, not just the keys derived from the URI it was given: a closure caches each URI it imports, so a vocabulary or package ontology kept answering from a document that had been edited or deleted until the container restarted - Clearing also purges every ontology response the admin proxy holds, through a surrogate key stamped on each ontology query, so the closure no longer rebuilds from the responses the clear was meant to discard - Adding a constructor to a class delivered by a package did nothing: the editor now writes to the graph that holds the constructor and accepts the `204` a PATCH answers - Opening a constructor that declares `rdf:langString` and pressing Save silently deleted those rows; the editor reads the object kind from the datatype rather than its namespace - Saving a constructor on a package's ontology document answered 500 and the edit never showed - `Edit constructors` was emitted for every reader and gated on the document being read; it is revealed only where the constructor's own document is writable - A form row's language field sat in a different place on every row - Reloading a page with block requests in flight painted a failure into the document the reader had already left - The sitemap covers every dataspace rather than the root one, and an origin is served its own: one file per host, generated against that dataspace's own services - The sitemap queries carry the service credentials, so a store that requires authentication no longer answers them 401 - Sitemap generation no longer joins the admin service through SPARQL `SERVICE`, which an isolated store refuses; the public read rules are passed as `VALUES`, and a failed generation is logged instead of stopping the platform - The sitemap lists a document once, with its latest date, and only when a public rule of its own dataspace covers it - Concurrent writes to a versioned application silently lost commits: the commit chain was keyed by file path while GitHub locks on the branch head; commits to one branch now share a single chain - A conflicting write is retried with a re-read blob SHA (`MAX_CONFLICT_RETRIES`) instead of being abandoned - A rate-limited commit retried with an empty request body, since `JsonObjectBuilder` yields its object only once - A view block derived from the ontology keeps its URI across reloads: the fragment is hashed from the host resource and the view instead of drawn from `ac:uuid()` - The context dataset's read/write race, a `ConcurrentModificationException` or a torn model on a `PATCH /settings` concurrent with any request: writes are copy-on-write behind a volatile swap and the file is written atomically - The expiring auth caches' check-then-get races, an intermittent login 500 - A `PATCH /settings` adding an `ldh:import` returned 204 but the package ontology never joined the `/ns` closure; the rebuild re-reads the app from the current dataspace model - Importing a package described on this instance answered 504 from a self-resolving loop; a local package's named graph is read from the store, and only foreign URIs are dereferenced - A `DOCTYPE` in an application's `ac:stylesheet` silently disabled package composition; the server parses through the same entity-tolerant reader the client uses - The `/access` endpoint under-reported class-based grants once the ACL query failed closed; it injects the resource's types the way `AuthorizationFilter` does - Document creation by `PUT` authorizes against the parent container's types rather than the wildcard that used to let it through - Asking the SPARQL endpoint for a document (`Accept: text/html`) answered 500 since the document tabs; the tab panel and body match a result set too, and the client's re-fetch keeps the query parameters - The drawer tree fetched an unbounded `DESCRIBE` of every child of a container on every render, so large containers answered 502 — 343 KB now where 20.5 MB was - A facet on a large container never returned: the label lookup's eight-alternative property path under `GRAPH ?labelGraph` scanned the store (24.9 s); binding the predicate with `VALUES` takes 0.055 s - A chart axis bound to a URI-valued variable printed escaped anchor markup; only the Table chart keeps the anchor - Filtering a facet on a language-tagged value matched nothing, since the tag was dropped at every step of the chain - Clearing a facet filter left "No results." over the rows in hand, and the empty state's Clear filters button never rendered - A chart or view created from a query block replaced the query block; it lands beside it now, and the Create buttons require the Properties layout - A chart pane's default series excluded nothing, so any plotted type raised "All series on a given axis must be of the same data type"; the default is the number columns minus the category - An ontology-defined view (`ldh:view`, `ldh:inverseView`) rendered as a nested well that missed every block-body layout rule; it comes out of the same emitter as an authored view - Reordering a block on a document with an embedded block `PATCH`ed the embedded document; the target is the dragged block's document, not the one under the pointer - The first save of prose nobody had edited rewrote the `rdf:XMLLiteral`, adding an `xmlns` to its first child; the wrapper is built as a node - An XHTML literal longer than the JS stack allows (about 6–7K characters) took the client transform down with "too much recursion"; `ldh:bounded-key()` folds long keys into prefix, length and hash - The pencil on a committed object value emptied the row when clicked by accident; the chip is stashed and Escape or an empty focusout restores it - `acl:mode()` threw inside a match pattern, which Saxon-JS discards, so click-to-edit and the Write-gated drag rules were dead until the first document response - A `PATCH` 422 echoed the entire would-be document graph rather than the violating resources, crashing the client on YASQE init; the response carries the violation roots' bounded descriptions - Opening any document on an instance nobody had made public raised a modal alert with raw Saxon text: the tree descent walked the loading placeholder as a child - Navigation that cannot work degrades quietly: a drawer section whose query is refused is removed, breadcrumbs stop at the ancestor that could not be read, and Geo, Latest and search defer their query to the click - A block's affordances gate on the pane's access modes rather than the window's, which answered for whichever dataspace tab was active - A document loaded into a tab drew its chart, map and 3D graph into every other tab's canvases, the canvas lookup having been scoped to the page rather than the pane ### Security - Entity tags were a XOR fold of per-statement hashes, so an agent with `acl:Append` and no `acl:Read` — a dropbox depositor — could add a statement, read the tag off its own write and learn whether that statement was already in a document it may not read; the digest that replaces it makes the difference between two tags say nothing, and covers `/settings` on the same footing - Unconditional writes silently overwrote concurrent ones, because every write reads the graph, changes it in memory and writes it back; requiring a precondition turns a lost update into a `412` the client can retry - A SPARQL `SERVICE` clause in a query to `/sparql` could read the admin store from inside the stack; the triplestores' outbound requests go through an `egress` Squid proxy that refuses loopback, private and link-local destinations - Deployments with their own compose files need the `egress` service and the stores' `JAVA_TOOL_OPTIONS`, including the empty `http.nonProxyHosts` - A `SERVICE` clause in a PATCH update or an import mapping runs in the platform's JVM; it is routed through the egress proxy too (`EGRESS_PROXY`, default `egress:3128`), and with no proxy and `ALLOW_INTERNAL_URLS` unset, in-JVM `SERVICE` is disabled - CSV/RDF imports validate the `ldh:file` source and `spin:query` URIs via `URLValidator` before fetching, closing an SSRF surface on the import path - An unbound `$Type` in the ACL query was a wildcard, so a typeless resource (`/settings`, `/sparql`, a missing URL) matched every `acl:accessToClass` authorization; both ACL queries carry a default `VALUES ?Type { rdfs:Resource }` - Net access after the fix: `/settings` owner-only, `/sparql` authenticated-only, `/ns` and `/access` public, OAuth login public - `MAX_CONTENT_LENGTH` was applied to the deployment's own triplestore as well as to the Linked Data proxy and the imports, so a document whose graph serialized past it could not be read at all and — a `PATCH` reading the graph back before writing it — could no longer be written either; requests to the deployment's own SPARQL, Graph Store and Quad Store services are now exempt, while the proxy, the import sources and WebID dereferencing stay bounded - An oversize response answers `502` rather than `413`, which states that the caller's request body was too large — untrue of a `GET` that carries none — and a chunked response now answers the same status as one carrying a `Content-Length` - The Linked Data proxy made every upstream request with the platform's own client certificate and delegated the caller with `On-Behalf-Of` only when there was one, so a caller with no certificate reached the origin as the secretary, a writer of every end-user dataspace; an anonymous caller now dereferences with a client that carries no certificate, and the origin decides on nobody - A private document of another dataspace was readable through `?uri=` without a certificate - A `PATCH` or `PUT` through `?uri=` without a certificate was applied at the origin - The `acl:mode` and `acl:agent` `Link` headers the proxy forwards described the secretary, so a block embedded from another dataspace drew edit controls for an anonymous reader ### Known limitations - The in-place editor edits tab-group content but cannot create tab groups; that markup is authored via the HTTP API (e.g. `ldh put`) for now - When a result set fits one page the view's total counts the related resources the `DESCRIBE` pulled in, so one row can read "Total results 2" - While a composed SEF key is unpublished every render submits a build, so a persistently unreachable `sef-compiler` costs one attempt per request ## [5.10.0] - 2026-08-30 ### Added - `ldh` command line interface (`cli/`): a standalone picocli/Jena port of the `bin/` HTTP API scripts — one command per script with the same option names, `bin/` subdirectories as nested subcommand groups, PKCS12 WebID keystore authentication, env-var defaults and a shaded executable jar - Every release attaches an `ldh-.tar.gz` archive of the CLI launcher and jar, stamped with the platform version, so using `ldh` needs a Java runtime rather than a source checkout and a Maven build - `make cli` builds the CLI and prints the `PATH` export to run, and `make tests` depends on it — the suite builds its fixtures with `ldh` and used to abort telling you to go build it by hand - `make cli-version` sets `cli/pom.xml` to the platform version, which `release.sh` now runs around both release bumps so the CLI shares the platform's version line instead of its own `1.0.0-SNAPSHOT` - Restore a document to an earlier version from the history modal: the memento is read back and written to the live document, so the restore rolls forward as a new commit and the versions rolled past stay in the TimeMap; gated on `acl:Write`, hidden on the version being viewed, and confirmed first - Version diffs in the history modal: From/To selection navigates to `?version=&diff=` and the diff renders on the document page — `diff-added`/`diff-removed`/`diff-changed` block borders, marked property values, a changed XHTML block stacking its old content above the new, and a color legend - `diff` is display state read from the URL at render time and never sent to the server, so back/forward re-render it and a plain reload degrades to the `?version=` snapshot - Memento TimeGate (`?timegate`): `Accept-Datetime` negotiation answers `302` with the closest Memento in `Location` — smallest absolute distance, ties towards the more recent, most recent when no datetime is asked for — carrying `Vary: accept-datetime` - TimeMaps serialized as `application/link-format` (`TimeMapWriter`), the representation RFC 7089 requires, derived from the PROV description and scoped to the `?timemap` response so ordinary documents answer `406` for it - `http-tests/versioning/` covers the RFC 7089 contract: datetime negotiation (`GET-timegate.sh`), the PROV description of the TimeMap, its link-format serialization and `406` scoping, `rel=original` on Mementos but not the Original Resource, and a zero-padded RFC 1123 `Memento-Datetime` - CLI unit tests for `HttpException.check`, the stdout contract the shell pipelines depend on, and stdin handling in `put`/`patch`, driven against a `com.sun.net.httpserver` stub — 41 tests to 63 - `http-tests/federation/` self-federation suite: one dataspace's client browses, queries and writes against another origin's dataspace through the Linked Data proxy — endpoint discovery from forwarded `Link` headers, the constructor SELECT against the remote `ns`, a graph-scoped SPARQL Update `PATCH` under the origin's `If-Match` precondition, and the unauthenticated negative ### Changed - http-tests build their fixtures with `ldh` instead of the `bin/` HTTP API scripts — 260 invocations of 19 commands per CI run, making the suite the CLI's end-to-end coverage; only the arrange phase moves, every assertion stays a `curl` call - **DEPRECATED**: the `bin/` HTTP API scripts, superseded by `ldh`; the certificate and WebID tooling (`webid-keygen.sh`, `webid-keygen-pem.sh`, `webid-uri.sh`, `webid-modulus.sh`, `server-cert-gen.sh`) talks to no API and is not deprecated - Test authentication splits by consumer: `ldh` reads the PKCS12 keystore, `curl` keeps the PEM derived beside it, and `run.sh` derives the keystore paths from the certificate paths it is given (its four-argument interface is unchanged) - `signup.sh` keeps the keystore it downloads instead of deleting it after the PEM conversion - CI builds `cli/`, runs its unit tests, puts the launcher on `PATH`, and extends the certificate permission fix to the keystores - The CLI JVM starts with `-XX:TieredStopAtLevel=1 -XX:+UseSerialGC`, tuned for startup rather than throughput (0.231s to 0.201s per invocation locally); `LDH_JAVA_OPTS` replaces those defaults rather than appending - `ldh import-ontology` mirrors the rewritten script's scratch-document flow (proxy fetch → PUT into a UUID-slugged scratch document → `construct-constructors` scoped to that graph → append the constructors and an `owl:imports` header to the target → delete the scratch document on every exit path), replacing the `POST` to the deleted `/transform` endpoint - **BREAKING**: TimeMaps are described with PROV-O instead of `http://mementoweb.org/ns#`, which has no published vocabulary — a `prov:Collection` of `prov:Entity` mementos, each `prov:specializationOf` the Original Resource, `prov:generatedAtTime` its commit datetime and `prov:wasRevisionOf` its predecessor; a TimeMap with no mementos `404`s - Memento hypermedia uses the IANA-registered relation types with `type="application/link-format"`, branching per response type: the Original Resource advertises `timemap`/`timegate` and never `original`, a Memento links back to its Original Resource, the TimeMap identifies itself with `self` - The `type` parameter of `Link` response headers is emitted as a quoted-string per RFC 8288 — a media type containing a solidus is not a token - `Memento-Datetime` uses the same zero-padded RFC 1123 formatter as the TimeMap body; `DateTimeFormatter.RFC_1123_DATE_TIME` leaves the day of month unpadded where the RFC 7089 grammar wants `2DIGIT` - TimeMap commit history is paged, bounded at `MAX_COMMIT_PAGES` with truncation logged, instead of stopping at the first 100 commits; mementos are ordered by parsed instant rather than datetime string - TimeGate redirects are `no-store`: `Vary: accept-datetime` gives Varnish an unbounded key space, and the write-time ban matches the document URL, never the `?timegate` URL - Install metadata is applied by one SPARQL update per app with the existence check in the `WHERE` clause, replacing `enrich_document_metadata`'s blind append - The owner and secretary authorizations get stable slugs (`acl/authorizations/owner-webid/`, `acl/authorizations/secretary-webid/`) instead of a fresh UUID per dataset load, and the test owner fixture points at the same slug - CI gives each run its own versioning branch, branched from `main` and deleted afterwards - Editing forms already open on the page reconcile with the constructor after a constructor save: missing properties get `bs2:FormControl` groups appended, value-less groups the constructor no longer asserts are removed, controls holding entered values and `rdf:type` controls are untouched, and a failed fetch leaves the form as it was - Snapshot params (`?version`/`?timemap`) deliberately do not survive a modal document save - Constructor instances are instantiated client-side: one SPARQL SELECT fetches the type set's `spin:constructor` queries (subclass closure, deduplicated) and their CONSTRUCT templates are expanded onto a single instance typed with all the resource's classes — same-range duplicate properties collapse, and a constructor must have an empty `WHERE` clause to be client-instantiable - Server-rendered edit forms show data properties only; the client re-render supplies the constructor controls (`ldh:construct-forClass` is an empty stub under SAXON, the `ac:construct` stub pattern) - "Add data" accepts foreign target documents: the proxied append carries the delegated agent identity, so the target instance's access control arbitrates and its refusal surfaces as the form error - "Import ontology" keeps the local-target requirement because its constructor derivation is scoped to the local `/sparql` endpoint - Packages are declarative: an application imports a package with a single ` ldh:import ` triple in its dataspace settings — in `config/dataspaces.trig` (permanent, applied on restart) or live via `PATCH /settings` (effective on the next request, no restart) - A package's components are discovered from its Linked Data description (bundled ones resolve from the classpath) and its stylesheet is composed into the application stylesheet in memory at compile time, per dataspace — nothing is copied into the webapp and `/static/` is never modified - The available-package catalog is data at the registry URI `https://packages.linkeddatahub.com/` (bundled one-entry copy listing the SKOS package, served through the Linked Data proxy's mapped-URI resolution until the registry is live) - The application settings modal lists the available packages with a per-row Installed checkbox serialized as an RDF/POST `ldh:import` input, and the form's single Save submits settings and package imports as one PATCH through `/settings` - The package's ontology joins the application's ontology imports closure automatically, derived from `ldh:import` at ontology-load time: each package ontology is assembled as its own `owl:imports` closure and added as a union member, with no `owl:imports` triple materialized anywhere - A `/settings` PATCH evicts the assembled closure, so package installs and uninstalls take effect on the next request; a package ontology that fails to load is skipped - "Import ontology" persists only the derived annotation ontology — generated class constructors plus `owl:imports` of the canonical vocabulary URI, the artifact shape a package ontology ships; the fetched vocabulary is scaffolding in a scratch document deleted on every exit path - The vocabulary resolves live through the graph repository, so constructors derived for bundled vocabularies now reach the ontology closure — the shipped file previously shadowed the local copy holding them - The annotation document is wired into the namespace ontology itself (`add-ontology-import.sh --import `) - XSLT compilation resolves `xsl:import` URLs under an application origin's `/static/` path to local webapp files (`LocalStylesheetResolver`) instead of HTTPS round-trips through nginx, and modules imported via different routes deduplicate under one URL - `ac:stylesheet` values in `config/dataspaces.trig` are absolute URLs on the application's own origin (previously relative, absolutized against the root base URI) ### Removed - **BREAKING**: `/ns?forClass=` constructed-instance responses — the client-side instantiation is the only consumer path; the `Namespace` endpoint serves SPARQL queries and the raw ontology graph only - **BREAKING**: `packages/install` and `packages/uninstall` endpoints, the admin `packages/` container and their ACL entries, the package Actions UI (`imports/lapp.xsl`), and `bin/admin/packages/` CLI scripts — the `ldh:import` declaration itself is the installation. Packages installed with earlier releases were webapp-file mutations and do not carry over: re-declare them with `ldh:import` - `XSLTMasterUpdater`, `Package.getStylesheetPath()` and the bundled `packages/skos/layout.xsl` copy — dead now that the webapp-file installation path is gone - `MEM` vocabulary class — no consumers left once the Memento namespace gave way to PROV-O and the IANA relation types - Vestigial `forClass` URL params: the `add-constructor` button `@href`s (the onclick reads `@data-for-class`), the chart form's `@action` (`btn-save-chart` `PATCH`es the current document), `ldh:build-query`'s `forClass` arity, and `CacheInvalidationFilter`'s unreachable ban branch - `ldh:NoOp`, replaced by the constructor-sync fan-out on `ldh:ClearNamespace` - `ProvenanceFilter` — a 2021 skeleton whose registration was commented out since it was written; the PROV-O provenance sidecar (P2.3) will not start from its graph-per-request shape ### Fixed - `ldh --version` reported a hardcoded `1.0.0-SNAPSHOT` regardless of the build; it now reads `Implementation-Version` back from the jar manifest - `--help` was unrecognized on every `ldh` subcommand — `mixinStandardHelpOptions` only reaches the root command, so the option now lives in `BaseCommand` and in a new `CommandGroup` base that the five subcommand groups extend in place of their duplicated `@Spec`/`call()` pairs - GraphMode rendering of `ldh:Object` blocks crashed with a cardinality error: the `bs2:Row` branch applied `bs2:Graph` without the required `canvas-id` param; the 3D force graph now initializes after the row is rendered - Constructor edits never surfaced in instance forms: the callback cleared the ontology derived from the class' `rdfs:isDefinedBy` and left the annotation graph cached, so it now clears the document its `PATCH` just updated (`ac:document-uri($constructor-uri)`) - Modal document save re-rendered in the default layout mode instead of the active one; the post-save navigation now carries the URL's `?mode=`, guarded to same-document reloads - Restore buttons were missing when History was opened from a `?version=` view: `acl:mode()` reflects the snapshot response, which `ResponseHeadersFilter` caps at `acl:Read`, so the modal now `HEAD`s the live document and reads its `acl:mode` links - Documents accumulated a `dct:created` value per container recreate — 201 on one development instance — because the dataset load appends rather than replaces; documents that already carry one keep the timestamp they have - Every dataset load left another copy of the owner's and secretary's authorizations behind, 60 on one development instance; the stable slugs make re-running the load a no-op - Concurrent CI runs silently lost versioning commits to the GitHub Contents API's optimistic lock on the branch head, timing out unrelated tests waiting for them - `create-file.sh` carried two lines on stdout once `ldh add-file` printed the content-addressed upload URI, mangling the URL `GET-file-304.sh` captures from it - `cache: 'maven'` failed the CLI job outright — `setup-java` runs before the checkout in that workflow, so there was no `pom.xml` to hash - Constructor-supplied inputs went nondeterministically missing (the intermittently vanishing app-settings Description field) and multi-range predicates raised cardinality errors — both fixed by the client-side instantiation - Modal violation re-renders harvested `property-uris` from everything except the edited resource, degrading property labels to their local-name fallback; the violation/response machinery no longer pollutes the `property-uris`/`object-uris` harvests - The `required` function on the modal violation context is stamped per flow by the response handlers, matching each flow's initial-render chain — the shared Container/Item test disagreed with the app-settings chain - The Linked Data proxy stamped re-serialization validators instead of forwarding the origin's `ETag`/`Last-Modified`, dropped conditional request headers (`If-Match`, `If-None-Match`, `If-Modified-Since`, `If-Unmodified-Since`), and parsed the origin's `4xx`/`5xx` error bodies as RDF (turning a proxied `412`/`403` into a `502`) — preconditioned and access-controlled writes against proxied documents are now evaluated at the origin and their real status reaches the client ## [5.9.1] - 2026-08-19 ### Added - Inline creation in views: views carrying the new `ldh:container` metadata render a Create button that creates a linked instance in that container (#351) - `ldh:showWhenEmpty` hides a view while its query returns no results ### Changed - Bundled SKOS package view queries tolerate untagged `skos:prefLabel` literals ### Fixed - Type/class typeaheads dropped `owl:Class`-typed classes under inference-free ontology serving - SKOS package URLs moved to LinkedDataHub-Apps `master` after the `develop` branch was deleted (package installs 404ed) ## [5.9.0] - 2026-08-18 ### Added - GitHub-backed graph versioning: writes mirror each document into a repository as a sorted N-Triples commit authored with the agent's WebID (per-dataspace `lapp:versioningRepository` → `doap:GitRepository` in `system.trig`; token as `a:authToken` in `secrets/credentials.trig`) (#350) - `GET ?version=` serves a historical version with `Memento-Datetime`, a SHA `ETag`, and immutable `Cache-Control` - `GET ?timemap` serves the version history as an RFC 7089 TimeMap in RDF (Memento vocabulary), advertised via a `Link rel=memento:timemap` header - History modal opened from the document's action-bar timestamp: version table with agent attribution, current version marked - Historical-version notice banner with a link back to the current version - Gated `http-tests/versioning/` suite (runs when `VERSIONING_TEST_REPO` and `GITHUB_TOKEN` are set) ### Changed - Historical version and TimeMap views are read-only: only `acl:Read` advertised in `Link` headers, write methods rejected with `405` - `version`/`timemap` query params ride the client-side RDF re-fetch and survive URL rebuilds, so snapshot pages render fully (content blocks included) - New hostname-verified HTTP client factory for public-web API hosts (the existing clients disable hostname verification against a truststore that includes public CAs) ### Fixed - Entrypoint parses the credentials secret from a `.trig`-suffixed copy — the extensionless secret mount made `riot` fail silently and killed startup whenever the `credentials` secret was enabled ## [5.8.0] - 2026-08-17 ### Added - HTTP tests pinning graph-scoped queries via SPARQL Protocol dataset parameters on `/sparql` (`default-graph-uri=` scopes a query to one document's graph; overrides `FROM`; `GRAPH` patterns match nothing) — the read-side counterpart of graph-scoped `PATCH`, no server changes needed ### Changed - Application ontologies resolved as a native ontapi `owl:imports` union graph (cached per ontology URI), no RDFS inference — replaces the manually flattened, RDFS-materialized model - "Import ontology" orchestrated client-side: proxy fetch → GSP append of the raw ontology to the target document → `construct-constructors` CONSTRUCT scoped to that document's graph via the SPARQL Protocol dataset specification (`default-graph-uri` on `/sparql`) → GSP append of the result; `import-ontology.sh` rewritten curl-only (drops the `turtle` CLI dependency) - `Namespace` no-query GET serves the raw ontology graph from the shared repository instead of rebuilding one per request - View controls (order-by dropdown, facet headers, parallax, `rdf:type` facet values) resolve term labels via `/ns` instead of per-term Linked Data proxy fetches (#340) - Dependency bumps: Jersey 3.1.12, csv2rdf 2.2.1, java-jwt 4.6.0, jsoup 1.23.1, central-publishing-maven-plugin 0.11.0, frontend-maven-plugin 2.0.2 - GitHub Actions bumps: checkout v7, setup-java v5, docker build/push actions v4 - HTTP tests count SPARQL results with `xmllint` instead of `grep -c` (#348); flaky DBpedia proxy test replaced with a local cross-origin query - **BREAKING**: "Add data" and "Generate containers" orchestrated client-side over the Graph Store Protocol (POST-append via `?uri=` proxy; per-class container PUT fan-out embedding the view as `ldh:Object` → `rdf:value` → `ldh:View`), replacing the `/add` and `/generate` endpoints ### Fixed - Raw ontology graphs no longer leak inferred `rdf:type rdfs:Resource` that broke View block rendering via multi-token `@typeof` - Missing labels on class-valued objects (e.g. `rdf:type`): `/ns` ontology labels merged into object-metadata, SSR and CSR (#345) ### Removed - Linked Data proxy no longer serves ontology terms (now dumb transport: bundled-vocab file cache + SSRF-checked external fetch); ontology terms served by `/ns` - **BREAKING**: `/add` and `/generate` server-side endpoints (`Add`/`Generate` JAX-RS resources), superseded by the client-orchestrated writes; removes their server-side fetch/SSRF surface (LNK-002) - **BREAKING**: `/transform` endpoint (`Transform` JAX-RS resource), superseded by the client-orchestrated "Import ontology" flow — the last bespoke server-side fetch/SSRF surface (LNK-002) is gone ## [5.7.1] - 2026-08-06 ### Changed - RDFa editor: annotation overlay rebuilt on demand (`rdfa-editor/overlay.xsl`) ### Fixed - RDFa editor: exit-canvas save fired a duplicate PATCH that 412'd on a stale `If-Match`; `onfocusout` is now the sole save trigger - RDFa editor: block drag-handle visibility (bootstrap.css collision) and dropping blocks inside a LinkedDataHub document - RDFa editor: annotation overlay dismissed on teardown; caret lands at the clicked word ## [5.7.0] - 2026-08-02 ### Changed - WYMEditor replaced with the RDFa editor for `rdf:XMLLiteral` editing (#336) - RDFa annotation dialog controls grouped into Subject and Object fieldsets - Entity-inlining and SEF compilation moved to the pre-integration-test Maven phase - Dependency and Maven plugin upgrades: slf4j-reload4j 2.0.18, Mockito 5.23.0, Surefire 3.5.6, maven-compiler-plugin 3.15.0, frontend-maven-plugin 2.0.0, maven-gpg-plugin 3.2.8 ### Fixed - Static asset URIs resolve against the shell-origin `$lapp:origin`, fixing CORS-blocked fetches on proxied dataspaces ## [5.6.1] - 2026-07-26 ### Security - SSRF: `URLValidator` blocks wildcard/any-local (`0.0.0.0`, `::`) addresses and checks every resolved address; loopback stays reachable, `ALLOW_INTERNAL_URLS` remains the escape hatch (LNK-003/LNK-009) - XXE: `SecureXML` hardened parser factories disable DTDs/external entities in `XSLTMasterUpdater` and `ldh:send-request` responses (LNK-005 residual) - Upgraded `java-jwt` 3.19.4 → 4.5.2 on the OAuth2/OIDC verification path - Documented the pinned-truststore invariant behind disabled hostname verification on internal HTTP clients ### Added - Unit tests for `AuthorizationFilter`'s HTTP-method → ACL access-mode contract, mode lookup, and owner grant - Loopback/wildcard `URLValidator` tests; JWKS-based `JWTVerifier` tests - `AGENTS.md`: agent-facing guide to driving a running instance's HTTP API - Dependabot config (`.github/dependabot.yml`) for Maven, the Docker base image, and GitHub Actions ### Changed - Cache TTLs configurable via `WEBID_CACHE_EXPIRATION` / `JWKS_CACHE_EXPIRATION` (default 86400s), bounding how long a revoked WebID stays authenticated - CSR label rendering consolidated on Web-Client per-vocab templates; retired `$ldt:lang` for `$ac:langs` from `navigator.languages`; added client-side `ac:uuid()` (#323) - Web-Client dependency bumped to 5.0.3 ### Fixed - View queries with a `FROM`/`FROM NAMED` clause returned `400`; `ldh:wrap-describe` now hoists the clause to the outer `DESCRIBE` - Duplicate bilingual widget headings when the browser language matched neither value (#323) - Language badge leaking into view headings (e.g. `enCurrent members`) - `acl:mode()` went stale on fetch-less tab-pane switches; flags now re-synced from the activated pane - Modal live-search debounce logged an unsupported-scheme fetch from `document="ixsl:page()"` - Left-sidebar flyout clipped on short viewports; now scrolls ## [5.6.0] - 2026-07-08 ### Added - `OntologyRepository` (renamed from `OntologyModelGetter`): a bounded, evicting ontology cache that serves bundled vocabularies without querying SPARQL; per-app creation is thread-safe and each ontology is materialized once under a lock (`owl:imports` closure flattened manually, then RDFS-inferred and materialized). Seeded ad-hoc in `Namespace` - Blank nodes skolemized after `PATCH` (+ HTTP test) - Unit tests for the graph store, RDF import streaming, and proxied WebID auth - Regression tests for Varnish cache poisoning (On-Behalf-Of delegation, and the Client-Cert + RDF path) - HTTP test for `?forClass` namespace requests against `rdfs:Class` ### Changed - Migrated 19 vocabularies off the deprecated-for-removal `org.apache.jena.ontology` API to `org.apache.jena.ontapi` (Jena 6 ont-api), using the `OWL2_FULL_MEM` profile; document/LDT/ACL classes normalized to `owl:Class`; SPIN constraints run against twirl's SPIN personality so LDH no longer registers SPIN globally (#316) - Dependency upgrades: Guava 33.6.0, twirl 2.0.0, Web-Client 5.0.1, jsoup 1.22.2, JUnit 6.1.0, Mockito 5.18.0; adapt to the `StylesheetResolver(Client)` constructor - Proxy namespace `DESCRIBE` query built with `ParameterizedSparqlString` - Forward upstream validators for `HEAD` proxy responses - `active` token deferred on new tab panes to `ldh:ActivateTab` - Progress cursor shown while 3D graph requests are in flight - Fuseki memory limits, heap, and restart policy set in `docker-compose.yml` ### Fixed - Varnish cache poisoning: bypass the `varnish-frontend` cache for any Client-Cert request outside `/static/` and for On-Behalf-Of requests ### Removed - Dead `CACHE_MODEL_LOADS` (`cacheModelLoads`) and `preemptiveAuth` config flags; `ENV CACHE_MODEL_LOADS` dropped from the `Dockerfile` - Unnecessary frontend cache purge; unused imports ## [5.5.4] - 2026-06-29 ### Fixed - HTTP client connection-pool exhaustion: the pooled clients had no socket/read timeout (Apache default `SO_TIMEOUT` = 0 = infinite), so a stalled backend read held its leased connection forever and the route eventually pinned at max, wedging the listener. Added socket timeout, connect timeout, connection time-to-live and validate-after-inactivity to the pooled clients, configurable via the `CLIENT_SOCKET_TIMEOUT`, `CLIENT_CONNECT_TIMEOUT`, `CLIENT_CONNECTION_TIME_TO_LIVE` and `CLIENT_VALIDATE_AFTER_INACTIVITY` env vars (`CATALINA_OPTS` system properties), with image defaults in the `Dockerfile` - `SignUp`: PublicKey/Agent/Authorization client `Response`s are now closed on all code paths (connection leak on the signup path) ## [5.5.3] - 2026-06-09 ### Changed - Dependency hygiene: exclude duplicate `jakarta.json` from `jena-arq`, align `slf4j-reload4j` to 2.0.17, drop unused `tomcat-coyote` ### Fixed - `JSONGRDDLFilter` response-side gate scoped per subclass (instance-level property key) with defensive `isApplicable` re-check; prevents cross-fire when multiple subclasses share the client filter chain ## [5.5.2] - 2026-06-09 ### Changed - Left sidebar moved to CSR: `ldh:LeftSidebar` emits its own `left-sidebar` wrapper and is injected via `ixsl:append-content`; SSR `bs2:DocumentTree` placeholder dropped ## [5.5.1] - 2026-06-08 ### Changed - Consolidated href parsing into `ldh:parse-href#1` (inverse of `ldh:href#3`), shared by all navigation handlers ### Fixed - Cross-origin URL query strings stripped on navigation (e.g. `youtube.com/watch?v=...`) - ContentMode block drag-drop reordering broken after the tab/`document-body` DOM flatten ## [5.5.0] - 2026-06-07 ### Added - HTML+JSON-LD reader: extracts every `