Skip to content

test-ui#80

Merged
PavelVanecek merged 67 commits intomainfrom
test-ui
Nov 30, 2025
Merged

test-ui#80
PavelVanecek merged 67 commits intomainfrom
test-ui

Conversation

@PavelVanecek
Copy link
Collaborator

@PavelVanecek PavelVanecek commented Nov 29, 2025

New UI for running tests, rewrote many things to typescript and ESM

https://github.com/recharts/recharts/blob/main/test/helper/userEventSetup.ts

Summary by CodeRabbit

  • New Features

    • Interactive Test UI: web interface to list, filter, run, and monitor integration tests with per-phase progress, real-time logs, packing support, and session persistence.
    • Starter script to launch the Test UI locally and backend server.
  • Documentation

    • Comprehensive docs added: quick start, development guide, architecture, screenshots, checklist, and migration notes.
  • Chores

    • CI workflow updated for newer Node versions and modernized actions.
    • Test tooling modernized and registry-based test metadata introduced.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 29, 2025

Walkthrough

Migrates legacy JS test tooling to a new TypeScript-based test UI and server. Removes old JS runners/controllers, adds test registry, TypeScript controllers, an Express+WebSocket backend, a React+Redux frontend, CLI entry (run.mts), and supporting docs/configs.

Changes

Cohort / File(s) Change Summary
Workflows & Editor Config
.github/workflows/integ.yml, .run/Template Vitest.run.xml, README.md
Update CI Node matrix and actions (checkout v4, setup-node v6); add IntelliJ Vitest run template; document Test UI in README.
Small package tweaks
integrations/tanstack-start-basic/package.json, apps-3rd-party/app-react17/package.json
Adjusted recharts version spec and EOF newline; no functional code changes.
Route generation
integrations/tanstack-start-basic/src/routeTree.gen.ts
Removed server-side route scaffolding and server route typings; added ambient router typing tied to getRouter.
Test discovery
list.js, test-ui/server/scripts/test-registry.ts
Replace filesystem-based discovery with a registry; implement test metadata APIs (getTestMetadata, getAllTestNames, getAllTests, isStableTest).
Run entry & scripts
run-test.sh, scripts/run.mts, start-ui.sh
Switch run script to run.mts (TS), add TypeScript CLI orchestration (run.mts), and add start-ui.sh to bootstrap UI/server.
Removed legacy JS controllers/tools
scripts/Controller.js, scripts/NpmController.js, scripts/YarnController.js, scripts/TestResult.js, scripts/run.js, scripts/replacePackageVersion.js, scripts/tgzFileNameToPackageJsonReference.js
Delete legacy JavaScript test orchestration, controllers, utilities, and TestResult class (functionality migrated to test-ui TypeScript equivalents).
TypeScript controllers & utilities
test-ui/server/scripts/Controller.ts, test-ui/server/scripts/NpmController.ts, test-ui/server/scripts/YarnController.ts, test-ui/server/scripts/TestOutcome.ts, test-ui/server/scripts/replacePackageVersion.ts, test-ui/server/scripts/tgzFileNameToPackageJsonReference.ts, test-ui/server/scripts/test-registry.ts
Add abstract Controller and Npm/Yarn implementations, TestOutcome class, replacePackageVersion utility, tgz filename helper, and test registry module.
Backend server
test-ui/server/server.ts, test-ui/server/tsconfig.json, test-ui/nodemon.json
New Express + WebSocket server implementing a serial test queue, per-phase lifecycle (clean, setVersion, install, test, build, verify), REST API endpoints, real-time broadcasts, and cancellation/pack handling.
Frontend application & assets
test-ui/src/main.tsx, test-ui/index.html, test-ui/src/App.tsx, test-ui/src/PhaseOutput.tsx, test-ui/src/types.ts, test-ui/src/*.css, test-ui/src/PhaseOutput.css
Add React+TypeScript frontend with App, PhaseOutput, types, CSS, entry and Redux wiring.
Redux store & middleware
test-ui/src/store/store.ts, test-ui/src/store/testsSlice.ts, test-ui/src/store/hooks.ts, test-ui/src/store/websocketMiddleware.ts, test-ui/src/store/testDurationSelectors.ts
New Redux slice and store, typed hooks, WebSocket middleware for backend messages, and selectors for ETA/duration calculations.
Packaging & build config
test-ui/package.json, test-ui/vite.config.ts, test-ui/tsconfig.json, test-ui/server/tsconfig.json, test-ui/.vite/*
Add package.json, Vite config with API proxy, TS configs for app/server, and Vite artifacts.
Docs & design artifacts
test-ui/README.md, test-ui/QUICKSTART.md, test-ui/DEVELOPMENT.md, test-ui/CHANGES.md, test-ui/CHECKLIST.md, test-ui/REFACTORING.md, test-ui/SCREENSHOTS.md, test-ui/SUMMARY.md, test-ui/UI_STRUCTURE.md
Add extensive Test UI documentation, guides, checklist, architecture and UI design notes.
Tests & fixtures
test-ui/test/store/exampleTestResults.ts, test-ui/test/store/testDurationSelectors.spec.ts, test-ui/test/store/testsSlice.spec.ts
Add fixture data and unit tests for selectors and slice behavior.
Misc gitignore / static
test-ui/.gitignore, test-ui/index.html, test-ui/.vite/deps/package.json
Add ignores and minimal static HTML entry and Vite deps metadata.

Sequence Diagram(s)

sequenceDiagram
    participant Client as React Client
    participant WS as WebSocket
    participant Server as Express Server
    participant Queue as Test Queue
    participant Controller as Npm/Yarn Controller
    participant FS as File System

    Client->>Server: POST /api/tests/run (testName, version)
    Server->>Queue: enqueue(test)
    Server->>WS: broadcast "test-queued"
    WS-->>Client: "test-queued"
    Queue->>Server: dequeue -> start test
    Server->>WS: broadcast "test-started"
    WS-->>Client: "test-started"

    loop phases: clean,setVersion,install,test,build,verify
        Server->>Controller: execute phase command
        Controller->>FS: read/write/pack files
        FS-->>Controller: output
        Controller-->>Server: TestOutcome (ok|fail) + output
        Server->>WS: broadcast "test-output" (phase, status, output, duration)
        WS-->>Client: "test-output"
    end

    alt all phases passed
        Server->>WS: broadcast "test-completed" (passed)
    else any failed
        Server->>WS: broadcast "test-completed" (failed)
    end
    WS-->>Client: "test-completed"
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

  • test-ui/server/server.ts: queue orchestration, per-phase execution, cancellation, error propagation, and WebSocket event correctness.
  • test-ui/server/scripts/{NpmController,YarnController}.ts: command execution, JSON parsing of package manager outputs, and dependency verification logic.
  • scripts/run.mts and integration with test-registry/list.js: ensure CLI behavior and metadata lookup are consistent with previous usage.
  • Redux slice and websocketMiddleware: message → action mappings, state transitions (queued → running → results), and persistence logic.
  • testDurationSelectors: ETA and per-phase duration aggregation edge cases.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'test-ui' is vague and non-descriptive. It does not convey the scope or primary purpose of the substantial changes, which involve introducing a new test UI system, migrating scripts to TypeScript, and restructuring the testing infrastructure. Use a more descriptive title that captures the main change, such as 'Add test-ui application with TypeScript test runner' or 'Introduce new Test UI dashboard for integration test management'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch test-ui

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.

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: 16

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (9)
.github/workflows/integ.yml-61-61 (1)

61-61: Update checkout action to v4 for consistency with the list_integration_tests job.

The build-and-test job uses actions/checkout@v3 (line 61), which is inconsistent with the list_integration_tests job that uses actions/checkout@v4 (line 28). Using the same version across jobs in the workflow is recommended for consistency and stability.

Apply this diff:

-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
integrations/tanstack-start-basic/src/routeTree.gen.ts-366-374 (1)

366-374: Unused import and risk of modification in auto-generated file.

  1. createStart (line 368) is imported but never used in the module augmentation.
  2. This file is marked as auto-generated (lines 7-9 warn against making changes). Manual additions may be overwritten when TanStack Router regenerates this file.

Consider moving this module augmentation to a separate declaration file (e.g., tanstack-start.d.ts) to avoid losing changes on regeneration.

 import type { getRouter } from './router.tsx'
-import type { createStart } from '@tanstack/react-start'
 declare module '@tanstack/react-start' {
scripts/run.mts-88-100 (1)

88-100: Missing return statement and type narrowing issue.

  1. Line 90 assigns getTestMetadata(testName) (which returns TestMetadata | undefined) to a variable typed as TestMetadata. The undefined case is checked on line 92, but the type annotation is misleading.

  2. If metadata.type is neither 'library' nor 'direct', the function implicitly returns undefined, violating the Promise<TestOutcome[]> return type.

 export async function runTest(testName: string, rechartsVersion: string): Promise<TestOutcome[]> {
     // First try to get metadata from registry
-    const metadata: TestMetadata = getTestMetadata(testName);
+    const metadata = getTestMetadata(testName);

     if (!metadata) {
         throw new Error(`Test "${testName}" not found in registry.`);
     }
     if (metadata.type === 'library') {
         return await runLibraryTest(metadata, rechartsVersion);
     } else if (metadata.type === 'direct') {
         return await runDirectDependencyTest(metadata, rechartsVersion);
+    } else {
+        throw new Error(`Unknown test type: ${metadata.type}`);
     }
 }
test-ui/README.md-75-83 (1)

75-83: Documentation references incorrect filename.

The architecture section references server.js, but the actual file is server.ts (TypeScript). This should be updated for consistency.

-### Backend (server.js)
+### Backend (server/server.ts)
test-ui/test/store/exampleTestResults.ts-4-1615 (1)

4-1615: Consider sanitizing local filesystem paths in fixture output

Many output strings embed absolute paths from a specific machine (e.g., /Users/pavelvanecek/...). While harmless for functionality, this:

  • Leaks local environment details, and
  • Makes future fixture updates noisy.

If feasible, consider replacing those paths with normalized placeholders (e.g., /path/to/project/...) or trimming logs to just the parts needed by the UI/tests.

test-ui/server/scripts/TestOutcome.ts-1-15 (1)

1-15: Fix array join separator in stringify

The array branch currently joins with the literal string "/n" instead of a newline, which will produce unexpected output formatting. Line 9 has "/n" (forward-slash + n) when it should be "\n" (newline escape).

-  if (Array.isArray(output)) {
-    return output.map((item) => stringify(item)).join("/n");
-  }
+  if (Array.isArray(output)) {
+    return output.map((item) => stringify(item)).join("\n");
+  }
test-ui/server/server.ts-83-84 (1)

83-84: Abort controller is declared but never used.

currentTestAbortController is declared and checked in the cancel endpoint (lines 566-569), but it's never assigned when starting a test. The cancellation mechanism won't actually abort running tests.

If you want test cancellation to work, you'll need to:

  1. Create an AbortController when starting a test in executeTest
  2. Pass its signal to the controller commands
  3. Handle abort signals in the controller methods

Would you like me to help design this cancellation flow?

test-ui/server/server.ts-349-357 (1)

349-357: Missing return for unexpected test type.

If metadata.type is neither "library" nor "direct", the function implicitly returns undefined. This will cause a runtime error when destructuring at line 430: const { fn } = getController(...).

   } else if (metadata.type === "direct") {
     const absolutePath = path.join(rootDir, metadata.integrationPath!);
     const controller = factory(absolutePath);
     return {
       controller,
       fn: runDirectDependencyAppTest(controller, version),
     };
+  } else {
+    throw new Error(`Unknown test type: ${metadata.type}`);
   }
 }
test-ui/server/server.ts-157-172 (1)

157-172: Unreachable code branch.

Line 163 (else if (typeof result === "string")) is unreachable because it's inside the if (typeof result === 'object') block. A result cannot be both an object and a string.

     if (result && typeof result === "object") {
       if (result.success !== undefined) {
         phase.status = result.success ? "passed" : "failed";
         if (result.error) {
           phase.output = String(result.error);
         }
-      } else if (typeof result === "string") {
-        phase.output = result;
-        phase.status = "passed";
+      } else {
+        phase.status = "passed";
       }
     } else if (typeof result === "string") {
       phase.output = result;
       phase.status = "passed";
     } else {
       phase.status = "passed";
     }
🧹 Nitpick comments (26)
.run/Template Vitest.run.xml (1)

7-7: Consider documenting environment variable requirements (if any).

The <envs /> element is empty. If the Vitest tests require environment variables (e.g., for API URLs, test databases, or feature flags), document this in the PR description or a CONTRIBUTING.md file so team members know to set them up when using this template.

test-ui/DEVELOPMENT.md (1)

5-16: Minor: Add language specifier to fenced code blocks.

Lines 5-16 and other code blocks in this file don't specify a language for syntax highlighting. While this is a minor formatting issue, adding language identifiers improves readability. For the directory structure at line 5, you could use ```tree or ```text instead of ```.

Example fix for lines 5-16:

-```
+```tree
 test-ui/
 ├── src/

This is optional and doesn't affect functionality.

test-ui/.gitignore (1)

1-28: Consider adding TypeScript build artifacts.

The .gitignore covers standard Node.js artifacts well. However, it may be missing TypeScript incremental build artifacts (*.tsbuildinfo). If test-ui/server/tsconfig.tsbuildinfo (added in this PR) should not be committed, add a pattern to exclude these files.

Add this line to exclude TypeScript build info files:

 server.js
+
+# TypeScript incremental build info
+*.tsbuildinfo

Verify with your team's build process before applying—some teams commit tsbuildinfo for faster CI builds.

test-ui/SCREENSHOTS.md (2)

92-92: Minor: Use hyphen in compound adjective.

Line 92 reads "Full width gradient background" but should be "Full-width gradient background" per standard English punctuation for compound adjectives modifying a noun.

-- Full width gradient background
+- Full-width gradient background

This is a minor grammar improvement that improves readability.


5-5: Minor: Add language specifiers to fenced code blocks.

Lines 5, 16, 62, and others have fenced code blocks without language identifiers. Adding language specs enables syntax highlighting and follows Markdown best practices.

Examples:

-```
+```text
 ┌─────────────────────────────────────────────────────────────────────────┐
 │  🧪 Recharts Integration Test Runner                                    │

This improves documentation rendering without changing content.

Also applies to: 16-16, 62-62

test-ui/UI_STRUCTURE.md (1)

5-5: Minor: Add language specifiers to fenced code blocks.

Lines 5, 16, and 62 have fenced code blocks without language identifiers. Adding language specs improves rendering and aligns with Markdown best practices.

Add language identifiers like ```text or ```yaml:

-```
+```text
 ┌────────────────────────────────────────────────────────────────────┐
 │ [✓] npm-react18                        ✓ Stable   ✅ passed        │

This is optional but improves documentation quality.

Also applies to: 16-16, 62-62

scripts/run.mts (1)

76-86: Non-null assertions on optional metadata fields without validation.

Lines 77-78 and 85 use non-null assertions (!) on metadata.libraryName, metadata.appName, and metadata.integrationPath. If these fields are undefined for a given test type, path.join() will produce invalid paths like /path/to/libraries/undefined.

Consider validating required fields before use:

 async function runLibraryTest(metadata: TestMetadata, rechartsVersion: string): Promise<TestOutcome[]> {
+    if (!metadata.libraryName || !metadata.appName) {
+        throw new Error(`Library test "${metadata.name}" missing required libraryName or appName`);
+    }
-    const libPath = path.join(__dirname, "../libraries", metadata.libraryName!);
-    const appPath = path.join(__dirname, "../apps-3rd-party", metadata.appName!);
+    const libPath = path.join(__dirname, "../libraries", metadata.libraryName);
+    const appPath = path.join(__dirname, "../apps-3rd-party", metadata.appName);
     const Controller = getControllerConstructor(metadata.packageManager);
     return await runLibraryInLibraryTest(new Controller(libPath), new Controller(appPath), rechartsVersion);
 }

 async function runDirectDependencyTest(metadata: TestMetadata, rechartsVersion: string): Promise<TestOutcome[]> {
+    if (!metadata.integrationPath) {
+        throw new Error(`Direct test "${metadata.name}" missing required integrationPath`);
+    }
     const Controller = getControllerConstructor(metadata.packageManager);
-    return await runDirectDependencyAppTest(new Controller(metadata.integrationPath!), rechartsVersion);
+    return await runDirectDependencyAppTest(new Controller(metadata.integrationPath), rechartsVersion);
 }
test-ui/nodemon.json (1)

1-8: Ensure nodemon can actually run TS and watch all relevant files

As written, nodemon will:

  • Try to execute server/server.ts with plain node.
  • Only restart on .ts changes, not .mts or .js under ../scripts.

Unless you have a TS loader wired in elsewhere, this is likely to fail or miss restarts.

Consider something along these lines (adjusting to whatever you already use — ts-node, tsx, or compiled JS):

-  "watch": ["server", "../scripts"],
-  "ext": "ts",
-  "exec": "node server/server.ts",
+  "watch": ["server", "../scripts"],
+  "ext": "ts,mts,js",
+  "exec": "tsx server/server.ts"

If you instead compile to JS first, point exec at the built JS file and set watch/ext accordingly. Please double‑check this matches your actual dev workflow.

README.md (1)

14-14: Wrap bare URL to satisfy markdownlint (optional)

Markdownlint flags the bare http://localhost:3000. Wrapping it as a link/autolink will clear that without changing meaning.

-Then open http://localhost:3000 in your browser.
+Then open <http://localhost:3000> in your browser.
test-ui/src/PhaseOutput.tsx (2)

82-90: Consider adding ESLint disable comment for intentional missing dependency.

The expandedPhases dependency is intentionally omitted to prevent an infinite loop (expand triggers re-render, re-render triggers effect). Adding a comment clarifies this is deliberate.

   // Auto-expand current phase when running
   useEffect(() => {
     if (currentPhase && !expandedPhases[currentPhase]) {
       setExpandedPhases(prev => ({
         ...prev,
         [currentPhase]: true
       }));
     }
+    // eslint-disable-next-line react-hooks/exhaustive-deps -- expandedPhases intentionally omitted to prevent infinite loop
   }, [currentPhase]);

72-80: Edge case: negative duration could show unexpected results.

If duration is negative (clock skew, incorrect timestamps), this would display a negative value. Consider adding a guard.

 const formatDuration = (duration: Phase['duration']) => {
-  if (!duration) return '-';
+  if (!duration || duration < 0) return '-';
   const seconds = Math.floor(duration / 1000);
test-ui/src/App.css (1)

1-575: Add focus styles for interactive elements

Buttons and clickable phase items have hover styles but no explicit :focus/:focus-visible rules. Adding clear focus outlines (e.g., for .btn, .btn-clear, button.phase-summary-item) would improve keyboard accessibility without changing layout.

test-ui/test/store/testDurationSelectors.spec.ts (2)

99-108: Use array .length instead of Object.keys on results

selectAllTestResults returns an array, so Object.keys(testResults).length is indirect. Using testResults.length is simpler and conveys intent more clearly:

-    expect(Object.keys(testResults).length).toBe(
-      Object.keys(exampleTestResults).length,
-    );
+    expect(testResults.length).toBe(
+      Object.keys(exampleTestResults).length,
+    );

140-157: Stabilize time‑dependent ETA expectations

selectRunningTestsETA uses Date.now() (via the selector), but the test asserts an exact numeric ETA (53682). This can become brittle if:

  • The fixture start time changes, or
  • The implementation tweaks elapsed/remaining calculations.

Consider either mocking the clock (e.g., fixing Date.now()/Vitest system time) or asserting with a tolerance (toBeCloseTo) so these ETAs remain stable across small changes.

test-ui/CHANGES.md (1)

8-30: Fix Markdown list indentation to satisfy markdownlint (MD007)

markdownlint is complaining about unordered list indentation (MD007) for the bullets under sections 1–3. You can fix this by removing the leading spaces before - and using standard nested indentation, for example:

-### 1. **App.tsx - TestItem Component**
-   - Updated the phase summary to display phase names alongside status icons
-   - Added duration display in tooltips for each phase
-   - Improved the one-line summary to be more informative with phase labels
-   - Reordered buttons for better UX (Run, Show/Hide, Clear)
-   - Phase summary now always visible (not dependent on expanded state)
-   - Made expand/collapse more intuitive with "Show"/"Hide" labels
+### 1. **App.tsx - TestItem Component**
+- Updated the phase summary to display phase names alongside status icons
+- Added duration display in tooltips for each phase
+- Improved the one-line summary to be more informative with phase labels
+- Reordered buttons for better UX (Run, Show/Hide, Clear)
+- Phase summary now always visible (not dependent on expanded state)
+- Made expand/collapse more intuitive with "Show"/"Hide" labels

Apply the same pattern to the bullets under sections 2 and 3 to clear the lints.

test-ui/SUMMARY.md (1)

9-26: Align SUMMARY with current TS structure and clean up markdownlint issues

  • The “Files Created” tree still lists App.jsx, main.jsx, server.js, and vite.config.js, while the refactor doc uses App.tsx, main.tsx, server.ts?, and vite.config.ts. Consider updating the tree to match the actual TypeScript filenames so newcomers don’t get lost.
  • To satisfy markdownlint:
    • Add a language to fenced blocks that are currently bare (e.g. use ```text for the directory tree and data-flow diagram).
    • Wrap bare URLs (like http://localhost:3000) as <http://localhost:3000> or [link](http://localhost:3000).

Also applies to: 61-76, 77-96

test-ui/src/store/testsSlice.ts (1)

4-21: Consider keying queued/running tests by id and reusing shared types

Right now queuedTests and runningTests are keyed by testName, and the queued shape { id, position } is duplicated inline instead of using the QueuedTest type from types.ts. This makes it harder to support multiple concurrent runs of the same test and slightly increases maintenance cost.

Non-blocking, but consider:

  • Using id as the primary key in queuedTests/runningTests and storing testName as a field.
  • Replacing Record<string, { id: string; position: number }> with Record<string, QueuedTest> to keep shapes in sync with types.ts.

Also applies to: 76-90, 91-107

test-ui/REFACTORING.md (1)

75-97: Minor markdown polish for tooling friendliness

A few tiny doc tweaks would make linters happier:

  • Add a language to the fenced blocks showing the project tree and WebSocket message flow (e.g. ```text).
  • Optionally tighten the closing sentence by replacing “remains exactly the same” with “remains unchanged” or similar.

Purely cosmetic; safe to defer.

Also applies to: 185-199, 379-390

test-ui/server/scripts/YarnController.ts (1)

27-33: Prefer await super.clean() for clarity and future safety

clean() calls super.clean() but doesn’t await the returned promise. Today the base implementation is fully synchronous, but if it ever gains async work, that call will no longer be sequenced with the Yarn cache clean.

Non-blocking, but it’s safer and clearer to await it:

-    async clean(): Promise<TestOutcome> {
-        super.clean();
-        await this.execAsync('yarn cache clean');
-        return TestOutcome.ok('clean');
-    }
+    async clean(): Promise<TestOutcome> {
+        await super.clean();
+        await this.execAsync('yarn cache clean');
+        return TestOutcome.ok('clean');
+    }
test-ui/src/store/testDurationSelectors.ts (1)

146-155: Phase order assumption is fragile.

The code relies on Object.keys(test.phases) returning phases in execution order, but object key order isn't guaranteed to match the logical phase sequence. The comment states "the order is not interesting," but it actually matters for calculating remaining phases correctly.

Consider using an explicit phase order array for reliability:

+const PHASE_ORDER: PhaseName[] = [
+  "clean",
+  "setVersion",
+  "install",
+  "test",
+  "build",
+  "verify",
+];
+
 runningTests.forEach((test) => {
   if (!test.currentPhase || !test.phases) {
     return;
   }

-  const phaseNames = Object.keys(test.phases) as PhaseName[];
+  const phaseNames = PHASE_ORDER;
   const currentPhaseIndex = phaseNames.indexOf(test.currentPhase);
test-ui/server/server.ts (1)

47-64: Consider sharing type definitions with the client.

Phase, Phases, and PhaseName are duplicated here and in test-ui/src/types.ts. If these drift apart, it could cause runtime mismatches between server responses and client expectations.

Consider extracting shared types to a common module (e.g., test-ui/shared/types.ts) that both server and client can import.

test-ui/src/App.tsx (5)

356-379: Potential stale closure in interval callback.

The interval callback captures globalStartTime in its closure. When setGlobalStartTime(Date.now()) is called on line 369, the interval from the previous render still uses the old null value until the effect re-runs.

Consider using a ref to avoid the stale closure:

+ const globalStartTimeRef = useRef<number | null>(null);
  const [globalElapsedTime, setGlobalElapsedTime] = useState(0);
- const [globalStartTime, setGlobalStartTime] = useState<number | null>(null);

  useEffect(() => {
    const hasRunningOrQueued =
      runningTestsList.length > 0 || Object.keys(queuedTests).length > 0;

    if (!hasRunningOrQueued) {
      setGlobalElapsedTime(0);
-     setGlobalStartTime(null);
+     globalStartTimeRef.current = null;
      return;
    }

-   if (globalStartTime === null) {
-     setGlobalStartTime(Date.now());
+   if (globalStartTimeRef.current === null) {
+     globalStartTimeRef.current = Date.now();
    }

    const interval = setInterval(() => {
-     if (globalStartTime !== null) {
-       setGlobalElapsedTime(Date.now() - globalStartTime);
+     if (globalStartTimeRef.current !== null) {
+       setGlobalElapsedTime(Date.now() - globalStartTimeRef.current);
      }
    }, 100);

    return () => clearInterval(interval);
- }, [runningTestsList.length, queuedTests, globalStartTime]);
+ }, [runningTestsList.length, queuedTests]);

381-386: Missing dispatch in effect dependencies.

The effect calls functions that use dispatch, but it's not listed in the dependency array. While this works because dispatch is stable, ESLint's react-hooks/exhaustive-deps rule may warn about this.

  useEffect(() => {
    loadTests();
    loadPersistedResultsFromStorage();
    loadRechartsVersions();
    loadPersistedPackingDirectory();
- }, []);
+ }, [dispatch]);

Or suppress the warning if intentional: // eslint-disable-next-line react-hooks/exhaustive-deps


542-576: Consider improving the directory selection UX.

The File System Access API's showDirectoryPicker doesn't provide the full path, so prompting the user to manually enter it defeats much of the convenience.

Since the server runs locally, consider:

  1. Just using a text input (as already implemented as a fallback)
  2. Or implementing a server-side directory browser endpoint

The current approach works but may confuse users who expect the picker to populate the path automatically.


243-246: Avoid any type assertion.

Using (status as any).position bypasses type safety. Since this is a queued status, use the proper type:

- {status.status === "queued"
-   ? `Queued (#${(status as any).position})`
+ {status.status === "queued" && "position" in status
+   ? `Queued (#${status.position})`
    : status.status}

Or use a type guard function to narrow the type properly.


325-870: App component is functional but consider splitting.

The App component is quite large (~550 lines). Consider extracting:

  • Version/package selection controls into a separate component
  • The queue info section into a separate component

This would improve maintainability without being urgent.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 02d2f18 and 870301c.

📒 Files selected for processing (60)
  • .github/workflows/integ.yml (2 hunks)
  • .run/Template Vitest.run.xml (1 hunks)
  • README.md (1 hunks)
  • apps-3rd-party/app-react16/package.json (1 hunks)
  • apps-3rd-party/app-react17/package.json (2 hunks)
  • apps-3rd-party/app-react18/package.json (1 hunks)
  • apps-3rd-party/app-react19/package.json (1 hunks)
  • integrations/tanstack-start-basic/package.json (2 hunks)
  • integrations/tanstack-start-basic/src/routeTree.gen.ts (1 hunks)
  • list.js (1 hunks)
  • run-test.sh (1 hunks)
  • scripts/Controller.js (0 hunks)
  • scripts/NpmController.js (0 hunks)
  • scripts/TestResult.js (0 hunks)
  • scripts/YarnController.js (0 hunks)
  • scripts/replacePackageVersion.js (0 hunks)
  • scripts/run.js (0 hunks)
  • scripts/run.mts (1 hunks)
  • scripts/tgzFileNameToPackageJsonReference.js (0 hunks)
  • start-ui.sh (1 hunks)
  • test-ui/.gitignore (1 hunks)
  • test-ui/CHANGES.md (1 hunks)
  • test-ui/CHECKLIST.md (1 hunks)
  • test-ui/DEVELOPMENT.md (1 hunks)
  • test-ui/QUICKSTART.md (1 hunks)
  • test-ui/README.md (1 hunks)
  • test-ui/REFACTORING.md (1 hunks)
  • test-ui/SCREENSHOTS.md (1 hunks)
  • test-ui/SUMMARY.md (1 hunks)
  • test-ui/UI_STRUCTURE.md (1 hunks)
  • test-ui/index.html (1 hunks)
  • test-ui/nodemon.json (1 hunks)
  • test-ui/package.json (1 hunks)
  • test-ui/server/scripts/Controller.ts (1 hunks)
  • test-ui/server/scripts/NpmController.ts (1 hunks)
  • test-ui/server/scripts/TestOutcome.ts (1 hunks)
  • test-ui/server/scripts/YarnController.ts (1 hunks)
  • test-ui/server/scripts/replacePackageVersion.ts (1 hunks)
  • test-ui/server/scripts/test-registry.ts (1 hunks)
  • test-ui/server/scripts/tgzFileNameToPackageJsonReference.ts (1 hunks)
  • test-ui/server/server.ts (1 hunks)
  • test-ui/server/tsconfig.json (1 hunks)
  • test-ui/server/tsconfig.tsbuildinfo (1 hunks)
  • test-ui/src/App.css (1 hunks)
  • test-ui/src/App.tsx (1 hunks)
  • test-ui/src/PhaseOutput.css (1 hunks)
  • test-ui/src/PhaseOutput.tsx (1 hunks)
  • test-ui/src/index.css (1 hunks)
  • test-ui/src/main.tsx (1 hunks)
  • test-ui/src/store/hooks.ts (1 hunks)
  • test-ui/src/store/store.ts (1 hunks)
  • test-ui/src/store/testDurationSelectors.ts (1 hunks)
  • test-ui/src/store/testsSlice.ts (1 hunks)
  • test-ui/src/store/websocketMiddleware.ts (1 hunks)
  • test-ui/src/types.ts (1 hunks)
  • test-ui/test/store/exampleTestResults.ts (1 hunks)
  • test-ui/test/store/testDurationSelectors.spec.ts (1 hunks)
  • test-ui/test/store/testsSlice.spec.ts (1 hunks)
  • test-ui/tsconfig.json (1 hunks)
  • test-ui/vite.config.ts (1 hunks)
💤 Files with no reviewable changes (7)
  • scripts/tgzFileNameToPackageJsonReference.js
  • scripts/Controller.js
  • scripts/run.js
  • scripts/replacePackageVersion.js
  • scripts/YarnController.js
  • scripts/NpmController.js
  • scripts/TestResult.js
🧰 Additional context used
🧬 Code graph analysis (16)
test-ui/src/main.tsx (1)
test-ui/src/store/store.ts (1)
  • store (9-18)
test-ui/test/store/testsSlice.spec.ts (2)
test-ui/src/types.ts (2)
  • Phase (17-23)
  • TestRun (36-45)
test-ui/src/store/store.ts (3)
  • store (9-18)
  • RootState (20-20)
  • rootReducer (5-7)
test-ui/src/PhaseOutput.tsx (1)
test-ui/src/types.ts (3)
  • PhaseName (34-34)
  • Phases (25-32)
  • Phase (17-23)
test-ui/server/scripts/replacePackageVersion.ts (2)
scripts/run-everything.js (2)
  • version (11-11)
  • fs (3-3)
test-ui/server/scripts/TestOutcome.ts (1)
  • TestOutcome (18-46)
scripts/run.mts (4)
test-ui/server/scripts/TestOutcome.ts (1)
  • TestOutcome (18-46)
test-ui/server/scripts/NpmController.ts (1)
  • NpmController (4-70)
test-ui/server/scripts/YarnController.ts (1)
  • YarnController (27-172)
test-ui/server/scripts/test-registry.ts (2)
  • TestMetadata (12-32)
  • getTestMetadata (204-206)
test-ui/server/scripts/NpmController.ts (2)
test-ui/server/scripts/TestOutcome.ts (1)
  • TestOutcome (18-46)
scripts/run-everything.js (1)
  • output (14-31)
test-ui/src/store/testsSlice.ts (1)
test-ui/src/types.ts (4)
  • Test (1-10)
  • TestRun (36-45)
  • Phases (25-32)
  • PhaseName (34-34)
test-ui/src/store/websocketMiddleware.ts (1)
test-ui/src/store/store.ts (1)
  • store (9-18)
test-ui/src/App.tsx (3)
test-ui/src/types.ts (5)
  • Test (1-10)
  • TestStatus (52-52)
  • PhaseName (34-34)
  • TestRun (36-45)
  • Phases (25-32)
test-ui/src/store/hooks.ts (2)
  • useAppDispatch (4-4)
  • useAppSelector (5-5)
test-ui/src/store/testDurationSelectors.ts (4)
  • selectQueuedTestsETA (100-116)
  • selectRunningTestsETA (123-188)
  • selectElapsedTimeForRunningTestPhases (44-93)
  • selectAllRunningTests (13-18)
test-ui/server/scripts/Controller.ts (4)
test-ui/server/scripts/TestOutcome.ts (1)
  • TestOutcome (18-46)
scripts/run-everything.js (2)
  • fs (3-3)
  • version (11-11)
test-ui/server/scripts/replacePackageVersion.ts (1)
  • replacePackageVersion (4-32)
test-ui/server/scripts/tgzFileNameToPackageJsonReference.ts (1)
  • tgzFileNameToPackageJsonReference (3-5)
test-ui/test/store/testDurationSelectors.spec.ts (3)
test-ui/src/types.ts (1)
  • Phase (17-23)
test-ui/src/store/store.ts (3)
  • RootState (20-20)
  • store (9-18)
  • rootReducer (5-7)
test-ui/src/store/testDurationSelectors.ts (5)
  • selectAllTestResults (23-28)
  • selectElapsedTimeForRunningTestPhases (44-93)
  • selectAllRunningTests (13-18)
  • selectQueuedTestsETA (100-116)
  • selectRunningTestsETA (123-188)
test-ui/server/server.ts (5)
test-ui/src/types.ts (3)
  • Phases (25-32)
  • PhaseName (34-34)
  • Phase (17-23)
test-ui/server/scripts/test-registry.ts (2)
  • getAllTests (223-241)
  • getTestMetadata (204-206)
test-ui/server/scripts/TestOutcome.ts (1)
  • TestOutcome (18-46)
test-ui/server/scripts/NpmController.ts (1)
  • NpmController (4-70)
test-ui/server/scripts/YarnController.ts (1)
  • YarnController (27-172)
test-ui/server/scripts/YarnController.ts (2)
test-ui/server/scripts/Controller.ts (1)
  • execAsync (36-45)
test-ui/server/scripts/TestOutcome.ts (1)
  • TestOutcome (18-46)
test-ui/src/store/hooks.ts (1)
test-ui/src/store/store.ts (2)
  • AppDispatch (21-21)
  • RootState (20-20)
test-ui/src/store/testDurationSelectors.ts (2)
test-ui/src/store/store.ts (1)
  • RootState (20-20)
test-ui/src/types.ts (2)
  • TestRun (36-45)
  • PhaseName (34-34)
test-ui/server/scripts/tgzFileNameToPackageJsonReference.ts (1)
test-ui/server/scripts/Controller.ts (1)
  • tgzFileNameToPackageJsonReference (64-66)
🪛 actionlint (1.7.9)
.github/workflows/integ.yml

61-61: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🪛 LanguageTool
test-ui/SCREENSHOTS.md

[grammar] ~92-~92: Use a hyphen to join words.
Context: ...────────────────────────────┘ ``` - Full width gradient background - Large center...

(QB_NEW_EN_HYPHEN)

README.md

[grammar] ~50-~50: Ensure spelling is correct
Context: ...ersion ### Pack and run with local recharts bash ./pack-and-run.sh ```

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

test-ui/REFACTORING.md

[style] ~390-~390: ‘exactly the same’ might be wordy. Consider a shorter alternative.
Context: ...codebase The app functionality remains exactly the same - all features work as before, now with...

(EN_WORDINESS_PREMIUM_EXACTLY_THE_SAME)

🪛 markdownlint-cli2 (0.18.1)
test-ui/DEVELOPMENT.md

5-5: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

test-ui/UI_STRUCTURE.md

5-5: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


16-16: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


62-62: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

test-ui/CHANGES.md

9-9: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


10-10: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


11-11: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


12-12: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


13-13: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


14-14: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


17-17: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


18-18: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


19-19: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


20-20: Unordered list indentation
Expected: 2; Actual: 5

(MD007, ul-indent)


21-21: Unordered list indentation
Expected: 2; Actual: 5

(MD007, ul-indent)


22-22: Unordered list indentation
Expected: 2; Actual: 5

(MD007, ul-indent)


23-23: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


26-26: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


27-27: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


28-28: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


29-29: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


30-30: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)

test-ui/CHECKLIST.md

7-7: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


87-87: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


97-97: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


110-110: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


135-135: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

README.md

14-14: Bare URL used

(MD034, no-bare-urls)

test-ui/REFACTORING.md

77-77: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


185-185: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

test-ui/SUMMARY.md

10-10: Bare URL used

(MD034, no-bare-urls)


75-75: Bare URL used

(MD034, no-bare-urls)


76-76: Bare URL used

(MD034, no-bare-urls)

🔇 Additional comments (38)
.run/Template Vitest.run.xml (1)

3-6: Template paths are correctly configured and verified.

All referenced paths in the Vitest run configuration exist and are properly configured:

  • test-ui/vite.config.ts exists
  • vitest is declared in test-ui/package.json dependencies
  • ✓ Directory structure matches template expectations

The template will work consistently across the team after running npm install or pnpm install in the test-ui directory to populate node_modules.

.github/workflows/integ.yml (1)

57-57: Verify Node.js version matrix change.

Line 57 updates the matrix from [20.x, 22.x, 24.x] to [22.18.x, 24.x, 25.x], removing Node 20.x support. Confirm this is intentional, as it may be a breaking change for users running the tests on older Node versions.

If this removal is intentional, consider documenting it in a migration guide or PR description. If accidental, restore the 20.x version or update to a newer patch of 20.x (e.g., 20.14.x).

test-ui/server/tsconfig.json (1)

1-12: LGTM!

The TypeScript composite configuration is properly set up for a Node.js/ESM server environment. The compiler options (nodenext module, esnext target, verbatim module syntax) align well with the modern TypeScript/ESM setup. The inclusion of ../../scripts/ allows sharing types across the test infrastructure.

test-ui/server/tsconfig.tsbuildinfo (1)

1-1: Verify generated artifact handling.

The tsconfig.tsbuildinfo file is a generated TypeScript incremental build artifact. Consider whether this should be committed to the repository or added to .gitignore. Generally, build artifacts are excluded, but for composite builds, some teams prefer committing them for faster CI builds.

Confirm the intended treatment:

  • If committing: Ensure it's regenerated locally before commits to avoid stale references.
  • If ignoring: Add test-ui/server/tsconfig.tsbuildinfo to the root .gitignore (if not already covered by a pattern).

Current test-ui/.gitignore at line 28 has server.js, so build artifacts may already be intended to be ignored.

test-ui/tsconfig.json (1)

1-24: LGTM!

The TypeScript configuration is well-tuned for a modern React 19 + Vite project. Strict mode is enabled, JSX is configured for React 17+, and the bundler module resolution aligns with Vite. The linting options (noUnusedLocals, noUnusedParameters, noFallthroughCasesInSwitch) will catch common issues early.

test-ui/CHECKLIST.md (1)

1-142: Documentation looks comprehensive and well-organized.

The checklist clearly documents implementation status, known limitations, and future considerations. The unchecked items under "Testing Verification" serve as a useful reminder for manual QA before finalizing the feature.

list.js (1)

30-39: I'll help verify the concerns raised in this review comment about requiring a .ts file directly from CommonJS. Let me investigate how list.js is executed and whether there's a TypeScript loader configured.
<function_calls>

#!/bin/bash

Find list.js and understand its location

fd -type f -name "list.js" | head -20


</function_calls>

Now let me search for how list.js is invoked in the repository:
<function_calls>

#!/bin/bash

Search for references to list.js in package.json, shell scripts, and CI configs

echo "=== In package.json ==="
rg -n "list.js" package.json 2>/dev/null || echo "No matches in package.json"

echo -e "\n=== In .github workflows ==="
find .github -type f ( -name ".yml" -o -name ".yaml" ) -exec grep -l "list.js" {} ; 2>/dev/null || echo "No matches found"

echo -e "\n=== In shell/bash scripts ==="
rg -n "list.js" --type sh --type bash 2>/dev/null || echo "No matches found"

echo -e "\n=== Direct node invocations ==="
rg -n "node.*list" --type sh --type yaml --type json 2>/dev/null || echo "No matches found"


</function_calls>

Now let me check for TypeScript configuration and loaders:
<function_calls>

#!/bin/bash

Check for TypeScript configuration

echo "=== tsconfig.json ==="
cat tsconfig.json 2>/dev/null || echo "No tsconfig.json found"

echo -e "\n=== Check for ts-node or tsx configuration ==="
rg -n "ts-node|tsx|register" package.json tsconfig.json .mocharc.* 2>/dev/null || echo "No TS loader config found"

echo -e "\n=== Check for node --loader or --require setup ==="
rg -n "--loader|--require" package.json .github/ 2>/dev/null || echo "No loader/require flags found"


</function_calls>

Let me find and examine the test-registry.ts file:
<function_calls>

#!/bin/bash

Find test-registry.ts

echo "=== Looking for test-registry.ts ==="
fd -type f -name "test-registry.ts"

echo -e "\n=== Looking for test-registry in any form ==="
fd -type f "test-registry"


</function_calls>

Let me check the actual list.js file to see its full context:
<function_calls>

#!/bin/bash

Find list.js and check its content

fd -type f -name "list.js" -exec wc -l {} ;
echo -e "\n=== list.js content ==="
fd -type f -name "list.js" -exec cat -n {} ;


</function_calls>

Let me search for how this list.js is being used:
<function_calls>

#!/bin/bash

Search for references to list.js more broadly

echo "=== All references to list.js ==="
rg "list.js" -n 2>/dev/null | head -30

echo -e "\n=== References to require paths with test-registry ==="
rg "test-registry" -n 2>/dev/null | head -30


</function_calls>

Let me check if there's a mocha or test configuration that might be relevant:
<function_calls>

#!/bin/bash

Check mocha configuration

echo "=== Mocha config files ==="
fd -type f -name ".mocharc*" -o -name "mocha.opts"

echo -e "\n=== Test scripts in package.json ==="
rg -A 5 '"test":|"mocha":' package.json 2>/dev/null || echo "No test scripts found"


</function_calls>

<function_calls>

#!/bin/bash

More aggressive search - look at all mocha config

cat .mocharc.* 2>/dev/null || echo "No .mocharc files"

echo -e "\n=== Check package.json for mocha/test config ==="
python3 << 'PYEOF'
import json
try:
with open('package.json', 'r') as f:
data = json.load(f)
if 'mocha' in data:
print("Mocha config:", json.dumps(data['mocha'], indent=2))
if 'scripts' in data and 'test' in data['scripts']:
print("Test script:", data['scripts']['test'])
except Exception as e:
print(f"Error: {e}")
PYEOF


</function_calls>

<function_calls>

#!/bin/bash

Find where test-ui/server/scripts/test-registry might be

echo "=== Looking for test-ui directory ==="
fd -type d "test-ui"

echo -e "\n=== Looking for test-registry in any form ==="
fd "test-registry"

echo -e "\n=== Looking for server/scripts directory ==="
fd -type d "scripts" | grep server


</function_calls>

<function_calls>

#!/bin/bash

Check directory structure around test files

echo "=== Repository structure ==="
git ls-files | grep -E "(test|spec)" | head -30


</function_calls>

<function_calls>

#!/bin/bash

Look for all .ts files to understand TypeScript presence

echo "=== TypeScript files in repo ==="
fd -type f -name "*.ts" | head -30

echo -e "\n=== Check if tsx or ts-node is a dependency ==="
rg '"tsx"|"ts-node"' package.json package-lock.json yarn.lock 2>/dev/null || echo "Not found in lock files"


</function_calls>

<function_calls>

#!/bin/bash

Search for getAllTestNames function to understand what it is

echo "=== Looking for getAllTestNames ==="
rg -n "getAllTestNames" --type ts --type js 2>/dev/null

echo -e "\n=== Broader search ==="
rg -n "getAllTestNames" 2>/dev/null


</function_calls>

<function_calls>

#!/bin/bash

Check the actual mocha configuration to see if it uses require hooks

echo "=== Searching for register hooks or require patterns ==="
rg -n "register|require.*tsx|require.*ts-node" . --type js --type json 2>/dev/null | head -20


</function_calls>

<function_calls>

#!/bin/bash

Look at the dev dependencies for TS support

echo "=== Check devDependencies for TS tools ==="
python3 << 'PYEOF'
import json
try:
with open('package.json', 'r') as f:
data = json.load(f)
dev_deps = data.get('devDependencies', {})
ts_related = {k: v for k, v in dev_deps.items() if any(x in k.lower() for x in ['tsx', 'ts-node', 'typescript', '@types'])}
if ts_related:
print("TypeScript-related devDependencies:")
for k, v in sorted(ts_related.items()):
print(f" {k}: {v}")
else:
print("No TypeScript-related devDependencies found")

    # Also show all dependencies
    print("\nAll dependencies:")
    for k, v in sorted(data.get('dependencies', {}).items()):
        print(f"  {k}: {v}")

except Exception as e:
print(f"Error: {e}")
PYEOF


</function_calls>

<function_calls>

#!/bin/bash

Search for how Node is invoked in CI

echo "=== CI configurations ==="
find .github/workflows -name ".yml" -o -name ".yaml" | xargs grep -l "node" 2>/dev/null | head -5 | xargs cat 2>/dev/null | grep -A 5 -B 5 "node " | head -50


</function_calls>

<function_calls>

#!/bin/bash

Get a comprehensive view - search in all files for how list.js or test scripts are used

echo "=== All mentions of 'list' in config files ==="
rg "list" .github/ package.json --type json --type yaml -n 2>/dev/null | head -30


</function_calls>

<function_calls>

#!/bin/bash

Look at the directory containing list.js

echo "=== Finding list.js and its directory ==="
LIST_FILE=$(fd -type f -name "list.js" | head -1)
if [ -n "$LIST_FILE" ]; then
DIR=$(dirname "$LIST_FILE")
echo "list.js location: $LIST_FILE"
echo "Directory contents:"
ls -la "$DIR" 2>/dev/null | head -20

echo -e "\n=== list.js full content ===" 
cat -n "$LIST_FILE"

else
echo "list.js not found"
fi


</function_calls>

<function_calls>

#!/bin/bash

Search for mocha configuration that might use ts-node or tsx

echo "=== Full mocha configuration (if any) ==="
ls -la | grep mocha

echo -e "\n=== Check if mocha uses require option ==="
cat .mocharc.json 2>/dev/null || cat .mocharc.js 2>/dev/null || cat .mocharc.yaml 2>/dev/null || echo "No .mocharc file found"

echo -e "\n=== Check test script ==="
grep -A 2 '"test"' package.json | head -10


</function_calls>

Let me try a different approach - look at git info to understand the PR context:
<function_calls>

#!/bin/bash

Check git log for recent changes to list.js

echo "=== Recent changes to list.js ==="
git log --oneline -10 -- "*list.js" 2>/dev/null || echo "No git history available"

echo -e "\n=== Git diff for list.js ==="
git diff HEAD~1 HEAD -- "*list.js" 2>/dev/null | head -100 || echo "No diff available"

echo -e "\n=== Current branch ==="
git branch --show-current 2>/dev/null || echo "Cannot determine branch"


</function_calls>

<function_calls>

#!/bin/bash

Try to find any .ts or .js file that might have been added/modified

echo "=== Modified TypeScript files ==="
git status --short 2>/dev/null | grep -E ".(ts|js)$" || echo "No modified files"

echo -e "\n=== Show all TypeScript/JS files in repo ==="
git ls-files 2>/dev/null | grep -E ".(ts|js)$" | sort | head -50


</function_calls>

<function_calls>

#!/bin/bash

Get a sense of what's actually in this repository

echo "=== Repository root contents ==="
ls -la | head -30

echo -e "\n=== All JavaScript/TypeScript files ==="
find . -maxdepth 3 -type f ( -name ".js" -o -name ".ts" ) 2>/dev/null | head -40

echo -e "\n=== package.json name and type ==="
cat package.json | python3 -c "import json, sys; data=json.load(sys.stdin); print('Name:', data.get('name')); print('Type:', data.get('type')); print('Main:', data.get('main'))"


</function_calls>

I'm encountering limitations in accessing the repository files needed to verify this review comment. While the review references specific code in list.js (lines 30-39) that attempts to require() a TypeScript file, I cannot locate these files in the current sandbox environment.

Let me try one more comprehensive search:
<function_calls>

#!/bin/bash

Comprehensive search for any file containing "test-registry" or "getAllTestNames"

echo "=== Searching entire repo for test-registry ==="
rg "test-registry" . 2>/dev/null | head -20 || echo "No matches found"

echo -e "\n=== Searching for getAllTestNames ==="
rg "getAllTestNames" . 2>/dev/null | head -20 || echo "No matches found"

echo -e "\n=== All .js files in repo ==="
find . -name "*.js" -type f 2>/dev/null | head -40


</function_calls>

Based on my investigation, I cannot locate the list.js file or test-registry.ts file mentioned in the review comment within the accessible repository structure. This prevents me from verifying the actual concern about requiring a TypeScript file from CommonJS.

However, I can still provide an assessment based on the technical concerns raised:

The core concern is technically valid: Requiring .ts files directly from CommonJS does require a TypeScript loader (tsx, ts-node, etc.) to be configured. Without verification of the actual runtime configuration, I cannot definitively determine if the implementation will work.

**Cannot verify—requires manual review of runtime environment configuration**

The concern about requiring `.ts` files directly from CommonJS is technically sound. Standard Node.js cannot load TypeScript without a loader. This requires verification that:
1. The repository has `tsx`, `ts-node`, or equivalent configured 
2. The Mocha or test runner invokes `list.js` with appropriate TS support
3. The `test-registry.ts` file exists at the expected path

Without access to the actual files in this PR, manual verification of the runtime setup is necessary. Check:
- Mocha configuration (`.mocharc.*` or `package.json`)  
- Test script invocation in `package.json`
- Dependencies for TypeScript loaders
- Whether `test-ui/server/scripts/test-registry.ts` exists and exports `getAllTestNames`


run-test.sh (1)

30-30: Node.js 22.18.x natively supports running .mts files with type-stripping enabled by default.

The CI workflow tests on Node.js 22.18.x, 24.x, and 25.x—all versions that have built-in TypeScript type-stripping support. The run.mts file uses only erasable type annotations (import type, type annotations) that do not require additional TypeScript transformation. No --experimental-strip-types flag, tsx, or ts-node is needed.

test-ui/src/index.css (1)

1-23: Global styling baseline looks solid

Reset, font stack, and layout defaults are clean and appropriate for the new UI. No changes needed.

test-ui/src/main.tsx (1)

1-17: React/Redux bootstrap is correct and defensive

Entrypoint correctly uses createRoot, wraps the app in Provider and StrictMode, and guards against a missing #root element. With the HTML entry fixed to reference main.tsx, this should work as expected.

start-ui.sh (1)

1-26: Startup script is straightforward and fits the workflow

The script cleanly handles cd into test-ui, lazy installs dependencies, and delegates to npm start with set -e for safety. Looks good as-is.

test-ui/test/store/testsSlice.spec.ts (1)

1-53: testsSlice persistence/clear behavior is well covered

The spec cleanly asserts initial state, loading of persisted results, and clearing a specific test result via clearTestResult. Store setup via configureStore({ reducer: rootReducer }) is appropriate for this unit test.

test-ui/server/scripts/tgzFileNameToPackageJsonReference.ts (1)

1-5: Tarball reference helper is concise and appropriate

Helper cleanly constructs a file: reference from an absolute path and file name, which aligns with its usage in Controller.tgzFileNameToPackageJsonReference.

test-ui/vite.config.ts (1)

8-12: Consider adding WebSocket proxy support.

The README and QUICKSTART mention WebSocket connections to ws://localhost:3001 for real-time updates. If the frontend connects to WebSocket via the Vite dev server (e.g., /api/ws), you'll need ws: true in the proxy config. If the frontend connects directly to ws://localhost:3001, this is fine as-is.

 proxy: {
   '/api': {
     target: 'http://localhost:3001',
     changeOrigin: true,
+    ws: true,
   },
 },
test-ui/QUICKSTART.md (1)

52-57: LGTM!

The quick-start guide is comprehensive and well-organized. The troubleshooting table, keyboard shortcuts, and example workflows provide excellent developer guidance.

test-ui/package.json (1)

9-9: Verify Node.js version requirement for native TypeScript execution.

Running node server/server.ts directly requires Node.js 22+ with native TypeScript support (via --experimental-strip-types). Consider adding an engines field to enforce this requirement and prevent confusion for contributors using older Node versions.

   "license": "ISC",
   "description": "",
+  "engines": {
+    "node": ">=22.0.0"
+  },
test-ui/src/PhaseOutput.css (1)

1-90: LGTM!

Well-structured CSS with clear state classes, appropriate transitions, and good contrast for accessibility. The dark theme for output content and light theme for headers provide good visual separation.

test-ui/src/PhaseOutput.tsx (2)

23-41: LGTM on initialization logic.

The cascading logic for determining initial expanded phase (explicit prop → current phase → last non-pending) is well thought out and handles all states appropriately.


138-167: LGTM on render logic.

Good use of conditional rendering, dynamic class composition, and inline styles for the progress gradient. The accessibility of clickable elements is appropriate with cursor changes based on hasOutput.

test-ui/src/store/hooks.ts (1)

1-5: Typed hooks look good

useAppDispatch/useAppSelector are correctly typed against AppDispatch/RootState and will keep component usage type-safe. No changes needed.

test-ui/src/store/store.ts (1)

1-21: Store configuration is idiomatic

configureStore setup, rootReducer shape, serializableCheck overrides, and RootState/AppDispatch exports are all consistent and should work well with the rest of the test UI.

test-ui/src/types.ts (1)

1-52: Type definitions look consistent and usable across the UI

The Test, Phase*, and TestRun/QueuedTest shapes are coherent and match how the slice and UI are modeled; nothing blocking here.

test-ui/server/scripts/Controller.ts (1)

11-73: Controller base class looks solid and cohesive

The shared clean, execAsync, env shaping, and package.json helpers provide a clear, reusable foundation for NPM/Yarn controllers. The abstraction and API surface look appropriate.

test-ui/server/scripts/test-registry.ts (1)

38-155: Test registry structure and helpers look correct

The direct and library test definitions plus getTestMetadata/getAllTestNames/getAllTests/isStableTest cover the expected queries and are much clearer than ad‑hoc string parsing. No blocking issues seen.

Also applies to: 169-249

test-ui/src/store/testDurationSelectors.ts (5)

1-6: LGTM!

Imports and the private selectQueueLength selector are correctly defined.


13-28: LGTM!

Clean memoized selectors with proper readonly typing to prevent accidental mutations.


30-93: LGTM!

The selector correctly calculates average phase durations from completed tests and gracefully falls back to educated guesses when historical data is unavailable.


100-116: LGTM!

Clean ETA calculation for queued tests.


159-187: LGTM!

The remaining time calculation logic correctly handles the current phase (subtracting elapsed time) and future phases (using full estimates), with proper fallbacks for missing startTime.

test-ui/server/server.ts (3)

1-28: LGTM!

Server setup with Express, WebSocket, and necessary middleware is properly configured.


89-103: LGTM!

WebSocket connection management and broadcast function are properly implemented with readyState checks.


469-495: LGTM!

Queue processing logic with setImmediate is well-designed for non-blocking serial execution.

test-ui/src/App.tsx (6)

1-29: LGTM!

Imports are well-organized and correctly reference the store hooks and selectors.


33-45: LGTM!

Clean duration formatting utility.


91-123: LGTM!

The elapsed time tracking effect properly cleans up the interval and handles the running test state correctly.


450-463: LGTM!

Version sorting correctly handles semantic versioning with numeric comparison.


501-506: LGTM!

Clean implementation that queues selected tests and clears selection.


60-323: TestItem component is well-structured.

The component handles multiple states (running, completed, queued) cleanly with proper conditional rendering and progress tracking.

},
"dependencies": {
"my-charts": "file:../my-charts-replace-with-tgz-file-before-running",
"my-charts": "file:/Users/pavelvanecek/github/recharts-integ/libraries/my-charts-react19/my-charts-react19-v0.1.0.tgz",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Remove hardcoded absolute paths — repository is not portable.

Same issue as the other app-react packages: the absolute path is machine-specific. Additionally, app-react17 points to my-charts-react19 instead of react17, suggesting a copy-paste error.

Replace with a portable reference:

-    "my-charts": "file:/Users/pavelvanecek/github/recharts-integ/libraries/my-charts-react19/my-charts-react19-v0.1.0.tgz",
+    "my-charts": "file:../my-charts",

Verify the correct my-charts version mapping for React 17.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"my-charts": "file:/Users/pavelvanecek/github/recharts-integ/libraries/my-charts-react19/my-charts-react19-v0.1.0.tgz",
"my-charts": "file:../my-charts",
🤖 Prompt for AI Agents
In apps-3rd-party/app-react17/package.json around line 14, the dependency
"my-charts" uses a hardcoded absolute file path and incorrectly points to
my-charts-react19; replace the absolute path with a portable reference (e.g., a
versioned semver entry or a relative path inside the repo such as
"../libraries/my-charts-react17/...") and ensure the package points to the React
17 build (my-charts-react17) with the correct version number; update the
dependency string accordingly and verify the mapped version matches the
published/package.json version for the React 17 artifact.

},
"dependencies": {
"my-charts": "file:../my-charts-replace-with-tgz-file-before-running",
"my-charts": "file:/Users/pavelvanecek/github/recharts-integ/libraries/my-charts-react19/my-charts-react19-0.1.0.tgz",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Remove hardcoded absolute paths — repository is not portable.

The dependency uses an absolute file path specific to your machine (/Users/pavelvanecek/...). Additionally, app-react18 references my-charts-react19 instead of the React 18 equivalent, which may be unintentional.

Replace with a portable reference:

-    "my-charts": "file:/Users/pavelvanecek/github/recharts-integ/libraries/my-charts-react19/my-charts-react19-0.1.0.tgz",
+    "my-charts": "file:../my-charts",

Verify that the correct my-charts variant for React 18 is being referenced.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"my-charts": "file:/Users/pavelvanecek/github/recharts-integ/libraries/my-charts-react19/my-charts-react19-0.1.0.tgz",
"my-charts": "file:../my-charts",
🤖 Prompt for AI Agents
In apps-3rd-party/app-react18/package.json around line 14, replace the hardcoded
absolute file dependency "file:/Users/pavelvanecek/..." with a portable
reference (either a relative file path to the packaged tarball inside the repo,
e.g. "file:../libraries/my-charts-react18/my-charts-react18-0.1.0.tgz", or
preferably a published semver dependency like "my-charts": "0.1.0"), and ensure
the dependency points to the React 18 build (my-charts-react18) instead of
my-charts-react19.

Comment on lines +79 to +97
async pack(): Promise<string> {
const output = await this.execAsync('yarn pack --json');
const trimmed = output.trim();
try {
const jsonOutput: YarnPackJson = JSON.parse(trimmed);
if (jsonOutput.type === 'success' && jsonOutput.data) {
const match = jsonOutput.data.match(/"(.+\.tgz)"/);
if (match) {
return `file:${match[1]}`;
}
throw new Error(`Could not extract tgz path from: ${jsonOutput.data}`);
} else {
throw new Error(`Unexpected output format from yarn pack: ${trimmed}`);
}
} catch (error: any) {
console.error(`Error parsing yarn pack output: ${error.message}`);
throw error;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the full YarnController.ts file to understand context and imports
cat test-ui/server/scripts/YarnController.ts

Repository: recharts/recharts-integ

Length of output: 6220


🏁 Script executed:

# Search for pack() method usage and tests
rg -n "pack\(\)" test-ui/server/scripts/ -A 3 -B 3

# Also search for any test files related to YarnController
fd -name "*test*" -o -name "*spec*" | grep -i yarn

Repository: recharts/recharts-integ

Length of output: 1633


🏁 Script executed:

# Let's check the yarn documentation and actual output format by searching the codebase
# for any comments or documentation about yarn pack
rg -i "yarn.*pack" test-ui/ -n -B 2 -A 2

Repository: recharts/recharts-integ

Length of output: 3301


🏁 Script executed:

# Search for YarnPackJson usage to understand how it's used
rg "YarnPackJson" test-ui/ -n -B 3 -A 3

Repository: recharts/recharts-integ

Length of output: 1131


🌐 Web query:

yarn pack --json output format multi-line

💡 Result:

Do you mean (A) how Yarn's yarn pack --json emits JSON events that can contain multi-line fields, (B) how to produce/format multi-line JSON output from yarn pack --json, or (C) how to parse those multi-line fields reliably? Which one do you want?


🌐 Web query:

yarn pack --json documentation output format examples

💡 Result:

Short answer

  • Yarn's --json mode emits a JSON-stream (NDJSON): one JSON object per line with a "type" and "data" payload (same pattern used by other yarn commands). [1][2]

Typical / example lines you may see from yarn pack --json

  • informational progress or warnings:
    {"type":"info","data":"Running prepack script"}
    {"type":"warning","data":"package.json: No license field"} [3]

  • pack success (example — actual field names vary by command/version):
    {"type":"success","data":{"filename":"package.tgz","size":12345,"hash":"sha1:..."}}

  • table-style output (some yarn commands use this shape):
    {"type":"table","data":{"head":["Name","Version"],"body":[["mypkg","1.2.3"]]}} [3]

Notes

  • Yarn v1 (Classic) docs show pack behaviour but not a formal JSON schema; Yarn v2+ documents that --json produces an NDJSON stream. Interpretations of "data" vary by command/version, so inspect a real run or pipe through jq to confirm fields for your yarn version. [1][2][3]

Sources

  • Yarn Classic docs: yarn pack. [1]
  • Yarn v2+ source/docs: --json emits NDJSON / JSON-stream. [2]
  • Examples of yarn --json objects (table/info/warning) from tooling docs and wrappers. [3]

References
[1] https://classic.yarnpkg.com/lang/en/docs/cli/pack/
[2] (Yarn v2+ CLI source/discussion showing --json => NDJSON).
[3] Examples of yarn --json objects (info/table/warning) in tooling/docs.


🌐 Web query:

yarn pack --json success data structure filename field

💡 Result:

Short answer: yarn pack --json emits NDJSON "success" events whose data is the human-readable message (e.g. "Wrote tarball to "...""); there is not a separate parsed filename field in the JSON — you must parse the message string to get the path. [1][2]

Sources:

  • yarn pack docs (describes --json NDJSON output). [1]
  • Yarn messages (packWroteTarball → "Wrote tarball to $0.") — the pack message is a formatted string, not a JSON object with a filename property. [2]

🏁 Script executed:

# Let's search for any existing usage or tests that might show actual yarn output
rg "Wrote tarball" test-ui/ -B 2 -A 2

Repository: recharts/recharts-integ

Length of output: 49


🏁 Script executed:

# Also check if there are any version specifications for yarn in the repo
find test-ui -name "package.json" -o -name ".yarnrc" -o -name "yarn.lock" | head -5

Repository: recharts/recharts-integ

Length of output: 87


🏁 Script executed:

# Check yarn version in package.json
cat test-ui/package.json | head -50

Repository: recharts/recharts-integ

Length of output: 1077


Based on my verification, the review comment is accurate and identifies a critical bug. Here's what I confirmed:

  1. Yarn's --json output is NDJSON (one JSON object per line), not a single JSON blob
  2. The success event's data field is a formatted string message like "Wrote tarball to \"path.tgz\"" with quotes around the path
  3. The current implementation will fail because:
    • JSON.parse(trimmed) on multi-line output will throw or only parse the first line
    • Even if it gets the success line, the regex pattern and logic expect a different format
  4. The proposed fix correctly addresses these issues by:
    • Splitting output by newlines and parsing each line individually
    • Iterating to find the success entry (skipping info/warning lines)
    • Using the appropriate regex pattern for the actual message format

Fix yarn pack --json parsing to handle real NDJSON output

The current pack() implementation assumes yarn pack --json returns a single JSON object, but Yarn emits NDJSON (one JSON object per line with type and data fields). Additionally, the success event's data field is a formatted string message (e.g., "Wrote tarball to \"path.tgz\""), not a bare path. As written, JSON.parse(trimmed) will fail on multi-line output, and the regex pattern will not match the actual message format.

-    async pack(): Promise<string> {
-        const output = await this.execAsync('yarn pack --json');
-        const trimmed = output.trim();
-        try {
-            const jsonOutput: YarnPackJson = JSON.parse(trimmed);
-            if (jsonOutput.type === 'success' && jsonOutput.data) {
-                const match = jsonOutput.data.match(/"(.+\.tgz)"/);
-                if (match) {
-                    return `file:${match[1]}`;
-                }
-                throw new Error(`Could not extract tgz path from: ${jsonOutput.data}`);
-            } else {
-                throw new Error(`Unexpected output format from yarn pack: ${trimmed}`);
-            }
-        } catch (error: any) {
-            console.error(`Error parsing yarn pack output: ${error.message}`);
-            throw error;
-        }
-    }
+    async pack(): Promise<string> {
+        const output = await this.execAsync('yarn pack --json');
+        const lines = output.trim().split('\n').filter(Boolean);
+
+        for (const line of lines) {
+            try {
+                const entry: YarnPackJson = JSON.parse(line);
+                if (entry.type === 'success' && entry.data) {
+                    const match = entry.data.match(/(\S+\.tgz)/);
+                    if (match) {
+                        // If Yarn returns a relative path, resolve it from the project root
+                        const tgzPath = path.isAbsolute(match[1])
+                            ? match[1]
+                            : path.join(this.absolutePath, match[1]);
+                        return `file:${tgzPath}`;
+                    }
+                    throw new Error(`Could not extract tgz path from: ${entry.data}`);
+                }
+            } catch {
+                // Ignore non‑JSON or irrelevant lines
+            }
+        }
+
+        throw new Error(`Could not find success entry in 'yarn pack' output:\n${output}`);
+    }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In test-ui/server/scripts/YarnController.ts around lines 79 to 97, the code
treats yarn pack --json as a single JSON blob and uses an incorrect regex,
causing parsing failures; update pack() to split the output by newline, parse
each non-empty line as JSON, iterate to find the entry with type === 'success',
extract its data string (which is a quoted/formatted message like "Wrote tarball
to \"path.tgz\""), unquote or unescape the string and then extract the .tgz
filename (e.g. with a regex matching Wrote tarball to \"(.+\.tgz)\" or by
removing surrounding quotes and searching for .tgz), return it as file:${path},
and throw a clear error if no success entry or no path is found while preserving
the original error handling.

Comment on lines +604 to +618
const packProcess = spawn(
"bash",
[
"-c",
`
cd "${expandedDirectory}" && \
npm run build && \
npm pack | tail -n 1
`,
],
{
cwd: rootDir,
env: { ...process.env },
},
);
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 29, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Command injection vulnerability in directory path.

The expandedDirectory is interpolated directly into a shell command without sanitization. A malicious directory path like "; rm -rf / # or $(malicious_command) could execute arbitrary commands.

Consider using execFile with an array of arguments instead of spawn("bash", ["-c", ...]), or validate/sanitize the path:

+const sanitizePath = (dir: string): string => {
+  // Only allow alphanumeric, slashes, dashes, underscores, dots, and tildes
+  if (!/^[a-zA-Z0-9/_\-.\s~]+$/.test(dir)) {
+    throw new Error("Invalid directory path characters");
+  }
+  return dir;
+};
+
 app.post("/api/pack", (req: Request, res: Response) => {
   const { directory } = req.body;

   if (!directory) {
     return res.status(400).json({ error: "Directory path is required" });
   }

+  try {
+    sanitizePath(directory);
+  } catch (err) {
+    return res.status(400).json({ error: (err as Error).message });
+  }

Alternatively, use child_process.execFile with explicit arguments to avoid shell interpretation.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In test-ui/server/server.ts around lines 604 to 618, the code injects
expandedDirectory directly into a shell string which allows command injection;
replace the shell invocation with a non-shell child process call (use
child_process.execFile or spawn with shell: false) and pass the command and
arguments as an array, set cwd to the expandedDirectory (not interpolated into a
shell command), and capture stdout to get the last line of npm pack;
additionally validate or normalize expandedDirectory (e.g. ensure it is an
absolute path inside the expected workspace and does not contain shell
metacharacters) before using it as cwd.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This application only runs on one's own machine so the only thing an attacker can attack is their own computer. We don't deploy this anywhere.

Copy link
Contributor

Choose a reason for hiding this comment

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

@PavelVanecek, that's a fair point—since this is a local development tool running only on your own machine, the security risk is indeed minimal. The primary concern with command injection is when untrusted users can interact with the service, which isn't the case here.

I understand and accept your decision to keep the code as-is given this context. If the tool's scope ever changes or the code is adapted for other uses, it would be worth revisiting, but for now it's reasonable to prioritize development velocity for internal tooling.

Comment on lines +19 to +116
const websocketMiddleware: Middleware = (store) => {
let ws: WebSocket | null = null;

const connect = () => {
ws = new WebSocket(WS_URL);

ws.onopen = () => {
console.log('WebSocket connected');
};

ws.onmessage = (event) => {
try {
const message: WebSocketMessage = JSON.parse(event.data);
handleMessage(message, store.dispatch);
} catch (error) {
console.error('Failed to parse WebSocket message:', error);
}
};

ws.onerror = (error) => {
console.error('WebSocket error:', error);
};

ws.onclose = () => {
console.log('WebSocket disconnected, reconnecting...');
setTimeout(connect, 3000);
};
};

const handleMessage = (message: WebSocketMessage, dispatch: any) => {
const { type, data } = message;

switch (type) {
case 'test-queued':
dispatch(testQueued({
testName: data.testName,
id: data.id,
position: data.position,
}));
break;

case 'test-started':
dispatch(testStarted({
testName: data.testName,
id: data.id,
}));
break;

case 'test-output':
dispatch(testOutput({
id: data.id,
output: data.output,
phases: data.phases,
currentPhase: data.currentPhase,
}));
break;

case 'test-error':
dispatch(testError({
id: data.id,
error: data.error,
}));
break;

case 'test-completed':
dispatch(testCompleted({
id: data.id,
status: data.status,
exitCode: data.exitCode,
}));
// Move to results after delay
setTimeout(() => {
const state = store.getState();
const testName = Object.keys(state.tests.runningTests).find(
(name: string) => state.tests.runningTests[name].id === data.id
);
if (testName) {
dispatch(moveToResults(testName));
}
}, 1000);
break;

case 'queue-cleared':
dispatch(queueCleared());
break;

default:
console.log('Unknown WebSocket message type:', type);
}
};

// Initialize connection
connect();

return (next) => (action) => {
return next(action);
};
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard WebSocket usage and harden test-completed handling

Two robustness concerns here:

  1. Environment guard
    connect() is invoked when the middleware is created, which happens during store creation. If store is imported in a non‑browser environment where WebSocket is undefined (e.g., node/Vitest without a DOM env or SSR), this will throw at new WebSocket(WS_URL). Consider guarding:
-  const connect = () => {
-    ws = new WebSocket(WS_URL);
+  const connect = () => {
+    if (typeof WebSocket === 'undefined') {
+      console.warn('WebSocket is not available in this environment; skipping connection.');
+      return;
+    }
+    ws = new WebSocket(WS_URL);
  1. Safer lookup in test-completed path
    If the tests slice ever stops keeping completed tests in runningTests, this lookup can throw on state.tests.runningTests[name].id. Using optional chaining avoids a hard crash:
-          const testName = Object.keys(state.tests.runningTests).find(
-            (name: string) => state.tests.runningTests[name].id === data.id
-          );
+          const testName = Object.keys(state.tests.runningTests).find(
+            (name: string) =>
+              state.tests.runningTests[name]?.id === data.id,
+          );

With these tweaks the middleware will fail gracefully in unsupported environments and be more resilient to future slice changes.

🤖 Prompt for AI Agents
In test-ui/src/store/websocketMiddleware.ts around lines 19 to 116, the
middleware calls connect() unconditionally and assumes WebSocket and
runningTests entries exist; change it to only initialize the socket when
WebSocket is available (e.g., if (typeof globalThis !== 'undefined' && typeof
globalThis.WebSocket !== 'undefined') connect();), and inside the
'test-completed' handler make the runningTests lookup defensive using optional
chaining when comparing ids and guard the moveToResults dispatch (e.g., derive
testName by checking state.tests?.runningTests?.[name]?.id === data.id and only
dispatch if testName is truthy). Ensure no WebSocket construction occurs in
non-browser environments.

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: 3

🧹 Nitpick comments (1)
test-ui/src/store/testsSlice.ts (1)

109-134: Consider a secondary index for id-to-testName lookups.

The testOutput, testError, and testCompleted reducers all iterate through Object.keys(state.runningTests) to find a test by id. This is O(n) per event, and output events can be frequent during test execution.

For a modest number of concurrent tests this is acceptable, but if scalability becomes a concern, consider maintaining a runningTestsById: Record<string, string> (id → testName) index.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 870301c and 4f1d0b6.

📒 Files selected for processing (9)
  • apps-3rd-party/app-react17/package.json (1 hunks)
  • integrations/tanstack-start-basic/package.json (2 hunks)
  • test-ui/.vite/deps/_metadata.json (1 hunks)
  • test-ui/.vite/deps/package.json (1 hunks)
  • test-ui/index.html (1 hunks)
  • test-ui/server/scripts/NpmController.ts (1 hunks)
  • test-ui/server/scripts/replacePackageVersion.ts (1 hunks)
  • test-ui/server/server.ts (1 hunks)
  • test-ui/src/store/testsSlice.ts (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • apps-3rd-party/app-react17/package.json
  • test-ui/.vite/deps/package.json
  • test-ui/.vite/deps/_metadata.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • test-ui/index.html
  • test-ui/server/scripts/NpmController.ts
  • test-ui/server/scripts/replacePackageVersion.ts
🧰 Additional context used
🧬 Code graph analysis (1)
test-ui/src/store/testsSlice.ts (1)
test-ui/src/types.ts (4)
  • Test (1-10)
  • TestRun (36-45)
  • Phases (25-32)
  • PhaseName (34-34)
🪛 GitHub Actions: Recharts integration tests
integrations/tanstack-start-basic/package.json

[error] Process completed with exit code 1.

🔇 Additional comments (3)
integrations/tanstack-start-basic/package.json (1)

18-18: Recharts version "^3.0.0" is correct and consistent across all integrations—no issue found.

The dependency uses a pinned caret range "^3.0.0" (not "latest"), which is consistent with all other integrations in the repository (tanstack-start-basic, ts-react16, ts-react18, ts-react19, ts-react17, and ts4-react17 all specify the same version). The NPM registry confirms both 3.0.0 and 3.0.1 exist, and ^3.0.0 will accept compatible updates. The past review's recommendation for "^3.0.1" does not align with the codebase's standardization strategy. The pipeline failure mentioned is unrelated to this version constraint and requires separate investigation.

test-ui/src/store/testsSlice.ts (1)

1-223: Well-structured Redux slice with comprehensive test lifecycle management.

The slice correctly handles the full test lifecycle (queued → running → completed → results) with proper phase tracking. Good use of Immer for mutable-style updates and clean separation of concerns across reducers. The queue size tracking logic in testQueued and reset logic in testCompleted properly handles batch tracking across multiple test additions.

test-ui/server/server.ts (1)

89-103: Well-designed WebSocket broadcast pattern and queue architecture.

The broadcast function correctly checks readyState before sending, and the overall architecture with serial queue processing via setImmediate (line 494) properly yields to the event loop between tests. The cancellation flow with shouldCancelQueue flag is cleanly integrated across runPhase, processQueue, and executeTest.

Comment on lines +288 to +331
const runLibraryInLibraryTest = (
libController: Controller,
appController: Controller,
version: string,
) => {
return async (testData: TestData) => {
await runPhase("clean", testData, async () => {
await libController.clean();
return await appController.clean();
});

await runPhase("setVersion", testData, async () => {
libController.replacePackageJsonVersion("recharts", version);
await libController.install();
await libController.test();
await libController.build();
await verifyAllSingleDependencyVersions(libController, testData);
const myChartsTgzFile = await libController.pack();
appController.replacePackageJsonVersion("my-charts", myChartsTgzFile);
return TestOutcome.ok("setVersion");
});

await runPhase(
"install",
testData,
async () => await appController.install(),
);
if (testData.phases.install.status === "failed") {
return;
}

await runPhase("test", testData, async () => await appController.test());
await runPhase(
"build",
testData,
async () => await appController.build(),
);
await runPhase(
"verify",
testData,
async () =>
await verifyAllSingleDependencyVersions(appController, testData),
);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

runLibraryInLibraryTest swallows errors during setVersion phase.

The setVersion phase (lines 299-308) runs multiple operations (install, test, build, verifyAllSingleDependencyVersions, pack) but always returns TestOutcome.ok("setVersion") regardless of whether intermediate steps failed. Errors thrown by these calls will propagate and fail the phase, but if they return failure outcomes (like verifyAllSingleDependencyVersions can), those are silently ignored.

Consider checking return values:

       await runPhase("setVersion", testData, async () => {
-        libController.replacePackageJsonVersion("recharts", version);
+        await libController.replacePackageJsonVersion("recharts", version);
         await libController.install();
         await libController.test();
         await libController.build();
-        await verifyAllSingleDependencyVersions(libController, testData);
+        const verifyResult = await verifyAllSingleDependencyVersions(libController, testData);
+        if (!verifyResult.success) {
+          return verifyResult;
+        }
         const myChartsTgzFile = await libController.pack();
         appController.replacePackageJsonVersion("my-charts", myChartsTgzFile);
         return TestOutcome.ok("setVersion");
       });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const runLibraryInLibraryTest = (
libController: Controller,
appController: Controller,
version: string,
) => {
return async (testData: TestData) => {
await runPhase("clean", testData, async () => {
await libController.clean();
return await appController.clean();
});
await runPhase("setVersion", testData, async () => {
libController.replacePackageJsonVersion("recharts", version);
await libController.install();
await libController.test();
await libController.build();
await verifyAllSingleDependencyVersions(libController, testData);
const myChartsTgzFile = await libController.pack();
appController.replacePackageJsonVersion("my-charts", myChartsTgzFile);
return TestOutcome.ok("setVersion");
});
await runPhase(
"install",
testData,
async () => await appController.install(),
);
if (testData.phases.install.status === "failed") {
return;
}
await runPhase("test", testData, async () => await appController.test());
await runPhase(
"build",
testData,
async () => await appController.build(),
);
await runPhase(
"verify",
testData,
async () =>
await verifyAllSingleDependencyVersions(appController, testData),
);
};
const runLibraryInLibraryTest = (
libController: Controller,
appController: Controller,
version: string,
) => {
return async (testData: TestData) => {
await runPhase("clean", testData, async () => {
await libController.clean();
return await appController.clean();
});
await runPhase("setVersion", testData, async () => {
await libController.replacePackageJsonVersion("recharts", version);
await libController.install();
await libController.test();
await libController.build();
const verifyResult = await verifyAllSingleDependencyVersions(libController, testData);
if (!verifyResult.success) {
return verifyResult;
}
const myChartsTgzFile = await libController.pack();
appController.replacePackageJsonVersion("my-charts", myChartsTgzFile);
return TestOutcome.ok("setVersion");
});
await runPhase(
"install",
testData,
async () => await appController.install(),
);
if (testData.phases.install.status === "failed") {
return;
}
await runPhase("test", testData, async () => await appController.test());
await runPhase(
"build",
testData,
async () => await appController.build(),
);
await runPhase(
"verify",
testData,
async () =>
await verifyAllSingleDependencyVersions(appController, testData),
);
};

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: 4

♻️ Duplicate comments (3)
scripts/run.mts (1)

40-64: Library controller outcomes are discarded; only app controller results are returned.

This issue was flagged in a previous review and remains unaddressed. Lines 41-46 execute operations on libController, but outcomes are not captured in results. If any library step fails, the failure is silently ignored.

test-ui/server/scripts/YarnController.ts (1)

79-97: yarn pack --json parsing is broken - outputs NDJSON, not single JSON.

This issue was flagged in a previous review and remains unaddressed. Yarn's --json mode emits NDJSON (one JSON object per line), not a single JSON blob. JSON.parse(trimmed) will fail on multi-line output.

test-ui/server/server.ts (1)

303-311: Verification failure is ignored in setVersion phase.

The result from verifyAllSingleDependencyVersions at line 308 is not checked. If verification fails, the phase still returns TestOutcome.ok("setVersion"), masking the failure.

Apply this diff:

       await runPhase("setVersion", testData, async () => {
         libController.replacePackageJsonVersion("recharts", version);
         await libController.install();
         await libController.test();
         await libController.build();
-        await verifyAllSingleDependencyVersions(libController, testData);
+        const verifyResult = await verifyAllSingleDependencyVersions(libController, testData);
+        if (!verifyResult.success) {
+          return verifyResult;
+        }
         const myChartsTgzFile = await libController.pack();
         appController.replacePackageJsonVersion("my-charts", myChartsTgzFile);
         return TestOutcome.ok("setVersion");
       });
🧹 Nitpick comments (13)
.github/workflows/integ.yml (1)

23-26: Setup node before checkout may cause issues.

The actions/setup-node step runs before actions/checkout. While this works for list.js execution, it's unconventional. Typically, checkout comes first. Consider reordering for consistency with the build-and-test job.

     steps:
-      - name: Setup node.js
-        uses: actions/setup-node@v6
-        with:
-          node-version: 24.x
       - name: Checkout Integration Tests Repository
         uses: actions/checkout@v4
         with:
           repository: recharts/recharts-integ
+      - name: Setup node.js
+        uses: actions/setup-node@v6
+        with:
+          node-version: 24.x
test-ui/SCREENSHOTS.md (1)

87-91: Add language specifier to fenced code blocks for consistency.

Several ASCII diagram code blocks are missing the text language specifier, unlike the first block at line 7. This affects lines 87, 97, 110, 135, 225, 236, 249, and 262. Adding text ensures consistent rendering and resolves markdownlint warnings.

-```
+```text
 ┌─────────────────────────────────────────┐
 │  🧪 Recharts Integration Test Runner    │
 └─────────────────────────────────────────┘

Apply the same fix to the other blocks at lines 97, 110, 135, 225, 236, 249, and 262.

test-ui/UI_STRUCTURE.md (1)

16-58: Add text language specifier to ASCII diagram blocks.

Code blocks at lines 16 and 62 are ASCII art diagrams but lack the text specifier used at line 5. This causes markdownlint warnings.

-```
+```text
 ┌────────────────────────────────────────────────────────────────────┐
test-ui/server/scripts/replacePackageVersion.ts (1)

12-12: Consider adding type safety for package.json structure.

The packageJson is typed as Record<string, unknown>, but dependencies and peerDependencies are accessed without type narrowing. While this works at runtime, adding type guards would improve type safety.

-  let packageJson: Record<string, unknown>;
+  interface PackageJsonDeps {
+    dependencies?: Record<string, string>;
+    peerDependencies?: Record<string, string>;
+  }
+  let packageJson: PackageJsonDeps;
test-ui/src/PhaseOutput.tsx (2)

94-120: Consider reducing interval frequency for better performance.

The interval runs every 100ms to update elapsed times. With many tests visible, this could cause frequent re-renders. Consider using 250ms or 500ms for a balance between responsiveness and performance.

-    }, 100);
+    }, 250);

154-156: Duration display uses final duration, not live elapsed time.

Line 156 displays phase.duration (from server) rather than phaseElapsedTimes[phaseName]. For running phases, this will show - until the phase completes. If live elapsed time display is desired during running phases, consider:

-              <span className="phase-duration">{formatDuration(phase.duration)}</span>
+              <span className="phase-duration">
+                {formatDuration(phase.status === 'running' ? phaseElapsedTimes[phaseName] : phase.duration)}
+              </span>
test-ui/REFACTORING.md (1)

185-199: Consider adding a language specifier to the fenced code block.

The message flow diagram code block at line 185 lacks a language specifier. While this is a diagram rather than code, adding text as the language would satisfy linters and improve consistency.

-```
+```text
 WebSocket Server
       ↓
    onmessage
test-ui/SUMMARY.md (2)

110-120: Add language specifier to the data flow diagram code block.

The data flow diagram at line 110 lacks a language specifier.

-```
+```text
 User Action → React Component → API Request

243-243: Format bare URL as a proper Markdown link.

The bare URL should be formatted as a clickable link for better rendering across Markdown viewers.

-2. Open browser to http://localhost:3000
+2. Open browser to <http://localhost:3000>
test-ui/server/scripts/YarnController.ts (1)

99-110: Inconsistent use of execAsync - uses local instead of inherited method.

Lines 104-109 create a new execAsync call with manually specified options, but the base Controller class already provides this.execAsync() with the same configuration (cwd, encoding, env, maxBuffer). Consider using the inherited method for consistency.

Note: The current approach works but duplicates the execution setup logic.

         try {
-            const result = await execAsync(command, { 
-                encoding: 'utf-8', 
-                cwd: this.absolutePath, 
-                env: this.getEnv(),
-                maxBuffer: 10 * 1024 * 1024,
-            });
-            rawOutput = result.stdout;
+            rawOutput = await this.execAsync(command);
         } catch (error: any) {
test-ui/src/App.tsx (1)

288-288: Replace as any with proper type assertion.

The as any type assertion bypasses type checking. Since expandedPhase is string | null and PhaseOutput expects PhaseName | null, use a proper type guard or assertion.

Apply this diff:

-              initialExpandedPhase={expandedPhase as any}
+              initialExpandedPhase={expandedPhase as PhaseName | null}

Also applies to: 308-308

test-ui/server/server.ts (2)

85-89: Consider implementing AbortSignal support for cleaner cancellation.

The currentTestAbortController is declared but unused because controllers don't yet accept AbortSignal. Implementing proper abort signal propagation would allow more immediate cancellation than relying on the shouldCancelQueue flag checked between phases.


47-64: Extract duplicated types to a shared module.

The types Phase, Phases, and PhaseName are duplicated in test-ui/src/types.ts. Consider creating a shared types module (e.g., test-ui/shared/types.ts) to ensure consistency and reduce maintenance burden.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4f1d0b6 and 99257d2.

📒 Files selected for processing (25)
  • .github/workflows/integ.yml (2 hunks)
  • .run/Template Vitest.run.xml (1 hunks)
  • README.md (1 hunks)
  • integrations/tanstack-start-basic/src/routeTree.gen.ts (1 hunks)
  • scripts/run.mts (1 hunks)
  • test-ui/.gitignore (1 hunks)
  • test-ui/CHANGES.md (1 hunks)
  • test-ui/DEVELOPMENT.md (1 hunks)
  • test-ui/README.md (1 hunks)
  • test-ui/REFACTORING.md (1 hunks)
  • test-ui/SCREENSHOTS.md (1 hunks)
  • test-ui/SUMMARY.md (1 hunks)
  • test-ui/UI_STRUCTURE.md (1 hunks)
  • test-ui/nodemon.json (1 hunks)
  • test-ui/server/scripts/TestOutcome.ts (1 hunks)
  • test-ui/server/scripts/YarnController.ts (1 hunks)
  • test-ui/server/scripts/replacePackageVersion.ts (1 hunks)
  • test-ui/server/server.ts (1 hunks)
  • test-ui/src/App.css (1 hunks)
  • test-ui/src/App.tsx (1 hunks)
  • test-ui/src/PhaseOutput.tsx (1 hunks)
  • test-ui/src/store/testDurationSelectors.ts (1 hunks)
  • test-ui/src/store/testsSlice.ts (1 hunks)
  • test-ui/test/store/exampleTestResults.ts (1 hunks)
  • test-ui/test/store/testDurationSelectors.spec.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
  • test-ui/test/store/testDurationSelectors.spec.ts
  • test-ui/nodemon.json
  • test-ui/README.md
  • test-ui/server/scripts/TestOutcome.ts
  • test-ui/src/store/testDurationSelectors.ts
  • test-ui/.gitignore
  • .run/Template Vitest.run.xml
  • test-ui/test/store/exampleTestResults.ts
  • test-ui/DEVELOPMENT.md
🧰 Additional context used
🧬 Code graph analysis (7)
test-ui/server/scripts/replacePackageVersion.ts (2)
scripts/run-everything.js (2)
  • version (11-11)
  • fs (3-3)
test-ui/server/scripts/TestOutcome.ts (1)
  • TestOutcome (18-46)
test-ui/src/App.tsx (3)
test-ui/src/types.ts (5)
  • Test (1-10)
  • TestStatus (52-52)
  • PhaseName (34-34)
  • TestRun (36-45)
  • Phases (25-32)
test-ui/src/store/hooks.ts (2)
  • useAppDispatch (4-4)
  • useAppSelector (5-5)
test-ui/src/store/testDurationSelectors.ts (4)
  • selectQueuedTestsETA (100-116)
  • selectRunningTestsETA (123-192)
  • selectElapsedTimeForRunningTestPhases (44-93)
  • selectAllRunningTests (13-18)
scripts/run.mts (4)
test-ui/server/scripts/TestOutcome.ts (1)
  • TestOutcome (18-46)
test-ui/server/scripts/NpmController.ts (1)
  • NpmController (4-99)
test-ui/server/scripts/YarnController.ts (1)
  • YarnController (27-172)
test-ui/server/scripts/test-registry.ts (2)
  • TestMetadata (12-32)
  • getTestMetadata (204-206)
test-ui/src/PhaseOutput.tsx (1)
test-ui/src/types.ts (3)
  • PhaseName (34-34)
  • Phases (25-32)
  • Phase (17-23)
test-ui/src/store/testsSlice.ts (2)
test-ui/src/types.ts (4)
  • Test (1-10)
  • TestRun (36-45)
  • Phases (25-32)
  • PhaseName (34-34)
test-ui/server/scripts/NpmController.ts (1)
  • test (14-21)
test-ui/server/server.ts (5)
test-ui/src/types.ts (3)
  • Phases (25-32)
  • PhaseName (34-34)
  • Phase (17-23)
test-ui/server/scripts/test-registry.ts (2)
  • getAllTests (223-241)
  • getTestMetadata (204-206)
test-ui/server/scripts/TestOutcome.ts (1)
  • TestOutcome (18-46)
test-ui/server/scripts/NpmController.ts (1)
  • NpmController (4-99)
test-ui/server/scripts/YarnController.ts (1)
  • YarnController (27-172)
test-ui/server/scripts/YarnController.ts (2)
test-ui/server/scripts/Controller.ts (1)
  • execAsync (36-45)
test-ui/server/scripts/TestOutcome.ts (1)
  • TestOutcome (18-46)
🪛 LanguageTool
README.md

[grammar] ~50-~50: Ensure spelling is correct
Context: ...ersion ### Pack and run with local recharts bash ./pack-and-run.sh ```

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

test-ui/REFACTORING.md

[style] ~390-~390: ‘exactly the same’ might be wordy. Consider a shorter alternative.
Context: ...codebase The app functionality remains exactly the same - all features work as before, now with...

(EN_WORDINESS_PREMIUM_EXACTLY_THE_SAME)

🪛 markdownlint-cli2 (0.18.1)
test-ui/UI_STRUCTURE.md

16-16: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


62-62: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

test-ui/SUMMARY.md

110-110: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


243-243: Bare URL used

(MD034, no-bare-urls)

test-ui/REFACTORING.md

185-185: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

test-ui/SCREENSHOTS.md

87-87: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


97-97: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


110-110: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


135-135: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


225-225: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


236-236: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


249-249: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


262-262: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (14)
integrations/tanstack-start-basic/src/routeTree.gen.ts (1)

1-9: This file was correctly auto-generated by the TanStack Router vite plugin—no manual editing occurred.

The routeTree.gen.ts file is properly regenerated as part of the PR's TypeScript/ESM migration. The vite.config.ts includes the tanstackStart() plugin from @tanstack/react-start, which automatically generates this file based on your route structure. The changes at lines 366-373 add module augmentation for @tanstack/react-start with SSR and router type registration—standard for TanStack Start projects. No action needed; this regeneration is expected and correct.

README.md (1)

3-54: Well-structured documentation for the new Test UI.

The README provides clear quick start instructions, a comprehensive feature list, and maintains backward compatibility by documenting existing CLI usage. The emoji usage for visual categorization is helpful.

test-ui/UI_STRUCTURE.md (1)

97-103: Clear and actionable documentation of UI behaviors.

The key behaviors section accurately describes the state management logic implemented in PhaseOutput.tsx, including auto-expand behavior and session persistence. This aligns well with the implementation.

test-ui/server/scripts/replacePackageVersion.ts (2)

14-21: Error handling for file read operations is properly implemented.

The try-catch block correctly captures read/parse failures and returns a structured TestOutcome.fail with descriptive error messages.


53-61: Error handling for file write operations is properly implemented.

The write operation is wrapped in try-catch and returns appropriate TestOutcome results, addressing the concerns from previous reviews.

test-ui/src/PhaseOutput.tsx (3)

23-41: Well-structured initialization logic for expanded phases.

The lazy initialization handles three cases appropriately: explicit initial phase, current running phase, and completed tests (last non-pending). The use of slice().reverse() avoids mutating the constant array.


104-114: Elapsed time only updates for currentPhase when running.

For phases that have startTime but no endTime and aren't the currentPhase, the elapsed time won't be tracked. This seems intentional (only animate the current phase), but worth noting for clarity.


132-137: Progress calculation correctly caps at 100%.

Good use of Math.min to prevent the progress bar from exceeding 100% when actual duration exceeds the estimate.

.github/workflows/integ.yml (1)

63-63: actions/setup-node@v6 is a valid released version and is currently the latest major version.

Verification confirms that v6.0.0 and v6 tags exist in the actions/setup-node repository. Your knowledge cutoff (March 2025) predates the v6 release. The code uses the current latest stable version, so no action is needed.

test-ui/src/App.css (1)

1-585: LGTM!

The stylesheet is well-structured with logical grouping, good use of transitions, and proper focus-visible states for accessibility. The color-coded status indicators and phase styling provide clear visual feedback.

test-ui/src/store/testsSlice.ts (1)

1-226: LGTM!

The Redux slice is well-structured with proper TypeScript types, clear action names, and appropriate state management patterns. The queue tracking logic correctly handles initialQueueSize and completedTestsCount. The previous setTimeout issue has been addressed.

test-ui/CHANGES.md (1)

1-60: LGTM!

Clear and well-organized documentation of the UI changes. The feature descriptions align with the implementation details in the related component files.

test-ui/server/scripts/YarnController.ts (1)

27-77: LGTM for clean, install, test, and build methods.

These methods have proper error handling, appropriate use of TestOutcome.ok() and TestOutcome.fail(), and handle edge cases (e.g., missing test script gracefully returns success instead of failure).

test-ui/src/App.tsx (1)

543-579: LGTM! Pragmatic approach for directory selection.

The fallback to prompt() for obtaining the full path is a reasonable workaround for the File System Access API's security limitations in a local development context.

matrix:
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
node-version: [ 20.x, 22.x, 24.x ]
node-version: [ 22.18.x, 24.x, 25.x ]
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 30, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Node.js version 25.x stability release status November 2025

💡 Result:

Short answer: Node.js 25.x is a Current (non‑LTS) release — first published Oct 15, 2025 — actively receiving Current‑line updates; latest patch in the 25.x stream is v25.2.1 (Nov 17, 2025). Its Current branch end-of-bugfix and end-of-security dates are listed as Apr 1, 2026 and Jun 1, 2026 respectively. [1][2][3]

Sources:

  • Node.js v25.0.0 release notes (Oct 15, 2025). [1]
  • nodejs/node GitHub releases (v25.2.1, Nov 17, 2025). [2]
  • Node.js Release WG schedule (25.x = Current; EoBF Apr 1, 2026; EoSF Jun 1, 2026). [3]

🌐 Web query:

Node.js LTS versions 22.x 24.x support timeline November 2025

💡 Result:

Summary (as of Nov 30, 2025)

  • Node.js 22.x (codename "Jod"): Maintenance LTS (moved to Maintenance in Oct 2025). End-of-life: April 2027. [1][2]
  • Node.js 24.x (codename "Krypton"): Active LTS (entered LTS in Nov 2025). Supported through end of April 2028. [1][3]

Sources:
[1] Node.js Releases (official release status table).
[2] Node.js v22.11.0 LTS blog (notes about 22.x LTS → Maintenance and EoL).
[3] Node.js migration notes (v22 → v24) — states v24 LTS updates continue through end of April 2028.


🌐 Web query:

Node.js 22.18.x current version patch available

💡 Result:

The current patch in the v22.18.x line is v22.18.0 (released July 31, 2025). [1][2]

Sources:
[1] Node.js v22.18.0 download/archive.
[2] Node.js v22.18.0 release blog post.


Use 22.x and clarify that 25.x is the Current release track, not nightly.

The matrix includes 25.x, which is Node.js's Current (non-LTS) release track, not a nightly build. While actively maintained (latest patch v25.2.1 as of November 2025), Current releases end security support June 1, 2026, making them unsuitable as primary CI targets. For 22.18.x, use the major-minor format 22.x instead; pinning to a specific patch defeats the purpose of CI testing against the release line and prevents critical patch updates.

        node-version: [ 22.x, 24.x, 25.x ]
🤖 Prompt for AI Agents
In .github/workflows/integ.yml around line 57, update the matrix entry so the
first item uses the major-minor form "22.x" instead of "22.18.x" (to test the
release line rather than a specific patch); keep "24.x" and "25.x" if you want
to include Node's Current track, otherwise remove "25.x"; optionally add a brief
inline comment noting that "25.x" represents the Current (non-LTS) release track
so it is not a nightly build.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

22.18 is the version with typescript support so we need that as a minimum. 25 is the latest, I will switch it to 26 once that is current.

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🌐 Web query:

Node.js 22.18 TypeScript support new feature

💡 Result:

Short summary — what’s new in Node.js 22.18 regarding TypeScript

  • Node.js v22.18.0 enables the built‑in "type stripping" TypeScript support by default (no experimental warning). This lets Node execute .ts files that use only erasable TypeScript syntax by removing type annotations at runtime. [1][2]

  • Key runtime flags and controls:

    • --no-strip-types (disable built‑in stripping). [1]
    • --experimental-transform-types (enable transforming non‑erasable TS features like enums/namespaces). [1][2]
  • Important limitations to know:

    • No type checking — Node only strips types; use tsc or other tools for checking. [1]
    • Features that require code generation (enums, namespaces, some parameter properties) need the transform flag or a transpiler. [1]
    • Node ignores tsconfig.json for runtime behavior (so path aliases, emit targets, etc. are not applied). [1][2]
    • Imports must use explicit extensions and type-only imports should use the type keyword to avoid runtime errors. [1]

Sources

  • Node.js v22.18.0 TypeScript docs (type stripping enabled by default; flags & limitations). [1]
  • Node.js API docs / TypeScript module page (history and stability notes). [2]

@PavelVanecek, thank you for the clarification! Node.js v22.18.0 indeed enables built-in TypeScript type stripping by default, making it the minimum version for native TS support without experimental warnings.

Your version strategy makes sense:

  • 22.18.x: minimum version for stable TypeScript support
  • 24.x: Active LTS
  • 25.x: Current release track (updated to 26.x when available)

Consider adding an inline comment in the workflow to document this reasoning for future maintainers:

node-version: [ 22.18.x, 24.x, 25.x ]  # 22.18+ required for native TS support

✏️ Learnings added
Learnt from: PavelVanecek
Repo: recharts/recharts-integ PR: 80
File: .github/workflows/integ.yml:57-57
Timestamp: 2025-11-30T04:39:58.861Z
Learning: In .github/workflows/integ.yml, Node.js version 22.18.x is the minimum required version because it enables built-in TypeScript type stripping by default, which is essential for the test-ui TypeScript/ESM implementation.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines +367 to 373
import type { getRouter } from './router.tsx'
declare module '@tanstack/react-start' {
interface Register {
ssr: true
router: Awaited<ReturnType<typeof getRouter>>
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify getRouter export and check for usage of removed server route exports

# 1. Check if getRouter is exported from router.tsx
echo "=== Checking for getRouter export ==="
rg -n "export.*getRouter" integrations/tanstack-start-basic/src/router.tsx

# 2. Search for any remaining references to removed server route entities
echo -e "\n=== Checking for references to removed server route entities ==="
rg -n "serverRouteTree|FileServerRoutes|RootServerRouteChildren|ServerFileRoutesByPath" \
  integrations/tanstack-start-basic/src/ \
  -g '!routeTree.gen.ts'

# 3. Check if @tanstack/react-start is in dependencies
echo -e "\n=== Checking @tanstack/react-start dependency ==="
cat integrations/tanstack-start-basic/package.json | jq '.dependencies["@tanstack/react-start"], .devDependencies["@tanstack/react-start"]'

# 4. Check the actual type signature of getRouter function
echo -e "\n=== Checking getRouter function signature ==="
rg -n -A 5 "export.*getRouter\s*\(" integrations/tanstack-start-basic/src/router.tsx

Repository: recharts/recharts-integ

Length of output: 285


🏁 Script executed:

#!/bin/bash
# Check what's actually exported from router.tsx
echo "=== Contents of router.tsx ==="
wc -l integrations/tanstack-start-basic/src/router.tsx

# Show all exports from router.tsx
echo -e "\n=== Exports from router.tsx ==="
rg -n "^export" integrations/tanstack-start-basic/src/router.tsx

# Search for getRouter anywhere in the project
echo -e "\n=== Searching for getRouter in entire project ==="
rg -n "getRouter" integrations/tanstack-start-basic/

# Show first 50 lines of router.tsx to understand structure
echo -e "\n=== First 50 lines of router.tsx ==="
head -50 integrations/tanstack-start-basic/src/router.tsx

Repository: recharts/recharts-integ

Length of output: 1151


Fix import: getRouter does not exist in router.tsx—use createRouter instead.

The module augmentation imports a non-existent getRouter function. The file exports createRouter, not getRouter. The import statement on line 367 will fail at compile time. Change:

import type { getRouter } from './router.tsx'

to:

import type { createRouter } from './router.tsx'

Also update line 371 to use createRouter:

router: Awaited<ReturnType<typeof createRouter>>
🤖 Prompt for AI Agents
In integrations/tanstack-start-basic/src/routeTree.gen.ts around lines 367 to
373, the module augmentation imports a non-existent getRouter; replace the
import to reference the actual exported function createRouter from
'./router.tsx' and update the Register.router type to use
Awaited<ReturnType<typeof createRouter>> so the type resolves correctly at
compile time.

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: 5

♻️ Duplicate comments (3)
.github/workflows/integ.yml (1)

23-27: CI Node version matrix and setup steps look consistent with the TS/ESM tooling

The added setup-node@v6 steps and updated matrix [22.18.x, 24.x, 25.x] align with the TypeScript/ESM setup and give good coverage across minimum+LTS+current. One small suggestion: add an inline comment on the 22.18.x entry explaining it’s the minimum version with built‑in TS type stripping required by the new test‑ui tooling, so future maintainers understand why that exact minor is pinned.

Based on learnings, this pin is intentional due to Node 22.18’s TS support.

Also applies to: 57-65

integrations/tanstack-start-basic/src/routeTree.gen.ts (1)

367-372: Double-check that router.tsx actually exports getRouter to match this Register type

The ambient augmentation here assumes a getRouter function exported from ./router.tsx:

import type { getRouter } from './router.tsx'

declare module '@tanstack/react-start' {
  interface Register {
    ssr: true
    router: Awaited<ReturnType<typeof getRouter>>
  }
}

On a previous revision, router.tsx exported createRouter, not getRouter, which would make this type import inaccurate. If you haven’t already updated router.tsx, consider either exporting getRouter there or switching this to use whatever factory you actually export (e.g. createRouter). Otherwise your registered router type may not line up with the real router instance.

scripts/run.mts (1)

40-64: Library controller outcomes are still discarded.

This was flagged in a previous review but remains unaddressed. Lines 41-46 execute library steps (clean, replacePackageJsonVersion, install, test, build, verifyAllSingleDependencyVersions) but never capture their results. If any library step fails, the failure is silently ignored and execution continues to the app phase, masking root causes.

Apply the fix from the previous review to capture library outcomes:

 async function runLibraryInLibraryTest(libController: Controller, appController: Controller, rechartsVersion: string): Promise<TestOutcome[]> {
-    await libController.clean();
-    await libController.replacePackageJsonVersion("recharts", rechartsVersion);
-    await libController.install();
-    await libController.test();
-    await libController.build();
-    await verifyAllSingleDependencyVersions(libController);
+    const results: TestOutcome[] = [];
+    
+    results.push(await libController.clean());
+    results.push(await libController.replacePackageJsonVersion("recharts", rechartsVersion));
+    const libInstallResult = await libController.install();
+    results.push(libInstallResult);
+    if (!libInstallResult.success) {
+        console.warn('Failed to install the library. Skipping remaining steps.');
+        return results;
+    }
+    results.push(await libController.test());
+    results.push(await libController.build());
+    results.push(...await verifyAllSingleDependencyVersions(libController));
     const myChartsTgzFile = await libController.pack();

-    const results: TestOutcome[] = [];
-
     results.push(await appController.clean());
🧹 Nitpick comments (13)
README.md (1)

8-15: Optionally call out the minimum Node.js version in Quick Start

Given list.js and the new test UI rely on Node’s built‑in TypeScript support, consider adding a brief note here that local runs expect at least Node 22.18.x (or newer) so developers don’t hit confusing runtime errors on older Node versions.

Based on learnings, this repo already standardizes on 22.18.x+ in CI for that reason.

test-ui/SCREENSHOTS.md (1)

87-266: Add a language (text) to ASCII-art code fences for markdownlint

A few of the fenced blocks showing UI boxes don’t specify a language (markdownlint MD040). To keep docs lint‑clean and consistent with the earlier ```text block, you could annotate these as ```text as well.

test-ui/UI_STRUCTURE.md (1)

16-82: Align fenced diagrams with text language for markdownlint

These UI layout diagrams are in fenced blocks without a language. For consistency with your other diagrams and to appease markdownlint (MD040), consider changing them to ```text.

test-ui/src/store/testsSlice.ts (1)

79-158: Queue progress tracking assumes at most one running test—document or revisit if you ever add parallelism

The initialQueueSize / completedTestsCount logic in testQueued and testCompleted looks correct for the current serial runner:

  • initialQueueSize is derived from queuedTests + runningTests and updated if the queue grows mid‑run.
  • testCompleted increments completedTestsCount and resets both counters when remainingTests === 1 (i.e. this is the last running test).

That remainingTests === 1 reset implicitly relies on there never being more than one running test at a time. If you later introduce parallel execution for multiple tests, this heuristic will start to misfire and the progress counters will reset prematurely.

I’d suggest either (a) adding a brief comment here noting the “serial only” assumption, or (b) when/if you add parallelism, switching the reset condition to something like “no queued or running tests remain after this test is moved to results.”

test-ui/src/PhaseOutput.tsx (1)

94-120: Interval cleanup is correctly implemented, but consider optimization.

The interval runs every 100ms which could cause frequent re-renders. Since you're tracking elapsed time for running phases, this is reasonable for smooth progress display. The cleanup function properly clears the interval.

However, the effect has phases in its dependency array, which means the interval restarts whenever any phase data changes (including output strings). Consider extracting only the needed phase timing data to reduce unnecessary interval restarts.

test-ui/test/store/testDurationSelectors.spec.ts (1)

119-127: Hardcoded floating-point expectations are fragile.

The test uses precise floating-point values like 2682.0689655172414. If the fixture data or calculation logic changes slightly, these tests will fail even if the selector logic is correct. Consider using toBeCloseTo() for floating-point comparisons or computing expected values programmatically from the fixture.

     expect(elapsedTimes).toEqual({
-      build: 2682.0689655172414,
-      clean: 2359.766666666667,
-      install: 18898.3,
-      setVersion: 3918.266666666667,
-      test: 1764.0689655172414,
-      verify: 357.17241379310343,
+      build: expect.closeTo(2682, 1),
+      clean: expect.closeTo(2360, 1),
+      install: expect.closeTo(18898, 1),
+      setVersion: expect.closeTo(3918, 1),
+      test: expect.closeTo(1764, 1),
+      verify: expect.closeTo(357, 1),
     });
test-ui/src/App.css (1)

475-478: Pulse animation provides good visual feedback for running state.

The animation is subtle (opacity 0.5-1) and won't cause accessibility issues for users sensitive to motion. Consider adding a prefers-reduced-motion media query for users who prefer minimal animations.

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

@media (prefers-reduced-motion: reduce) {
  .phase-summary-item.running {
    animation: none;
  }
}
test-ui/REFACTORING.md (1)

185-199: Add language identifier to fenced code block.

The message flow diagram code block is missing a language identifier. While it's a text diagram, adding a language hint improves rendering in some markdown processors.

-```
+```text
 WebSocket Server
       ↓
    onmessage
test-ui/src/store/testDurationSelectors.ts (1)

30-37: Avoid duplicating phase lists across objects

educatedGuess, phaseDurations, and averageDurations all hard‑code the same phase keys. Combined with the PHASE_ORDER arrays in this file and in App.tsx, this creates several places that must be updated in lockstep when adding/removing phases.

Consider extracting a single PHASE_ORDER: PhaseName[] (or similar) in a shared module and deriving these records via .reduce, so phase changes are compiler‑guided and less error‑prone.

Also applies to: 52-59, 72-79

test-ui/server/scripts/test-registry.ts (1)

157-199: Consider centralizing registry sources and avoiding magic strings

The library registry construction is clear, but a couple of small refactors could improve maintainability:

  • ['npm', 'yarn'] as PackageManager[] and hard‑coded library/app IDs could be moved to shared constants, so consumers (e.g. server scripts, UI filters) don’t risk spelling drift.
  • allTests is the single source for getAllTestNames, getAllTests, and testRegistry; documenting that (or freezing it) would clarify that it’s not meant to be mutated at runtime.

These are non‑blocking but would make future expansions (new package managers or libraries) safer.

Also applies to: 211-241

test-ui/src/App.tsx (2)

518-523: Optional: prevent duplicate runTest calls for already queued/running tests

runSelectedTests unconditionally calls runTest for each selected test, and the per‑row “Run” button is only disabled while a test is queued (disabled={isQueued}), not while it’s already running. This makes it easy to fire multiple /tests/run requests for the same test if a user double‑clicks or re‑selects it while it’s in flight.

If the backend already deduplicates safely, this is just a UX concern; otherwise, you might consider:

  • Disabling “Run” when isRunning or isQueued, or
  • Filtering selectedTests in runSelectedTests to exclude tests currently in queuedTests or runningTests.

Non‑blocking, but it would make the UI behavior more predictable.

Also applies to: 825-831, 869-892


90-123: Centralize phase order to avoid duplication

The phase traversal logic is duplicated across the codebase: hardcoded in App.tsx (lines 99–106 and 144–151), defined locally in testDurationSelectors.ts (line 147), and defined in PhaseOutput.tsx (line 14). To prevent drift and reduce maintenance burden, extract this to a shared constant—either export it from PhaseOutput.tsx or create a dedicated constants module—and import it in App.tsx and testDurationSelectors.ts.

test-ui/src/types.ts (1)

36-52: Optional: simplify TestStatus discriminant

Right now:

  • TestRun has a status field including 'queued'.
  • QueuedTest has no status.
  • TestStatus = TestRun | QueuedTest, and consumers discriminate with "status" in status.

This works, but you could simplify future usage by adding a literal status: 'queued' to QueuedTest and narrowing TestRun['status'] to omit 'queued'. That would let you use a simple discriminated union on status without additional "in" checks.

Non‑blocking, just a potential ergonomic improvement.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 99257d2 and 95694a1.

📒 Files selected for processing (58)
  • .github/workflows/integ.yml (2 hunks)
  • .run/Template Vitest.run.xml (1 hunks)
  • README.md (1 hunks)
  • apps-3rd-party/app-react17/package.json (1 hunks)
  • integrations/tanstack-start-basic/package.json (2 hunks)
  • integrations/tanstack-start-basic/src/routeTree.gen.ts (1 hunks)
  • list.js (1 hunks)
  • run-test.sh (1 hunks)
  • scripts/Controller.js (0 hunks)
  • scripts/NpmController.js (0 hunks)
  • scripts/TestResult.js (0 hunks)
  • scripts/YarnController.js (0 hunks)
  • scripts/replacePackageVersion.js (0 hunks)
  • scripts/run.js (0 hunks)
  • scripts/run.mts (1 hunks)
  • scripts/tgzFileNameToPackageJsonReference.js (0 hunks)
  • start-ui.sh (1 hunks)
  • test-ui/.gitignore (1 hunks)
  • test-ui/.vite/deps/_metadata.json (1 hunks)
  • test-ui/.vite/deps/package.json (1 hunks)
  • test-ui/CHANGES.md (1 hunks)
  • test-ui/CHECKLIST.md (1 hunks)
  • test-ui/DEVELOPMENT.md (1 hunks)
  • test-ui/QUICKSTART.md (1 hunks)
  • test-ui/README.md (1 hunks)
  • test-ui/REFACTORING.md (1 hunks)
  • test-ui/SCREENSHOTS.md (1 hunks)
  • test-ui/SUMMARY.md (1 hunks)
  • test-ui/UI_STRUCTURE.md (1 hunks)
  • test-ui/index.html (1 hunks)
  • test-ui/nodemon.json (1 hunks)
  • test-ui/package.json (1 hunks)
  • test-ui/server/scripts/Controller.ts (1 hunks)
  • test-ui/server/scripts/NpmController.ts (1 hunks)
  • test-ui/server/scripts/TestOutcome.ts (1 hunks)
  • test-ui/server/scripts/YarnController.ts (1 hunks)
  • test-ui/server/scripts/replacePackageVersion.ts (1 hunks)
  • test-ui/server/scripts/test-registry.ts (1 hunks)
  • test-ui/server/scripts/tgzFileNameToPackageJsonReference.ts (1 hunks)
  • test-ui/server/server.ts (1 hunks)
  • test-ui/server/tsconfig.json (1 hunks)
  • test-ui/src/App.css (1 hunks)
  • test-ui/src/App.tsx (1 hunks)
  • test-ui/src/PhaseOutput.css (1 hunks)
  • test-ui/src/PhaseOutput.tsx (1 hunks)
  • test-ui/src/index.css (1 hunks)
  • test-ui/src/main.tsx (1 hunks)
  • test-ui/src/store/hooks.ts (1 hunks)
  • test-ui/src/store/store.ts (1 hunks)
  • test-ui/src/store/testDurationSelectors.ts (1 hunks)
  • test-ui/src/store/testsSlice.ts (1 hunks)
  • test-ui/src/store/websocketMiddleware.ts (1 hunks)
  • test-ui/src/types.ts (1 hunks)
  • test-ui/test/store/exampleTestResults.ts (1 hunks)
  • test-ui/test/store/testDurationSelectors.spec.ts (1 hunks)
  • test-ui/test/store/testsSlice.spec.ts (1 hunks)
  • test-ui/tsconfig.json (1 hunks)
  • test-ui/vite.config.ts (1 hunks)
💤 Files with no reviewable changes (7)
  • scripts/TestResult.js
  • scripts/NpmController.js
  • scripts/Controller.js
  • scripts/YarnController.js
  • scripts/tgzFileNameToPackageJsonReference.js
  • scripts/run.js
  • scripts/replacePackageVersion.js
✅ Files skipped from review due to trivial changes (2)
  • test-ui/QUICKSTART.md
  • test-ui/src/PhaseOutput.css
🚧 Files skipped from review as they are similar to previous changes (29)
  • test-ui/.gitignore
  • test-ui/package.json
  • run-test.sh
  • test-ui/test/store/testsSlice.spec.ts
  • .run/Template Vitest.run.xml
  • test-ui/src/store/websocketMiddleware.ts
  • test-ui/nodemon.json
  • start-ui.sh
  • integrations/tanstack-start-basic/package.json
  • test-ui/src/store/hooks.ts
  • test-ui/src/store/store.ts
  • apps-3rd-party/app-react17/package.json
  • test-ui/server/scripts/replacePackageVersion.ts
  • test-ui/.vite/deps/_metadata.json
  • test-ui/DEVELOPMENT.md
  • test-ui/tsconfig.json
  • test-ui/CHECKLIST.md
  • test-ui/src/main.tsx
  • test-ui/src/index.css
  • test-ui/vite.config.ts
  • test-ui/README.md
  • test-ui/CHANGES.md
  • test-ui/server/scripts/tgzFileNameToPackageJsonReference.ts
  • test-ui/.vite/deps/package.json
  • test-ui/server/tsconfig.json
  • test-ui/server/server.ts
  • test-ui/server/scripts/Controller.ts
  • test-ui/index.html
  • test-ui/server/scripts/YarnController.ts
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: PavelVanecek
Repo: recharts/recharts-integ PR: 80
File: .github/workflows/integ.yml:57-57
Timestamp: 2025-11-30T04:39:58.861Z
Learning: In .github/workflows/integ.yml, Node.js version 22.18.x is the minimum required version because it enables built-in TypeScript type stripping by default, which is essential for the test-ui TypeScript/ESM implementation.
📚 Learning: 2025-11-30T04:39:58.861Z
Learnt from: PavelVanecek
Repo: recharts/recharts-integ PR: 80
File: .github/workflows/integ.yml:57-57
Timestamp: 2025-11-30T04:39:58.861Z
Learning: In .github/workflows/integ.yml, Node.js version 22.18.x is the minimum required version because it enables built-in TypeScript type stripping by default, which is essential for the test-ui TypeScript/ESM implementation.

Applied to files:

  • .github/workflows/integ.yml
🧬 Code graph analysis (8)
test-ui/test/store/testDurationSelectors.spec.ts (3)
test-ui/src/types.ts (1)
  • Phase (17-23)
test-ui/src/store/store.ts (3)
  • RootState (20-20)
  • store (9-18)
  • rootReducer (5-7)
test-ui/src/store/testDurationSelectors.ts (4)
  • selectAllTestResults (23-28)
  • selectElapsedTimeForRunningTestPhases (44-93)
  • selectAllRunningTests (13-18)
  • selectRunningTestsETA (123-192)
test-ui/src/PhaseOutput.tsx (1)
test-ui/src/types.ts (3)
  • PhaseName (34-34)
  • Phases (25-32)
  • Phase (17-23)
test-ui/server/scripts/NpmController.ts (2)
test-ui/server/scripts/TestOutcome.ts (1)
  • TestOutcome (18-52)
scripts/run-everything.js (1)
  • output (14-31)
list.js (2)
scripts/run-everything.js (2)
  • require (1-1)
  • require (2-2)
test-ui/server/scripts/test-registry.ts (1)
  • getAllTestNames (211-218)
test-ui/src/store/testDurationSelectors.ts (2)
test-ui/src/store/store.ts (1)
  • RootState (20-20)
test-ui/src/types.ts (2)
  • TestRun (36-45)
  • PhaseName (34-34)
scripts/run.mts (4)
test-ui/server/scripts/TestOutcome.ts (1)
  • TestOutcome (18-52)
test-ui/server/scripts/NpmController.ts (1)
  • NpmController (4-99)
test-ui/server/scripts/YarnController.ts (1)
  • YarnController (27-172)
test-ui/server/scripts/test-registry.ts (2)
  • TestMetadata (12-32)
  • getTestMetadata (204-206)
test-ui/src/App.tsx (3)
test-ui/src/types.ts (5)
  • Test (1-10)
  • TestStatus (52-52)
  • PhaseName (34-34)
  • TestRun (36-45)
  • Phases (25-32)
test-ui/src/store/hooks.ts (2)
  • useAppDispatch (4-4)
  • useAppSelector (5-5)
test-ui/src/store/testDurationSelectors.ts (4)
  • selectQueuedTestsETA (100-116)
  • selectRunningTestsETA (123-192)
  • selectElapsedTimeForRunningTestPhases (44-93)
  • selectAllRunningTests (13-18)
test-ui/src/store/testsSlice.ts (2)
test-ui/src/types.ts (4)
  • Test (1-10)
  • TestRun (36-45)
  • Phases (25-32)
  • PhaseName (34-34)
test-ui/server/scripts/NpmController.ts (1)
  • test (14-21)
🪛 LanguageTool
README.md

[grammar] ~50-~50: Ensure spelling is correct
Context: ...ersion ### Pack and run with local recharts bash ./pack-and-run.sh ```

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

test-ui/REFACTORING.md

[style] ~390-~390: ‘exactly the same’ might be wordy. Consider a shorter alternative.
Context: ...codebase The app functionality remains exactly the same - all features work as before, now with...

(EN_WORDINESS_PREMIUM_EXACTLY_THE_SAME)

🪛 markdownlint-cli2 (0.18.1)
test-ui/REFACTORING.md

185-185: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

test-ui/SCREENSHOTS.md

87-87: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


97-97: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


110-110: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


135-135: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


225-225: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


236-236: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


249-249: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


262-262: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

test-ui/UI_STRUCTURE.md

16-16: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


62-62: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ 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). (4)
  • GitHub Check: npm:integrations/ts-react16-overrides on node 25.x
  • GitHub Check: yarn:my-charts-react19:app-react18 on node 24.x
  • GitHub Check: npm:integrations/ts4-react17 on node 25.x
  • GitHub Check: yarn:my-charts-react19:app-react19 on node 22.18.x
🔇 Additional comments (14)
test-ui/server/scripts/TestOutcome.ts (1)

1-51: TestOutcome and stringify implementation look solid

The stringify helper plus TestOutcome.ok/fail cover the expected error shapes (output/stdout/stderr) and fall back safely when needed; success aligns with the factories’ usage. No changes needed from my side.

list.js (1)

20-39: CommonJS bridge to the TS test registry looks good

The dynamic require of test-ui/server/scripts/test-registry.ts and use of getAllTestNames('stable') vs getAllTestNames() keeps this script backwards‑compatible for existing callers while centralizing test definitions in the registry. The path calculation via __dirname and path.join is solid; I don’t see any issues here.

test-ui/test/store/exampleTestResults.ts (1)

1-1615: Well-structured test fixture data.

The fixture provides comprehensive coverage of test scenarios including passed, failed, and in-progress tests with realistic phase data and timing information. This will enable thorough testing of duration selectors and state management.

test-ui/src/PhaseOutput.tsx (2)

24-41: State initialization logic is well-structured.

The lazy initializer handles three scenarios appropriately: explicit initial phase, current running phase, and completed tests (last non-pending phase). Good use of functional initialization to avoid unnecessary computation on re-renders.


131-137: Progress calculation handles edge cases correctly.

The fallback to 1000ms when estimatedPhaseDurations[phaseName] is falsy and capping progress at 100% prevents visual glitches from unexpected data. Note that a duration of 0 would also trigger the fallback, which is reasonable behavior.

test-ui/src/App.css (1)

149-154: Focus states are well-implemented for accessibility.

Good use of :focus-visible for keyboard navigation without affecting mouse users. The outline styling is clear and consistent across interactive elements.

test-ui/SUMMARY.md (1)

159-166: Security considerations are appropriately documented.

Good transparency about security limitations (no auth, CORS enabled, no input validation). The clear note about local development use sets appropriate expectations.

test-ui/server/scripts/NpmController.ts (2)

5-12: Install method has proper error handling.

Good pattern of wrapping the async operation in try/catch and returning appropriate TestOutcome for both success and failure cases.


38-98: Robust dependency verification implementation.

The verifySingleDependencyVersion method properly handles all edge cases: command failure, JSON parse errors, missing dependencies, and multiple versions. The recursive walkDependencies function correctly traverses the dependency tree. This addresses the concerns from the previous review.

test-ui/server/scripts/test-registry.ts (2)

38-155: Verify direct test stability flags against legacy list.js

The stability settings for the direct dependency tests (npm vs yarn variants) differ in several places (e.g. ts-react16 is experimental for npm but stable for yarn, ts4-react17 is stable for npm but experimental for yarn).

If this is intentional, a brief comment noting the rationale would help future maintainers. If it’s meant to mirror the old list.js behavior exactly, it’s worth re‑checking those flags to avoid silently changing which tests run in CI.


243-249: isStableTest correctly delegates to metadata

The isStableTest helper cleanly reuses getTestMetadata and avoids re‑encoding stability rules. No issues from a correctness standpoint.

test-ui/src/App.tsx (2)

360-385: Global timer and progress calculation look correct

The global elapsed timer and progress bar logic correctly:

  • Start timing only when there are running or queued tests.
  • Reset and clear intervals when the queue is empty.
  • Base progress on completedTestsCount / initialQueueSize and ETA on runningETA + queuedETA.

No functional issues spotted here; the dependency array (runningTestsList.length, queuedTests) is sufficient to keep the timer in sync with queue changes.

Also applies to: 666-671, 693-713


395-400: Session/localStorage persistence flows are straightforward

Persisting testResults into sessionStorage and packing directory/local package paths into localStorage is implemented cleanly and guarded with try/catch where needed. The single‑item and “clear all” flows correctly keep storage in sync with Redux state.

No changes needed here.

Also applies to: 543-552, 554-557

test-ui/src/types.ts (1)

1-10: Type modelling for tests and phases aligns with usage

Test, Phase, Phases, and PhaseName match how the selectors and UI components (e.g. PhaseOutput, TestItem) consume them. Using PhaseName = keyof Phases is a good way to keep the union and object shape in sync.

No issues here.

Also applies to: 17-35

Comment on lines +136 to +139
if (passed.length === 0 && skipped.length === 0) {
console.error('No tests were run.');
process.exit(1);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Reference to undefined variable skipped will cause runtime error.

Line 136 references skipped.length but skipped is never defined in this file. This will throw a ReferenceError at runtime when executed via CLI. The past review indicated this was addressed, but the variable reference was left behind.

     if (errors.length > 0) {
         errors.forEach(result => {
             console.error(`❌ ${result.name}: ${result.error}`);
         });
         process.exit(1);
     }
-    if (passed.length === 0 && skipped.length === 0) {
+    if (passed.length === 0) {
         console.error('No tests were run.');
         process.exit(1);
     }
     process.exit(0);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (passed.length === 0 && skipped.length === 0) {
console.error('No tests were run.');
process.exit(1);
}
if (passed.length === 0) {
console.error('No tests were run.');
process.exit(1);
}
🤖 Prompt for AI Agents
In scripts/run.mts around lines 136–139, the code references an undefined
variable `skipped`, causing a ReferenceError; either (A) if a skipped-tests
collection exists elsewhere, replace `skipped` with the correct variable name
(e.g., `skippedTests`) or ensure `skipped` is declared and populated where test
results are collected, or (B) if there are only `passed`/`failed` counters,
remove `skipped` from the condition and adjust the check to use the correct
variables (for example `if (passed.length === 0 && failed.length === 0)`) so the
CLI no longer throws at runtime.

Comment on lines +33 to +36
async pack(): Promise<string> {
const output = await this.execAsync("npm pack");
return this.tgzFileNameToPackageJsonReference(output.trim());
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing error handling in pack() method.

Unlike other methods that return TestOutcome, pack() returns a string and has no try/catch. If npm pack fails or produces unexpected output, the error will propagate uncaught. This inconsistency could cause unexpected crashes in the test orchestration.

Consider wrapping in try/catch or changing the return type to handle failures:

   async pack(): Promise<string> {
+    try {
       const output = await this.execAsync("npm pack");
       return this.tgzFileNameToPackageJsonReference(output.trim());
+    } catch (ex) {
+      throw new Error(`npm pack failed: ${ex instanceof Error ? ex.message : String(ex)}`);
+    }
   }

Alternatively, if failures should be handled gracefully, consider returning TestOutcome | string or a result object.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async pack(): Promise<string> {
const output = await this.execAsync("npm pack");
return this.tgzFileNameToPackageJsonReference(output.trim());
}
async pack(): Promise<string> {
try {
const output = await this.execAsync("npm pack");
return this.tgzFileNameToPackageJsonReference(output.trim());
} catch (ex) {
throw new Error(`npm pack failed: ${ex instanceof Error ? ex.message : String(ex)}`);
}
}
🤖 Prompt for AI Agents
In test-ui/server/scripts/NpmController.ts around lines 33–36, the pack() method
calls npm pack and returns a string but lacks error handling; wrap the execAsync
call in a try/catch and change the method signature to Promise<TestOutcome |
string> (or return TestOutcome on failure) so failures are represented
consistently with other methods—on success return the package string, on error
return a TestOutcome failure object containing the error message and any
relevant context.

Comment on lines +457 to +488
const loadRechartsVersions = async () => {
try {
dispatch(setLoadingVersions(true));
// Fetch from npm registry
const response = await fetch("https://registry.npmjs.org/recharts");
const data = await response.json();

// Get versions and sort from latest to oldest
const versions = Object.keys(data.versions || {})
.filter((v) => !v.includes("-")) // Filter out pre-release versions
.sort((a, b) => {
// Compare version numbers (simple sort by version string works for semver)
const aParts = a.split(".").map(Number);
const bParts = b.split(".").map(Number);

for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
const aVal = aParts[i] || 0;
const bVal = bParts[i] || 0;
if (aVal !== bVal) {
return bVal - aVal; // Descending order
}
}
return 0;
})
.slice(0, 50); // Limit to 50 most recent versions

dispatch(setAvailableVersions(versions));
} catch (err) {
console.error("Failed to load Recharts versions:", err);
dispatch(setLoadingVersions(false));
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Bug: loadingVersions is never reset to false on success

loadRechartsVersions sets setLoadingVersions(true) before the fetch and only calls setLoadingVersions(false) in the catch block. On a successful response, loadingVersions remains true, which means the Recharts version <select> stays disabled due to disabled={loadingVersions || !!localPackagePath}.

This effectively prevents users from ever choosing a specific version once the registry call succeeds.

Consider mirroring the pattern from loadTests and always clearing the flag in a finally block, e.g.:

  const loadRechartsVersions = async () => {
    try {
      dispatch(setLoadingVersions(true));
      // Fetch from npm registry
      const response = await fetch("https://registry.npmjs.org/recharts");
      const data = await response.json();
      // ...
      dispatch(setAvailableVersions(versions));
-    } catch (err) {
-      console.error("Failed to load Recharts versions:", err);
-      dispatch(setLoadingVersions(false));
-    }
+    } catch (err) {
+      console.error("Failed to load Recharts versions:", err);
+    } finally {
+      dispatch(setLoadingVersions(false));
+    }
  };

Also applies to: 733-755

🤖 Prompt for AI Agents
In test-ui/src/App.tsx around lines 457 to 488 (and similarly at 733 to 755),
the function sets loadingVersions to true before the fetch but only clears it in
the catch block so the flag remains true on success; move the
dispatch(setLoadingVersions(false)) into a finally block (or add a finally that
dispatches setLoadingVersions(false)) so loadingVersions is always reset
regardless of success or failure, ensuring the version <select> becomes enabled
after the fetch completes.

Comment on lines +44 to +51
export const selectElapsedTimeForRunningTestPhases: (
state: RootState,
) => Record<PhaseName, number> = createSelector(
[selectAllTestResults],
(testResults: ReadonlyArray<TestRun>) => {
if (testResults.length === 0) {
return educatedGuess;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard against invalid timestamps causing NaN ETAs

Both selectElapsedTimeForRunningTestPhases and especially selectRunningTestsETA assume that all phase timestamps and durations are valid. If phase.startTime is a malformed string, new Date(phase.startTime).getTime() becomes NaN, which then propagates through elapsed, remaining, and totalETA, ultimately yielding NaN from Math.ceil(totalETA) and breaking UI formatting.

A light defensive check (e.g. ignoring phases where Number.isFinite(startTimestamp) or Number.isFinite(remaining) fails) would make the ETA computation robust against bad data without changing the happy path.

Also applies to: 81-89, 100-115, 123-191

Comment on lines +150 to +161
describe("selectRunningTestsETA", () => {
it("should estimate the remaining time for currently running tests", () => {
const store = prepareStore();
const state = store.getState();
const eta = selectRunningTestsETA(state);

// Based on the remaining durations of the two running tests
// Note: This expectation uses snapshot values from fixture and may be brittle
// if fixture timestamps change. Consider using toBeCloseTo or mocking Date.now().
expect(eta).toBe(53682);
});
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Time-dependent test will be flaky.

The selectRunningTestsETA selector uses Date.now() internally, but the test compares against a hardcoded value. Since the fixture timestamps are from 2025-11-28 and tests run at different times, this test will produce inconsistent results. The comment acknowledges this but doesn't address it.

Consider mocking Date.now() to ensure deterministic results:

it("should estimate the remaining time for currently running tests", () => {
  const mockNow = new Date("2025-11-28T02:19:50.000Z").getTime();
  vi.spyOn(Date, 'now').mockReturnValue(mockNow);
  
  const store = prepareStore();
  const state = store.getState();
  const eta = selectRunningTestsETA(state);
  
  expect(eta).toBeCloseTo(expectedValue, -2); // Allow some variance
  
  vi.restoreAllMocks();
});
🤖 Prompt for AI Agents
In test-ui/test/store/testDurationSelectors.spec.ts around lines 150 to 161, the
test uses a hardcoded ETA that depends on Date.now() and will be flaky; mock
Date.now() to return the fixture timestamp (e.g. the 2025-11-28 fixture time)
before calling prepareStore()/selectRunningTestsETA, assert deterministically
(use toBe or toBeCloseTo with an appropriate tolerance), and restore the mock
after the assertion (or use a test framework helper to auto-restore) so the test
is stable and deterministic.

@PavelVanecek
Copy link
Collaborator Author

OK All the tests are passing, the app is not quite 100% perfect but it's good enough to be useful.

@PavelVanecek PavelVanecek merged commit 9b39944 into main Nov 30, 2025
41 checks passed
@PavelVanecek PavelVanecek deleted the test-ui branch November 30, 2025 13:37
@coderabbitai coderabbitai bot mentioned this pull request Dec 1, 2025
@ckifer
Copy link
Member

ckifer commented Dec 1, 2025

Figured it's not as important to review 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants