Add subject-IRI dereferencing and a copy-the-IRI Data-tab control#1107
Merged
Conversation
Follows-up to #1090 Subject IRIs (`$wgNeoWikiRdfBaseUri/entity/{subjectId}`, the `neo-subj:` IRI every RDF export mints) did not dereference — nothing routed `/entity/…`. This makes them dereferenceable concept URIs and surfaces each Subject's IRI in the Data tab. ## Concept-URI negotiator: `GET /neowiki/v0/entity/{subjectId}` A new REST handler (`ResolveSubjectIriApi`) content-negotiates a Subject's concept URI and replies **303 See Other** with an absolute `Location`: | `Accept` | Redirects to | |---|---| | `application/trig` | the Subject's TriG RDF (`…/subject/{id}/rdf?format=trig`) | | `text/turtle` | the Subject's Turtle RDF (`…/subject/{id}/rdf?format=turtle`) | | `text/html`, `*/*`, absent, anything else | the Subject's hosting page | TriG wins when both RDF types are acceptable (matching the existing `RdfFormatNegotiation`); the RDF redirects target the native projection, keeping the entity route Accept-only (no `projection`/`format` params). The Subject is resolved and read-authorized before the Accept header is inspected, so an absent Subject and one on a page the caller may not read return one byte-identical `404` (anti-oracle parity with the per-Subject RDF endpoint, #1046); a malformed id returns `400`. Resolution + read-gating live in a new Application service (`SubjectHostingPageResolver`) — the resolve→authorize half of `RdfSubjectExporter` without loading page content. ## Serving the bare IRI MediaWiki's REST router matches on the request path, so the bare `/entity/{id}` must reach it as `/w/rest.php/…`. A plain internal rewrite leaves `REQUEST_URI` unchanged (the router would 404), so the dev image serves it with an internal self-proxy (no client-visible redirect), on Apache with `mod_proxy` + `mod_proxy_http`: RewriteRule ^/?entity/(.+)$ http://127.0.0.1/w/rest.php/neowiki/v0/entity/$1 [P,L] The REST path (`/w/rest.php/neowiki/v0/entity/{id}`) works with no server config. This applies when `$wgNeoWikiRdfBaseUri` is the wiki's own host (the default); an external base URI is the operator's routing concern. ## Copy-the-IRI control (Data tab) Each expanded Subject row footer now shows the Subject's full IRI with a copy-to-clipboard control next to the existing ID one — copy-only, not a link: on installs without the rewrite the bare IRI 404s, and LD users paste IRIs into tools. The frontend does not rebuild IRI logic: `SubjectsAction::onView` exposes `wgNeoWikiSubjectIriBase` (the `$base/entity/` prefix from the same `RdfNamespaces` rule the export uses, via a new `subjectIriBase()` accessor), and the component only appends the Subject id. Relates to the export-menu work in #1097. ## Docs and tests `docs/rdf/rdf-export.md` gains a "Dereferencing subject IRIs" contract section (conneg table, the always-available REST path, the shipped rewrite snippet); `docs/api/rest-api.md` gains the route row. Handler integration tests cover the full conneg matrix (turtle/trig/both/html/`*/*`/absent), absolute Locations, 404 unknown, denied == absent byte-identical, and 400 malformed; unit tests cover the resolver, the `subjectIriBase()` accessor (including trailing-slash trimming), and the config var. `make phpunit`, `make cs`, and the TypeScript build/lint/vitest all pass. Verified live on both the REST path and the bare `/entity/` rewrite path: the full conneg matrix (following a 303 to a Turtle body and to the HTML page), 404/400, and the Data tab showing `ID` + `IRI` controls with the IRI matching the export's `neo-subj:` expansion. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
JeroenDeDauw
marked this pull request as ready for review
July 20, 2026 20:26
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follows-up to #1090
Subject IRIs (
$wgNeoWikiRdfBaseUri/entity/{subjectId}, theneo-subj:IRI every RDF export mints) did notdereference — nothing routed
/entity/…. This makes them dereferenceable concept URIs and surfaces eachSubject's IRI in the Data tab.
Concept-URI negotiator:
GET /neowiki/v0/entity/{subjectId}A new REST handler (
ResolveSubjectIriApi) content-negotiates a Subject's concept URI and replies303 See Other with an absolute
Location:Acceptapplication/trig…/subject/{id}/rdf?format=trig)text/turtle…/subject/{id}/rdf?format=turtle)text/html,*/*, absent, anything elseTriG wins when both RDF types are acceptable (matching the existing
RdfFormatNegotiation); the RDFredirects target the native projection, keeping the entity route Accept-only (no
projection/formatparams). The Subject is resolved and read-authorized before the Accept header is inspected, so an absent
Subject and one on a page the caller may not read return one byte-identical
404(anti-oracle parity withthe per-Subject RDF endpoint, #1046); a malformed id returns
400.Resolution + read-gating live in a new Application service (
SubjectHostingPageResolver) — theresolve→authorize half of
RdfSubjectExporterwithout loading page content.Serving the bare IRI
MediaWiki's REST router matches on the request path, so the bare
/entity/{id}must reach it as/w/rest.php/…. A plain internal rewrite leavesREQUEST_URIunchanged (the router would 404), so the devimage serves it with an internal self-proxy (no client-visible redirect), on Apache with
mod_proxy+mod_proxy_http:The REST path (
/w/rest.php/neowiki/v0/entity/{id}) works with no server config. This applies when$wgNeoWikiRdfBaseUriis the wiki's own host (the default); an external base URI is the operator's routingconcern.
Copy-the-IRI control (Data tab)
Each expanded Subject row footer now shows the Subject's full IRI with a copy-to-clipboard control next to
the existing ID one — copy-only, not a link: on installs without the rewrite the bare IRI 404s, and LD users
paste IRIs into tools. The frontend does not rebuild IRI logic:
SubjectsAction::onViewexposeswgNeoWikiSubjectIriBase(the$base/entity/prefix from the sameRdfNamespacesrule the export uses, viaa new
subjectIriBase()accessor), and the component only appends the Subject id. Relates to the export-menuwork in #1097.
Docs and tests
docs/rdf/rdf-export.mdgains a "Dereferencing subject IRIs" contract section (conneg table, thealways-available REST path, the shipped rewrite snippet);
docs/api/rest-api.mdgains the route row. Handlerintegration tests cover the full conneg matrix (turtle/trig/both/html/
*/*/absent), absolute Locations, 404unknown, denied == absent byte-identical, and 400 malformed; unit tests cover the resolver, the
subjectIriBase()accessor (including trailing-slash trimming), and the config var.make phpunit,make cs, and the TypeScript build/lint/vitest all pass.Verified live on both the REST path and the bare
/entity/rewrite path: the full conneg matrix (following a303 to a Turtle body and to the HTML page), 404/400, and the Data tab showing
ID+IRIcontrols with theIRI matching the export's
neo-subj:expansion.