Skip to content

Conversation

toyamarinyon
Copy link
Contributor

@toyamarinyon toyamarinyon commented Sep 30, 2025

User description

  • Add runner syncing to keep local runners with remote state
  • Simplify generation selection logic for clearer, more predictable behavior
  • Simplify stream handling to reduce complexity and improve reliability

This is part 2 of 2 in a stack made with GitButler:


PR Type

Enhancement


Description

  • Add runner syncing to keep local state with remote

  • Simplify generation selection logic for better predictability

  • Separate stream handling into distinct lifecycle phases

  • Prevent duplicate generations in store with deduplication


Diagram Walkthrough

flowchart LR
  A["Remote Generations"] --> B["Sync to Store"]
  B --> C["Filter & Deduplicate"]
  C --> D["Current Generation"]
  E["Generation Runner"] --> F["Start Generation"]
  F --> G["Listen to Stream"]
Loading

File Walkthrough

Relevant files
Enhancement
use-node-generations.ts
Sync generations and simplify selection logic                       

packages/giselle/src/react/generations/hooks/use-node-generations.ts

  • Add useEffect to sync remote generations with local store
  • Simplify generation selection by removing complex deduplication logic
  • Filter generations directly from store instead of merging sources
+26/-26 
store.ts
Add generation deduplication in store                                       

packages/giselle/src/react/generations/store.ts

  • Add deduplication logic to prevent duplicate generations
  • Filter existing generations before adding new ones
  • Use Set-based approach for efficient ID matching
+5/-1     
generate-content-runner.tsx
Separate generation start and stream handling                       

packages/giselle/src/react/generations/generate-content-runner.tsx

  • Split generation lifecycle into separate refs for start and listen
    phases
  • Separate useEffect hooks for starting and listening to generation
  • Remove stream processing from generation start effect
+18/-5   

Note

Sync remote generations into the store with deduplication, simplify current-generation selection, and split generation start vs. stream listening with lifecycle-safe resets.

  • Generation Runner lifecycle (packages/giselle/src/react/generations/generate-content-runner.tsx)
    • Split lifecycle into start and listen phases with separate refs and effects; start only when queued, stream only when running.
    • Reset lifecycle/queues when generation.id changes (clear message queue, cancel pending animation frame, reset flags).
  • Generations syncing & selection (packages/giselle/src/react/generations/hooks/use-node-generations.ts)
    • Sync fetched generations into runner system via addGenerationRunner after SWR load.
    • Simplify currentGeneration by filtering/sorting store entries (remove merge/dedup logic with fetched data).
  • Store deduplication (packages/giselle/src/react/generations/store.ts)
    • addGenerationRunner now deduplicates by ID before appending to generations.

Written by Cursor Bugbot for commit 59152a7. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • New Features

    • None
  • Bug Fixes

    • Eliminated duplicate generation entries when adding new items.
    • More reliably selects the most recent active generation for a node.
    • Ensures content streaming starts only when a generation is actively running, preventing missed or premature processing.
  • Refactor

    • Two-phase generation lifecycle with lifecycle resets for greater stability.
    • Streamlined generation selection and deduplication logic.

Copy link

changeset-bot bot commented Sep 30, 2025

⚠️ No Changeset found

Latest commit: 59152a7

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

💥 An error occurred when fetching the changed packages and changesets in this PR
Some errors occurred when validating the changesets config:
The package or glob expression "giselle-sdk" is specified in the `ignore` option but it is not found in the project. You may have misspelled the package name or provided an invalid glob expression. Note that glob expressions must be defined according to https://www.npmjs.com/package/micromatch.

Copy link

giselles-ai bot commented Sep 30, 2025

Finished running flow.

Step Status Updated(UTC)
1 Sep 30, 2025 12:27am
2 Sep 30, 2025 12:28am
3 Sep 30, 2025 12:28am
4 Sep 30, 2025 12:28am

Copy link

vercel bot commented Sep 30, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
giselle Ready Ready Preview Comment Sep 30, 2025 7:17am

Copy link
Contributor

coderabbitai bot commented Sep 30, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Implements a two‑phase generation runner (initiate then listen), replaces a single didRun flag with didPerformingContentGeneration and didListeningContentGeneration, resets lifecycle state when generation.id changes, exposes and uses addGenerationRunner in the node hook, changes currentGeneration derivation to filter+sort, and deduplicates incoming generations on add.

Changes

Cohort / File(s) Summary
Runner control flow (two‑phase start/listen)
packages/giselle/src/react/generations/generate-content-runner.tsx
Replace didRun with didPerformingContentGeneration and didListeningContentGeneration; add effect to reset lifecycle flags, clear message queue, and cancel RAF when generation.id changes; set performing flag when starting generation and defer processStream to an effect that triggers when generation.status === "running"; mark listening flag after starting stream; guard re-entry with the performing flag.
Hook: fetch → runner integration
packages/giselle/src/react/generations/hooks/use-node-generations.ts
Import/use useEffect and isLoading; expose addGenerationRunner from the runner system; call addGenerationRunner(data) once SWR data is loaded; compute currentGeneration via a single pipeline: filter out cancelled generations, apply node/origin constraints, sort by createdAt (desc), and pick the most recent.
Store: deduplicate on add
packages/giselle/src/react/generations/store.ts
In addGenerationRunner, deduplicate incoming generations by id: build a set of incoming ids, filter existing generations to remove any with those ids, then append incoming generations to avoid duplicates.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant UI
  participant Runner as GenerateContentRunner
  participant API as GenerationService
  participant Stream as StreamProcessor
  participant Store as GenerationStore

  UI->>Runner: startGeneration()
  activate Runner
  Runner->>API: initiateGeneration()
  Note right of Runner #f9f0c1: set didPerformingContentGeneration
  API-->>Runner: generation (status: pending)
  Note right of Runner #e6f7ff: await status === "running"
  API-->>Runner: generation (status: running)
  Runner->>Stream: processStream(generation)
  Note right of Stream #e6f7ff: set didListeningContentGeneration
  Stream-->>Runner: chunks/events
  Runner->>Store: apply streamed updates
  deactivate Runner
Loading
sequenceDiagram
  autonumber
  participant Hook as use-node-generations
  participant SWR as useSWR
  participant Store as GenerationStore

  Hook->>SWR: fetch generations
  SWR-->>Hook: { data, isLoading }
  alt data available and not loading
    Hook->>Store: addGenerationRunner(data)
  end
  Hook->>Hook: currentGeneration = filter(!cancelled & node/origin) -> sort(createdAt desc) -> pick first
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • shige

Poem

"I thump my paws and tap the key,
Two phases hum — begin, then see.
Hooks gather, IDs pruned with care,
Streams spill bits into the air.
A rabbit cheers the runner's dare. 🥕"

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The provided description does not follow the repository’s required template: it uses custom headings like “User description” and “PR Type” instead of the mandated “## Summary,” “## Related Issue,” “## Changes,” “## Testing,” and “## Other Information” sections, leaving several required fields absent or unclear. Please restructure the pull request description to match the repository template by including a clear “## Summary” of the changes, linking any related issue under “## Related Issue,” listing the specific modifications under “## Changes,” describing how the changes were tested in “## Testing,” and adding any additional context in “## Other Information.”
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly identifies the key enhancements—adding runner syncing, simplifying generation selection, and improving stream handling—in a concise single sentence that directly reflects the main changes in the pull request. It avoids unnecessary detail or vague language and will make it easy for teammates to understand the primary purpose at a glance.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-runner-gen-selection

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
🧪 Early access (Sonnet 4.5): enabled

We are currently testing the Sonnet 4.5 model, which is expected to improve code review quality. However, this model may lead to increased noise levels in the review comments. Please disable the early access features if the noise level causes any inconvenience.

Note:

  • Public repositories are always opted into early access features.
  • You can enable or disable early access features from the CodeRabbit UI or by updating the CodeRabbit configuration file.

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

Copy link

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Debug Logging

A console.log call remains in the sync effect; consider removing or guarding it to avoid noisy logs in production.

	console.log(data);
	addGenerationRunner(data);
}, [isLoading, data, addGenerationRunner]);
Sync Semantics

The sync effect pushes fetched generations into the runner store via addGenerationRunner; confirm this is the intended store for remote state and that repeated fetches won't cause ordering issues despite deduplication.

useEffect(() => {
	if (isLoading || data === undefined) {
		return;
	}
	console.log(data);
	addGenerationRunner(data);
}, [isLoading, data, addGenerationRunner]);
Effect Guards

didPerforming/didListening refs are never reset; verify they won’t block handling when the same component instance receives a different generation or status transitions back.

	if (didPerformingContentGeneration.current) {
		return;
	}
	if (generation.status !== "queued") {
		return;
	}
	didPerformingContentGeneration.current = true;
	client
		.startContentGeneration({ generation })
		.then(({ generation: runningGeneration }) => {
			onStart?.(runningGeneration);
			updateGeneration(runningGeneration);
		});
}, [generation, client, updateGeneration, onStart]);

useEffect(() => {
	if (didListeningContentGeneration.current) {
		return;
	}
	if (generation.status !== "running") {
		return;
	}
	didListeningContentGeneration.current = true;

	processStream();
}, [generation, processStream]);

Copy link

giselles-ai bot commented Sep 30, 2025

🔍 QA Testing Assistant by Giselle

📋 Manual QA Checklist

Based on the changes in this PR, here are the key areas to test manually:

  • 1. Basic Content Generation: Navigate to a page where you can generate content, click to start, verify the UI enters a "generating" state, observe text streaming, and confirm the final content is displayed correctly.
  • 2. Syncing on Initial Page Load: Navigate to a page with a known completed generation, verify the page loads and automatically displays the content of the most recent completed generation without user interaction.
  • 3. Displaying Latest Generation After New Generation: Generate content, wait for completion, generate again on the same page, verify the second generation's streaming content replaces the first, and after refreshing, confirm the second generation's content is displayed.
  • 4. Syncing State Across Different Browser Tabs: Open the same page in two tabs, start and complete a generation in Tab A, switch to Tab B and refresh, verify Tab B displays the new content from Tab A.

✨ Prompt for AI Agents

Use the following prompts with Cursor or Claude Code to automate E2E testing:

📝 E2E Test Generation Prompt

```

You are an expert QA engineer. Your task is to write a comprehensive E2E test suite using Playwright for a Pull Request that introduces significant changes to a content generation feature.

The key changes involve:

  1. Runner Syncing: The application now fetches the state of content generations from a remote server on load and syncs it with the local UI state. This ensures that if a user reloads the page, they will see the correct state of any in-progress or completed generations.
  2. Simplified Generation Selection: The logic to determine which generation is the "current" one for a given context has been simplified. It now relies on a single, synced source of truth in the local state store.
  3. Refactored Stream Handling: The internal logic for how a generation task transitions from queued to running and starts processing the content stream has been changed to a two-step process, making it more robust.

Your goal is to create a new Playwright test file, generation-syncing.spec.ts, that validates these changes thoroughly.


1. Context Summary

  • PR Goal: To make the content generation experience more robust and stateful by syncing local state with a remote server. This prevents the UI from "forgetting" about generations when the page is reloaded.
  • User Flow Affected: The primary user flow is initiating a content generation, observing the content being streamed into the UI, and seeing the final result. The change critically impacts what the user sees when they first load or reload a page that has an associated generation.
  • Critical Paths:
    1. Initial Load Syncing: A user loads a page where a generation was previously started. The test must verify that the state of that generation (e.g., running or completed) is correctly fetched and displayed.
    2. Live Generation & Reload: A user starts a new generation, sees it streaming, reloads the page, and the UI correctly reflects the running state and continues to update.
    3. Generation Selection: When multiple generations exist for a context (e.g., some are cancelled, some are completed), the UI must correctly display the most recent, non-cancelled one.
    4. State Transition: The queued -> running state transition and the subsequent stream processing must function correctly after the refactor.

2. Test Scenarios

Create tests for the following scenarios within a test.describe('Content Generation Syncing and State Management', () => { ... }); block.

Key Requirement: You must use Playwright's page.route() to mock API endpoints. This is essential for controlling the "remote state" that the application will sync with. The primary endpoint to mock seems to be a GET request related to node-generations.

  • Scenario 1: Happy Path - Syncing a Completed Generation on Load

    • Description: Test that when a user loads a page, the app fetches and correctly displays a generation that was already completed.
    • Steps:
      1. Mock the /node-generations API endpoint to return a single, completed generation.
      2. Navigate to the relevant page.
      3. Assert that the final content of the completed generation is visible in the UI.
      4. Assert that the generation status indicator (if any) shows "completed" and no "generating" spinner is visible.
      5. Assert that the "Generate" button is enabled, allowing a new generation.
  • Scenario 2: Happy Path - Syncing a Running Generation on Load

    • Description: Test that if a generation was running on the server, the UI syncs to that state and begins listening for stream updates.
    • Steps:
      1. Mock the /node-generations endpoint to return a single generation with status running.
      2. Mock the streaming endpoint (e.g., /generations/{id}/stream) to provide a few chunks of text and then complete.
      3. Navigate to the page.
      4. Assert that a "generating" indicator/spinner is visible.
      5. Assert that the streamed text chunks appear progressively in the output area.
      6. Assert that the generation completes and the final content is displayed.
  • Scenario 3: Happy Path - Starting a New Generation and Verifying State Transition

    • Description: Test the complete flow from a user click, verifying the new queued -> running state transition.
    • Steps:
      1. Mock the /node-generations endpoint to return an empty array [].
      2. Mock the POST endpoint for startContentGeneration to return a generation with status running.
      3. Mock the stream endpoint.
      4. Navigate to the page.
      5. Click the "Generate" button.
      6. Assert that a generation request is sent.
      7. Assert that the UI enters a running state (e.g., spinner appears, button is disabled).
      8. Assert that content is streamed into the output area correctly.
  • Scenario 4: Edge Case - Correct Generation Selection

    • Description: Verify the simplified selection logic. When the API returns multiple generations, the UI should only render the most recent, non-cancelled one.
    • Steps:
      1. Mock the /node-generations endpoint to return an array of generations for the same node:
        • One old, completed generation.
        • One cancelled generation.
        • One newer, completed generation (this is the one that should be displayed).
      2. Navigate to the page.
      3. Assert that the content of the newest completed generation is displayed.
      4. Assert that the content from the older or cancelled generations is not visible.
  • Scenario 5: Regression - Page Reload During Generation

    • Description: This combines Scenarios 2 and 3. A user starts a generation and reloads the page mid-stream. The app should re-sync and continue.
    • Steps:
      1. Setup mocks for starting and streaming a generation.
      2. Click "Generate". Wait for the first chunk of text to appear.
      3. Reload the page using await page.reload().
      4. Crucially, update the mock for /node-generations before the reload to return the now running generation. You can use a variable to hold the state.
      5. After reload, assert the UI is in a running state.
      6. Assert the stream continues and the generation completes.

3. Playwright Implementation Instructions

  • Selectors: Since no specific selectors are provided, use attribute or role-based selectors that are robust. Assume the following exist and use them. If they are not suitable, use functionally equivalent selectors.

    • Generate Button: page.getByRole('button', { name: 'Generate' })
    • Generation Output Area: page.locator('[data-testid="generation-output-area"]')
    • Generating Spinner: page.locator('[data-testid="generation-spinner"]')
  • User Interactions:

    • Navigation: await page.goto('/path-to-your-feature');
    • Clicks: await page.getByRole('button', { name: 'Generate' }).click();
    • Reload: await page.reload();
  • Assertions:

    • Verify visibility: await expect(locator).toBeVisible();
    • Verify text content: await expect(locator).toContainText('expected content');
    • Verify absence: await expect(locator).not.toBeVisible(); or await expect(locator).toBeHidden();
    • Use page.waitForResponse or page.waitForRequest to ensure API calls are made.
  • Data Setup & Mocking (Most Important Part):

    • Use test.beforeEach to set up common mocks.
    • Use page.route(url, handler) to mock API calls.
    • Example for mocking the initial load:
    // In your test file
    import { test, expect } from '@playwright/test';
    
    // Example Generation object structure (adapt based on actual types)
    const MOCK_COMPLETED_GENERATION = {
      id: 'gen-123',
      status: 'completed',
      createdAt: new Date().toISOString(),
      context: { /* ... */ },
      messages: [{ role: 'assistant', content: 'This is the final completed content.' }]
    };
    
    test('should sync a completed generation on load', async ({ page }) => {
      // Mock the API to return the completed generation
      await page.route('**/api/v1/node-generations?**', async (route) => {
        await route.fulfill({
          status: 200,
          contentType: 'application/json',
          json: [MOCK_COMPLETED_GENERATION],
        });
      });
    
      await page.goto('/your-page-with-generations');
    
      const outputArea = page.locator('[data-testid="generation-output-area"]');
      await expect(outputArea).toContainText('This is the final completed content.');
      await expect(page.locator('[data-testid="generation-spinner"]')).toBeHidden();
    });

4. MCP Integration Guidelines (Optional)

  • This section is informational for the context of a full CI pipeline.
  • Command Structure: The tests should be executable via a standard Playwright command, which Playwright MCP would wrap. Example: mcp playwright test --headed or mcp playwright test generation-syncing.spec.ts.
  • Environment Configuration: Assume environment variables like API_BASE_URL might be used in a real scenario, but for these tests, rely on mocking with relative paths (**/api/v1/...) to make them environment-agnostic.

5. CI-Ready Code Requirements

  • Test Organization:
    • Create a new file named generation-syncing.spec.ts.
    • Use test.describe() to group all related tests as suggested above.
    • Use test.beforeEach to handle repetitive setup like navigation and initial API mocking.
  • Naming Conventions:
    • Test titles should be descriptive, e.g., test('should sync a running generation after a page reload', ...).
  • Atomicity and Independence:
    • Each test should be self-contained and not depend on the state from a previous test. All necessary state should be established within the test or its beforeEach hook (e.g., via API mocks). This is critical for parallel execution.
  • Error Handling:
    • Playwright's expect assertions handle most failures. No special try/catch blocks are needed unless you are performing complex cleanup outside of what afterEach can provide. Use web-first assertions to avoid flakiness.

-->

```

---

Copy link

qodo-merge-for-open-source bot commented Sep 30, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Re-evaluate the state synchronization strategy

The current state synchronization strategy overwrites local data, which can
cause race conditions. A more robust merge strategy, like comparing timestamps,
should be implemented to prevent losing optimistic local updates to stale server
data.

Examples:

packages/giselle/src/react/generations/store.ts [27-33]
			const incomingIds = new Set(arr.map((g) => g.id));
			const filteredExisting = state.generations.filter(
				(g) => !incomingIds.has(g.id),
			);
			return {
				generations: [...filteredExisting, ...arr],
			};
packages/giselle/src/react/generations/hooks/use-node-generations.ts [54-60]
	useEffect(() => {
		if (isLoading || data === undefined) {
			return;
		}
		console.log(data);
		addGenerationRunner(data);
	}, [isLoading, data, addGenerationRunner]);

Solution Walkthrough:

Before:

// packages/giselle/src/react/generations/store.ts
addGenerationRunner: (generations) =>
  set((state) => {
    const arr = Array.isArray(generations) ? generations : [generations];
    const incomingIds = new Set(arr.map((g) => g.id));
    // Remove any existing generation that has a matching incoming ID
    const filteredExisting = state.generations.filter(
      (g) => !incomingIds.has(g.id),
    );
    // Add all incoming generations, effectively replacing existing ones
    return {
      generations: [...filteredExisting, ...arr],
    };
  }),

After:

// packages/giselle/src/react/generations/store.ts
addGenerationRunner: (generations) =>
  set((state) => {
    const generationsMap = new Map(state.generations.map(g => [g.id, g]));
    const incoming = Array.isArray(generations) ? generations : [generations];

    for (const incomingGen of incoming) {
      const existingGen = generationsMap.get(incomingGen.id);
      // Only update if incoming is newer or no local version exists
      if (!existingGen || new Date(incomingGen.updatedAt) > new Date(existingGen.updatedAt)) {
        generationsMap.set(incomingGen.id, incomingGen);
      }
    }

    return {
      generations: Array.from(generationsMap.values()),
    };
  }),
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical race condition in the new state synchronization logic, where optimistic local updates can be overwritten by stale data from the server, potentially breaking the generation process.

High
Possible issue
Avoid overwriting existing generation state

Modify addGenerationRunner to only add new generations that are not already in
the store, instead of replacing existing ones. This preserves the state of
active generations and prevents them from being overwritten by older server
data.

packages/giselle/src/react/generations/store.ts [24-34]

 	addGenerationRunner: (generations) =>
 		set((state) => {
 			const arr = Array.isArray(generations) ? generations : [generations];
-			const incomingIds = new Set(arr.map((g) => g.id));
-			const filteredExisting = state.generations.filter(
-				(g) => !incomingIds.has(g.id),
-			);
+			const existingIds = new Set(state.generations.map((g) => g.id));
+			const newGenerations = arr.filter((g) => !existingIds.has(g.id));
+
+			if (newGenerations.length === 0) {
+				return state;
+			}
+
 			return {
-				generations: [...filteredExisting, ...arr],
+				generations: [...state.generations, ...newGenerations],
 			};
 		}),
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a critical issue where the current implementation of addGenerationRunner overwrites existing generations, potentially causing loss of state for active, streaming generations. The proposed fix correctly preserves the state of existing generations while adding only new ones.

Medium
Refine effect dependencies for stability

Refine the useEffect dependency arrays to use specific properties of the
generation object, such as generation.id and generation.status, instead of the
entire object reference to prevent unnecessary re-renders.

packages/giselle/src/react/generations/generate-content-runner.tsx [163-189]

 	useEffect(() => {
 		if (didPerformingContentGeneration.current) {
 			return;
 		}
 		if (generation.status !== "queued") {
 			return;
 		}
 		didPerformingContentGeneration.current = true;
 		client
 			.startContentGeneration({ generation })
 			.then(({ generation: runningGeneration }) => {
 				onStart?.(runningGeneration);
 				updateGeneration(runningGeneration);
 			});
-	}, [generation, client, updateGeneration, onStart]);
+	}, [generation.id, generation.status, client, updateGeneration, onStart]);
 
 	useEffect(() => {
 		if (didListeningContentGeneration.current) {
 			return;
 		}
 		if (generation.status !== "running") {
 			return;
 		}
 		didListeningContentGeneration.current = true;
 
 		processStream();
-	}, [generation, processStream]);
+	}, [generation.id, generation.status, processStream]);
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a potential for unnecessary re-runs of the useEffect hooks by depending on the generation object reference. Using specific, stable properties like generation.id and generation.status is a React best practice that improves stability and performance.

Low
Learned
best practice
Remove noisy console logging
Suggestion Impact:The commit removed the console.log(data) call, reducing noisy debug output as suggested.

code diff:

@@ -55,7 +55,6 @@
 		if (isLoading || data === undefined) {
 			return;
 		}
-		console.log(data);
 		addGenerationRunner(data);

Remove the raw console.log and, if needed, gate diagnostic logging behind a
feature flag or use proper debug-level logging to avoid noisy output and
potential data leakage.

packages/giselle/src/react/generations/hooks/use-node-generations.ts [54-60]

 useEffect(() => {
 	if (isLoading || data === undefined) {
 		return;
 	}
-	console.log(data);
 	addGenerationRunner(data);
 }, [isLoading, data, addGenerationRunner]);

[Suggestion processed]

Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Remove debug/noisy logs and avoid leaking sensitive info; keep necessary errors and use proper logging levels or feature flags.

Low
  • Update

cursor[bot]

This comment was marked as outdated.

@shige
Copy link
Member

shige commented Sep 30, 2025

@coderabbitai review

@shige
Copy link
Member

shige commented Sep 30, 2025

@codex review

Copy link
Contributor

coderabbitai bot commented Sep 30, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

🧹 Nitpick comments (1)
packages/giselle/src/react/generations/generate-content-runner.tsx (1)

64-66: Consider resetting refs when generation.id changes.

The refs didPerformingContentGeneration and didListeningContentGeneration persist across renders. If the same component instance is reused for a new generation (different generation.id), the refs won't reset, preventing the new generation from starting or listening.

Add an effect to reset the refs when generation.id changes:

 	const didPerformingContentGeneration = useRef(false);
 	const didListeningContentGeneration = useRef(false);
 	const reachedStreamEnd = useRef(false);
+
+	useEffect(() => {
+		didPerformingContentGeneration.current = false;
+		didListeningContentGeneration.current = false;
+		reachedStreamEnd.current = false;
+	}, [generation.id]);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d601558 and 3f8d894.

📒 Files selected for processing (3)
  • packages/giselle/src/react/generations/generate-content-runner.tsx (2 hunks)
  • packages/giselle/src/react/generations/hooks/use-node-generations.ts (3 hunks)
  • packages/giselle/src/react/generations/store.ts (1 hunks)
🔇 Additional comments (8)
packages/giselle/src/react/generations/store.ts (1)

24-34: LGTM! Deduplication logic is correct and efficient.

The Set-based deduplication prevents duplicate generations by ID when adding new generations to the store, making addGenerationRunner idempotent. The approach is clean and efficient.

packages/giselle/src/react/generations/hooks/use-node-generations.ts (5)

2-2: LGTM! useEffect import supports new sync effect.


39-39: LGTM! isLoading extraction guards the sync effect.


54-60: LGTM! Remote generation sync effect is correct.

The effect properly waits for data to load and syncs it into the local store via addGenerationRunner, preventing duplicates thanks to the store's deduplication logic.


62-80: LGTM! Simplified currentGeneration computation is clear and correct.

The new filter-and-sort approach is more straightforward than the previous implementation. The filtering logic correctly handles both studio and non-studio origins, and the dependencies are accurate.


58-58: Remove debug console.log statement.

This console.log exposes generation data in the browser console and should be removed before production.

Apply this diff:

-		console.log(data);
 		addGenerationRunner(data);
packages/giselle/src/react/generations/generate-content-runner.tsx (2)

64-65: LGTM! Separate refs for two-phase lifecycle are clear.

Using distinct refs for the start and listen phases prevents duplicate execution and makes the intent explicit.


163-177: LGTM! Start generation effect is correct.

The effect correctly initiates content generation when the generation is queued, and the ref guard prevents duplicate starts.

Copy link

Codex Review: Didn't find any major issues. You're on a roll.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

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

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

Codex can also answer questions or update the PR. Try commenting

Copy link
Member

@shige shige left a comment

Choose a reason for hiding this comment

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

Thank you! 🤾

Base automatically changed from feat-add-inngest-middleware-expose-external to main September 30, 2025 00:45
@Copilot Copilot AI review requested due to automatic review settings September 30, 2025 03:45
@toyamarinyon toyamarinyon force-pushed the feat-runner-gen-selection branch from 3f8d894 to 745fbbe Compare September 30, 2025 03:45
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR enhances the generation system by adding runner syncing to maintain consistency between local and remote state, simplifying the generation selection logic for better predictability, and separating stream handling into distinct lifecycle phases to improve reliability.

  • Add runner syncing to synchronize remote generations with local store using deduplication
  • Simplify generation selection by filtering directly from store instead of complex merging logic
  • Separate generation lifecycle into distinct start and stream listening phases

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
store.ts Adds deduplication logic to prevent duplicate generations when syncing
use-node-generations.ts Implements runner syncing and simplifies generation selection logic
generate-content-runner.tsx Separates generation start from stream handling using distinct lifecycle phases

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

return allGenerations[0];
}, [generations, data, nodeId, origin]);
return filteredGenerations[0];
}, [generations, nodeId, origin]);
Copy link
Preview

Copilot AI Sep 30, 2025

Choose a reason for hiding this comment

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

The dependency array is missing data which is used indirectly through the syncing effect. Consider adding data to ensure the memo recalculates when remote data changes, or document why it's intentionally excluded.

Suggested change
}, [generations, nodeId, origin]);
}, [generations, nodeId, origin, data]);

Copilot uses AI. Check for mistakes.

cursor[bot]

This comment was marked as outdated.

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

♻️ Duplicate comments (1)
packages/giselle/src/react/generations/generate-content-runner.tsx (1)

179-189: Verify processStream idempotency and concurrent execution safety.

The listen effect correctly waits for "running" status before calling processStream. However, ensure that:

  1. processStream itself is safe if called multiple times (e.g., if React strict mode double-invokes effects).
  2. The reachedStreamEnd ref is also reset when generation.id changes, otherwise a new generation won't process its stream.

Run the following script to check if reachedStreamEnd is reset anywhere:

#!/bin/bash
# Description: Check if reachedStreamEnd ref is reset on generation.id change.

rg -nP 'reachedStreamEnd\.current\s*=\s*false' packages/giselle/src/react/generations/generate-content-runner.tsx
🧹 Nitpick comments (1)
packages/giselle/src/react/generations/hooks/use-node-generations.ts (1)

61-79: Simplify filter logic to avoid redundant checks.

The filter on lines 62-73 checks generation.context.operationNode.id === nodeId and generation.context.origin.type === origin.type, but the generations array (line 21) is already filtered by nodeId. This redundant nodeId check adds unnecessary overhead.

Apply this diff to remove the redundant nodeId check:

 	const currentGeneration = useMemo<Generation>(() => {
 		const filteredGenerations = generations
 			.filter(
 				(generation) =>
 					generation.status !== "cancelled" &&
-					generation.context.operationNode.id === nodeId &&
 					generation.context.origin.type === origin.type &&
 					(origin.type === "studio"
 						? generation.context.origin.type === "studio" &&
 							generation.context.origin.workspaceId === origin.workspaceId
 						: generation.context.origin.type !== "studio" &&
 							generation.context.origin.actId === origin.actId),
 			)
 			.sort(
 				(a, b) =>
 					new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
 			);
 		return filteredGenerations[0];
-	}, [generations, nodeId, origin]);
+	}, [generations, origin]);

Note: The nodeId dependency can also be removed from the useMemo dependencies since it's no longer used in the filter.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f8d894 and 745fbbe.

📒 Files selected for processing (3)
  • packages/giselle/src/react/generations/generate-content-runner.tsx (2 hunks)
  • packages/giselle/src/react/generations/hooks/use-node-generations.ts (3 hunks)
  • packages/giselle/src/react/generations/store.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/giselle/src/react/generations/store.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development-guide.mdc)

**/*.{ts,tsx}: Use Biome for formatting with tab indentation and double quotes
Follow organized imports pattern (enabled in biome.json)
Use TypeScript for type safety; avoid any types
Use Next.js patterns for web applications
Use async/await for asynchronous code rather than promises
Error handling: use try/catch blocks and propagate errors appropriately
Use kebab-case for all filenames (e.g., user-profile.ts)
Use camelCase for variables, functions, and methods (e.g., userEmail)
Use prefixes like is, has, can, should for boolean variables and functions for clarity
Use verbs or verb phrases that clearly indicate purpose for function naming (e.g., calculateTotalPrice(), not process())

If breaking changes are introduced in new AI SDK versions, update code to accommodate those changes

**/*.{ts,tsx}: Use TypeScript and avoid any
Use async/await and proper error handling
Variables and functions should use camelCase
Boolean variables and functions should use is/has/can/should prefixes
Function names should clearly indicate purpose

Files:

  • packages/giselle/src/react/generations/generate-content-runner.tsx
  • packages/giselle/src/react/generations/hooks/use-node-generations.ts
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/development-guide.mdc)

**/*.tsx: Use functional components with React hooks
Use PascalCase for React components and classes (e.g., UserProfile)

**/*.tsx: Components should use React hooks and Next.js patterns
Component identifiers should use PascalCase

Files:

  • packages/giselle/src/react/generations/generate-content-runner.tsx
**/*

📄 CodeRabbit inference engine (.cursor/rules/naming-guide.mdc)

All filenames should use kebab-case (lowercase with hyphens)

Files:

  • packages/giselle/src/react/generations/generate-content-runner.tsx
  • packages/giselle/src/react/generations/hooks/use-node-generations.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/naming-guide.mdc)

**/*.{js,jsx,ts,tsx}: React components and classes should use PascalCase
Variables, functions, and methods should use camelCase
Use verbs or verb phrases for function names; names should clearly indicate what the function does; avoid ambiguous names that could lead to misuse
Use nouns or noun phrases for variable names; names should describe what the variable represents; avoid single-letter variables except in very short scopes
Use prefixes like 'is', 'has', 'can', 'should' for both variables and functions returning boolean values; make the true/false meaning clear

Files:

  • packages/giselle/src/react/generations/generate-content-runner.tsx
  • packages/giselle/src/react/generations/hooks/use-node-generations.ts
🧬 Code graph analysis (1)
packages/giselle/src/react/generations/hooks/use-node-generations.ts (3)
packages/giselle/src/react/generations/contexts/generation-runner-system.tsx (1)
  • useGenerationRunnerSystem (79-87)
packages/giselle/src/react/use-giselle-engine.ts (1)
  • useGiselleEngine (86-168)
packages/giselle/src/react/feature-flags/context.ts (1)
  • useFeatureFlag (16-24)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (3)
packages/giselle/src/react/generations/generate-content-runner.tsx (1)

64-65: LGTM! Two-phase refs correctly separate start and listen.

The separate refs clearly distinguish the start phase (queued → running) from the listen phase (running → stream processing).

packages/giselle/src/react/generations/hooks/use-node-generations.ts (2)

54-59: LGTM! Sync effect correctly registers remote generations.

The effect properly waits for data to load and then syncs fetched generations into the store via addGenerationRunner. The dependencies are correct.


61-79: No changes required—‘stage’ and ‘github-app’ origins both define a required actId, so filtering on actId correctly covers all non-studio cases.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/giselle/src/react/generations/generate-content-runner.tsx (1)

171-185: Consider more granular dependencies for the start effect.

The effect depends on the entire generation object, which may cause re-runs even when only non-status properties change. While the didPerformingContentGeneration guard prevents duplicate starts, a more granular dependency would be clearer:

-	}, [generation, client, updateGeneration, onStart]);
+	}, [generation.id, generation.status, client, updateGeneration, onStart]);

This makes the intent explicit: restart the effect only when ID or status changes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 745fbbe and 880304c.

📒 Files selected for processing (1)
  • packages/giselle/src/react/generations/generate-content-runner.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development-guide.mdc)

**/*.{ts,tsx}: Use Biome for formatting with tab indentation and double quotes
Follow organized imports pattern (enabled in biome.json)
Use TypeScript for type safety; avoid any types
Use Next.js patterns for web applications
Use async/await for asynchronous code rather than promises
Error handling: use try/catch blocks and propagate errors appropriately
Use kebab-case for all filenames (e.g., user-profile.ts)
Use camelCase for variables, functions, and methods (e.g., userEmail)
Use prefixes like is, has, can, should for boolean variables and functions for clarity
Use verbs or verb phrases that clearly indicate purpose for function naming (e.g., calculateTotalPrice(), not process())

If breaking changes are introduced in new AI SDK versions, update code to accommodate those changes

**/*.{ts,tsx}: Use TypeScript and avoid any
Use async/await and proper error handling
Variables and functions should use camelCase
Boolean variables and functions should use is/has/can/should prefixes
Function names should clearly indicate purpose

Files:

  • packages/giselle/src/react/generations/generate-content-runner.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/development-guide.mdc)

**/*.tsx: Use functional components with React hooks
Use PascalCase for React components and classes (e.g., UserProfile)

**/*.tsx: Components should use React hooks and Next.js patterns
Component identifiers should use PascalCase

Files:

  • packages/giselle/src/react/generations/generate-content-runner.tsx
**/*

📄 CodeRabbit inference engine (.cursor/rules/naming-guide.mdc)

All filenames should use kebab-case (lowercase with hyphens)

Files:

  • packages/giselle/src/react/generations/generate-content-runner.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/naming-guide.mdc)

**/*.{js,jsx,ts,tsx}: React components and classes should use PascalCase
Variables, functions, and methods should use camelCase
Use verbs or verb phrases for function names; names should clearly indicate what the function does; avoid ambiguous names that could lead to misuse
Use nouns or noun phrases for variable names; names should describe what the variable represents; avoid single-letter variables except in very short scopes
Use prefixes like 'is', 'has', 'can', 'should' for both variables and functions returning boolean values; make the true/false meaning clear

Files:

  • packages/giselle/src/react/generations/generate-content-runner.tsx
⏰ 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). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (2)
packages/giselle/src/react/generations/generate-content-runner.tsx (2)

70-76: Lifecycle reset correctly addresses past review concerns.

The reset effect properly clears all lifecycle guards when generation.id changes, ensuring new generations can start and process streams. The exhaustive dependencies suppression is justified since resetting should only occur on ID changes, not on other generation property updates.


187-197: Parent subscription to generation updates is correct via useGenerationStore selectors.

cursor[bot]

This comment was marked as outdated.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/giselle/src/react/generations/generate-content-runner.tsx (1)

188-193: Add error handling for startContentGeneration.

The promise from startContentGeneration lacks error handling. If it rejects, this could result in an unhandled promise rejection and the generation would be stuck in "queued" status without user feedback.

Apply this diff to handle errors:

-		client
-			.startContentGeneration({ generation })
-			.then(({ generation: runningGeneration }) => {
-				onStart?.(runningGeneration);
-				updateGeneration(runningGeneration);
-			});
+		client
+			.startContentGeneration({ generation })
+			.then(({ generation: runningGeneration }) => {
+				onStart?.(runningGeneration);
+				updateGeneration(runningGeneration);
+			})
+			.catch((error) => {
+				console.error("Failed to start content generation:", error);
+				onError?.(error);
+			});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 880304c and d4a61c3.

📒 Files selected for processing (1)
  • packages/giselle/src/react/generations/generate-content-runner.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development-guide.mdc)

**/*.{ts,tsx}: Use Biome for formatting with tab indentation and double quotes
Follow organized imports pattern (enabled in biome.json)
Use TypeScript for type safety; avoid any types
Use Next.js patterns for web applications
Use async/await for asynchronous code rather than promises
Error handling: use try/catch blocks and propagate errors appropriately
Use kebab-case for all filenames (e.g., user-profile.ts)
Use camelCase for variables, functions, and methods (e.g., userEmail)
Use prefixes like is, has, can, should for boolean variables and functions for clarity
Use verbs or verb phrases that clearly indicate purpose for function naming (e.g., calculateTotalPrice(), not process())

If breaking changes are introduced in new AI SDK versions, update code to accommodate those changes

**/*.{ts,tsx}: Use TypeScript and avoid any
Use async/await and proper error handling
Variables and functions should use camelCase
Boolean variables and functions should use is/has/can/should prefixes
Function names should clearly indicate purpose

Files:

  • packages/giselle/src/react/generations/generate-content-runner.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/development-guide.mdc)

**/*.tsx: Use functional components with React hooks
Use PascalCase for React components and classes (e.g., UserProfile)

**/*.tsx: Components should use React hooks and Next.js patterns
Component identifiers should use PascalCase

Files:

  • packages/giselle/src/react/generations/generate-content-runner.tsx
**/*

📄 CodeRabbit inference engine (.cursor/rules/naming-guide.mdc)

All filenames should use kebab-case (lowercase with hyphens)

Files:

  • packages/giselle/src/react/generations/generate-content-runner.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/naming-guide.mdc)

**/*.{js,jsx,ts,tsx}: React components and classes should use PascalCase
Variables, functions, and methods should use camelCase
Use verbs or verb phrases for function names; names should clearly indicate what the function does; avoid ambiguous names that could lead to misuse
Use nouns or noun phrases for variable names; names should describe what the variable represents; avoid single-letter variables except in very short scopes
Use prefixes like 'is', 'has', 'can', 'should' for both variables and functions returning boolean values; make the true/false meaning clear

Files:

  • packages/giselle/src/react/generations/generate-content-runner.tsx
⏰ 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). (2)
  • GitHub Check: codeql / languages (javascript) / Perform CodeQL for javascript
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (4)
packages/giselle/src/react/generations/generate-content-runner.tsx (4)

64-65: LGTM! Clear separation of lifecycle phases.

The two separate refs effectively guard the start and listen phases independently, making the lifecycle explicit and easier to reason about.


70-85: LGTM! Comprehensive cleanup on generation switch.

The reset effect correctly clears all stateful refs and pending operations when the generation ID changes, preventing stale state from interfering with the new generation lifecycle.


180-194: LGTM! Start phase correctly initiates generation.

The effect properly guards against duplicate execution and only initiates content generation for queued generations. The dependency on the entire generation object ensures the effect reacts to status changes while the ref guard prevents re-execution.


196-206: LGTM! Listen phase correctly starts stream processing.

The effect properly waits for "running" status before initiating stream processing. Calling processStream() without await is correct—it runs asynchronously without blocking the effect, and the guard ensures it's only initiated once per generation.

cancelAnimationFrame(pendingUpdate.current);
pendingUpdate.current = null;
}
}, [generation.id]);
Copy link

Choose a reason for hiding this comment

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

Bug: Stale State Causes Incorrect Reset Logic

The useEffect responsible for resetting generation state doesn't update prevGenerationId.current after a generation change. This causes the reset logic to either run on every subsequent render or incorrectly skip when a generation ID is reused. As a result, lifecycle flags and message queues can remain stale, which may prevent generations from running as expected.

Fix in Cursor Fix in Web

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

🧹 Nitpick comments (2)
packages/giselle/src/react/generations/generate-content-runner.tsx (2)

183-197: Consider refining dependency array for efficiency.

The effect depends on the entire generation object, causing it to re-run on any property change. Since the guard prevents duplicate starts, this is safe but potentially inefficient.

Consider this refinement to run only on relevant changes:

-	}, [generation, client, updateGeneration, onStart]);
+	}, [generation.id, generation.status, client, updateGeneration, onStart]);

This limits re-runs to when the ID or status changes, reducing unnecessary effect executions.


199-209: Refine dependencies to avoid relying on effect execution order.

The effect depends on the entire generation object, which may re-run more often than necessary. More importantly, the reset effect (line 72) and this listen effect both depend on generation.id changes, but React doesn't guarantee effect execution order. While the current code order likely works, it's fragile.

Simplify the dependency array to rely only on processStream, which already tracks generation.id:

-	}, [generation, processStream]);
+	}, [generation.status, processStream]);

This makes the dependency more explicit (we only care about status for the guard check) and removes the implicit reliance on effect ordering for the reset logic to work correctly. The processStream callback already depends on generation.id, so ID changes will still trigger this effect via the recreated callback.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d4a61c3 and 59152a7.

📒 Files selected for processing (1)
  • packages/giselle/src/react/generations/generate-content-runner.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development-guide.mdc)

**/*.{ts,tsx}: Use Biome for formatting with tab indentation and double quotes
Follow organized imports pattern (enabled in biome.json)
Use TypeScript for type safety; avoid any types
Use Next.js patterns for web applications
Use async/await for asynchronous code rather than promises
Error handling: use try/catch blocks and propagate errors appropriately
Use kebab-case for all filenames (e.g., user-profile.ts)
Use camelCase for variables, functions, and methods (e.g., userEmail)
Use prefixes like is, has, can, should for boolean variables and functions for clarity
Use verbs or verb phrases that clearly indicate purpose for function naming (e.g., calculateTotalPrice(), not process())

If breaking changes are introduced in new AI SDK versions, update code to accommodate those changes

**/*.{ts,tsx}: Use TypeScript and avoid any
Use async/await and proper error handling
Variables and functions should use camelCase
Boolean variables and functions should use is/has/can/should prefixes
Function names should clearly indicate purpose

Files:

  • packages/giselle/src/react/generations/generate-content-runner.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/development-guide.mdc)

**/*.tsx: Use functional components with React hooks
Use PascalCase for React components and classes (e.g., UserProfile)

**/*.tsx: Components should use React hooks and Next.js patterns
Component identifiers should use PascalCase

Files:

  • packages/giselle/src/react/generations/generate-content-runner.tsx
**/*

📄 CodeRabbit inference engine (.cursor/rules/naming-guide.mdc)

All filenames should use kebab-case (lowercase with hyphens)

Files:

  • packages/giselle/src/react/generations/generate-content-runner.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/naming-guide.mdc)

**/*.{js,jsx,ts,tsx}: React components and classes should use PascalCase
Variables, functions, and methods should use camelCase
Use verbs or verb phrases for function names; names should clearly indicate what the function does; avoid ambiguous names that could lead to misuse
Use nouns or noun phrases for variable names; names should describe what the variable represents; avoid single-letter variables except in very short scopes
Use prefixes like 'is', 'has', 'can', 'should' for both variables and functions returning boolean values; make the true/false meaning clear

Files:

  • packages/giselle/src/react/generations/generate-content-runner.tsx
⏰ 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). (3)
  • GitHub Check: codeql / languages (javascript) / Perform CodeQL for javascript
  • GitHub Check: Cursor Bugbot
  • GitHub Check: check
🔇 Additional comments (1)
packages/giselle/src/react/generations/generate-content-runner.tsx (1)

64-65: LGTM! Clear two-phase lifecycle guards.

The separation of didPerformingContentGeneration and didListeningContentGeneration makes the distinct lifecycle phases explicit and easier to reason about.

Comment on lines +69 to +88
const prevGenerationId = useRef(generation.id);

// Reset lifecycle refs when generation changes
useEffect(() => {
if (prevGenerationId.current === generation.id) {
return;
}
didPerformingContentGeneration.current = false;
didListeningContentGeneration.current = false;
reachedStreamEnd.current = false;

// Clear message queue to prevent stale messages from previous generation
messageUpdateQueue.current.clear();

// Cancel pending animation frame to prevent applying stale updates
if (pendingUpdate.current !== null) {
cancelAnimationFrame(pendingUpdate.current);
pendingUpdate.current = null;
}
}, [generation.id]);
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

Update prevGenerationId after reset to complete the guard logic.

The effect correctly resets state when generation.id changes, but prevGenerationId.current is never updated to the new ID. This makes the early-return check ineffective and could cause redundant resets if React re-runs the effect.

Apply this diff to update the ref after the reset:

 	// Reset lifecycle refs when generation changes
 	useEffect(() => {
 		if (prevGenerationId.current === generation.id) {
 			return;
 		}
+		prevGenerationId.current = generation.id;
+
 		didPerformingContentGeneration.current = false;
 		didListeningContentGeneration.current = false;
 		reachedStreamEnd.current = false;
 
 		// Clear message queue to prevent stale messages from previous generation
 		messageUpdateQueue.current.clear();
 
 		// Cancel pending animation frame to prevent applying stale updates
 		if (pendingUpdate.current !== null) {
 			cancelAnimationFrame(pendingUpdate.current);
 			pendingUpdate.current = null;
 		}
 	}, [generation.id]);
📝 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 prevGenerationId = useRef(generation.id);
// Reset lifecycle refs when generation changes
useEffect(() => {
if (prevGenerationId.current === generation.id) {
return;
}
didPerformingContentGeneration.current = false;
didListeningContentGeneration.current = false;
reachedStreamEnd.current = false;
// Clear message queue to prevent stale messages from previous generation
messageUpdateQueue.current.clear();
// Cancel pending animation frame to prevent applying stale updates
if (pendingUpdate.current !== null) {
cancelAnimationFrame(pendingUpdate.current);
pendingUpdate.current = null;
}
}, [generation.id]);
const prevGenerationId = useRef(generation.id);
// Reset lifecycle refs when generation changes
useEffect(() => {
if (prevGenerationId.current === generation.id) {
return;
}
// Update the stored previous ID so the guard logic stays in sync
prevGenerationId.current = generation.id;
didPerformingContentGeneration.current = false;
didListeningContentGeneration.current = false;
reachedStreamEnd.current = false;
// Clear message queue to prevent stale messages from previous generation
messageUpdateQueue.current.clear();
// Cancel pending animation frame to prevent applying stale updates
if (pendingUpdate.current !== null) {
cancelAnimationFrame(pendingUpdate.current);
pendingUpdate.current = null;
}
}, [generation.id]);
🤖 Prompt for AI Agents
In packages/giselle/src/react/generations/generate-content-runner.tsx around
lines 69 to 88, the effect resets lifecycle refs when generation.id changes but
never updates prevGenerationId.current, so the early-return guard remains
ineffective; fix by setting prevGenerationId.current = generation.id after
performing the resets (and after cancelling any pending animation frame) so
future effect runs correctly detect the current generation and avoid redundant
resets.

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

Successfully merging this pull request may close these issues.

2 participants