feat(atoms)!: 675 — one asyncRouteAtom API for async route data - #98

Open
randomdevpete wants to merge 3 commits into
task-679-investigate-per-demo-scoped-store-contextfrom
task-675-coalesce-resolvedatom-and-asyncrouteatom
Open

feat(atoms)!: 675 — one asyncRouteAtom API for async route data#98
randomdevpete wants to merge 3 commits into
task-679-investigate-per-demo-scoped-store-contextfrom
task-675-coalesce-resolvedatom-and-asyncrouteatom

Conversation

@randomdevpete

Copy link
Copy Markdown
Owner

Restored PR.#95 was merged by mistake into task-679-investigate-per-demo-scoped-store-context; that merge has been reverted (679 is back at 1339a1a) and this branch restored unchanged at 12050ba. A merged PR cannot be reopened, so this PR replaces #95 with the identical branch and body. Anchor tags: presquash/task-679-.../premerge-95, presquash/task-675-.../restore-1.

Summary

Coalesces resolvedAtom and asyncRouteAtom into one constructor, asyncRouteAtom(route, name, load). asyncRouteAtom was already built on resolvedAtom, and their loader types were the same shape under two names; they differed only in whether the load gates the parent route's match.

That axis is now which member you read, not which function you call:

  • .dataAtom<Promise<Data | Redirect | undefined>>, a plain async atom with no bearing on matching and no lifecycle to wire up. This is exactly what resolvedAtom was.
  • the atom itself — a route whose match is decided by the load, with the loaded object bound to name in values, using the preloadAsyncRoutes/hydrateAsyncRoutes/followAsyncRoutes lifecycle as before.

A single mode argument was rejected because the two uses return different kinds (data atom vs route atom); two thin wrappers were rejected because that is what the code already was.

Migration

beforeafter
resolvedAtom(route, load)asyncRouteAtom(route, name, load).data
followResolvedRedirects(store, atoms)followAsyncRedirects(store, dataAtoms)
preloadRoutes(store, routes)preloadAsyncRoutes(store, routes)
AsyncRouteAtom.lookupAsyncRouteAtom.data
Resolver<T, Data>, RouteLookup<T, Data>RouteLoader<T, Data>
resolvedAtom.tsdeleted; merged into asyncRouteAtom.ts

hydrateAsyncRoutes, followAsyncRoutes and AsyncRouteSnapshot are unchanged. No deprecated aliases — the packages are on the 2.0.0-beta line and CODING-STYLE.md bans pre-production compat hedging.

Stacking

Stacked on #90 (task-679-investigate-per-demo-scoped-store-context), which touches the same jarl-atoms files. Retargets to master automatically once #90 merges.

Style review (this PR)

Reviewed the producing branch against CODING-STYLE.md line by line — no changes were needed:

  • Commit history: 3 commits, each self-contained (impl+tests, docs+e2e migration, prose docs), all trailered Ticket: 675.
  • No archeology: the deleted resolvedAtom.ts carried a large "v1 porting history" comment block; it's gone, not carried forward.
  • oxfmt --check passes on every touched file (verified from the standing master worktree).
  • No stale references to resolvedAtom/RouteLookup/followResolvedRedirects/preloadRoutes/.lookup anywhere except CHANGELOG.md (historical, correct) and e2e/registry-smoke (see below).

Deliberate exceptions

  1. README.md is untouched even though it names asyncRouteAtom in prose. Verified this is not a missed reformat: README.md is explicitly listed in .prettierignore (which oxfmt honours), and ci.yml has no format:check step at all — nothing in CI or tooling scope covers this file.
  2. e2e/registry-smoke still uses resolvedAtom/followResolvedRedirects. It installs jarl-atoms@latest from npm to exercise the published tarballs, so it must keep matching npm until this change ships. Confirmed in ci.yml: that job's condition is github.event_name == 'workflow_dispatch' && inputs.registry_smoke — it never runs on a pull_request trigger, so it is genuinely outside this PR's CI path. It needs updating as part of the release that ships this change.

For OWNER

  • Release-strategy flag, not decided here: this ships as feat!, which .releaserc.json's major-version suppression currently turns into a minor, like every other breaking change so far. This is a removal from jarl-atoms' published surface, so it's a reasonable candidate for lifting that suppression — flagged, not decided.
  • e2e/registry-smoke needs updating as part of whatever release actually ships this change (see exception 2 above) — it will keep testing the old, still-published resolvedAtom API until then, which is correct until release day but not after.

🤖 Generated with Claude Code

`resolvedAtom` and `asyncRouteAtom` were two entry points over one mechanism:
`asyncRouteAtom` already built on `resolvedAtom`, and their loader types were the
same shape under two names. They differed only in whether the load gates the
parent route's match.
That axis is now which member you read, not which function you call.
`asyncRouteAtom(route, name, load)` exposes `.data` - a plain async atom of the
loaded value, with no bearing on matching and no lifecycle to wire up, exactly
what `resolvedAtom` was. Reading the returned atom as a route instead gates the
match on the load, as before. Promoting data loading to route gating is dropping
`.data`.
BREAKING CHANGE: `resolvedAtom` is removed. `resolvedAtom(route, load)` becomes
`asyncRouteAtom(route, name, load).data`, where `name` is what the loaded object
binds to if the route is ever used for gating. `followResolvedRedirects` is
renamed `followAsyncRedirects` and takes `.data` atoms. `preloadRoutes` is
renamed `preloadAsyncRoutes`, matching `hydrateAsyncRoutes`/`followAsyncRoutes`.
The `Resolver` and `RouteLookup` types are replaced by one `RouteLoader`, and
`AsyncRouteAtom.lookup` is renamed `data`. `hydrateAsyncRoutes`,
`followAsyncRoutes` and `AsyncRouteSnapshot` are unchanged.
Ticket: 675
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@randomdevpete
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(atoms)!: 675 — one asyncRouteAtom API for async route data - #98

Open
randomdevpete wants to merge 3 commits into
task-679-investigate-per-demo-scoped-store-contextfrom
task-675-coalesce-resolvedatom-and-asyncrouteatom
Open

feat(atoms)!: 675 — one asyncRouteAtom API for async route data#98
randomdevpete wants to merge 3 commits into
task-679-investigate-per-demo-scoped-store-contextfrom
task-675-coalesce-resolvedatom-and-asyncrouteatom

Conversation

@randomdevpete

Copy link
Copy Markdown
Owner

Restored PR.#95 was merged by mistake into task-679-investigate-per-demo-scoped-store-context; that merge has been reverted (679 is back at 1339a1a) and this branch restored unchanged at 12050ba. A merged PR cannot be reopened, so this PR replaces #95 with the identical branch and body. Anchor tags: presquash/task-679-.../premerge-95, presquash/task-675-.../restore-1.

Summary

Coalesces resolvedAtom and asyncRouteAtom into one constructor, asyncRouteAtom(route, name, load). asyncRouteAtom was already built on resolvedAtom, and their loader types were the same shape under two names; they differed only in whether the load gates the parent route's match.

That axis is now which member you read, not which function you call:

  • .dataAtom<Promise<Data | Redirect | undefined>>, a plain async atom with no bearing on matching and no lifecycle to wire up. This is exactly what resolvedAtom was.
  • the atom itself — a route whose match is decided by the load, with the loaded object bound to name in values, using the preloadAsyncRoutes/hydrateAsyncRoutes/followAsyncRoutes lifecycle as before.

A single mode argument was rejected because the two uses return different kinds (data atom vs route atom); two thin wrappers were rejected because that is what the code already was.

Migration

beforeafter
resolvedAtom(route, load)asyncRouteAtom(route, name, load).data
followResolvedRedirects(store, atoms)followAsyncRedirects(store, dataAtoms)
preloadRoutes(store, routes)preloadAsyncRoutes(store, routes)
AsyncRouteAtom.lookupAsyncRouteAtom.data
Resolver<T, Data>, RouteLookup<T, Data>RouteLoader<T, Data>
resolvedAtom.tsdeleted; merged into asyncRouteAtom.ts

hydrateAsyncRoutes, followAsyncRoutes and AsyncRouteSnapshot are unchanged. No deprecated aliases — the packages are on the 2.0.0-beta line and CODING-STYLE.md bans pre-production compat hedging.

Stacking

Stacked on #90 (task-679-investigate-per-demo-scoped-store-context), which touches the same jarl-atoms files. Retargets to master automatically once #90 merges.

Style review (this PR)

Reviewed the producing branch against CODING-STYLE.md line by line — no changes were needed:

  • Commit history: 3 commits, each self-contained (impl+tests, docs+e2e migration, prose docs), all trailered Ticket: 675.
  • No archeology: the deleted resolvedAtom.ts carried a large "v1 porting history" comment block; it's gone, not carried forward.
  • oxfmt --check passes on every touched file (verified from the standing master worktree).
  • No stale references to resolvedAtom/RouteLookup/followResolvedRedirects/preloadRoutes/.lookup anywhere except CHANGELOG.md (historical, correct) and e2e/registry-smoke (see below).

Deliberate exceptions

  1. README.md is untouched even though it names asyncRouteAtom in prose. Verified this is not a missed reformat: README.md is explicitly listed in .prettierignore (which oxfmt honours), and ci.yml has no format:check step at all — nothing in CI or tooling scope covers this file.
  2. e2e/registry-smoke still uses resolvedAtom/followResolvedRedirects. It installs jarl-atoms@latest from npm to exercise the published tarballs, so it must keep matching npm until this change ships. Confirmed in ci.yml: that job's condition is github.event_name == 'workflow_dispatch' && inputs.registry_smoke — it never runs on a pull_request trigger, so it is genuinely outside this PR's CI path. It needs updating as part of the release that ships this change.

For OWNER

  • Release-strategy flag, not decided here: this ships as feat!, which .releaserc.json's major-version suppression currently turns into a minor, like every other breaking change so far. This is a removal from jarl-atoms' published surface, so it's a reasonable candidate for lifting that suppression — flagged, not decided.
  • e2e/registry-smoke needs updating as part of whatever release actually ships this change (see exception 2 above) — it will keep testing the old, still-published resolvedAtom API until then, which is correct until release day but not after.

🤖 Generated with Claude Code

`resolvedAtom` and `asyncRouteAtom` were two entry points over one mechanism:
`asyncRouteAtom` already built on `resolvedAtom`, and their loader types were the
same shape under two names. They differed only in whether the load gates the
parent route's match.
That axis is now which member you read, not which function you call.
`asyncRouteAtom(route, name, load)` exposes `.data` - a plain async atom of the
loaded value, with no bearing on matching and no lifecycle to wire up, exactly
what `resolvedAtom` was. Reading the returned atom as a route instead gates the
match on the load, as before. Promoting data loading to route gating is dropping
`.data`.
BREAKING CHANGE: `resolvedAtom` is removed. `resolvedAtom(route, load)` becomes
`asyncRouteAtom(route, name, load).data`, where `name` is what the loaded object
binds to if the route is ever used for gating. `followResolvedRedirects` is
renamed `followAsyncRedirects` and takes `.data` atoms. `preloadRoutes` is
renamed `preloadAsyncRoutes`, matching `hydrateAsyncRoutes`/`followAsyncRoutes`.
The `Resolver` and `RouteLookup` types are replaced by one `RouteLoader`, and
`AsyncRouteAtom.lookup` is renamed `data`. `hydrateAsyncRoutes`,
`followAsyncRoutes` and `AsyncRouteSnapshot` are unchanged.
Ticket: 675
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@randomdevpete
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(atoms)!: 675 — one asyncRouteAtom API for async route data - #98

Open
randomdevpete wants to merge 3 commits into
task-679-investigate-per-demo-scoped-store-contextfrom
task-675-coalesce-resolvedatom-and-asyncrouteatom
Open

feat(atoms)!: 675 — one asyncRouteAtom API for async route data#98
randomdevpete wants to merge 3 commits into
task-679-investigate-per-demo-scoped-store-contextfrom
task-675-coalesce-resolvedatom-and-asyncrouteatom

Conversation

@randomdevpete

Copy link
Copy Markdown
Owner

Restored PR.#95 was merged by mistake into task-679-investigate-per-demo-scoped-store-context; that merge has been reverted (679 is back at 1339a1a) and this branch restored unchanged at 12050ba. A merged PR cannot be reopened, so this PR replaces #95 with the identical branch and body. Anchor tags: presquash/task-679-.../premerge-95, presquash/task-675-.../restore-1.

Summary

Coalesces resolvedAtom and asyncRouteAtom into one constructor, asyncRouteAtom(route, name, load). asyncRouteAtom was already built on resolvedAtom, and their loader types were the same shape under two names; they differed only in whether the load gates the parent route's match.

That axis is now which member you read, not which function you call:

  • .dataAtom<Promise<Data | Redirect | undefined>>, a plain async atom with no bearing on matching and no lifecycle to wire up. This is exactly what resolvedAtom was.
  • the atom itself — a route whose match is decided by the load, with the loaded object bound to name in values, using the preloadAsyncRoutes/hydrateAsyncRoutes/followAsyncRoutes lifecycle as before.

A single mode argument was rejected because the two uses return different kinds (data atom vs route atom); two thin wrappers were rejected because that is what the code already was.

Migration

beforeafter
resolvedAtom(route, load)asyncRouteAtom(route, name, load).data
followResolvedRedirects(store, atoms)followAsyncRedirects(store, dataAtoms)
preloadRoutes(store, routes)preloadAsyncRoutes(store, routes)
AsyncRouteAtom.lookupAsyncRouteAtom.data
Resolver<T, Data>, RouteLookup<T, Data>RouteLoader<T, Data>
resolvedAtom.tsdeleted; merged into asyncRouteAtom.ts

hydrateAsyncRoutes, followAsyncRoutes and AsyncRouteSnapshot are unchanged. No deprecated aliases — the packages are on the 2.0.0-beta line and CODING-STYLE.md bans pre-production compat hedging.

Stacking

Stacked on #90 (task-679-investigate-per-demo-scoped-store-context), which touches the same jarl-atoms files. Retargets to master automatically once #90 merges.

Style review (this PR)

Reviewed the producing branch against CODING-STYLE.md line by line — no changes were needed:

  • Commit history: 3 commits, each self-contained (impl+tests, docs+e2e migration, prose docs), all trailered Ticket: 675.
  • No archeology: the deleted resolvedAtom.ts carried a large "v1 porting history" comment block; it's gone, not carried forward.
  • oxfmt --check passes on every touched file (verified from the standing master worktree).
  • No stale references to resolvedAtom/RouteLookup/followResolvedRedirects/preloadRoutes/.lookup anywhere except CHANGELOG.md (historical, correct) and e2e/registry-smoke (see below).

Deliberate exceptions

  1. README.md is untouched even though it names asyncRouteAtom in prose. Verified this is not a missed reformat: README.md is explicitly listed in .prettierignore (which oxfmt honours), and ci.yml has no format:check step at all — nothing in CI or tooling scope covers this file.
  2. e2e/registry-smoke still uses resolvedAtom/followResolvedRedirects. It installs jarl-atoms@latest from npm to exercise the published tarballs, so it must keep matching npm until this change ships. Confirmed in ci.yml: that job's condition is github.event_name == 'workflow_dispatch' && inputs.registry_smoke — it never runs on a pull_request trigger, so it is genuinely outside this PR's CI path. It needs updating as part of the release that ships this change.

For OWNER

  • Release-strategy flag, not decided here: this ships as feat!, which .releaserc.json's major-version suppression currently turns into a minor, like every other breaking change so far. This is a removal from jarl-atoms' published surface, so it's a reasonable candidate for lifting that suppression — flagged, not decided.
  • e2e/registry-smoke needs updating as part of whatever release actually ships this change (see exception 2 above) — it will keep testing the old, still-published resolvedAtom API until then, which is correct until release day but not after.

🤖 Generated with Claude Code

`resolvedAtom` and `asyncRouteAtom` were two entry points over one mechanism:
`asyncRouteAtom` already built on `resolvedAtom`, and their loader types were the
same shape under two names. They differed only in whether the load gates the
parent route's match.
That axis is now which member you read, not which function you call.
`asyncRouteAtom(route, name, load)` exposes `.data` - a plain async atom of the
loaded value, with no bearing on matching and no lifecycle to wire up, exactly
what `resolvedAtom` was. Reading the returned atom as a route instead gates the
match on the load, as before. Promoting data loading to route gating is dropping
`.data`.
BREAKING CHANGE: `resolvedAtom` is removed. `resolvedAtom(route, load)` becomes
`asyncRouteAtom(route, name, load).data`, where `name` is what the loaded object
binds to if the route is ever used for gating. `followResolvedRedirects` is
renamed `followAsyncRedirects` and takes `.data` atoms. `preloadRoutes` is
renamed `preloadAsyncRoutes`, matching `hydrateAsyncRoutes`/`followAsyncRoutes`.
The `Resolver` and `RouteLookup` types are replaced by one `RouteLoader`, and
`AsyncRouteAtom.lookup` is renamed `data`. `hydrateAsyncRoutes`,
`followAsyncRoutes` and `AsyncRouteSnapshot` are unchanged.
Ticket: 675
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@randomdevpete
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(atoms)!: 675 — one asyncRouteAtom API for async route data - #98

Open
randomdevpete wants to merge 3 commits into
task-679-investigate-per-demo-scoped-store-contextfrom
task-675-coalesce-resolvedatom-and-asyncrouteatom
Open

feat(atoms)!: 675 — one asyncRouteAtom API for async route data#98
randomdevpete wants to merge 3 commits into
task-679-investigate-per-demo-scoped-store-contextfrom
task-675-coalesce-resolvedatom-and-asyncrouteatom

Conversation

@randomdevpete

Copy link
Copy Markdown
Owner

Restored PR.#95 was merged by mistake into task-679-investigate-per-demo-scoped-store-context; that merge has been reverted (679 is back at 1339a1a) and this branch restored unchanged at 12050ba. A merged PR cannot be reopened, so this PR replaces #95 with the identical branch and body. Anchor tags: presquash/task-679-.../premerge-95, presquash/task-675-.../restore-1.

Summary

Coalesces resolvedAtom and asyncRouteAtom into one constructor, asyncRouteAtom(route, name, load). asyncRouteAtom was already built on resolvedAtom, and their loader types were the same shape under two names; they differed only in whether the load gates the parent route's match.

That axis is now which member you read, not which function you call:

  • .dataAtom<Promise<Data | Redirect | undefined>>, a plain async atom with no bearing on matching and no lifecycle to wire up. This is exactly what resolvedAtom was.
  • the atom itself — a route whose match is decided by the load, with the loaded object bound to name in values, using the preloadAsyncRoutes/hydrateAsyncRoutes/followAsyncRoutes lifecycle as before.

A single mode argument was rejected because the two uses return different kinds (data atom vs route atom); two thin wrappers were rejected because that is what the code already was.

Migration

beforeafter
resolvedAtom(route, load)asyncRouteAtom(route, name, load).data
followResolvedRedirects(store, atoms)followAsyncRedirects(store, dataAtoms)
preloadRoutes(store, routes)preloadAsyncRoutes(store, routes)
AsyncRouteAtom.lookupAsyncRouteAtom.data
Resolver<T, Data>, RouteLookup<T, Data>RouteLoader<T, Data>
resolvedAtom.tsdeleted; merged into asyncRouteAtom.ts

hydrateAsyncRoutes, followAsyncRoutes and AsyncRouteSnapshot are unchanged. No deprecated aliases — the packages are on the 2.0.0-beta line and CODING-STYLE.md bans pre-production compat hedging.

Stacking

Stacked on #90 (task-679-investigate-per-demo-scoped-store-context), which touches the same jarl-atoms files. Retargets to master automatically once #90 merges.

Style review (this PR)

Reviewed the producing branch against CODING-STYLE.md line by line — no changes were needed:

  • Commit history: 3 commits, each self-contained (impl+tests, docs+e2e migration, prose docs), all trailered Ticket: 675.
  • No archeology: the deleted resolvedAtom.ts carried a large "v1 porting history" comment block; it's gone, not carried forward.
  • oxfmt --check passes on every touched file (verified from the standing master worktree).
  • No stale references to resolvedAtom/RouteLookup/followResolvedRedirects/preloadRoutes/.lookup anywhere except CHANGELOG.md (historical, correct) and e2e/registry-smoke (see below).

Deliberate exceptions

  1. README.md is untouched even though it names asyncRouteAtom in prose. Verified this is not a missed reformat: README.md is explicitly listed in .prettierignore (which oxfmt honours), and ci.yml has no format:check step at all — nothing in CI or tooling scope covers this file.
  2. e2e/registry-smoke still uses resolvedAtom/followResolvedRedirects. It installs jarl-atoms@latest from npm to exercise the published tarballs, so it must keep matching npm until this change ships. Confirmed in ci.yml: that job's condition is github.event_name == 'workflow_dispatch' && inputs.registry_smoke — it never runs on a pull_request trigger, so it is genuinely outside this PR's CI path. It needs updating as part of the release that ships this change.

For OWNER

  • Release-strategy flag, not decided here: this ships as feat!, which .releaserc.json's major-version suppression currently turns into a minor, like every other breaking change so far. This is a removal from jarl-atoms' published surface, so it's a reasonable candidate for lifting that suppression — flagged, not decided.
  • e2e/registry-smoke needs updating as part of whatever release actually ships this change (see exception 2 above) — it will keep testing the old, still-published resolvedAtom API until then, which is correct until release day but not after.

🤖 Generated with Claude Code

`resolvedAtom` and `asyncRouteAtom` were two entry points over one mechanism:
`asyncRouteAtom` already built on `resolvedAtom`, and their loader types were the
same shape under two names. They differed only in whether the load gates the
parent route's match.
That axis is now which member you read, not which function you call.
`asyncRouteAtom(route, name, load)` exposes `.data` - a plain async atom of the
loaded value, with no bearing on matching and no lifecycle to wire up, exactly
what `resolvedAtom` was. Reading the returned atom as a route instead gates the
match on the load, as before. Promoting data loading to route gating is dropping
`.data`.
BREAKING CHANGE: `resolvedAtom` is removed. `resolvedAtom(route, load)` becomes
`asyncRouteAtom(route, name, load).data`, where `name` is what the loaded object
binds to if the route is ever used for gating. `followResolvedRedirects` is
renamed `followAsyncRedirects` and takes `.data` atoms. `preloadRoutes` is
renamed `preloadAsyncRoutes`, matching `hydrateAsyncRoutes`/`followAsyncRoutes`.
The `Resolver` and `RouteLookup` types are replaced by one `RouteLoader`, and
`AsyncRouteAtom.lookup` is renamed `data`. `hydrateAsyncRoutes`,
`followAsyncRoutes` and `AsyncRouteSnapshot` are unchanged.
Ticket: 675
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@randomdevpete
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(atoms)!: 675 — one asyncRouteAtom API for async route data - #98

Open
randomdevpete wants to merge 3 commits into
task-679-investigate-per-demo-scoped-store-contextfrom
task-675-coalesce-resolvedatom-and-asyncrouteatom
Open

feat(atoms)!: 675 — one asyncRouteAtom API for async route data#98
randomdevpete wants to merge 3 commits into
task-679-investigate-per-demo-scoped-store-contextfrom
task-675-coalesce-resolvedatom-and-asyncrouteatom

Conversation

@randomdevpete

Copy link
Copy Markdown
Owner

Restored PR.#95 was merged by mistake into task-679-investigate-per-demo-scoped-store-context; that merge has been reverted (679 is back at 1339a1a) and this branch restored unchanged at 12050ba. A merged PR cannot be reopened, so this PR replaces #95 with the identical branch and body. Anchor tags: presquash/task-679-.../premerge-95, presquash/task-675-.../restore-1.

Summary

Coalesces resolvedAtom and asyncRouteAtom into one constructor, asyncRouteAtom(route, name, load). asyncRouteAtom was already built on resolvedAtom, and their loader types were the same shape under two names; they differed only in whether the load gates the parent route's match.

That axis is now which member you read, not which function you call:

  • .dataAtom<Promise<Data | Redirect | undefined>>, a plain async atom with no bearing on matching and no lifecycle to wire up. This is exactly what resolvedAtom was.
  • the atom itself — a route whose match is decided by the load, with the loaded object bound to name in values, using the preloadAsyncRoutes/hydrateAsyncRoutes/followAsyncRoutes lifecycle as before.

A single mode argument was rejected because the two uses return different kinds (data atom vs route atom); two thin wrappers were rejected because that is what the code already was.

Migration

beforeafter
resolvedAtom(route, load)asyncRouteAtom(route, name, load).data
followResolvedRedirects(store, atoms)followAsyncRedirects(store, dataAtoms)
preloadRoutes(store, routes)preloadAsyncRoutes(store, routes)
AsyncRouteAtom.lookupAsyncRouteAtom.data
Resolver<T, Data>, RouteLookup<T, Data>RouteLoader<T, Data>
resolvedAtom.tsdeleted; merged into asyncRouteAtom.ts

hydrateAsyncRoutes, followAsyncRoutes and AsyncRouteSnapshot are unchanged. No deprecated aliases — the packages are on the 2.0.0-beta line and CODING-STYLE.md bans pre-production compat hedging.

Stacking

Stacked on #90 (task-679-investigate-per-demo-scoped-store-context), which touches the same jarl-atoms files. Retargets to master automatically once #90 merges.

Style review (this PR)

Reviewed the producing branch against CODING-STYLE.md line by line — no changes were needed:

  • Commit history: 3 commits, each self-contained (impl+tests, docs+e2e migration, prose docs), all trailered Ticket: 675.
  • No archeology: the deleted resolvedAtom.ts carried a large "v1 porting history" comment block; it's gone, not carried forward.
  • oxfmt --check passes on every touched file (verified from the standing master worktree).
  • No stale references to resolvedAtom/RouteLookup/followResolvedRedirects/preloadRoutes/.lookup anywhere except CHANGELOG.md (historical, correct) and e2e/registry-smoke (see below).

Deliberate exceptions

  1. README.md is untouched even though it names asyncRouteAtom in prose. Verified this is not a missed reformat: README.md is explicitly listed in .prettierignore (which oxfmt honours), and ci.yml has no format:check step at all — nothing in CI or tooling scope covers this file.
  2. e2e/registry-smoke still uses resolvedAtom/followResolvedRedirects. It installs jarl-atoms@latest from npm to exercise the published tarballs, so it must keep matching npm until this change ships. Confirmed in ci.yml: that job's condition is github.event_name == 'workflow_dispatch' && inputs.registry_smoke — it never runs on a pull_request trigger, so it is genuinely outside this PR's CI path. It needs updating as part of the release that ships this change.

For OWNER

  • Release-strategy flag, not decided here: this ships as feat!, which .releaserc.json's major-version suppression currently turns into a minor, like every other breaking change so far. This is a removal from jarl-atoms' published surface, so it's a reasonable candidate for lifting that suppression — flagged, not decided.
  • e2e/registry-smoke needs updating as part of whatever release actually ships this change (see exception 2 above) — it will keep testing the old, still-published resolvedAtom API until then, which is correct until release day but not after.

🤖 Generated with Claude Code

`resolvedAtom` and `asyncRouteAtom` were two entry points over one mechanism:
`asyncRouteAtom` already built on `resolvedAtom`, and their loader types were the
same shape under two names. They differed only in whether the load gates the
parent route's match.
That axis is now which member you read, not which function you call.
`asyncRouteAtom(route, name, load)` exposes `.data` - a plain async atom of the
loaded value, with no bearing on matching and no lifecycle to wire up, exactly
what `resolvedAtom` was. Reading the returned atom as a route instead gates the
match on the load, as before. Promoting data loading to route gating is dropping
`.data`.
BREAKING CHANGE: `resolvedAtom` is removed. `resolvedAtom(route, load)` becomes
`asyncRouteAtom(route, name, load).data`, where `name` is what the loaded object
binds to if the route is ever used for gating. `followResolvedRedirects` is
renamed `followAsyncRedirects` and takes `.data` atoms. `preloadRoutes` is
renamed `preloadAsyncRoutes`, matching `hydrateAsyncRoutes`/`followAsyncRoutes`.
The `Resolver` and `RouteLookup` types are replaced by one `RouteLoader`, and
`AsyncRouteAtom.lookup` is renamed `data`. `hydrateAsyncRoutes`,
`followAsyncRoutes` and `AsyncRouteSnapshot` are unchanged.
Ticket: 675
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@randomdevpete
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(atoms)!: 675 — one asyncRouteAtom API for async route data - #98

Open
randomdevpete wants to merge 3 commits into
task-679-investigate-per-demo-scoped-store-contextfrom
task-675-coalesce-resolvedatom-and-asyncrouteatom
Open

feat(atoms)!: 675 — one asyncRouteAtom API for async route data#98
randomdevpete wants to merge 3 commits into
task-679-investigate-per-demo-scoped-store-contextfrom
task-675-coalesce-resolvedatom-and-asyncrouteatom

Conversation

@randomdevpete

Copy link
Copy Markdown
Owner

Restored PR.#95 was merged by mistake into task-679-investigate-per-demo-scoped-store-context; that merge has been reverted (679 is back at 1339a1a) and this branch restored unchanged at 12050ba. A merged PR cannot be reopened, so this PR replaces #95 with the identical branch and body. Anchor tags: presquash/task-679-.../premerge-95, presquash/task-675-.../restore-1.

Summary

Coalesces resolvedAtom and asyncRouteAtom into one constructor, asyncRouteAtom(route, name, load). asyncRouteAtom was already built on resolvedAtom, and their loader types were the same shape under two names; they differed only in whether the load gates the parent route's match.

That axis is now which member you read, not which function you call:

  • .dataAtom<Promise<Data | Redirect | undefined>>, a plain async atom with no bearing on matching and no lifecycle to wire up. This is exactly what resolvedAtom was.
  • the atom itself — a route whose match is decided by the load, with the loaded object bound to name in values, using the preloadAsyncRoutes/hydrateAsyncRoutes/followAsyncRoutes lifecycle as before.

A single mode argument was rejected because the two uses return different kinds (data atom vs route atom); two thin wrappers were rejected because that is what the code already was.

Migration

beforeafter
resolvedAtom(route, load)asyncRouteAtom(route, name, load).data
followResolvedRedirects(store, atoms)followAsyncRedirects(store, dataAtoms)
preloadRoutes(store, routes)preloadAsyncRoutes(store, routes)
AsyncRouteAtom.lookupAsyncRouteAtom.data
Resolver<T, Data>, RouteLookup<T, Data>RouteLoader<T, Data>
resolvedAtom.tsdeleted; merged into asyncRouteAtom.ts

hydrateAsyncRoutes, followAsyncRoutes and AsyncRouteSnapshot are unchanged. No deprecated aliases — the packages are on the 2.0.0-beta line and CODING-STYLE.md bans pre-production compat hedging.

Stacking

Stacked on #90 (task-679-investigate-per-demo-scoped-store-context), which touches the same jarl-atoms files. Retargets to master automatically once #90 merges.

Style review (this PR)

Reviewed the producing branch against CODING-STYLE.md line by line — no changes were needed:

  • Commit history: 3 commits, each self-contained (impl+tests, docs+e2e migration, prose docs), all trailered Ticket: 675.
  • No archeology: the deleted resolvedAtom.ts carried a large "v1 porting history" comment block; it's gone, not carried forward.
  • oxfmt --check passes on every touched file (verified from the standing master worktree).
  • No stale references to resolvedAtom/RouteLookup/followResolvedRedirects/preloadRoutes/.lookup anywhere except CHANGELOG.md (historical, correct) and e2e/registry-smoke (see below).

Deliberate exceptions

  1. README.md is untouched even though it names asyncRouteAtom in prose. Verified this is not a missed reformat: README.md is explicitly listed in .prettierignore (which oxfmt honours), and ci.yml has no format:check step at all — nothing in CI or tooling scope covers this file.
  2. e2e/registry-smoke still uses resolvedAtom/followResolvedRedirects. It installs jarl-atoms@latest from npm to exercise the published tarballs, so it must keep matching npm until this change ships. Confirmed in ci.yml: that job's condition is github.event_name == 'workflow_dispatch' && inputs.registry_smoke — it never runs on a pull_request trigger, so it is genuinely outside this PR's CI path. It needs updating as part of the release that ships this change.

For OWNER

  • Release-strategy flag, not decided here: this ships as feat!, which .releaserc.json's major-version suppression currently turns into a minor, like every other breaking change so far. This is a removal from jarl-atoms' published surface, so it's a reasonable candidate for lifting that suppression — flagged, not decided.
  • e2e/registry-smoke needs updating as part of whatever release actually ships this change (see exception 2 above) — it will keep testing the old, still-published resolvedAtom API until then, which is correct until release day but not after.

🤖 Generated with Claude Code

`resolvedAtom` and `asyncRouteAtom` were two entry points over one mechanism:
`asyncRouteAtom` already built on `resolvedAtom`, and their loader types were the
same shape under two names. They differed only in whether the load gates the
parent route's match.
That axis is now which member you read, not which function you call.
`asyncRouteAtom(route, name, load)` exposes `.data` - a plain async atom of the
loaded value, with no bearing on matching and no lifecycle to wire up, exactly
what `resolvedAtom` was. Reading the returned atom as a route instead gates the
match on the load, as before. Promoting data loading to route gating is dropping
`.data`.
BREAKING CHANGE: `resolvedAtom` is removed. `resolvedAtom(route, load)` becomes
`asyncRouteAtom(route, name, load).data`, where `name` is what the loaded object
binds to if the route is ever used for gating. `followResolvedRedirects` is
renamed `followAsyncRedirects` and takes `.data` atoms. `preloadRoutes` is
renamed `preloadAsyncRoutes`, matching `hydrateAsyncRoutes`/`followAsyncRoutes`.
The `Resolver` and `RouteLookup` types are replaced by one `RouteLoader`, and
`AsyncRouteAtom.lookup` is renamed `data`. `hydrateAsyncRoutes`,
`followAsyncRoutes` and `AsyncRouteSnapshot` are unchanged.
Ticket: 675
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@randomdevpete
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(atoms)!: 675 — one asyncRouteAtom API for async route data - #98

Open
randomdevpete wants to merge 3 commits into
task-679-investigate-per-demo-scoped-store-contextfrom
task-675-coalesce-resolvedatom-and-asyncrouteatom
Open

feat(atoms)!: 675 — one asyncRouteAtom API for async route data#98
randomdevpete wants to merge 3 commits into
task-679-investigate-per-demo-scoped-store-contextfrom
task-675-coalesce-resolvedatom-and-asyncrouteatom

Conversation

@randomdevpete

Copy link
Copy Markdown
Owner

Restored PR.#95 was merged by mistake into task-679-investigate-per-demo-scoped-store-context; that merge has been reverted (679 is back at 1339a1a) and this branch restored unchanged at 12050ba. A merged PR cannot be reopened, so this PR replaces #95 with the identical branch and body. Anchor tags: presquash/task-679-.../premerge-95, presquash/task-675-.../restore-1.

Summary

Coalesces resolvedAtom and asyncRouteAtom into one constructor, asyncRouteAtom(route, name, load). asyncRouteAtom was already built on resolvedAtom, and their loader types were the same shape under two names; they differed only in whether the load gates the parent route's match.

That axis is now which member you read, not which function you call:

  • .dataAtom<Promise<Data | Redirect | undefined>>, a plain async atom with no bearing on matching and no lifecycle to wire up. This is exactly what resolvedAtom was.
  • the atom itself — a route whose match is decided by the load, with the loaded object bound to name in values, using the preloadAsyncRoutes/hydrateAsyncRoutes/followAsyncRoutes lifecycle as before.

A single mode argument was rejected because the two uses return different kinds (data atom vs route atom); two thin wrappers were rejected because that is what the code already was.

Migration

beforeafter
resolvedAtom(route, load)asyncRouteAtom(route, name, load).data
followResolvedRedirects(store, atoms)followAsyncRedirects(store, dataAtoms)
preloadRoutes(store, routes)preloadAsyncRoutes(store, routes)
AsyncRouteAtom.lookupAsyncRouteAtom.data
Resolver<T, Data>, RouteLookup<T, Data>RouteLoader<T, Data>
resolvedAtom.tsdeleted; merged into asyncRouteAtom.ts

hydrateAsyncRoutes, followAsyncRoutes and AsyncRouteSnapshot are unchanged. No deprecated aliases — the packages are on the 2.0.0-beta line and CODING-STYLE.md bans pre-production compat hedging.

Stacking

Stacked on #90 (task-679-investigate-per-demo-scoped-store-context), which touches the same jarl-atoms files. Retargets to master automatically once #90 merges.

Style review (this PR)

Reviewed the producing branch against CODING-STYLE.md line by line — no changes were needed:

  • Commit history: 3 commits, each self-contained (impl+tests, docs+e2e migration, prose docs), all trailered Ticket: 675.
  • No archeology: the deleted resolvedAtom.ts carried a large "v1 porting history" comment block; it's gone, not carried forward.
  • oxfmt --check passes on every touched file (verified from the standing master worktree).
  • No stale references to resolvedAtom/RouteLookup/followResolvedRedirects/preloadRoutes/.lookup anywhere except CHANGELOG.md (historical, correct) and e2e/registry-smoke (see below).

Deliberate exceptions

  1. README.md is untouched even though it names asyncRouteAtom in prose. Verified this is not a missed reformat: README.md is explicitly listed in .prettierignore (which oxfmt honours), and ci.yml has no format:check step at all — nothing in CI or tooling scope covers this file.
  2. e2e/registry-smoke still uses resolvedAtom/followResolvedRedirects. It installs jarl-atoms@latest from npm to exercise the published tarballs, so it must keep matching npm until this change ships. Confirmed in ci.yml: that job's condition is github.event_name == 'workflow_dispatch' && inputs.registry_smoke — it never runs on a pull_request trigger, so it is genuinely outside this PR's CI path. It needs updating as part of the release that ships this change.

For OWNER

  • Release-strategy flag, not decided here: this ships as feat!, which .releaserc.json's major-version suppression currently turns into a minor, like every other breaking change so far. This is a removal from jarl-atoms' published surface, so it's a reasonable candidate for lifting that suppression — flagged, not decided.
  • e2e/registry-smoke needs updating as part of whatever release actually ships this change (see exception 2 above) — it will keep testing the old, still-published resolvedAtom API until then, which is correct until release day but not after.

🤖 Generated with Claude Code

`resolvedAtom` and `asyncRouteAtom` were two entry points over one mechanism:
`asyncRouteAtom` already built on `resolvedAtom`, and their loader types were the
same shape under two names. They differed only in whether the load gates the
parent route's match.
That axis is now which member you read, not which function you call.
`asyncRouteAtom(route, name, load)` exposes `.data` - a plain async atom of the
loaded value, with no bearing on matching and no lifecycle to wire up, exactly
what `resolvedAtom` was. Reading the returned atom as a route instead gates the
match on the load, as before. Promoting data loading to route gating is dropping
`.data`.
BREAKING CHANGE: `resolvedAtom` is removed. `resolvedAtom(route, load)` becomes
`asyncRouteAtom(route, name, load).data`, where `name` is what the loaded object
binds to if the route is ever used for gating. `followResolvedRedirects` is
renamed `followAsyncRedirects` and takes `.data` atoms. `preloadRoutes` is
renamed `preloadAsyncRoutes`, matching `hydrateAsyncRoutes`/`followAsyncRoutes`.
The `Resolver` and `RouteLookup` types are replaced by one `RouteLoader`, and
`AsyncRouteAtom.lookup` is renamed `data`. `hydrateAsyncRoutes`,
`followAsyncRoutes` and `AsyncRouteSnapshot` are unchanged.
Ticket: 675
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@randomdevpete
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(atoms)!: 675 — one asyncRouteAtom API for async route data - #98

Open
randomdevpete wants to merge 3 commits into
task-679-investigate-per-demo-scoped-store-contextfrom
task-675-coalesce-resolvedatom-and-asyncrouteatom
Open

feat(atoms)!: 675 — one asyncRouteAtom API for async route data#98
randomdevpete wants to merge 3 commits into
task-679-investigate-per-demo-scoped-store-contextfrom
task-675-coalesce-resolvedatom-and-asyncrouteatom

Conversation

@randomdevpete

Copy link
Copy Markdown
Owner

Restored PR.#95 was merged by mistake into task-679-investigate-per-demo-scoped-store-context; that merge has been reverted (679 is back at 1339a1a) and this branch restored unchanged at 12050ba. A merged PR cannot be reopened, so this PR replaces #95 with the identical branch and body. Anchor tags: presquash/task-679-.../premerge-95, presquash/task-675-.../restore-1.

Summary

Coalesces resolvedAtom and asyncRouteAtom into one constructor, asyncRouteAtom(route, name, load). asyncRouteAtom was already built on resolvedAtom, and their loader types were the same shape under two names; they differed only in whether the load gates the parent route's match.

That axis is now which member you read, not which function you call:

  • .dataAtom<Promise<Data | Redirect | undefined>>, a plain async atom with no bearing on matching and no lifecycle to wire up. This is exactly what resolvedAtom was.
  • the atom itself — a route whose match is decided by the load, with the loaded object bound to name in values, using the preloadAsyncRoutes/hydrateAsyncRoutes/followAsyncRoutes lifecycle as before.

A single mode argument was rejected because the two uses return different kinds (data atom vs route atom); two thin wrappers were rejected because that is what the code already was.

Migration

beforeafter
resolvedAtom(route, load)asyncRouteAtom(route, name, load).data
followResolvedRedirects(store, atoms)followAsyncRedirects(store, dataAtoms)
preloadRoutes(store, routes)preloadAsyncRoutes(store, routes)
AsyncRouteAtom.lookupAsyncRouteAtom.data
Resolver<T, Data>, RouteLookup<T, Data>RouteLoader<T, Data>
resolvedAtom.tsdeleted; merged into asyncRouteAtom.ts

hydrateAsyncRoutes, followAsyncRoutes and AsyncRouteSnapshot are unchanged. No deprecated aliases — the packages are on the 2.0.0-beta line and CODING-STYLE.md bans pre-production compat hedging.

Stacking

Stacked on #90 (task-679-investigate-per-demo-scoped-store-context), which touches the same jarl-atoms files. Retargets to master automatically once #90 merges.

Style review (this PR)

Reviewed the producing branch against CODING-STYLE.md line by line — no changes were needed:

  • Commit history: 3 commits, each self-contained (impl+tests, docs+e2e migration, prose docs), all trailered Ticket: 675.
  • No archeology: the deleted resolvedAtom.ts carried a large "v1 porting history" comment block; it's gone, not carried forward.
  • oxfmt --check passes on every touched file (verified from the standing master worktree).
  • No stale references to resolvedAtom/RouteLookup/followResolvedRedirects/preloadRoutes/.lookup anywhere except CHANGELOG.md (historical, correct) and e2e/registry-smoke (see below).

Deliberate exceptions

  1. README.md is untouched even though it names asyncRouteAtom in prose. Verified this is not a missed reformat: README.md is explicitly listed in .prettierignore (which oxfmt honours), and ci.yml has no format:check step at all — nothing in CI or tooling scope covers this file.
  2. e2e/registry-smoke still uses resolvedAtom/followResolvedRedirects. It installs jarl-atoms@latest from npm to exercise the published tarballs, so it must keep matching npm until this change ships. Confirmed in ci.yml: that job's condition is github.event_name == 'workflow_dispatch' && inputs.registry_smoke — it never runs on a pull_request trigger, so it is genuinely outside this PR's CI path. It needs updating as part of the release that ships this change.

For OWNER

  • Release-strategy flag, not decided here: this ships as feat!, which .releaserc.json's major-version suppression currently turns into a minor, like every other breaking change so far. This is a removal from jarl-atoms' published surface, so it's a reasonable candidate for lifting that suppression — flagged, not decided.
  • e2e/registry-smoke needs updating as part of whatever release actually ships this change (see exception 2 above) — it will keep testing the old, still-published resolvedAtom API until then, which is correct until release day but not after.

🤖 Generated with Claude Code

`resolvedAtom` and `asyncRouteAtom` were two entry points over one mechanism:
`asyncRouteAtom` already built on `resolvedAtom`, and their loader types were the
same shape under two names. They differed only in whether the load gates the
parent route's match.
That axis is now which member you read, not which function you call.
`asyncRouteAtom(route, name, load)` exposes `.data` - a plain async atom of the
loaded value, with no bearing on matching and no lifecycle to wire up, exactly
what `resolvedAtom` was. Reading the returned atom as a route instead gates the
match on the load, as before. Promoting data loading to route gating is dropping
`.data`.
BREAKING CHANGE: `resolvedAtom` is removed. `resolvedAtom(route, load)` becomes
`asyncRouteAtom(route, name, load).data`, where `name` is what the loaded object
binds to if the route is ever used for gating. `followResolvedRedirects` is
renamed `followAsyncRedirects` and takes `.data` atoms. `preloadRoutes` is
renamed `preloadAsyncRoutes`, matching `hydrateAsyncRoutes`/`followAsyncRoutes`.
The `Resolver` and `RouteLookup` types are replaced by one `RouteLoader`, and
`AsyncRouteAtom.lookup` is renamed `data`. `hydrateAsyncRoutes`,
`followAsyncRoutes` and `AsyncRouteSnapshot` are unchanged.
Ticket: 675
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@randomdevpete