Skip to content

feat: Add chat prompt context - #598

Merged
ilyabo merged 11 commits into
mainfrom
feat--Add-chat-prompt-context
May 11, 2026
Merged

ilyabo merged 11 commits into
mainfrom
feat--Add-chat-prompt-context

Conversation

@ilyabo

@ilyabo ilyabo commented May 7, 2026 •

Copy link
Copy Markdown
Collaborator

Solving #607

image

Summary by CodeRabbit

  • New Features

    • Context selector added to chat/composer with drag‑and‑drop reordering; run context is captured at analysis start and included in prompts, instructions, and tool executions.
    • Drag‑and‑drop enhancements: shared tab-strip DnD, room-level DnD provider, docking/overlay improvements, and artifact tab drag payloads.
  • Public API

    • Exposed run-context schemas, extraction helper, context-selector exports, and a tool-execution context type.
  • Tests

    • New/updated tests for context selection, reordering, run-context capture, and migration/parsing.

@coderabbitai

coderabbitai Bot commented May 7, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds run-context schemas/helpers; captures runContext at startAnalysis; composes session-scoped instructions including formatted run-context text; injects runContext into tool executions; adds DnD ContextSelector with reorder; extends DnD across layout/tab components; expands public exports and tests.

Changes

Run Context Feature

Layer / File(s) Summary
Data Shape / Schema
packages/ai-config/src/schema/AnalysisSessionSchema.ts, packages/ai-config/src/index.ts, packages/ai-core/src/types.ts
Adds AiRunContextItemSchema, AiRunContextSchema, getAiRunContextItems; AnalysisSessionSchema gains optional runContext; AiToolExecutionContext and AiRunContext types imported/re-exported.
Slice Configuration
packages/ai-core/src/AiSlice.ts
AiSliceOptions accepts getRunContext, getInstructions({session, runContext}), and formatRunContextInstructions; getFullInstructions(sessionId?) composes base, run-context, and settings instructions; startAnalysis captures runContext.
Tool Context Injection
packages/ai-core/src/chatTransport.ts
Adds withRunContextTools wrapper to forward sessionId and aiRunContext into tool execute calls; local transport extracts session runContext and uses wrapped tools.
ContextSelector Component
packages/ai-core/src/components/ContextSelector.tsx
Adds reorderContextSelectorItems helper, DnD badge reordering, reorderItems in Root context, and updates Badge/SearchDropdown to support selection, running badges, and reordering.
Chat Integration
packages/ai-core/src/components/Chat.tsx, packages/ai-core/src/components/QueryControls.tsx
Exports Chat.ContextSelector; QueryControls detects ContextSelector children via extractComposerChildren and renders them above the textarea in normal prompt mode; adds optional context drop target handling.
Public API Exports
packages/ai-core/src/index.ts, packages/ai/src/index.ts
Expanded re-exports include ContextSelector, CHAT_CONTEXT_SELECTOR_SLOT, AiRunContextItemSchema, AiRunContextSchema, getAiRunContextItems, and AiToolExecutionContext; retains AiRunContext types.
Tests & Validation
packages/ai-core/__tests__/*, packages/cells/__tests__/*
Test helpers accept getRunContext; new/updated tests verify context capture at startAnalysis, legacy migration to runContext.items, selection helper behavior and reordering, and integration tests updated to use artifactId.
Dependencies
packages/ai-core/package.json
Adds @dnd-kit/core, @dnd-kit/sortable, and @jest/globals.

Shared DnD & Layout / UI

Layer / File(s) Summary
Room-level DnD Provider
packages/layout/src/dnd/RoomDndProvider.tsx, packages/layout/src/LayoutRenderer.tsx, packages/layout/src/index.ts
Adds RoomDndProvider with pointerWithin tagging and roomDndPriority sorting; LayoutRenderer wraps DockingProvider inside RoomDndProvider; provider re-export added.
DockingProvider Monitor Integration
packages/layout/src/docking/DockingProvider.tsx
DockingProvider uses useDndMonitor and guards docking updates to LAYOUT_PANEL_DND_KIND while still rendering overlays.
Leaf Panel & Scroll Hook
packages/layout/src/node-renderers/...
Export LAYOUT_PANEL_DND_KIND and tag draggable/droppable data; tighten useScrollNewGridChildIntoView ref type.
TabStrip Shared-DnD
packages/ui/src/components/tab-strip.tsx, packages/ui/src/index.ts
Adds TabStripDndMode/TabStripDragData, richer sortable payloads, shared-mode DragOverlay and TabDragOverlay, scoped sortable ids, useDndMonitor handlers, and converts TabStripButton to forwardRef.
Artifact Tabs
packages/artifacts/src/artifactTabs.tsx
ArtifactTabsRoot accepts getTabDragData and supplies a default artifact drag payload.
Barrel Exports
packages/ai/src/index.ts, packages/room-shell/src/index.ts, packages/ai-core/src/index.ts, packages/ui/src/index.ts
Update barrel exports to include new DnD/layout/run-context symbols and types.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant AiSlice
  participant ChatTransport
  participant ToolLoopAgent
  User->>AiSlice: startAnalysis()
  AiSlice->>AiSlice: getRunContext() -> draftSession.runContext
  User->>AiSlice: sendPrompt(text)
  AiSlice->>ChatTransport: send request (sessionId, prompt)
  ChatTransport->>ToolLoopAgent: start with wrapped tools (sessionId, aiRunContext)
  ToolLoopAgent->>ToolLoopAgent: tool.execute(options + aiRunContext)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

codex

Suggested reviewers

  • mihajlo-stojkovic
  • dmitriy-kostianetskiy
  • bdjulbic

🐰 I found a context, tucked and neat,
I captured it quick when analysis did meet,
Dragged and reordered the bits with a hop,
Prompts now remember the very first stop,
✨ Hop on, dear code — let context repeat!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Add chat prompt context' directly aligns with the PR's primary objective of adding context selection and drag-and-drop support for artifacts into the chat prompt area, as evidenced by the ContextSelector component, drag-and-drop infrastructure, and run context schema changes throughout the codebase.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat--Add-chat-prompt-context

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/ai-core/src/components/ContextSelector.tsx (1)

454-461: 💤 Low value

Two Object.assign calls can be merged into one.

♻️ Proposed refactor
-export const ContextSelector: ContextSelectorComponent = Object.assign(Root, {
+export const ContextSelector: ContextSelectorComponent = Object.assign(Root, {
   Badge,
   SearchDropdown,
-}) as ContextSelectorComponent;
-
-Object.assign(ContextSelector, {
   [CHAT_CONTEXT_SELECTOR_SLOT]: true,
-});
+}) as ContextSelectorComponent;
🤖 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 `@packages/ai-core/src/components/ContextSelector.tsx` around lines 454 - 461,
Merge the two Object.assign calls into a single assignment: when creating
ContextSelector, assign Root plus the properties Badge and SearchDropdown and
also add the keyed property [CHAT_CONTEXT_SELECTOR_SLOT] in one Object.assign
call so ContextSelector is created with all properties at once; locate the
ContextSelector declaration (currently using Object.assign(Root, { Badge,
SearchDropdown }) and the subsequent Object.assign(ContextSelector, {
[CHAT_CONTEXT_SELECTOR_SLOT]: true }) and combine them into a single
Object.assign(Root, { Badge, SearchDropdown, [CHAT_CONTEXT_SELECTOR_SLOT]: true
}) assigned to ContextSelector.
🤖 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 `@packages/ai-core/__tests__/AiSlice.modelSelection.test.ts`:
- Line 3: The test imports from `@jest/globals` but that package is not declared
in ai-core's devDependencies; add "@jest/globals": "^30.3.0" (matching other
packages) to the ai-core package.json devDependencies so the import in the test
(import {jest} from '@jest/globals') resolves in CI.

In `@packages/ai-core/src/components/ContextSelector.tsx`:
- Around line 178-181: The context value currently captures a freshly created
setOpen closure that references onOpenChange but isn’t stable or included in the
useMemo deps, causing a stale callback in controlled mode; to fix, memoize
setOpen (e.g., with useCallback) so it doesn't get recreated each render and
include that memoized setOpen in the useMemo dependency array that builds the
context value (references: setOpen, isControlled, setUncontrolledOpen,
onOpenChange, useMemo, and the context value used by SearchDropdown).

In `@packages/ai/src/index.ts`:
- Around line 91-95: Replace the inline redeclaration of
CHAT_CONTEXT_SELECTOR_SLOT with a re-export from `@sqlrooms/ai-core`: remove the
const CHAT_CONTEXT_SELECTOR_SLOT = Symbol.for(...) declaration in
packages/ai/src/index.ts and instead export the symbol from the ai-core package
so callers (and isContextSelector) use the exact same exported symbol; ensure
the export uses the exact exported name CHAT_CONTEXT_SELECTOR_SLOT from
'@sqlrooms/ai-core'.

---

Nitpick comments:
In `@packages/ai-core/src/components/ContextSelector.tsx`:
- Around line 454-461: Merge the two Object.assign calls into a single
assignment: when creating ContextSelector, assign Root plus the properties Badge
and SearchDropdown and also add the keyed property [CHAT_CONTEXT_SELECTOR_SLOT]
in one Object.assign call so ContextSelector is created with all properties at
once; locate the ContextSelector declaration (currently using
Object.assign(Root, { Badge, SearchDropdown }) and the subsequent
Object.assign(ContextSelector, { [CHAT_CONTEXT_SELECTOR_SLOT]: true }) and
combine them into a single Object.assign(Root, { Badge, SearchDropdown,
[CHAT_CONTEXT_SELECTOR_SLOT]: true }) assigned to ContextSelector.
🪄 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: 0f830b4b-2bb7-4cac-b244-583159b12c54

📥 Commits

Reviewing files that changed from the base of the PR and between 4ffaf6c and b82b9c9.

📒 Files selected for processing (14)
  • packages/ai-config/src/index.ts
  • packages/ai-config/src/schema/AnalysisSessionSchema.ts
  • packages/ai-core/__tests__/AiSlice.modelSelection.test.ts
  • packages/ai-core/__tests__/ContextSelector.test.ts
  • packages/ai-core/__tests__/migration.test.ts
  • packages/ai-core/src/AiSlice.ts
  • packages/ai-core/src/chatTransport.ts
  • packages/ai-core/src/components/Chat.tsx
  • packages/ai-core/src/components/ContextSelector.tsx
  • packages/ai-core/src/components/QueryControls.tsx
  • packages/ai-core/src/index.ts
  • packages/ai-core/src/types.ts
  • packages/ai/src/index.ts
  • packages/ui/src/components/tab-strip.tsx

Comment thread packages/ai-core/__tests__/AiSlice.modelSelection.test.ts
Comment thread packages/ai-core/src/components/ContextSelector.tsx Outdated
Comment thread packages/ai/src/index.ts Outdated

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 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 `@packages/ai-core/package.json`:
- Line 54: The project mixes Jest 30 globals with ts-jest v29 which is
incompatible; update the dependency in packages/ai-core package.json by
replacing the existing "ts-jest" entry with a Jest-30-compatible release (e.g.
"ts-jest": "^30.0.0") or otherwise pin "@jest/globals" back to ^29.x to match
ts-jest, and ensure any presets like "@sqlrooms/preset-jest" are aligned (update
or override its ts-jest/jest versions) so that "@jest/globals" and "ts-jest" use
the same major Jest release.
🪄 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: 70a5d28d-01b4-4548-bf73-1fa729a6e6c5

📥 Commits

Reviewing files that changed from the base of the PR and between b82b9c9 and d7fecef.

📒 Files selected for processing (4)
  • packages/ai-core/__tests__/ContextSelector.test.ts
  • packages/ai-core/package.json
  • packages/ai-core/src/components/ContextSelector.tsx
  • packages/cells/__tests__/defaultCellRegistry.integration.test.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ai-core/tests/ContextSelector.test.ts
  • packages/ai-core/src/components/ContextSelector.tsx

"typedoc": "typedoc"
},
"devDependencies": {
"@jest/globals": "^30.3.0",

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.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check what version of Jest `@sqlrooms/preset-jest` pulls in and
# verify the ts-jest peer-dependency range against the installed Jest version.

# Find preset-jest package.json to inspect its Jest dependency
fd -i "package.json" --exclude "node_modules" | xargs grep -l "preset-jest" 2>/dev/null | head -5

# Show the preset-jest package.json if it exists in the monorepo
fd -g "package.json" --search-path packages/preset-jest 2>/dev/null | xargs cat 2>/dev/null || true

# Show ts-jest peer-dependency declaration
rg -n "peerDependencies" node_modules/ts-jest/package.json 2>/dev/null | head -10 || true
rg -A 10 '"peerDependencies"' node_modules/ts-jest/package.json 2>/dev/null | head -15 || true

Repository: sqlrooms/sqlrooms

Length of output: 609


Align ts-jest with Jest 30 across the project.

The @jest/globals@^30.3.0 dependency is correct for Jest 30, but ts-jest@^29.4.4 is built for Jest 29 and is incompatible with Jest 30. This is a pre-existing issue introduced by @sqlrooms/preset-jest, which already specifies jest@^30.1.3 alongside ts-jest@^29.4.4. Adding @jest/globals@^30 to this package makes the incompatibility explicit across the codebase. Update ts-jest to a Jest 30-compatible version (or pin @jest/globals to ^29.x if remaining on Jest 29).

🤖 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 `@packages/ai-core/package.json` at line 54, The project mixes Jest 30 globals
with ts-jest v29 which is incompatible; update the dependency in
packages/ai-core package.json by replacing the existing "ts-jest" entry with a
Jest-30-compatible release (e.g. "ts-jest": "^30.0.0") or otherwise pin
"@jest/globals" back to ^29.x to match ts-jest, and ensure any presets like
"@sqlrooms/preset-jest" are aligned (update or override its ts-jest/jest
versions) so that "@jest/globals" and "ts-jest" use the same major Jest release.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🧹 Nitpick comments (5)
packages/ui/src/components/tab-strip.tsx (3)

497-506: 💤 Low value

onDragOver overlay logic flips overlay back on after isDragInsideTabStrip returned true.

The flow is:

  1. If !over || isDragInsideTabStrip(event) → setHideSharedDragOverlay(true) and return.
  2. Otherwise set hideSharedDragOverlay to (over.data.current.tabStripId === dndScopeId).

Step 2 only runs when the drag is outside this tab strip. In that case tabStripId === dndScopeId would only be true if the user dragged the source tab outside its own strip and hovered some other droppable that happens to declare the same tabStripId — which usually won't happen. So effectively, whenever the drag leaves the strip, hideSharedDragOverlay is set to false, revealing the overlay. That's the intended UX.

However, onDragMove only ever sets it to true (line 494), never back to false. Between an onDragMove (inside) tick and the next onDragOver (outside) tick there can be intermediate frames where hideSharedDragOverlay is stale. Consider unifying overlay visibility into a single derived value computed in one place (e.g., compute it in onDragMove too, mirroring onDragOver).

🤖 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 `@packages/ui/src/components/tab-strip.tsx` around lines 497 - 506, The
onDragOver handler flips hideSharedDragOverlay based on tabStripId but
onDragMove only ever sets it to true, causing stale visibility between events;
update onDragMove to apply the same visibility logic used in onDragOver (use
isDragInsideTabStrip(event) and compare event.over?.data.current?.tabStripId ===
dndScopeId when dndMode === 'shared') or extract the logic into a shared helper
used by both onDragMove and onDragOver so both handlers compute and call
setHideSharedDragOverlay consistently.

182-188: ⚡ Quick win

Spread order may let user-provided drag data clobber kind.

...getTabDragData?.(tab) is spread between the literal kind: DEFAULT_TAB_DRAG_KIND and the trailing tabId/tabStripId. That ordering correctly protects tabId and tabStripId from being overridden by user data, but the spread can intentionally or accidentally overwrite kind (e.g., ArtifactTabsRoot relies on this to set kind: 'artifact'). If overriding kind is intended, please document it on the getTabDragData prop type (TabStripDragData) so callers know they can flip the discriminator; if not, freeze kind by putting it last.

🤖 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 `@packages/ui/src/components/tab-strip.tsx` around lines 182 - 188, The spread
of getTabDragData?.(tab) sits between kind and tabId/tabStripId which allows
user data to overwrite kind; either explicitly document in the TabStripDragData
prop type that callers may override the discriminator, or (preferred) freeze
kind by moving the spread to the front and placing tabId, tabStripId and kind
after it (e.g., data: { ...getTabDragData?.(tab), tabId: tab.id, tabStripId:
dndScopeId, kind: DEFAULT_TAB_DRAG_KIND }) so neither tabId/tabStripId nor kind
can be clobbered.

480-518: ⚡ Quick win

Consider consolidating useDndMonitor logic to avoid unnecessary hook registration in 'internal' mode.

useDndMonitor is called unconditionally in TabStripTabs, but in 'internal' mode it serves no purpose since all handlers contain early if (dndMode !== 'shared') return; guards. While useDndMonitor does not throw outside a DndContext (it simply no-ops), registering and maintaining the hook in internal mode is unnecessary overhead. For clarity and efficiency, consider either:

  1. Wrapping the useDndMonitor call in a conditional: if (dndMode === 'shared') { useDndMonitor(...) }
  2. Extracting the monitor logic into a small inner component rendered only in 'shared' mode

The current code functions correctly but can be streamlined.

🤖 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 `@packages/ui/src/components/tab-strip.tsx` around lines 480 - 518, The DnD
monitor is registered unconditionally in TabStripTabs despite only being needed
for dndMode === 'shared'; move the useDndMonitor call and its handlers into a
small inner component (e.g., SharedDndMonitor) that you render only when dndMode
=== 'shared' so React hook rules remain satisfied. Inside SharedDndMonitor, use
useDndMonitor with the existing handlers
(onDragStart/onDragMove/onDragOver/onDragEnd/onDragCancel) and reference the
same helpers and state setters (isDragInsideTabStrip, handleDragEnd,
setActiveDraggedTabId, setHideSharedDragOverlay, dndScopeId) so behavior is
unchanged but the hook is not registered in 'internal' mode.
packages/layout/src/docking/DockingProvider.tsx (1)

29-33: 💤 Low value

Consider documenting the new ancestor DndContext requirement.

DockingProvider previously created its own DndContext; it now uses useDndMonitor, which throws when invoked outside a DndContext. The current wiring works because LayoutRenderer wraps DockingProvider in RoomDndProvider, but consumers reusing DockingProvider directly will hit a confusing runtime error. A short JSDoc note on the component (and/or a dev-time assert) would help.

📝 Suggested doc
+/**
+ * Renders docking preview/overlay for layout panels. Must be rendered inside
+ * a `RoomDndProvider` (or any `@dnd-kit/core` `DndContext`); otherwise
+ * `useDndMonitor` will throw.
+ */
 export const DockingProvider: FC<PropsWithChildren<DockingProviderProps>> = ({

Also applies to: 163-168

🤖 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 `@packages/layout/src/docking/DockingProvider.tsx` around lines 29 - 33,
DockingProvider now calls useDndMonitor and therefore requires to be rendered
inside a DndContext (previously it created its own); add a short JSDoc above the
DockingProvider export describing this requirement (mention DndContext and that
LayoutRenderer wraps it with RoomDndProvider) and add a dev-time assert inside
DockingProvider to surface a clear error when useDndMonitor is missing (e.g.,
detect that useDndMonitor returns undefined/error and throw a descriptive error
like "DockingProvider must be wrapped in a DndContext/RoomDndProvider"). Ensure
references to DockingProvider, useDndMonitor, DndContext, LayoutRenderer and
RoomDndProvider are included in the doc/assert so consumers can find the
intended wrapper.
packages/ai-core/src/components/QueryControls.tsx (1)

122-165: ⚡ Quick win

Consider isolating DnD wiring behind contextDropTarget presence.

useDroppable and useDndMonitor are invoked unconditionally. When contextDropTarget is not provided, this still:

  • Registers a droppable using the shared fallback id 'chat-composer-context-drop-target-disabled'. Multiple QueryControls instances mounted simultaneously without DnD will collide on this id and may trigger @dnd-kit duplicate-id warnings.
  • Subscribes to every onDragEnd via useDndMonitor only to immediately bail out.

A cleaner pattern is to extract the DnD wiring into a small internal subcomponent that is only rendered when contextDropTarget is provided, and conditionally pass its setNodeRef / isAcceptedContextDropOver to the container. That removes the dummy id and avoids subscribing dnd listeners for the common no-DnD case.

♻️ Sketch of the refactor
// Conditionally render a child that owns useDroppable + useDndMonitor.
function ContextDropTarget({
  target,
  children,
}: {
  target: NonNullable<QueryControlsProps['contextDropTarget']>;
  children: (args: {
    setNodeRef: (el: HTMLElement | null) => void;
    isAcceptedOver: boolean;
  }) => React.ReactNode;
}) {
  const {active, isOver, setNodeRef} = useDroppable({
    id: target.id,
    data: {roomDndPriority: 100},
  });
  const data = active?.data.current;
  const canAccept = Boolean(data && target.canAccept(data));
  useDndMonitor({
    onDragEnd: (event) => {
      if (event.over?.id !== target.id) return;
      if (
        !event.collisions?.some(
          (c) => c.id === target.id && c.data?.pointerWithin === true,
        )
      )
        return;
      const d = event.active.data.current;
      if (target.canAccept(d)) target.onDrop(d);
    },
  });
  return <>{children({setNodeRef, isAcceptedOver: isOver && canAccept})}</>;
}

Then wrap the composer container only when contextDropTarget is defined.

🤖 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 `@packages/ai-core/src/components/QueryControls.tsx` around lines 122 - 165,
The current implementation always calls useDroppable and useDndMonitor even when
contextDropTarget is null, causing a shared fallback id and unnecessary DnD
subscriptions; extract the DnD wiring into a small internal component (e.g.,
ContextDropTarget) that is only rendered when contextDropTarget is non-null,
move useDroppable and useDndMonitor there (use the real target.id instead of the
fallback), compute the accepted-over boolean inside that component and pass
setNodeRef (formerly setContextDropTargetRef) and isAcceptedOver (formerly
isAcceptedContextDropOver) back to the parent via a render-prop or children
callback, and remove isPointerWithinContextDropTarget from the outer scope so
the onDragEnd logic lives with the DnD hooks and calls target.onDrop when
appropriate.
🤖 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 `@packages/artifacts/src/artifactTabs.tsx`:
- Around line 557-564: The consumer-supplied getTabDragData currently replaces
the artifact discriminator and fields if it returns any object, causing loss of
kind/id/type/title; update the getTabDragData logic so it merges the consumer
payload with the artifact defaults rather than using the consumer result
verbatim: call getTabDragData?.(tab) to get userData, then produce { kind:
'artifact', id: (tab as ArtifactTabDescriptor).artifact.id, type: (tab as
ArtifactTabDescriptor).artifact.type, title: (tab as
ArtifactTabDescriptor).artifact.title, ...userData } (or spread userData first
if you want user override) so DEFAULT_TAB_DRAG_KIND and RoomDndProvider can
still detect artifact tabs and downstream code can read id/type/title. Ensure
this change is applied where getTabDragData is passed into TabStrip so artifact
fields are preserved unless intentionally overridden.

In
`@packages/layout/src/node-renderers/grid-node-renderer/useScrollNewGridChildIntoView.ts`:
- Around line 46-47: The hook useScrollNewGridChildIntoView currently declares a
non-nullable RefObject<HTMLDivElement> but initializes scrollContainerRef with
useRef<HTMLDivElement>(null!), which lies about nullability; change the hook's
return type to RefObject<HTMLDivElement | null> and initialize
scrollContainerRef with useRef<HTMLDivElement | null>(null) so the type
accurately reflects that current may be null before mount, keeping internal
null-checks as-is and leaving callers that pass the ref to DOM elements
unchanged.

---

Nitpick comments:
In `@packages/ai-core/src/components/QueryControls.tsx`:
- Around line 122-165: The current implementation always calls useDroppable and
useDndMonitor even when contextDropTarget is null, causing a shared fallback id
and unnecessary DnD subscriptions; extract the DnD wiring into a small internal
component (e.g., ContextDropTarget) that is only rendered when contextDropTarget
is non-null, move useDroppable and useDndMonitor there (use the real target.id
instead of the fallback), compute the accepted-over boolean inside that
component and pass setNodeRef (formerly setContextDropTargetRef) and
isAcceptedOver (formerly isAcceptedContextDropOver) back to the parent via a
render-prop or children callback, and remove isPointerWithinContextDropTarget
from the outer scope so the onDragEnd logic lives with the DnD hooks and calls
target.onDrop when appropriate.

In `@packages/layout/src/docking/DockingProvider.tsx`:
- Around line 29-33: DockingProvider now calls useDndMonitor and therefore
requires to be rendered inside a DndContext (previously it created its own); add
a short JSDoc above the DockingProvider export describing this requirement
(mention DndContext and that LayoutRenderer wraps it with RoomDndProvider) and
add a dev-time assert inside DockingProvider to surface a clear error when
useDndMonitor is missing (e.g., detect that useDndMonitor returns
undefined/error and throw a descriptive error like "DockingProvider must be
wrapped in a DndContext/RoomDndProvider"). Ensure references to DockingProvider,
useDndMonitor, DndContext, LayoutRenderer and RoomDndProvider are included in
the doc/assert so consumers can find the intended wrapper.

In `@packages/ui/src/components/tab-strip.tsx`:
- Around line 497-506: The onDragOver handler flips hideSharedDragOverlay based
on tabStripId but onDragMove only ever sets it to true, causing stale visibility
between events; update onDragMove to apply the same visibility logic used in
onDragOver (use isDragInsideTabStrip(event) and compare
event.over?.data.current?.tabStripId === dndScopeId when dndMode === 'shared')
or extract the logic into a shared helper used by both onDragMove and onDragOver
so both handlers compute and call setHideSharedDragOverlay consistently.
- Around line 182-188: The spread of getTabDragData?.(tab) sits between kind and
tabId/tabStripId which allows user data to overwrite kind; either explicitly
document in the TabStripDragData prop type that callers may override the
discriminator, or (preferred) freeze kind by moving the spread to the front and
placing tabId, tabStripId and kind after it (e.g., data: {
...getTabDragData?.(tab), tabId: tab.id, tabStripId: dndScopeId, kind:
DEFAULT_TAB_DRAG_KIND }) so neither tabId/tabStripId nor kind can be clobbered.
- Around line 480-518: The DnD monitor is registered unconditionally in
TabStripTabs despite only being needed for dndMode === 'shared'; move the
useDndMonitor call and its handlers into a small inner component (e.g.,
SharedDndMonitor) that you render only when dndMode === 'shared' so React hook
rules remain satisfied. Inside SharedDndMonitor, use useDndMonitor with the
existing handlers (onDragStart/onDragMove/onDragOver/onDragEnd/onDragCancel) and
reference the same helpers and state setters (isDragInsideTabStrip,
handleDragEnd, setActiveDraggedTabId, setHideSharedDragOverlay, dndScopeId) so
behavior is unchanged but the hook is not registered in 'internal' mode.
🪄 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: a0ec7161-20eb-42cc-8f74-a5d1c9f69b90

📥 Commits

Reviewing files that changed from the base of the PR and between 468f819 and 978bf57.

📒 Files selected for processing (12)
  • packages/ai-core/src/components/QueryControls.tsx
  • packages/artifacts/src/artifactTabs.tsx
  • packages/layout/src/LayoutRenderer.tsx
  • packages/layout/src/dnd/RoomDndProvider.tsx
  • packages/layout/src/docking/DockingProvider.tsx
  • packages/layout/src/index.ts
  • packages/layout/src/node-renderers/grid-node-renderer/useScrollNewGridChildIntoView.ts
  • packages/layout/src/node-renderers/leaf-node-renderer/LeafLayoutPanel.tsx
  • packages/room-shell/src/RoomShell.tsx
  • packages/room-shell/src/index.ts
  • packages/ui/src/components/tab-strip.tsx
  • packages/ui/src/index.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/layout/src/index.ts
  • packages/ui/src/index.ts

Comment thread packages/artifacts/src/artifactTabs.tsx Outdated
Comment on lines +46 to +47
): RefObject<HTMLDivElement> {
const scrollContainerRef = useRef<HTMLDivElement>(null!);

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) Find hook call sites"
rg -n --type=ts --type=tsx '\buseScrollNewGridChildIntoView\s*\(' -C2

echo
echo "2) Find direct .current dereferences that may need null checks"
rg -n --type=ts --type=tsx '\bscrollContainerRef\.current\.[A-Za-z_]\w*' -C2

Repository: sqlrooms/sqlrooms

Length of output: 131


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the file first to confirm it exists
echo "=== Locating file ==="
find . -name "useScrollNewGridChildIntoView.ts" 2>/dev/null || echo "File not found"

echo
echo "=== Searching for hook call sites (corrected syntax) ==="
rg 'useScrollNewGridChildIntoView' -A2 -B2 || echo "No matches found"

echo
echo "=== Searching for scrollContainerRef usage ==="
rg 'scrollContainerRef' -A2 -B2 || echo "No matches found"

Repository: sqlrooms/sqlrooms

Length of output: 7632


🏁 Script executed:

cat -n ./packages/layout/src/node-renderers/grid-node-renderer/useScrollNewGridChildIntoView.ts

Repository: sqlrooms/sqlrooms

Length of output: 3435


🏁 Script executed:

cat -n ./packages/layout/src/node-renderers/grid-node-renderer/GridLayout.tsx | head -200

Repository: sqlrooms/sqlrooms

Length of output: 8056


🏁 Script executed:

grep -n "scrollContainerRef" ./packages/layout/src/node-renderers/grid-node-renderer/GridLayout.tsx

Repository: sqlrooms/sqlrooms

Length of output: 162


🏁 Script executed:

sed -n '495,510p' ./packages/layout/src/node-renderers/grid-node-renderer/GridLayout.tsx
sed -n '590,605p' ./packages/layout/src/node-renderers/grid-node-renderer/GridLayout.tsx

Repository: sqlrooms/sqlrooms

Length of output: 1215


Fix type contract mismatch on returned ref

The return type RefObject<HTMLDivElement> claims non-nullable current, but initialization with useRef<HTMLDivElement>(null!) suppresses this guarantee. While the hook properly null-checks before accessing .current internally and the current call site passes the ref safely to a DOM element, the type lie could mislead future callers into unsafe dereferences.

Update the type to accurately reflect that current is nullable before mount:

Suggested fix
-): RefObject<HTMLDivElement> {
-  const scrollContainerRef = useRef<HTMLDivElement>(null!);
+): RefObject<HTMLDivElement | null> {
+  const scrollContainerRef = useRef<HTMLDivElement | null>(null);
📝 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
): RefObject<HTMLDivElement> {
const scrollContainerRef = useRef<HTMLDivElement>(null!);
): RefObject<HTMLDivElement | null> {
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
🤖 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
`@packages/layout/src/node-renderers/grid-node-renderer/useScrollNewGridChildIntoView.ts`
around lines 46 - 47, The hook useScrollNewGridChildIntoView currently declares
a non-nullable RefObject<HTMLDivElement> but initializes scrollContainerRef with
useRef<HTMLDivElement>(null!), which lies about nullability; change the hook's
return type to RefObject<HTMLDivElement | null> and initialize
scrollContainerRef with useRef<HTMLDivElement | null>(null) so the type
accurately reflects that current may be null before mount, keeping internal
null-checks as-is and leaving callers that pass the ref to DOM elements
unchanged.

@ilyabo
ilyabo merged commit ce29e0e into main May 11, 2026
3 checks passed
@ilyabo
ilyabo deleted the feat--Add-chat-prompt-context branch May 11, 2026 14:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant