Reload edited app sources for SSR in development - #22
Merged
Conversation
The dev middleware recompiles the client bundle on change, but the SSR runtime loaded component sources once per process: after editing a component, hydration was fresh while the server-rendered markup stayed stale until a server restart (hit while building the tsunematsu storefront). boot! now snapshots the app source list with mtimes and, when auto_reload is enabled, re-loads the sources on the next render when anything changed. Re-running the initializer rebuilds the server-side router, so route changes refresh too. The railtie enables auto_reload in development alongside the recompile middleware; other environments keep the load-once behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR addresses stale server-rendered HTML in development by making the SSR runtime optionally reload app source files when they change, aligning SSR output with the dev middleware’s client-side recompilation behavior.
Changes:
- Add
auto_reload+ source snapshotting toFunicular::SSR::Runtime.boot!to reload SSR sources when files change. - Enable SSR auto-reload in development via the railtie initializer.
- Add minitest coverage ensuring edited sources reload and unchanged sources do not.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
minitest/ssr_reload_test.rb |
Adds tests for SSR reload behavior in development-like conditions. |
lib/funicular/ssr/runtime.rb |
Implements source snapshotting and conditional reload behavior for SSR boot. |
lib/funicular/railtie.rb |
Enables SSR auto-reload in development to match the client recompilation workflow. |
Suppressed comments (1)
lib/funicular/ssr/runtime.rb:119
reset_app!resets@app_loadedbut leaves@sources_snapshotintact. Since snapshotting is now part of the runtime state, keeping the old snapshot can makesources_changed?results confusing after a reset, and retains unnecessary references to the previous app’s file list until the next boot.
# Test/escape hatch: forget loaded application state so a different
# app (or a reload) can be booted. Does not unload the framework.
def reset_app!
@app_loaded = false
end
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+81
to
+85
| def boot!(source_dir) | ||
| load_framework! | ||
| return if @app_loaded | ||
| if @app_loaded | ||
| return unless auto_reload && sources_changed?(source_dir) | ||
| end |
Comment on lines
+109
to
113
| def sources_snapshot(source_dir) | ||
| Funicular::Compiler.source_files(source_dir.to_s).map do |file| | ||
| [ file, File.exist?(file) ? File.mtime(file).to_f : nil ] | ||
| end | ||
| end |
Concurrent renders (Puma threads in development) now take a mutex around framework/app loading so two requests cannot interleave a reload, with a lock-free fast path for the steady production/test state. Snapshot mtime reads rescue SystemCallError so a file deleted or renamed mid-edit counts as absent instead of breaking the render, and reset_app! discards the snapshot along with the loaded flag. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Member
Author
|
Addressed all three review points:
Suite green (233 tests). 🤖 Generated with Claude Code |
hasumikin
added a commit
that referenced
this pull request
Aug 13, 2026
The Unreleased section had accumulated the local-database work, StyleValue#+, and the navigation guard, but six merged changes were missing: SSR.render_component (#25), DOMTest negative assertions (#19), Testing.ensure_compiled! (#21), Response#body plus named callback errors (#23), dev-mode SSR source reload (#22), and the readonly schema introspection skip (#20). All entered, dated 2026-08-13. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Why
Dogfooding note from the tsunematsu app (FUNICULAR_NOTES proposal 6):
Funicular::SSR::Runtimeloads app sources once per process, while the dev middleware recompiles app.mrb on change. After editing a component, the hydrated page is new but the server-rendered HTML stays stale until the Rails server restarts — confusing precisely because half the stack did update.What
Runtime.boot!snapshots the source file list + mtimes; withRuntime.auto_reloadenabled it re-loads the app sources when the snapshot changed (re-running the initializer also rebuilds the server-side router, so routes refresh)auto_reloadin development, next to the recompile middlewareVerification
New minitest cases (reload picks up an edited component; unchanged sources are not re-loaded); full suite green.
🤖 Generated with Claude Code