Skip to content

Migrate OSS dummy client examples to TypeScript#3606

Merged
justin808 merged 60 commits into
mainfrom
ihabadham/chore/dummy-client-typescript-migration
Jun 5, 2026
Merged

Migrate OSS dummy client examples to TypeScript#3606
justin808 merged 60 commits into
mainfrom
ihabadham/chore/dummy-client-typescript-migration

Conversation

@ihabadham

@ihabadham ihabadham commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add real tsc --noEmit coverage for the OSS dummy client app and let Shakapacker choose SWC parser settings per source extension.
  • Convert the high-value handwritten dummy client/runtime examples to TypeScript, including render functions, Router, HelloWorld flows, Redux/shared-store flows, strict-mode helpers, ReScript wrappers, and client/server bundle entrypoints.
  • Support TypeScript source server-bundle entrypoints while preserving the existing runtime output name (server-bundle.js) and existing plain-object server_render_js registrations.
  • Use the non-deprecated registerStoreGenerators API in the migrated dummy bundle for shared-store registration.

Resolves #1583.

Scope

This intentionally leaves legacy or fixture-only JavaScript in place:

  • BrokenApp.jsx remains an intentionally broken fixture.
  • HelloWorldES5.jsx remains ES5/createReactClass legacy coverage.
  • Generator fixtures, app-react16 compatibility fixtures, e2e/test files, and build/tool configs are not converted in this PR.

Test plan

  • pnpm -r run type-check
  • pnpm start format.listDifferent
  • PR-scoped eslint over touched TypeScript files
  • pnpm run lint currently fails outside PR Migrate OSS dummy client examples to TypeScript #3606 scope in test/shakaperf/rsc-fouc after merging main (shaka-shared unresolved, with cascading unsafe-type errors).
  • (cd react_on_rails && bundle exec rubocop)
  • git diff --check

Additional verification performed during the branch work:

  • Dummy SWC parser probe over current dummy sources: 55 files, 53 TS/TSX, 0 failures.
  • Renderer arity probe confirmed converted 3-arg renderer functions preserve runtime arity.
  • bundle exec rspec spec/packs_generator_spec.rb from react_on_rails/spec/dummy.
  • react_on_rails:generate_packs idempotency check for server-bundle.ts generated import.
  • Dummy pnpm run build:test passed with the OSS bundle and generated packs.
  • Live Rails HTTP checks returned 200 and expected content for representative SSR/client pages: /, /render_js, /server_side_hello_world, /server_side_redux_app.
  • Pro/RSC temporary server-bundle TS source probe confirmed RSC derivation still uses source server-bundle.ts while outputting rsc-bundle.js.
  • CI-order Knip reproduction after react_on_rails:generate_packs: pnpm exec knip --exclude binaries and pnpm exec knip --production --exclude binaries.
  • Docs target existence check for the converted repository-relative dummy TS example links. Local lychee --config .lychee.toml is blocked because installed lychee 0.24.2 rejects the repo config field used by the pinned CI lychee version.

Review follow-up notes:

  • Review follow-up at 4258650b: registration debug logging now labels function source length vs object export-key counts, generated import/extensions suppression is scoped to TS/JSX-family server-bundle source entrypoints, and build:test intentionally relies on bin/shakapacker-precompile-hook because the shared hook runs build:rescript before generated pack creation when ReScript config is present.
  • Merged current origin/main into the branch and resolved the CHANGELOG.md conflict only.
  • Added client/app/types/generated-packs.d.ts for */generated/server-bundle-generated.js rather than committing an ignored generated stub, so clean-checkout dummy type-checking accepts the generated import while the real generated pack stays uncommitted.
  • Switched new dummy TypeScript example docs links from SHA-pinned GitHub URLs to repository-relative links. main URLs would 404 before this PR merges, and pinned SHA URLs go stale after the migration.
  • Deferred pure cleanup/nit review comments to Follow up on non-blocking dummy TypeScript migration review nits #3648: PRRT_kwDOAnNnU86HM0BD, PRRT_kwDOAnNnU86HM0VO, PRRT_kwDOAnNnU86HM0Xc, PRRT_kwDOAnNnU86HRvIZ, PRRT_kwDOAnNnU86HRvOS, and PRRT_kwDOAnNnU86HM0dG.

Summary by CodeRabbit

  • Bug Fixes

    • Server bundle resolution now detects source files by extension and works with auto-generated packs.
  • New Features

    • Component registration accepts object values and render APIs accept richer prop shapes.
    • Dummy app and tooling now include comprehensive TypeScript entrypoints and type-check support.
  • Documentation

    • Examples, guides, and error messages updated to reference TypeScript filenames and revised SWC/pack wiring.
  • Tests

    • Added coverage for pack generation and server-bundle resolution behaviors.

Codex follow-up notes

  • RailsContextForDisplay remains a dummy-app local type in client/app/types rather than becoming a react-on-rails public type; the migration keeps public API surface unchanged.
  • Redux dummy startup files share the HMR-aware hydrate/render helper, preserving one WeakMap per helper module and avoiding duplicated root-reuse logic.

Comment thread react_on_rails/lib/react_on_rails/packs_generator.rb
Comment thread react_on_rails/lib/react_on_rails/packs_generator.rb
Comment thread packages/react-on-rails-pro/src/ComponentRegistry.ts
Comment thread react_on_rails/spec/dummy/spec/packs_generator_spec.rb

@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: 80f04e80de

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/react-on-rails/src/createReactOutput.ts Outdated
@claude

claude Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Code Review — PR #3606: Migrate OSS dummy client examples to TypeScript

This is a well-structured, high-quality PR. The TypeScript migration is thorough, type discipline is strong (no any escapes, no missing keys, clean hook patterns), and the registerStoreGenerators migration is complete and correct. The Ruby PacksGenerator changes are solid with good spec coverage. The notes below are in descending priority.


1. Pro registry silently narrows the new RegisteredComponentValue union (type safety)

File: packages/react-on-rails-pro/src/ComponentRegistry.ts, line 51

component: component as ReactComponentOrRenderFunction,

The OSS registry now accepts RegisteredComponentValue = ReactComponentOrRenderFunction | Record<string, unknown>, but the pro registry casts back to the narrower function type at storage time. If a plain-object server_render_js registration ever flows through the pro path (e.g. someone uses the pro bundle with a plain-object HelloString-style registration), the stored .component will have a misleading type — the runtime guards downstream will still catch it, but a type-level assertion that should narrow has been silently widened away. Worth aligning the pro registry's stored type with the OSS change or adding a guard that rejects the plain-object case explicitly instead of casting.


2. New throw paths in createReactOutput / ClientRenderer have no unit tests

Files:

  • packages/react-on-rails/src/createReactOutput.ts lines 86–87, 121–122
  • packages/react-on-rails/src/ClientRenderer.ts lines 138–139

These are the three new typeof component !== 'function' guards that fire when a plain-object registration reaches a render-function or renderer path. They are the main safety net for misuse of the new RegisteredComponentValue API. A unit test that registers a plain object and asserts that calling render() / hydrateOrRender() throws the expected message would both document the contract and prevent future regressions.


3. getComponent() return type is a soft breaking change for TS consumers

.component on the return of getComponent() / getOrWaitForComponent() is now typed as RegisteredComponentValue (i.e. ReactComponentOrRenderFunction | Record<string, unknown>). Any downstream TypeScript caller that wrote:

const { component } = ReactOnRails.getComponent('MyComp');
component(props, railsContext); // ← now a type error: object is not callable

…will get a compile error after upgrading. This is technically correct (the caller should narrow), but the type signature change deserves a mention in the CHANGELOG under a "TypeScript breaking changes" note alongside the existing entry, so users know to add a typeof component === 'function' guard or cast.


4. Minor: generated_server_bundle_import_pattern interpolation in extended-mode regex

File: react_on_rails/lib/react_on_rails/packs_generator.rb, lines 438–447

generated_server_bundle_import_pattern = Regexp.union(
  relative_import_path_to_generated_server_bundle,
  import_path_to_generated_server_bundle,
  legacy_relative_import_path_to_generated_server_bundle
)
generated_server_bundle_import_regex = /
  import\s+['"]
  #{generated_server_bundle_import_pattern}
  ['"]
/x

Regexp.union with string arguments does auto-escape (Ruby docs: "If a String is passed, it is first escaped with Regexp.escape"), so the union itself is safe. However, the surrounding /x (extended) regex strips all literal whitespace in the alternatives. Pack paths only contain word chars, dots, and slashes so there's no real risk today — just worth a brief comment noting why /x is safe here (no spaces in pack paths) to avoid a future maintainer adding a space-containing import alias and being surprised.


5. Spec gap: .mts/.cts not covered in add_generated_pack_to_server_bundle integration tests

File: react_on_rails/spec/dummy/spec/packs_generator_spec.rb

The resolve_server_bundle_source_entrypoint tests cover .mts/.cts correctly, but the add_generated_pack_to_server_bundle integration block only loops over %w[.mjs .cjs]. Since .mts/.cts also land in SERVER_BUNDLE_IMPORT_EXTENSION_COMMENT_EXTENSIONS (and would get the eslint-disable-line import/extensions comment appended), adding them to the integration loop would complete the coverage.


Summary

# Severity Area
1 Medium Pro registry type cast — align stored type with OSS change or add explicit guard
2 Low Missing unit tests for new throw paths in createReactOutput/ClientRenderer
3 Low CHANGELOG/docs: note .component type narrowing required for TS consumers
4 Nit Comment on why /x regex with union of paths is safe
5 Nit Add .mts/.cts to add_generated_pack_to_server_bundle spec loop

Items 4–5 are nits suitable for the deferred #3648 cleanup. Items 1–3 are worth addressing before merge.

componentRegistry.set(name, {
name,
component,
component: component as ReactComponentOrRenderFunction,

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 as ReactComponentOrRenderFunction cast silently discards the Record<string, unknown> arm of the new RegisteredComponentValue union at the type level. The OSS registry stores component typed as RegisteredComponentValue; the pro registry narrows it back to the function-only type at storage time.

If a plain-object server_render_js registration ever flows through the pro bundle, downstream runtime guards in createReactOutput / delegateToRenderer will still fire — but callers that read .component from this registry will see the wrong type and any function-call guard that depends on the stored type won't trigger.

Consider one of:

  1. Widening the CallbackRegistry generic to RegisteredComponent<RegisteredComponentValue> (aligning with the OSS change), or
  2. Keeping the narrowed type but adding an explicit pre-storage guard that throws/warns when typeof component !== 'function', instead of casting.
Suggested change
component: component as ReactComponentOrRenderFunction,
component: component as ReactComponentOrRenderFunction,

}

if (typeof component !== 'function') {
throw new Error(`Registered render function "${name}" must be a function.`);

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.

This new guard (and its sibling at line ~135 for the component path) has no dedicated test. A unit test asserting that registering a plain object and calling through the render-function path throws "must be a function" would document the contract. The corresponding guard in ClientRenderer.ts has the same gap.

import_path_to_generated_server_bundle,
legacy_relative_import_path_to_generated_server_bundle
)
generated_server_bundle_import_regex = /

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 /x (extended/free-spacing) mode strips literal whitespace from the regex. Regexp.union with string arguments auto-escapes them (per Ruby docs), so the union alternatives are safe. However, the \s+ between import and the quote is also inside the /x block — this still works because \s is a character class escape, not a literal space.

Worth a brief inline comment that /x is intentional here (free-spacing for readability) and that the union alternatives contain no spaces, so stripping is harmless — saves future readers from having to rediscover this.

@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: d61e13cd3c

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

};

type ReactComponent = ComponentType<any> | string;
type ReactComponent = ComponentType<any> | ExoticComponent<any> | string;

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 Wrap async exotic render-function results

Because ReactComponent now includes ExoticComponent, TypeScript render functions can now validly resolve to React.memo, React.forwardRef, or React.lazy component objects, but the createReactOutput promise branch still only calls createElement when typeof result === 'function'. In that async scenario the exotic component object is returned raw to ReactDOM/server rendering instead of being wrapped as an element, so it fails at runtime; please apply the same React object component handling used for synchronous registrations to promised render-function results.

Useful? React with 👍 / 👎.

* origin/main:
  Improve create-app smoke diagnostics (#3599)
  Suppress regression issues when only /posts_page: Pro regresses (#3668)
  Harden ShakaPerf artifact setup
  Polish RSC generator install follow-ups
  Harden renderer teardown polish
  fix(benchmark): harden target monitoring
  Docs: clarify rails new JavaScript skip flag (#3666)
  Tighten benchmark summary annotations
  Decouple Shakapacker config helpers from SystemChecker

# Conflicts:
#	packages/react-on-rails/src/createReactOutput.ts
@claude

claude Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

PR Review: Migrate OSS dummy client examples to TypeScript

Overview

This PR accomplishes two complementary things:

  1. Core Ruby change (PacksGenerator): Resolves server bundle source entrypoints by extension, so config.server_bundle_js_file = "server-bundle.js" continues to name the compiled output while the source can be server-bundle.ts, server-bundle.tsx, etc.
  2. Dummy app TypeScript migration: 53 of 55 dummy client source files converted to TS/TSX with proper type annotations, replacing PropTypes throughout.

Strengths

Core framework changes:

  • The RegisteredComponent<T> generic with ReactComponentOrRenderFunction as the default is an excellent backward-compatibility choice.
  • Adding ExoticComponent<any> to the ReactComponent type union correctly enables React.memo, React.forwardRef, and React.lazy to pass through createReactOutput.
  • The new componentRegistrationMetric helper correctly distinguishes function-vs-object registrations instead of unconditionally calling .toString() (which would throw on plain objects).
  • Runtime type guards added to createReactOutput.ts and ClientRenderer.ts before coercions are good defensive additions.

Ruby packs generator:

  • Extension-resolution logic in resolve_server_bundle_source_entrypoint is clean: configured path wins if it exists, then tries sibling candidates in a documented priority order.
  • The idempotency regex accounts for three historical path forms (normalized, extensionless, legacy ./-prefixed). Good forward/backward compat.
  • The @server_bundle_entrypoint reset pattern (at method start + in ensure) is correct.
  • Test coverage for the new Ruby methods is thorough: resolution by extension, JSX-before-TS priority, bare-extension configured entrypoint, and import idempotency for each non-JS extension.

TypeScript migration quality:

  • Class-to-arrow-method conversions remove the manual bind calls correctly.
  • RailsContextForDisplay as a local extension of the public RailsContext type keeps the public API surface unchanged.
  • Accessibility improvements in ImageExample.tsx (div with role="img" + aria-label) are correct.
  • The routes.tsx change from anonymous default export to named routes constant is a good debugging improvement.

Issues and Observations

1. $$typeof duck-typing is less strict than strictModeSupport.tsx (minor)

createReactOutput.ts:isReactObjectComponentType accepts any object whose $$typeof is any symbol or number. The parallel check in strictModeSupport.tsx:isObjectComponent validates against specific known React component type symbols via a Set. While not a practical security concern, the inconsistency means a plain object with $$typeof: Symbol('anything') would pass the createReactOutput guard as a renderable component. The stricter version already exists in this repo — worth aligning. See inline comment.

2. Duplicate alternatives in Regexp.union for TS entrypoints (very minor)

In add_generated_pack_to_server_bundle, Regexp.union receives three path alternatives. For TypeScript source entrypoints, relative_import_path_to_generated_server_bundle and import_path_to_generated_server_bundle evaluate to the same string (../generated/server-bundle-generated.js), because generated_server_bundle_import_path only strips .js when the source entrypoint is itself .js. The union works correctly — the duplicate alternative is harmless. For .js sources they differ (with vs without .js), which is the important case. A clarifying comment would help future readers. See inline comment.

3. build:test script change depends on hook internals (low risk)

The old pnpm run build:rescript was explicit. The new bin/shakapacker-precompile-hook relies on the hook knowing to run ReScript compilation when ReScript config is present. This is correct today, but the implicit dependency could be surprising when debugging CI failures. The PR description explains the rationale; consider adding a brief inline comment in the script.

4. Relative doc links (informational)

The ../../../react_on_rails/... paths in docs/oss/*/... all resolve correctly to the repo root — verified the relative depth for each changed doc file. These links will render correctly on GitHub after merge.


Summary

The code is well-written, well-tested, and the design decisions (generic default, extension resolution priority, import idempotency across formats) are sound. The Ruby spec coverage for the new resolution logic is particularly thorough. LGTM with the minor observations above.

// React.memo, React.forwardRef, React.lazy, and related component types are non-callable
// objects tagged with React's element-type marker.
const typeMarker = (value as { $$typeof?: unknown }).$$typeof;
return typeof typeMarker === 'symbol' || typeof typeMarker === 'number';

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.

This check accepts any object whose $$typeof is any symbol or number. The parallel guard in strictModeSupport.tsx:isObjectComponent is more precise — it validates against a Set of specific known React component type symbols:

const componentType = (component as ObjectComponent).$$typeof;
return componentType !== undefined && getReactObjectComponentTypes().has(componentType);

The difference: a plain object with $$typeof: Symbol('custom') passes here but would be rejected there. In practice this is unlikely to cause problems (registered components are intentional), but the inconsistency could be surprising. Consider importing/reusing the same Set-based check, or at minimum add a comment noting that this is intentionally looser (duck-typing for any React-like marker vs. exhaustive known-symbol list).

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Pro Node Renderer Benchmark Summary

Benchmark RPS p50(ms) p90(ms) Status
Pro Node Renderer: simple_eval (non-RSC) 2111.86 ▼3.6% (2191.76) 4.29 ▲3.2% (4.16) 5.37 ▼32.2% (7.92) 200=63358
Pro Node Renderer: react_ssr (non-RSC) 1923.87 ▼0.1% (1925.5) 4.69 ▼0.7% (4.72) 5.92 ▼27.8% (8.2) 200=57718

▲/▼ non-zero change vs baseline · 0.0% exact/near-zero match · 🔴 significant regression · 🟢 significant improvement (tracked measures) · (n) = baseline

Comment on lines +438 to +447
generated_server_bundle_import_pattern = Regexp.union(
relative_import_path_to_generated_server_bundle,
import_path_to_generated_server_bundle,
legacy_relative_import_path_to_generated_server_bundle
)
generated_server_bundle_import_regex = /
import\s+['"]
#{generated_server_bundle_import_pattern}
['"]
/x

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 three-alternative union is correct, but the first two alternatives are identical when the source entrypoint is not .js (e.g. .ts, .tsx, .jsx). generated_server_bundle_import_path only strips the .js suffix when the source entrypoint is itself .js — for all other extensions it returns the same explicit .js path that relative_import_path already produced.

This is harmless (a duplicate in a Regexp.union is silently redundant), but could confuse a future maintainer who wonders why three alternatives are needed. Consider a brief comment:

# Three alternatives for idempotency across path formats:
#   1. relative_import_path_to_generated_server_bundle  — current normalized path (always with .js)
#   2. import_path_to_generated_server_bundle           — .js stripped for .js-source extensionless imports
#      (same as #1 for non-.js sources; duplicate is harmless)
#   3. legacy "./" prefixed path from old generator output
generated_server_bundle_import_pattern = Regexp.union(

"test:e2e:debug": "npx playwright test --config=e2e/playwright.config.js --debug",
"test:e2e:report": "npx playwright show-report e2e/playwright-report",
"build:test": "rm -rf public/webpack/test && pnpm run build:rescript && RAILS_ENV=test NODE_ENV=test bin/shakapacker",
"build:test": "rm -rf public/webpack/test && RAILS_ENV=test NODE_ENV=test bin/shakapacker-precompile-hook && SHAKAPACKER_SKIP_PRECOMPILE_HOOK=true RAILS_ENV=test NODE_ENV=test bin/shakapacker",

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 switch from pnpm run build:rescript && bin/shakapacker to bin/shakapacker-precompile-hook && ... bin/shakapacker is correct — the hook runs build:rescript before pack generation when a ReScript config is present, and SHAKAPACKER_SKIP_PRECOMPILE_HOOK=true prevents it from running again as part of the main shakapacker call.

That said, the behavior is now implicit: if someone adds a new pre-compilation step and forgets to wire it into the hook, build:test will silently skip it. A short comment on this line would help contributors understand why the hook is used instead of an explicit build:rescript call:

"build:test": "rm -rf public/webpack/test && RAILS_ENV=test NODE_ENV=test bin/shakapacker-precompile-hook && SHAKAPACKER_SKIP_PRECOMPILE_HOOK=true RAILS_ENV=test NODE_ENV=test bin/shakapacker",

e.g. add an npm comment key or note in a nearby README that the hook runs build:rescript (and any future pre-compilation steps) before pack generation.

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Core Benchmark Summary

Benchmark RPS p50(ms) p90(ms) Status
/: Core 3.54 🟢 29.0% (2.74) 2334.08 ▼13.9% (2712.31) 2891.06 ▼20.2% (3624.48) 200=114
/client_side_hello_world: Core 750.38 ▲14.8% (653.85) 8.86 ▼3.6% (9.19) 17.56 ▼20.6% (22.12) 200=22671
/client_side_rescript_hello_world: Core 745.7 ▲10.3% (676.12) 8.91 ▼2.5% (9.14) 17.56 ▼19.0% (21.67) 200=22529
/client_side_hello_world_shared_store: Core 688.07 ▲8.8% (632.43) 9.32 ▼5.4% (9.86) 23.59 ▲2.8% (22.95) 200=20786
/client_side_hello_world_shared_store_controller: Core 704.13 ▲13.0% (623.04) 9.24 ▼6.3% (9.86) 19.12 ▼18.3% (23.41) 200=21274
/client_side_hello_world_shared_store_defer: Core 705.62 ▲11.2% (634.78) 9.12 ▼8.0% (9.91) 18.79 ▼15.6% (22.26) 200=21318
/server_side_hello_world_shared_store: Core 15.5 ▲31.2% (11.81) 585.25 ▼12.5% (668.8) 708.52 ▼16.4% (847.2) 200=473
/server_side_hello_world_shared_store_controller: Core 15.5 🟢 31.6% (11.78) 559.77 ▼12.7% (641.43) 698.65 ▼17.2% (843.96) 200=473
/server_side_hello_world_shared_store_defer: Core 12.96 ▲10.4% (11.74) 454.2 ▼29.5% (644.58) 605.63 ▼30.1% (866.03) 200=398
/server_side_hello_world: Core 31.4 ▲31.2% (23.94) 265.91 ▼15.9% (316.31) 314.1 ▼20.5% (395.04) 200=955
/server_side_hello_world_hooks: Core 31.31 ▲31.5% (23.81) 267.83 ▼15.8% (318.0) 321.78 ▼19.1% (397.87) 200=951
/server_side_hello_world_props: Core 31.36 ▲31.5% (23.84) 269.74 ▼17.2% (325.67) 313.85 ▼20.3% (393.67) 200=955
/client_side_log_throw: Core 705.47 ▲6.5% (662.68) 8.59 ▼10.3% (9.57) 21.35 ▲8.2% (19.73) 200=21313
/server_side_log_throw: Core 30.36 ▲29.9% (23.37) 278.33 ▼16.5% (333.41) 331.58 ▼17.8% (403.54) 200=920
/server_side_log_throw_plain_js: Core 30.62 ▲29.8% (23.6) 272.03 ▼13.1% (313.13) 329.32 ▼17.8% (400.86) 200=931
/server_side_log_throw_raise: Core 30.82 🟢 30.3% (23.65) 278.17 ▼11.4% (314.01) 327.02 ▼17.9% (398.33) 3xx=935
/server_side_log_throw_raise_invoker: Core 841.17 ▲8.8% (773.12) 9.49 ▲10.6% (8.58) 16.7 ▲2.3% (16.33) 200=25410
/server_side_hello_world_es5: Core 30.72 ▲30.6% (23.52) 208.89 ▼34.9% (320.73) 310.14 ▼22.4% (399.8) 200=939
/server_side_redux_app: Core 29.75 ▲28.8% (23.1) 281.83 ▼15.1% (331.91) 330.3 ▼18.4% (404.76) 200=906
/server_side_hello_world_with_options: Core 31.39 ▲31.2% (23.92) 205.82 ▼35.9% (320.96) 304.86 ▼23.1% (396.34) 200=957
/server_side_redux_app_cached: Core 768.13 ▲19.5% (642.63) 9.41 ▼9.9% (10.44) 16.06 ▼13.4% (18.55) 200=23205
/client_side_manual_render: Core 773.07 ▲11.7% (692.26) 7.86 ▼15.0% (9.24) 13.1 ▼32.4% (19.37) 200=23358
/render_js: Core 33.88 ▲37.5% (24.65) 248.24 ▼19.5% (308.39) 294.66 ▼22.6% (380.65) 200=1030
/react_router: Core 29.99 ▲32.9% (22.56) 302.51 ▼13.2% (348.45) 335.44 ▼19.3% (415.6) 200=909
/pure_component: Core 31.98 ▲31.2% (24.38) 258.12 ▼19.8% (321.72) 306.27 ▼23.0% (397.94) 200=976
/css_modules_images_fonts_example: Core 31.31 ▲32.1% (23.7) 193.55 ▼39.2% (318.2) 302.55 ▼24.1% (398.42) 200=959
/turbolinks_cache_disabled: Core 511.73 ▼23.8% (671.43) 11.21 ▲18.5% (9.46) 12.81 ▼35.4% (19.84) 200=15561
/rendered_html: Core 32.06 ▲36.4% (23.5) 261.7 ▼19.3% (324.46) 310.34 ▼22.6% (400.95) 200=974
/xhr_refresh: Core 16.19 🟢 31.4% (12.32) 344.2 ▼44.3% (617.59) 642.16 ▼23.5% (839.94) 200=498
/react_helmet: Core 31.03 🟢 31.7% (23.57) 266.05 ▼18.2% (325.11) 320.47 ▼20.6% (403.6) 200=943
/broken_app: Core 31.06 🟢 32.1% (23.51) 272.22 ▼16.0% (324.04) 323.71 ▼18.3% (396.16) 200=943
/image_example: Core 24.56 ▲6.3% (23.09) 243.27 ▼25.5% (326.44) 297.42 ▼25.6% (399.61) 200=752
/turbo_frame_tag_hello_world: Core 794.21 ▲8.5% (731.73) 7.95 ▼8.8% (8.72) 18.43 ▲1.4% (18.18) 200=23992
/manual_render_test: Core 775.73 ▲16.5% (665.75) 8.42 ▼10.5% (9.41) 16.78 ▼14.9% (19.72) 200=23430

▲/▼ non-zero change vs baseline · 0.0% exact/near-zero match · 🔴 significant regression · 🟢 significant improvement (tracked measures) · (n) = baseline

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Pro (shard 1/2) Benchmark Summary

Benchmark RPS p50(ms) p90(ms) Status
/: Pro 171.16 ▼2.9% (176.27) 46.46 ▲11.0% (41.86) 64.04 ▲2.0% (62.77) 200=5177
/error_scenarios_hub: Pro 334.38 ▼5.1% (352.4) 22.49 ▲12.6% (19.97) 33.98 ▲8.0% (31.46) 200=10105
/ssr_async_error: Pro 307.51 ▼7.2% (331.43) 25.28 ▲22.5% (20.63) 37.44 ▲9.6% (34.16) 200=9303
/ssr_async_prop_error: Pro 255.9 ▼20.6% (322.4) 22.74 ▲4.0% (21.87) 40.11 ▲9.4% (36.67) 200=7736
/non_existing_react_component: Pro 272.32 ▼20.4% (342.27) 21.31 ▲5.2% (20.26) 46.82 ▲33.4% (35.1) 200=8240
/non_existing_rsc_payload: Pro 314.43 ▼12.0% (357.49) 23.73 ▲17.7% (20.17) 36.6 ▲1.8% (35.95) 200=9503
/cached_react_helmet: Pro 374.18 ▲1.3% (369.28) 20.58 ▲5.6% (19.49) 34.11 ▲12.3% (30.38) 200=11306
/cached_redux_component: Pro 343.05 ▼11.0% (385.62) 16.78 ▼12.1% (19.09) 53.65 ▲70.5% (31.46) 200=10367
/lazy_apollo_graphql: Pro 153.18 ▲1.8% (150.47) 46.95 ▼2.9% (48.34) 70.14 ▼13.4% (80.95) 200=4632
/redis_receiver: Pro 89.66 ▲2.7% (87.31) 57.1 ▼15.6% (67.64) 144.2 ▼1.3% (146.15) 200=2732
/stream_shell_error_demo: Pro 315.97 ▼5.3% (333.59) 18.16 ▼11.8% (20.6) 37.44 ▲8.9% (34.37) 200=9549
/test_incremental_rendering: Pro 374.12 ▲10.0% (340.01) 20.66 ▼2.5% (21.19) 33.35 ▼8.6% (36.49) 200=11303
/rsc_posts_page_over_redis: Pro 99.8 ▼6.5% (106.75) 74.2 ▲18.1% (62.83) 121.94 ▲11.9% (108.95) 200=3019
/async_on_server_sync_on_client: Pro 313.78 ▼1.1% (317.28) 23.7 ▲6.9% (22.18) 37.69 ▼6.4% (40.27) 200=9485
/server_router: Pro 282.01 ▼14.7% (330.49) 30.71 ▲44.2% (21.29) 43.79 ▲24.2% (35.26) 200=8520
/unwrapped_rsc_route_client_render: Pro 253.8 ▼31.8% (372.21) 12.5 ▼36.4% (19.66) 25.64 ▼17.3% (31.0) 200=7723
/async_render_function_returns_string: Pro 218.13 ▼36.3% (342.52) 26.29 ▲26.5% (20.78) 35.62 ▲5.4% (33.8) 200=6635
/async_components_demo: Pro 172.04 ▼15.5% (203.72) 47.18 ▲29.3% (36.49) 67.22 ▲30.4% (51.53) 200=5201
/stream_native_metadata: Pro 260.36 ▼22.7% (337.02) 21.25 ▲0.7% (21.1) 40.01 ▲13.9% (35.12) 200=7921
/rsc_native_metadata: Pro 246.25 ▼25.0% (328.48) 30.77 ▲37.8% (22.33) 50.53 ▲41.7% (35.67) 200=7442
/client_side_hello_world: Pro 291.36 ▼19.3% (361.14) 25.84 ▲30.5% (19.81) 41.52 ▲35.2% (30.7) 200=8806
/client_side_hello_world_shared_store_controller: Pro 278.07 ▼18.3% (340.5) 27.92 ▲33.5% (20.92) 43.08 ▲32.8% (32.45) 200=8402
/server_side_hello_world_shared_store: Pro 241.56 ▼16.6% (289.5) 32.81 ▲27.1% (25.81) 49.48 ▲27.4% (38.85) 200=7300
/server_side_hello_world_shared_store_defer: Pro 243.36 ▼16.1% (289.97) 32.45 ▲25.7% (25.81) 45.6 ▲21.4% (37.57) 200=7355
/server_side_hello_world_hooks: Pro 324.41 ▼6.7% (347.61) 23.39 ▲13.0% (20.7) 37.03 ▲4.4% (35.47) 200=9802
/server_side_log_throw: Pro 358.21 ▲4.0% (344.46) 21.78 ▲0.9% (21.59) 31.65 ▼8.6% (34.61) 200=10826
/server_side_log_throw_raise: Pro 643.11 ▼1.8% (654.71) 13.53 ▲21.2% (11.16) 18.34 ▲1.0% (18.15) 3xx=19427
/server_side_hello_world_es5: Pro 352.76 ▲3.4% (341.05) 22.17 ▲4.2% (21.27) 31.94 ▼5.6% (33.83) 200=10679
/server_side_hello_world_with_options: Pro 287.84 ▼13.6% (333.21) 20.03 ▼7.4% (21.64) 35.92 ▲5.3% (34.1) 200=8714
/client_side_manual_render: Pro 388.61 ▲6.3% (365.69) 14.02 ▼27.3% (19.29) 27.13 ▼10.6% (30.34) 200=11822
/react_router: Pro 429.86 ▲9.6% (392.04) 16.39 ▼7.2% (17.66) 30.8 ▲4.9% (29.35) 200=12990
/css_modules_images_fonts_example: Pro 372.16 ▲9.2% (340.89) 20.8 ▼2.8% (21.4) 31.53 ▼5.5% (33.37) 200=11248
/rendered_html: Pro 390.38 ▲14.0% (342.5) 19.92 ▼6.3% (21.27) 29.74 ▼9.8% (32.96) 200=11798
/react_helmet: Pro 367.79 ▲9.3% (336.44) 15.03 ▼31.9% (22.07) 28.82 ▼12.9% (33.11) 200=11187
/image_example: Pro 295.32 ▼9.8% (327.38) 19.27 ▼13.6% (22.31) 23.59 ▼32.3% (34.87) 200=8927
/posts_page: Pro 64.76 ▼90.2% (657.79) 86.95 ▲217.5% (27.39) 166.76 ▲327.0% (39.06) 200=1963

▲/▼ non-zero change vs baseline · 0.0% exact/near-zero match · 🔴 significant regression · 🟢 significant improvement (tracked measures) · (n) = baseline

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Pro (shard 2/2) Benchmark Summary

Benchmark RPS p50(ms) p90(ms) Status
/empty: Pro 1175.09 ▼4.7% (1232.99) 6.53 ▲11.9% (5.83) 11.19 ▲25.9% (8.89) 200=35497
/ssr_shell_error: Pro 253.21 ▼23.1% (329.28) 23.0 ▲4.9% (21.93) 57.5 ▲64.0% (35.06) 200=7652
/ssr_sync_error: Pro 294.56 ▼9.7% (326.23) 26.31 ▲20.3% (21.87) 41.96 ▲13.6% (36.93) 200=8902
/rsc_component_error: Pro 292.72 ▼10.2% (326.05) 26.51 ▲18.7% (22.33) 39.76 ▲11.1% (35.77) 200=8849
/non_existing_stream_react_component: Pro 309.95 ▼8.2% (337.55) 25.21 ▲19.0% (21.18) 37.45 ▲8.4% (34.55) 200=9365
/server_side_redux_app_cached: Pro 326.59 ▼10.5% (365.01) 16.86 ▼18.4% (20.67) 32.52 ▲0.4% (32.4) 200=9935
/loadable: Pro 275.42 ▼8.0% (299.52) 11.85 ▼51.4% (24.39) 37.04 ▲3.5% (35.79) 200=8380
/apollo_graphql: Pro 125.8 ▼9.7% (139.24) 56.85 ▲11.2% (51.14) 85.09 ▼0.5% (85.48) 200=3805
/console_logs_in_async_server: Pro 3.15 ▼2.9% (3.24) 2123.88 ▲0.1% (2122.16) 2150.06 ▼2.4% (2202.23) 200=108
/stream_error_demo: Pro 288.13 ▼10.8% (323.16) 26.77 ▲22.5% (21.86) 43.23 ▲20.3% (35.94) 200=8706
/stream_async_components: Pro 293.09 ▼9.9% (325.33) 26.79 ▲19.9% (22.34) 42.4 ▲14.7% (36.98) 200=8857
/rsc_posts_page_over_http: Pro 289.92 ▼10.5% (323.91) 30.18 ▲33.8% (22.56) 41.85 ▲16.8% (35.84) 200=8758
/rsc_echo_props: Pro 200.42 ▼12.0% (227.75) 39.64 ▲20.7% (32.85) 57.36 ▲17.1% (49.0) 200=6061
/async_on_server_sync_on_client_client_render: Pro 262.02 ▼24.9% (348.8) 22.25 ▲7.2% (20.76) 33.69 ▼1.0% (34.01) 200=7919
/server_router_client_render: Pro 259.37 ▼25.1% (346.19) 22.24 ▲7.2% (20.74) 40.96 ▲27.6% (32.09) 200=7839
/unwrapped_rsc_route_stream_render: Pro 299.39 ▼10.3% (333.76) 25.78 ▲21.4% (21.23) 44.45 ▲19.1% (37.32) 200=9044
/async_render_function_returns_component: Pro 298.93 ▼8.4% (326.48) 26.22 ▲23.1% (21.3) 41.45 ▲19.4% (34.73) 200=9036
/native_metadata: Pro 295.58 ▼10.4% (329.96) 26.59 ▲19.4% (22.27) 41.8 ▲18.7% (35.2) 200=8932
/hybrid_metadata_streaming: Pro 284.25 ▼14.0% (330.43) 27.22 ▲24.3% (21.9) 43.79 ▲17.9% (37.14) 200=8591
/cache_demo: Pro 234.63 ▼26.8% (320.48) 24.26 ▲7.7% (22.53) 32.34 ▼15.0% (38.04) 200=7138
/client_side_hello_world_shared_store: Pro 290.91 ▼11.5% (328.85) 21.81 ▼0.3% (21.87) 35.97 ▲7.4% (33.48) 200=8794
/client_side_hello_world_shared_store_defer: Pro 289.69 ▼12.4% (330.53) 26.7 ▲19.2% (22.39) 39.31 ▲15.5% (34.03) 200=8754
/server_side_hello_world_shared_store_controller: Pro 248.82 ▼9.7% (275.57) 31.75 ▲20.4% (26.36) 50.22 ▲23.4% (40.7) 200=7520
/server_side_hello_world: Pro 231.01 ▼28.0% (321.05) 25.17 ▲12.9% (22.29) 69.14 ▲95.9% (35.3) 200=6982
/client_side_log_throw: Pro 263.76 ▼28.0% (366.14) 21.87 ▲8.0% (20.26) 36.0 ▲17.4% (30.67) 200=7972
/server_side_log_throw_plain_js: Pro 325.0 ▼12.0% (369.13) 17.24 ▼12.2% (19.63) 32.35 ▲1.5% (31.88) 200=9823
/server_side_log_throw_raise_invoker: Pro 352.38 ▼12.5% (402.93) 22.18 ▲26.9% (17.48) 34.92 ▲25.6% (27.81) 200=10647
/server_side_redux_app: Pro 290.52 ▼10.1% (323.13) 27.07 ▲21.8% (22.23) 42.52 ▲21.0% (35.15) 200=8777
/server_side_redux_app_cached: Pro 322.88 ▼11.5% (365.01) 24.34 ▲17.8% (20.67) 35.82 ▲10.5% (32.4) 200=9761
/render_js: Pro 334.19 ▼9.3% (368.47) 22.92 ▲15.5% (19.84) 35.51 ▲12.0% (31.7) 200=10099
/pure_component: Pro 302.61 ▼8.1% (329.24) 25.59 ▲16.8% (21.91) 38.3 ▲2.2% (37.48) 200=9146
/turbolinks_cache_disabled: Pro 317.83 ▼11.9% (360.59) 24.4 ▲21.4% (20.09) 35.75 ▲13.9% (31.39) 200=9606
/xhr_refresh: Pro 249.35 ▼10.1% (277.49) 31.98 ▲21.8% (26.26) 48.7 ▲27.0% (38.34) 200=7533
/broken_app: Pro 292.65 ▼14.7% (343.17) 26.76 ▲24.5% (21.5) 39.51 ▲11.6% (35.41) 200=8847
/server_render_with_timeout: Pro 238.21 ▼30.5% (342.54) 24.06 ▲9.4% (22.0) 30.41 ▼10.8% (34.09) 200=7202

▲/▼ non-zero change vs baseline · 0.0% exact/near-zero match · 🔴 significant regression · 🟢 significant improvement (tracked measures) · (n) = baseline

@justin808 justin808 merged commit af4d304 into main Jun 5, 2026
58 checks passed
@justin808 justin808 deleted the ihabadham/chore/dummy-client-typescript-migration branch June 5, 2026 21:05
justin808 added a commit that referenced this pull request Jun 5, 2026
…lement

* origin/main:
  Migrate OSS dummy client examples to TypeScript (#3606)
  Docs: use async props pattern in metadata and render-function examples (#3672)
  Require PR numbers in squash merge titles (#3684)
  Improve create-app smoke diagnostics (#3599)
  Suppress regression issues when only /posts_page: Pro regresses (#3668)
  Harden ShakaPerf artifact setup
  Polish RSC generator install follow-ups
  Harden renderer teardown polish
  fix(benchmark): harden target monitoring
  Docs: clarify rails new JavaScript skip flag (#3666)
  Tighten benchmark summary annotations
  Decouple Shakapacker config helpers from SystemChecker
  Derive Pro RSC client refs from the RSC graph (#3556)
  Docs: follow up v15 anchor and Rails 5.2 flag

# Conflicts:
#	react_on_rails/spec/dummy/client/app/startup/ManualRenderApp.jsx
#	react_on_rails/spec/dummy/client/app/startup/ReduxApp.client.jsx
#	react_on_rails/spec/dummy/client/app/startup/ReduxSharedStoreApp.client.jsx
justin808 added a commit that referenced this pull request Jun 5, 2026
…cessing-flow

* origin/main:
  Add maintainer +ci commands for full-CI dispatch and waivers (#3687)
  Adopt renderer-function teardown in dummy apps + docs (#3578) (#3585)
  Migrate OSS dummy client examples to TypeScript (#3606)
  Docs: use async props pattern in metadata and render-function examples (#3672)
  Require PR numbers in squash merge titles (#3684)
justin808 added a commit that referenced this pull request Jun 8, 2026
## Summary
- Document why the dummy Redux state keeps `railsContext` indexable
while retaining the RailsContext fields.

Fixes #3648.

## Codex Decision Log
- **Non-blocking:** #3648 is a mixed bucket of optional PR #3606 review
nits.
- **Decision:** Implement only the bounded Redux state-indexing
documentation note.
- **Why:** It is a one-line explanation on the exact type shape under
review and avoids broad generator, Redux Toolkit, path-regex, or shared
startup refactors.
- **Review later:** The remaining #3648 candidates should stay separate
if maintainers want them.

## Test plan
- `script/ci-changes-detector origin/main` - pass; source comments only,
recommends lint.
- `git diff --check origin/main..HEAD` - pass.
- `pnpm run lint` - pass.
- `pnpm start format.listDifferent` - pass.
- `pnpm run type-check` from `react_on_rails/spec/dummy` - pass.
- `bundle exec rubocop` - fails on pre-existing unrelated
`react_on_rails/spike/3313_prism_gemfile_rewriter/*` offenses; this PR
changes no Ruby files.

## Review gates
- Manual diff review used.
- `codex review --base origin/main` skipped because this is a trivial
one-line source-comment change.
- Claude review and `/simplify` skipped; no high-risk/full-ci/benchmark
scope.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Comment-only change in a spec dummy file; no behavior, types, or
production code affected.
> 
> **Overview**
> Adds an inline comment on **`ReduxAppState`** in the dummy app’s
`reduxTypes.ts` explaining why **`railsContext`** is typed as
`RailsContext & Record<string, unknown>`: **`combineReducers`** keys
state by reducer name, and the intersection keeps normal RailsContext
fields while still allowing that indexed lookup.
> 
> No runtime or type-shape changes—documentation only for reviewers of
the dummy Redux example (addresses #3648).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
dc4c4db. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Convert spec/dummy/client to Typescript

2 participants