Skip to content

Conversation

@rossmanko
Copy link
Contributor

@rossmanko rossmanko commented Nov 24, 2025

  • Add Previous/Next buttons and slider to navigate between tool executions
  • Implement 'Jump to Live' button to view latest tool result
  • Extract navigation logic to useSidebarNavigation hook for better organization
  • Add auto-follow: automatically update sidebar when at live position during streaming
  • Show green 'live' indicator when status is streaming
  • Pass status prop to ComputerSidebar to detect streaming state
  • Fix file content extraction to only include operations with output available
  • Remove useSidebarAutoOpen hook and auto-opener functionality
  • Update TerminalToolHandler to handle streaming updates directly

Summary by CodeRabbit

  • New Features

    • Sidebar footer with Prev/Next, progress slider, Jump to live and live indicator
    • Diff viewer for file edits (Diff / Original / Modified) and in-editor diff support
    • Sidebar navigation controls (Prev/Next/Jump/slider) with keyboard & ARIA improvements
    • Sidebar shows richer, real-time tool outputs (terminal, Python, file operations) and auto-follow when at live
  • Bug Fixes / Changes

    • Removed automatic sidebar auto-open; sidebar opens only via explicit navigation/manual actions

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

- Add Previous/Next buttons and slider to navigate between tool executions
- Implement 'Jump to Live' button to view latest tool result
- Extract navigation logic to useSidebarNavigation hook for better organization
- Add auto-follow: automatically update sidebar when at live position during streaming
- Show green 'live' indicator when status is streaming
- Pass status prop to ComputerSidebar to detect streaming state
- Fix file content extraction to only include operations with output available
- Remove useSidebarAutoOpen hook and auto-opener functionality
- Update TerminalToolHandler to handle streaming updates directly
@vercel
Copy link

vercel bot commented Nov 24, 2025

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

Project Deployment Preview Comments Updated (UTC)
hackerai Ready Ready Preview Comment Nov 24, 2025 8:53pm

@coderabbitai
Copy link

coderabbitai bot commented Nov 24, 2025

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objectives of the pull request: adding sidebar navigation controls (Previous/Next buttons, slider) and auto-follow functionality for tool executions.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/timeline-navigation-computer-sidebar

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

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

🧹 Nitpick comments (5)
app/share/[shareId]/SharedChatView.tsx (1)

19-36: Shared sidebar wrappers and messages plumbing look correct; consider tightening types

The desktop/mobile wrappers correctly bridge SharedChatContext into ComputerSidebarBase, and using messages || [] avoids undefined in navigation. You might later tighten the props to ChatMessage[] (or the shared message type) instead of any[] for better type safety, but the current wiring is functionally sound.

Also applies to: 43-58, 210-218

lib/utils/sidebar-utils.ts (2)

3-16: Tighten MessagePart/Message typing instead of broad any + index signatures

Right now MessagePart and Message are locally defined with any fields and [key: string]: any. This loses type-safety and can hide mismatches with upstream message shapes (e.g., data, input, output).

Consider importing and extending the existing MessagePart/message type from lib/utils/message-utils.ts (or the canonical chat types) instead of redefining them here, so changes to message structure remain consistent and type-checked.


52-69: Consider extracting shared “result → string output” helper

The logic to turn part.output?.result into a string (result.output vs stdout/stderr vs direct string) is repeated for terminal, Python, and partially for file-edit tools. This makes it easy for these branches to drift if result formats evolve.

A small internal helper like extractResultText(result: any): string used across these call sites would:

  • Ensure consistent handling of new/legacy formats.
  • Centralize any future adjustments to result shape.
  • Reduce cognitive overhead in extractAllSidebarContent.

Not blocking, but would make this utility more maintainable.

Also applies to: 90-106, 170-176

app/components/ComputerSidebar.tsx (2)

75-106: Clarify auto-follow condition vs. “during streaming” comment

The auto-follow effect comment says “during streaming”, but the condition only checks sidebarOpen, onNavigate, and toolExecutions.length, not status:

if (!sidebarOpen || !onNavigate || toolExecutions.length === 0) return;

If messages were to change outside of an active streaming phase, this effect would still auto-jump when at live. If you intend the behavior to be strictly tied to a streaming state, consider including status === "streaming" in the guard or updating the comment to match the actual behavior.


30-37: Use a concrete message type instead of any[] for messages

messages is currently typed as any[] on both ComputerSidebarProps and the ComputerSidebar wrapper:

messages?: any[];

Since useSidebarNavigation and extractAllSidebarContent expect a specific message shape, narrowing this to the actual Message type used in the chat layer will:

  • Catch mismatches at compile time.
  • Make usage clearer to callers.
  • Avoid accidental misuse from other parts of the app.

Also applies to: 516-521

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a192005 and 6807904.

📒 Files selected for processing (10)
  • app/components/ComputerSidebar.tsx (4 hunks)
  • app/components/Messages.tsx (1 hunks)
  • app/components/chat.tsx (2 hunks)
  • app/components/tools/TerminalToolHandler.tsx (3 hunks)
  • app/hooks/useChatHandlers.ts (0 hunks)
  • app/hooks/useSidebarAutoOpen.ts (0 hunks)
  • app/hooks/useSidebarNavigation.ts (1 hunks)
  • app/share/[shareId]/SharedChatView.tsx (4 hunks)
  • lib/utils/sidebar-utils.ts (1 hunks)
  • types/chat.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • app/hooks/useChatHandlers.ts
  • app/hooks/useSidebarAutoOpen.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-18T20:33:42.057Z
Learnt from: RostyslavManko
Repo: hackerai-tech/hackerai PR: 10
File: app/components/ComputerCodeBlock.tsx:154-158
Timestamp: 2025-08-18T20:33:42.057Z
Learning: In ComputerCodeBlock component, the wrap state should be controlled by the parent component (ComputerSidebar) which passes the wrap prop. The internal isWrapped state conflicts with parent state management when used in ComputerSidebar context.

Applied to files:

  • app/share/[shareId]/SharedChatView.tsx
  • app/components/ComputerSidebar.tsx
  • app/components/chat.tsx
🧬 Code graph analysis (4)
lib/utils/sidebar-utils.ts (2)
lib/utils/message-utils.ts (1)
  • MessagePart (5-8)
types/chat.ts (2)
  • SidebarContent (38-38)
  • SidebarFile (10-20)
app/share/[shareId]/SharedChatView.tsx (2)
app/share/[shareId]/SharedChatContext.tsx (1)
  • useSharedChatContext (41-49)
e2e/page-objects/ChatComponent.ts (1)
  • messages (28-32)
app/components/ComputerSidebar.tsx (2)
types/chat.ts (2)
  • SidebarContent (38-38)
  • ChatStatus (77-77)
app/hooks/useSidebarNavigation.ts (1)
  • useSidebarNavigation (16-124)
app/components/chat.tsx (2)
app/components/ComputerSidebar.tsx (1)
  • ComputerSidebar (516-533)
e2e/page-objects/ChatComponent.ts (1)
  • messages (28-32)
🔇 Additional comments (5)
types/chat.ts (1)

10-20: Adding toolCallId to SidebarFile looks consistent with the sidebar model

Making toolCallId optional on SidebarFile while it remains required on SidebarTerminal/SidebarPython fits the union and the matching logic in navigation; type guards remain valid.

app/components/chat.tsx (1)

769-777: Passing messages and status into ComputerSidebar is consistent across layouts

Both desktop and mobile sidebars now receive the live messages array and status from useChat, which is exactly what the navigation/auto-follow logic needs. No issues spotted here.

Also applies to: 790-794

app/components/Messages.tsx (1)

128-128: Comment accurately reflects removal of sidebar auto-open logic

The inline note makes it clear that sidebar opening is now entirely user-driven; no functional concerns here.

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

1-9: Terminal sidebar streaming sync and output resolution look correct

The memoized streamingOutput/finalOutput logic and the isSidebarActive-gated updateSidebarContent effect give you live, accurate sidebar updates while keeping changes scoped to the active tool call. The SidebarTerminal payload (including isExecuting) is consistent with the new sidebar model.

Also applies to: 21-23, 32-41, 42-61, 62-74, 76-93

app/components/ComputerSidebar.tsx (1)

47-48: Wrap state controlled by parent sidebar aligns with prior guidance

isWrapped is managed in ComputerSidebarBase and passed down as wrap/isWrapped props to ComputerCodeBlock and TerminalCodeBlock, with onToggleWrap wired from the sidebar:

const [isWrapped, setIsWrapped] = useState(true);
// ...
isWrapped={isWrapped}
onToggleWrap={handleToggleWrap}
// ...
<ComputerCodeBlock ... wrap={isWrapped} />
<TerminalCodeBlock ... wrap={isWrapped} />

This keeps wrapping behavior controlled from the parent sidebar, avoiding conflicting internal state inside ComputerCodeBlock, which matches the earlier learning for this component’s usage. Based on learnings, this looks good.

Also applies to: 326-327, 344-355, 370-397

… in code blocks

- Add bg-background to content containers in ComputerSidebar to prevent bg-muted/20 bleed-through
- Change TerminalCodeBlock from bg-card to bg-transparent for consistent backgrounds
- Remove overflow-visible that was causing background bleed-through
- Add transparent background classes to nested code and span elements
- Clean up redundant bg-background classes in nested containers
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

♻️ Duplicate comments (3)
app/hooks/useSidebarNavigation.ts (1)

98-101: Clamp currentIndex when computing progress to avoid negative percentages

When sidebarContent is null, currentIndex is -1. With multiple tool executions, getProgressPercentage returns a negative value, which can break slider UI.

Clamp the index into range before computing:

-  const getProgressPercentage = useMemo(() => {
-    if (toolExecutions.length <= 1) return 100;
-    return (currentIndex / (toolExecutions.length - 1)) * 100;
-  }, [currentIndex, toolExecutions.length]);
+  const getProgressPercentage = useMemo(() => {
+    if (toolExecutions.length <= 1) return 100;
+    const clampedIndex = Math.max(
+      0,
+      Math.min(currentIndex, toolExecutions.length - 1),
+    );
+    return (clampedIndex / (toolExecutions.length - 1)) * 100;
+  }, [currentIndex, toolExecutions.length]);
lib/utils/sidebar-utils.ts (1)

163-167: Fix falsy offset/limit check so ranges at offset 0 are not dropped

if (fileInput.offset && fileInput.limit) treats 0 as falsy, so a valid range starting at the beginning of a file won’t get a range attached, even when offset/limit are provided.

Use explicit numeric checks instead:

-          if (fileInput.offset && fileInput.limit) {
-            range = {
-              start: fileInput.offset,
-              end: fileInput.offset + fileInput.limit - 1,
-            };
-          }
+          if (
+            typeof fileInput.offset === "number" &&
+            typeof fileInput.limit === "number"
+          ) {
+            range = {
+              start: fileInput.offset,
+              end: fileInput.offset + fileInput.limit - 1,
+            };
+          }

This preserves existing behavior while correctly handling offset = 0.

app/components/ComputerSidebar.tsx (1)

339-408: Call getProgressPercentage() when building slider styles

In the slider track and handle styles you’re interpolating the function reference instead of its return value:

style={{
  left: "0%",
  width: `${getProgressPercentage}%`,
}}
...
style={{
  left: `${getProgressPercentage}%`,
}}

This will stringify the function (e.g. "function getProgressPercentage() {...}") and produce invalid CSS, breaking the visual progress and handle position. You need to call the function:

-                      style={{
-                        left: "0%",
-                        width: `${getProgressPercentage}%`,
-                      }}
+                      style={{
+                        left: "0%",
+                        width: `${getProgressPercentage()}%`,
+                      }}
...
-                      style={{
-                        left: `${getProgressPercentage}%`,
-                      }}
+                      style={{
+                        left: `${getProgressPercentage()}%`,
+                      }}

Everything else in the footer (prev/next buttons, live indicator based on status, and “Jump to live” CTA) looks good.

Also applies to: 410-514

🧹 Nitpick comments (5)
app/components/ComputerCodeBlock.tsx (1)

24-29: Sync isWrapped with wrap prop to avoid parent/child desync

isWrapped is initialized from wrap but never updated when wrap changes. If ComputerSidebar (or another parent) treats wrap as a controlled prop, its toggles can drift out of sync with the local state and button styling.

You can keep the local toggle while letting the parent remain source-of-truth on prop changes by syncing via an effect:

-import { useState, useMemo } from "react";
+import { useState, useMemo, useEffect } from "react";

 const ComputerCodeBlock = ({
   children,
   language,
   wrap = true,
   showButtons = true,
 }: ComputerCodeBlockProps) => {
   const codeContent = String(children);
   const [copied, setCopied] = useState(false);
   const [isWrapped, setIsWrapped] = useState(wrap);
+
+  useEffect(() => {
+    setIsWrapped(wrap);
+  }, [wrap]);

This preserves the local toggle behavior while keeping external wrap changes honored. Based on learnings, this avoids reintroducing the earlier parent/child wrap control conflict.

Also applies to: 153-195

app/hooks/useSidebarNavigation.ts (1)

10-24: Consider tightening messages type for better safety

messages: any[] works but loses the structure that extractAllSidebarContent expects (assistant messages with parts). If you have a shared Message/UIMessage type elsewhere, wiring it through here will make the hook safer and self-documenting.

No change is strictly required for functionality; this is more about future-proofing.

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

32-61: Streaming/final output aggregation and live sidebar sync look solid

The use of useMemo to aggregate streaming output and then layer newFormatOutput, legacy stdout/stderr, streaming chunks, result.error, and errorText into finalOutput is coherent and should cover both new and legacy formats.

The isSidebarActive + useEffect pattern for pushing { output, isExecuting } into updateSidebarContent only when the relevant terminal sidebar is open also avoids unnecessary global churn. One minor thing to keep in mind is that isExecuting here only checks for state === "input-available" && status === "streaming", whereas extractAllSidebarContent also treats "running" as executing; if you notice inconsistent “live” indicators between sidebar navigation and the active panel, it might be worth aligning those definitions.

Functionally, this looks good to ship.

Also applies to: 62-95

app/components/ComputerSidebar.tsx (2)

30-37: Prop threading for messages, onNavigate, and status looks consistent; consider tightening types later

The new props (messages, onNavigate, status) are wired cleanly through ComputerSidebarBase and the ComputerSidebar wrapper, and openSidebar as onNavigate matches the intended navigation flow. The only thing I’d consider later is replacing messages?: any[] with a more specific message type to get better TS safety.

Also applies to: 39-46, 524-539


48-73: Navigation hook and auto-follow behavior match the PR intent

The integration with useSidebarNavigation plus the previousToolCountRef + dual useEffect setup correctly implements “auto-follow when at live”: new tools only trigger onNavigate when the user was/is on the last execution, and the guard conditions (!sidebarOpen, !onNavigate, empty toolExecutions) prevent unnecessary work. The first effect’s dependency on just sidebarOpen also ensures the initial ref baseline is set when the sidebar opens. This all looks coherent with the described behavior.

Also applies to: 75-112, 16-16

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6807904 and f59f339.

📒 Files selected for processing (7)
  • app/components/ComputerCodeBlock.tsx (2 hunks)
  • app/components/ComputerSidebar.tsx (5 hunks)
  • app/components/TerminalCodeBlock.tsx (2 hunks)
  • app/components/tools/TerminalToolHandler.tsx (3 hunks)
  • app/hooks/useChatHandlers.ts (0 hunks)
  • app/hooks/useSidebarNavigation.ts (1 hunks)
  • lib/utils/sidebar-utils.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • app/hooks/useChatHandlers.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-18T20:33:42.057Z
Learnt from: RostyslavManko
Repo: hackerai-tech/hackerai PR: 10
File: app/components/ComputerCodeBlock.tsx:154-158
Timestamp: 2025-08-18T20:33:42.057Z
Learning: In ComputerCodeBlock component, the wrap state should be controlled by the parent component (ComputerSidebar) which passes the wrap prop. The internal isWrapped state conflicts with parent state management when used in ComputerSidebar context.

Applied to files:

  • app/components/ComputerSidebar.tsx
  • app/components/ComputerCodeBlock.tsx
🧬 Code graph analysis (4)
app/hooks/useSidebarNavigation.ts (2)
types/chat.ts (4)
  • SidebarContent (38-38)
  • isSidebarTerminal (46-50)
  • isSidebarFile (40-44)
  • isSidebarPython (52-56)
lib/utils/sidebar-utils.ts (1)
  • extractAllSidebarContent (18-200)
app/components/tools/TerminalToolHandler.tsx (2)
app/contexts/GlobalState.tsx (1)
  • useGlobalState (704-710)
types/chat.ts (2)
  • SidebarTerminal (22-29)
  • isSidebarTerminal (46-50)
lib/utils/sidebar-utils.ts (1)
types/chat.ts (2)
  • SidebarContent (38-38)
  • SidebarFile (10-20)
app/components/ComputerCodeBlock.tsx (1)
lib/utils/shiki.tsx (1)
  • ShikiErrorBoundary (23-45)
🔇 Additional comments (4)
app/components/TerminalCodeBlock.tsx (2)

159-168: AnsiCodeBlock styling additions look correct

Adding bg-transparent for pre/code/span and keeping the height utilities tied to isWrapped is consistent with the Shiki-based styling and shouldn’t introduce functional regressions.


261-280: Sidebar scroll container background change is safe

Using bg-background on the sidebar scroll container should fix transparency issues without affecting layout or streaming behavior.

lib/utils/sidebar-utils.ts (1)

23-123: Overall sidebar content extraction logic is well-structured

The separation between streaming maps (terminalDataMap/pythonDataMap) and the second pass that builds Terminal/Python/File SidebarContent entries keeps the logic readable and makes it straightforward to add new tool types later. The output precedence (new result.output → legacy stdout/stderr → streaming → output.output) also provides sensible fallbacks for mixed-format histories.

File operation handling (read/write/edit) with path normalization and read-file line-number stripping via /^\s*\d+\|/gm should give clean content for the sidebar. Aside from the range condition below, the behavior looks consistent with the SidebarFile contract.

Also applies to: 125-195

app/components/ComputerSidebar.tsx (1)

47-47: Parent-controlled wrap state aligns with previous ComputerCodeBlock guidance

Managing isWrapped in ComputerSidebarBase and passing wrap/onToggleWrap through to CodeActionButtons, ComputerCodeBlock, and TerminalCodeBlock keeps wrapping behavior centralized in the parent and avoids internal state conflicts in ComputerCodeBlock. This matches the earlier preference that wrap should be driven by the sidebar rather than the code block itself.

Based on learnings, this is the desired ownership of the wrap state.

Also applies to: 229-231, 332-333

- Remove diff-storage.ts utility (no longer needed)
- Simplify FileToolsHandler to use only stream data from data-diff parts
- Remove isTemporaryChat prop from FileToolsHandler (no longer needed)
- Update sidebar-utils to only use stream data, no localStorage fallback
- Diff view now only works during active session, shows result message after reload
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: 2

🧹 Nitpick comments (5)
lib/ai/tools/search-replace.ts (1)

5-7: Diff emission wiring looks good; consider guarding on missing toolCallId.

The new SearchReplaceOutput type, updated execute signature, and writer.write({ type: "data-diff", ... }) payload align cleanly with the sidebar diff flow and FileToolsHandler expectations. One small improvement: if toolCallId can ever be absent, you may want to skip emitting the diff (or log and return early) to avoid producing unmatchable data-diff parts downstream.

Also applies to: 10-11, 40-53, 105-115

app/components/DiffView.tsx (2)

20-76: Custom LCS diff is correct but can be expensive on large files.

computeLCS + computeDiff is logically sound, but the DP table is O(m·n) in both time and space. For very large files (thousands of lines), this could become slow or memory‑heavy. If you expect large diffs, consider a more incremental or chunked diff approach, or delegating to a battle‑tested diff library; otherwise this is fine for typical code‑sized inputs.

Also applies to: 81-119, 129-133


134-149: Tab keyboard handling is slightly redundant; wrap control matches parent‑owned pattern.

Since you’re using <button> elements, they already handle Enter/Space activation; the extra onKeyDown that also calls setViewMode is optional and may cause duplicate state updates. You can rely on onClick alone for simplicity. The wrap prop being threaded through to ComputerCodeBlock and DiffContent without internal state keeps wrapping under parent control, which aligns with the earlier ComputerSidebar/ComputerCodeBlock pattern. Based on learnings, this is the right ownership model.

Also applies to: 151-199, 208-257

app/components/tools/FileToolsHandler.tsx (1)

1-3: Diff streaming integration for search/replace is well‑wired; only minor defensive tweaks are optional.

The new DiffDataPart type, diffDataFromStream lookup keyed by toolCallId, and updated renderSearchReplaceTool ensure the sidebar opens with modifiedContent (and originalContent when available) instead of the summary string, while gracefully falling back to the previous behavior if no diff part was streamed. You might optionally add a small guard like if (!message.parts) return null; before the .find to make the diff extraction more defensive, but given how this component is used for tool parts, it’s not strictly necessary.

Also applies to: 8-17, 24-44, 249-287, 288-299, 301-317

app/components/ComputerSidebar.tsx (1)

69-75: Auto‑follow effect ignores status despite “during streaming” intent

The auto‑follow effect comment says:

// Auto-follow new tools when at live position during streaming

but the effect only checks sidebarOpen, onNavigate, and toolExecutions.length and never reads the status prop. As written, any increase in toolExecutions.length while the sidebar is open and the user is at live will cause an automatic jump, regardless of whether the chat is actually in "streaming" state.

If the intent is to limit auto‑follow to active streaming (and avoid unexpected jumps when, for example, historical messages are added or the status is "ready"), you could gate on status and add it to the dependencies:

-  useEffect(() => {
-    if (!sidebarOpen || !onNavigate || toolExecutions.length === 0) {
+  useEffect(() => {
+    if (
+      !sidebarOpen ||
+      !onNavigate ||
+      status !== "streaming" ||
+      toolExecutions.length === 0
+    ) {
       return;
     }
@@
-  }, [
-    toolExecutions.length,
-    currentIndex,
-    sidebarOpen,
-    onNavigate,
-    toolExecutions,
-  ]);
+  }, [
+    toolExecutions.length,
+    currentIndex,
+    sidebarOpen,
+    onNavigate,
+    toolExecutions,
+    status,
+  ]);

If you do intend auto‑follow to apply regardless of status, consider updating the comment to avoid confusion between code and documented behavior.

Also applies to: 76-113

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f59f339 and 4fef665.

📒 Files selected for processing (8)
  • app/components/ComputerSidebar.tsx (6 hunks)
  • app/components/DiffView.tsx (1 hunks)
  • app/components/MessagePartHandler.tsx (1 hunks)
  • app/components/tools/FileToolsHandler.tsx (2 hunks)
  • app/hooks/useSidebarNavigation.ts (1 hunks)
  • lib/ai/tools/search-replace.ts (3 hunks)
  • lib/utils/sidebar-utils.ts (1 hunks)
  • types/chat.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • types/chat.ts
  • lib/utils/sidebar-utils.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-18T20:33:42.057Z
Learnt from: RostyslavManko
Repo: hackerai-tech/hackerai PR: 10
File: app/components/ComputerCodeBlock.tsx:154-158
Timestamp: 2025-08-18T20:33:42.057Z
Learning: In ComputerCodeBlock component, the wrap state should be controlled by the parent component (ComputerSidebar) which passes the wrap prop. The internal isWrapped state conflicts with parent state management when used in ComputerSidebar context.

Applied to files:

  • app/components/DiffView.tsx
  • app/components/ComputerSidebar.tsx
🧬 Code graph analysis (6)
app/components/DiffView.tsx (2)
app/components/MemoizedMarkdown.tsx (1)
  • a (14-26)
app/components/ComputerCodeBlock.tsx (1)
  • ComputerCodeBlock (20-197)
app/components/MessagePartHandler.tsx (1)
app/components/tools/FileToolsHandler.tsx (1)
  • FileToolsHandler (24-402)
app/components/ComputerSidebar.tsx (5)
types/chat.ts (2)
  • SidebarContent (42-42)
  • ChatStatus (81-81)
components/ui/sidebar.tsx (1)
  • SidebarContent (753-753)
app/hooks/useSidebarNavigation.ts (1)
  • useSidebarNavigation (20-133)
app/components/DiffView.tsx (1)
  • DiffView (121-201)
app/components/ComputerCodeBlock.tsx (1)
  • ComputerCodeBlock (20-197)
app/components/tools/FileToolsHandler.tsx (2)
types/chat.ts (1)
  • ChatStatus (81-81)
app/contexts/GlobalState.tsx (1)
  • useGlobalState (704-710)
lib/ai/tools/search-replace.ts (1)
types/agent.ts (1)
  • ToolContext (19-29)
app/hooks/useSidebarNavigation.ts (2)
lib/utils/sidebar-utils.ts (2)
  • Message (12-16)
  • extractAllSidebarContent (18-226)
types/chat.ts (4)
  • SidebarContent (42-42)
  • isSidebarTerminal (50-54)
  • isSidebarFile (44-48)
  • isSidebarPython (56-60)
🔇 Additional comments (2)
app/components/MessagePartHandler.tsx (1)

70-76: Passing message into FileToolsHandler is consistent and necessary.

Wiring message={message} here matches how other tool handlers work and enables in‑memory diff lookup inside FileToolsHandler without changing control flow.

app/hooks/useSidebarNavigation.ts (1)

20-32: Navigation/index logic and progress clamping look solid.

Matching by toolCallId first, then falling back to content‑based matching per sidebar type, gives robust index resolution. Slider clicks are correctly clamped to valid indices, and getProgressPercentage now guards against negative indices and out‑of‑range values, keeping progress strictly within [0, 100] even when nothing is currently selected.

Also applies to: 35-63, 65-100, 102-119

Comment on lines 48 to +50
const [isWrapped, setIsWrapped] = useState(true);
const previousToolCountRef = useRef<number>(0);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Sidebar wrap toggle is currently out of sync with ComputerCodeBlock rendering

You manage isWrapped in ComputerSidebarBase and pass it both to CodeActionButtons and as wrap={isWrapped} to ComputerCodeBlock/TerminalCodeBlock, but ComputerCodeBlock still owns its own isWrapped state (initialized from wrap only once) and ignores subsequent prop changes. Because you set showButtons={false} here, there’s no way for the child to update its own isWrapped, so clicking the wrap control in the sidebar header updates only the sidebar’s isWrapped and not the actual code wrapping. The visible wrap toggle in the header will therefore not affect the file/Python/result blocks.

To make the new sidebar‑level wrap control actually work and avoid the parent/child conflict called out previously, I’d recommend syncing ComputerCodeBlock’s internal state from the wrap prop when it’s being controlled externally, e.g. something along these lines in ComputerCodeBlock.tsx:

// inside ComputerCodeBlock
const [isWrapped, setIsWrapped] = useState(wrap);

// When used in controlled mode (sidebar passes wrap and hides local buttons),
// keep internal state in sync with the prop.
useEffect(() => {
  if (!showButtons) {
    setIsWrapped(wrap);
  }
}, [wrap, showButtons]);

This lets standalone uses (with showButtons defaulting to true) keep their internal toggle, while sidebar uses (with showButtons={false} and a parent wrap prop) behave as a controlled component and reflect the sidebar’s isWrapped correctly. Based on learnings, this aligns with the goal of having the sidebar own wrap state.

Also applies to: 230-232, 308-336, 351-378

🤖 Prompt for AI Agents
In app/components/ComputerSidebar.tsx around lines 48-50 (and similarly at lines
230-232, 308-336, 351-378), the sidebar manages a wrap toggle but child
ComputerCodeBlock/TerminalCodeBlock components initialize their own isWrapped
state from the wrap prop only once and ignore subsequent prop changes; update
the child components so they initialize isWrapped from the wrap prop and add an
effect that, when the component is being externally controlled (showButtons is
false), synchronizes internal isWrapped with the incoming wrap prop (i.e., call
setIsWrapped(wrap) inside a useEffect that depends on [wrap, showButtons]); this
preserves standalone toggle behavior when showButtons is true and makes the
sidebar-controlled wrap toggle actually affect the rendered blocks when
showButtons is false.

Comment on lines 351 to +378
{isFile && (
<ComputerCodeBlock
language={
sidebarContent.language ||
getLanguageFromPath(sidebarContent.path)
}
wrap={isWrapped}
showButtons={false}
>
{sidebarContent.content}
</ComputerCodeBlock>
<>
{/* Show DiffView for editing actions with diff data */}
{sidebarContent.action === "editing" &&
sidebarContent.originalContent &&
sidebarContent.modifiedContent ? (
<DiffView
originalContent={sidebarContent.originalContent}
modifiedContent={sidebarContent.modifiedContent}
language={
sidebarContent.language ||
getLanguageFromPath(sidebarContent.path)
}
wrap={isWrapped}
/>
) : (
<ComputerCodeBlock
language={
sidebarContent.language ||
getLanguageFromPath(sidebarContent.path)
}
wrap={isWrapped}
showButtons={false}
>
{sidebarContent.content}
</ComputerCodeBlock>
)}
</>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Diff view guard will skip valid diffs when content is an empty string

The condition:

sidebarContent.action === "editing" &&
sidebarContent.originalContent &&
sidebarContent.modifiedContent

relies on truthiness, so cases where either side of the diff is an empty string (e.g. creating a new file, or clearing a file) will fall back to the plain ComputerCodeBlock path instead of showing a proper diff.

To treat “empty content” as valid while still requiring the fields to be present, consider tightening the check to explicit null/undefined:

- {sidebarContent.action === "editing" &&
-  sidebarContent.originalContent &&
-  sidebarContent.modifiedContent ? (
+ {sidebarContent.action === "editing" &&
+  sidebarContent.originalContent != null &&
+  sidebarContent.modifiedContent != null ? (

This keeps the guard against missing data but allows empty strings to still render through DiffView.

🤖 Prompt for AI Agents
In app/components/ComputerSidebar.tsx around lines 351 to 378, the DiffView
guard uses truthiness for sidebarContent.originalContent and
sidebarContent.modifiedContent which wrongly rejects empty-string content;
change the condition to explicitly check for null/undefined (e.g.
sidebarContent.originalContent != null && sidebarContent.modifiedContent !=
null) while keeping sidebarContent.action === "editing", so empty strings are
treated as valid diff content but missing fields still block DiffView.

@rossmanko rossmanko merged commit e0e282a into main Nov 24, 2025
4 checks passed
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