Skip to content

[codex] Polish workspace chat activity UI#601

Merged
urjitc merged 3 commits into
mainfrom
codex/workspace-chat-tool-polish
Jul 7, 2026
Merged

[codex] Polish workspace chat activity UI#601
urjitc merged 3 commits into
mainfrom
codex/workspace-chat-tool-polish

Conversation

@urjitc

@urjitc urjitc commented Jul 7, 2026

Copy link
Copy Markdown
Member

Summary

  • Add a structural workspace outline to AI context so the assistant sees folder/file shape without item bodies.
  • Smooth chat pending/message animations and remove the workspace skeleton ring.
  • Replace card-like tool activity with compact inline rows, concise status copy, source favicons, and expandable details.

Why

The workspace chat activity UI was too heavy for lay users: tool calls looked like cards, source details competed with the answer, and the pending indicator did not persist smoothly while work continued.

Changes

  • Adds a bounded workspace outline model, validation, and prompt formatting.
  • Keeps the ThinkEx thinking/working indicator visible at the transcript tail while streaming continues.
  • Uses explicit sent-message ids for user-message entry animation.
  • Maps visible tools to simple labels/icons and input-aware running/completed summaries.
  • Extracts web/research source previews so favicons stay inline and detailed source metadata lives behind expansion.

Testing

  • pnpm run check

Note: the command passes, with the existing local Node engine warning because this shell uses Node v25.8.0 while the repo declares >=24.11.0 <25.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

Review in cubic

Summary by CodeRabbit

  • New Features

    • AI chat now shows richer activity updates, including a new “working” state and clearer in-progress summaries.
    • Workspace AI context now includes a structured outline, helping provide more complete workspace-aware responses.
    • Tool activity rows can now show source previews, favicons, and expanded details for web/research results.
  • Bug Fixes

    • Improved chat message animation timing and reduced-motion behavior.
    • Refined tool labels and presentation text for clearer, more consistent wording.
    • Updated workspace card styling for a cleaner appearance.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@urjitc, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 45 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fff10d08-f6ed-4791-914b-09e6cccce552

📥 Commits

Reviewing files that changed from the base of the PR and between ac9bd13 and 2759d1a.

📒 Files selected for processing (6)
  • src/features/workspaces/ai/ai-tool-presentation.ts
  • src/features/workspaces/components/ai-chat/AiChatMessageList.tsx
  • src/features/workspaces/components/ai-chat/AiChatToolActivityRow.tsx
  • src/features/workspaces/components/ai-chat/ai-chat-display-state.ts
  • src/features/workspaces/components/ai-chat/ai-chat-tool-receipts.ts
  • src/features/workspaces/components/ai-chat/ai-chat-tool-source-previews.ts
📝 Walkthrough

Walkthrough

This PR adds tool activity icon derivation, working-state loaders, entry/layout animations for chat messages, source preview rendering with favicons for AI tool activities, richer running/completed tool receipt summaries, and a new workspace AI context outline feature (types, builder, prompt formatting, validation), plus CSS animation refactors and a minor skeleton style tweak.

Changes

AI Chat UX and Tool Activity

Layer / File(s) Summary
Tool icon derivation
src/features/workspaces/ai/ai-tool-presentation.ts
Adds AiToolActivityIconKind and getAiToolActivityIconKind, and updates title strings for several tools.
Loader and thinking-mark animation
src/features/workspaces/components/ai-chat/AiChatAssistantPending.tsx, src/styles.css
Adds a "working" loader state and AiChatWorkingLoader, refactors ThinkExThinkingMark into named animation blocks with matching CSS keyframes/delay classes.
Message list animations
src/features/workspaces/components/ai-chat/AiChatMessageList.tsx
Wraps rows in motion components, adds sentMessageAnimationId prop and entry-animation selection logic with reduced-motion support.
Thread view message id wiring
src/features/workspaces/components/ai-chat/AiChatThreadView.tsx, src/features/workspaces/components/ai-chat/types.ts
Generates a message id on send, tracks it for animation targeting, and adds id to AiChatSendMessage.
Tool activity row and source previews
src/features/workspaces/components/ai-chat/AiChatToolActivityRow.tsx, src/features/workspaces/components/ai-chat/ai-chat-tool-source-previews.ts
Reworks activity row UI with icons, favicons, expandable source lists, and motion; adds getToolSourcePreviews/getToolSourceHostname normalization logic.
Tool receipt summaries
src/features/workspaces/components/ai-chat/ai-chat-tool-receipts.ts, src/features/workspaces/components/ai-chat/ai-chat-display-state.ts
Adds getRunningToolReceipt, updates completed summaries to use tool input context, adds truncation/formatting helpers, and adds a "working" pending state.

Workspace AI Context Outline

Layer / File(s) Summary
Outline types
src/features/workspaces/model/workspace-ai-context-types.ts, src/features/workspaces/model/workspace-ai-context-prompt.ts
Adds WorkspaceAiContextOutline/WorkspaceAiContextOutlineItem types and prompt character-limit constants.
Outline builder
src/features/workspaces/model/workspace-ai-context-outline.ts
Implements buildWorkspaceAiContextOutline with folder/root selection, child counts, and memoized descendant counting.
Snapshot wiring
src/features/workspaces/model/workspace-ai-context-snapshot.ts
Populates workspace.outline in the built snapshot.
Prompt formatting
src/features/workspaces/model/workspace-ai-context-prompt.ts
Renders outline sections in prompts with header text, metadata, and truncation.
Validation
src/features/workspaces/model/workspace-ai-context-validation.ts
Adds runtime type guards validating the outline shape within snapshot validation.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant AiChatThreadView
  participant AiChatMessageList
  participant AiChatToolActivityRow
  participant ai-chat-tool-receipts

  User->>AiChatThreadView: submit prompt
  AiChatThreadView->>AiChatThreadView: generateId() for message
  AiChatThreadView->>AiChatMessageList: pass sentMessageAnimationId
  AiChatMessageList->>AiChatMessageList: getAiChatRowEntryAnimation(row)
  AiChatMessageList-->>User: render "sent" animation for matching row

  AiChatThreadView->>AiChatToolActivityRow: render tool activity (running/finished)
  AiChatToolActivityRow->>ai-chat-tool-receipts: getRunningToolReceipt / getFinishedToolReceipt
  ai-chat-tool-receipts-->>AiChatToolActivityRow: summary text
  AiChatToolActivityRow->>AiChatToolActivityRow: getToolSourcePreviews(activity)
  AiChatToolActivityRow-->>User: render icon, summary, favicons, status
Loading
sequenceDiagram
  participant buildWorkspaceAiContextSnapshot
  participant buildWorkspaceAiContextOutline
  participant formatWorkspaceAiContextForPrompt
  participant isWorkspaceAiContextSnapshot

  buildWorkspaceAiContextSnapshot->>buildWorkspaceAiContextOutline: build outline for workspace scope
  buildWorkspaceAiContextOutline-->>buildWorkspaceAiContextSnapshot: outline (included or summarized)
  buildWorkspaceAiContextSnapshot->>formatWorkspaceAiContextForPrompt: snapshot with outline
  formatWorkspaceAiContextForPrompt-->>formatWorkspaceAiContextForPrompt: format outline lines within char limits
  isWorkspaceAiContextSnapshot->>isWorkspaceAiContextSnapshot: validate outline via isWorkspaceAiContextOutline
Loading

Suggested labels: capy

Suggested reviewers: cubic-dev-ai

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main workspace chat UI polish in this changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/workspace-chat-tool-polish

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.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

React Doctor found no new issues. 🎉

Reviewed by React Doctor for commit 2759d1a.

@urjitc
urjitc force-pushed the codex/workspace-chat-tool-polish branch from c69373c to ac9bd13 Compare July 7, 2026 19:58
@urjitc
urjitc marked this pull request as ready for review July 7, 2026 20:04
@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds workspace structure to AI context and polishes the workspace chat activity UI. The main changes are:

  • A bounded workspace outline model, validation, and prompt formatting.
  • Smoother pending and sent-message animations in the chat transcript.
  • Compact inline tool activity rows with icons, status copy, favicons, and expandable details.
  • Source preview extraction for web and research tools.

Confidence Score: 4/5

Close to mergeable after fixing one contained UI interaction bug.

One issue affects expandable tool activity rows with source previews by nesting source links inside the collapsible trigger. The workspace outline, prompt formatting, and animation changes appear contained.

src/features/workspaces/components/ai-chat/AiChatToolActivityRow.tsx

T-Rex T-Rex Logs

What T-Rex did

  • I documented that nested source links inside a collapsible trigger could not be fully exercised due to the agent's step limit, and I mapped the render path from CollapsibleTrigger to ActivitySummary to InlineSourceFavicons.
  • I executed the check command pnpm run check in /home/user/repo; it exited with code 1 after about 2 seconds, indicating formatting issues surfaced during contract validation.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
src/features/workspaces/components/ai-chat/AiChatToolActivityRow.tsx Reworks tool activity rows with inline icons, source previews, and expandable details; contains invalid nested source links inside the collapsible trigger.
src/features/workspaces/model/workspace-ai-context-outline.ts Builds a bounded folder-first workspace outline with child and descendant counts.
src/features/workspaces/model/workspace-ai-context-prompt.ts Formats bounded workspace outline data into the AI context prompt.
src/features/workspaces/components/ai-chat/AiChatMessageList.tsx Adds reduced-motion-aware row entry and layout animations plus tail pending rendering.
src/features/workspaces/components/ai-chat/ai-chat-tool-receipts.ts Adds running summaries and more concise completed summaries for workspace, web, and research tools.
src/features/workspaces/components/ai-chat/ai-chat-tool-source-previews.ts Adds source preview extraction for web and research tool outputs.

Fix All in Cursor

Reviews (1): Last reviewed commit: "feat(ai-chat): render tool activity inli..." | Re-trigger Greptile

Comment on lines +62 to +63
<CollapsibleTrigger className="group/collapsible w-fit max-w-full text-left">
<ActivitySummary activity={activity} canExpand sourcePreviews={sourcePreviews} />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Avoid nested links
ActivitySummary renders InlineSourceFavicons as <a> elements when sources have URLs, but this summary is now inside CollapsibleTrigger, which renders an interactive trigger. Source favicon clicks are then nested inside another interactive control, so the row can toggle instead of opening the source and the markup is invalid for keyboard and screen-reader users. Render the favicons outside the trigger or make them non-linking while inside the trigger.

Fix in Cursor

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ac9bd13678

ℹ️ About Codex in GitHub

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

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

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

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +62 to +63
<CollapsibleTrigger className="group/collapsible w-fit max-w-full text-left">
<ActivitySummary activity={activity} canExpand sourcePreviews={sourcePreviews} />

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Move source favicons out of the trigger

When a web/research tool has sources, this ActivitySummary includes InlineSourceFavicons, which renders source <a> elements. With the default CollapsibleTrigger element, those links become nested interactive controls inside the trigger button, so clicking or focusing a favicon can toggle the row instead of opening the source and produces invalid HTML; keep the links outside the trigger or render the trigger as a non-button control.

Useful? React with 👍 / 👎.

Comment on lines +86 to +87
: !isBusy
? null

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Show pending state while submitted

When a send has just entered status === "submitted", awaitingFirstToken is true, but isBusy can still be false because it only reflects recovering/streaming/server-streaming flags. This branch returns null before checking awaitingFirstToken, so the transcript can show no Thinking row after a user submits until another streaming flag flips; check awaitingFirstToken before the !isBusy branch or include submitted in the busy condition.

Useful? React with 👍 / 👎.

@urjitc
urjitc force-pushed the codex/workspace-chat-tool-polish branch from ac9bd13 to 2759d1a Compare July 7, 2026 20:14
@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@capy-ai

capy-ai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Capy auto-review is paused for this organization because the usage-cycle auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews.

@urjitc
urjitc merged commit 75091ad into main Jul 7, 2026
7 of 8 checks passed
@urjitc
urjitc deleted the codex/workspace-chat-tool-polish branch July 7, 2026 20:18
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Dev Board Jul 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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)
src/features/workspaces/components/ai-chat/ai-chat-tool-source-previews.ts (1)

83-89: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate primitive helpers and hostname-parsing logic across files.

asRecord/getArray/getString here duplicate the same private helpers already present in ai-chat-tool-receipts.ts, and getToolSourceHostname duplicates the identical new URL(url).hostname.replace(/^www\./, "") logic used by formatUrl in that file. Consider extracting these into a shared module (e.g. a small record-utils.ts) imported by both files to avoid divergence.

Also applies to: 122-143

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/workspaces/components/ai-chat/ai-chat-tool-source-previews.ts`
around lines 83 - 89, The primitive helpers and hostname parsing logic are
duplicated between ai-chat-tool-source-previews and ai-chat-tool-receipts, so
centralize them into a shared module and import them from both places. Extract
asRecord/getArray/getString plus the hostname normalization used by
getToolSourceHostname/formatUrl into a common utility (for example a shared
record-utils helper), then update ai-chat-tool-source-previews and
ai-chat-tool-receipts to use the shared functions so the logic stays consistent
in one place.
src/features/workspaces/components/ai-chat/AiChatToolActivityRow.tsx (1)

92-116: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Drop the nested LazyMotion here. AiChatMessageList already wraps these rows in a shared LazyMotion, so this provider is redundant on every tool-activity row.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/workspaces/components/ai-chat/AiChatToolActivityRow.tsx` around
lines 92 - 116, The ToolActivityMotion component is creating a nested LazyMotion
provider that is already supplied by AiChatMessageList, so remove the redundant
LazyMotion wrapper and keep only the motion.div rendering path. Update
ToolActivityMotion in AiChatToolActivityRow.tsx to rely on the existing shared
motion context while preserving the disabled early return and the same animation
props on m.div.
src/features/workspaces/model/workspace-ai-context-outline.ts (3)

77-110: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Three full linear passes over all items on every outline build.

summarizeWorkspaceAiContextOutlineRows iterates input.rows three times even after addRow has stopped adding (once rows.length >= input.limit, subsequent calls are no-ops but the loops still run to completion). For very large workspaces this is wasted work on every chat message send; an early break once the limit is reached in the first two loops would avoid unnecessary iteration.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/workspaces/model/workspace-ai-context-outline.ts` around lines
77 - 110, The outline summarization in summarizeWorkspaceAiContextOutlineRows
still scans input.rows after the limit has already been reached, which wastes
work on large workspaces. Update the first two loops in
summarizeWorkspaceAiContextOutlineRows to stop iterating once rows.length
reaches input.limit, while keeping the existing addRow deduplication and
ordering behavior intact. Use the existing addRow helper, selectedItemIds set,
and rows collection to detect when to break early.

126-159: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Unbounded recursion in countDescendants has no cycle guard.

countDescendants recurses through childrenByParentId with no visited-set protection. This function runs on every sendMessage call via buildWorkspaceAiContextSnapshotbuildWorkspaceAiContextOutline, so if the parent/child graph is ever malformed (e.g. a cycle introduced by a bug elsewhere, or a very deep folder chain), this will stack-overflow and break message sending workspace-wide rather than just the outline feature.

♻️ Optional: add basic recursion-depth/cycle protection
 	const countDescendants = (itemId: string): number => {
 		const cachedCount = descendantCountsByItemId.get(itemId);
 
 		if (cachedCount !== undefined) {
 			return cachedCount;
 		}
 
+		descendantCountsByItemId.set(itemId, 0); // guard against cycles during recursion
 		const count = (childrenByParentId.get(itemId) ?? []).reduce(
 			(total, child) => total + 1 + countDescendants(child.id),
 			0,
 		);
 
 		descendantCountsByItemId.set(itemId, count);
 		return count;
 	};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/workspaces/model/workspace-ai-context-outline.ts` around lines
126 - 159, `getWorkspaceAiContextOutlineDescendantCounts` and its nested
`countDescendants` recursion need cycle/depth protection because malformed
parent-child graphs can cause infinite recursion and stack overflow. Add a
visited-set or recursion-stack guard inside `countDescendants` so repeated
traversal of the same item id is detected and stopped, and ensure the guard is
applied when `buildWorkspaceAiContextSnapshot` calls
`buildWorkspaceAiContextOutline`. Keep the existing descendant-count caching,
but make recursion safe for cycles and excessively deep folder chains.

1-160: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

No test coverage for the bounded outline builder.

This file implements non-trivial bounded/priority logic (folder-first summarization, memoized descendant counts, item-limit enforcement) but ships without unit tests in this batch. Given the truncation/priority rules are easy to regress silently (e.g., off-by-one at exactly the 50-item boundary, folder-vs-root-item precedence), adding focused unit tests for buildWorkspaceAiContextOutline boundary cases would materially increase confidence.
Want me to draft unit tests covering the exactly-at-limit, folder-priority, and empty-workspace cases?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/workspaces/model/workspace-ai-context-outline.ts` around lines 1
- 160, The bounded outline builder in buildWorkspaceAiContextOutline lacks unit
coverage for its truncation and prioritization behavior, so add focused tests
around the exact 50-item boundary, summarized vs included output, folder-first
selection in summarizeWorkspaceAiContextOutlineRows, and empty-workspace
handling. Use the public buildWorkspaceAiContextOutline entrypoint plus the
helper ordering logic to verify childCount/descendantCount and omittedItems stay
correct when limits are hit, and ensure the tests lock in root-item and folder
precedence so regressions are caught.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/features/workspaces/components/ai-chat/AiChatToolActivityRow.tsx`:
- Around line 276-306: The Favicon component currently fetches icons from Google
using the raw hostname, which leaks source hostnames and has no fallback if the
request fails. Update Favicon in AiChatToolActivityRow to avoid direct
google.com/s2/favicons calls by routing favicon loading through a first-party
proxy or equivalent internal source, and add load-failure handling so the
existing letter-avatar placeholder is shown when the image cannot load. Use a
state-based fallback in Favicon (with useState if needed) and keep the
hostname/title behavior intact.

In `@src/styles.css`:
- Around line 360-402: The .thinkex-thinking-mark rule has a stylelint
declaration-empty-line-before violation because overflow: visible follows the
custom property without the required blank line. Update the
.thinkex-thinking-mark block in the stylesheet so the overflow declaration is
separated by an empty line from --thinkex-thinking-cycle, matching the project’s
declaration spacing convention.

---

Nitpick comments:
In `@src/features/workspaces/components/ai-chat/ai-chat-tool-source-previews.ts`:
- Around line 83-89: The primitive helpers and hostname parsing logic are
duplicated between ai-chat-tool-source-previews and ai-chat-tool-receipts, so
centralize them into a shared module and import them from both places. Extract
asRecord/getArray/getString plus the hostname normalization used by
getToolSourceHostname/formatUrl into a common utility (for example a shared
record-utils helper), then update ai-chat-tool-source-previews and
ai-chat-tool-receipts to use the shared functions so the logic stays consistent
in one place.

In `@src/features/workspaces/components/ai-chat/AiChatToolActivityRow.tsx`:
- Around line 92-116: The ToolActivityMotion component is creating a nested
LazyMotion provider that is already supplied by AiChatMessageList, so remove the
redundant LazyMotion wrapper and keep only the motion.div rendering path. Update
ToolActivityMotion in AiChatToolActivityRow.tsx to rely on the existing shared
motion context while preserving the disabled early return and the same animation
props on m.div.

In `@src/features/workspaces/model/workspace-ai-context-outline.ts`:
- Around line 77-110: The outline summarization in
summarizeWorkspaceAiContextOutlineRows still scans input.rows after the limit
has already been reached, which wastes work on large workspaces. Update the
first two loops in summarizeWorkspaceAiContextOutlineRows to stop iterating once
rows.length reaches input.limit, while keeping the existing addRow deduplication
and ordering behavior intact. Use the existing addRow helper, selectedItemIds
set, and rows collection to detect when to break early.
- Around line 126-159: `getWorkspaceAiContextOutlineDescendantCounts` and its
nested `countDescendants` recursion need cycle/depth protection because
malformed parent-child graphs can cause infinite recursion and stack overflow.
Add a visited-set or recursion-stack guard inside `countDescendants` so repeated
traversal of the same item id is detected and stopped, and ensure the guard is
applied when `buildWorkspaceAiContextSnapshot` calls
`buildWorkspaceAiContextOutline`. Keep the existing descendant-count caching,
but make recursion safe for cycles and excessively deep folder chains.
- Around line 1-160: The bounded outline builder in
buildWorkspaceAiContextOutline lacks unit coverage for its truncation and
prioritization behavior, so add focused tests around the exact 50-item boundary,
summarized vs included output, folder-first selection in
summarizeWorkspaceAiContextOutlineRows, and empty-workspace handling. Use the
public buildWorkspaceAiContextOutline entrypoint plus the helper ordering logic
to verify childCount/descendantCount and omittedItems stay correct when limits
are hit, and ensure the tests lock in root-item and folder precedence so
regressions are caught.
🪄 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: d6b48e46-e102-4933-82ce-b5a8cbda130e

📥 Commits

Reviewing files that changed from the base of the PR and between f4d1022 and ac9bd13.

📒 Files selected for processing (16)
  • src/features/workspaces/ai/ai-tool-presentation.ts
  • src/features/workspaces/components/WorkspaceCardSkeleton.tsx
  • src/features/workspaces/components/ai-chat/AiChatAssistantPending.tsx
  • src/features/workspaces/components/ai-chat/AiChatMessageList.tsx
  • src/features/workspaces/components/ai-chat/AiChatThreadView.tsx
  • src/features/workspaces/components/ai-chat/AiChatToolActivityRow.tsx
  • src/features/workspaces/components/ai-chat/ai-chat-display-state.ts
  • src/features/workspaces/components/ai-chat/ai-chat-tool-receipts.ts
  • src/features/workspaces/components/ai-chat/ai-chat-tool-source-previews.ts
  • src/features/workspaces/components/ai-chat/types.ts
  • src/features/workspaces/model/workspace-ai-context-outline.ts
  • src/features/workspaces/model/workspace-ai-context-prompt.ts
  • src/features/workspaces/model/workspace-ai-context-snapshot.ts
  • src/features/workspaces/model/workspace-ai-context-types.ts
  • src/features/workspaces/model/workspace-ai-context-validation.ts
  • src/styles.css

Comment on lines +276 to +306
function Favicon({
className,
hostname,
title,
}: {
className?: string;
hostname: string | null;
title: string;
}) {
if (!hostname) {
return (
<Marker role="status" aria-live="polite" className="w-fit max-w-full py-1 text-sm">
<MarkerContent className="shimmer ai-status-shimmer">{activity.summary}</MarkerContent>
{canExpand ? <ChevronDown className="size-3 shrink-0" aria-hidden="true" /> : null}
</Marker>
<span
className={cn(
"grid size-3.5 shrink-0 place-items-center rounded-sm bg-muted text-[9px] text-muted-foreground",
className,
)}
>
{title.charAt(0).toUpperCase()}
</span>
);
}

return (
<Marker className="w-fit max-w-full py-1 text-sm">
<MarkerContent className="truncate text-muted-foreground/80">
{activity.summary}
</MarkerContent>
{canExpand ? <ChevronDown className="size-3 shrink-0" aria-hidden="true" /> : null}
</Marker>
<img
src={`https://www.google.com/s2/favicons?domain=${encodeURIComponent(hostname)}&sz=32`}
alt=""
className={cn("size-3.5 shrink-0 rounded-sm", className)}
loading="lazy"
/>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Favicon fetch leaks source hostnames to Google, with no failure fallback.

Favicon sends every tool-activity source hostname to google.com/s2/favicons, exposing what the AI agent searched/read to a third party without user consent — a privacy concern for any workspace containing sensitive research topics. It also lacks an onError handler, so a blocked/failed request (common with ad-blockers/privacy extensions) leaves a permanently broken image instead of falling back to the existing letter-avatar placeholder.

🔒 Suggested fix: add image-load fallback (and consider a first-party favicon proxy)
 function Favicon({
 	className,
 	hostname,
 	title,
 }: {
 	className?: string;
 	hostname: string | null;
 	title: string;
 }) {
-	if (!hostname) {
+	const [failed, setFailed] = useState(false);
+
+	if (!hostname || failed) {
 		return (
 			<span
 				className={cn(
 					"grid size-3.5 shrink-0 place-items-center rounded-sm bg-muted text-[9px] text-muted-foreground",
 					className,
 				)}
 			>
 				{title.charAt(0).toUpperCase()}
 			</span>
 		);
 	}

 	return (
 		<img
 			src={`https://www.google.com/s2/favicons?domain=${encodeURIComponent(hostname)}&sz=32`}
 			alt=""
 			className={cn("size-3.5 shrink-0 rounded-sm", className)}
 			loading="lazy"
+			onError={() => setFailed(true)}
 		/>
 	);
 }

Remember to import useState from react. For the privacy leak itself, consider proxying favicon requests through your own backend rather than calling Google directly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/workspaces/components/ai-chat/AiChatToolActivityRow.tsx` around
lines 276 - 306, The Favicon component currently fetches icons from Google using
the raw hostname, which leaks source hostnames and has no fallback if the
request fails. Update Favicon in AiChatToolActivityRow to avoid direct
google.com/s2/favicons calls by routing favicon loading through a first-party
proxy or equivalent internal source, and add load-failure handling so the
existing letter-avatar placeholder is shown when the image cannot load. Use a
state-based fallback in Favicon (with useState if needed) and keep the
hostname/title behavior intact.

Comment thread src/styles.css
Comment on lines 360 to +402
.thinkex-thinking-mark {
--thinkex-thinking-cycle: 2.2s;
overflow: visible;
}

.thinkex-thinking-block {
animation: thinkex-thinking-block 2.3s ease-in-out infinite both;
animation: thinkex-thinking-block-loop var(--thinkex-thinking-cycle) ease-in-out infinite both;
opacity: 0;
will-change: opacity;
}

.thinkex-thinking-block--red-left {
animation-delay: 840ms;
}

.thinkex-thinking-block--top-left {
animation-delay: 0ms;
}

.thinkex-thinking-block--top-middle {
animation-delay: 120ms;
}

.thinkex-thinking-block--top-right {
animation-delay: 240ms;
}

.thinkex-thinking-block--right-middle {
animation-delay: 360ms;
}

.thinkex-thinking-block--bottom-right {
animation-delay: 480ms;
}

.thinkex-thinking-block--center {
animation-delay: 600ms;
}

.thinkex-thinking-block--bottom-left {
animation-delay: 720ms;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix stylelint declaration-empty-line-before violation.

Static analysis flags Line 362 (overflow: visible;) as missing a required empty line before the declaration, since it follows the custom-property declaration on Line 361.

🎨 Proposed fix
 	.thinkex-thinking-mark {
 		--thinkex-thinking-cycle: 2.2s;
+
 		overflow: visible;
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.thinkex-thinking-mark {
--thinkex-thinking-cycle: 2.2s;
overflow: visible;
}
.thinkex-thinking-block {
animation: thinkex-thinking-block 2.3s ease-in-out infinite both;
animation: thinkex-thinking-block-loop var(--thinkex-thinking-cycle) ease-in-out infinite both;
opacity: 0;
will-change: opacity;
}
.thinkex-thinking-block--red-left {
animation-delay: 840ms;
}
.thinkex-thinking-block--top-left {
animation-delay: 0ms;
}
.thinkex-thinking-block--top-middle {
animation-delay: 120ms;
}
.thinkex-thinking-block--top-right {
animation-delay: 240ms;
}
.thinkex-thinking-block--right-middle {
animation-delay: 360ms;
}
.thinkex-thinking-block--bottom-right {
animation-delay: 480ms;
}
.thinkex-thinking-block--center {
animation-delay: 600ms;
}
.thinkex-thinking-block--bottom-left {
animation-delay: 720ms;
}
.thinkex-thinking-mark {
--thinkex-thinking-cycle: 2.2s;
overflow: visible;
}
.thinkex-thinking-block {
animation: thinkex-thinking-block-loop var(--thinkex-thinking-cycle) ease-in-out infinite both;
opacity: 0;
will-change: opacity;
}
.thinkex-thinking-block--red-left {
animation-delay: 840ms;
}
.thinkex-thinking-block--top-left {
animation-delay: 0ms;
}
.thinkex-thinking-block--top-middle {
animation-delay: 120ms;
}
.thinkex-thinking-block--top-right {
animation-delay: 240ms;
}
.thinkex-thinking-block--right-middle {
animation-delay: 360ms;
}
.thinkex-thinking-block--bottom-right {
animation-delay: 480ms;
}
.thinkex-thinking-block--center {
animation-delay: 600ms;
}
.thinkex-thinking-block--bottom-left {
animation-delay: 720ms;
}
🧰 Tools
🪛 Stylelint (17.14.0)

[error] 362-362: Expected empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/styles.css` around lines 360 - 402, The .thinkex-thinking-mark rule has a
stylelint declaration-empty-line-before violation because overflow: visible
follows the custom property without the required blank line. Update the
.thinkex-thinking-mark block in the stylesheet so the overflow declaration is
separated by an empty line from --thinkex-thinking-cycle, matching the project’s
declaration spacing convention.

Source: Linters/SAST tools

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

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant