From 147f26317b1863efa49480f31a39fab7bb69f421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Sat, 29 Aug 2026 21:49:13 +0200 Subject: [PATCH 1/5] Give the persons listing filter and sort columns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit app/persons.ttl — replace the stock ldh:ChildrenView with an ldh:View + sp:Select in ac:GridMode. LDH derives a view's facet pills and sort options from the BGP triples whose subject is the query's FIRST projected variable and whose object is a variable, so the focus has to be the person entity (?person = {doc}#this) rather than the dh:Item document — that is where foaf:givenName / foaf:familyName / foaf:gender / ltlod:nominatedBy sit. The stock ChildrenView projects the document and therefore offers no person columns at all. Only ?person is projected (LDH wraps the SELECT into DESCRIBE *, so every projected term becomes a rendered card), sorted by family then given name. app/ns.ttl — carry the same columns into the person-valued 1:N views (:CurrentMembers, :FormerMembers, :NominatedMembers, :MembersElectedHere) so every person listing filters and sorts alike, and label the faceted predicates (a predicate with no rdfs:label in {base}ns degrades to its local name in the pill). files/client.xsl — override ldh:bgp-value-counts, without which the pills are unusable. To label a filter pill's values LDH appends an OPTIONAL with a 7-way alternative property path, and ARQ does not resolve an alternative path through the quad indexes inside a GRAPH block: it walks all ~117k named graphs, ~104 s per pill (a 500 in the browser). The override emits the equivalent variable predicate + FILTER (?labelProp IN (…)) — same predicates in the same order, so SAMPLE(?label) is unchanged — at ~0.1 s per pill. Also tidies the :Memberships table client-side (role / organization / memberDuring only, no anchor column, the interval rendered as its dct:title period text). Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01PoiMWyMGtWZos9wWoVUCwg --- CLAUDE.md | 38 +++++++++++++-- README.md | 12 +++-- app/ns.ttl | 76 +++++++++++++++++++++++------ app/persons.ttl | 61 ++++++++++++++++++++--- files/client.xsl | 123 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 280 insertions(+), 30 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 725645b..081cac2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -103,7 +103,9 @@ since the rows differ in the image cell. Containers and taxonomy scheme docs come from `app/` (one Turtle file per container, PUT via LDH CLI by `make install`); each carries an `rdf:_1 <#select-children>` → `ldh:Object`/`ldh:ChildrenView` block, without - which LDH renders no children listing at all. + which LDH renders no children listing at all. `app/persons.ttl` swaps the stock + `ldh:ChildrenView` for its own `ldh:View` + `sp:Select` (`ac:GridMode`) so the + listing gets person filter/sort columns — see the next bullet. - **1:N entity views**: cross-entity listings (county → municipalities, committee → members, party → nominees) are `ldh:inverseView` definitions in `app/ns.ttl` (the LDH namespace ontology, northwind-traders style): ` @@ -114,6 +116,29 @@ since the rows differ in the image cell. `cv:PublicOrganisation` for the Seimas). `app/import-ns.sh` (called by `make install`) resets + POSTs the ontology into the admin `ontologies/namespace/` document (served at `{base}ns`) and evicts the server-side ontology cache. +- **View filter/sort columns**: LDH builds a view's facet pills and its sort + dropdown from the BGP triples whose **subject is the query's FIRST projected + variable** and whose **object is a variable** (`ldh:RenderFacets` and + `$var-predicates` in LDH's `client/block/view.xsl`). Those variables must stay + **unprojected**: LDH wraps the SELECT into `DESCRIBE *`, so every projected term + becomes a rendered card/row — hence the "project a single entity type" rule in + `app/root.ttl`. Consequences for authoring a view query: + - the focus variable has to be the entity carrying the columns (`?person` = + `{doc}#this`, never the `dh:Item` document — the attributes hang off `#this`); + - a column is only a *filter* pill when its predicate is a plain URI (a property + path still yields a sort option, labelled with the bare variable name); + - column labels come from the `{base}ns` ontology, so every faceted predicate + needs an `rdfs:label` in `app/ns.ttl`; + - `ORDER BY` sets the initial sort; a second condition is the tie-breaker. Both + are re-applied client-side over the (unordered) DESCRIBEd graph; + - opening a pill makes LDH append a label lookup to the facet-value-count query + that ARQ cannot index inside a `GRAPH` block (~104 s per pill → 500); + `files/client.xsl` rewrites it — see the XSLT overrides below. Keep writing + `GRAPH ?graph` in view queries. + Person listings — the `persons/` container view and the `:CurrentMembers`, + `:FormerMembers`, `:NominatedMembers`, `:MembersElectedHere` ontology views — + therefore project only `?person` and carry `foaf:givenName`/`foaf:familyName`/ + `foaf:gender`/`ltlod:nominatedBy` triples, sorted by family then given name. - **XSLT overrides** (`files/`): custom Saxon-JS templates that change how LDH renders, layered over the stock stylesheets — the linkeddatahub.com pattern. Three files, all mounted over the running image's `ROOT/static` for the @@ -128,8 +153,15 @@ since the rows differ in the image cell. …)`). Imported by **both** client.xsl and layout.xsl so the blocks are omitted server-side *and* client-side — server-only would still let CSR re-render them (flash); client-only lets the server render them first (flash). - - `files/client.xsl` — imports `client.xsl` + `overrides.xsl`; adds the - **client-only** `:Memberships` table tidy (restrict columns to role / + - `files/client.xsl` — imports `client.xsl` + `overrides.xsl`; overrides the + stock `ldh:bgp-value-counts` **facet label lookup**: to label a filter pill's + values LDH appends `OPTIONAL { { ?value <7-way alt path> ?label } UNION + { GRAPH ?g { … } } }`, and ARQ does not resolve an alternative path through the + quad indexes inside a `GRAPH` block — it walks the ~117k named graphs, so one + pill took ~104 s (a 500 in the browser). The override emits the equivalent + `?value ?labelProp ?label` + `FILTER (?labelProp IN (…))` (same predicates, + same order, so `SAMPLE(?label)` is unchanged) — ~0.1 s per pill. It also adds + the **client-only** `:Memberships` table tidy (restrict columns to role / organization / memberDuring, drop the anchor column, render the `memberDuring` cell as the interval's `dct:title` period *text* not a link). Compiled to the SEF; the view table is client-rendered so these needn't run server-side. diff --git a/README.md b/README.md index a66c2eb..0257656 100644 --- a/README.md +++ b/README.md @@ -184,10 +184,14 @@ Duomenų struktūra kuriama dviem lygiais: schemos iš `app/` katalogo dokumentas po dokumento **per LDH CLI** (`put.sh`, kaip [LinkedDataHub-Apps](https://github.com/AtomGraph/LinkedDataHub-Apps) projektuose) — taip dokumentai gauna `ldh:ChildrenView` bloką, dėl kurio - konteinerių puslapiai rodo vaikų sąrašus. Kartu įdiegiama vardų erdvės - ontologija (`app/ns.ttl`) su 1:N rodiniais (`ldh:inverseView`): apskrities - puslapis rodo jos savivaldybes, komiteto — dabartinius ir buvusius narius, - Seimo — padalinius, partijos — jos iškeltus Seimo narius. Reikia šalia + konteinerių puslapiai rodo vaikų sąrašus. Asmenų konteineris + (`app/persons.ttl`) vietoj jo turi savo rodinį su užklausa: Seimo nariai + rodomi nuotraukų tinkleliu, kurį galima filtruoti ir rikiuoti pagal vardą, + pavardę, lytį ir iškėlusią partiją. Kartu įdiegiama vardų erdvės ontologija + (`app/ns.ttl`) su 1:N rodiniais (`ldh:inverseView`): apskrities puslapis rodo + jos savivaldybes, komiteto — dabartinius ir buvusius narius, Seimo — + padalinius, partijos — jos iškeltus Seimo narius; visi asmenų sąrašai turi tuos + pačius filtrus ir rikiavimą kaip asmenų konteineris. Reikia šalia išklonintos [LinkedDataHub](https://github.com/AtomGraph/LinkedDataHub) repozitorijos (`../LinkedDataHub`, keičiama per `make install LDH_HOME=…`). - **Duomenys** (`make load`): ETL rinkiniai — vien `dh:Item` dokumentai su diff --git a/app/ns.ttl b/app/ns.ttl index 211abf0..7ca9116 100644 --- a/app/ns.ttl +++ b/app/ns.ttl @@ -15,6 +15,25 @@ # image-bearing persons (foaf:depiction portraits from lrs.lt) use ac:GridMode so # LDH lays them out as a thumbnail grid (bs2:ContainerGrid → foaf:depiction per # card). Imageless targets (Seimas org-units) keep the default list. +# +# Filter/sort columns: LDH turns the BGP triples whose subject is the query's +# FIRST projected variable — and whose object is a variable — into the view's +# facet pills and sort-dropdown options. Those variables stay UNPROJECTED: LDH +# wraps the SELECT into DESCRIBE *, so anything else in the projection would be +# rendered as its own card. So the person views (:CurrentMembers, :FormerMembers, +# :NominatedMembers, :MembersElectedHere, and the persons/ container view in +# app/persons.ttl) project ?person alone and carry foaf:givenName / +# foaf:familyName / foaf:gender — plus ltlod:nominatedBy wherever the party is not +# already pinned by $about — then ORDER BY family, given name (the first ORDER BY +# condition is the initial sort, the second the tie-breaker). Facet and sort +# labels resolve against the property labels below, so a column predicate without +# an rdfs:label here degrades to its local name. +# +# Those pills only work because files/client.xsl overrides LDH's +# ldh:bgp-value-counts label lookup: its alternative property path is not indexed +# by ARQ inside a GRAPH block and scans all ~117k named graphs (~104 s per pill); +# rewritten to a variable predicate + FILTER IN it is ~0.1 s. Keep the GRAPH ?graph +# form in these queries — the override, not the query shape, is what pays for it. @prefix : <#> . @prefix ldh: . @@ -366,9 +385,10 @@ ORDER BY ?org rdfs:isDefinedBy : . # Memberships are n-ary org:Membership fragments in the person's graph; the -# interval lives in the same graph, so one GRAPH block suffices. Current -# membership = org:memberDuring interval without time:hasEnd. A person with an -# ended stint and a current one appears in both lists — accepted. +# interval and the person's own attributes live in the same graph, so one GRAPH +# block suffices. Current membership = org:memberDuring interval without +# time:hasEnd. A person with an ended stint and a current one appears in both +# lists — accepted. :CurrentMembers a ldh:View ; dct:title "Dabartiniai nariai"@lt, "Current members"@en ; @@ -379,19 +399,25 @@ ORDER BY ?org :SelectCurrentMembers a sp:Select ; rdfs:label "Select current members" ; sp:text """ -PREFIX org: -PREFIX time: +PREFIX foaf: +PREFIX ltlod: +PREFIX org: +PREFIX time: SELECT DISTINCT ?person WHERE { GRAPH ?graph { ?membership org:organization $about ; org:member ?person ; - org:memberDuring ?interval + org:memberDuring ?interval . + ?person foaf:givenName ?givenName ; + foaf:familyName ?familyName ; + foaf:gender ?gender ; + ltlod:nominatedBy ?party FILTER NOT EXISTS { ?interval time:hasEnd ?end } } } -ORDER BY ?person +ORDER BY ?familyName ?givenName """ ; rdfs:isDefinedBy : . @@ -404,17 +430,24 @@ ORDER BY ?person :SelectFormerMembers a sp:Select ; rdfs:label "Select former members" ; sp:text """ -PREFIX org: -PREFIX time: +PREFIX foaf: +PREFIX ltlod: +PREFIX org: +PREFIX time: SELECT DISTINCT ?person WHERE { GRAPH ?graph { ?membership org:organization $about ; org:member ?person ; - org:memberDuring/time:hasEnd ?end } + org:memberDuring/time:hasEnd ?end . + ?person foaf:givenName ?givenName ; + foaf:familyName ?familyName ; + foaf:gender ?gender ; + ltlod:nominatedBy ?party + } } -ORDER BY ?person +ORDER BY ?familyName ?givenName """ ; rdfs:isDefinedBy : . @@ -446,14 +479,19 @@ ORDER BY ?unit :SelectNominatedMembers a sp:Select ; rdfs:label "Select persons nominated by party" ; sp:text """ +PREFIX foaf: PREFIX ltlod: SELECT DISTINCT ?person WHERE { GRAPH ?graph - { ?person ltlod:nominatedBy $about } + { ?person ltlod:nominatedBy $about ; + foaf:givenName ?givenName ; + foaf:familyName ?familyName ; + foaf:gender ?gender + } } -ORDER BY ?person +ORDER BY ?familyName ?givenName """ ; rdfs:isDefinedBy : . @@ -469,16 +507,22 @@ ORDER BY ?person :SelectMembersElectedHere a sp:Select ; rdfs:label "Select members elected in this district" ; sp:text """ -PREFIX org: +PREFIX foaf: PREFIX ltlod: +PREFIX org: SELECT DISTINCT ?person WHERE { GRAPH ?graph { ?membership org:member ?person ; - ltlod:electoralDistrict $about } + ltlod:electoralDistrict $about . + ?person foaf:givenName ?givenName ; + foaf:familyName ?familyName ; + foaf:gender ?gender ; + ltlod:nominatedBy ?party + } } -ORDER BY ?person +ORDER BY ?familyName ?givenName """ ; rdfs:isDefinedBy : . diff --git a/app/persons.ttl b/app/persons.ttl index 2d9f988..b12b71e 100644 --- a/app/persons.ttl +++ b/app/persons.ttl @@ -1,11 +1,58 @@ -@prefix dh: . -@prefix ldh: . -@prefix rdf: . -@prefix dct: . +@prefix dh: . +@prefix ldh: . +@prefix ac: . +@prefix rdf: . +@prefix dct: . +@prefix sp: . +@prefix spin: . <> a dh:Container ; dct:title "Asmenys"@lt ; - rdf:_1 <#select-children> . + rdf:_1 <#persons-block> . -<#select-children> a ldh:Object ; - rdf:value ldh:ChildrenView . +# Custom children listing instead of the stock ldh:ChildrenView. LDH derives the +# view's filter pills and sort options from the BGP triples whose SUBJECT is the +# query's FIRST projected variable and whose OBJECT is a variable (see the +# "View filter/sort columns" note in CLAUDE.md) — so the focus variable has to be +# the person entity (?person = {doc}#this), not the document, because that is +# where foaf:givenName / foaf:familyName / foaf:gender / ltlod:nominatedBy sit. +# The stock ChildrenView projects the document (?child) and therefore offers no +# person columns at all. +# +# Filter pills need the ldh:bgp-value-counts override in files/client.xsl to be +# usable here: the label lookup LDH appends to the facet-value query uses an +# alternative property path, which ARQ does not index inside a GRAPH block (~104 s +# per pill over this dataspace's ~117k graphs). The override rewrites it to a +# variable predicate + FILTER IN, so a pill loads in ~0.1 s. + +<#persons-block> a ldh:Object ; + rdf:value <#persons-view> . + +<#persons-view> a ldh:View ; + dct:title "Seimo nariai"@lt, "Members of the Seimas"@en ; + spin:query <#persons-query> ; + ac:mode ac:GridMode . + +# dct:title (not just rdfs:label) is mandatory on sp:Select — LDH enforces +# def:MissingTitle as a SPIN constraint and rejects the PUT with 422 otherwise. +<#persons-query> a sp:Select ; + dct:title "Seimo nariai"@lt, "Members of the Seimas"@en ; + sp:text """ +PREFIX foaf: +PREFIX sioc: +PREFIX ltlod: + +SELECT DISTINCT ?person +WHERE + { GRAPH ?graph + { ?doc sioc:has_container $this ; + foaf:primaryTopic ?person . + ?person a foaf:Person ; + foaf:givenName ?givenName ; + foaf:familyName ?familyName ; + foaf:gender ?gender ; + ltlod:nominatedBy ?party + } + } +ORDER BY ?familyName ?givenName +""" . diff --git a/files/client.xsl b/files/client.xsl index aafef6c..441971b 100644 --- a/files/client.xsl +++ b/files/client.xsl @@ -30,6 +30,7 @@ xmlns:ldh="&ldh;" xmlns:ac="∾" xmlns:rdf="&rdf;" + xmlns:json="http://www.w3.org/2005/xpath-functions" xmlns:org="&org;" xmlns:time="&time;" xmlns:bs2="http://graphity.org/xsl/bootstrap/2.3.2" @@ -117,4 +118,126 @@ ORDER BY ?title + + + + + + + + + + + + + + + optional + + + union + + + + bgp + + + + + + + + + + + graph + + + bgp + + + + + + + + + + ? + + + + + + filter + + operation + in + + ? + + + + + + + + + + + + + + + + + + + + + ? + ? + ? + + + From 434be3836a40642a90ad0f1c2ddb852843ecfd0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Sat, 29 Aug 2026 21:50:06 +0200 Subject: [PATCH 2/5] Made frontpage end-user-oriented Updated footer --- .gitignore | 1 + app/root.ttl | 10 +++--- files/layout.xsl | 85 ++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 89 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 3ef414f..9082b2f 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ files/client.xsl.sef.json /uploads/ /datasets/owner/ /datasets/secretary/ +docker-compose.override.yml diff --git a/app/root.ttl b/app/root.ttl index bc7518d..a56a43c 100644 --- a/app/root.ttl +++ b/app/root.ttl @@ -8,7 +8,7 @@ <> a def:Root ; dct:title "Lietuvos susietieji atvirieji duomenys"@lt ; - dct:description "Lietuvos atvirieji duomenys, sujungti į vientisą RDF žinių grafą: administraciniai vienetai, Seimo nariai, juridiniai asmenys, klasifikatoriai."@lt ; + dct:description "Oficialių Lietuvos registrų duomenys vienoje vietoje: administraciniai vienetai, Seimo nariai, juridiniai asmenys, klasifikatoriai."@lt ; rdf:_1 <#intro> ; rdf:_2 <#counties-intro> ; rdf:_3 <#counties-block> ; @@ -26,8 +26,8 @@ <#intro> a ldh:XHTML ; rdf:value """
-

Lietuvos atvirieji duomenys iš oficialių registrų, sujungti į vientisą, standartais grįstą RDF žinių grafą — kiekvienas objektas turi pasaulinį URI, tad rinkiniai susijungia savaime ir yra susieti su Wikidata bei ES žodynais.

-

Visą grafą galima užklausti SPARQL kalba; pavyzdinės užklausos su rezultatais — etl/queries/EXAMPLES.md. Žemiau — keli pjūviai iš gyvo grafo; konteineriai su visais rinkiniais — puslapio apačioje.

+

Oficialių Lietuvos registrų duomenys vienoje vietoje: administraciniai vienetai, Seimo nariai, partijos, juridiniai asmenys ir klasifikatoriai. Duomenys susieti tarpusavyje, tad nuo vieno objekto galite pereiti prie visų su juo susijusių — nuo apskrities prie savivaldybių, nuo Seimo nario prie jo komitetų ir partijos.

+

Žemiau — keli pjūviai iš duomenų: apskritys žemėlapyje, Seimo frakcijos, partijos ir komitetų pirmininkai. Visi duomenų rinkiniai — puslapio apačioje.

"""^^rdf:XMLLiteral . # rdf:_2 / rdf:_3 — top-level administrative units (counties) as a grid of coats of arms @@ -84,7 +84,7 @@ ORDER BY ?label""" . <#chart-factions-intro> a ldh:XHTML ; rdf:value """

Frakcijų dydžiai

-

Dabartinių Seimo frakcijų sudėtis pagal narių skaičių. Rodomi tik dabartiniai nariai — tie, kurių narystė dar neturi pabaigos datos.

+

Kiek narių šiuo metu turi kiekviena Seimo frakcija. Skaičiuojami tik dabartiniai frakcijų nariai.

"""^^rdf:XMLLiteral . <#chart-factions-block> a ldh:Object ; @@ -202,7 +202,7 @@ ORDER BY ?person""" . <#browse-intro> a ldh:XHTML ; rdf:value """

Naršyti duomenų rinkinius

-

Visi rinkiniai — administraciniai vienetai, asmenys, organizacijos, partijos, juridiniai asmenys, gatvės ir taksonomijos — pasiekiami per konteinerius žemiau.

+

Visi rinkiniai — administraciniai vienetai, asmenys, organizacijos, partijos, juridiniai asmenys, gatvės ir klasifikatoriai. Pasirinkite rinkinį žemiau ir naršykite jo įrašus.

"""^^rdf:XMLLiteral . <#select-children> a ldh:Object ; diff --git a/files/layout.xsl b/files/layout.xsl index 78e8efc..02b494d 100644 --- a/files/layout.xsl +++ b/files/layout.xsl @@ -3,13 +3,18 @@ Server-side layout override for the LTLOD end-user app. - Two jobs: + Three jobs: 1. repoint the client-side XSLT bootstrap at our custom Saxon-JS stylesheet (files/client.xsl → client.xsl.sef.json) instead of the stock one (xhtml:Script below); 2. import the shared files/overrides.xsl so the membership plumbing blocks are suppressed in the SERVER render too — otherwise the server emits them - and the client removes them during CSR, causing a visible flash. + and the client removes them during CSR, causing a visible flash; + 3. replace the stock LinkedDataHub footer (bs2:Footer) with the LTLOD one — + our own wordmark, dataset shortcuts, source attribution and licence. + The footer is server-rendered ONLY (layout.xsl applies bs2:Footer once, + client.xsl never re-renders it), so it lives here and not in + overrides.xsl — no SEF rebuild needed after editing it. Imports the base layout.xsl and overrides only the xhtml:Script template's client-stylesheet parameter. @@ -23,14 +28,18 @@ --> + ]> @@ -46,4 +55,76 @@ + + + + + + From 3b6ec84479314862f953415c8a038211d24accb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Sat, 29 Aug 2026 21:50:57 +0200 Subject: [PATCH 3/5] Document the footer override and the recreate-to-reload rule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CLAUDE.md — files/layout.xsl now also replaces the stock bs2:Footer, so record what the replacement has to honour: app.css styles the footer structurally (.ldh-footer > .cols is a 1.4fr repeat(4, 1fr) grid, hence exactly one .brand-col plus four .col children, each a .ftitle followed by bare s, then .legal with two space-between spans), so the markup contract is the design system and only the content is ours. The one deviation is the wordmark .mark, restyled inline to the Lithuanian tricolour with the class geometry kept. It belongs in layout.xsl rather than overrides.xsl because bs2:Footer is applied once server-side and never re-rendered during CSR — so unlike the block suppression it needs no client-side twin and no SEF rebuild. Two rules the footer work surfaced: - declare xmlns="http://www.w3.org/1999/xhtml" on xsl:stylesheet whenever the override emits literal result elements. LDH's own layout.xsl declares it, so its bare
s are XHTML; without it ours land in no namespace and the serialiser emits a stray xmlns="" on the block. HTML parsers ignore the attribute, but namespace-sensitive XPath/XSLT matching on xhtml:* misses those nodes. - `docker compose restart linkeddatahub` does NOT pick up an edited mounted stylesheet — it keeps serving the one compiled at the previous start. `docker compose up -d --force-recreate linkeddatahub` does, followed by an nginx/varnish restart per the 502 gotcha. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01PoiMWyMGtWZos9wWoVUCwg --- CLAUDE.md | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 081cac2..9c716f1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -174,15 +174,34 @@ since the rows differ in the image cell. views (`:SubUnits`, the frontpage counties map) use their own `spin:query` and are unaffected, so settlement points stay mappable there. - `files/layout.xsl` — imports base `layout.xsl` + `overrides.xsl`; repoints the - client bootstrap (`xhtml:Script` → `client-stylesheet`) at our SEF. Mounted at - the end-user app's `ac:stylesheet` target `static/xsl/layout.xsl`; imports - `overrides.xsl` from `static/xsl/` (its own dir). + client bootstrap (`xhtml:Script` → `client-stylesheet`) at our SEF, and + **replaces the stock `bs2:Footer`** with the LTLOD one (own wordmark, dataset + shortcuts, source attribution, licence; the developer entry points — SPARQL, + example queries, dataset downloads, `{base}ns` — live there instead of on the + frontpage). The footer keeps the stock markup contract, since `app.css` styles + it structurally: `.ldh-footer > .cols` is a `1.4fr repeat(4, 1fr)` grid, so + exactly one `.brand-col` plus FOUR `.col` children, each a `.ftitle` followed + by bare ``s, then `.legal` with two space-between spans. Only the wordmark + `.mark` deviates — restyled inline to the Lithuanian tricolour, class-supplied + geometry kept. Footer-only because `bs2:Footer` is applied ONCE server-side + (LDH `layout.xsl`) and never re-rendered by client.xsl — so it belongs here, + not in `overrides.xsl`, and needs no SEF rebuild. Mounted at the end-user app's + `ac:stylesheet` target `static/xsl/layout.xsl`; imports `overrides.xsl` from + `static/xsl/` (its own dir). + **Declare `xmlns="http://www.w3.org/1999/xhtml"` on `xsl:stylesheet`** whenever + this file emits literal result elements: LDH's own layout.xsl declares it, so + its bare `
`s are XHTML; without it ours land in no namespace and the + serialiser emits a stray `xmlns=""` on the block (HTML parsers ignore it, but + namespace-sensitive XPath/XSLT matching on `xhtml:*` then misses those nodes). Wiring: `make sef` c14n's `client.xsl`+`overrides.xsl` and compiles the SEF against the pinned image's `static/` tree; `docker-compose.yml` bind-mounts the four files (`layout.xsl`, `overrides.xsl` under `static/xsl/`; `client.xsl`, `client.xsl.sef.json` under `static/com/ltlod/xsl/`). Rebuild the SEF + recreate - the container after editing any of them. The `:Memberships` table replaces the - suppressed blocks; it needs a readable period, so `persons.rq` puts a `dct:title` + the container after editing any of them — `docker compose restart linkeddatahub` + does NOT pick up an edited mount (it keeps serving the stylesheet compiled at the + previous start); `docker compose up -d --force-recreate linkeddatahub` does, and + then restart `nginx`/varnish per the 502 gotcha below. The `:Memberships` table + replaces the suppressed blocks; it needs a readable period, so `persons.rq` puts a `dct:title` (e.g. `"2024-11-14 – dabar"`) on each `time:Interval` — `ldh:View`/`ac:TableMode` is DESCRIBE-based, so date literals can't be view columns; the interval's `dct:title` surfaces in the `org:memberDuring` cell via object-label resolution. From f0f97fd4103f73d79afd5e4473502f631527e429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Sat, 29 Aug 2026 21:50:57 +0200 Subject: [PATCH 4/5] Pin the runtime image to LDH e38f693d MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docker-compose.override.yml — the new design system (the ldh-footer grid the LTLOD footer override targets, the vendored latin-ext typefaces, the acl:Write gate on the block edit pencil) only exists in the image built from that LDH commit, so the SEF and the layout override are both compiled and mounted against it. Note .gitignore now lists docker-compose.override.yml, which is inert while the file stays tracked — untracking it would also drop the tdb-loader service and the TZ/signup settings that make load and make up rely on, so it stays committed for now. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01PoiMWyMGtWZos9wWoVUCwg --- docker-compose.override.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.override.yml b/docker-compose.override.yml index ecf99b1..f45f913 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -1,6 +1,6 @@ services: linkeddatahub: - # image: atomgraph/linkeddatahub:fe7106d14e022a9eb709fbe1b3beb60496f7d177 + image: atomgraph/linkeddatahub:e38f693d681ff65cee72d3ed92cf981f4dd7dfbe environment: - TZ="Europe/Vilnius" - ENABLE_WEBID_SIGNUP=false # disable public WebID signup From ca4bad9410084a2ceb6735de6a65655c5d46a73a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Sat, 29 Aug 2026 21:51:48 +0200 Subject: [PATCH 5/5] Revert "Pin the runtime image to LDH e38f693d" This reverts commit f0f97fd4103f73d79afd5e4473502f631527e429. --- docker-compose.override.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.override.yml b/docker-compose.override.yml index f45f913..ecf99b1 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -1,6 +1,6 @@ services: linkeddatahub: - image: atomgraph/linkeddatahub:e38f693d681ff65cee72d3ed92cf981f4dd7dfbe + # image: atomgraph/linkeddatahub:fe7106d14e022a9eb709fbe1b3beb60496f7d177 environment: - TZ="Europe/Vilnius" - ENABLE_WEBID_SIGNUP=false # disable public WebID signup