Skip to content

docs(planning): JSON render-request body migration plan (#3280)#3299

Merged
AbanoubGhadban merged 2 commits into
mainfrom
docs/3280-json-render-body-plan
May 15, 2026
Merged

docs(planning): JSON render-request body migration plan (#3280)#3299
AbanoubGhadban merged 2 commits into
mainfrom
docs/3280-json-render-body-plan

Conversation

@AbanoubGhadban

@AbanoubGhadban AbanoubGhadban commented May 15, 2026

Copy link
Copy Markdown
Collaborator

Planning artifact for #3280docs only, no code change. Lives in internal/planning/ per repo convention (alongside MONOREPO_MERGER_PLAN.md, js-pro-package-separation-plan.md, etc.).

Companion to experiment PR #3293. This is the production-quality follow-up plan the issue asked for once we knew the numbers moved: it records the change, the full benchmark results, the operational problems hit while running the experiment, the deep compatibility/protocol findings, and a phased production implementation plan.

Supersedes the earlier (closed) #3298, which targeted upcoming-v16.3.0 and put the file under react_on_rails_pro/. This one targets main and uses internal/planning/.

What's in internal/planning/json-render-body-migration-plan.md

  • The change — the 9-line render_code conditional and why no renderer change is needed (Fastify default JSON parser; extractBodyArrayField already handles both key and key[]; auth reads req.body.password either way). Includes the runtime instrumentation proof (Ruby↔Node byte-for-byte match on 3 payload sizes).
  • Performance numbers — sequential sweep, isolation, autocannon, web-vitals, three-methodology robustness (sequential / paired-curl / parallel-A/B), the perf(pro): enable TCP_NODELAY on httpx (remove 40ms HTTP/2 tail) #3217 (TCP_NODELAY) orthogonality re-verification, and byte-equivalence. Headline: mega page 396 → 92 ms wall (−304), 112.8 → 9.8 ms GC (−103), ~2× the acceptance bar.
  • 13 operational problems faced — each with the workaround used.
  • Production plan — compat matrix + protocol-version gating, multipart preservation, streaming out-of-scope, tests, docs/changelog, defensive audit, sequencing vs perf(pro): enable TCP_NODELAY on httpx (remove 40ms HTTP/2 tail) #3217.

Links

Issue #3280
Experiment PR (demo) #3293
Experiment commit 352743ef1
Demo-app harness commit b90977ef3
Demo-app branch https://github.com/AbanoubGhadban/rsc-benchmar/tree/experiment/3280-json-render-body
Orthogonal PR #3217

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation
    • Added planning document for a render request body format experiment: includes experiment summary, benchmarking and correctness results, operational findings, and a staged production implementation, testing, and rollout plan.

Review Change Stack

Findings doc derived from the completed #3280 experiment, placed in
internal/planning/ per repo convention. Captures:
- the 9-line demo change + why no renderer change is needed
- full perf numbers (sequential / isolation / autocannon / web-vitals /
  paired-curl / parallel A/B / byte-equivalence), incl. the #3217
  orthogonality re-verification
- the 13 operational problems hit while running the experiment
- a phased production plan (compat matrix + protocol-version gating,
  multipart preservation, streaming out-of-scope, tests, docs, audit)
- links to the experiment commit/PR and the demo-app harness commit

No code change — planning artifact only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This PR adds a production planning document for switching steady-state render request bodies from form-encoded to JSON (when no bundle is attached), recording the demo, verification, benchmark results, operational issues, and a production rollout plan with protocol-version gating and required test/docs updates.

Changes

JSON Render-Body Migration Planning

Layer / File(s) Summary
Experiment Overview and Demo Verification
internal/planning/json-render-body-migration-plan.md
Introduces the proposed wire-level change from form-encoded to JSON for steady-state render requests. Documents the experimental demo approach (Ruby conditional: multipart when send_bundle true, otherwise use HTTPX json:), Fastify JSON parsing compatibility, and runtime verification via content-type and byte-matching checks.
Performance Validation and Operational Learning
internal/planning/json-render-body-migration-plan.md
Reports benchmarking results (sequential sweeps, isolation, concurrent load, web-vitals), cross-method agreement, robustness checks vs the orthogonal TCP_NODELAY change, and correctness via byte-equivalent HTML. Catalogs operational/environmental issues encountered during experiments.
Production Implementation Roadmap
internal/planning/json-render-body-migration-plan.md
Specifies protocol compatibility/version gating, keep multipart for send_bundle: true, defer streaming conversions, enumerates required Ruby and Node test updates (including JSON-body and byte-equivalence assertions), documentation/changelog tasks, and sequencing guidance relative to related PRs.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~5 minutes

Possibly related issues

  • #3280 — Documents and formalizes the same render-request body change and demo/experiment described in this planning document.

Poem

A rabbit scribbles a JSON plan with glee,
Forms folded, now data flows free,
Benchmarks hum, byte-matches shine,
Steady requests step in a straight line,
Hops of joy for a migration small and spry 🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and specifically identifies the main change: adding a JSON render-request body migration plan documentation. It directly maps to the only file added and accurately summarizes the content.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs/3280-json-render-body-plan

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.

@greptile-apps

greptile-apps Bot commented May 15, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a docs-only planning artifact at internal/planning/json-render-body-migration-plan.md that records the completed AI-agent experiment for #3280 and lays out the work required for a production-quality implementation.

  • The experiment result: a 9-line conditional in request.rb routing send_bundle: false paths through HTTPX's json: option; no Node-side change needed because Fastify's built-in JSON parser and extractBodyArrayField's dual-key check already handle both encodings. Headline: median wall 396 \u2192 92 ms, GC 112.8 \u2192 9.8 ms on the mega-payload page.
  • The production plan: covers backward compatibility via a protocolVersion gate (\u00a74.1), multipart preservation for send_bundle: true (\u00a74.2), streaming paths out of scope (\u00a74.3), test requirements (\u00a74.4), changelog/docs (\u00a74.5), a defensive audit (\u00a74.6), and sequencing against perf(pro): enable TCP_NODELAY on httpx (remove 40ms HTTP/2 tail) #3217 (\u00a74.7).
  • 13 operational problems from the experiment run are catalogued so the production implementation can budget for them.

Confidence Score: 4/5

Docs-only change; no executable code is modified. Safe to merge as a planning record.

The document is thorough and well-structured, with three independent methodologies supporting the core performance claim. All three comments are about completeness gaps in the planning text: an unexplained web vitals anomaly in the baseline data, an optional re-measurement that should be a hard gate before shipping, and a missing version number for the protocol bump. None affect the correctness of the proposal itself.

The web vitals table in §2.4 and the hello_server footnote merit a second look before the plan is used as the basis for a production PR.

Important Files Changed

Filename Overview
internal/planning/json-render-body-migration-plan.md New planning document recording the JSON render-body experiment results, 13 operational problems, and a phased production implementation plan. Three minor gaps: baseline web vitals appear anomalous for mega_benchmark and are unexplained, the hello_server TTFB re-measurement is optional rather than required, and the protocol version bump is not scoped to a specific version.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[render_code called] --> B{send_bundle?}
    B -- true --> C["perform_request form multipart"]
    B -- false --> D["perform_request json application/json"]
    D --> E{protocolVersion gate met?}
    E -- yes --> F[Fastify default JSON body parser]
    E -- no --> G[Fall back to form-encoded body]
    F --> H[req.body read agnostically]
    G --> H
    C --> I[multipart handler]
    I --> H
    H --> J[extractBodyArrayField checks key and key-array]
    J --> K[Byte-equivalent HTML response]
Loading

Reviews (1): Last reviewed commit: "docs(planning): JSON render-request body..." | Re-trigger Greptile

Comment on lines +148 to +156

Streaming SSR (`hello_server`, `mega_benchmark`) flat ±noise — untouched path,
no regression.

### 2.4 Web vitals — Playwright (3 iters)

| Page | base TTFB / load | after TTFB / load |
| ------------------------------ | ---------------: | ----------------: |
| hello_world | 8.5 / 55 | 4.0 / 45 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Baseline web vitals appear anomalous for mega_benchmark — plan should acknowledge this

The mega_benchmark_traditional row shows baseline total page load of 240 ms with a 233 ms TTFB, implying the browser received and fully rendered 2.18 MB of HTML in only 7 ms — not physically plausible. The after-measurement (553 ms total, 135 ms TTFB → 418 ms remaining load) is far more credible for a 2.18 MB document. This looks like a baseline measurement artifact rather than an after-state regression, but the plan never says so. Similarly, heavy_benchmark_traditional load doubles (37 → 79 ms) without any note. The "Go" verdict and §2.5's "three methodologies agree" claim could be misread by implementers who see the web vitals table without this explanation — the footnote only addresses hello_server. A short note clarifying that the load-time increases reflect a plausible baseline anomaly would prevent this misreading.

Comment on lines +155 to +156
| ------------------------------ | ---------------: | ----------------: |
| hello_world | 8.5 / 55 | 4.0 / 45 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 hello_server re-measurement should be required, not conditional

The footnote phrases the ≥10-iteration re-measurement as "if it matters," but hello_server shows an ~20× TTFB increase (6 → 118.9 ms) on an allegedly untouched streaming path. Even if the root cause is cold-cache noise, a production plan that ships with an unresolved 20× regression on any endpoint — even a likely false positive — leaves reviewers without a clear "we checked and it's fine" sign-off. Consider promoting this to an explicit pre-ship gate in §4 rather than leaving it optional.

Comment on lines +236 to +242
tool die at the 10-minute max timeout. Fixed by launching detached:
`nohup bash -c '…' >/dev/null 2>&1 & disown` (and `setsid -f` earlier).
6. **HTTPX pool exhaustion at c=50.** Default `renderer_http_pool_size`
plus a 5 s checkout timeout → `HTTPX::PoolTimeoutError` and a Rails
`Exiting` under sustained c=50. Fixed in the demo initializer:
`renderer_http_pool_size=64`, `renderer_http_pool_timeout=30`,
`ssr_timeout=30`. Identical on baseline and after, so the A/B stays fair;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Protocol version bump semantics are underspecified

§4.1 says to bump protocolVersion and gate JSON on renderers at/above the bump, but doesn't specify whether the bump is minor (2.0.0 → 2.1.0) or major (2.0.0 → 3.0.0). Sending a different Content-Type is a wire-breaking change for any renderer that doesn't have Fastify's default JSON parser (e.g., the pre-Fastify Express path listed as ✗ _risk_ in the compatibility matrix). Calling out the intended version string would prevent the production PR from having to relitigate this decision.

Add §6 documenting that the experiment codebase PR #3293 is closed
(superseded by this plan), with a disposition table for #3293/#3298/this
PR, and an explicit note that orthogonal #3217 is left untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor

Code Review (PR 3299 - docs only, no code change)

Verified against current source: extractBodyArrayField dual-key check, Fastify JSON parser never removed, bodyLimit 100MB, protocol version 2.0.0 matches both sides, password auth via timingSafeEqual, /asset-exists already uses json: keyword, common_form_data returns only JSON-serializable types. All claims accurate.

Four gaps to address before the implementation PR:

  1. PROTOCOL VERSION GATING MECHANISM (section 4.1) -- The plan says "gate on protocolVersion, send form below the bump, JSON at/above." But checkProtocolVersionHandler.ts uses a strict equality check -- the gem cannot query the renderer version before choosing encoding. A renderer on 2.0.0 already rejects a gem on 2.1.0 with 412 (version mismatch), so "send form to old renderers" is unreachable regardless of encoding. Clarify: Option A (simpler) -- always send JSON after the version bump, document minimum supported renderer version; or Option B -- add a GET /status capability endpoint for the gem to discover the renderer version at startup. Option A matches current protocol behavior.

  2. JSON.generate FAILURE MODE (sections 4.2/4.6) -- The plan warns against "silently" routing a Pathname through JSON.generate, but JSON.generate on a Pathname raises TypeError immediately -- not silent. The real risk is get_form_body_for_file returning a String (file contents), making it JSON-serializable but wrong. The regression test should assert Content-Type is multipart/form-data when send_bundle is true, not just that the call succeeds.

  3. hello_server TTFB SPIKE (section 2.4 footnote 1) -- A 20x TTFB inflation (6ms to 119ms) on the streaming SSR path dismissed as "likely noise, re-measure if it matters" is too passive for a production plan. Require a clean web-vitals run with 10+ iterations on streaming paths before merging the implementation PR.

  4. WAF/PROXY OPERATOR NOTE (section 4.5) -- The content-type change is visible to any proxy or WAF between Rails and the renderer. A WAF allowing only multipart/form-data or application/x-www-form-urlencoded would start blocking requests. Migration note should call this out for operators who restrict that internal traffic.

Minor: Section 1 references utils.rb:167 for common_form_data but it is at line 165. Section 4.4 Ruby spec should verify perform_request is called with the json: keyword argument, not just inspect the serialized body bytes.

Overall: benchmarking solid, compatibility analysis accurate, risk items identified. Resolve the protocol gating mechanism question (item 1) before writing the implementation PR.

`HTTPX::TCP.ancestors.include?(ReactOnRailsPro::HttpxTcpNodelayPatch) == true`):

| Workload | Δ, #3217 NOT on baseline | Δ, #3217 ON baseline |
| -------------------- | -----------------------: | -------------------: |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Protocol version gating mechanism needs clarification.

The plan says "gate on protocolVersion — bump it and have the gem send form to renderers below the bump, JSON at/above." But checkProtocolVersionHandler.ts uses a strict equality check — the gem cannot discover the renderer version before choosing its encoding format. A renderer still on 2.0.0 already rejects any gem sending protocolVersion: 2.1.0 with HTTP 412, regardless of content-type. So "send form to old renderers" is unreachable in practice.

Two concrete options to resolve this:

  • Option A (simpler): Always send JSON once the gem bumps the protocol version. Old renderers are already broken by the 412 version-mismatch rejection. Document the minimum supported renderer version. Consistent with current protocol behavior.
  • Option B (graceful degradation): Add a GET /status (or equivalent) capability endpoint the gem queries once at startup, then stores and uses to choose encoding. More complex but allows rolling upgrades.

Option A is the path of least resistance given the existing strict-equality version gate.

1. **Branch base.** Worktree first cut from `main`; the experiment had to
match the renderer/gem version rsc-benchmar pins, so it was recut from
`upcoming-v16.3.0` (`3d571c32`).
2. **Fresh demo-app setup.** `pnpm install` (root, auto-builds the 3 JS

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Failure mode is loud (TypeError), not silent — reframe the test goal.

JSON.generate on a hash containing a Pathname raises TypeError: no implicit conversion of Pathname into String immediately. There is no silent failure path via the Pathname value itself.

The actual silent failure risk is if get_form_body_for_file is ever changed to return the file contents as a String — that value is JSON-serializable, so json: form would succeed, but the renderer would receive the full bundle JS embedded as a JSON string field rather than as a multipart file upload, and the rendering would silently misbehave.

The regression test should therefore assert that when send_bundle: true, the outgoing request's Content-Type header is multipart/form-data, not just that no exception is raised.


| Point (u,p,c) | base total / gc (ms) | after total / gc (ms) | Δ total |
| ------------------- | -------------------: | --------------------: | -------: |
| 0, 0, 0 | 7 / 0.1 | 7 / 0.0 | 0 |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

hello_server TTFB anomaly needs a mandatory re-measurement, not an optional one.

The footnote says "re-measure with iters≥10 if it matters." A 20x TTFB inflation on the streaming SSR path (6 ms → 119 ms) should be treated as a blocker requiring proactive clearance rather than something to revisit only "if it matters." The autocannon result (120→115 ms, flat) is encouraging but covers aggregate throughput, not per-page first-byte latency under realistic conditions.

Suggest strengthening the production plan to require: a clean Playwright web-vitals run with ≥10 iterations on hello_server and mega_benchmark (streaming paths) before the implementation PR is marked ready for merge, with the results included in the PR description.

`bodyLimit` (100 MB, `worker.ts:222`) — fine; document it.

### 4.2 Keep multipart where it must stay

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Add a WAF/proxy operator note here.

The content-type change from application/x-www-form-urlencoded to application/json on the render endpoint is observable at the network level. Any operator who has:

  • a WAF allowlisting specific content-types on the internal Rails↔renderer path
  • a reverse proxy (nginx, HAProxy, etc.) with content-type filtering
  • network-level logging/IDS inspecting that traffic

…will see requests start failing or triggering alerts after this change. The CHANGELOG entry should include a migration callout along the lines of: "If you have a WAF or proxy restricting Content-Type on the renderer endpoint, add application/json to the allowlist."

```

No Node-side change. `react_on_rails_pro/lib/react_on_rails_pro/utils.rb:167`
`common_form_data` already produces only JSON-serializable values

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Minor: utils.rb:167 — in the current checkout common_form_data is defined at line 165, not 167. Worth correcting before the implementation PR references it as a starting point.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 30231ea303

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +296 to +297
advertises it. Cleanest: gate on `protocolVersion` — bump it and have the
gem send form to renderers below the bump, JSON at/above.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid using protocolVersion as the JSON feature gate

When the production PR follows this recommendation for mixed deployments, a new gem talking to an old renderer will not fall back cleanly: the gem includes its ReactOnRailsPro::PROTOCOL_VERSION in every request body, and the renderer rejects any mismatch with HTTP 412 before handling the render. If the protocol is bumped to select JSON vs form, the first fallback form request to an old renderer still carries the new protocol value and is rejected, so the plan needs a real capability discovery/config mechanism or an explicit old-protocol fallback.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/planning/json-render-body-migration-plan.md (1)

1-378: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix Prettier formatting violations before merge.

The CI pipeline reports that this markdown file has formatting issues. As per coding guidelines, Prettier must be used for all markdown files.

Run the following to fix:

prettier --write internal/planning/json-render-body-migration-plan.md

Then commit the changes. As per coding guidelines, Prettier is required for markdown files matching **/*.md.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/planning/json-render-body-migration-plan.md` around lines 1 - 378,
Summary: Prettier formatting violations in the "Production Migration Plan — JSON
render-request body (`#3280`)" markdown need fixing. Fix: run Prettier
(project-configured prettier) to format that markdown document (the migration
plan whose heading/title is "Production Migration Plan — JSON render-request
body (`#3280`)"), review the formatted diff to ensure only whitespace/format
changes, then commit the resulting changes; ensure CI passes. Location hints:
look for the markdown file containing the document title and the experiment diff
section in the repo.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@internal/planning/json-render-body-migration-plan.md`:
- Around line 1-378: Summary: Prettier formatting violations in the "Production
Migration Plan — JSON render-request body (`#3280`)" markdown need fixing. Fix:
run Prettier (project-configured prettier) to format that markdown document (the
migration plan whose heading/title is "Production Migration Plan — JSON
render-request body (`#3280`)"), review the formatted diff to ensure only
whitespace/format changes, then commit the resulting changes; ensure CI passes.
Location hints: look for the markdown file containing the document title and the
experiment diff section in the repo.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fe079ca8-6318-4d98-9f40-c44d3a02918d

📥 Commits

Reviewing files that changed from the base of the PR and between e88bc4c and 30231ea.

📒 Files selected for processing (1)
  • internal/planning/json-render-body-migration-plan.md

@claude

claude Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor

Code Review for PR 3299 - docs-only planning document. Overall: thorough and well-organized. See inline comments for specific issues.

@claude

claude Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor

Code review top-level summary (see inline comments for detail): Docs-only planning artifact -- thorough and well-organized. Four issues: (1) Load-time regressions in s2.4 web vitals table (heavy +114%, mega +130%) are not flagged or explained -- re-measure at n>=10 before the production PR ships. (2) Two stale line numbers: utils.rb:167 should be 165 for common_form_data; s4.2 request.rb:189-197 maps to add_bundle_to_form, not populate_form_with_bundle_and_assets (165-187), and form_with_code is at 158-163. (3) s4.4 spec-update list cites ~:123-135 but misses the identical assertion at line 153 in a second test case. (4) s4.6 defensive audit underspecified -- suggest JSON.generate(common_form_data) unit test or a Sorbet sig as the concrete guard.

end
```

No Node-side change. `react_on_rails_pro/lib/react_on_rails_pro/utils.rb:167`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Stale line number: utils.rb:167 should be 165. def self.common_form_data is currently at line 165 in react_on_rails_pro/lib/react_on_rails_pro/utils.rb.

`send_bundle: true` already stays on multipart (the experiment's conditional)
because `form_with_code` → `populate_form_with_bundle_and_assets` embeds
bundle/asset entries whose `:body` is a `Pathname`/IO
(`request.rb:189-197`, `321-344`). `/upload-assets` (multipart, file uploads)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The line range here points to the wrong function. In the current request.rb:

  • form_with_code is at lines 158–163
  • populate_form_with_bundle_and_assets (which form_with_code calls when send_bundle: true) is at lines 165–187
  • add_bundle_to_form (which actually has the Pathname/IO :body entries) starts at line 189

So request.rb:189-197 is add_bundle_to_form, not populate_form_with_bundle_and_assets. The conceptual chain (form_with_codepopulate_form_with_bundle_and_assetsadd_bundle_to_form) is correct; only the cited line range needs updating.

### 4.4 Tests

- Ruby (`react_on_rails_pro/spec/react_on_rails_pro/request_spec.rb`): the
existing specs at ~`:123-135` assert `body.to_s` includes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The spec range ~:123-135 only covers the first test case. There is a second identical assertion at line 153 in request_spec.rb (the "raises duplicate bundle upload error" test case) that also asserts body.to_s.include("renderingRequest=console.log"). Both will need updating when the production implementation switches to JSON.

Comment on lines +157 to +162
| heavy_benchmark_traditional | 21.9 / 37 | 12.1 / 79 |
| **mega_benchmark_traditional** | **233.1 / 240** | **135.6 / 553** |
| hello_server | 6.0 / 13 | 118.9 / 220 ¹ |

¹ `hello_server` first-iter cold-cache noise at 3 iters; autocannon c=1 shows
it flat (120→115). Re-measure with iters≥10 if it matters.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The load-time column shows significant regressions that are not mentioned anywhere in the document:

  • heavy_benchmark_traditional: 37 → 79 ms (+114%)
  • mega_benchmark_traditional: 240 → 553 ms (+130%)

The hello_server TTFB spike has a footnote; these do not. At n=3 this is very likely noise, but without a note the reader cannot tell whether it was investigated. Recommend either adding a footnote explaining why these are noise (e.g., renderer GC or JIT warmup on first full-page load) or, better, requiring a re-run at ≥10 iterations as a prerequisite in the production PR rather than leaving it as optional ("if it matters").

@AbanoubGhadban AbanoubGhadban merged commit fd9feb5 into main May 15, 2026
29 of 30 checks passed
@AbanoubGhadban AbanoubGhadban deleted the docs/3280-json-render-body-plan branch May 15, 2026 12:05
justin808 added a commit that referenced this pull request May 15, 2026
…rer-client-main

* origin/main:
  docs(planning): JSON render-request body migration plan (#3280) (#3299)
  docs: experiment plan for decoupling props from JS source (#3281) (#3297)
ihabadham added a commit that referenced this pull request May 15, 2026
…on main (#3301)

## Summary

Two markdown files were merged to `main` without passing the repo-wide
checks, which turns `markdown-format-check` and the `build` job's format
step **red on every open PR** (both run `prettier --check` over the
entire repo, not just changed files):

- `internal/planning/json-render-body-migration-plan.md` (from #3299) —
prettier formatting only.
- `reports/ror-memory-leakage-investigation.md` (from #3296) — prettier
formatting **and** a relative link to
`../../docs/pro/js-memory-leaks.md`, which is one level too high and
resolves outside the repo. The target exists at repo-root
`docs/pro/js-memory-leaks.md`; corrected to
`../docs/pro/js-memory-leaks.md`.

Formatting changes are `prettier --write` output only (no content
edits). The link change is a single relative-path correction; verified
the target file exists and the `markdown-links` (lychee) pre-commit hook
now passes.

## Why this is its own PR

These files are unrelated to any feature work, but their CI failures
block unrelated PRs (e.g. #3232). Fixing them on `main` directly via a
focused PR unblocks everything in flight.

## Test plan

- `pnpm exec prettier --check
internal/planning/json-render-body-migration-plan.md
reports/ror-memory-leakage-investigation.md` → clean
- `markdown-links` lefthook hook passes (corrected link resolves)
- No content changes beyond the one relative-path fix

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

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Updated planning documentation with status updates for related pull
requests and issue tracking
* Enhanced investigation report with improved structure and readability
through reformatted content into consistent table formats

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/shakacode/react_on_rails/pull/3301)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
justin808 added a commit that referenced this pull request May 20, 2026
…y-adapter

* origin/main:
  feat(pro): Async props & incremental RSC rendering + length-prefixed streaming protocol (#2903)
  fix: stale references and lost Gemfile pins after Pro migration (#3104)
  Restore Metrics/ModuleLength limit to 180
  Clarify RuboCop lint contract
  Exclude dead project URLs from markdown link check (#3306)
  Canonicalize GitHub links in docs
  Reduce `react-on-rails-pro-node-renderer` package size (#3304)
  fix: format stray markdown files and correct broken link breaking CI on main (#3301)
  docs(planning): JSON render-request body migration plan (#3280) (#3299)
  docs: experiment plan for decoupling props from JS source (#3281) (#3297)
  docs: memory leakage investigation report for SSR pipeline (#3296)
  docs: fix MDX-incompatible patterns blocking sc-website docs sync (#3275)
  docs: document RSC node renderer test setup (#3223)
  Clarify Pro pricing and license guidance (#3272)
  docs: add AI security scanner evaluation plan (#3236)
  fix: pin pnpm version in scaffolded CI when packageManager is absent (#3172) (#3174)
  docs: document RSC HTTP response ownership (#3222)
  docs: document legacy Webpacker compatibility shims (#3225)
  chore: sync address-review command and import prompt variant (#3271)

# Conflicts:
#	.lychee.toml
#	react_on_rails/spec/lib/react_on_rails/doctor_spec.rb
#	react_on_rails_pro/lib/react_on_rails_pro/pre_seed_renderer_cache.rb
justin808 added a commit that referenced this pull request May 21, 2026
* origin/main:
  docs: clarify extensible precompile ownership (#3226)
  docs: fix broken Empirical-Core GitHub link in PROJECTS.md (#3326)
  docs(planning): PPR implementation plan for React on Rails (#3323)
  docs: plan React 19 partial pre-rendering work (#3237)
  Relax Pro jwt dependency to allow 3.2.0 security fix
  docs(planning): plan RSCRoute ssr=false implementation (#3317)
  docs: Add PPR investigation findings (#3314)
  feat(pro): Async props & incremental RSC rendering + length-prefixed streaming protocol (#2903)
  fix: stale references and lost Gemfile pins after Pro migration (#3104)
  Restore Metrics/ModuleLength limit to 180
  Clarify RuboCop lint contract
  Exclude dead project URLs from markdown link check (#3306)
  Canonicalize GitHub links in docs
  Reduce `react-on-rails-pro-node-renderer` package size (#3304)
  fix: format stray markdown files and correct broken link breaking CI on main (#3301)
  docs(planning): JSON render-request body migration plan (#3280) (#3299)
  docs: experiment plan for decoupling props from JS source (#3281) (#3297)
  docs: memory leakage investigation report for SSR pipeline (#3296)
  docs: fix MDX-incompatible patterns blocking sc-website docs sync (#3275)
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.

1 participant