Skip to content

Conversation

MH4GF
Copy link
Member

@MH4GF MH4GF commented Oct 3, 2025

Issue

Why is this change needed?

When users reload the page during workflow execution, the streaming is interrupted and doesn't automatically resume. This PR implements a reconnection mechanism to detect and resume interrupted workflows using sessionStorage.

replay.mov

What does this PR do?

New workflow reconnection logic

  1. Pure function for action determination (determineWorkflowAction)

    • Determines whether to start, replay, or do none
    • Priority: Already triggered → Workflow in progress → Single message → None
    • Fully tested with 9 test cases
  2. SessionStorage helpers (workflowStorage)

    • Key: liam:workflow:${designSessionId}
    • Value: 'in_progress' string flag
    • SSR-safe with window checks
  3. Integration with existing components

    • SessionDetailPageClient: Uses determination logic
    • useStream: Exports replay function, manages flags

Technical details

  • Uses LangGraph's existing /api/chat/replay endpoint
  • No new database tables or API endpoints needed
  • Session storage is tab-isolated (won't affect other tabs)
  • Pure function design for easy testing and maintenance

Test results

✓ components/SessionDetailPage/utils/determineWorkflowAction.test.ts (9 tests)
  Test Files  1 passed (1)
  Tests  9 passed (9)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Sessions automatically resume in-progress workflows on load (replay).
    • Workflows auto-start when there’s a single unanswered user message.
    • Manual replay action available for session streaming.
    • Per-session workflow state persisted so sessions remember in-progress workflows.
  • Bug Fixes

    • Streaming lifecycle reliably clears workflow state to prevent stuck sessions.
    • Navigation away during active streaming now stops the stream without prompting.
    • Improved error handling for streaming and workflow failures.
  • Tests

    • Added comprehensive unit tests covering workflow action decision logic.

Copy link

changeset-bot bot commented Oct 3, 2025

⚠️ No Changeset found

Latest commit: d5b74c9

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.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

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

Copy link
Contributor

coderabbitai bot commented Oct 3, 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

Reworks session workflow orchestration: adds sessionStorage helpers to track workflow-in-progress, introduces determineWorkflowAction to choose none/start/replay on load, extends useStream to expose replay and manage workflow flags across stream exit paths, and updates SessionDetailPageClient to invoke actions and unify error handling.

Changes

Cohort / File(s) Summary
Session flow orchestration
frontend/apps/app/components/SessionDetailPage/SessionDetailPageClient.tsx
Replaces first-message heuristic with determineWorkflowAction + getWorkflowInProgress; computes action and calls useStream.replay or start accordingly; adds replay to deps; removes direct isHumanMessage/first-item assumptions; merges stream and workflow errors.
Streaming hook updates
frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts
Adds replay to public return; introduces completeWorkflow and abortWorkflow flows; uses setWorkflowInProgress on start/replay and clearWorkflowInProgress on completion; renames payload→params and JSON.stringify(params); replaces finalize calls with abort/complete flows and updates effect deps.
Workflow decision utility + tests
frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.ts, frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.test.ts
New pure determineWorkflowAction(messages, isWorkflowInProgress, hasTriggered) returning `none
Workflow storage utility
frontend/apps/app/components/SessionDetailPage/utils/workflowStorage.ts
New sessionStorage-backed helpers: getWorkflowInProgress, setWorkflowInProgress, clearWorkflowInProgress; defines WORKFLOW_KEY_PREFIX and guards for non-browser environments.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant SessionPage as SessionDetailPageClient
  participant Store as workflowStorage (sessionStorage)
  participant Decide as determineWorkflowAction
  participant Stream as useStream

  User->>SessionPage: Load session (messages, designSessionId)
  SessionPage->>Store: getWorkflowInProgress(designSessionId)
  SessionPage->>Decide: determineWorkflowAction(messages, isInProgress, hasTriggered)
  Decide-->>SessionPage: Action (none | replay | start(userInput))

  alt Action == start
    SessionPage->>Store: setWorkflowInProgress(designSessionId)
    SessionPage->>Stream: start(designSessionId, userInput, isDeepModelingEnabled)
  else Action == replay
    SessionPage->>Store: setWorkflowInProgress(designSessionId)
    SessionPage->>Stream: replay(designSessionId, isDeepModelingEnabled)
  else Action == none
    Note over SessionPage: No-op
  end

  rect rgb(242,248,255)
    Note over Stream: Streaming lifecycle (isStreaming, messages, error)
    Stream->>Store: clearWorkflowInProgress(designSessionId) on complete/abort
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

Review effort 4/5

Suggested reviewers

  • NoritakaIkeda
  • junkisai
  • FunamaYukina

Poem

I nibble code and set the flag to go,
Replay or start — whichever winds do blow.
Session burrows tidy, streams hum light,
Flags set and cleared before the night.
A rabbit cheers: workflows hop in flight! 🐇

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly captures the main feature of this pull request by naming the added workflow reconnection logic specifically for page reload scenarios and aligns directly with the key changes to the session storage, stream hook, and page component. It is clear and concise without extraneous details, giving teammates a quick understanding of the primary enhancement.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
Description Check ✅ Passed The pull request description includes a ## Issue section with a resolve link and a ## Why is this change needed? section that clearly explains the problem and proposed solution. It adheres to the repository’s description template by including all required headings and the necessary information. The additional sections such as technical details and test results provide helpful context without conflicting with template requirements.
✨ 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 session-reconnect

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

vercel bot commented Oct 3, 2025

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

Project Deployment Preview Comments Updated (UTC)
liam-app Ready Ready Preview Comment Oct 8, 2025 8:46am
liam-assets Ready Ready Preview Comment Oct 8, 2025 8:46am
liam-storybook Ready Ready Preview Comment Oct 8, 2025 8:46am
2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
liam-docs Ignored Ignored Preview Oct 8, 2025 8:46am
liam-erd-sample Skipped Skipped Oct 8, 2025 8:46am

Copy link

supabase bot commented Oct 3, 2025

Updates to Preview Branch (session-reconnect) ↗︎

Deployments Status Updated
Database Wed, 08 Oct 2025 08:38:56 UTC
Services Wed, 08 Oct 2025 08:38:56 UTC
APIs Wed, 08 Oct 2025 08:38:56 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Wed, 08 Oct 2025 08:38:57 UTC
Migrations Wed, 08 Oct 2025 08:38:57 UTC
Seeding Wed, 08 Oct 2025 08:38:57 UTC
Edge Functions Wed, 08 Oct 2025 08:38:57 UTC

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

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)
frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.test.ts (1)

59-63: Strengthen the non-string content test

Once the extraction logic is fixed, please cover the real multi-part case and assert the reconstructed string so this regression can’t slip back in.

-    it('handles when content is not a string', () => {
-      const message = new HumanMessage({ content: 'test' })
-      const result = determineWorkflowAction([message], false, false)
-      expect(result.type).toBe('start')
-    })
+    it('extracts text when content is provided as message parts', () => {
+      const message = new HumanMessage({
+        content: [{ type: 'text', text: 'hello' }],
+      })
+      const result = determineWorkflowAction([message], false, false)
+      expect(result).toEqual({
+        type: 'start',
+        userInput: 'hello',
+      })
+    })
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between daee8b6 and e0d9c8d.

📒 Files selected for processing (5)
  • frontend/apps/app/components/SessionDetailPage/SessionDetailPageClient.tsx (3 hunks)
  • frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts (9 hunks)
  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.test.ts (1 hunks)
  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.ts (1 hunks)
  • frontend/apps/app/components/SessionDetailPage/utils/workflowStorage.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Name utility files in camelCase (e.g., mergeSchema.ts)

Files:

  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.ts
  • frontend/apps/app/components/SessionDetailPage/utils/workflowStorage.ts
  • frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts
  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript/TSX across the codebase

**/*.{ts,tsx}: Prefer early returns for readability
Use named exports only (no default exports)
Prefer const arrow functions over function declarations for simple utilities (e.g., const toggle = () => {})

Files:

  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.ts
  • frontend/apps/app/components/SessionDetailPage/SessionDetailPageClient.tsx
  • frontend/apps/app/components/SessionDetailPage/utils/workflowStorage.ts
  • frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts
  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.test.ts
frontend/apps/**

📄 CodeRabbit inference engine (AGENTS.md)

Next.js apps live under frontend/apps; target app-specific scripts and configs there

Files:

  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.ts
  • frontend/apps/app/components/SessionDetailPage/SessionDetailPageClient.tsx
  • frontend/apps/app/components/SessionDetailPage/utils/workflowStorage.ts
  • frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts
  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.test.ts
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Follow existing import patterns and tsconfig path aliases

Files:

  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.ts
  • frontend/apps/app/components/SessionDetailPage/SessionDetailPageClient.tsx
  • frontend/apps/app/components/SessionDetailPage/utils/workflowStorage.ts
  • frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts
  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.test.ts
frontend/apps/**/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

frontend/apps/**/app/**/*.{ts,tsx}: Use Server Components for server-side data fetching
Do client-side data fetching only when necessary
Align data fetching responsibilities with component roles
Use Server Actions for all data mutations (create/update/delete)

Files:

  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.ts
  • frontend/apps/app/components/SessionDetailPage/SessionDetailPageClient.tsx
  • frontend/apps/app/components/SessionDetailPage/utils/workflowStorage.ts
  • frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts
  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.test.ts
**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Name React component files in PascalCase and use TSX (e.g., App.tsx)

Prefix event handler functions with “handle” (e.g., handleClick)

Files:

  • frontend/apps/app/components/SessionDetailPage/SessionDetailPageClient.tsx
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Write unit tests with filenames ending in .test.ts or .test.tsx colocated near source

Files:

  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.test.ts
🧬 Code graph analysis (3)
frontend/apps/app/components/SessionDetailPage/SessionDetailPageClient.tsx (3)
frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts (1)
  • useStream (66-300)
frontend/apps/app/components/SessionDetailPage/utils/workflowStorage.ts (1)
  • getWorkflowInProgress (6-10)
frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.ts (1)
  • determineWorkflowAction (17-45)
frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts (1)
frontend/apps/app/components/SessionDetailPage/utils/workflowStorage.ts (2)
  • clearWorkflowInProgress (24-28)
  • setWorkflowInProgress (15-19)
frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.test.ts (1)
frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.ts (1)
  • determineWorkflowAction (17-45)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: CodeQL
  • GitHub Check: Supabase Preview
  • GitHub Check: frontend-ci
  • GitHub Check: frontend-lint
  • GitHub Check: security-review
  • GitHub Check: Supabase Preview

Comment on lines 34 to 39
const firstMessage = messages[0]
if (firstMessage && isHumanMessage(firstMessage)) {
return {
type: 'start',
userInput: String(firstMessage.content),
}
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

Don't coerce structured HumanMessage content

BaseMessage.content is often an array of message parts in LangChain; String(firstMessage.content) collapses that to "[object Object]", so the resumed workflow would receive gibberish instead of the actual user text. Please extract the text safely (e.g., join text parts and bail if nothing usable) before starting the workflow.

-    if (firstMessage && isHumanMessage(firstMessage)) {
-      return {
-        type: 'start',
-        userInput: String(firstMessage.content),
-      }
-    }
+    if (firstMessage && isHumanMessage(firstMessage)) {
+      const { content } = firstMessage
+      const userInput =
+        typeof content === 'string'
+          ? content
+          : Array.isArray(content)
+            ? content
+                .map((part) => {
+                  if (typeof part === 'string') return part
+                  if (part && typeof part === 'object' && 'text' in part) {
+                    const maybeText = (part as { text?: unknown }).text
+                    return typeof maybeText === 'string' ? maybeText : ''
+                  }
+                  return ''
+                })
+                .filter(Boolean)
+                .join('')
+            : ''
+
+      if (!userInput) {
+        return { type: 'none' }
+      }
+
+      return {
+        type: 'start',
+        userInput,
+      }
+    }
📝 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 firstMessage = messages[0]
if (firstMessage && isHumanMessage(firstMessage)) {
return {
type: 'start',
userInput: String(firstMessage.content),
}
const firstMessage = messages[0]
if (firstMessage && isHumanMessage(firstMessage)) {
const { content } = firstMessage
const userInput =
typeof content === 'string'
? content
: Array.isArray(content)
? content
.map((part) => {
if (typeof part === 'string') return part
if (part && typeof part === 'object' && 'text' in part) {
const maybeText = (part as { text?: unknown }).text
return typeof maybeText === 'string' ? maybeText : ''
}
return ''
})
.filter(Boolean)
.join('')
: ''
if (!userInput) {
return { type: 'none' }
}
return {
type: 'start',
userInput,
}
}
🤖 Prompt for AI Agents
In
frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.ts
around lines 34 to 39, the code coerces a structured HumanMessage content with
String(firstMessage.content) which collapses arrays/objects into "[object
Object]"; instead, detect and extract usable text from the message content
(handle string, array of parts, or objects with text fields), join text parts
into a single string, trim and validate non-empty result, and if nothing usable
bail (return undefined or fallback) rather than passing the malformed "[object
Object]" into the workflow start action.

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

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e0d9c8d and 74a5dde.

📒 Files selected for processing (1)
  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Name utility files in camelCase (e.g., mergeSchema.ts)

Files:

  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript/TSX across the codebase

**/*.{ts,tsx}: Prefer early returns for readability
Use named exports only (no default exports)
Prefer const arrow functions over function declarations for simple utilities (e.g., const toggle = () => {})

Files:

  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.ts
frontend/apps/**

📄 CodeRabbit inference engine (AGENTS.md)

Next.js apps live under frontend/apps; target app-specific scripts and configs there

Files:

  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.ts
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Follow existing import patterns and tsconfig path aliases

Files:

  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.ts
frontend/apps/**/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

frontend/apps/**/app/**/*.{ts,tsx}: Use Server Components for server-side data fetching
Do client-side data fetching only when necessary
Align data fetching responsibilities with component roles
Use Server Actions for all data mutations (create/update/delete)

Files:

  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.ts
⏰ 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: Supabase Preview
  • GitHub Check: Supabase Preview
  • GitHub Check: frontend-ci
  • GitHub Check: Supabase Preview
🔇 Additional comments (3)
frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.ts (3)

1-2: LGTM!

The imports are correct and appropriate for the workflow action determination logic.


4-7: LGTM!

The WorkflowAction type is well-structured as a discriminated union, enabling safe type narrowing in consuming code.


22-30: LGTM!

The priority checks for hasTriggered and isWorkflowInProgress are correctly ordered and use early returns for clarity.

Comment on lines +35 to +40
if (firstMessage && isHumanMessage(firstMessage)) {
return {
type: 'start',
userInput: firstMessage.text,
}
}
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

Validate non-empty userInput before starting workflow.

The code correctly uses firstMessage.text (the LangChain getter that safely extracts text), which addresses the past review concern about content coercion. However, if .text returns an empty string, the function returns { type: 'start', userInput: '' }, which may cause issues when attempting to start a workflow with no actual user input.

Apply this diff to validate non-empty userInput:

     if (firstMessage && isHumanMessage(firstMessage)) {
+      const userInput = firstMessage.text.trim()
+      if (!userInput) {
+        return { type: 'none' }
+      }
       return {
         type: 'start',
-        userInput: firstMessage.text,
+        userInput,
       }
     }
📝 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 (firstMessage && isHumanMessage(firstMessage)) {
return {
type: 'start',
userInput: firstMessage.text,
}
}
if (firstMessage && isHumanMessage(firstMessage)) {
const userInput = firstMessage.text.trim()
if (!userInput) {
return { type: 'none' }
}
return {
type: 'start',
userInput,
}
}
🤖 Prompt for AI Agents
In
frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.ts
around lines 35 to 40, the code returns a start action using firstMessage.text
even when that text is an empty string; change the guard to only return the
start action when firstMessage exists, is a human message, and firstMessage.text
is non-empty (e.g., trim and length check), otherwise fall through (do not start
the workflow). Ensure you use the LangChain .text getter as before and only
create { type: 'start', userInput: firstMessage.text } when the validated
non-empty string check passes.

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 (4)
frontend/apps/app/components/SessionDetailPage/SessionDetailPageClient.tsx (1)

151-157: Guard against empty/whitespace user input before start

Avoid starting a workflow with blank input (e.g., accidental whitespace-only first message). Trim and short‑circuit.

-      } else if (action.type === 'start') {
-        // Trigger the workflow for the initial user message
-        await start({
-          designSessionId,
-          userInput: action.userInput,
-          isDeepModelingEnabled,
-        })
-      }
+      } else if (action.type === 'start') {
+        // Trigger the workflow for the initial user message
+        const trimmed = action.userInput.trim()
+        if (!trimmed) return
+        await start({
+          designSessionId,
+          userInput: trimmed,
+          isDeepModelingEnabled,
+        })
+      }
frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.test.ts (1)

46-58: Add a blank/whitespace-only HumanMessage case

Ensure we don’t start for empty/whitespace input. This both documents intended behavior and protects UX.

   describe('Priority 3: Single unanswered user message', () => {
     it('returns start when there is a single HumanMessage', () => {
@@
     })
 
+    it('returns none when HumanMessage is only whitespace', () => {
+      const result = determineWorkflowAction(
+        [new HumanMessage('   ')],
+        false,
+        false,
+      )
+      expect(result).toEqual({ type: 'none' })
+    })
frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts (2)

236-251: Replay: retry policy skips retries on network/unknown errors

Right now you return on first err; consider retrying non‑abort errors with a small backoff.

   const replay = useCallback(
     async (params: ReplayParams): Promise<Result<void, StreamError>> => {
       for (let attempt = 1; attempt <= MAX_RETRIES; attempt += 1) {
         retryCountRef.current = attempt
 
         const result = await runStreamAttempt('/api/chat/replay', params)
 
-        if (result.isErr()) {
-          return err(result.error)
-        }
+        if (result.isErr()) {
+          // Abort is user-driven; bubble it immediately.
+          if (result.error.type === 'abort') {
+            return err(result.error)
+          }
+          // For transient errors (network/unknown), retry.
+          // Simple linear backoff.
+          await new Promise((r) => setTimeout(r, attempt * 500))
+          continue
+        }
 
         if (result.value === 'complete') {
           return ok(undefined)
         }
       }

253-261: On terminal timeout, consider clearing the flag to avoid sticky replays on reload

After exhausting retries, the in‑progress flag remains set, causing auto‑replay on every reload. Clear it so the user can start fresh.

-      const timeoutMessage = ERROR_MESSAGES.CONNECTION_TIMEOUT
-      abortWorkflow()
+      const timeoutMessage = ERROR_MESSAGES.CONNECTION_TIMEOUT
+      abortWorkflow()
+      // Prevent endless auto-replays on next load after terminal failure
+      clearWorkflowInProgress(params.designSessionId)
       setError(timeoutMessage)
       return err({
         type: 'timeout',
         message: timeoutMessage,
       })
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fc395fd and 8a35e14.

📒 Files selected for processing (5)
  • frontend/apps/app/components/SessionDetailPage/SessionDetailPageClient.tsx (3 hunks)
  • frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts (10 hunks)
  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.test.ts (1 hunks)
  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.ts (1 hunks)
  • frontend/apps/app/components/SessionDetailPage/utils/workflowStorage.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/apps/app/components/SessionDetailPage/utils/workflowStorage.ts
  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.ts
🧰 Additional context used
📓 Path-based instructions (6)
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Name utility files in camelCase (e.g., mergeSchema.ts)

Files:

  • frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts
  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript/TSX across the codebase

**/*.{ts,tsx}: Use runtime type validation with valibot for external data validation
Prefer early returns for readability
Write simple, direct code without backward compatibility shims; update all call sites together
Use const-assigned arrow functions instead of function declarations for small utilities (e.g., const toggle = () => {})
Follow existing import patterns and tsconfig path aliases

Files:

  • frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts
  • frontend/apps/app/components/SessionDetailPage/SessionDetailPageClient.tsx
  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.test.ts
frontend/apps/**

📄 CodeRabbit inference engine (AGENTS.md)

Next.js apps live under frontend/apps; target app-specific scripts and configs there

Files:

  • frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts
  • frontend/apps/app/components/SessionDetailPage/SessionDetailPageClient.tsx
  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.test.ts
**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Name React component files in PascalCase and use TSX (e.g., App.tsx)

**/*.tsx: Prefix React event handler functions with "handle" (e.g., handleClick)
Import UI components from @liam-hq/ui when available
Import icons from @liam-hq/ui

Files:

  • frontend/apps/app/components/SessionDetailPage/SessionDetailPageClient.tsx
**/!(page).tsx

📄 CodeRabbit inference engine (CLAUDE.md)

Use named exports only (no default exports) for React/TSX modules

Files:

  • frontend/apps/app/components/SessionDetailPage/SessionDetailPageClient.tsx
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Write unit tests with filenames ending in .test.ts or .test.tsx colocated near source

Files:

  • frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.test.ts
🧬 Code graph analysis (3)
frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts (3)
frontend/apps/app/components/SessionDetailPage/utils/workflowStorage.ts (2)
  • clearWorkflowInProgress (25-29)
  • setWorkflowInProgress (16-20)
frontend/apps/app/app/api/chat/replay/route.ts (1)
  • start (142-161)
frontend/apps/app/app/api/chat/stream/route.ts (1)
  • start (130-149)
frontend/apps/app/components/SessionDetailPage/SessionDetailPageClient.tsx (3)
frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts (1)
  • useStream (66-298)
frontend/apps/app/components/SessionDetailPage/utils/workflowStorage.ts (1)
  • getWorkflowInProgress (6-11)
frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.ts (1)
  • determineWorkflowAction (17-45)
frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.test.ts (2)
frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.ts (1)
  • determineWorkflowAction (17-45)
frontend/apps/app/components/SessionDetailPage/components/Chat/components/Messages/HumanMessage/HumanMessage.tsx (1)
  • HumanMessage (18-66)
⏰ 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: Supabase Preview
  • GitHub Check: Supabase Preview
  • GitHub Check: codeql / languages (javascript) / Perform CodeQL for javascript
  • GitHub Check: Supabase Preview
🔇 Additional comments (2)
frontend/apps/app/components/SessionDetailPage/SessionDetailPageClient.tsx (1)

118-121: useStream replay integration looks good

Destructuring replay and wiring it through the effect aligns with the new flow.

frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts (1)

84-96: Workflow flag semantics on complete vs. abort are correct

Clearing the flag only on complete and preserving it on abort matches the reconnection objective. Good fix.

Comment on lines +59 to +63
it('handles when content is not a string', () => {
const message = new HumanMessage({ content: 'test' })
const result = determineWorkflowAction([message], false, false)
expect(result.type).toBe('start')
})
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

Test name mismatch: actually still string content

This case claims “not a string” but still passes string content. Use a non‑string content shape to truly exercise the path.

-    it('handles when content is not a string', () => {
-      const message = new HumanMessage({ content: 'test' })
-      const result = determineWorkflowAction([message], false, false)
-      expect(result.type).toBe('start')
-    })
+    it('handles when content is not a string', () => {
+      const message = new HumanMessage({
+        // non-string content (array of parts)
+        content: [{ type: 'text', text: 'hello from parts' }],
+      })
+      const result = determineWorkflowAction([message], false, false)
+      expect(result.type).toBe('start')
+    })
🤖 Prompt for AI Agents
frontend/apps/app/components/SessionDetailPage/utils/determineWorkflowAction.test.ts
around lines 59 to 63: the test claims to check the "not a string" path but
constructs a HumanMessage with string content; replace the message content with
a non-string shape (for example an object like { foo: 'bar' } or an array) so
the function truly receives non-string content, then keep the same expectation
that result.type is 'start'.

MH4GF and others added 8 commits October 8, 2025 17:19
- Add determineWorkflowAction: Pure function to determine start/replay/none
- Add workflowStorage helpers: Manage workflow state in sessionStorage
- Add comprehensive tests for workflow action determination

Implements workflow reconnection detection logic for resolving page reload
interruption issues during workflow execution.

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

Co-Authored-By: Claude <[email protected]>
Dynamic imports were unnecessary as these utilities are always needed.
Static imports improve type safety and code clarity.

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

Co-Authored-By: Claude <[email protected]>
Remove unnecessary dynamic imports for workflowStorage helpers.
Static imports are simpler and provide better type safety.

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

Co-Authored-By: Claude <[email protected]>
Replace 'unknown' payload type with explicit 'StartParams | ReplayParams'.
Remove redundant sessionId parameter - use params.designSessionId instead.

This makes the code more type-safe and eliminates parameter duplication.

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

Co-Authored-By: Claude <[email protected]>
HumanMessage.text is already a string getter, no need for String() conversion.

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

Co-Authored-By: Claude <[email protected]>
With workflow reconnection feature, users can safely reload the page
during streaming - the workflow will automatically resume from the
last checkpoint. The confirmation dialog is no longer necessary.

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

Co-Authored-By: Claude <[email protected]>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Separate workflow completion from abortion to preserve reconnection ability.
When a workflow is aborted or errors, keep the in-progress flag so users can
reconnect on page reload. Only clear the flag when workflow completes successfully.

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

Co-Authored-By: Claude <[email protected]>
MH4GF and others added 2 commits October 8, 2025 17:35
This ensures the abort controller is properly cleaned up when navigating away during streaming.

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

Co-Authored-By: Claude <[email protected]>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@vercel vercel bot temporarily deployed to Preview – liam-erd-sample October 8, 2025 08:38 Inactive
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)
frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts (1)

258-258: Consider clearing the workflow flag after MAX_RETRIES timeout.

After MAX_RETRIES failures, abortWorkflow() leaves the workflow flag set. If the underlying issue persists (e.g., the stream never sends an END event), every page reload will attempt replay and timeout again, potentially creating an indefinite retry loop. If this becomes a concern in practice, consider calling clearWorkflowInProgress(params.designSessionId) after the timeout to prevent automatic replay on reload.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 991254b and d5b74c9.

📒 Files selected for processing (1)
  • frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts (11 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Name utility files in camelCase (e.g., mergeSchema.ts)

Files:

  • frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript/TSX across the codebase

**/*.{ts,tsx}: Use runtime type validation with valibot for external data validation
Prefer early returns for readability
Write simple, direct code without backward compatibility shims; update all call sites together
Use const-assigned arrow functions instead of function declarations for small utilities (e.g., const toggle = () => {})
Follow existing import patterns and tsconfig path aliases

Files:

  • frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts
frontend/apps/**

📄 CodeRabbit inference engine (AGENTS.md)

Next.js apps live under frontend/apps; target app-specific scripts and configs there

Files:

  • frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts
🧬 Code graph analysis (1)
frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts (1)
frontend/apps/app/components/SessionDetailPage/utils/workflowStorage.ts (2)
  • clearWorkflowInProgress (25-29)
  • setWorkflowInProgress (16-20)
⏰ 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: Supabase Preview
  • GitHub Check: frontend-lint
  • GitHub Check: frontend-ci
  • GitHub Check: Supabase Preview
🔇 Additional comments (5)
frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts (5)

93-106: Clean separation of workflow completion and abort paths.

The distinction between completeWorkflow (clears flag on success) and abortWorkflow (preserves flag for reconnection) is clear and aligns with the PR's objective of enabling workflow resumption after page reloads.


112-117: Navigation guard correctly triggers abort without clearing workflow flag.

Calling abortWorkflow() instead of a confirm dialog ensures the workflow flag persists across page reloads, enabling the reconnection logic to detect and resume the interrupted workflow on the next page load.


172-239: Stream lifecycle management is correct and consistent.

The orchestration of abortWorkflow (for errors/aborts, preserving the flag) and completeWorkflow (for successful completion, clearing the flag) is sound. The shouldRetry path correctly avoids clearing state, allowing the retry loop to continue.


288-290: LGTM: Workflow flag set before streaming.

Setting the flag before initiating the stream ensures the workflow is marked in progress even if the initial request fails immediately, allowing reconnection attempts on page reload.


312-319: API change: replay added, stop removed.

The hook now exposes replay to support the reconnection flow and no longer exposes stop, removing the ability for users to manually abort streaming via the hook interface. This aligns with the PR's focus on page-reload-driven reconnection, where the navigation guard handles abort scenarios.

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 adds workflow reconnection logic to handle page reload scenarios during workflow execution. When users reload the page, the streaming is interrupted and this change implements a mechanism to detect and resume interrupted workflows using sessionStorage.

  • Workflow reconnection mechanism - Implements pure function logic to determine whether to start, replay, or skip workflow execution
  • SessionStorage persistence - Adds helpers to track workflow state across page reloads with tab isolation
  • Automatic workflow resumption - Integrates reconnection logic into existing components to handle interrupted workflows

Reviewed Changes

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

Show a summary per file
File Description
workflowStorage.ts New utility functions for managing workflow state in sessionStorage
determineWorkflowAction.ts Pure function to determine workflow action based on state and message history
determineWorkflowAction.test.ts Comprehensive unit tests for workflow action determination logic
useStream.ts Enhanced streaming hook with workflow state management and replay functionality
SessionDetailPageClient.tsx Updated client to use new workflow determination logic for automatic reconnection

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

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

Successfully merging this pull request may close these issues.

1 participant