Skip to content

AI in Stack Companion#1297

Merged
aadesh18 merged 22 commits intodevfrom
ai-in-stack-companion
Apr 13, 2026
Merged

AI in Stack Companion#1297
aadesh18 merged 22 commits intodevfrom
ai-in-stack-companion

Conversation

@aadesh18
Copy link
Copy Markdown
Contributor

@aadesh18 aadesh18 commented Mar 28, 2026

This PR puts the ask ai functionality into the ai stack companion, along with persistent history.

Summary by CodeRabbit

  • New Features

    • "Ask AI" chat sidebar with streaming assistant responses, progressive word-by-word reveal, auto-scroll, Enter-to-send and Arrow-key navigation, "Thinking…" and error indicators
    • Chat UI primitives: inline/code blocks, smart links, copy-to-clipboard for code/URLs, and expandable tool-result cards with copyable outputs
  • Bug Fixes

    • Prevented button/menu clicks inside list items from bubbling to parent row handlers
  • Refactor

    • Chat rendering, streaming, parsing, and UI helpers consolidated into a shared module and integrated into the sidebar widget

Copilot AI review requested due to automatic review settings March 28, 2026 00:35
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 28, 2026

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

Project Deployment Actions Updated (UTC)
stack-auth-hosted-components Ready Ready Preview, Comment Apr 13, 2026 5:45pm
stack-backend Ready Ready Preview, Comment Apr 13, 2026 5:45pm
stack-dashboard Ready Ready Preview, Comment Apr 13, 2026 5:45pm
stack-demo Ready Ready Preview, Comment Apr 13, 2026 5:45pm
stack-docs Ready Ready Preview, Comment Apr 13, 2026 5:45pm
stack-preview-backend Ready Ready Preview, Comment Apr 13, 2026 5:45pm
stack-preview-dashboard Ready Ready Preview, Comment Apr 13, 2026 5:45pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 28, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a shared AI chat utilities module, refactors ask-ai to use it, adds an Ask AI widget to the companion sidebar with streaming and word-by-word reveal, and prevents event bubbling from list button wrappers. (27 words)

Changes

Cohort / File(s) Summary
AI Chat Shared Module
apps/dashboard/src/components/commands/ai-chat-shared.tsx
New shared module providing createAskAiTransport (authenticated streaming transport), UI primitives (CopyButton, InlineCode, CodeBlock, SmartLink), tool invocation types/UI (ToolInvocationPart, ToolInvocationCard), markdownComponents for react-markdown, parsing helpers (countWords, getFirstNWords, getMessageContent, getToolInvocations), memoized UserMessage/AssistantMessage, and useWordStreaming hook.
Ask AI Refactoring
apps/dashboard/src/components/commands/ask-ai.tsx
Removed local chat rendering/streaming logic and delegates transport/rendering/streaming to ai-chat-shared (imports createAskAiTransport, AssistantMessage, UserMessage, getMessageContent, getToolInvocations, useWordStreaming). Adjusted assistant lookup and follow-up input trimming; added IME composition guard.
AI Chat Widget & Integration
apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx, apps/dashboard/src/components/stack-companion.tsx
Added AIChatWidget (client-side component: conversation state, remount-on-reset, autoscroll, Enter-to-submit, ArrowUp/Down navigation, streaming + word reveal, error UI) and integrated it as new "Ask AI" drawer item with layout changes when active.
List Component Event Handling
apps/dashboard/src/components/design-components/list.tsx
ListItemButtons wrapper now stops event propagation (e.stopPropagation()), preventing inner clicks from triggering parent row onClick.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant AIChatWidget
    participant useChat
    participant API as /api/latest/ai/query/stream
    participant useWordStreaming
    participant UI as MessageRenderer

    User->>AIChatWidget: submit message
    AIChatWidget->>useChat: sendMessage (systemPrompt, tools, projectId, auth)
    useChat->>API: open streaming request
    API-->>useChat: stream response chunks (assistant parts + tool invocations)
    useChat->>AIChatWidget: emit assistant message parts
    AIChatWidget->>useWordStreaming: supply assistant content for reveal
    useWordStreaming-->>UI: incremental (word-by-word) display updates
    UI->>AIChatWidget: render messages & tool cards, update scroll
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Cmd K #1037: Overlapping changes touching the dashboard AI chat UI/transport and markdown/code/link rendering used by the ask-ai flow.

Suggested reviewers

  • N2D4
  • TheUntraceable

Poem

🐰✨ I hopped through code to teach the bot to speak,
Streaming tiny words like carrots for a peek.
Cards and copies, links trimmed neat and bright,
Tools that whisper answers through the night.
Hooray — a burrowed chat that feels just right!

🚥 Pre-merge checks | ❌ 3

❌ Failed checks (1 warning, 2 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'AI in Stack Companion' is vague and generic, using non-descriptive phrasing that doesn't convey the specific changes being made. Use a more specific title that describes the primary change, such as 'Add AI chat widget to Stack Companion' or 'Implement Ask AI feature in dashboard'.
Description check ❓ Inconclusive PR description is vague and generic, lacking detail about implementation, testing, or scope despite substantial code changes across multiple files. Add comprehensive description covering: (1) Feature overview and motivation; (2) Files modified and their purposes; (3) Testing approach; (4) Any breaking changes or dependencies; (5) Screenshots/demos if applicable.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ai-in-stack-companion

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.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 28, 2026

Greptile Summary

This PR adds an "Ask AI" panel to the Stack Companion sidebar by introducing a new AIChatWidget component and extracting all shared AI-chat UI primitives (message bubbles, markdown rendering, copy buttons, word-streaming hook, etc.) from the existing ask-ai.tsx command-palette component into a new ai-chat-shared.tsx module. A minor fix to list.tsx also prevents button clicks inside list rows from bubbling to the parent row handler.

Key changes:

  • ai-chat-shared.tsx (new): shared UserMessage, AssistantMessage, ToolInvocationCard, CodeBlock, InlineCode, SmartLink, markdownComponents, and useWordStreaming — consumed by both the command-palette and sidebar chat.
  • ai-chat-widget.tsx (new): standalone sidebar widget with an idle/conversation two-phase UI; key-based remounting for "new conversation" resets chat state cleanly.
  • ask-ai.tsx: slimmed down to consume ai-chat-shared; note that targetWordCount is now an unused destructured variable (lint warning), and the DefaultChatTransport configuration is duplicated verbatim from the new widget.
  • stack-companion.tsx: "Ask AI" added as the first sidebar item; content area layout adjusted to remove padding/scroll for the full-height chat panel.
  • list.tsx: e.stopPropagation() added to the buttons container to prevent action-button clicks from triggering the parent row handler.

Confidence Score: 5/5

Safe to merge; all remaining findings are P2 style/cleanup suggestions with no correctness or data-integrity impact.

The feature is well-structured (shared module, keyed remounting for conversation resets, proper scroll handling). The only open items are: an unused variable that produces a lint warning but has no runtime impact, a project-convention suggestion around runAsynchronouslyWithAlert, and a code-duplication note about the DefaultChatTransport config. None of these affect correctness or safety.

apps/dashboard/src/components/commands/ask-ai.tsx (unused variable), apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx (duplicated transport config)

Important Files Changed

Filename Overview
apps/dashboard/src/components/commands/ai-chat-shared.tsx New shared module extracting AI chat UI primitives (CopyButton, CodeBlock, markdown components, message components, word-streaming hook) from ask-ai.tsx; note the truncateUrl refactor drops the URL-constructor try/catch in favour of a regex approach
apps/dashboard/src/components/commands/ask-ai.tsx Stripped down to import from ai-chat-shared; introduces an unused targetWordCount variable and uses runAsynchronously for async button/keyboard handlers where runAsynchronouslyWithAlert is specified by project rules
apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx New sidebar AI chat widget with two-phase UI (idle vs active conversation); duplicates the useChat DefaultChatTransport configuration from ask-ai.tsx verbatim and uses runAsynchronously instead of runAsynchronouslyWithAlert in async handlers
apps/dashboard/src/components/stack-companion.tsx Adds 'Ask AI' as the first sidebar item and conditionally removes padding/overflow for the AI panel; straightforward integration change
apps/dashboard/src/components/design-components/list.tsx Adds e.stopPropagation() on the ListItemButtons wrapper to prevent button clicks from bubbling to the parent list-item click handler; correct fix
pnpm-lock.yaml Lockfile bump: updates @babel/core peer from 7.26.0 to 7.29.0 for next, adds rolldown to the nitro snapshot, removes the now-deleted packages/private devDependency, and tightens eslint-import-resolver-typescript snapshot keys

Sequence Diagram

sequenceDiagram
    participant User
    participant AIChatWidget
    participant AIChatWidgetInner
    participant useChat
    participant Backend as /api/latest/ai/query/stream

    User->>AIChatWidget: types question, hits Enter
    AIChatWidget->>AIChatWidgetInner: setConversationStarted(true)
    AIChatWidgetInner->>useChat: sendMessage({ text })
    useChat->>Backend: POST (systemPrompt, tools, messages)
    Backend-->>useChat: streaming response (tokens)
    useChat-->>AIChatWidgetInner: messages[], status="streaming"
    AIChatWidgetInner->>AIChatWidgetInner: useWordStreaming reveals words
    AIChatWidgetInner-->>User: AssistantMessage rendered progressively

    User->>AIChatWidgetInner: types follow-up, hits Enter
    AIChatWidgetInner->>useChat: sendMessage({ text })
    useChat->>Backend: POST (full conversation history)
    Backend-->>useChat: streaming response
    useChat-->>AIChatWidgetInner: updated messages[]
    AIChatWidgetInner-->>User: new AssistantMessage rendered

    User->>AIChatWidgetInner: clicks "New conversation"
    AIChatWidgetInner->>AIChatWidget: onNewConversation()
    AIChatWidget->>AIChatWidget: setConversationKey(prev+1), reset state
    AIChatWidget->>AIChatWidgetInner: remount via key change
    AIChatWidgetInner-->>User: idle input screen shown
Loading

Comments Outside Diff (2)

  1. apps/dashboard/src/components/commands/ask-ai.tsx, line 95 (link)

    P2 Unused destructured variable targetWordCount

    targetWordCount is destructured from useWordStreaming but never referenced anywhere in the component after this line. This will produce an ESLint no-unused-vars warning. Consider removing it.

  2. apps/dashboard/src/components/commands/ask-ai.tsx, line 137 (link)

    P2 runAsynchronouslyWithAlert preferred for async handlers

    Per project conventions, runAsynchronouslyWithAlert should be used for async button-click and form-submission handlers instead of runAsynchronously. The same pattern applies in ai-chat-widget.tsx at lines 142 and 165.

    Note: if errors are intentionally suppressed here because they are already surfaced via the aiError state, add a brief comment explaining that decision so future readers understand the deviation.

    Rule Used: Use runAsynchronouslyWithAlert from `@stackframe... (source)

    Learnt From
    stack-auth/stack-auth#943

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Reviews (1): Last reviewed commit: "initial commit" | Re-trigger Greptile

Comment thread apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx
Copy link
Copy Markdown
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.

🧹 Nitpick comments (2)
apps/dashboard/src/components/stack-companion.tsx (1)

432-436: Consider updating the Playground page to showcase the new AI chat widget.

The conditional layout change for ask-ai (no padding, flex column, overflow hidden) makes sense for a chat interface that manages its own scrolling. As per coding guidelines, any design components added to the dashboard should have corresponding updates in the Playground page.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/stack-companion.tsx` around lines 432 - 436,
Playground page is missing the updated layout and AIChatWidget rendering when
activeItem === 'ask-ai'; update the Playground component to apply the same
conditional className logic used here (using cn with "flex-1 overflow-x-hidden
no-drag cursor-auto" and the branch that sets "overflow-hidden p-0 flex
flex-col" for activeItem === 'ask-ai' vs "overflow-y-auto p-5") and render
<AIChatWidget isActive={true} /> when activeItem === 'ask-ai'; ensure
AIChatWidget is imported and that the Playground's activeItem state/value uses
the same 'ask-ai' key so the widget receives the correct isActive prop and the
container handles its scrolling.
apps/dashboard/src/components/commands/ai-chat-shared.tsx (1)

407-411: Type cast through unknown bypasses type safety.

The as unknown as ToolInvocationPart cast assumes the filtered parts match the ToolInvocationPart structure. While the startsWith("tool-") check provides some validation, this doesn't guarantee the full structure. Consider using a type guard or runtime validation for safer typing.

♻️ Suggested type guard approach
+function isToolInvocationPart(part: unknown): part is ToolInvocationPart {
+  if (typeof part !== 'object' || part === null) return false;
+  const p = part as Record<string, unknown>;
+  return (
+    typeof p.type === 'string' &&
+    p.type.startsWith('tool-') &&
+    typeof p.toolCallId === 'string' &&
+    typeof p.state === 'string'
+  );
+}
+
 // Helper to extract tool invocations from UIMessage parts
 export function getToolInvocations(message: UIMessage): ToolInvocationPart[] {
   return message.parts
-    .filter((part) => part.type.startsWith("tool-"))
-    .map((part) => part as unknown as ToolInvocationPart);
+    .filter(isToolInvocationPart);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/commands/ai-chat-shared.tsx` around lines 407 -
411, The cast in getToolInvocations blindly forces parts to ToolInvocationPart
via "as unknown as ToolInvocationPart"; replace that with a proper type guard or
runtime validation (e.g., implement isToolInvocationPart(part: any): part is
ToolInvocationPart that checks required fields like type, name, args/result
shapes) and use it in the filter (message.parts.filter(isToolInvocationPart)).
Ensure the guard references the ToolInvocationPart shape and return the typed
array without double-casting.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/dashboard/src/components/commands/ai-chat-shared.tsx`:
- Around line 407-411: The cast in getToolInvocations blindly forces parts to
ToolInvocationPart via "as unknown as ToolInvocationPart"; replace that with a
proper type guard or runtime validation (e.g., implement
isToolInvocationPart(part: any): part is ToolInvocationPart that checks required
fields like type, name, args/result shapes) and use it in the filter
(message.parts.filter(isToolInvocationPart)). Ensure the guard references the
ToolInvocationPart shape and return the typed array without double-casting.

In `@apps/dashboard/src/components/stack-companion.tsx`:
- Around line 432-436: Playground page is missing the updated layout and
AIChatWidget rendering when activeItem === 'ask-ai'; update the Playground
component to apply the same conditional className logic used here (using cn with
"flex-1 overflow-x-hidden no-drag cursor-auto" and the branch that sets
"overflow-hidden p-0 flex flex-col" for activeItem === 'ask-ai' vs
"overflow-y-auto p-5") and render <AIChatWidget isActive={true} /> when
activeItem === 'ask-ai'; ensure AIChatWidget is imported and that the
Playground's activeItem state/value uses the same 'ask-ai' key so the widget
receives the correct isActive prop and the container handles its scrolling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3917f0c5-7955-49ce-9a5a-a843b3e2cf2e

📥 Commits

Reviewing files that changed from the base of the PR and between 4ddf6a5 and f3e27f8.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • apps/dashboard/src/components/commands/ai-chat-shared.tsx
  • apps/dashboard/src/components/commands/ask-ai.tsx
  • apps/dashboard/src/components/design-components/list.tsx
  • apps/dashboard/src/components/stack-companion.tsx
  • apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx

Copy link
Copy Markdown
Contributor

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

Introduces an “Ask AI” experience inside the Stack Companion UI, sharing chat rendering logic between the command palette preview and the new companion widget.

Changes:

  • Adds a new Stack Companion sidebar item and full-page chat widget for “Ask AI”.
  • Extracts shared AI chat rendering utilities/components into ai-chat-shared.tsx and wires ask-ai to use them.
  • Updates list-row button behavior to prevent row click handlers from firing when interacting with action buttons.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pnpm-lock.yaml Updates lockfile for dependency graph changes introduced by the PR.
apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx New Stack Companion AI chat widget implementation (streaming, scrolling, input handling).
apps/dashboard/src/components/stack-companion.tsx Adds “Ask AI” to the companion sidebar and renders the new widget when selected.
apps/dashboard/src/components/design-components/list.tsx Stops click propagation from row action buttons to the row onClick handler.
apps/dashboard/src/components/commands/ask-ai.tsx Refactors the command palette AI preview to use shared chat UI helpers.
apps/dashboard/src/components/commands/ai-chat-shared.tsx New shared components/utilities for AI chat rendering (markdown, tool invocations, streaming).
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx
Comment thread apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx Outdated
Comment thread apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx Outdated
Copy link
Copy Markdown
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/dashboard/src/components/commands/ask-ai.tsx (2)

127-127: ⚠️ Potential issue | 🟡 Minor

handleFollowUp() returns void, not a Promise.

handleFollowUp internally calls runAsynchronously(sendMessage(...)) and returns void. Wrapping runAsynchronously(handleFollowUp()) wraps undefined, not a promise. This is a no-op.

Either remove the wrapper or make handleFollowUp return the promise:

Proposed fix (remove redundant wrapper)
       if (e.key === "Enter" && !e.shiftKey) {
         e.preventDefault();
         e.stopPropagation();
-        runAsynchronously(handleFollowUp());
+        handleFollowUp();
       } else if (e.key === "ArrowLeft") {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/commands/ask-ai.tsx` at line 127, The call
runAsynchronously(handleFollowUp()) is a no-op because handleFollowUp() returns
void; either remove the wrapper and call handleFollowUp() directly, or change
handleFollowUp to return the promise from runAsynchronously/sendMessage (e.g.,
have handleFollowUp return runAsynchronously(sendMessage(...)) or be async and
return the awaited promise). Update the implementation of handleFollowUp (the
function that currently calls runAsynchronously(sendMessage(...))) to return
that promise if you need to keep runAsynchronously(...) here, otherwise call
handleFollowUp() without wrapping.

239-239: ⚠️ Potential issue | 🟡 Minor

Same issue: redundant runAsynchronously wrapper.

Same as above—handleFollowUp() returns void, so runAsynchronously(handleFollowUp()) wraps undefined.

Proposed fix
-          onClick={() => runAsynchronously(handleFollowUp())}
+          onClick={handleFollowUp}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/commands/ask-ai.tsx` at line 239, The onClick
currently invokes handleFollowUp immediately
(runAsynchronously(handleFollowUp())), passing undefined; change it to pass the
function reference instead or call the handler directly: either use onClick={()
=> runAsynchronously(handleFollowUp)} if you intend runAsynchronously to wrap
the handler, or simply use onClick={handleFollowUp} (or onClick={() =>
handleFollowUp()} if you prefer an arrow) so handleFollowUp is executed on click
rather than at render time.
🧹 Nitpick comments (5)
apps/dashboard/src/components/commands/ai-chat-shared.tsx (3)

214-216: Add comment explaining the type assertions for SDK types.

Per coding guidelines, when using as casts, leave a comment explaining why and how errors would still be flagged. The input/output shapes depend on the tool definition on the backend.

Proposed documentation
-  // Extract query from input
-  const input = invocation.input as { query?: string } | undefined;
-  const queryArg = input?.query;
-  const result = invocation.output as { success?: boolean, result?: unknown[], error?: string, rowCount?: number } | undefined;
+  // Extract query from input
+  // Type assertion: invocation.input/output are typed as `unknown` by the AI SDK.
+  // The actual shape depends on the tool definition (sql-query tool) on the backend.
+  // Using optional chaining ensures safe access even if the shape differs.
+  const input = invocation.input as { query?: string } | undefined;
+  const queryArg = input?.query;
+  const result = invocation.output as { success?: boolean, result?: unknown[], error?: string, rowCount?: number } | undefined;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/commands/ai-chat-shared.tsx` around lines 214 -
216, Add an inline comment explaining the use of `as` casts on
`invocation.input` and `invocation.output` near the `input`, `queryArg`, and
`result` declarations: state that these casts are narrowing the SDK-generic
types to the expected shapes produced by the backend tool definition, note that
runtime shape mismatches will still surface as errors, and mention where the
true canonical shape is defined (the backend tool/schema). Keep the comment
succinct and colocated with the `const input = invocation.input as { query?:
string } | undefined;` and `const result = invocation.output as { success?:
boolean, result?: unknown[], error?: string, rowCount?: number } | undefined;`
lines.

52-56: Clean up timeout on unmount to avoid state update on unmounted component.

If CopyButton unmounts before the 1500ms timeout fires, React will warn about setting state on an unmounted component.

Proposed fix using useEffect cleanup
 export const CopyButton = memo(function CopyButton({ text, className, size = "sm" }: {
   text: string,
   className?: string,
   size?: "sm" | "xs",
 }) {
   const [copied, setCopied] = useState(false);
+  const timeoutRef = useRef<ReturnType<typeof setTimeout>>(null);
+
+  useEffect(() => {
+    return () => {
+      if (timeoutRef.current) clearTimeout(timeoutRef.current);
+    };
+  }, []);

   const handleCopy = useCallback(async () => {
     await navigator.clipboard.writeText(text);
     setCopied(true);
-    setTimeout(() => setCopied(false), 1500);
+    if (timeoutRef.current) clearTimeout(timeoutRef.current);
+    timeoutRef.current = setTimeout(() => setCopied(false), 1500);
   }, [text]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/commands/ai-chat-shared.tsx` around lines 52 -
56, handleCopy sets a timeout that calls setCopied after 1500ms but never clears
it, causing a potential state update on an unmounted component; add a ref (e.g.,
copiedTimeoutRef) to store the timeout id, clear any existing timeout before
creating a new one inside handleCopy, and add a useEffect with a cleanup that
clears copiedTimeoutRef.current on unmount to avoid setState on unmounted
component; reference the handleCopy function, setCopied state updater, and
useCallback so reviewers can find and update the logic.

516-526: Interval continues running after all words are revealed.

The interval keeps firing every 15ms even after displayedWordCount equals targetWordCount. Consider clearing the interval when reveal completes to reduce unnecessary state updates.

Proposed fix
     const intervalId = setInterval(() => {
       setDisplayedWordCount(prev => {
         if (prev < targetWordCountRef.current) {
           return prev + 1;
         }
+        clearInterval(intervalId);
         return prev;
       });
     }, 15);

     return () => clearInterval(intervalId);
-  }, [hasContent]);
+  }, [hasContent]);

Alternatively, use a ref to track the interval ID and clear it from within the state updater callback.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/commands/ai-chat-shared.tsx` around lines 516 -
526, The interval created in the effect keeps running even after
displayedWordCount reaches targetWordCountRef.current; update the logic so the
interval is cleared when the reveal completes: store the interval ID
(intervalId) in a ref or accessible variable, and inside the
setDisplayedWordCount updater check if prev < targetWordCountRef.current and if
the next value will reach or exceed the target, call clearInterval(intervalId)
before returning the final value; ensure the effect cleanup still clears the
interval to cover unmounts (references: setDisplayedWordCount,
targetWordCountRef, intervalId).
apps/dashboard/src/components/commands/ask-ai.tsx (1)

69-69: Consider using findLast instead of reverse().find().

findLast is more idiomatic and avoids creating an intermediate reversed array. It's supported in ES2023+ (TypeScript 5+, Node 18+, modern browsers).

Proposed refactor
-  const lastAssistantMessage = messages.slice(1).reverse().find((m: UIMessage) => m.role === "assistant");
+  const lastAssistantMessage = messages.slice(1).findLast((m: UIMessage) => m.role === "assistant");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/commands/ask-ai.tsx` at line 69, The current
computation of lastAssistantMessage uses messages.slice(1).reverse().find(...)
which allocates an intermediate reversed array; replace it with the ES2023
String.prototype-like array method by calling findLast on the sliced array
(e.g., messages.slice(1).findLast(m => m.role === "assistant")) to avoid
reversing and reduce allocation; ensure the runtime/TS target supports
Array.prototype.findLast or add a polyfill if needed and update the reference to
the const lastAssistantMessage accordingly in ask-ai.tsx.
apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx (1)

77-77: Same as ask-ai.tsx: prefer findLast over reverse().find().

For consistency with the suggested refactor in ask-ai.tsx:

Proposed refactor
-  const lastAssistantMessage = messages.slice().reverse().find((m: UIMessage) => m.role === "assistant");
+  const lastAssistantMessage = messages.findLast((m: UIMessage) => m.role === "assistant");

Note: Unlike ask-ai.tsx, this doesn't skip the first message, so no .slice(1) needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx` at line 77,
Replace the messages.slice().reverse().find(...) pattern used to compute
lastAssistantMessage with Array.prototype.findLast for consistency and clarity:
locate the expression assigning lastAssistantMessage in ai-chat-widget.tsx (the
variable computed from messages and UIMessage) and change it to use
messages.findLast((m: UIMessage) => m.role === "assistant") so it directly
returns the last assistant message without creating a reversed copy.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx`:
- Around line 19-24: The component AIChatWidget calls React hooks (useState)
before returning early when isActive is false, violating the Rules of Hooks; fix
by moving the early return to the top of AIChatWidget before any hooks or by
refactoring stateful logic into an inner component (e.g., create
AIChatWidgetInner that holds the useState calls and onNewConversation reset
logic) and have AIChatWidget conditionally render that inner component, or
alternatively remove the isActive prop entirely and let the parent unmount
AIChatWidget (stack-companion.tsx already conditionally renders it) so hooks are
only used when mounted.

---

Outside diff comments:
In `@apps/dashboard/src/components/commands/ask-ai.tsx`:
- Line 127: The call runAsynchronously(handleFollowUp()) is a no-op because
handleFollowUp() returns void; either remove the wrapper and call
handleFollowUp() directly, or change handleFollowUp to return the promise from
runAsynchronously/sendMessage (e.g., have handleFollowUp return
runAsynchronously(sendMessage(...)) or be async and return the awaited promise).
Update the implementation of handleFollowUp (the function that currently calls
runAsynchronously(sendMessage(...))) to return that promise if you need to keep
runAsynchronously(...) here, otherwise call handleFollowUp() without wrapping.
- Line 239: The onClick currently invokes handleFollowUp immediately
(runAsynchronously(handleFollowUp())), passing undefined; change it to pass the
function reference instead or call the handler directly: either use onClick={()
=> runAsynchronously(handleFollowUp)} if you intend runAsynchronously to wrap
the handler, or simply use onClick={handleFollowUp} (or onClick={() =>
handleFollowUp()} if you prefer an arrow) so handleFollowUp is executed on click
rather than at render time.

---

Nitpick comments:
In `@apps/dashboard/src/components/commands/ai-chat-shared.tsx`:
- Around line 214-216: Add an inline comment explaining the use of `as` casts on
`invocation.input` and `invocation.output` near the `input`, `queryArg`, and
`result` declarations: state that these casts are narrowing the SDK-generic
types to the expected shapes produced by the backend tool definition, note that
runtime shape mismatches will still surface as errors, and mention where the
true canonical shape is defined (the backend tool/schema). Keep the comment
succinct and colocated with the `const input = invocation.input as { query?:
string } | undefined;` and `const result = invocation.output as { success?:
boolean, result?: unknown[], error?: string, rowCount?: number } | undefined;`
lines.
- Around line 52-56: handleCopy sets a timeout that calls setCopied after 1500ms
but never clears it, causing a potential state update on an unmounted component;
add a ref (e.g., copiedTimeoutRef) to store the timeout id, clear any existing
timeout before creating a new one inside handleCopy, and add a useEffect with a
cleanup that clears copiedTimeoutRef.current on unmount to avoid setState on
unmounted component; reference the handleCopy function, setCopied state updater,
and useCallback so reviewers can find and update the logic.
- Around line 516-526: The interval created in the effect keeps running even
after displayedWordCount reaches targetWordCountRef.current; update the logic so
the interval is cleared when the reveal completes: store the interval ID
(intervalId) in a ref or accessible variable, and inside the
setDisplayedWordCount updater check if prev < targetWordCountRef.current and if
the next value will reach or exceed the target, call clearInterval(intervalId)
before returning the final value; ensure the effect cleanup still clears the
interval to cover unmounts (references: setDisplayedWordCount,
targetWordCountRef, intervalId).

In `@apps/dashboard/src/components/commands/ask-ai.tsx`:
- Line 69: The current computation of lastAssistantMessage uses
messages.slice(1).reverse().find(...) which allocates an intermediate reversed
array; replace it with the ES2023 String.prototype-like array method by calling
findLast on the sliced array (e.g., messages.slice(1).findLast(m => m.role ===
"assistant")) to avoid reversing and reduce allocation; ensure the runtime/TS
target supports Array.prototype.findLast or add a polyfill if needed and update
the reference to the const lastAssistantMessage accordingly in ask-ai.tsx.

In `@apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx`:
- Line 77: Replace the messages.slice().reverse().find(...) pattern used to
compute lastAssistantMessage with Array.prototype.findLast for consistency and
clarity: locate the expression assigning lastAssistantMessage in
ai-chat-widget.tsx (the variable computed from messages and UIMessage) and
change it to use messages.findLast((m: UIMessage) => m.role === "assistant") so
it directly returns the last assistant message without creating a reversed copy.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6b013c2d-cf9b-4b12-bdf3-35a79bad649c

📥 Commits

Reviewing files that changed from the base of the PR and between f3e27f8 and 53ac1ff.

📒 Files selected for processing (3)
  • apps/dashboard/src/components/commands/ai-chat-shared.tsx
  • apps/dashboard/src/components/commands/ask-ai.tsx
  • apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx

Comment thread apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx Outdated
Copy link
Copy Markdown
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)
apps/dashboard/src/components/commands/ai-chat-shared.tsx (1)

20-20: Clarify the error message to reflect the actual fallback logic.

The error message states "NEXT_PUBLIC_BROWSER_STACK_API_URL is not set" but the code actually checks both NEXT_PUBLIC_BROWSER_STACK_API_URL and NEXT_PUBLIC_STACK_API_URL as fallbacks. This could mislead developers during debugging.

💡 Suggested improvement
-  const backendBaseUrl = getPublicEnvVar("NEXT_PUBLIC_BROWSER_STACK_API_URL") ?? getPublicEnvVar("NEXT_PUBLIC_STACK_API_URL") ?? throwErr("NEXT_PUBLIC_BROWSER_STACK_API_URL is not set");
+  const backendBaseUrl = getPublicEnvVar("NEXT_PUBLIC_BROWSER_STACK_API_URL") ?? getPublicEnvVar("NEXT_PUBLIC_STACK_API_URL") ?? throwErr("Neither NEXT_PUBLIC_BROWSER_STACK_API_URL nor NEXT_PUBLIC_STACK_API_URL is set");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/commands/ai-chat-shared.tsx` at line 20, The
backendBaseUrl assignment uses getPublicEnvVar for
NEXT_PUBLIC_BROWSER_STACK_API_URL then NEXT_PUBLIC_STACK_API_URL but the
throwErr message only mentions NEXT_PUBLIC_BROWSER_STACK_API_URL; update the
error text in the fallback throwErr call (used with backendBaseUrl) to
accurately list both env vars (e.g., "NEXT_PUBLIC_BROWSER_STACK_API_URL or
NEXT_PUBLIC_STACK_API_URL is not set") so logs reflect the actual fallback logic
involving getPublicEnvVar and throwErr.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/dashboard/src/components/commands/ai-chat-shared.tsx`:
- Around line 214-216: The code unsafely casts external AI invocation data using
"as" for invocation.input and invocation.output (variables input, queryArg,
result), which can cause runtime errors if the shape differs; replace these
casts with defensive runtime validation or safe access: check invocation?.input
and invocation?.output exist, use optional chaining (e.g.,
invocation?.input?.query) and validate types (e.g., typeof query === 'string',
Array.isArray for result.result, typeof result.error === 'string', typeof
result.success === 'boolean') before using values, or call a small
validator/guard function to assert the shape; if you truly rely on a backend
contract, add a clear comment near invocation stating the trust boundary and why
the cast is safe.

---

Nitpick comments:
In `@apps/dashboard/src/components/commands/ai-chat-shared.tsx`:
- Line 20: The backendBaseUrl assignment uses getPublicEnvVar for
NEXT_PUBLIC_BROWSER_STACK_API_URL then NEXT_PUBLIC_STACK_API_URL but the
throwErr message only mentions NEXT_PUBLIC_BROWSER_STACK_API_URL; update the
error text in the fallback throwErr call (used with backendBaseUrl) to
accurately list both env vars (e.g., "NEXT_PUBLIC_BROWSER_STACK_API_URL or
NEXT_PUBLIC_STACK_API_URL is not set") so logs reflect the actual fallback logic
involving getPublicEnvVar and throwErr.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6efb31bc-56a0-4668-bf1b-c804a7c71a38

📥 Commits

Reviewing files that changed from the base of the PR and between 53ac1ff and 4f1806b.

📒 Files selected for processing (1)
  • apps/dashboard/src/components/commands/ai-chat-shared.tsx

Comment thread apps/dashboard/src/components/commands/ai-chat-shared.tsx Outdated
Comment thread apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
apps/dashboard/src/components/commands/ai-chat-shared.tsx (1)

213-215: ⚠️ Potential issue | 🟠 Major

Stop casting AI tool payloads to the happy path.

invocation.input and invocation.output are external tool payloads. A non-string query or unexpected output shape can break this card when React renders it or when CopyButton tries to copy it.

🛡️ Defensive extraction sketch
-  const input = invocation.input as { query?: string } | undefined;
-  const queryArg = input?.query;
-  const result = invocation.output as { success?: boolean, result?: unknown[], error?: string, rowCount?: number } | undefined;
+  const queryArg =
+    typeof invocation.input === "object" &&
+    invocation.input !== null &&
+    "query" in invocation.input &&
+    typeof invocation.input.query === "string"
+      ? invocation.input.query
+      : undefined;
+
+  const result =
+    typeof invocation.output === "object" && invocation.output !== null
+      ? invocation.output
+      : undefined;

As per coding guidelines, "Do NOT use as/any/type casts or anything else to bypass the type system unless you specifically asked the user about it."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/commands/ai-chat-shared.tsx` around lines 213 -
215, The code currently force-casts invocation.input and invocation.output to
assumed shapes (variables input, queryArg, result) which can break rendering or
copying when external tool payloads differ; replace these casts with defensive
extraction: read invocation.input and invocation.output as unknown, check and
validate types at runtime (e.g., ensure input is an object and typeof
input.query === "string" before using it, and ensure output is an object and its
fields (success, result, error, rowCount) have expected primitive/array types),
provide safe fallbacks (empty string, [] or undefined) and only pass validated
values to the UI/CopyButton; update the code around variables
input/queryArg/result (and any components that consume them) to use these
validated values instead of type assertions.
🧹 Nitpick comments (1)
apps/dashboard/src/components/commands/ai-chat-shared.tsx (1)

20-22: Use urlString for the transport endpoint.

This still builds the API URL with raw interpolation. It relies on the env value already having the right slash shape and misses the repo’s standard URL helper.

As per coding guidelines, "Use urlString or encodeURIComponent() instead of normal string interpolation for URLs for consistency."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/commands/ai-chat-shared.tsx` around lines 20 -
22, The transport endpoint is built via raw string interpolation; replace it
with the repo URL helper by passing the base env + path to urlString so slashes
are normalized. Keep using getPublicEnvVar and the existing
backendBaseUrl/throwErr logic, then set the DefaultChatTransport api value using
urlString (e.g., urlString({ base: backendBaseUrl, path:
'/api/latest/ai/query/stream' })) instead of
`${backendBaseUrl}/api/latest/ai/query/stream` so the URL helper handles
encoding and slash normalization.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/dashboard/src/components/commands/ai-chat-shared.tsx`:
- Around line 507-525: The interval in the useEffect (using hasContent,
setInterval, clearInterval) never stops because the effect only depends on
hasContent; update the effect to depend on content (or hasContent) and
displayedWordCount (and read targetWordCountRef.current inside), and inside the
interval callback or in the effect body clear the interval as soon as
displayedWordCount >= targetWordCountRef.current so it stops waking the thread;
also ensure that when content changes you reset displayedWordCount
(setDisplayedWordCount(0)) and set up a fresh interval tied to the new
content/targetWordCountRef so the reveal restarts cleanly for new assistant
messages.

In `@apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx`:
- Around line 197-209: The send buttons render only an SVG (e.g., the button
that calls handleSubmit and contains PaperPlaneTiltIcon), so screen readers have
no label; add an accessible name by giving the button an aria-label (e.g.,
aria-label="Send message") or include visually-hidden text (e.g., a span with
class "sr-only" containing "Send message") inside the button; apply the same
change to the other icon-only send button instance (the second occurrence around
the other PaperPlaneTiltIcon) so both are accessible to assistive tech.
- Around line 123-130: handleInputKeyDown and handleFollowUpKeyDown currently
submit on Enter even during IME composition; add a guard that checks
e.nativeEvent.isComposing (or e.nativeEvent && e.nativeEvent.isComposing) and
return early if true before handling Enter so compositions (CJK) aren’t
submitted prematurely; update both functions (handleInputKeyDown and
handleFollowUpKeyDown) to perform this composition check at the top of the
handler and keep the existing Enter/shift logic unchanged.

---

Duplicate comments:
In `@apps/dashboard/src/components/commands/ai-chat-shared.tsx`:
- Around line 213-215: The code currently force-casts invocation.input and
invocation.output to assumed shapes (variables input, queryArg, result) which
can break rendering or copying when external tool payloads differ; replace these
casts with defensive extraction: read invocation.input and invocation.output as
unknown, check and validate types at runtime (e.g., ensure input is an object
and typeof input.query === "string" before using it, and ensure output is an
object and its fields (success, result, error, rowCount) have expected
primitive/array types), provide safe fallbacks (empty string, [] or undefined)
and only pass validated values to the UI/CopyButton; update the code around
variables input/queryArg/result (and any components that consume them) to use
these validated values instead of type assertions.

---

Nitpick comments:
In `@apps/dashboard/src/components/commands/ai-chat-shared.tsx`:
- Around line 20-22: The transport endpoint is built via raw string
interpolation; replace it with the repo URL helper by passing the base env +
path to urlString so slashes are normalized. Keep using getPublicEnvVar and the
existing backendBaseUrl/throwErr logic, then set the DefaultChatTransport api
value using urlString (e.g., urlString({ base: backendBaseUrl, path:
'/api/latest/ai/query/stream' })) instead of
`${backendBaseUrl}/api/latest/ai/query/stream` so the URL helper handles
encoding and slash normalization.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fc2e0636-d206-47cc-a428-ba1b304caea9

📥 Commits

Reviewing files that changed from the base of the PR and between 4f1806b and b12cd6f.

📒 Files selected for processing (3)
  • apps/dashboard/src/components/commands/ai-chat-shared.tsx
  • apps/dashboard/src/components/stack-companion.tsx
  • apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/dashboard/src/components/stack-companion.tsx

Comment thread apps/dashboard/src/components/commands/ai-chat-shared.tsx Outdated
Comment thread apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx
Comment thread apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx
Copy link
Copy Markdown
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: 2

♻️ Duplicate comments (1)
apps/dashboard/src/components/commands/ai-chat-shared.tsx (1)

213-215: ⚠️ Potential issue | 🟡 Minor

Avoid raw casts on streamed tool payloads.

invocation.input and invocation.output are the trust boundary for streamed tool data. These as casts drop type safety right where the UI branches on query, success, and rowCount. Please narrow the shape or add a short comment documenting the backend contract if this payload is guaranteed.

As per coding guidelines, "Do NOT use as/any/type casts or anything else to bypass the type system unless you specifically asked the user about it."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/commands/ai-chat-shared.tsx` around lines 213 -
215, The code currently unsafely casts invocation.input and invocation.output
with "as", which bypasses the type system; replace these raw casts by defining a
narrow runtime-validated shape (e.g., ToolInvocationInput { query?: string } and
ToolInvocationOutput { success?: boolean; result?: unknown[]; error?: string;
rowCount?: number }) and either perform a small type-guard function
(isToolInvocationInput/isToolInvocationOutput) that checks typeof/Array.isArray
for the fields before using them or access fields defensively (optional chaining
+ typeof checks) when reading query, success, and rowCount; update uses of
invocation.input, queryArg, invocation.output, and result to use the
guard/validated value and add a short comment documenting the backend contract
if the validation can be guaranteed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/dashboard/src/components/commands/ai-chat-shared.tsx`:
- Around line 61-79: The icon-only copy button lacks an accessible name; update
the button element (the CopyButton usage in ai-chat-shared.tsx that calls
runAsynchronously(handleCopy()), uses the copied state, and title prop) to
include an explicit aria-label (e.g., aria-label={copied ? "Copied" : "Copy"})
so screen readers get a reliable label even when title is ignored; ensure the
aria-label mirrors the existing title logic and remains in sync with the copied
state.

In `@apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx`:
- Around line 187-198: The input currently uses only placeholder text (the JSX
input with ref={inputRef}, value={input}, onChange and
onKeyDown={handleInputKeyDown}) which is not accessible; add an accessible name
by either adding a visible <label> tied to that input via htmlFor/id or by
adding an appropriate aria-label (e.g., aria-label="Initial prompt" or
aria-label="Follow-up question") for both the initial prompt input and the
follow-up input referenced in the same file (the other input around the block at
lines 298-309), ensuring unique id attributes if you use labels and using the
same inputRef/handleInputKeyDown handlers unchanged.

---

Duplicate comments:
In `@apps/dashboard/src/components/commands/ai-chat-shared.tsx`:
- Around line 213-215: The code currently unsafely casts invocation.input and
invocation.output with "as", which bypasses the type system; replace these raw
casts by defining a narrow runtime-validated shape (e.g., ToolInvocationInput {
query?: string } and ToolInvocationOutput { success?: boolean; result?:
unknown[]; error?: string; rowCount?: number }) and either perform a small
type-guard function (isToolInvocationInput/isToolInvocationOutput) that checks
typeof/Array.isArray for the fields before using them or access fields
defensively (optional chaining + typeof checks) when reading query, success, and
rowCount; update uses of invocation.input, queryArg, invocation.output, and
result to use the guard/validated value and add a short comment documenting the
backend contract if the validation can be guaranteed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 04cb44af-9bfc-42b1-bc9a-6cf8cad32205

📥 Commits

Reviewing files that changed from the base of the PR and between b12cd6f and 7dc8669.

📒 Files selected for processing (3)
  • apps/dashboard/src/components/commands/ai-chat-shared.tsx
  • apps/dashboard/src/components/commands/ask-ai.tsx
  • apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx

Comment thread apps/dashboard/src/components/commands/ai-chat-shared.tsx
Comment thread apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx
aadesh18 and others added 2 commits March 30, 2026 12:33
Merging with AI in stack companion


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Added AI Chat functionality with conversation management capabilities
  * New "Ask AI" sidebar option for quick access to AI assistance
  * Ability to create, browse, and delete AI conversations
* Rich chat UI with support for code blocks, links, and tool invocations
  * Word-by-word message streaming for better readability

* **Bug Fixes**
* Fixed event propagation issue in list item action buttons to prevent
unintended triggering

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Comment thread apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx Outdated
@aadesh18 aadesh18 requested a review from N2D4 April 2, 2026 17:53
@aadesh18 aadesh18 enabled auto-merge (squash) April 13, 2026 17:40
@aadesh18 aadesh18 merged commit 8aa80ce into dev Apr 13, 2026
31 of 33 checks passed
@aadesh18 aadesh18 deleted the ai-in-stack-companion branch April 13, 2026 18:40
This was referenced Apr 13, 2026
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.

3 participants