Skip to content

Reload edited app sources for SSR in development - #22

Merged
hasumikin merged 2 commits into
masterfrom
feature/ssr-dev-reload
Aug 12, 2026
Merged

Reload edited app sources for SSR in development#22
hasumikin merged 2 commits into
masterfrom
feature/ssr-dev-reload

Conversation

@hasumikin

Copy link
Copy Markdown
Member

Why

Dogfooding note from the tsunematsu app (FUNICULAR_NOTES proposal 6): Funicular::SSR::Runtime loads 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; with Runtime.auto_reload enabled it re-loads the app sources when the snapshot changed (re-running the initializer also rebuilds the server-side router, so routes refresh)
  • The railtie enables auto_reload in development, next to the recompile middleware
  • Production/test keep the load-once behavior

Verification

New minitest cases (reload picks up an edited component; unchanged sources are not re-loaded); full suite green.

🤖 Generated with Claude Code

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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 to Funicular::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_loaded but leaves @sources_snapshot intact. Since snapshotting is now part of the runtime state, keeping the old snapshot can make sources_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 thread lib/funicular/ssr/runtime.rb Outdated
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>
@hasumikin

Copy link
Copy Markdown
Member Author

Addressed all three review points:

  • Concurrency: boot! now serializes framework/app loading behind a mutex so concurrent dev renders cannot interleave a reload; the steady production/test state (loaded && !auto_reload) returns before taking the lock, so non-dev renders pay nothing.
  • mtime race: snapshot reads go through mtime_or_nil, rescuing SystemCallError so a file deleted/renamed between discovery and stat counts as absent instead of raising mid-render.
  • reset_app!: also discards @sources_snapshot (with a regression test).

Suite green (233 tests).

🤖 Generated with Claude Code

@hasumikin
hasumikin merged commit 640d310 into master Aug 12, 2026
1 check passed
@hasumikin
hasumikin deleted the feature/ssr-dev-reload branch August 12, 2026 08:24
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>
Sign up for free to 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.

2 participants