Detail pages render empty on direct URL load (share/bookmark links): sib-router never binds data-src outside client-side navigation
Summary
Detail pages (/action-group-detail/<id>/, appel-soutiens-detail, fiche-detail, consultation-detail, lois-detail) render only their shell (back-link + footer) when the URL is loaded directly — share links, bookmarks, newsletter links, QR codes. The same page renders fully when reached by clicking a card in a list.
Root cause (verified against [email protected] bundle as served in prod)
[email protected] binds the resource only during client-side navigation:
-
SolidRoute.resourceId=router.currentURL.split("@")[1]— a plain/route/<id>/path has no@-encoded segment, so on a direct load the parsed resource id is empty. -
updateResource()— the only code path that setsdata-srcon the view'sbind-resourceselements — runs insidenavigate()(client-side nav). It never runs for a plain direct URL, sodata-srcstays null and the detail component renders nothing. - Click-nav works because the CTA dispatches
requestNavigationwith a full resource URL (viause-id), which the router binds throughupdateResourcenormally.
Reproducible with one line in any browser console:
// direct load https://<instance>/action-group-detail/<id>/
document.querySelector('solid-risefor-action-detail')?.getAttribute('data-src')
// → null (click-nav to the same campaign → data-src set, full render)
Live example (prod palestine, 2026-09-25): https://palestine.risefor.org/action-group-detail/46/ — empty on direct load, renders after click-nav from /actions/.
Affected surfaces
Any entry point that hands users a plain detail URL: campaign share links (social share buttons), newsletters, printed/QR links, browser bookmarks, external press links. Verified empty on prod palestine; mechanism verified on upstream dev stack (:5174) too — not fork-specific.
Proposed fix (already built & validated on the palestine fork — MR !5 (merged) there)
Replay the navigation the router already understands, from RiseforComponent.gatekeeper():
- New helper
directLoadResource.ts:-
resolveDirectLoadResource(pathname, baseHostname): parses/route/<id-or-encoded-resource>/and composes the full resource URL the same way a list-card CTA would (${VITE_INSTANCE_HOSTNAME}/${container}/${id}/). Route→container map for the 5 detail routes. Accepts the@-encoded form too. -
requestNavigationWhenReady(route, resourceUrl): sib-router'snavigate()silently no-ops when the targetsolid-routeelement doesn't exist yet (orbit renders the shell asynchronously) — this retries until the route element exists (200ms × up to 10s) before dispatching, eliminating the timing race.
-
- Integration in
riseforComponent_deprecate.tsgatekeeper(): only when!dataSrc, nodefaultDataSrc, route active, and the componenthasAttribute("bind-resources")(matches the orbit config convention — all 5 upstream detail components shipbind-resources: ""), thenrequestNavigationwith the composed URL → the router's ownupdateResourcebindsdata-srcexactly like a click.
Notes:
- Uses the same
VITE_INSTANCE_HOSTNAME+ container-path composition convention astask_getResource.ts— no new config surface. - Fires only on
bind-resourcescomponents, so configs missing that attribute keep current behavior (config errors stay config errors). -
lois-detailmust map to the SSR mirror container (ssr/laws/) to match what law CTAs pass. - Validated live on the palestine fork: direct load binds
data-src, full render, click-nav unaffected, double-bind guard in place (both flags: dev + prod-palestine-style config).
Two mechanics to double-check before merging (happy to take another path if you prefer)
-
Redirect shim instead of replay: rewrite
/route/<id>/→/route/@<resource-url>before router init (location.replace). One fewer moving part, but changes the visible URL and needs per-route container knowledge anyway. -
Router-level fix (upstream @startinblox/router): make
resourceId/activate()handle plain paths. Cleanest long-term, but a third-party package bump affects all orbit-based views — riskier, slower.
Happy to open the MR on this repo with the gatekeeper() approach. Feedback welcome on the route→container map (hard-coded in the helper) — alternatives welcome.