Every string the editor shows comes from a catalog a host can replace - #32
Open
namedgraph wants to merge 6 commits into
Open
namedgraph wants to merge 6 commits into
namedgraph wants to merge 6 commits into
Conversation
Nine reads reached through ixsl:call/ixsl:get for facts the data model
already exposes, so the module asked JavaScript a question XPath answers.
isSameNode($a, $b) is `$a is $b`; parentNode is `..`; parentElement, which
is null when the parent is not an element, is exactly `parent::*`; and a
Range container's nodeType = 3 test is `instance of text()`. The event
targets that carried two of the identity tests take `as="node()?"` so the
comparison has a node to compare - the same typing relatedTarget already
had in navigate.xsl, and the reason `is` was reachable there and not here.
Two constructions follow: Object() through ixsl:call is ixsl:new('Object',
[]), the extension that exists for it; and typeahead's three-line scroll
options object - allocate, set `block`, pass - is one XDM map literal, the
form navigate.xsl was already passing to the same scrollIntoView on the
line above its own call.
Nothing moves in the DOM layer proper. Building detached subtrees through
rdfae:element and splicing them with insertNode/appendChild/before stays:
xsl:result-document emits at a location and returns nothing to hold, and
the insertion model hands the constructed node onward to be spliced, to
receive the caret and to seed a Range. That is an architecture, not a
workaround, and converting it would be a redesign of the editing core.
make sef compiles both entries; make test passes 52; make test-browser
reports 532 assertions true with the two pre-existing canonicalSource
paste-probe values unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSRUJBNKKjdgJeP3qRZTPd
The chrome hardcoded its English. 159 sites across eight modules - dialog labels and buttons, the toolbar, the slash menu, the breadcrumb and inspector, confirm() prompts, the titles set through ixsl:set-attribute - each one a literal in the template that rendered it. A host wanting the editor in its own language had nowhere to put a translation: overriding a whole dialog to change a word forks markup it would rather inherit, and editing a vendored copy of the source is worse, because the next copy silently reverts it. So the strings move to translations.rdf and the templates read them through rdfae:label(). The catalog is plain RDF/XML - rdf:Description keyed by rdf:nodeID, carrying rdfs:label per language - which needs no manifest format of its own and is the shape a host is likely to keep its own strings in already. A host translates the editor by overriding rdfae:translations() to return its catalog, or by pointing $translations-href at another document; overriding rdfae:label() as well replaces the language selection for a host that resolves it from something richer than $translations-lang. The keys are named after the concept rather than the English, so a catalog can be written against them without reading the templates. rdfae:translations() is two declarations under use-when rather than one with a branch: in SaxonJS the catalog resolves against the page URI the way the vocabularies do, so doc() hits the preloaded pool, and under Saxon there is no page and static-base-uri() reaches the file the test drivers read. A runtime branch cannot span that, because ixsl:location() does not exist to compile against outside SaxonJS. Like the vocabularies, the catalog has to be preloaded by the host page - index.html and the four test fixtures now do, and generate-sef.sh copies it into dist/. Two things a host should not have to patch travel with it. Every dialog label now carries @for and its control an @id, so clicking the label focuses the field it names - the labels were bare, pointing at nothing. And the block drag handle's class becomes rdfa-editor-drag-handle: `drag-handle` is a token a host is likely to have styled already, and when it has, its own rule hides the editor's handle. The 52 headless assertions and the browser suites pass unchanged, the two tolerated false results (noId, noMsoClass) being exactly the two on master. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BSRUJBNKKjdgJeP3qRZTPd
The catalog lookup was split under use-when - doc() against ixsl:location() in the browser, document() against static-base-uri() under Saxon - on the assumption that a relative href cannot mean the same thing in both. It can, and the build already says so: the SEF is compiled with -relocate:on, which resolves relative document() hrefs against the SEF's load location. So a bare document($translations-href) finds the catalog beside the stylesheet in either product, and the module drops its ixsl: dependency along with the second declaration. The catalog moves to src/, where the stylesheet that reads it lives, and generate-sef.sh copies it into dist/ as it does the vocabularies. Host pages pool it under the URI that resolution actually asks for - beside the SEF, not beside the page, which is where the vocabularies sit. Verified by pooling it under both URIs, then removing the page-relative one: the browser suites stay green, so the SEF-relative URI is the one being requested. Its DTD entity declarations go too. xslt3 does not expand entity references in a document() target, so the headless path failed with FODC0002 on &rdf; the moment anything called rdfae:label() - invisible until now only because the test drivers import the content model and the linters, never the chrome. The namespaces are written out instead, and a scratch stylesheet under Saxon-HE now answers table-columns, drag-to-reorder and an unknown key (which returns the key) from a 112-entry catalog. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BSRUJBNKKjdgJeP3qRZTPd
LinkedDataHub vendors src/ and had patched all three in place, where the next copy would have silently reverted them. None is host-specific. rdfae:populate-form disclosed the advanced fields inline, so a host whose overlay groups type, subject and object differently - behind tabs, or a segmented object switch - had to override the whole prefill to change that one step. It becomes rdfae:reveal-fields, the single shape-dependent part of populate-form, overridable on its own while the rest of the prefill stays inherited. The annotation handler called populate-form without checking the overlay is still there. A host that re-renders the page DOM drops it, and populate-form reaches the form by id(): over an empty sequence it does nothing, silently, and the dialog opens blank. rdfae:show-overlay rebuilds the overlay too, but it runs after populate, which is exactly too late. The guard goes in front of the call; show-overlay keeps its own as the backstop for callers that arrive without passing through the handler. And the subject readout is a single line that may be narrower than the IRI it shows, with nothing offering the rest. @title is now written wherever textContent is - in show-overlay and on the subject input's own handler - so the two cannot come to say different things. 52 headless assertions and the browser suites pass unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BSRUJBNKKjdgJeP3qRZTPd
$translations-lang defaulted to 'en', so a catalog with Spanish in it still rendered English until a host told the editor otherwise - and every host would tell it the same thing, since the preference has one source: the browser. rdfae:langs() asks navigator.languages directly, reducing to primary subtags so a catalog tagged es-ES answers a browser asking for es-419, deduplicating because es-ES,es yields that subtag twice, and flooring at 'en' for a reader who expressed no preference. It is the same computation, from the same fact, that a host would otherwise perform on the editor's behalf. Two declarations under use-when: navigator exists in the browser only, and the editor compiles under Saxon for its headless tests, where there is no reader to ask and 'en' is the answer. $translations-lang stays, now defaulting from that function rather than from a literal. A host that resolves the language from something the browser does not know - a negotiated Accept-Language, a profile setting - still redeclares it, and one that does not gets the reader's language without configuring anything. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BSRUJBNKKjdgJeP3qRZTPd
A host with its own design system had one way to restyle an editor button: copy the template that emits it and change the class. LinkedDataHub carries four such copies - the link, figure, table and find dialogs - 56 lines whose only divergence from this source is btn-primary becoming the host's own button class, and which quietly stop tracking anything that changes here. Five presentational tokens become parameters instead: the primary, secondary and danger buttons, the helper text and the checkbox label. Their defaults are what this editor's CSS already styles, so nothing changes for a standalone editor, and a host that redeclares them gets its own buttons throughout the chrome - dialogs, toolbar, overlay, block extension - without forking a template. The line is what is presentational and what is load-bearing. rdfa-editor- content, slash-item, typeahead-input, remove-action and their kind are what the editor's own ixsl:on* rules match on and its stylesheets select; those stay literal, because a host redeclaring one would break dispatch rather than restyle anything. None of the five carries a handler or appears outside the editor's own CSS - which is what makes them parameterisable and the rest not. 52 headless assertions and the browser suites pass unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BSRUJBNKKjdgJeP3qRZTPd
namedgraph
added a commit
to AtomGraph/LinkedDataHub
that referenced
this pull request
Sep 14, 2026
…ms and a function. xsl/rdfa-editor/ is a copy of RDFa-Editor/src, and a copy's contract is that diff -r comes out empty. Seven of its files had drifted - 229 lines - because the editor hardcoded its English and applied its own button classes, and the only places to change either were the copy itself, where the next cp reverts it, or a whole-template override here, which forks markup this platform would rather inherit. Both had happened: the toolbar and slash menu were localised in the copy, four dialogs were forked into client/form.xsl for the sake of a class name, and six lines of editor-generic work - a seam, a guard, two @title readouts - existed nowhere but the copy. The editor now carries the mechanisms instead (AtomGraph/RDFa-Editor#32): a translations.rdf catalog read through rdfae:translations() and rdfae:label(), rdfae:langs() asking navigator.languages, and presentational classes declared as parameters. All of it upstream, so all of it arrives by copying. What is left here is the part that is genuinely this platform's. One function points the editor's label lookup at LDH's own catalog, which already answered 110 of its 112 keys in both languages - block-ellipsis and failed-to-load-block-data are the two it did not. Four parameters dress the editor's buttons and helper text in the design system. The four dialog templates that used to do that by copying are deleted, and the overlay redesign, the ldh:Combobox typeahead and the block dialog stay, because those are behaviour rather than costume. Measured on the running instance rather than argued: in a Spanish browser the slash menu reads Párrafo, the find dialog Buscar, the drag handle Arrastre para reordenar; in an English one, Paragraph, Find, Drag to reorder. The dialogs arrive from the editor's own templates wearing ac-btn in-primary ap-solid sz-sm, and the annotation overlay still opens as LDH's redesign - tabs, and an object that is a literal or a resource but never both - because upstream's populate-form calls the rdfae:reveal-fields seam this platform overrides. diff -r against RDFa-Editor/src is empty across all 21 files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BSRUJBNKKjdgJeP3qRZTPd
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.
The editor hardcoded its English: 159 sites across eight modules — dialog labels and buttons, the toolbar, the slash menu, the breadcrumb and inspector,
confirm()prompts, titles set throughixsl:set-attribute. A host that wants the editor in another language has nowhere to put a translation today; overriding a whole dialog to change one word forks markup it would rather inherit, and editing a vendored copy ofsrc/is worse, since the next copy silently reverts it.The seam
translations.rdf— 112 keys, English. Plain RDF/XML:rdf:Descriptionkeyed byrdf:nodeID, carryingrdfs:labelper language. No manifest format of its own, and the shape a host is likely to keep its own strings in already.src/translations.xsl—$translations-href,$translations-lang, aresourceskey,rdfae:translations()andrdfae:label().rdfae:label('key'). Keys name the concept, not the English, so a catalog can be written against them without reading the templates.A host translates the editor by overriding
rdfae:translations()to return its own catalog, or by pointing$translations-hrefelsewhere. Overridingrdfae:label()too replaces the language selection, for a host that resolves it from something richer than a parameter.Two details worth a look
rdfae:translations()is ause-whenpair, not a runtime branch. Under SaxonJS the catalog resolves against the page URI the way$vocab-hrefsdoes, sodoc()hits the preloaded pool; under Saxon there is no page andstatic-base-uri()reaches the file the headless drivers read. A runtime branch can't span that —ixsl:location()doesn't exist to compile against outside SaxonJS.The catalog has to be preloaded by the host page, exactly like the vocabularies.
index.htmland the fourtests/fixture*.htmlpages now do;generate-sef.shcopies it intodist/. This is the one thing an embedder must remember.Two host-facing fixes travelling with it
@for, their controls@id. The labels were bare and pointed at nothing, so clicking one focused nothing.drag-handle→rdfa-editor-drag-handle.drag-handleis a token an embedding application is likely to have styled already; when it has, its own rule can hide the editor's handle.Tests
Headless suite 52 PASS. Browser suites exit 0, 622 assertions true. The two tolerated
falseresults (noId,noMsoClass) are exactly the two produced bymaster— baselined by stashing this branch, rebuilding the SEF and re-running.🤖 Generated with Claude Code
https://claude.ai/code/session_01BSRUJBNKKjdgJeP3qRZTPd