Skip to content

Conversation

@rossmanko
Copy link
Contributor

@rossmanko rossmanko commented Aug 18, 2025

Summary by CodeRabbit

  • New Features

    • Optional web search (when API key provided) with UI blocks and integrated tool actions.
    • Sidebar now supports terminal command output with live streaming and can be opened from terminal entries.
    • Footer shown to signed-out users with Terms and Privacy links.
  • Refactor

    • Header redesigned for distinct desktop/mobile auth flows.
    • Unified sidebar state to handle file and terminal content.
  • Style

    • Improved wrapping/word-break for code and terminal displays; user messages render as plain text, assistant as Markdown.
  • Documentation

    • Env template updated with optional Web Search API key placeholder.
  • Chores

    • Added web search runtime dependency.

@vercel
Copy link

vercel bot commented Aug 18, 2025

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

Project Deployment Preview Comments Updated (UTC)
hackerai Ready Ready Preview Comment Aug 18, 2025 9:19pm

@coderabbitai
Copy link

coderabbitai bot commented Aug 18, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds an optional EXA web-search API key to env template, wires geolocation into the chat API and tool factory, introduces an Exa-backed webSearch tool, converts the sidebar to a content union (file | terminal) with streaming updates, and updates related UI, types, and dependencies.

Changes

Cohort / File(s) Summary
Env & Dependency
./.env.local.example, package.json
Add EXA_API_KEY placeholder to env example and add exa-js dependency (^1.8.27).
Chat API
app/api/chat/route.ts
Read geolocation(req) -> userLocation; pass userLocation into createTools; inject getAIHeaders() into downstream stream calls.
AI Headers & Title Gen
lib/actions/index.ts
Add getAIHeaders() (env-based Referer/X-Title) and truncateMiddle; use headers in title generation.
Tools Factory & Types
lib/ai/tools/index.ts, types/agent.ts
createTools now accepts userLocation: Geo; ToolContext includes userLocation; webSearch conditionally registered when EXA_API_KEY present.
Web Search Tool
lib/ai/tools/web-search.ts
New createWebSearchTool(context) using exa-js; Zod schema for input; uses context.userLocation and retries without location on location-related errors; returns results or error string.
Sidebar Types
types/chat.ts
Add SidebarTerminal and SidebarContent (union) plus isSidebarFile / isSidebarTerminal type guards.
Global State
app/contexts/GlobalState.tsx
Replace sidebarFile API with sidebarContent, openSidebar, and updateSidebarContent; update provider/value and closeSidebar behavior.
Auto-open & Streaming
app/hooks/useSidebarAutoOpen.ts
Auto-open now emits SidebarContent; detect terminal runs and stream data-terminal parts to updateSidebarContent for live sidebar updates.
Sidebar UI & Handlers
app/components/ComputerSidebar.tsx, app/components/tools/TerminalToolHandler.tsx, app/components/tools/FileToolsHandler.tsx
Sidebar supports file or terminal content; TerminalToolHandler opens sidebar with aggregated output; openFileInSidebaropenSidebar rename applied.
Message & Tool Rendering
app/components/MessagePartHandler.tsx, app/components/tools/WebSearchToolHandler.tsx
Add WebSearchToolHandler; user message parts render as plain pre-wrapped text; tool handler UI for web-search added.
Terminal UI
app/components/TerminalCodeBlock.tsx
Simplify terminal UI: remove copy/download/tooltips; add variant ("default"
Code Block Styling
app/components/ComputerCodeBlock.tsx
CSS tweaks for pre min-width and word-break when wrap enabled.
Sidebar Refactor Helpers
app/components/ComputerSidebar.tsx
Refactor to unified sidebarContent handling, add isSidebarFile/isSidebarTerminal usage and derived helpers for labels/icons/actions; render ComputerCodeBlock or TerminalCodeBlock accordingly.
Header, Footer & Page
app/components/Header.tsx, app/components/Footer.tsx, app/page.tsx
New Footer component; responsive Desktop/Mobile header split with auth actions; page.tsx renders Footer, uses useAppAuth(){ user, loading }, and adjusts empty-state layout.
Auth JSDoc
lib/auth/server.ts
Minor JSDoc formatting only (no behavior change).

Sequence Diagram(s)

sequenceDiagram
  participant C as Client
  participant API as /api/chat
  participant Geo as Vercel Geolocation
  participant Tools as createTools
  participant LLM as streamText

  C->>API: POST /api/chat
  API->>Geo: geolocation(req)
  Geo-->>API: userLocation
  API->>Tools: createTools(userID, writer, mode, execMode, userLocation)
  API->>LLM: streamText(..., headers: getAIHeaders())
  LLM-->>C: streamed response
Loading
sequenceDiagram
  participant Agent as Agent Tools
  participant Hook as useSidebarAutoOpen
  participant GS as GlobalState
  participant UI as ComputerSidebar

  Agent-->>Hook: tool status updates (input-available / streaming / output-available)
  Hook->>GS: openSidebar({ command, output: "", isExecuting: true, toolCallId })
  Agent-->>Hook: data-terminal streaming parts
  Hook->>GS: updateSidebarContent({ output: append(stream) })
  GS-->>UI: sidebarContent changed
  UI-->>User: sidebar terminal view updates live
Loading
sequenceDiagram
  participant Tool as webSearch Tool
  participant Exa as Exa API
  participant Ctx as ToolContext

  Tool->>Ctx: read userLocation
  Tool->>Exa: search(query, userLocation?)
  alt error mentions userLocation/country
    Tool->>Exa: retry search(query) without location
  end
  Exa-->>Tool: results
  Tool-->>Caller: results array or error string
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

I hopped through headers, keys held tight,
I sniffed the web by moonlit night,
A sidebar hum, output aglow,
Geo in paw, the results flow.
Rabbit cheers — the search takes flight! 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ac5fe4a and 8721692.

📒 Files selected for processing (7)
  • app/api/chat/route.ts (4 hunks)
  • app/components/TerminalCodeBlock.tsx (4 hunks)
  • app/components/tools/TerminalToolHandler.tsx (2 hunks)
  • app/hooks/useSidebarAutoOpen.ts (6 hunks)
  • app/page.tsx (5 hunks)
  • lib/actions/index.ts (2 hunks)
  • lib/ai/tools/web-search.ts (1 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch daily-branch-2025-08-18

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🔭 Outside diff range comments (2)
types/agent.ts (1)

15-20: Ensure userLocation is passed and remove unintended runtime imports of @vercel/functions

Your new required userLocation: Geo on ToolContext is a breaking change—every createTools(…) call must now include that fourth argument. Also, we’ve detected runtime imports of @vercel/functions that may leak into client bundles.

• lib/utils/safe-wait-until.ts:

import { waitUntil } from "@vercel/functions";

→ Confirm this module is only used server-side. If it ever ends up in client code, convert to a import type { … } or relocate it behind a server boundary.

• app/api/chat/route.ts (server):

import { geolocation } from "@vercel/functions";      // OK for server

const { tools, getSandbox } = createTools(
  userID,
  writer,
  mode,                   // ← missing userLocation!
);

Update to:

const { tools, getSandbox } = createTools(
  userID,
  writer,
  mode,
  geolocation            // pass as userLocation: Geo
);

Review any other createTools(...) invocations to ensure the fourth userLocation argument is supplied, and verify no client-side imports of @vercel/functions remain.

app/components/TerminalCodeBlock.tsx (1)

161-168: Fix invalid Tailwind class for word-break

[&_pre]:word-break-break-word isn’t a valid Tailwind utility. Use break-words instead; combined with whitespace-pre-wrap, it achieves the intended wrapping.

Apply this diff:

-    const baseClasses = `shiki not-prose relative bg-card text-sm font-[450] text-card-foreground [&_pre]:!bg-transparent [&_pre]:px-[1em] [&_pre]:py-[1em] [&_pre]:rounded-none [&_pre]:m-0 [&_pre]:min-w-0 ${heightClasses} ${
-      isWrapped
-        ? "[&_pre]:whitespace-pre-wrap [&_pre]:break-words [&_pre]:overflow-visible [&_pre]:word-break-break-word"
-        : "[&_pre]:overflow-x-auto [&_pre]:max-w-full"
-    }`;
+    const baseClasses = `shiki not-prose relative bg-card text-sm font-[450] text-card-foreground [&_pre]:!bg-transparent [&_pre]:px-[1em] [&_pre]:py-[1em] [&_pre]:rounded-none [&_pre]:m-0 [&_pre]:min-w-0 ${heightClasses} ${
+      isWrapped
+        ? "[&_pre]:whitespace-pre-wrap [&_pre]:break-words [&_pre]:overflow-visible"
+        : "[&_pre]:overflow-x-auto [&_pre]:max-w-full"
+    }`;
🧹 Nitpick comments (22)
lib/auth/server.ts (1)

24-26: Consider reducing error verbosity and using a structured logger

console.error may include sensitive/verbose details and can be noisy under load. Prefer a structured logger with levels/sampling, and avoid logging full error objects unless necessary.

-  } catch (error) {
-    console.error("Failed to get user session:", error);
+  } catch (error) {
+    // TODO: route through structured logger and reduce verbosity in production
+    console.error("Failed to get user session");
app/components/MessagePartHandler.tsx (1)

21-35: Optional: treat all non-assistant roles as plain text

To be conservative, you can render Markdown only for assistant messages and treat all other roles (user/system/tool) as plain text. This reduces accidental Markdown rendering for roles you might add later.

Apply this diff:

-    const isUser = message.role === "user";
+    const isAssistant = message.role === "assistant";

-    // For user messages, render plain text to avoid markdown processing
-    if (isUser) {
+    // For non-assistant roles, render plain text to avoid markdown processing
+    if (!isAssistant) {
       return (
         <div key={partId} className="whitespace-pre-wrap">
           {part.text ?? ""}
         </div>
       );
     }

-    // For assistant messages, use markdown rendering
+    // Assistant messages: use markdown rendering
app/components/ComputerCodeBlock.tsx (2)

111-121: A11y: reflect toggle state with aria-pressed.

Expose the pressed state for assistive tech on the wrap toggle button.

                 <button
                   onClick={handleToggleWrap}
                   className={`inline-flex items-center justify-center rounded-md px-3 py-1 text-xs transition-colors ${
                     isWrapped
                       ? "bg-background text-foreground shadow-sm"
                       : "text-muted-foreground hover:bg-background hover:text-foreground"
                   }`}
                   aria-label={
                     isWrapped ? "Disable text wrapping" : "Enable text wrapping"
                   }
+                  aria-pressed={isWrapped}
                 >

61-64: Prefer falling back to download if File System Access API fails.

If the native save dialog throws (not user cancel), returning early prevents the fallback download. Let it continue to the fallback for better resilience.

-    } catch {
-      toast.error("Failed to save file");
-      return;
-    }
+    } catch {
+      toast.error("Failed to save file, falling back to download");
+      // Continue to fallback below
+    }
lib/actions/index.ts (1)

34-38: Centralize header construction to avoid duplication and drift.

Since similar headers are also set in app/api/chat/route.ts, consider factoring a small helper to keep them in sync.

Create lib/ai/providerHeaders.ts:

// lib/ai/providerHeaders.ts
export const buildProviderHeaders = () => ({
  "HTTP-Referer": process.env.NEXT_PUBLIC_APP_URL ?? "https://www.hackerai.co",
  "X-Title": process.env.NODE_ENV === "development" ? "HackerAI-Dev" : "HackerAI",
});

Then here:

-    headers: {
-      "HTTP-Referer": "https://www.hackerai.co",
-      "X-Title":
-        process.env.NODE_ENV === "development" ? "HackerAI-Dev" : "HackerAI",
-    },
+    headers: buildProviderHeaders(),

And mirror that usage in your route handler.

types/chat.ts (1)

24-34: Prefer a discriminated union over ad-hoc property checks for future-proofing.

Current type guards rely on "path" and "command" existence. This works but can become brittle if shapes evolve. A discriminant (e.g., kind: "file" | "terminal") simplifies narrowing and prevents accidental property collisions.

Apply this diff to introduce a discriminant while keeping existing fields:

 export interface SidebarFile {
+  kind: "file";
   path: string;
   content: string;
   language?: string;
   range?: {
     start: number;
     end: number;
   };
   action?: "reading" | "creating" | "editing" | "writing";
 }

 export interface SidebarTerminal {
+  kind: "terminal";
   command: string;
   output: string;
   isExecuting: boolean;
   isBackground?: boolean;
   toolCallId: string;
 }

 export type SidebarContent = SidebarFile | SidebarTerminal;

-export const isSidebarFile = (
-  content: SidebarContent,
-): content is SidebarFile => {
-  return "path" in content;
-};
+export const isSidebarFile = (
+  content: SidebarContent,
+): content is SidebarFile => content.kind === "file";
 
-export const isSidebarTerminal = (
-  content: SidebarContent,
-): content is SidebarTerminal => {
-  return "command" in content;
-};
+export const isSidebarTerminal = (
+  content: SidebarContent,
+): content is SidebarTerminal => content.kind === "terminal";

Note: This change will require updating producers to set kind accordingly.

app/components/Header.tsx (2)

25-69: Responsive split looks good; minor navigation improvement suggested.

Desktop/mobile split with mutually exclusive visibility is clear. Consider client-side navigation for a faster UX instead of full page reloads via window.location.

Apply this diff to use Next.js router:

 import React from "react";
 import { HackerAISVG } from "@/components/icons/hackerai-svg";
 import { Button } from "@/components/ui/button";
 import { useAppAuth } from "../hooks/useAppAuth";
 import { isWorkOSEnabled } from "@/lib/auth/client";
+import { useRouter } from "next/navigation";
 
 const Header: React.FC = () => {
-  const { user, loading } = useAppAuth();
+  const { user, loading } = useAppAuth();
+  const router = useRouter();
 
   const handleSignIn = () => {
-    window.location.href = "/login";
+    router.push("/login");
   };
 
   const handleSignUp = () => {
-    window.location.href = "/signup";
+    router.push("/signup");
   };
 
   const handleSignOut = async () => {
-    window.location.href = "/logout";
+    router.push("/logout");
   };

72-114: Consistent mobile UX; consider making the logo/title a home link.

Nice mobile treatment and button sizing. As a small UX boost, wrap the logo/title in a link to “/” to provide a predictable tap target for returning home.

Example:

-        <div className="flex items-center gap-2">
-          <HackerAISVG theme="dark" scale={0.12} />
-          <span className="text-foreground text-lg font-semibold">
-            HackerAI
-          </span>
-        </div>
+        <a href="/" className="flex items-center gap-2" aria-label="Go to home">
+          <HackerAISVG theme="dark" scale={0.12} />
+          <span className="text-foreground text-lg font-semibold">
+            HackerAI
+          </span>
+        </a>
app/components/Footer.tsx (1)

13-37: Add noopener for security when using target="_blank".

To avoid reverse tabnabbing, include noopener alongside noreferrer.

Apply this diff:

-          rel="noreferrer"
+          rel="noopener noreferrer"
         >
           Terms
         </a>{" "}
         and have read our{" "}
         <a
           href="/privacy-policy"
           target="_blank"
-          rel="noreferrer"
+          rel="noopener noreferrer"
         >
           Privacy Policy
         </a>
app/components/tools/FileToolsHandler.tsx (1)

64-69: Line-number stripping and range mapping are sensible; guard offset/limit interpretation.

Truthiness checks on offset/limit assume 1-based offsets. If offset could be 0, these branches may misbehave. Consider explicit undefined checks to avoid edge cases.

Apply this diff:

-      if (readInput.offset && readInput.limit) {
+      if (readInput.offset !== undefined && readInput.limit !== undefined) {
         return ` L${readInput.offset}-${readInput.offset + readInput.limit - 1}`;
       }
-      if (!readInput.offset && readInput.limit) {
+      if (readInput.offset === undefined && readInput.limit !== undefined) {
         return ` L1-${readInput.limit}`;
       }

And for the range payload:

-          const range =
-            readInput.offset && readInput.limit
+          const range =
+            readInput.offset !== undefined && readInput.limit !== undefined
               ? {
                   start: readInput.offset,
                   end: readInput.offset + readInput.limit - 1,
                 }
               : undefined;
app/page.tsx (1)

25-25: Leverage loading to prevent premature login redirects.

Right now loading is destructured but never used. If WorkOS auth is still resolving, a quick submit could incorrectly redirect to /login. Gate the redirect on !loading to avoid flicker and false negatives.

Example adjustment inside handleSubmit:

// Only require authentication in WorkOS mode
if (isWorkOSEnabled()) {
  if (loading) return; // wait for auth to resolve
  if (!user) {
    window.location.href = "/login";
    return;
  }
}
lib/ai/tools/web-search.ts (2)

31-35: Validate non-empty queries.

A zero-length query wastes a tool call. Enforce a minimum length.

-      query: z
-        .string()
+      query: z
+        .string()
+        .min(1, "Query must not be empty")

84-90: Standardize return shape for success and error.

Returning an array on success and a string on error forces downstream consumers to branch on types. Consider returning a consistent object like { results, error }.

If you want to proceed, confirm the tool consumer(s) can handle the new shape. I can generate the change across call sites.

app/components/tools/TerminalToolHandler.tsx (4)

41-47: Preserve separation between stdout and stderr.

Concatenating without a delimiter can blur outputs. Join with a newline to retain readability.

-    const stdout = terminalOutput?.result?.stdout ?? "";
-    const stderr = terminalOutput?.result?.stderr ?? "";
-    const combinedOutput = stdout + stderr;
+    const stdout = terminalOutput?.result?.stdout ?? "";
+    const stderr = terminalOutput?.result?.stderr ?? "";
+    const combinedOutput =
+      [stdout, stderr].filter(Boolean).join("\n");

78-90: Add keyboard focusability to clickable ToolBlock (input-available).

You pass onKeyDown but not tabIndex/role; without them, keyboard users may not be able to focus the element.

         <ToolBlock
           key={toolCallId}
           icon={<Terminal />}
           action="Executing"
           target={terminalInput?.command || ""}
           isShimmer={true}
           isClickable={true}
           onClick={handleOpenInSidebar}
           onKeyDown={handleKeyDown}
+          tabIndex={0}
+          role="button"
         />

97-105: Add keyboard focusability to clickable ToolBlock (output-available).

Same accessibility concern for the executed state.

         <ToolBlock
           key={toolCallId}
           icon={<Terminal />}
           action={hasOutput ? "Executed" : "Command completed"}
           target={terminalInput?.command || ""}
           isClickable={true}
           onClick={handleOpenInSidebar}
           onKeyDown={handleKeyDown}
+          tabIndex={0}
+          role="button"
         />

70-75: Minor: key prop on non-array children has no effect.

These components aren’t rendered as list items, so key={toolCallId} is unnecessary. Safe to remove.

Also applies to: 80-85, 98-100

app/components/ComputerSidebar.tsx (1)

69-85: Fallback action label could be friendlier.

Returning "Unknown action" (Line 83) is jarring in UI. Consider a neutral default like "Processing" to avoid confusing users.

-    return "Unknown action";
+    return "Processing";
app/hooks/useSidebarAutoOpen.ts (2)

34-55: Nit: Default isBackground to boolean

If upstream changes ever omit is_background, this can propagate undefined. Coercing to a boolean is safer and consistent with the prop type used by consumers.

Apply this diff:

-          isBackground: input.is_background,
+          isBackground: Boolean(input.is_background),

31-33: Prefer typed tool-part narrowing over any

Using any for tool parts erodes type safety in two hot paths. Consider introducing minimal discriminated unions for the terminal tool and data parts to avoid runtime surprises.

For example, define these locally or in your types module and use them for narrowing:

type TerminalToolPart = {
  type: "tool-runTerminalCmd";
  state: "input-available" | "output-available" | string;
  input?: { command?: string; is_background?: boolean };
  toolCallId: string;
};

type TerminalDataPart = {
  type: "data-terminal";
  data?: { toolCallId?: string; terminal?: string };
};

Then:

const toolPart = part as TerminalToolPart;
// ...
const terminalToolPart = (lastAssistantMessage.parts ?? []).find(
  (part): part is TerminalToolPart =>
    (part as any).type === "tool-runTerminalCmd" &&
    (part as any).toolCallId === sidebarContent.toolCallId,
);

Also applies to: 156-169

app/components/TerminalCodeBlock.tsx (1)

67-68: Cross-platform timeout type

NodeJS.Timeout may not match the browser typings depending on tsconfig libs. Use ReturnType<typeof setTimeout> for universal compatibility.

Apply this diff:

-  const renderTimeoutRef = useRef<NodeJS.Timeout | null>(null);
+  const renderTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
app/contexts/GlobalState.tsx (1)

66-74: Guarded content merge is fine, but consider variant-safe updates

Current shallow merge can accidentally mix fields across union variants (e.g., add output to a file content). Not harmful at runtime, but type-safety and invariants can drift. Optionally narrow by current variant and whitelist fields.

Apply this diff to narrow updates safely without importing extra types:

-  const updateSidebarContent = (updates: Partial<SidebarContent>) => {
-    setSidebarContent((current) => {
-      if (current) {
-        return { ...current, ...updates };
-      }
-      return current;
-    });
-  };
+  const updateSidebarContent = (updates: Partial<SidebarContent>) => {
+    setSidebarContent((current) => {
+      if (!current) return current;
+      // Discriminate on union by presence of 'path'
+      if ("path" in current) {
+        const {
+          path,
+          content,
+          range,
+          action,
+          // ignore terminal-only fields
+          command: _c,
+          output: _o,
+          isExecuting: _ie,
+          isBackground: _ib,
+          toolCallId: _tc,
+          ...rest
+        } = updates as any;
+        return {
+          ...current,
+          ...(path !== undefined ? { path } : {}),
+          ...(content !== undefined ? { content } : {}),
+          ...(range !== undefined ? { range } : {}),
+          ...(action !== undefined ? { action } : {}),
+        };
+      } else {
+        const {
+          command,
+          output,
+          isExecuting,
+          isBackground,
+          toolCallId,
+          // ignore file-only fields
+          path: _p,
+          content: _fc,
+          range: _r,
+          action: _a,
+          ...rest
+        } = updates as any;
+        return {
+          ...current,
+          ...(command !== undefined ? { command } : {}),
+          ...(output !== undefined ? { output } : {}),
+          ...(isExecuting !== undefined ? { isExecuting } : {}),
+          ...(isBackground !== undefined ? { isBackground } : {}),
+          ...(toolCallId !== undefined ? { toolCallId } : {}),
+        };
+      }
+    });
+  };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e1e7e8f and 3167023.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (20)
  • .env.local.example (1 hunks)
  • app/api/chat/route.ts (4 hunks)
  • app/components/ComputerCodeBlock.tsx (1 hunks)
  • app/components/ComputerSidebar.tsx (3 hunks)
  • app/components/Footer.tsx (1 hunks)
  • app/components/Header.tsx (1 hunks)
  • app/components/MessagePartHandler.tsx (1 hunks)
  • app/components/TerminalCodeBlock.tsx (4 hunks)
  • app/components/tools/FileToolsHandler.tsx (4 hunks)
  • app/components/tools/TerminalToolHandler.tsx (2 hunks)
  • app/contexts/GlobalState.tsx (5 hunks)
  • app/hooks/useSidebarAutoOpen.ts (6 hunks)
  • app/page.tsx (4 hunks)
  • lib/actions/index.ts (1 hunks)
  • lib/ai/tools/index.ts (3 hunks)
  • lib/ai/tools/web-search.ts (1 hunks)
  • lib/auth/server.ts (1 hunks)
  • package.json (1 hunks)
  • types/agent.ts (2 hunks)
  • types/chat.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (11)
app/components/Footer.tsx (1)
app/hooks/useAppAuth.ts (1)
  • useAppAuth (8-18)
lib/ai/tools/web-search.ts (1)
types/agent.ts (1)
  • ToolContext (15-20)
app/components/tools/TerminalToolHandler.tsx (2)
app/contexts/GlobalState.tsx (1)
  • useGlobalState (105-111)
types/chat.ts (1)
  • SidebarTerminal (14-20)
app/components/Header.tsx (3)
components/icons/hackerai-svg.tsx (1)
  • HackerAISVG (8-67)
lib/auth/client.ts (1)
  • isWorkOSEnabled (15-16)
components/ui/button.tsx (1)
  • Button (59-59)
app/components/TerminalCodeBlock.tsx (1)
app/components/ShimmerText.tsx (1)
  • ShimmerText (12-39)
app/components/ComputerSidebar.tsx (4)
types/chat.ts (2)
  • isSidebarFile (24-28)
  • isSidebarTerminal (30-34)
components/ui/code-action-buttons.tsx (1)
  • CodeActionButtons (22-187)
app/components/ComputerCodeBlock.tsx (1)
  • ComputerCodeBlock (19-165)
app/components/TerminalCodeBlock.tsx (1)
  • TerminalCodeBlock (195-283)
app/page.tsx (2)
app/hooks/useAppAuth.ts (1)
  • useAppAuth (8-18)
app/components/ChatInput.tsx (1)
  • ChatInput (28-183)
lib/ai/tools/index.ts (3)
types/chat.ts (1)
  • ChatMode (1-1)
types/agent.ts (1)
  • ExecutionMode (22-22)
lib/ai/tools/web-search.ts (1)
  • createWebSearchTool (10-93)
app/components/tools/FileToolsHandler.tsx (1)
app/contexts/GlobalState.tsx (1)
  • useGlobalState (105-111)
app/contexts/GlobalState.tsx (1)
types/chat.ts (1)
  • SidebarContent (22-22)
app/hooks/useSidebarAutoOpen.ts (2)
types/chat.ts (2)
  • SidebarContent (22-22)
  • isSidebarFile (24-28)
app/contexts/GlobalState.tsx (1)
  • useGlobalState (105-111)
🔇 Additional comments (27)
lib/auth/server.ts (3)

12-12: Docblock spacing change is fine

The extra blank line in the JSDoc improves readability and is harmless.


20-23: Good use of dynamic import to keep client bundle clean

Dynamically importing "@workos-inc/authkit-nextjs" only when needed prevents accidental inclusion in client bundles. Nice.


6-8: No issues found with server-only import
The lib/auth/client.ts module does not contain a "use client" directive and is explicitly marked as safe for both client and server use. You can keep the isWorkOSEnabled import here without changes.

app/components/MessagePartHandler.tsx (2)

21-30: Good call: render user text as plain text

Rendering user messages as plain text avoids unintended Markdown parsing of untrusted input and reduces XSS surface. The whitespace-pre-wrap choice preserves user-entered formatting nicely.


32-35: Raw HTML is safely escaped by default in MemoizedMarkdown
MemoizedMarkdown invokes ReactMarkdown without the rehypeRaw plugin, so any raw HTML in the input is escaped rather than rendered. It also opens links with target="_blank" and rel="noopener noreferrer". No additional sanitization configuration is required unless you intend to enable raw‐HTML support.

types/agent.ts (1)

3-3: Type-only import is correct and safe for client bundles.

Using import type { Geo } from "@vercel/functions"; ensures no runtime import leakage into client code.

package.json (2)

29-29: Adding exa-js is appropriate for web search.

Dependency looks good and aligns with the new tool. Keep it server-only to avoid inflating client bundles.


29-29: All exa-js imports and EXA_API_KEY references are confined to server-side modules

  • lib/ai/tools/web-search.ts imports Exa from “exa-js” and reads process.env.EXA_API_KEY, and contains no “use client” directive.
  • lib/ai/tools/index.ts conditionally wires up webSearch based on process.env.EXA_API_KEY, also in a server-only file.

No client components import exa-js or reference EXA_API_KEY. ✅

lib/actions/index.ts (1)

34-38: Provider headers look correct for OpenRouter-style metadata.

Setting HTTP-Referer and X-Title at call time is aligned with provider requirements and mirrors the route handler.

.env.local.example (1)

9-12: Good addition: EXA_API_KEY template is clearly marked optional and not exposed client-side.

This aligns with the server-gated web search tool and avoids accidental client-side exposure.

types/chat.ts (1)

14-22: Solid union modeling for sidebar content.

Introducing SidebarTerminal and unifying via SidebarContent reads cleanly and aligns with the consuming components’ needs. Required fields on SidebarTerminal are sensible for streaming states, and SidebarFile remains backward compatible.

app/api/chat/route.ts (1)

33-33: Ensure geolocation(req) safely falls back on non-Vercel runtimes

File: app/api/chat/route.ts:33

We call:

const userLocation = geolocation(req);

– this works on Vercel, but on other platforms you must confirm:

  • geolocation(req) never throws and returns a default object (e.g. { country: null, region: null, ... }) when native geolocation isn’t available.
  • Downstream in createTools(…, userLocation) (around lines 63–65) handles missing or null location fields without error.
app/components/tools/FileToolsHandler.tsx (2)

11-11: API rename adoption looks correct.

Switching to openSidebar aligns with the new SidebarContent model and keeps payload shape consistent for file content.


132-146: Good: opening written file in sidebar with action context.

Click and keyboard handlers are both wired to openSidebar with the right payload. Nice a11y consideration with Enter/Space handling.

app/page.tsx (2)

119-124: Solid container height fix.

Using h-full on the chat column aligns its height with the parent wrapper and prevents layout gaps when the sidebar toggles. Looks good.


165-193: Empty state UX improvements are clear and cohesive.

Centering the ChatInput and adding Footer in the empty state creates a focused first-run experience without impacting the messages view.

lib/ai/tools/index.ts (1)

47-58: Conditional wiring of webSearch looks good.

Gating on EXA_API_KEY and exposing webSearch in ask mode alongside readFile is sane and keeps the surface area minimal.

app/components/ComputerSidebar.tsx (2)

16-20: Unified sidebar state integration is clean.

Switching to sidebarContent with type guards is a solid improvement and removes file-only assumptions.


176-193: Header and content composition for file/terminal are well-factored.

  • Left title area switching between filename and Terminal icon is intuitive.
  • CodeActionButtons content, filename, and language derivation for terminal vs file are sensible.
  • TerminalCodeBlock’s sidebar variant use aligns with the new sidebar UX.

No changes requested.

Also applies to: 197-214, 233-257

app/hooks/useSidebarAutoOpen.ts (4)

141-151: Good guardrails for terminal streaming updates

Nice gating: you exit early when there’s no content, it’s a file, the terminal isn’t executing, or the status isn’t streaming. This prevents unnecessary work and avoids accidental updates to file-type sidebars.


76-85: LGTM: readFile mapping and range calculation

Strips line numbers, computes inclusive end, and marks action as reading. Looks correct and consistent.


91-99: LGTM: writeFile sidebar payload

Writes the target path and contents with an explicit "writing" action. Matches the new SidebarContent shape.


124-139: Viewport-gated auto-open is prudent

Only opening the sidebar on >= 950px avoids jarring layout shifts on small screens. The SSR guard on window is also correct.

app/components/TerminalCodeBlock.tsx (2)

215-259: Sidebar vs default variants are cleanly separated

Good separation of concerns. Default variant renders a full terminal UI with header; sidebar variant renders a compact, full-bleed ANSI block. The “executing” shimmer conditions are coherent.


101-107: Verify bundling of the “houston” theme and ANSI grammar

I ran a search across the repo and didn’t find any explicit imports or Shiki config that would pre-bundle the “houston” theme or the “ansi” grammar. Without those assets registered up front, each call to codeToHtml(code, { lang: "ansi", theme: "houston" }) may fail, fall back to unstyled plain text, and spam the console—hurting both performance and user experience.

Please verify and, if needed, add explicit theme/grammar registration, for example:

  • Import the theme JSON so your bundler includes it:
    import houston from "shiki/themes/houston.json";
  • Register or preload the ANSI grammar:
    import ansi from "shiki/languages/ansi.tmLanguage.json";
  • Then initialize your highlighter once, e.g.:
    const highlighter = await getHighlighter({
      theme: houston,
      langs: [ansi],
    });
  • Or adjust your build/plugin settings to bundle all Shiki themes and grammars you’ll use.

That will prevent repeated load errors and fallback renders at runtime.

app/contexts/GlobalState.tsx (2)

61-64: openSidebar transition is correct

Setting content then opening the sidebar ensures consumers can render immediately without a flash of empty content.


80-96: Global state API surface looks good

The migration from file-based to content-based sidebar state (openSidebar/updateSidebarContent/sidebarContent) is coherent and matches the hook and component changes in this PR.

Copy link

@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 (5)
app/components/tools/WebSearchToolHandler.tsx (4)

4-7: Type ‘part’ precisely instead of any.

Using any hides shape issues at compile time. Define a dedicated WebSearchPart type to catch mismatches early.

Apply this diff to tighten the prop type:

 interface WebSearchToolHandlerProps {
-  part: any;
+  part: WebSearchPart;
   status: "ready" | "submitted" | "streaming" | "error";
 }

Add these types above the interface:

type WebSearchState = "input-streaming" | "input-available" | "output-available";

interface WebSearchInput {
  query: string;
  explanation?: string;
}

interface WebSearchPart {
  toolCallId: string;
  state: WebSearchState;
  input?: WebSearchInput;
}

13-17: Guard against missing or non-string input.query.

If input is undefined or query is not a string, target={webInput.query} could pass an unexpected value. Be defensive and normalize to string | undefined.

Apply this diff:

-  const { toolCallId, state, input } = part;
-  const webInput = input as {
-    query: string;
-    explanation?: string;
-  };
+  const { toolCallId, state, input } = part;
+  const webInput = (input ?? {}) as Partial<{
+    query: string;
+    explanation?: string;
+  }>;
+  const query = typeof webInput.query === "string" ? webInput.query.trim() : undefined;
-          action="Searching web"
-          target={webInput.query}
+          action="Searching web"
+          target={query}
-          action="Searched web"
-          target={webInput.query}
+          action="Searched web"
+          target={query}

Also applies to: 35-36, 45-46


19-50: Reduce repetition and optionally handle error status.

The three branches repeat identical props (key, icon). Consider extracting baseProps and collapsing conditions. Also, a simple error-state block could improve UX.

Example refactor:

const baseProps = { key: toolCallId, icon: <Search /> };

if (status === "error") {
  return <ToolBlock {...baseProps} action="Web search failed" target={query} />;
}

if (state === "input-streaming" && status === "streaming") {
  return <ToolBlock {...baseProps} action="Searching web" isShimmer />;
}

if (state === "input-available" && status === "streaming") {
  return <ToolBlock {...baseProps} action="Searching web" target={query} isShimmer />;
}

if (state === "output-available") {
  return <ToolBlock {...baseProps} action="Searched web" target={query} />;
}

return null;

25-25: User-facing copy: minor grammar improvement.

“Searching the web” / “Searched the web” reads more naturally.

Apply this diff:

-          action="Searching web"
+          action="Searching the web"
-          action="Searching web"
+          action="Searching the web"
-          action="Searched web"
+          action="Searched the web"

Also applies to: 34-34, 44-44

app/components/MessagePartHandler.tsx (1)

22-33: Consider word-break utilities for long unbroken user input.

Rare but possible: very long tokens (e.g., pasted hashes/URLs) can overflow. Add break-words or break-all if this becomes an issue.

Example:

-        <div key={partId} className="whitespace-pre-wrap">
+        <div key={partId} className="whitespace-pre-wrap break-words">
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3167023 and 8f127c2.

📒 Files selected for processing (2)
  • app/components/MessagePartHandler.tsx (3 hunks)
  • app/components/tools/WebSearchToolHandler.tsx (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
app/components/MessagePartHandler.tsx (1)
app/components/tools/WebSearchToolHandler.tsx (1)
  • WebSearchToolHandler (9-51)
🔇 Additional comments (6)
app/components/tools/WebSearchToolHandler.tsx (2)

39-47: LGTM: Output rendering is clear.

The final, non-shimmer ToolBlock with action and target communicates the result succinctly.


1-2: ToolBlock import path validated

The file components/ui/tool-block.tsx exists with the exact lowercase name, matching the import in app/components/tools/WebSearchToolHandler.tsx. Alias resolution is configured correctly and there are no casing mismatches—no changes required.

app/components/MessagePartHandler.tsx (4)

5-5: LGTM: Added web-search handler import.

Import path looks correct relative to this file.


22-33: Good call rendering user text as plain text.

Avoiding Markdown for user messages reduces XSS/formatting surprises and keeps UX consistent for user-authored content.


51-53: LGTM: Added case for tool-webSearch.

The handler wiring aligns with the new tool UI component.


51-53: Validation complete: “tool-webSearch” part is correctly emitted

  • In lib/ai/tools/index.ts the factory spreads { webSearch: createWebSearchTool(context) }.
  • In app/components/MessagePartHandler.tsx there is a case "tool-webSearch" branch rendering <WebSearchToolHandler/>.

The tool key (webSearch) and the case label (tool-webSearch) align—no gaps detected.

Copy link

@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)
app/page.tsx (1)

11-11: Footer import and conditional rendering LGTM; optional semantic tweak

Importing and placing Footer in the empty-state area works, and the component self-gates on auth state. Optionally, wrap it in a semantic footer tag.

Apply this small semantic tweak:

-              <div className="flex-shrink-0">
-                <Footer />
-              </div>
+              <footer className="flex-shrink-0">
+                <Footer />
+              </footer>

Also applies to: 193-197

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8f127c2 and ac5fe4a.

📒 Files selected for processing (5)
  • app/components/Footer.tsx (1 hunks)
  • app/components/Header.tsx (1 hunks)
  • app/components/MessagePartHandler.tsx (3 hunks)
  • app/components/tools/WebSearchToolHandler.tsx (1 hunks)
  • app/page.tsx (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • app/components/MessagePartHandler.tsx
  • app/components/Footer.tsx
  • app/components/tools/WebSearchToolHandler.tsx
  • app/components/Header.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
app/page.tsx (3)
app/hooks/useAppAuth.ts (1)
  • useAppAuth (8-18)
app/components/MemoizedMarkdown.tsx (2)
  • h1 (26-32)
  • p (89-93)
app/components/ChatInput.tsx (1)
  • ChatInput (28-183)
🔇 Additional comments (3)
app/page.tsx (3)

110-117: Empty-state header placement LGTM

Wrapping the Header in a flex-shrink-0 container and showing it only in the empty state is a clean layout separation. No issues spotted.


118-126: Good flex and overflow guards

The switch to flex max-w-full flex-1 min-h-0 and adding h-full plus min-w-0 on the chat panel addresses nested scrolling and overflow clipping. Solid layout hardening.

Also applies to: 121-121


167-190: Centered empty-state UX looks solid

The centered title + subtitle and ChatInput with isCentered provide a clean first-use experience. Structure and spacing look good.

@rossmanko rossmanko merged commit c9c9680 into main Aug 18, 2025
2 of 3 checks passed
This was referenced Aug 20, 2025
@coderabbitai coderabbitai bot mentioned this pull request Sep 8, 2025
@coderabbitai coderabbitai bot mentioned this pull request Sep 29, 2025
@coderabbitai coderabbitai bot mentioned this pull request Jan 3, 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.

2 participants