Repository navigation
refactor: give each app page its own data scope - #289
Merged
Merged
Conversation
…ge's params Resource watchers ran before AppContainer's route watcher, so leaving /category/:category for / refetched with category=undefined. Stop them in a beforeEach guard when the destination is a different page. Fixes frappe#284
The beforeEach guard stopped resource watchers before the navigation was confirmed, so a cancelled navigation left the current page's resources dead. A sync route watcher runs loadPage's teardown before the resource watchers flush, and only once the route has actually changed.
getEvaluatedFilters sets a filter to undefined on purpose so it is dropped from the request, which Filters does not allow.
Page data (variables, resources, page script and the contexts bindings evaluate against) lived in the code store, a singleton, so there could only ever be one page's data at a time. Move it into createPageScope(), which returns one page's data as a reactive object. The code store now holds the route, the router and the active scope, and components read page data through usePageScope(). Page script hot updates go to the active scope. There is still a single scope that each page load resets in place, so behaviour is unchanged.
A page switch reset the single page scope while the old blocks were still on screen, so they re-rendered against empty data: the app flashed blank between pages and threw on bindings like rows.map. The app renderer now loads the next page into a new scope and activates it in the same tick as the block swap, so the page on screen keeps its own data until it is replaced. The new page's script and resources also set up against their own scope instead of the outgoing page's, and a failed or superseded load is torn down without touching the active page. Fixes frappe#287
Contributor
|
setPageVariables, setPageResources and teardownPage moved from the code store to the page scope.
ruchamahabal
force-pushed
the
fix-render
branch
from
October 5, 2026 18:00
ac413d3 to
d764908
Compare
A router beforeResolve guard (pageLoader.ts) builds the next page's scope before the navigation completes, so the page being left stays untouched and the new one mounts with every binding resolved. AppPage provides its scope and pins its own route, so leaving a page never refetches with the next page's params. The code store is now editor-only. Fixes frappe#284, fixes frappe#287
Reading the preview window's location is cross-origin in dev, and a reused named tab is reloaded in the background without focus.
- navigating again while a page is still loading no longer breaks the new page - a page that fails to load frees its data sources - a preview reload is skipped if you already moved to another page
loadPage and discardPage for the router guards, useLoadedPage for AppPage. The pinned-route watcher moves into useLoadedPage with the handoff.
Neither is a store or a router concern: both describe one rendered page.
Vite hoists renderer.html's built script tag out of the Jinja else-branch, so a developer-mode site ran the built renderer and the dev server's renderer together and flashed the error page on reload. A post transformIndexHtml hook wraps the tag in the Jinja condition again.
The first visit fetches the definition. Later visits mount from the cache at once and refresh it in the background, so a republished page shows on the next visit. The preview keeps fetching so draft saves show.
ruchamahabal
force-pushed
the
fix-render
branch
from
October 8, 2026 10:17
cf6dfd4 to
2d37ef3
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.
Before
A rendered app keeps the data of the current page in one shared store. When the user goes to a different page, two things go wrong:
get_public_article?name=undefined(A page being left refetches its resources with the next page's route params #284).pr289-before.mp4
After
pr289-after.mp4
Each page now has its own data. The router prepares a page before it shows it.
page/pageScope.tscreates one scope for one page. A scope has the variables, the resources and the page script of that page. The code is the oldcodeStorebody, moved without changes.page/pageLoader.tsruns in abeforeResolveguard. It fetches the page, creates its scope, loads the script and builds the block tree. The page that the user leaves stays on the screen and keeps its data until the new page is ready.AppPage.vuereplacesAppContainer.vue. One instance shows one page. It takes the prepared page, provides the scope to its blocks and tears the scope down when it unmounts.usePageScope()gives a component the scope of its page. In the editor it gives the editor's one scope fromcodeStore.router.replaceof the same URL. The page mounts again with a fresh scope.Behavior
router.tshooks run before Studio's guards, as before.Closes #284, Closes #287