feat: declare navigations to Solid's observe tier (withOrigin) - #601
Merged
Conversation
Every client location write — navigate(), a redirect hop chased while the previous target is pending, the browser's own back/forward — now runs inside OBSERVE.attribution.withOrigin with the parametrized route pattern, params, and origin location. The attribution engine names holds and re-runs after the route, times the navigation from the user event to settle, folds redirect hops onto the navigation they belong to (redirect depth from _navigation), and reports routes in feedback().navigations. The ref's name and params are getters read at settle, so a lazy subtree that resolved during the hold names the exact route rather than its placeholder. The location signal and the matches / routingPending / lazyRoutes memos are named so diagnostics read as themselves instead of `signal` / `computed`. Production builds are untouched: OBSERVE is undefined there and the declaration folds out. Requires solid-js 2.0.0-rc.8. Co-authored-by: Claude via Cursor <noreply@cursor.com>
🦋 Changeset detectedLatest commit: 3753889 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
OBSERVE.attribution.withOrigin shipped in rc.8; dev/peer ranges and the lockfile move with it. 401 client + 35 server tests, types clean, against the published packages. Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
ryansolid
marked this pull request as ready for review
September 11, 2026 16:35
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.
Depends on
OBSERVE.attribution.withOriginfrom solidjs/solid#3332, released insolid-js2.0.0-rc.8; the last commit moves the dev/peer ranges and lockfile to rc.8. Against the published packages: 401/401 client tests, 35/35 server tests, type tests clean. Ready for review.What
Every client location write —
navigate(), a redirect hop chased while the previous target is still pending, the browser's own back/forward — now runs insideOBSERVE.attribution.withOrigin(ref, write), declaring the navigation to Solid's attribution engine. One seam, increateIntegration's setter, since every client write passes through it.The ref carries:
name— the parametrized route pattern of the leaf match (/users/:id;/for the root route, whose pattern is"")params— merged params of the match chainto/from— the target and the committed location (whichread()still holds while a navigation is pending, so a hop'sfromis right too)redirect—_navigation - 1when_navigation > 1, i.e. the router's own redirect depth becomes the engine's hop indexnameandparamsare getters. The engine re-reads the ref when the navigation settles, so a lazy route subtree that loaded during the hold names the exact route it resolved to instead of its placeholder (/plugins/widgets/:id, not/plugins/*). No refine call, no plumbing betweencreateRouterContextand the integration — the late-read contract does the work.Also names the location signal (
location) and thematches,routingPending, and lazy-subtree (lazyRoutes) memos, so diagnostics show them as themselves rather thansignal/computed(acknowledgedBy: ["isPending:location"],heldWrites: ["location"]).What the engine then gives
attribution.navigations()records per navigation: route name,to/from/params,writes,outcome(committed|held|superseded),settledMs, theHoldEventwhen held,redirects: [{ name, to, at }]for hopsorigin=navigation to /users/:id (/users/42), andredirected from /fileswhen a hop was involvedfeedback().navigationsranks routes by time spent held navigating to them, withsilent/superseded/redirectedcountsProduction
Nothing.
OBSERVEisundefinedoutside the observe/dev builds, andOBSERVE ? … : commit()folds out.Tests
test/observe-navigation.spec.tsx: pattern/params/from on a plain navigation; root named/; a query redirect thrown while pending recorded as one navigation withwrites: 2andredirects: [/files]; lazy subtree named by its resolved route at settle;history.go(-1)declared.Notes from the spike that motivated this
Observed with a Sentry adapter prototype over the observe tier (findings list to follow in the solid repo):
data-pendinglink claim readsisPending(location)from an effect, so a held navigation started from a link always counts as acknowledged by the engine's hold census. That is by design: the claim is the router's hook for attaching a pending affordance to the link that started the navigation (in particular when the source sits in a non-hydrated region and nothing else can react), so it is an affordance, not a loophole — no opt-out. Under this routerSILENT_HOLDfor a navigation therefore fires only for a programmaticnavigate()with no claimed link and no other indicator, which is the case that deserves the report.string | undefined;NavigationRef.paramsisRecord<string, string>. Cast here; loosening the core type is the right fix.