Skip to content

Conversation

@justin808
Copy link
Member

@justin808 justin808 commented Nov 22, 2025

Summary

REVERTED: After analysis, the parameter reordering was reverted to preserve better API ergonomics. The original parameter order (customConsoleHistory, numberOfMessagesToSkip) is more natural for the actual usage patterns in the codebase.

Problem & Analysis

Initially attempted to fix ESLint default-param-last rule violation by reordering parameters. However, analysis of all 6 call sites showed this degraded API quality:

Usage Pattern Analysis:

  • 3 call sites: Pass only customConsoleHistory (most common)
  • 2 call sites: Use defaults
  • 1 call site: Pass both parameters (rare)

Impact of Reordering:

// Before reorder (natural)
consoleReplay(consoleHistory)  // ✅ Clean - most common use

// After reorder (forced)
consoleReplay(0, consoleHistory)  // ❌ Ugly - requires passing 0!

The ESLint rule optimizes for passing only numberOfMessagesToSkip, which never happens in the codebase.

Final Solution

Kept original parameter order and disabled the ESLint rule with justification:

// eslint-disable-next-line @typescript-eslint/default-param-last
export function consoleReplay(
  customConsoleHistory: Console['history'] | undefined = undefined,
  numberOfMessagesToSkip = 0
): string

Changes

Commit 1: Initial Refactor (Reverted)

  • Reordered parameters to satisfy ESLint rule
  • Updated all call sites

Commit 2: Code Review Fixes

  • ✅ Removed debug console.error statements from production code
  • ✅ Added documentation explaining consoleReplay() vs buildConsoleReplay()
  • ✅ Changed nonce parameter to idiomatic TypeScript syntax (nonce?: string)
  • ✅ Added comprehensive test coverage for parameter functionality

Commit 3: Parameter Order Reversion ⭐

  • Reverted to original parameter order based on usage analysis
  • Added ESLint disable comments with detailed justification
  • Updated all call sites back to natural, readable forms
  • See PARAMETER_ORDER_ANALYSIS.md for complete analysis

Files Changed

  1. buildConsoleReplay.ts: Kept original parameter order, added ESLint disable
  2. serverRenderReactComponent.ts: Clean call sites: buildConsoleReplay(consoleHistory)
  3. streamingUtils.ts: Natural order: consoleReplay(consoleHistory, skipCount)
  4. buildConsoleReplay.test.js: Added 3 new test cases, updated for parameter order

Testing

  • ✅ All 104 tests pass
  • ✅ Build passes: yarn run build
  • ✅ RuboCop passes: bundle exec rubocop
  • ✅ ESLint passes: rake lint:eslint
  • ✅ Type checking passes: yarn run type-check

Breaking Change?

No - These are internal functions. All call sites updated to use the original, more natural parameter order.

Key Takeaway

Sometimes lint rules don't fit the domain. This is a case where:

  • The rule is valid in general (default params should usually be last)
  • But doesn't match our usage (we frequently pass only the first param)
  • Disabling is justified when it improves API ergonomics

See PARAMETER_ORDER_ANALYSIS.md for detailed analysis of all call sites and rationale.


🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests

    • Added new test cases for console replay functionality with various parameter combinations to improve code coverage.
  • Chores

    • Refactored internal code structure for improved maintainability.

✏️ Tip: You can customize this high-level summary in your review settings.

Fix ESLint default-param-last rule violation by reordering parameters
in buildConsoleReplay and consoleReplay functions.

**Changes:**

- Reorder consoleReplay parameters: numberOfMessagesToSkip comes first
- Reorder buildConsoleReplay parameters: numberOfMessagesToSkip, customConsoleHistory, nonce
- Update all call sites in serverRenderReactComponent.ts
- Update Pro package streaming to use correct parameter order
- Update tests to use new parameter order

**Why:**

ESLint enforces that optional parameters with defaults must come last.
The previous order had customConsoleHistory (with default) before
numberOfMessagesToSkip (with default), violating this rule.

**Testing:**

- ✅ Build passes: yarn run build
- ✅ Linting passes: bundle exec rubocop && yarn run eslint
- ✅ No breaking changes - all call sites updated

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

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

coderabbitai bot commented Nov 22, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Import pattern updated from default to named import for console replay utilities. Type annotations removed from function parameters while preserving default values. New test cases added to verify console message skipping and custom history handling during streaming.

Changes

Cohort / File(s) Summary
Import and Usage Refactoring
packages/react-on-rails-pro/src/streamingUtils.ts
Changed from default import buildConsoleReplay to named import consoleReplay, updated function call accordingly, and added inline comments explaining unwrapped JavaScript behavior and streaming context implications.
Type Annotation Removal
packages/react-on-rails/src/buildConsoleReplay.ts
Removed explicit number type annotation from numberOfMessagesToSkip parameter in both consoleReplay and buildConsoleReplay functions, relying on type inference from default value. Added eslint-disable-next-line @typescript-eslint/default-param-last`` directives before both function declarations.
Test Coverage Expansion
packages/react-on-rails/tests/buildConsoleReplay.test.js
Added three new test cases: message skipping (verifying specified messages are omitted), custom console history (verifying custom history replaces global), and combined skip count with custom history.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

  • Verify the import refactoring (buildConsoleReplayconsoleReplay) doesn't introduce unexpected side effects in the streaming context.
  • Confirm that removing explicit type annotations and relying on inference is the intended TypeScript behavior and aligns with the eslint directives.
  • Review new test cases for completeness and correct assertion logic.

Possibly related PRs

Suggested reviewers

  • AbanoubGhadban
  • alexeyr-ci
  • Judahmeek

Poem

🐰 A named import hops through the stream,
Type annotations fade like a dream,
Default parameters stay in place,
While tests embrace each edge case,
The console replays with graceful ease! 🎪

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: reordering buildConsoleReplay parameters to maintain original order while complying with ESLint rules, which is the core objective of this PR.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a9a1864 and c158656.

📒 Files selected for processing (3)
  • packages/react-on-rails-pro/src/streamingUtils.ts (2 hunks)
  • packages/react-on-rails/src/buildConsoleReplay.ts (2 hunks)
  • packages/react-on-rails/tests/buildConsoleReplay.test.js (1 hunks)

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 Nov 22, 2025

Code Review: Refactor buildConsoleReplay Parameter Ordering

Overall Assessment: LGTM with minor suggestions

This is a well-executed refactoring that fixes an ESLint rule violation while improving API ergonomics. The changes are thorough, well-tested, and properly documented.


Strengths

1. Correctness

  • ✅ All call sites updated correctly across both open-source and Pro packages
  • ✅ Test suite comprehensively updated
  • ✅ Parameter reordering makes logical sense (skip count before optional history override)

2. Breaking Change Handling

  • ✅ Correctly identified as internal API (not publicly exported)
  • ✅ All usages within the codebase updated
  • ✅ Good justification that the new order is more intuitive

3. Documentation

  • ✅ Excellent PR description with before/after examples
  • ✅ Clear explanation of the ESLint rule violation
  • ✅ Testing checklist included

Issues & Suggestions

🔴 Critical: Debug Code Left in Production

Location: packages/react-on-rails-pro/src/streamingUtils.ts:118-122

// DEBUG: Log to verify we're using the unwrapped version
if (consoleReplayScript && consoleReplayScript.startsWith('<script')) {
  console.error('ERROR: Console replay is wrapped when it should be unwrapped\!');
  console.error('First 100 chars:', consoleReplayScript.substring(0, 100));
}

Issue: This debug code should be removed before merging. It will pollute server logs in production.

Recommendation: Remove these lines entirely or convert to a conditional debug log:

if (process.env.NODE_ENV === 'development' && consoleReplayScript?.startsWith('<script')) {
  console.warn('Unexpected wrapped console replay:', consoleReplayScript.substring(0, 100));
}

⚠️ Medium: Inconsistent Function Usage in streamingUtils.ts

Location: packages/react-on-rails-pro/src/streamingUtils.ts:20

Before (PR diff):

import buildConsoleReplay from 'react-on-rails/buildConsoleReplay';
// ...
const consoleReplayScript = buildConsoleReplay(consoleHistory, previouslyReplayedConsoleMessages);

After:

import { consoleReplay } from 'react-on-rails/buildConsoleReplay';
// ...
const consoleReplayScript = consoleReplay(previouslyReplayedConsoleMessages, consoleHistory);

Question: Why switch from buildConsoleReplay to consoleReplay here? This is more than just parameter reordering—it's changing which function is called.

Analysis:

  • buildConsoleReplay wraps output in <script> tags
  • consoleReplay returns unwrapped JavaScript
  • The debug code suggests you need unwrapped output for streaming

Recommendation:

  1. Add a comment explaining WHY consoleReplay is used instead of buildConsoleReplay
  2. If this is a bug fix (streaming previously used wrong function), mention it in the PR description
  3. Consider if this deserves its own test case

💡 Minor: Test Coverage Gap

The test suite has good coverage for the parameter reordering, but could benefit from:

  1. Test for numberOfMessagesToSkip functionality:
it('consoleReplay skips specified number of messages', () => {
  console.history = [
    { arguments: ['skip 1'], level: 'log' },
    { arguments: ['skip 2'], level: 'log' },
    { arguments: ['keep 1'], level: 'log' },
  ];
  const actual = consoleReplay(2); // Skip first 2 messages
  expect(actual).toEqual('console.log.apply(console, ["keep 1"]);');
});
  1. Test for custom console history parameter:
it('consoleReplay uses custom console history when provided', () => {
  console.history = [{ arguments: ['ignored'], level: 'log' }];
  const customHistory = [{ arguments: ['custom'], level: 'warn' }];
  const actual = consoleReplay(0, customHistory);
  expect(actual).toEqual('console.warn.apply(console, ["custom"]);');
});

📝 Minor: TypeScript Type Annotations

Location: buildConsoleReplay.ts:60

nonce: string | undefined = undefined,

Observation: Changed from nonce?: string to nonce: string | undefined = undefined

Question: Was this change intentional? Both are functionally equivalent, but the optional parameter syntax (?) is more idiomatic TypeScript.

Recommendation: Consider keeping nonce?: string unless there's a specific reason for the explicit | undefined union type.


Testing Validation

Based on CLAUDE.md requirements, please confirm:

  • bundle exec rubocop passes
  • yarn run eslint . --max-warnings 0 passes
  • yarn run build succeeds
  • rake run_rspec:gem passes
  • rake js_tests passes
  • Manual verification: Streaming console replay works correctly in Pro package

Security Review

✅ No security concerns identified:

  • XSS protection maintained (existing scriptSanitizedVal still in place)
  • Nonce sanitization unchanged
  • No new attack vectors introduced

Performance Review

✅ No performance impact:

  • Parameter reordering is zero-cost abstraction
  • No algorithmic changes
  • No additional allocations

Recommendation

Approve with changes:

  1. Remove debug code from streamingUtils.ts
  2. Add comment explaining consoleReplay vs buildConsoleReplay usage
  3. ⚠️ Consider adding tests for numberOfMessagesToSkip functionality
  4. 💭 Consider reverting nonce type annotation to optional parameter syntax

Great work on this refactoring! The parameter order is definitely more intuitive now. 👍

- Remove debug console.error statements from streamingUtils.ts
- Add documentation explaining why consoleReplay() is used instead of buildConsoleReplay()
  in streaming contexts (unwrapped JS for JSON payloads vs wrapped for HTML)
- Use idiomatic TypeScript optional parameter syntax (nonce?: string)
- Add comprehensive test coverage for numberOfMessagesToSkip and customConsoleHistory parameters
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/react-on-rails/tests/buildConsoleReplay.test.js (1)

127-173: New consoleReplay tests give good coverage of skip/custom-history behavior

The three added specs cleanly validate:

  • Skipping an initial prefix of messages (consoleReplay(2)),
  • Preferring a supplied customHistory over console.history,
  • Combining skip count with a custom history (consoleReplay(1, customHistory)).

They align with the reordered (numberOfMessagesToSkip, customConsoleHistory) API and assertions use toContain/not.toContain, so they’re robust to formatting details.

One follow‑up you might consider: the global Console.history.level type in buildConsoleReplay.ts currently excludes 'warn', but these tests (and earlier ones) use 'warn' values; widening that union to include 'warn' would better match actual usage.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f91737d and a9a1864.

📒 Files selected for processing (4)
  • packages/react-on-rails-pro/src/streamingUtils.ts (2 hunks)
  • packages/react-on-rails/src/buildConsoleReplay.ts (2 hunks)
  • packages/react-on-rails/src/serverRenderReactComponent.ts (3 hunks)
  • packages/react-on-rails/tests/buildConsoleReplay.test.js (4 hunks)
🧰 Additional context used
🧠 Learnings (12)
📚 Learning: 2025-09-16T08:01:11.146Z
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.

Applied to files:

  • packages/react-on-rails-pro/src/streamingUtils.ts
  • packages/react-on-rails/src/serverRenderReactComponent.ts
📚 Learning: 2025-04-26T21:55:55.874Z
Learnt from: alexeyr-ci2
Repo: shakacode/react_on_rails PR: 1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.

Applied to files:

  • packages/react-on-rails-pro/src/streamingUtils.ts
  • packages/react-on-rails/src/serverRenderReactComponent.ts
📚 Learning: 2024-10-08T20:53:47.076Z
Learnt from: theforestvn88
Repo: shakacode/react_on_rails PR: 1620
File: spec/dummy/client/app/startup/HelloTurboStream.jsx:3-3
Timestamp: 2024-10-08T20:53:47.076Z
Learning: The `RailsContext` import in `spec/dummy/client/app/startup/HelloTurboStream.jsx` is used later in the project, as clarified by the user theforestvn88.

Applied to files:

  • packages/react-on-rails-pro/src/streamingUtils.ts
📚 Learning: 2024-12-12T13:07:09.929Z
Learnt from: alexeyr-ci
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/ReactOnRailsRSC.ts:87-87
Timestamp: 2024-12-12T13:07:09.929Z
Learning: When handling errors in 'node_package/src/ReactOnRailsRSC.ts', include the error stack in error messages in development and test environments to aid debugging.

Applied to files:

  • packages/react-on-rails-pro/src/streamingUtils.ts
  • packages/react-on-rails/src/serverRenderReactComponent.ts
  • packages/react-on-rails/src/buildConsoleReplay.ts
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
Repo: shakacode/react_on_rails PR: 1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.

Applied to files:

  • packages/react-on-rails-pro/src/streamingUtils.ts
  • packages/react-on-rails/src/serverRenderReactComponent.ts
📚 Learning: 2025-02-13T19:09:15.991Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/RSCWebpackLoader.ts:0-0
Timestamp: 2025-02-13T19:09:15.991Z
Learning: In React Server Components webpack loader, using `new Function('return import("react-server-dom-webpack/node-loader")')()` is necessary as a workaround to bypass TypeScript compilation issues with direct dynamic imports.

Applied to files:

  • packages/react-on-rails-pro/src/streamingUtils.ts
  • packages/react-on-rails/src/serverRenderReactComponent.ts
📚 Learning: 2025-02-13T16:50:47.848Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/clientStartup.ts:18-21
Timestamp: 2025-02-13T16:50:47.848Z
Learning: In the react_on_rails module, the `reactOnRailsPageUnloaded` function in clientStartup.ts is intentionally kept private as it's only used internally as a callback for `onPageUnloaded`.

Applied to files:

  • packages/react-on-rails-pro/src/streamingUtils.ts
📚 Learning: 2025-07-08T05:57:29.630Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1745
File: node_package/src/RSCRequestTracker.ts:8-14
Timestamp: 2025-07-08T05:57:29.630Z
Learning: The global `generateRSCPayload` function in React on Rails Pro (RORP) is provided by the framework during rendering requests, not implemented in application code. The `declare global` statements are used to document the expected interface that RORP will inject at runtime.

Applied to files:

  • packages/react-on-rails-pro/src/streamingUtils.ts
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.

Applied to files:

  • packages/react-on-rails-pro/src/streamingUtils.ts
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation is handled in deeper level calls of the React on Rails Pro codebase, so it doesn't need to be validated again in the `rsc_payload_react_component` helper method.

Applied to files:

  • packages/react-on-rails-pro/src/streamingUtils.ts
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.

Applied to files:

  • packages/react-on-rails-pro/src/streamingUtils.ts
📚 Learning: 2025-04-09T13:02:16.009Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1696
File: node_package/src/transformRSCNodeStream.ts:3-31
Timestamp: 2025-04-09T13:02:16.009Z
Learning: In the React Server Components (RSC) stream processing implementation, all chunks end with a newline character (`\n`), so the `lastIncompleteChunk` variable is expected to be empty by the time the stream ends, making a flush handler unnecessary in the Transform stream.

Applied to files:

  • packages/react-on-rails-pro/src/streamingUtils.ts
🧬 Code graph analysis (3)
packages/react-on-rails/tests/buildConsoleReplay.test.js (2)
packages/react-on-rails/src/buildConsoleReplay.ts (2)
  • buildConsoleReplay (57-67)
  • consoleReplay (18-55)
packages/react-on-rails/src/base/client.ts (1)
  • buildConsoleReplay (174-176)
packages/react-on-rails-pro/src/streamingUtils.ts (1)
packages/react-on-rails/src/buildConsoleReplay.ts (1)
  • consoleReplay (18-55)
packages/react-on-rails/src/serverRenderReactComponent.ts (3)
packages/react-on-rails/src/buildConsoleReplay.ts (1)
  • buildConsoleReplay (57-67)
packages/react-on-rails/src/base/client.ts (1)
  • buildConsoleReplay (174-176)
packages/react-on-rails/src/serverRenderUtils.ts (1)
  • createResultObject (9-24)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: dummy-app-integration-tests (3.4, 22, latest)
  • GitHub Check: build
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: pro-lint-js-and-ruby
  • GitHub Check: claude-review
🔇 Additional comments (7)
packages/react-on-rails/src/buildConsoleReplay.ts (2)

18-21: consoleReplay parameter reordering looks correct and preserves behavior

numberOfMessagesToSkip as the first (and defaulted) parameter reads well, and the function body still uses it consistently via slice(numberOfMessagesToSkip). No behavioral change beyond the signature shape.


57-63: buildConsoleReplay signature and internal call aligned with new API

Defaulted (numberOfMessagesToSkip = 0, customConsoleHistory = undefined, nonce?) combined with consoleReplay(numberOfMessagesToSkip, customConsoleHistory) keeps prior semantics while fixing the parameter ordering rule; nonce handling is untouched.

packages/react-on-rails/tests/buildConsoleReplay.test.js (1)

79-86: Updated buildConsoleReplay call sites match the new signature

The changes to buildConsoleReplay(0, undefined, 'abc123' | maliciousNonce) correctly map to (numberOfMessagesToSkip, customConsoleHistory, nonce) and preserve the previous behavior of “no skipping, default history, custom nonce”.

Also applies to: 88-93, 111-125

packages/react-on-rails-pro/src/streamingUtils.ts (1)

20-20: Streaming now correctly uses unwrapped consoleReplay with new parameter order

Using { consoleReplay } and calling it as consoleReplay(previouslyReplayedConsoleMessages, consoleHistory) matches the new (skipCount, customHistory) signature and is appropriate for embedding plain JS into the streaming JSON payload, leaving script‑tag wrapping to the caller as documented in the comments.

Also applies to: 115-121

packages/react-on-rails/src/serverRenderReactComponent.ts (3)

6-6: Import swap to default buildConsoleReplay is consistent with module exports

Switching from a named consoleReplay import to the default buildConsoleReplay aligns this file with the refactored module while keeping the public behavior identical.


109-118: Async result handling still correctly preserves console history

In createPromiseResult, calling buildConsoleReplay(0, consoleHistory) in both the success and error branches continues to rely on the captured consoleHistory reference taken before the await. This preserves the prior guarantee that logs aren’t lost even if the global console.history is reset later.


131-132: Final synchronous result now uses buildConsoleReplay with defaulted parameters

buildConsoleReplay() in createFinalResult is a drop‑in replacement for the previous consoleReplay() usage, adding the script‑tag wrapping while defaulting to “no skip, global history, no nonce”. Behavior for the sync path remains unchanged for callers.

The original parameter order (customConsoleHistory, numberOfMessagesToSkip) was
changed to satisfy the ESLint rule @typescript-eslint/default-param-last. However,
this change degraded API ergonomics:

OLD (natural): consoleReplay(consoleHistory)
NEW (forced):  consoleReplay(0, consoleHistory)

Analysis of all 6 call sites shows:
- 3 call sites: Pass only customConsoleHistory (most common)
- 2 call sites: Use defaults
- 1 call site:  Pass both parameters (rare)

The new parameter order forces the most common use case to pass an ugly '0'
parameter, while the supposed benefit (passing only numberOfMessagesToSkip) is
never actually used in the codebase.

Disable the ESLint rule for these functions since the original order better
matches real-world usage patterns.

See PARAMETER_ORDER_ANALYSIS.md for complete analysis.
@justin808 justin808 changed the title Refactor buildConsoleReplay to fix parameter ordering Keep original buildConsoleReplay parameter order (disable ESLint rule) Nov 22, 2025
These documents provide context and analysis for the parameter order
refactoring work:

- PARAMETER_ORDER_ANALYSIS.md: Detailed analysis of the parameter order
  issue and rationale for reverting to original order
- REVIEW_CHANGES_SUMMARY.md: Summary of code review feedback that was
  addressed

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

Co-Authored-By: Claude <noreply@anthropic.com>
@justin808
Copy link
Member Author

@claude review this PR

@claude
Copy link

claude bot commented Nov 23, 2025

Claude finished @justin808's task —— View job


Code Review for PR #2097

Status: ✅ APPROVED - Excellent decision to prioritize API ergonomics over lint rules


Summary

This PR demonstrates excellent engineering judgment: it reverts a parameter reordering that would have satisfied an ESLint rule but degraded API usability. The final solution keeps the original, more natural parameter order and disables the ESLint rule with clear justification.


🎯 Key Strengths

1. Data-Driven Decision Making

The PARAMETER_ORDER_ANALYSIS.md document is exemplary - it analyzes all 6 call sites in the codebase and proves that the original parameter order matches actual usage patterns:

  • 3 call sites: Pass only customConsoleHistory (most common)
  • 2 call sites: Use defaults
  • 1 call site: Pass both parameters

The reordering would have forced the most common use case to become consoleReplay(0, consoleHistory) - ugly and less readable.

2. Proper Lint Rule Handling

The PR shows maturity in understanding when to disable lint rules:

// eslint-disable-next-line @typescript-eslint/default-param-last
export function consoleReplay(
  customConsoleHistory: (typeof console)['history'] | undefined = undefined,
  numberOfMessagesToSkip = 0,
): string

The rule is valuable in general, but this is a domain-specific case where it doesn't apply. This is the right decision.

3. All Previous Review Issues Addressed

Comparing to the earlier @claude review comment:

Debug code removed - No console.error statements in production code
Documentation added - Excellent comment in streamingUtils.ts explaining why consoleReplay() is used instead of buildConsoleReplay()
Type annotation improved - Changed to idiomatic nonce?: string
Test coverage added - 3 new comprehensive test cases

4. Excellent Documentation

  • PARAMETER_ORDER_ANALYSIS.md: Thorough analysis with real usage examples
  • REVIEW_CHANGES_SUMMARY.md: Clear summary of all changes made
  • Code comments in streamingUtils.ts explain the streaming context requirements

📝 Code Quality Assessment

buildConsoleReplay.ts

Status: ✅ Excellent

// eslint-disable-next-line @typescript-eslint/default-param-last
export function consoleReplay(
  customConsoleHistory: (typeof console)['history'] | undefined = undefined,
  numberOfMessagesToSkip = 0,
): string
  • ESLint disable is justified (see packages/react-on-rails/src/buildConsoleReplay.ts:18)
  • Parameter order matches actual usage patterns
  • Type annotations are idiomatic (nonce?: string)

serverRenderReactComponent.ts

Status: ✅ Excellent

All call sites use the clean, natural API:

buildConsoleReplay(consoleHistory)  // Clean - captures history before await
buildConsoleReplay()                // Clean - uses global history

This is much better than the forced buildConsoleReplay(0, consoleHistory) that would have been required with the reordered parameters.

streamingUtils.ts (Pro)

Status: ✅ Excellent

// Get unwrapped console replay JavaScript (not wrapped in <script> tags)
// We use consoleReplay() instead of buildConsoleReplay() because streaming
// contexts handle script tag wrapping separately (e.g., with CSP nonces).
// This returns pure JavaScript without wrapping, which is then embedded
// into the result object JSON payload.
const consoleReplayScript = consoleReplay(consoleHistory, previouslyReplayedConsoleMessages);

Highlights:

  • ✅ No debug code (previous review issue resolved)
  • ✅ Clear documentation explaining design decision
  • ✅ Correct use of consoleReplay() (unwrapped) vs buildConsoleReplay() (wrapped)
  • ✅ Natural parameter order: consoleReplay(consoleHistory, previouslyReplayedConsoleMessages)

Test Coverage

Status: ✅ Comprehensive

Three new test cases added:

  1. Skip functionality: Tests numberOfMessagesToSkip parameter (packages/react-on-rails/tests/buildConsoleReplay.test.js:127-143)
  2. Custom history: Tests customConsoleHistory parameter (packages/react-on-rails/tests/buildConsoleReplay.test.js:145-159)
  3. Combined usage: Tests both parameters together (packages/react-on-rails/tests/buildConsoleReplay.test.js:161-173)

All tests use the original parameter order and verify correct behavior.


🔍 Technical Review

Security

✅ No concerns

  • XSS protection maintained via scriptSanitizedVal
  • Nonce sanitization unchanged
  • No new attack vectors

Performance

✅ No concerns

  • Parameter order is zero-cost abstraction
  • No algorithmic changes
  • Same performance characteristics

Type Safety

✅ Excellent

  • TypeScript signatures are correct
  • Type inference works properly with defaults
  • Optional parameter syntax is idiomatic

Breaking Changes

✅ None

  • These are internal functions
  • All call sites updated
  • External API unchanged

💡 Suggestions (Optional, Non-Blocking)

1. Consider Removing Analysis Documents Post-Merge

The analysis documents are excellent for the review process, but they might belong in the PR description or a comment rather than the repository:

# Optional cleanup after merge:
git rm PARAMETER_ORDER_ANALYSIS.md REVIEW_CHANGES_SUMMARY.md

Reasoning: These are PR-specific and may clutter the repository long-term. However, if you want to keep them for historical reference, consider moving them to /analysis/ directory per the CLAUDE.md guidelines.

2. ESLint Configuration for Project-Wide Pattern

If this parameter pattern occurs elsewhere, consider documenting it:

// .eslintrc.js
rules: {
  '@typescript-eslint/default-param-last': ['error', {
    // Note: Disable on case-by-case basis when parameter order matches
    // actual usage patterns better than the rule's assumption
  }]
}

🎓 Key Takeaway

This PR is a textbook example of:

  • Using data to drive decisions (analyzing all 6 call sites)
  • Prioritizing developer experience over mechanical rule compliance
  • Thoroughly documenting the rationale
  • Properly addressing code review feedback

The ESLint rule default-param-last assumes you'll frequently pass only the first parameter and skip the second. Your analysis proves the opposite is true in this codebase. Disabling the rule is the right choice.


✅ Recommendation

APPROVE AND MERGE - This PR improves the codebase by:

  1. Maintaining a more natural, readable API
  2. Properly documenting design decisions
  3. Adding comprehensive test coverage
  4. Addressing all previous review feedback

The decision to revert the parameter order and disable the ESLint rule is well-justified and correct.


Files Changed (6)

  • PARAMETER_ORDER_ANALYSIS.md - Excellent analysis documentation
  • REVIEW_CHANGES_SUMMARY.md - Clear change summary
  • packages/react-on-rails-pro/src/streamingUtils.ts - Clean, well-documented
  • packages/react-on-rails/src/buildConsoleReplay.ts - Correct ESLint disable
  • packages/react-on-rails/src/serverRenderReactComponent.ts - Natural API usage
  • packages/react-on-rails/tests/buildConsoleReplay.test.js - Comprehensive coverage

Great work on this refactoring! The analysis and decision-making process is exemplary.


|

justin808 added a commit that referenced this pull request Nov 23, 2025
Reverted the parameter order changes from the ill-conceived PR #2097.
The correct/original parameter order is:
- customConsoleHistory first (most common usage pattern)
- numberOfMessagesToSkip second (rarely used)

This matches what's currently in master and provides better API ergonomics.

Updated:
- buildConsoleReplay.ts: Restored original parameter order
- serverRenderReactComponent.ts: Updated call sites
- buildConsoleReplay.test.js: Updated test calls

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

Co-Authored-By: Claude <noreply@anthropic.com>
The Rails helper wraps console replay script in <script> tags itself,
so we need to use consoleReplay() which returns unwrapped JavaScript,
not buildConsoleReplay() which adds <script> tags.

This fixes all integration test failures where console replay was empty.
@justin808
Copy link
Member Author

Closing this PR. After discussion, we decided to suppress the ESLint rule violation with a comment instead, as the parameter ordering change would be more disruptive than helpful. The rule is suppressed in PR #2094.

@justin808 justin808 closed this Nov 23, 2025
@justin808 justin808 reopened this Nov 23, 2025
These documents described intermediate refactoring steps that are no
longer relevant. The final fix was simply using the unwrapped
consoleReplay() function instead of buildConsoleReplay() in
serverRenderReactComponent.ts.
@justin808 justin808 merged commit 0263541 into master Nov 23, 2025
19 of 20 checks passed
@justin808 justin808 deleted the refactor-console-replay-parameters branch November 23, 2025 00:57
justin808 added a commit that referenced this pull request Nov 23, 2025
After rebasing on master (which merged PR #2097 with parameter order fixes),
the ESLint configuration now has the plain 'default-param-last' rule enabled,
not the TypeScript version.

The buildConsoleReplay function has default params before an optional param
(nonce?), which violates the rule. Added block-level eslint-disable comments.

The consoleReplay function doesn't violate the rule (all params have defaults),
so no disable needed.

Fixes CI error:
  error  Default parameters should be last  default-param-last

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

Co-Authored-By: Claude <noreply@anthropic.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.

2 participants