feat(library)!: an open JLink, the mixin label, a narrowable allow-list, and the edit-mode URL - #773
Draft
romain-pm wants to merge 2 commits into
Draft
feat(library)!: an open JLink, the mixin label, a narrowable allow-list, and the edit-mode URL#773romain-pm wants to merge 2 commits into
romain-pm wants to merge 2 commits into
Conversation
📝 Documentation GuidelinesThank you for contributing to our documentation! To ensure your contributions meet our standards, please review these resources:
This comment is posted automatically when changes are detected in the |
3 tasks
commit: |
This was referenced Aug 23, 2026
romain-pm
force-pushed
the
feat/link-api
branch
from
August 23, 2026 22:21
8a24e44 to
d1656c2
Compare
…is correct `attributes` takes a record or a function of the resolved link, so a value derived from the URL and the label the component computed is reachable at last; `asChild` hands the link to a wrapper that is not a bare `<a>`; and the anchor attributes the component accepts are derived from its own props rather than hand-listed. `labelProperties` and `labelFrom` say where the label of a link mixin lives, `state.node` returns what the link resolved to, `readNodeReference` exports the safe reference read behind it as a JCR concern, and `allowedSchemes` / `setLinkDefaults` narrow the scheme allow-list per call or per module. `buildNodeUrl` emits `/cms/editframe/` for edit mode: `/cms/edit/` redirects to the jContent UI on 8.2.3 and only ever reached a page because EditModeFilter substitutes the two for an `a[href]`.
romain-pm
force-pushed
the
feat/link-improvements
branch
from
August 23, 2026 22:22
10b025d to
effccbb
Compare
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.
Summary
Closes the five gaps that kept the link API off most of a real site. Adopting it on Jahia/jahia.com moved all 24 link sites, but only 5 could use
<JLink>; the other 19 dropped togetLinkPropsplus a hand-written<a>plus astate.navigablebranch. Implements #768, #769, #770, #771 and #772, on top of #751.Every part of that call site was previously impossible.
Why
The two blockers were the same shape: the component computed something and would not hand it back.
data-element-urlanddata-element-textare derived from the href and the label<JLink>had just built, and React's typings do not modeldata-*on a component's props at all — so the only way to write them was to stop using the component. A CTA mixin, meanwhile, sits on a card or a hero whosejcr:titleis the heading rather than the link label, which made the derived label wrong on 23 of those call sites.Changes
attributes(#768) — a record, or a function of the resolved link. It receives exactly whatgetLinkPropsreturns, so the same callback works on both tiers, and it is the same shape<JImage attributes>settled on. Spread last; not called when the link is not navigable.asChild(#768) — the child element becomes the link, for a design-system call to action that is not a bare<a>. Next.js met the same need and called itpassHref. Exactly one element child; anything else is an error naming the way out.Omit<AnchorHTMLAttributes, keyof JLinkOwnProps>rather than a hand-written list, so a prop added later cannot silently swallow an attribute. Same fix shape as #762 on the image side.state.nodeandreadNodeReference(#769) — the link hands back what it resolved to, and the safe reference read behind it is exported next togetNodeProps, because reading aweakreferencewithout letting a dangling one break the render is a JCR concern, not a link one. It separates the two cases a falsy value merges:nullfor unset,{ uuid }with nonodefor set-but-unreachable.labelProperties/labelFrom(#770) — where the label of a link mixin actually lives.labelFrom="target"is the readable spelling oflabelProperties={[]}and takes precedence over it.allowedSchemesandsetLinkDefaults(#771) — per call and module-wide,bundleKey-scoped exactly likesetImageDefaults. Narrowing only: a scheme outside the built-in list is dropped rather than added, with a development-mode warning so the links do not silently disappear./cms/editframe/(#772) — the manual branch ofbuildNodeUrlnow emits the servlet path that renders a page. See below.attributesandasChild, one on the mixin label, the allow-list narrowing,state.node/readNodeReference, and a table of exactly which tag/attribute pairs core finishes a URL in.The empirical question #772 asked
Is the
/cms/edit/→/cms/editframe/replace still needed on 8.2.3? Yes — but it is a library bug, not core cargo, so it disappears rather than needing a new API. Measured on a localjahia/jahia-ee:8.2.3with the luxe site:/cms/edit/default/en/sites/luxe/home.html302→/jahia/jcontent/luxe/en/pages/home/cms/editframe/default/en/sites/luxe/home.html200, the pageIn the rendered edit-mode page, every
<a href>carried/cms/editframe/…while the language-switcher Island's JSON payload carried/cms/edit/…— the exact defect the issue describes, reproduced. The cause:EditModeFiltersubstitutes the two, and its traverser visitsa[href]and nothing else, so the same URL in an Island payload keeps the redirecting form and navigates the builder's iframe into a second copy of jContent.node.getUrl()already returned the editframe path; only the manual branch ofbuildNodeUrl— the one any ofmode,languageorextensionselects — hardcoded/cms/edit/.So the remedy is one string, not a new call: emit
/cms/editframe/. Jahia/jahia.com's 15-year-old.replace("/cms/edit/", "/cms/editframe/")can be deleted, and the URL is now correct wherever it is put. This also ticks themode: "edit"box of #756.The rest of #772 does shrink, and what remains is documentation rather than API. There is no "final URL" call to add: the finishing pass is
URLFilterwalking assembled HTML, which does not exist while a view runs. What a developer needs is to know where it applies, so the guide now states it exactly —a[href],img[src|srcset|data-src|data-srcset],form[action],link[href],source[srcset],embed[src],param[value], in anhtmltemplate type — and that adata-*attribute of your own, an Island payload, an action's JSON, a<meta>tag and JSON-LD get nothing. The vanity-URL and?jsite=gap in an Island payload is real and stays open.Validation
All run in
~/dev/jsm-wt/768with the real exit code captured, after the final source edit.npx tsc --noEmit -p tsconfig.json— 0.npx tsc --noEmit -p tsconfig.spec.json— 0.npx vitest run— 0, 257 tests / 7 files (up from 214). New coverage for the record and function forms ofattributes,asChildin all four states, the derived omit,state.node,readNodeReference, the mixin label precedence, the allow-list narrowing including the dev warning and the production silence, and the/cms/editframe/path.hrefwith no accessible name, and a discriminator or label option on thenodeshape.npx eslint javascript-modules-library samples/hydrogen— 0.npx prettier --checkon every changed file — clean.yarn buildafterrm -rf dist— 0, publint "All good!".npx tsc --noEmitinsamples/hydrogenagainst the fresh dist — 0. Emitted declarations read back to confirm the new surface.server.config.isDevelopmentMode().Decided
attributesreceivesLinkProps, notAnchorProps. #768 sketched({ href, label, isCurrent }), but two of the five attributes jahia.com needs come fromanchorand two fromstate. Passing the whole thing keeps one shape across both tiers instead of inventing a third.state.node, notstate.target.targetis already the anchor attribute, and the two are never the same thing.readNodeReferencelives inutils/jcr/. #769 asked whether it belongs next togetNodeProps; it does.resolveContentLinkis now a caller rather than the owner.allowedSchemesintersects rather than replaces. A call site is not where a project loosens its URL policy —javascript:anddata:are why the list exists. Silently dropping a request would look like missing content, so development mode says so once per scheme.asChildstill renders its child when the link is not navigable, without the link. A call to action that lost its target is a call to action with nohref, not a hole in the page;whenUnresolved="none"is how you drop it.Not done
/cms/edit/replace, the hand-writtenreadReferenceinNavBar.tsx, and thegetLinkProps+<a>+ branch shape at ~19 sites; Jahia/luxe-jahia-demo can drop its own.?jsite=still do not reach an Island payload. Documented, not solved.samples/hydrogendemonstrates none of the new options: its call to action is a plain<a>and needed no change.Based on #751, which is based on #766.