You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On both production (https://nanodash.knowledgepixels.com) and locally, when a user partially fills a form (e.g. on /publish?template=...) and presses browser refresh (F5), the page reloads with all fields blank. The previously entered values are gone.
This used to work: refresh restored the partly-filled form.
History / root cause
There are two relevant commits:
e03c61594 (PR #401, 2026-03-16) — fix: use ONE_PASS_RENDER so browser refresh creates fresh pages. This switched Wicket's render strategy from the default REDIRECT_TO_BUFFER to ONE_PASS_RENDER in WicketApplication.java. The commit message:
With the default REDIRECT_TO_BUFFER strategy, Wicket added page instance info to the URL. Browser refresh then loaded the stored page (with its already-built component tree) instead of creating a new one, so updated data from singletons like Space was not reflected in the HTML. Switching to ONE_PASS_RENDER keeps URLs clean, so F5 always creates a new page instance that reads current data.
063c82c2 (PR #449, 2026-04-22) — refactor: drop formobj, use PageReference for preview Back. This removed the formobj session-stored form-map workaround that had been compensating for the lost state in PublishPage since 8d1f2c5a (2025-06-05).
ONE_PASS_RENDER was introduced to solve a real problem: pages held direct references to mutable singleton data (e.g. a Space object), so after the page was restored from the page store on F5, the HTML rendered from stale fields rather than reflecting current singleton state.
REDIRECT_TO_BUFFER and ONE_PASS_RENDER are opposite trade-offs of the same setting:
Strategy
F5 behavior
Form state on F5
Singleton data on F5
REDIRECT_TO_BUFFER (default)
restore stored page
preserved
stale if held by reference
ONE_PASS_RENDER (current)
build fresh page
lost
fresh
You cannot have both via the render-strategy knob alone.
Proposed solution
The Wicket-idiomatic fix is to leave the render strategy at the default REDIRECT_TO_BUFFER and stop holding live data references in the component tree. Use LoadableDetachableModel (LDM) for anything fetched from a singleton/registry.
An LDM serializes only the identifier (e.g. a space id) and detaches at the end of every request. On the next render it re-loads via load(). So even when Wicket restores a stored page tree on F5, anything wrapped in an LDM re-fetches from the singleton before rendering.
This gives us both:
F5 preserves the stored page instance → form state intact
LDMs re-fetch on each render → singleton data always fresh
Concrete pattern (e.g. for Space)
```java
IModel spaceModel = new LoadableDetachableModel() { @OverRide protected Space load() {
return SpaceRepository.get().findById(spaceId); // only spaceId is serialized
}
};
```
Then pass spaceModel to components or use PropertyModel.of(spaceModel, \"label\"). Label, ListView, etc. all accept IModel natively.
page/SpacePage.java:53 — private final Space space field captures snapshot.
page/MaintainedResourcePage.java:41 — same pattern with MaintainedResource resource.
Fix ExplorePage.java:129-133. Replace Model.of(\"back to \" + …findById(contextId).getLabel()) with an LDM that re-resolves the label.
Fix HomePage.java:73. Replace the findById(...) snapshot passed to ViewList with an LDM if the home resource is editable mid-session.
Remove the ONE_PASS_RENDER setting from WicketApplication.java:139.
Verify by F5-testing. Other pages that hold singleton references (ResourcePartPage, SearchPage, DownloadRdfPage, UserPage, ChannelPage, ListPage, PreviewPage, ViewPage, SpaceListPage) are medium-risk — most hold data that is effectively immutable within a session. Convert any that show stale data in testing, following the same LDM pattern.
Wins of the revert
These form pages will immediately get F5-preserves-state behavior back:
PublishPage
GenPublishPage
All forms under page/connector/
Notes
This issue is independent of (but in the same neighborhood as) #271 (fixed in PR #455), which dealt with shared template-component models being overwritten by URL params.
The audit above lists only file:line references for the high-risk pages. A fuller list of medium-risk pages is available on request.
Symptom
On both production (https://nanodash.knowledgepixels.com) and locally, when a user partially fills a form (e.g. on
/publish?template=...) and presses browser refresh (F5), the page reloads with all fields blank. The previously entered values are gone.This used to work: refresh restored the partly-filled form.
History / root cause
There are two relevant commits:
e03c61594(PR #401, 2026-03-16) —fix: use ONE_PASS_RENDER so browser refresh creates fresh pages. This switched Wicket's render strategy from the defaultREDIRECT_TO_BUFFERtoONE_PASS_RENDERinWicketApplication.java. The commit message:063c82c2(PR #449, 2026-04-22) —refactor: drop formobj, use PageReference for preview Back. This removed theformobjsession-stored form-map workaround that had been compensating for the lost state inPublishPagesince8d1f2c5a(2025-06-05).So the chain is:
REDIRECT_TO_BUFFER— URL carries a page-id, F5 restores stored page instance, form state preserved for free.ONE_PASS_RENDER— F5 always builds a fresh page, so form state is lost.formobjmasked this for the publish flow.formobjremoved. Nothing compensates anymore. The regression is now user-visible.The underlying tension
ONE_PASS_RENDERwas introduced to solve a real problem: pages held direct references to mutable singleton data (e.g. aSpaceobject), so after the page was restored from the page store on F5, the HTML rendered from stale fields rather than reflecting current singleton state.REDIRECT_TO_BUFFERandONE_PASS_RENDERare opposite trade-offs of the same setting:REDIRECT_TO_BUFFER(default)ONE_PASS_RENDER(current)You cannot have both via the render-strategy knob alone.
Proposed solution
The Wicket-idiomatic fix is to leave the render strategy at the default
REDIRECT_TO_BUFFERand stop holding live data references in the component tree. UseLoadableDetachableModel(LDM) for anything fetched from a singleton/registry.An LDM serializes only the identifier (e.g. a space id) and detaches at the end of every request. On the next render it re-loads via
load(). So even when Wicket restores a stored page tree on F5, anything wrapped in an LDM re-fetches from the singleton before rendering.This gives us both:
Concrete pattern (e.g. for
Space)```java
IModel spaceModel = new LoadableDetachableModel() {
@OverRide protected Space load() {
return SpaceRepository.get().findById(spaceId); // only spaceId is serialized
}
};
```
Then pass
spaceModelto components or usePropertyModel.of(spaceModel, \"label\").Label,ListView, etc. all acceptIModelnatively.Steps
Space space/MaintainedResource resourcetoString id+ an LDM. Replace directspace.getXxx()calls withPropertyModel.of(spaceModel, \"xxx\")or short helper LDMs.page/SpacePage.java:53—private final Space spacefield captures snapshot.page/MaintainedResourcePage.java:41— same pattern withMaintainedResource resource.ExplorePage.java:129-133. ReplaceModel.of(\"back to \" + …findById(contextId).getLabel())with an LDM that re-resolves the label.HomePage.java:73. Replace thefindById(...)snapshot passed toViewListwith an LDM if the home resource is editable mid-session.ONE_PASS_RENDERsetting fromWicketApplication.java:139.ResourcePartPage,SearchPage,DownloadRdfPage,UserPage,ChannelPage,ListPage,PreviewPage,ViewPage,SpaceListPage) are medium-risk — most hold data that is effectively immutable within a session. Convert any that show stale data in testing, following the same LDM pattern.Wins of the revert
These form pages will immediately get F5-preserves-state behavior back:
PublishPageGenPublishPagepage/connector/Notes