Skip to content

Conversation

@AbanoubGhadban
Copy link
Collaborator

Summary

  • Adds comprehensive documentation for the Async Props feature
  • Includes 4 static SVG diagrams explaining core concepts
  • Includes 12 animated SVG diagrams with SMIL animations covering:
    • Streaming flow visualization
    • Suspense boundaries and resolution
    • Waterfall vs parallel fetching comparison
    • Hydration process
    • Error handling isolation
    • Full request lifecycle
    • Server vs client components
    • User experience comparison

Documentation Structure

docs/async-props/
├── README.md           # Overview and quick start
├── how-it-works.md     # Deep dive into streaming architecture
├── api-reference.md    # Complete API reference
├── advanced-usage.md   # Error handling, caching, patterns
└── images/
    ├── *.svg           # Static diagrams
    └── animated/*.svg  # Animated SMIL diagrams

Test plan

  • Verify SVG diagrams render correctly in documentation
  • Review documentation content for accuracy
  • Consider converting animated SVGs to GIFs for GitHub compatibility

🤖 Generated with Claude Code

- Introduced `IncrementalRenderRequestManager` to handle streaming NDJSON requests, managing state and processing of incremental render requests.
- Added `validateBundlesExist` utility function to check for the existence of required bundles, improving error handling for missing assets.
- Refactored the incremental render endpoint to utilize the new request manager, enhancing the response lifecycle and error management.
- Updated tests to cover scenarios for missing bundles and validate the new request handling logic.
- Replaced the `IncrementalRenderRequestManager` with `handleIncrementalRenderStream` to manage streaming NDJSON requests more efficiently.
- Enhanced error handling and validation during the rendering process.
- Updated the `run` function to utilize the new stream handler, improving the response lifecycle and overall performance.
- Removed the deprecated `IncrementalRenderRequestManager` class to streamline the codebase.
- Introduced improved error handling for malformed JSON chunks during the incremental rendering process.
- Added logging and reporting for errors in subsequent chunks while allowing processing to continue.
- Updated tests to verify behavior for malformed JSON in both initial and update chunks, ensuring robust error management.
…inability

- Introduced helper functions to reduce redundancy in test setup, including `getServerAddress`, `createHttpRequest`, and `createInitialObject`.
- Streamlined the handling of HTTP requests and responses in tests, enhancing clarity and organization.
- Updated tests to utilize new helper functions, ensuring consistent structure and easier future modifications.
- Replaced inline wait functions with a new `waitFor` utility to improve test reliability and readability.
- Updated tests to utilize `waitFor` for asynchronous expectations, ensuring proper handling of processing times.
- Simplified the test structure by removing redundant wait logic, enhancing maintainability.
…processing

- Introduced `createBasicTestSetup` and `createStreamingTestSetup` helper functions to streamline test initialization and improve readability.
- Added `sendChunksAndWaitForProcessing` to handle chunk sending and processing verification, reducing redundancy in test logic.
- Updated existing tests to utilize these new helpers, enhancing maintainability and clarity in the test structure.
- Added detailed error reporting in the `waitFor` function to include the last encountered error message when a timeout occurs.
- Refactored the `createStreamingResponsePromise` function to improve clarity and maintainability by renaming variables and returning received chunks alongside the promise.
- Updated tests to utilize the new structure, ensuring robust handling of streaming responses and error scenarios.
…ment

- Removed unnecessary bundle validation checks from the incremental render request flow.
- Enhanced the `handleIncrementalRenderRequest` function to directly call `handleRenderRequest`, streamlining the rendering process.
- Updated the `IncrementalRenderInitialRequest` type to support a more flexible structure for dependency timestamps.
- Improved error handling to capture unexpected errors during the rendering process, ensuring robust responses.
- Added cleanup logic in tests to restore mocks after each test case.
- Removed individual protocol version and authentication checks from the request handling flow.
- Introduced a new `performRequestPrechecks` function to streamline the validation process for incoming requests.
- Updated the `authenticate` and `checkProtocolVersion` functions to accept request bodies directly, enhancing modularity.
- Improved error handling by ensuring consistent response structures across precheck validations.
- Updated the `/upload-assets` endpoint to differentiate between assets and bundles, allowing for more flexible uploads.
- Introduced logic to extract bundles prefixed with 'bundle_' and handle them separately.
- Integrated the `handleNewBundlesProvided` function to manage the processing of new bundles.
- Added comprehensive tests to verify the correct handling of uploads with various combinations of assets and bundles, including edge cases for empty requests and duplicate bundle hashes.
- Added tests to verify directory structure and file presence for uploaded bundles and assets.
- Implemented checks for scenarios with empty requests and duplicate bundle hashes, ensuring correct behavior without overwriting existing files.
- Improved coverage of the `/upload-assets` endpoint to handle various edge cases effectively.
- Implemented a new test case for the `/upload-assets` endpoint to verify that bundles are correctly placed in their own hash directories rather than the targetBundles directory.
- Ensured that the test checks for the existence of the bundle in the appropriate directory and confirms that the target bundle directory remains empty, enhancing coverage for asset upload scenarios.
- Implemented a suite of tests for the `/bundles/:bundleTimestamp/incremental-render/:renderRequestDigest` endpoint to verify successful rendering under various conditions, including pre-uploaded bundles and assets.
- Added scenarios to test failure cases, such as missing bundles, incorrect passwords, and invalid JSON payloads.
- Enhanced coverage for handling multiple dependency bundles and processing NDJSON chunks, ensuring robust error management and response validation.
- Simplified test structure by introducing helper functions to reduce code duplication for creating worker apps and uploading bundles.
- Improved test cases for the `/bundles/:bundleTimestamp/incremental-render/:renderRequestDigest` endpoint, ensuring robust validation of successful renders and error handling for various scenarios.
- Added tests for handling invalid JSON and missing required fields, enhancing coverage for edge cases in the rendering process.
- Updated tests to ensure proper handling of multiple dependency bundles and improved response validation for different payload conditions.
- Replaced the `runInVM` function with a new `ExecutionContext` class to manage VM contexts more effectively.
- Updated the `handleRenderRequest` function to utilize the new `ExecutionContext`, improving the handling of rendering requests.
- Enhanced error management by introducing `VMContextNotFoundError` for better clarity when VM contexts are missing.
- Refactored tests to align with the new execution context structure, ensuring consistent behavior across rendering scenarios.
…andling

- Updated the parameters for the `runOnOtherBundle` function to ensure correct execution order.
- Introduced a reference to `globalThis.runOnOtherBundle` in the server rendering code for better accessibility.
- Enhanced the test fixture to align with the changes in the global context, ensuring consistent behavior across rendering requests.
- Introduced `IncrementalRenderSink` type to manage streaming updates more effectively.
- Updated `handleIncrementalRenderRequest` to return an optional sink and handle execution context errors gracefully.
- Refactored the `run` function to utilize the new sink for processing updates, enhancing error logging for unexpected chunks.
- Simplified test setup by removing unused sink methods, ensuring tests focus on relevant functionality.
- Updated the `setResponse` call in the `run` function to correctly use `result.response`.
- Expanded the incremental render tests to cover new scenarios, including basic updates, multi-bundle interactions, and error handling for malformed update chunks.
- Introduced new helper functions in test fixtures to streamline the creation of async values and streams, enhancing the robustness of the tests.
- Improved the secondary bundle's functionality to support async value resolution and streaming, ensuring consistent behavior across bundles.
AbanoubGhadban and others added 16 commits December 29, 2025 20:28
Fix tests and refactor fixtures after NDJSON renderer changes

  - Fix request_spec.rb, handleRenderRequest, and serverRenderRSCReactComponent tests for new response structure
  - Separate incremental render fixtures from base test fixtures to prevent interference
  - Remove obsolete promise-based incremental render tests
  - Refactor VM bundle creation to use serverBundleCachePath
  - Clean up unneeded buildConfig call
- Fix stream closed checks for compatibility with different stream implementations
  - Add tests for concurrent incremental HTML streaming
  - Fix race condition by using unique bundle paths per test
  - Add tests for handleRequestClosed on connection close
This PR fixes streaming request compatibility issues when using HTTPX
with both `:stream` and `:stream_bidi` plugins loaded on the same
connection.

When the base branch combined two separate HTTPX connections into one
(to use a single HTTP/2 connection for both standard and incremental
requests), streaming requests started timing out. The root cause:

1. The `stream_bidi` plugin's `RequestBodyMethods#empty?` method always
returns `false` when `stream: true`
2. This prevents the `END_STREAM` flag from being sent on the HTTP/2
DATA frame
3. The server waits indefinitely for more request data, causing a
timeout

Refactored `perform_request` to use the `build_request` pattern for both
streaming and non-streaming requests:

- **`execute_http_request`**: New helper that uses `build_request`
instead of `connection.post`
- **`encode_request_body`**: Manually encodes form/JSON data since
`form:` option doesn't work with `stream: true` (Form::Encoder doesn't
implement `<<`)
- **For streaming requests**: Passes `stream: true` to `build_request`
and calls `request.close` to explicitly send the `END_STREAM` flag
- **Consolidated error handling**: Both streaming and non-streaming
requests now share the same retry logic, timeout handling, and error
handling

Additionally, this PR includes a temporary patch for an [HTTPX
stream_bidi plugin
bug](HoneyryderChuck/httpx#124):

**Problem:** When a streaming request fails and is retried, the
`@headers_sent` flag is not reset. This causes the `:body` callback to
fire prematurely on retry, leading to re-entrant `handle()` calls that
crash with `HTTP2::Error::InternalError`.

**Workaround:** The patch resets `@headers_sent` to `false` when
transitioning back to `:idle` state. This can be removed once fixed
upstream in the httpx gem.

Added size limits to the NDJSON incremental render stream handler to
prevent memory exhaustion from malicious or broken clients:

**Problem:** The NDJSON endpoint buffers incoming data until a newline
is encountered. A client sending continuous data without newlines would
cause unbounded memory growth. Fastify's `bodyLimit` is not enforced for
custom content type parsers that pass through raw streams.

**Solution:** Added two size limits:
- **`MAX_NDJSON_LINE_SIZE` (10MB)**: Limits single JSON line size
(matches Fastify's `fieldSizeLimit`)
- **`MAX_NDJSON_REQUEST_SIZE` (100MB)**: Limits total request size
(matches Fastify's `bodyLimit`)

- Updated `lib/react_on_rails_pro/request.rb`:
- Consolidated `perform_request` and `perform_streaming_request` into
one unified method
  - Added `execute_http_request` helper using `build_request` pattern
  - Added `encode_request_body` to handle both form and JSON encoding

- Added `lib/react_on_rails_pro/httpx_stream_bidi_patch.rb`:
- Temporary patch for HTTPX stream_bidi retry bug (resets
`@headers_sent` on `:idle` transition)

- Updated
`packages/react-on-rails-pro-node-renderer/src/worker/handleIncrementalRenderStream.ts`:
- Added `MAX_NDJSON_LINE_SIZE` (10MB) and `MAX_NDJSON_REQUEST_SIZE`
(100MB) constants
  - Added buffer size checks to prevent memory exhaustion

- Added
`packages/react-on-rails-pro-node-renderer/tests/handleIncrementalRenderStream.test.ts`:
  - Unit tests for NDJSON buffer size protection

- Updated `spec/react_on_rails_pro/request_spec.rb`:
- Changed tests to check encoded body string instead of internal HTTPX
structure
- Tests now verify actual request content rather than implementation
details

- Other fixes:
  - Fixed fixture paths in incremental rendering integration spec
  - Fixed RSpec tests for incremental rendering
  - Ignored `hasVMContextForBundle` in knip production check

- [x] Add/update test to cover these changes
- ~[ ] Update documentation~ (No documentation changes needed)
- ~[ ] Update CHANGELOG file~ (Internal fix, no user-facing changes)

- [x] All 18 request spec tests pass
- [x] RSC payload endpoint returns 200 OK with proper streamed JSON
response
- [x] Manual testing of streaming endpoints confirmed working
- [x] NDJSON buffer protection unit tests pass (5 tests)

---------

Co-authored-by: Claude <noreply@anthropic.com>
1. AsyncPropsManager: Add missing `resolved = true` in setProp()
   - The resolved flag was never set, causing endStream() to incorrectly
     reject already-resolved promises

2. handleIncrementalRenderRequest: Fix type mismatch
   - Changed onRequestClosedUpdateChunk type from string to UpdateChunk
   - Updated assertFirstIncrementalRenderRequestChunk to validate using
     assertIsUpdateChunk (DRY principle)
   - Removed redundant assertion in handleRequestClosed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
errorReporter.message() already calls log.error() internally, so having
both resulted in duplicate log entries. Removed the explicit log.error()
calls and the unused log import.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Avoid type cast by using local variable for vmCreationPromises.get()
- Simplify return: async functions auto-wrap return values in Promise

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Created shared constants file with BODY_SIZE_LIMIT (100MB) and
FIELD_SIZE_LIMIT (10MB) to eliminate duplication between:
- worker.ts (Fastify config)
- handleIncrementalRenderStream.ts (NDJSON parsing)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add comprehensive documentation for the Async Props feature:

- README.md: Overview, benefits, and quick start guide
- how-it-works.md: Deep dive into streaming architecture
- api-reference.md: Complete API reference for Rails and React
- advanced-usage.md: Error handling, caching, optimization patterns

SVG diagrams (GitHub-compatible):
- traditional-vs-streaming-ssr.svg: Visual comparison of SSR approaches
- timeline-comparison.svg: Gantt-style timing comparison
- progressive-loading-sequence.svg: 3-stage loading visualization
- architecture-flow.svg: Rails ↔ Node ↔ Browser data flow

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Animated SVG files using SMIL animations:

- streaming-flow.svg: Shows data packets flowing from Rails → Node → Browser
  with timeline markers appearing as content loads

- progressive-browser.svg: Browser mockup with skeleton shimmer effect,
  content sections appearing progressively with status badges

- race-comparison.svg: Side-by-side Traditional vs Async Props race
  showing the dramatic TTFB difference (4000ms vs 50ms)

- data-packets.svg: Dark-themed visualization of NDJSON protocol
  with animated packets traveling between Rails, Node, and Browser

All animations use SMIL (<animate>, <animateTransform>) which can be
converted to GIF using tools like svg2gif or Puppeteer.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added 8 more animated SVG visualizations:
- chunk-streaming.svg - HTML chunks flowing through streaming pipeline
- error-handling.svg - Graceful error isolation visualization
- full-request-lifecycle.svg - Complete request flow diagram
- hydration-process.svg - React hydration with water effects
- server-vs-client-components.svg - Server vs Client rendering
- suspense-boundaries.svg - Suspense resolution timeline
- user-experience-comparison.svg - UX comparison with metrics
- waterfall-vs-parallel.svg - Fetching patterns comparison

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 30, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch async-props-documentation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link

claude bot commented Dec 30, 2025

📚 Documentation Review: Async Props with Animated SVG Diagrams

This is an excellent addition to the React on Rails Pro documentation! The comprehensive coverage with visual aids will greatly help users understand the streaming SSR feature.


Strengths

1. Outstanding Visual Documentation

  • 12 animated SVG diagrams using SMIL animations effectively illustrate complex streaming concepts
  • 4 static SVGs provide clear architectural overviews
  • Progressive loading sequence diagrams are particularly helpful for understanding the user experience
  • Clean, professional design with good use of color gradients and animations

2. Well-Structured Content

  • Logical progression from problem → solution → implementation
  • Clear separation of concerns across 4 documents:
    • README.md: Quick start and overview
    • how-it-works.md: Deep technical dive
    • api-reference.md: Complete API documentation
    • advanced-usage.md: Patterns and best practices

3. Comprehensive Coverage

  • Covers the full spectrum from beginner to advanced usage
  • Includes error handling, caching, testing, and performance monitoring
  • Real-world examples with both Ruby and TypeScript/React code
  • Migration guide from traditional SSR

4. Technical Accuracy

  • Documentation aligns with actual implementation in react_on_rails_pro/lib/react_on_rails_pro/async_props_emitter.rb
  • Correctly describes the NDJSON protocol and streaming architecture
  • Accurate explanation of sharedExecutionContext for request isolation

🔍 Suggestions for Improvement

1. GitHub SVG Animation Compatibility ⚠️

Issue: GitHub's markdown renderer may not support SMIL animations in SVG files for security reasons.

Recommendation: Consider one of these approaches:

  • Convert animated SVGs to GIF animations for GitHub compatibility (tools like svg2gif or ffmpeg)
  • Provide both SVG (for full quality) and GIF fallbacks
  • Add a note in the PR description that animations may not display on GitHub but work in local environments

Test this: View the PR in GitHub's preview to confirm animations display correctly.

2. API Documentation Minor Gap

In api-reference.md, the async_prop helper is documented but the actual Rails controller method that makes this available isn't shown.

Suggested addition to api-reference.md:

# Ensure the helper is available in your controller
class ApplicationController < ActionController::Base
  include ReactOnRailsPro::Helper
end

3. Documentation Location

Placing docs at docs/async-props/ is good, but consider:

  • Adding a reference in the main README.md linking to this feature documentation
  • Adding an entry to CHANGELOG_PRO.md (since this is Pro-specific documentation)
  • Updating any existing Pro documentation index/table of contents

4. Code Example Consistency

In how-it-works.md line 28-32, the comment says "Deferred: streamed when ready" but the actual implementation shows async_prop blocks are evaluated immediately in parallel (not deferred).

Clarification needed: The blocks execute immediately, but results stream as they complete. Consider updating the comment to:

title: "Dashboard",           # Immediate: sent in initial request
users: async_prop { ... },    # Parallel: executes immediately, streams when ready
posts: async_prop { ... }     # Parallel: executes immediately, streams when ready

5. Missing Error Code Documentation

api-reference.md lists error codes (lines 253-262), but these aren't referenced elsewhere. Consider:

  • Adding examples showing when each error occurs
  • Explaining how to handle each error type
  • Verifying these match actual error codes in the implementation

6. TypeScript Types Verification

The TypeScript interfaces in api-reference.md (lines 226-251) should be verified against actual Pro package types:

# Check if these types exist in the codebase
grep -r "AsyncPropsManager" react_on_rails_pro/packages/ --include="*.ts" --include="*.tsx"

7. Testing Examples Need Adjustment

In advanced-usage.md lines 273-293, the Jest example imports from @react-on-rails-pro/core:

import { AsyncPropsProvider } from '@react-on-rails-pro/core';

Verify: Does this export exist in the Pro package? If not, provide the correct import path or note that this is a conceptual example.


📋 Pre-Merge Checklist

  • Test SVG animations render in GitHub's markdown preview
  • Add link to this documentation from main README.md
  • Add entry to CHANGELOG_PRO.md documenting new documentation
  • Verify all TypeScript type examples match actual Pro package exports
  • Test all code examples work with current Pro version
  • Consider converting animated SVGs to GIFs if animations don't render on GitHub
  • Run bundle exec rubocop (documentation changes likely won't trigger violations, but good practice)
  • Ensure all markdown files end with newline character

🎯 Overall Assessment

Code Quality: N/A (documentation only)
Content Quality: ⭐⭐⭐⭐⭐ (Excellent)
Visual Quality: ⭐⭐⭐⭐⭐ (Outstanding)
Completeness: ⭐⭐⭐⭐☆ (Very comprehensive, minor gaps noted)
Security Concerns: ✅ None
Performance Impact: ✅ None (documentation only)


🚀 Recommendation

Approve with minor revisions. This is excellent documentation that will significantly improve developer experience with the Async Props feature. The suggestions above are refinements rather than blockers.

Priority fixes:

  1. Verify/convert animated SVGs for GitHub compatibility
  2. Link from main documentation
  3. Update CHANGELOG_PRO.md

Nice-to-haves:

  • Verify all code examples against actual Pro package
  • Add more real-world troubleshooting scenarios

Great work on creating such comprehensive, visual documentation! 🎉

@AbanoubGhadban AbanoubGhadban force-pushed the abanoubghadban/pro509/make-renderer-use-ndjson-for-communication branch from b0c9de1 to f973013 Compare December 30, 2025 10:32
Base automatically changed from abanoubghadban/pro509/make-renderer-use-ndjson-for-communication to upcoming-v16.3.0 December 31, 2025 15:07
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