Repository navigation
feat: dnd-kit docking layout - #552
Conversation
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
…com:sqlrooms/sqlrooms into react-mosaic-update
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 203 out of 207 changed files in this pull request and generated 8 comments.
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/ui/src/components/tab-strip.tsx (1)
566-579:⚠️ Potential issue | 🟡 MinorSearch dropdown ignores the new strip-level
closeableprop.
renderOpenTabActionsstill renders the closeXaction for open tabs based solely onpreventCloseLastTab. When a consumer setscloseable={false}on theTabStrip(as the layout tabs renderer does for docked panels), inline tab close buttons are hidden, but the search dropdown still exposes a close control and callshandleClose, letting users close tabs that the strip declares non-closeable.Consider gating this action on
closeableas well (destructure it fromuseTabStripContext()inTabStripSearchDropdownand skip rendering the close action when it is false).🔧 Suggested fix
const { openTabItems, search, setSearch, closedTabs, filteredTabs, closedTabIds, openTabs, preventCloseLastTab, + closeable, onOpenTabsChange, onSelect, renderSearchItemActions, getLastOpenedAt, handleClose, } = useTabStripContext(); @@ const renderOpenTabActions = (tab: TabDescriptor) => { - const disableClose = preventCloseLastTab && (openTabsList.length ?? 0) <= 1; + if (!closeable) { + return renderSearchItemActions?.(tab) ?? null; + } + const disableClose = preventCloseLastTab && (openTabsList.length ?? 0) <= 1; return ( <> {renderSearchItemActions?.(tab)} <TabStripSearchItemAction icon={<XIcon className="h-3 w-3" />} aria-label={`Close ${tab.name}`} onClick={disableClose ? undefined : () => handleClose(tab.id)} className={cn(disableClose && 'pointer-events-none opacity-40')} /> </> ); };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/ui/src/components/tab-strip.tsx` around lines 566 - 579, The search dropdown still shows the inline close action regardless of the strip-level closeable prop; update TabStripSearchDropdown to read closeable from useTabStripContext() and modify renderOpenTabActions to check both closeable and preventCloseLastTab before rendering TabStripSearchItemAction (and before calling handleClose). Specifically, destructure closeable from useTabStripContext(), compute disableClose = !closeable || (preventCloseLastTab && (openTabsList.length ?? 0) <= 1), and skip rendering the TabStripSearchItemAction when closeable is false so the dropdown respects the strip-level closeable setting.examples/deckgl-mosaic/src/store.ts (1)
2-3:⚠️ Potential issue | 🟡 MinorFix import path to use package public entry point
MosaicSliceStateis exported from the@sqlrooms/mosaicpackage root and should be imported from there instead of the deepdistsubpath. Change line 3 from:import {MosaicSliceState} from '@sqlrooms/mosaic/dist/MosaicSlice';to:
import {MosaicSliceState} from '@sqlrooms/mosaic';Importing from private
distpaths is fragile and will break if the package's internal build structure changes.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/deckgl-mosaic/src/store.ts` around lines 2 - 3, Update the import to use the package public entry point: replace the deep import of MosaicSliceState from '@sqlrooms/mosaic/dist/MosaicSlice' with an import from '@sqlrooms/mosaic' so that both MosaicSliceState and createMosaicSlice are imported from the package root (ensure the import statement imports { MosaicSliceState } from '@sqlrooms/mosaic' alongside createMosaicSlice).
♻️ Duplicate comments (1)
apps/sqlrooms-cli-ui/src/createDashboardCommands.ts (1)
109-118:⚠️ Potential issue | 🟠 Major
createDashboardCreateSheetCommand.executestill doesn't select the new sheet.The description says "Create a new dashboard sheet and select it", but
executeonly callscreateDashboardSheet(title)and never sets the newly created sheet as current. The genericcreateSheetCommandhelper above (line 80) correctly callsgetState().cells.setCurrentSheet(sheetId)— mirror that here so the dashboard command matches its contract and is consistent with the other create commands.🔧 Suggested fix
execute: ({getState}, input) => { const {title} = (input as CreateSheetCommandInput | undefined) ?? {}; const sheetId = getState().dashboard.createDashboardSheet(title); + getState().cells.setCurrentSheet(sheetId); return { success: true, commandId: 'dashboard.create-sheet', message: `Created dashboard sheet "${sheetId}".`, data: {sheetId}, }; },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/sqlrooms-cli-ui/src/createDashboardCommands.ts` around lines 109 - 118, The execute implementation in createDashboardCommands (the execute function that calls getState().dashboard.createDashboardSheet(title)) creates a sheet but doesn't select it; update this execute to call getState().cells.setCurrentSheet(sheetId) after obtaining sheetId (mirror the behavior from the generic createSheetCommand helper) so the new dashboard sheet is set as the current sheet before returning the success response.
🧹 Nitpick comments (17)
examples/mosaic/src/components/DataSourcesPanel.tsx (1)
59-61: Remove commented-outAddDataModalblock.Dead/commented code adds noise; drop it or convert to a TODO with a tracked issue if the modal is still planned.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/mosaic/src/components/DataSourcesPanel.tsx` around lines 59 - 61, Remove the commented-out AddDataModal block to eliminate dead code: delete the commented JSX lines referencing AddDataModal, isOpen, and handleModalClose in DataSourcesPanel.tsx; if the modal is still intended to be implemented later, replace the commented block with a concise TODO comment referencing a tracking issue (e.g., "TODO: implement AddDataModal - see ISSUE-###") so intent is preserved without leftover commented JSX.examples/canvas/src/components/DataSourcesPanel.tsx (1)
7-9: Align with codebase convention by using type-only import forFC.The repository consistently uses
import typefor type-only imports throughout the codebase. Change toimport type {FC}to maintain consistency.♻️ Proposed fix
-import {FC} from 'react'; +import type {FC} from 'react';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/canvas/src/components/DataSourcesPanel.tsx` around lines 7 - 9, The import of React's FC should be a type-only import to match repo conventions; change the top import to use a type-only import (e.g., import type { FC } from 'react') and ensure the component declaration DataSourcesPanel: FC remains unchanged so only type information is imported.packages/ui/src/components/tab-strip.tsx (1)
136-141:TAB_STRIP_BUTTON_CLASSNAMESmixes sizing/padding classes into surfaces that also passsize="icon".
TAB_STRIP_BUTTON_CLASSNAMESbakes inpx-6 py-1andmin-h-7, and is applied to:
- the
TabsTrigger(a text tab — expected),- the search dropdown trigger which is a
Button size="icon"(line 586),TabStripButtonwhich is alsoButton size="icon"(line 765).For the two icon buttons, the shared classes override
size="icon"'s own padding/size via tailwind-merge, which works but couples all three surfaces to a single shape. If the text-tab padding (px-6) is ever tuned, the icon buttons silently change too.Consider splitting this into two groups — a focus/hover ring set shared by all, and a padding/sizing set applied only to the tab trigger — so the icon-button surfaces stop paying for text-tab padding. Not blocking.
Also applies to: 194-203, 581-592, 762-775
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/ui/src/components/tab-strip.tsx` around lines 136 - 141, TAB_STRIP_BUTTON_CLASSNAMES currently mixes focus/hover ring classes with sizing/padding (px-6 py-1, min-h-7) which overrides Button size="icon"; split this constant into two: e.g. TAB_STRIP_SURFACE_CLASSNAMES (shared focus/hover/ring/overflow classes) and TAB_STRIP_TEXT_PADDING_CLASSNAMES (padding/min-height for text tabs), then update uses so TabsTrigger and the text tab composition include both arrays but the search dropdown trigger and TabStripButton (Button size="icon") only receive TAB_STRIP_SURFACE_CLASSNAMES so their intrinsic size/padding from size="icon" is preserved.examples/get-started/src/components/MainView.tsx (1)
10-12: Useimport typeforFCsince it's only used as a type.
FCis only used as a TypeScript type annotation and is never referenced at runtime. Usingimport typeclarifies intent and prevents accidental runtime references.Proposed fix
-import {FC} from 'react'; +import type {FC} from 'react';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/get-started/src/components/MainView.tsx` around lines 10 - 12, Change the runtime import of FC to a type-only import: replace the current import of FC with a type import (use "import type { FC } from 'react';") so FC is erased at runtime; keep the component declaration export const MainView: FC = () => { ... } unchanged. This ensures FC is only used as a TypeScript type and prevents accidental runtime references.examples/cosmos-embedding/src/components/DataSourcesPanel.tsx (1)
83-85: Remove the commented-outAddDataModalblock.Stale debug/dead code; either restore it as a tracked TODO with context or drop it to keep the example clean.
🧹 Proposed cleanup
- {/* {AddDataModal ? ( - <AddDataModal isOpen={isOpen} onClose={handleModalClose} /> - ) : null} */} </RoomPanel>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/cosmos-embedding/src/components/DataSourcesPanel.tsx` around lines 83 - 85, Remove the stale commented-out JSX block referencing AddDataModal (the fragment containing "<AddDataModal isOpen={isOpen} onClose={handleModalClose} />") from DataSourcesPanel; either delete the commented lines entirely or, if you want to preserve intent, replace them with a short tracked TODO comment explaining why AddDataModal was removed and what props (isOpen, handleModalClose) it used to require so future restoration is clear.examples/minimal/src/room.tsx (1)
9-9: Useimport typefor the React FC type.
FCis used only in a type annotation. Usingimport typeimproves code clarity by explicitly marking it as a type-only dependency.Proposed diff
-import {FC} from 'react'; +import type {FC} from 'react';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/minimal/src/room.tsx` at line 9, The import in room.tsx currently brings in FC as a regular value import; change it to a type-only import to reflect that FC is used only in type annotations by replacing the import of FC with an `import type` declaration (e.g., import type { FC } from 'react') so the symbol FC is treated as a type-only dependency and bundlers/typecheckers can optimize accordingly; update the import statement where FC is referenced.examples/deckgl-mosaic/src/store.ts (1)
27-28: Minor:as constonz.enumargument is redundant.When passing an array literal directly to
z.enum(...), the literal union is inferred withoutas const. The assertion can be dropped. Additionally, declaring the runtime const and the inferred type with the same identifier (RoomPanelTypes) is confusing at call sites — consider usingRoomPanelTypefor the type instead.Proposed change
-export const RoomPanelTypes = z.enum(['left', 'data', 'main'] as const); -export type RoomPanelTypes = z.infer<typeof RoomPanelTypes>; +export const RoomPanelTypes = z.enum(['left', 'data', 'main']); +export type RoomPanelType = z.infer<typeof RoomPanelTypes>;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/deckgl-mosaic/src/store.ts` around lines 27 - 28, The z.enum call uses an unnecessary "as const" and the runtime and type share the same name which is confusing; update the declaration by removing the "as const" from the z.enum argument (keep export const RoomPanelTypes = z.enum(['left', 'data', 'main'])) and rename the inferred type to a distinct singular name (export type RoomPanelType = z.infer<typeof RoomPanelTypes>); also search for any usages of the old type name RoomPanelTypes in the code and replace them with RoomPanelType.examples/query-motherduck/src/components/MainView.tsx (1)
22-22: Move shared token constants out of the rootAppmodule.Line 22 makes this child component depend on
../Appjust to readMD_TOKEN_KEY. A neutral constants module avoids root-component coupling and circular-import surprises as theRoom/MainViewtree evolves.♻️ Proposed refactor
+// examples/query-motherduck/src/constants.ts +export const MD_TOKEN_KEY = 'motherduck-token';// examples/query-motherduck/src/App.tsx import {ThemeProvider} from '@sqlrooms/ui'; import {FC, useEffect, useState} from 'react'; +import {MD_TOKEN_KEY} from './constants'; import {MdConnectPane} from './components/MdConnectPane'; import {Room} from './Room'; -export const MD_TOKEN_KEY = 'motherduck-token'; - export const App: FC = () => {// examples/query-motherduck/src/components/MainView.tsx -import {MD_TOKEN_KEY} from '../App'; +import {MD_TOKEN_KEY} from '../constants'; import {useRoomStore} from '../store';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/query-motherduck/src/components/MainView.tsx` at line 22, MainView.tsx currently imports MD_TOKEN_KEY from ../App which couples the child component to the root; create a neutral constants module (e.g., export MD_TOKEN_KEY from a new module like tokenConstants or constants) and move the MD_TOKEN_KEY definition there, then update MainView.tsx and App (and any other consumers) to import MD_TOKEN_KEY from the new module (ensure the symbol name MD_TOKEN_KEY is exported as-is to avoid breaking usages).examples/query-motherduck/src/store.ts (1)
19-19:'left'enum value is a container id, not a panel type.
RoomPanelTypesis used both as the layout node id for the tabs container (Line 51) and as keys intolayout.panels(Lines 70, 73). The'left'value is only used for the container and has no corresponding entry inpanels, which conflates two different namespaces in one enum. Consider either adding a separate type for container ids, or documenting that'left'is intentionally a container slot without apanelsentry, to avoid confusion for downstream consumers who iterateRoomPanelTypes.options.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/query-motherduck/src/store.ts` at line 19, RoomPanelTypes currently mixes a container id ('left') with actual panel keys causing confusion when iterating RoomPanelTypes.options and indexing layout.panels; split the namespaces by creating a new enum (e.g., ContainerIds = z.enum(['left'] as const)) and change RoomPanelTypes to only include real panel keys (e.g., z.enum(['data','main'] as const)), then update usages that refer to the tabs container id to use ContainerIds (or add a clarifying comment if you intentionally keep 'left' in the enum) and ensure code that indexes layout.panels uses RoomPanelTypes only.examples/query/src/components/DataPanel.tsx (1)
7-9: Useimport typefor type-only imports.
FCis only used in a type position here. While the repository does not currently enforce this via stricter TypeScript configs, importing type-only symbols withimport typeis a best practice that improves clarity and aligns with the pattern used in other packages in this repository.♻️ Proposed fix
-import {FC} from 'react'; +import type {FC} from 'react';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/query/src/components/DataPanel.tsx` around lines 7 - 9, The import of FC from 'react' in the DataPanel component is type-only; change the import to a type import by replacing "import {FC} from 'react'" with a type-only import (e.g., "import type { FC } from 'react'") so the DataPanel: FC declaration uses a type import rather than a runtime import.examples/pivot/src/App.tsx (1)
4-6: Make the ReactFCimport type-only.
FCis only used as a type annotation; preferimport typeto avoid issues with stricter TypeScript module emit settings.♻️ Proposed fix
-import {FC} from 'react'; +import type {FC} from 'react';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/pivot/src/App.tsx` around lines 4 - 6, The import of the React type FC in App.tsx is not type-only; update the import statement that brings in FC so it is a type-only import (used by the App: FC annotation) to avoid runtime import emission under stricter TS settings—replace the regular import of FC with a type-only import and keep the App component signature as App: FC.examples/pivot/src/components/DataPanel.tsx (1)
7-9: Useimport typefor theFCimport.
FCis only used as a type annotation here. While this codebase doesn't haveverbatimModuleSyntaxenabled, usingimport typeis a best practice to ensure the import is properly tree-shaken and avoid any unnecessary runtime overhead.♻️ Proposed fix
-import {FC} from 'react'; +import type {FC} from 'react';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/pivot/src/components/DataPanel.tsx` around lines 7 - 9, The import of FC is only used as a type; change the import statement to a type-only import so it won't be kept at runtime. Update the top-level import from "import {FC} from 'react';" to use "import type" for the FC symbol, leaving the DataPanel: FC usage unchanged; ensure the symbol FC is imported via a type-only import to allow proper tree-shaking.packages/layout/src/docking/dock-layout.ts (1)
117-128: Gate or remove docking debug logs.These logs run from core docking/size paths and can spam production consoles during panel rearrangement. Prefer a debug logger or remove them before release.
Also applies to: 149-164, 342-348, 434-447, 472-476, 559-574
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/layout/src/docking/dock-layout.ts` around lines 117 - 128, The console.log in dock-layout.ts (inside equalizeChildren) and the other debug prints at the indicated ranges should be gated or removed so production consoles aren't spammed; replace direct console.log calls with a debug-level logger or guard them behind a runtime/dev flag (e.g., only run when DEBUG_DOCKING or NODE_ENV !== 'production') and keep the same structured payload (references: equalizeChildren, getLayoutNodeId, getSizeProps) so behavior is unchanged when enabled; do this for all instances (lines ~117-128, 149-164, 342-348, 434-447, 472-476, 559-574). Ensure no console.* calls remain unguarded in DockLayout-related functions.examples/layout/src/components/ResultsPanel.tsx (1)
17-32: Minor: column header/data field naming mismatch.The header is labelled
created_at(Line 17) but the row objects usedate(Lines 22-26, rendered viarow.dateon Line 32). Since theSchemaPaneladvertises acreated_at: TIMESTAMPcolumn, renaming the field tocreated_atwould make the demo data self-consistent.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/layout/src/components/ResultsPanel.tsx` around lines 17 - 32, The table header uses created_at but the mock rows use date; update the mock data objects and render access in ResultsPanel (the array of rows and the JSX that reads row.date) to use created_at instead of date so the header, rendered cell (row.created_at) and the advertised SchemaPanel column (created_at: TIMESTAMP) are consistent.packages/layout/src/docking/DockDragOverlay.tsx (1)
15-30: Consider clamping badge position to viewport.Near the right/bottom edges the badge can extend past the viewport (adding
BADGE_OFFSETunconditionally). Consider flipping to the opposite side or clamping againstwindow.innerWidth/innerHeightminus the badge size when it would overflow. Low priority since it’s cosmetic.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/layout/src/docking/DockDragOverlay.tsx` around lines 15 - 30, The badge currently places itself at cursorPosition + BADGE_OFFSET in DockDragOverlay (used when activePanelTitle and cursorPosition exist) which can overflow the viewport; update the positioning logic used for the createPortal inline style to clamp or flip coordinates against window.innerWidth/window.innerHeight so the badge stays fully visible: measure or approximate the badge size (via a ref or known constants) and if cursorPosition.x + BADGE_OFFSET + badgeWidth > window.innerWidth then position it to the left of the cursor (cursorPosition.x - BADGE_OFFSET - badgeWidth) and similarly for vertical overflow use cursorPosition.y - BADGE_OFFSET - badgeHeight, otherwise use the original offset; ensure BADGE_OFFSET, cursorPosition, and createPortal usage in DockDragOverlay are updated to apply these computed left/top values.packages/layout/src/docking/DockingProvider.tsx (1)
121-144: Cursor tracking breaks under non-pointer sensors.
handleDragMovereadsevent.activatorEvent.clientX/Yto seed the cursor position. Today you only register aPointerSensor, so this is safe, but any future addition ofKeyboardSensor(or similar) will makehasPointerCoordinatesreturn false and the custom drag overlay will stop following the caret. Consider driving the overlay fromtranslatedRect.left/top + window.scrollX/Y(which is available for any sensor) so this remains sensor-agnostic. Non-blocking today.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/layout/src/docking/DockingProvider.tsx` around lines 121 - 144, In handleDragMove, avoid relying on event.activatorEvent.clientX/Y (used only by pointer sensors); instead compute the overlay cursor from the translatedRect coordinates plus page scroll so it works with non-pointer sensors—use translatedRect.left/top combined with window.scrollX/window.scrollY (or similar) to setCursorPosition; keep the existing hasPointerCoordinates branch only for pointer-specific offsets and fallback to using translatedRect + scroll when activatorEvent lacks pointer coordinates, updating the logic in handleDragMove (and leaving updatePreview/setCursorPosition calls intact).examples/layout/src/store.tsx (1)
289-348: Minor: redundant first lookup and lossy equal-size distribution.Two observations on
addChartToDashboard:
- Redundant
findNodeById— lines 294–304 look up and validate the dock on the originalconfig, then lines 308–316 repeat the same lookup/validation on the clonednewConfig. Since you only mutate the clone, the first pass is redundant and can be dropped.Math.floor(100 / totalChildren)loses percent (line 328) — e.g., 3 children → 33 each → 99% total; 6 → 96%. Depending on how the split renderer normalizes sizes this may cause a small residual gap or truncation. Consider distributing the remainder to the first child, or simply omittingdefaultSizeand letting the panel library normalize.🛠️ Suggested simplification
- const dockResult = findNodeById(config, dashboardId); - if (!dockResult || !isLayoutDockNode(dockResult.node)) { - return; - } - - const dockNode = dockResult.node; - - // Find the root split of the dock node - if (!isLayoutSplitNode(dockNode.root)) { - return; - } - - // Clone the config and add the new chart panel to the dock's root split - const newConfig = JSON.parse(JSON.stringify(config)) as LayoutConfig; + // Clone up front and operate on the clone only. + const newConfig = + typeof structuredClone === 'function' + ? (structuredClone(config) as LayoutConfig) + : (JSON.parse(JSON.stringify(config)) as LayoutConfig); const newDockResult = findNodeById(newConfig, dashboardId); if (!newDockResult || !isLayoutDockNode(newDockResult.node)) { return; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/layout/src/store.tsx` around lines 289 - 348, In addChartToDashboard: remove the initial lookup/validation against the original config (the block using findNodeById(config) and isLayoutDockNode on the original dock) and only perform the lookup/validation after cloning (use newConfig and newDockResult/newDockNode), and change the equal-size assignment so it doesn’t use Math.floor(100 / totalChildren) which drops the remainder — either omit setting defaultSize so the panel library normalizes sizes, or compute base = Math.floor(100 / totalChildren) and add the leftover (100 - base * totalChildren) to the first child in newDockNode.root.children; update references to totalChildren, equalSize (or base/leftover) accordingly before calling setConfig(newConfig).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@examples/ai/src/components/DataSourcesPanel.tsx`:
- Around line 7-9: The import of FC should be type-only; change the import to a
type-only import (use "import type { FC } from 'react'") and keep the component
signature export const DataSourcesPanel: FC = () => { ... } unchanged so the
runtime bundle doesn't include a value import for FC; update the import
statement that currently imports FC to use "import type" referencing the FC type
and DataSourcesPanel component.
In `@examples/deckgl-discuss/src/store.ts`:
- Line 26: The code uses the string-indexed enum access
RoomPanelTypes.enum['main'] which hardcodes the literal; replace those
occurrences with the imported MAIN_VIEW constant (i.e., use MAIN_VIEW wherever
RoomPanelTypes.enum['main'] is referenced) so the value stays in sync with the
source of truth; update all references (e.g., the two spots currently accessing
RoomPanelTypes.enum['main']) to use MAIN_VIEW and run type checks to ensure
compatibility with RoomPanelTypes usage.
In `@examples/notebook/src/room.tsx`:
- Around line 14-15: Two identical instances of RoomShell.LoadingProgress are
rendered causing duplicate UI and potential duplicated effects/subscriptions;
remove the duplicate so only a single RoomShell.LoadingProgress remains in the
render. Locate the repeated components (both references to
RoomShell.LoadingProgress) and delete one of them, keeping a single instance to
preserve expected behavior and avoid duplicate side-effects.
In `@examples/query-motherduck/src/store.ts`:
- Around line 49-61: The left tabs container (id RoomPanelTypes.enum['left']) is
initialized with collapsed: true, collapsedSize: 0 and hideTabStrip: true which
hides the Data panel on first load; either ensure there is a UI trigger that
toggles this container (e.g., implement or wire a SidebarButton that flips the
collapsed state for RoomPanelTypes.enum['left']) or change the initial layout to
make the panel discoverable by setting collapsed: false or collapsedSize to a
non-zero value (and/or show the tab strip) in the layout config where children
includes RoomPanelTypes.enum['data'] so users can see and expand the Data panel
on first load.
In `@examples/query/src/App.tsx`:
- Around line 4-6: The import of FC should be type-only since it's only used for
the App type annotation; change the import to use a type-only import (import
type { FC } from 'react') and keep the App: FC annotation as-is so the runtime
bundle no longer includes the FC symbol.
In `@packages/layout-config/README.md`:
- Around line 12-15: The README's public schema section omits the dock node and
its type guards; update the exports/listing to include LayoutDockNode,
isLayoutDockNode and the 'dock' node type so the documentation matches the new
layout model and public API. Specifically add mentions of LayoutDockNode and
isLayoutDockNode alongside LayoutPanelNode/LayoutSplitNode/LayoutTabsNode, and
include 'dock' in the node type list and example snippets (e.g., where
createDefaultLayout(), LayoutConfig, and node-type predicates are documented) so
consumers can find and use the dock node.
In `@packages/layout/README.md`:
- Around line 92-108: The Button handlers in PanelButtons are calling undefined
functions (setActiveTab, addTab, setCollapsed); update each onClick to call the
actions selected from useRoomStore: call setActivePanel for the "Show Data
Panel" button, call addPanelToArea for the "Add Schema Tab" button, and call
setAreaCollapsed for the "Collapse Sidebar" button so the component uses the
declared layout actions (PanelButtons, useRoomStore, layout.setActivePanel,
layout.addPanelToArea, layout.setAreaCollapsed).
In `@packages/layout/src/docking/dock-layout.ts`:
- Around line 449-457: The current logic picks sizedChildren by either
equalizeChildren(children) or splitTargetShare(children, targetIndexAfterInsert,
insertIndex, targetSize), but when moving an existing child (source was removed)
siblings retain old defaultSize and splitTargetShare can produce totals <100%;
normalize sizes after removal. Update the block that computes sizedChildren (the
use of equalizeChildren and splitTargetShare) so that when performing a move
(i.e., sourceRemoved is true or when targetSize is provided and the children
list was mutated by removal) you first normalize remaining children sizes (call
equalizeChildren or a normalizeDefaultSizes helper) before calling
splitTargetShare, ensuring the final sizedChildren totals sum to 100% and
preventing the undersized result shown; reference splitTargetShare,
equalizeChildren, sizedChildren, targetIndexAfterInsert, insertIndex, and
targetSize to locate and change the logic.
In `@packages/layout/src/docking/docking-helpers.ts`:
- Around line 52-65: buildPreview currently generates wrap/insert previews even
when movePanel would reject the drop; update buildPreview to mirror movePanel's
eligibility checks by early-returning null when the target is missing, not
inside a dock (i.e., findNodeById(rootLayout, targetId) yields no valid parent
that is a layout split node), or belongs to a different dock than the source.
Locate buildPreview in docking-helpers.ts and use the same guards as movePanel:
check found from findNodeById, ensure parent exists and
isLayoutSplitNode(parent), and verify the dock/container identity via
getContainerElement(sameAxisParent?.id) (or compare ancestor roots) before
proceeding to compute containerRect so invalid drops produce no preview.
In `@packages/layout/src/layout-tree.ts`:
- Around line 163-175: visitLayoutLeafNodes currently short-circuits based on
truthiness so valid falsy visitor returns (false, 0, '') are treated as
no-result; update the two short-circuit checks inside the isLayoutSplitNode and
isLayoutTabsNode branches (the lines that read "if (result) return result;") to
explicitly check for undefined (e.g., "if (result !== undefined) return
result;") so any non-undefined T value is preserved and returned.
In
`@packages/layout/src/node-renderers/leaf-node-renderer/LeafLayoutDragHandle.tsx`:
- Around line 40-57: The overlay is only revealed via hover
(group-hover:opacity-100) so keyboard users can't see focusable items; update
the container CSS/classes to also reveal on focus by adding focus-within (and
optionally focus-visible on children) so when the inner div with {...attributes}
{...listeners} or the close button (onClick={handleRemove}) receives focus the
parent overlay becomes visible; target the same element that currently has
"opacity-0 group-hover:opacity-100" (the div wrapping GripHorizontalIcon and the
button) and add classes for focus-within:opacity-100 and/or
focus-visible:opacity-100 so the drag grip and XIcon become visible for keyboard
navigation.
---
Outside diff comments:
In `@examples/deckgl-mosaic/src/store.ts`:
- Around line 2-3: Update the import to use the package public entry point:
replace the deep import of MosaicSliceState from
'@sqlrooms/mosaic/dist/MosaicSlice' with an import from '@sqlrooms/mosaic' so
that both MosaicSliceState and createMosaicSlice are imported from the package
root (ensure the import statement imports { MosaicSliceState } from
'@sqlrooms/mosaic' alongside createMosaicSlice).
In `@packages/ui/src/components/tab-strip.tsx`:
- Around line 566-579: The search dropdown still shows the inline close action
regardless of the strip-level closeable prop; update TabStripSearchDropdown to
read closeable from useTabStripContext() and modify renderOpenTabActions to
check both closeable and preventCloseLastTab before rendering
TabStripSearchItemAction (and before calling handleClose). Specifically,
destructure closeable from useTabStripContext(), compute disableClose =
!closeable || (preventCloseLastTab && (openTabsList.length ?? 0) <= 1), and skip
rendering the TabStripSearchItemAction when closeable is false so the dropdown
respects the strip-level closeable setting.
---
Duplicate comments:
In `@apps/sqlrooms-cli-ui/src/createDashboardCommands.ts`:
- Around line 109-118: The execute implementation in createDashboardCommands
(the execute function that calls
getState().dashboard.createDashboardSheet(title)) creates a sheet but doesn't
select it; update this execute to call getState().cells.setCurrentSheet(sheetId)
after obtaining sheetId (mirror the behavior from the generic createSheetCommand
helper) so the new dashboard sheet is set as the current sheet before returning
the success response.
---
Nitpick comments:
In `@examples/canvas/src/components/DataSourcesPanel.tsx`:
- Around line 7-9: The import of React's FC should be a type-only import to
match repo conventions; change the top import to use a type-only import (e.g.,
import type { FC } from 'react') and ensure the component declaration
DataSourcesPanel: FC remains unchanged so only type information is imported.
In `@examples/cosmos-embedding/src/components/DataSourcesPanel.tsx`:
- Around line 83-85: Remove the stale commented-out JSX block referencing
AddDataModal (the fragment containing "<AddDataModal isOpen={isOpen}
onClose={handleModalClose} />") from DataSourcesPanel; either delete the
commented lines entirely or, if you want to preserve intent, replace them with a
short tracked TODO comment explaining why AddDataModal was removed and what
props (isOpen, handleModalClose) it used to require so future restoration is
clear.
In `@examples/deckgl-mosaic/src/store.ts`:
- Around line 27-28: The z.enum call uses an unnecessary "as const" and the
runtime and type share the same name which is confusing; update the declaration
by removing the "as const" from the z.enum argument (keep export const
RoomPanelTypes = z.enum(['left', 'data', 'main'])) and rename the inferred type
to a distinct singular name (export type RoomPanelType = z.infer<typeof
RoomPanelTypes>); also search for any usages of the old type name RoomPanelTypes
in the code and replace them with RoomPanelType.
In `@examples/get-started/src/components/MainView.tsx`:
- Around line 10-12: Change the runtime import of FC to a type-only import:
replace the current import of FC with a type import (use "import type { FC }
from 'react';") so FC is erased at runtime; keep the component declaration
export const MainView: FC = () => { ... } unchanged. This ensures FC is only
used as a TypeScript type and prevents accidental runtime references.
In `@examples/layout/src/components/ResultsPanel.tsx`:
- Around line 17-32: The table header uses created_at but the mock rows use
date; update the mock data objects and render access in ResultsPanel (the array
of rows and the JSX that reads row.date) to use created_at instead of date so
the header, rendered cell (row.created_at) and the advertised SchemaPanel column
(created_at: TIMESTAMP) are consistent.
In `@examples/layout/src/store.tsx`:
- Around line 289-348: In addChartToDashboard: remove the initial
lookup/validation against the original config (the block using
findNodeById(config) and isLayoutDockNode on the original dock) and only perform
the lookup/validation after cloning (use newConfig and
newDockResult/newDockNode), and change the equal-size assignment so it doesn’t
use Math.floor(100 / totalChildren) which drops the remainder — either omit
setting defaultSize so the panel library normalizes sizes, or compute base =
Math.floor(100 / totalChildren) and add the leftover (100 - base *
totalChildren) to the first child in newDockNode.root.children; update
references to totalChildren, equalSize (or base/leftover) accordingly before
calling setConfig(newConfig).
In `@examples/minimal/src/room.tsx`:
- Line 9: The import in room.tsx currently brings in FC as a regular value
import; change it to a type-only import to reflect that FC is used only in type
annotations by replacing the import of FC with an `import type` declaration
(e.g., import type { FC } from 'react') so the symbol FC is treated as a
type-only dependency and bundlers/typecheckers can optimize accordingly; update
the import statement where FC is referenced.
In `@examples/mosaic/src/components/DataSourcesPanel.tsx`:
- Around line 59-61: Remove the commented-out AddDataModal block to eliminate
dead code: delete the commented JSX lines referencing AddDataModal, isOpen, and
handleModalClose in DataSourcesPanel.tsx; if the modal is still intended to be
implemented later, replace the commented block with a concise TODO comment
referencing a tracking issue (e.g., "TODO: implement AddDataModal - see
ISSUE-###") so intent is preserved without leftover commented JSX.
In `@examples/pivot/src/App.tsx`:
- Around line 4-6: The import of the React type FC in App.tsx is not type-only;
update the import statement that brings in FC so it is a type-only import (used
by the App: FC annotation) to avoid runtime import emission under stricter TS
settings—replace the regular import of FC with a type-only import and keep the
App component signature as App: FC.
In `@examples/pivot/src/components/DataPanel.tsx`:
- Around line 7-9: The import of FC is only used as a type; change the import
statement to a type-only import so it won't be kept at runtime. Update the
top-level import from "import {FC} from 'react';" to use "import type" for the
FC symbol, leaving the DataPanel: FC usage unchanged; ensure the symbol FC is
imported via a type-only import to allow proper tree-shaking.
In `@examples/query-motherduck/src/components/MainView.tsx`:
- Line 22: MainView.tsx currently imports MD_TOKEN_KEY from ../App which couples
the child component to the root; create a neutral constants module (e.g., export
MD_TOKEN_KEY from a new module like tokenConstants or constants) and move the
MD_TOKEN_KEY definition there, then update MainView.tsx and App (and any other
consumers) to import MD_TOKEN_KEY from the new module (ensure the symbol name
MD_TOKEN_KEY is exported as-is to avoid breaking usages).
In `@examples/query-motherduck/src/store.ts`:
- Line 19: RoomPanelTypes currently mixes a container id ('left') with actual
panel keys causing confusion when iterating RoomPanelTypes.options and indexing
layout.panels; split the namespaces by creating a new enum (e.g., ContainerIds =
z.enum(['left'] as const)) and change RoomPanelTypes to only include real panel
keys (e.g., z.enum(['data','main'] as const)), then update usages that refer to
the tabs container id to use ContainerIds (or add a clarifying comment if you
intentionally keep 'left' in the enum) and ensure code that indexes
layout.panels uses RoomPanelTypes only.
In `@examples/query/src/components/DataPanel.tsx`:
- Around line 7-9: The import of FC from 'react' in the DataPanel component is
type-only; change the import to a type import by replacing "import {FC} from
'react'" with a type-only import (e.g., "import type { FC } from 'react'") so
the DataPanel: FC declaration uses a type import rather than a runtime import.
In `@packages/layout/src/docking/dock-layout.ts`:
- Around line 117-128: The console.log in dock-layout.ts (inside
equalizeChildren) and the other debug prints at the indicated ranges should be
gated or removed so production consoles aren't spammed; replace direct
console.log calls with a debug-level logger or guard them behind a runtime/dev
flag (e.g., only run when DEBUG_DOCKING or NODE_ENV !== 'production') and keep
the same structured payload (references: equalizeChildren, getLayoutNodeId,
getSizeProps) so behavior is unchanged when enabled; do this for all instances
(lines ~117-128, 149-164, 342-348, 434-447, 472-476, 559-574). Ensure no
console.* calls remain unguarded in DockLayout-related functions.
In `@packages/layout/src/docking/DockDragOverlay.tsx`:
- Around line 15-30: The badge currently places itself at cursorPosition +
BADGE_OFFSET in DockDragOverlay (used when activePanelTitle and cursorPosition
exist) which can overflow the viewport; update the positioning logic used for
the createPortal inline style to clamp or flip coordinates against
window.innerWidth/window.innerHeight so the badge stays fully visible: measure
or approximate the badge size (via a ref or known constants) and if
cursorPosition.x + BADGE_OFFSET + badgeWidth > window.innerWidth then position
it to the left of the cursor (cursorPosition.x - BADGE_OFFSET - badgeWidth) and
similarly for vertical overflow use cursorPosition.y - BADGE_OFFSET -
badgeHeight, otherwise use the original offset; ensure BADGE_OFFSET,
cursorPosition, and createPortal usage in DockDragOverlay are updated to apply
these computed left/top values.
In `@packages/layout/src/docking/DockingProvider.tsx`:
- Around line 121-144: In handleDragMove, avoid relying on
event.activatorEvent.clientX/Y (used only by pointer sensors); instead compute
the overlay cursor from the translatedRect coordinates plus page scroll so it
works with non-pointer sensors—use translatedRect.left/top combined with
window.scrollX/window.scrollY (or similar) to setCursorPosition; keep the
existing hasPointerCoordinates branch only for pointer-specific offsets and
fallback to using translatedRect + scroll when activatorEvent lacks pointer
coordinates, updating the logic in handleDragMove (and leaving
updatePreview/setCursorPosition calls intact).
In `@packages/ui/src/components/tab-strip.tsx`:
- Around line 136-141: TAB_STRIP_BUTTON_CLASSNAMES currently mixes focus/hover
ring classes with sizing/padding (px-6 py-1, min-h-7) which overrides Button
size="icon"; split this constant into two: e.g. TAB_STRIP_SURFACE_CLASSNAMES
(shared focus/hover/ring/overflow classes) and TAB_STRIP_TEXT_PADDING_CLASSNAMES
(padding/min-height for text tabs), then update uses so TabsTrigger and the text
tab composition include both arrays but the search dropdown trigger and
TabStripButton (Button size="icon") only receive TAB_STRIP_SURFACE_CLASSNAMES so
their intrinsic size/padding from size="icon" is preserved.
🪄 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: 9f1b0ea4-436f-4a66-9ae1-bec7a3b461f5
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (116)
apps/sqlrooms-cli-ui/src/artifactTypes.tsxapps/sqlrooms-cli-ui/src/createDashboardAiTools.tsapps/sqlrooms-cli-ui/src/createDashboardCommands.tsapps/sqlrooms-cli-ui/src/layout.tsxapps/sqlrooms-cli-ui/src/workspace/ArtifactsContainerPanel.tsxdocs/upgrade-guide.mdexamples/ai-rag/src/components/DataSourcesPanel.tsxexamples/ai-rag/src/components/MainView.tsxexamples/ai-rag/src/room.tsxexamples/ai-rag/src/store.tsexamples/ai/src/components/DataSourcesPanel.tsxexamples/ai/src/components/MainView.tsxexamples/ai/src/room.tsxexamples/ai/src/store.tsexamples/canvas-sync/src/components/DataSourcesPanel.tsxexamples/canvas-sync/src/components/InputApiKey.tsxexamples/canvas-sync/src/room.tsxexamples/canvas-sync/src/store.tsexamples/canvas/src/components/DataSourcesPanel.tsxexamples/canvas/src/components/InputApiKey.tsxexamples/canvas/src/room.tsxexamples/canvas/src/store.tsexamples/cosmos-embedding/src/components/DataSourcesPanel.tsxexamples/cosmos-embedding/src/room.tsxexamples/cosmos-embedding/src/store.tsexamples/cosmos/src/components/DataSourcesPanel.tsxexamples/cosmos/src/room.tsxexamples/cosmos/src/store.tsexamples/deckgl-discuss/src/room.tsxexamples/deckgl-discuss/src/store.tsexamples/deckgl-mosaic/src/room.tsxexamples/deckgl-mosaic/src/store.tsexamples/deckgl/package.jsonexamples/deckgl/src/components/DataPanel.tsxexamples/deckgl/src/room.tsxexamples/deckgl/src/store.tsexamples/get-started/src/components/MainView.tsxexamples/get-started/src/room.tsxexamples/kepler/src/components/DataPanel.tsxexamples/kepler/src/room.tsxexamples/kepler/src/store.tsexamples/layout/package.jsonexamples/layout/src/App.tsxexamples/layout/src/components/BottomTabs.tsxexamples/layout/src/components/CollapseBottomButton.tsxexamples/layout/src/components/ConsolePanel.tsxexamples/layout/src/components/DashboardTabs.tsxexamples/layout/src/components/DataSourcesPanel.tsxexamples/layout/src/components/DynamicChartPanel.tsxexamples/layout/src/components/MainPanel.tsxexamples/layout/src/components/ResultsPanel.tsxexamples/layout/src/components/SchemaPanel.tsxexamples/layout/src/panel-types.tsexamples/layout/src/store.tsxexamples/minimal/src/room.tsxexamples/mosaic/src/components/DataSourcesPanel.tsxexamples/mosaic/src/components/MainView.tsxexamples/mosaic/src/components/RoomDetailsPanel.tsxexamples/mosaic/src/room.tsxexamples/mosaic/src/store.tsexamples/multi-room/src/RoomPage.tsxexamples/nextjs/src/components/app-shell-client.tsxexamples/nextjs/src/components/main-view.tsxexamples/nextjs/src/components/room.tsxexamples/notebook/src/components/DataSourcesPanel.tsxexamples/notebook/src/components/NotebookPanel.tsxexamples/notebook/src/room.tsxexamples/notebook/src/store.tsexamples/pivot/src/App.tsxexamples/pivot/src/components/DataPanel.tsxexamples/pivot/src/components/MainView.tsxexamples/pivot/src/store.tsexamples/query-motherduck/src/App.tsxexamples/query-motherduck/src/Room.tsxexamples/query-motherduck/src/components/DataPanel.tsxexamples/query-motherduck/src/components/MainView.tsxexamples/query-motherduck/src/components/MdConnectPane.tsxexamples/query-motherduck/src/store.tsexamples/query-pwa/src/App.tsxexamples/query-pwa/src/components/DataPanel.tsxexamples/query-pwa/src/components/MainView.tsxexamples/query-pwa/src/store.tsexamples/query-websocket/src/App.tsxexamples/query-websocket/src/components/DataSourcesPanel.tsxexamples/query-websocket/src/components/MainView.tsxexamples/query-websocket/src/store.tsexamples/query/src/App.tsxexamples/query/src/components/DataPanel.tsxexamples/query/src/components/MainView.tsxexamples/query/src/store.tspackages/layout-config/README.mdpackages/layout/README.mdpackages/layout/package.jsonpackages/layout/src/LayoutRenderer.tsxpackages/layout/src/LayoutSlice.tspackages/layout/src/docking/DockDragOverlay.tsxpackages/layout/src/docking/DockPreviewOverlay.tsxpackages/layout/src/docking/DockingContext.tsxpackages/layout/src/docking/DockingProvider.tsxpackages/layout/src/docking/dock-layout.tspackages/layout/src/docking/docking-helpers.tspackages/layout/src/docking/docking-types.tspackages/layout/src/index.tspackages/layout/src/layout-tree.tspackages/layout/src/node-renderers/RenderNodeContext.tsxpackages/layout/src/node-renderers/dock-node-renderer/DockLayout.tsxpackages/layout/src/node-renderers/leaf-node-renderer/LeafLayout.tsxpackages/layout/src/node-renderers/leaf-node-renderer/LeafLayoutContent.tsxpackages/layout/src/node-renderers/leaf-node-renderer/LeafLayoutDragHandle.tsxpackages/layout/src/node-renderers/renderLayoutNode.tsxpackages/layout/src/node-renderers/split-node-renderer/SplitLayoutPanelContent.tsxpackages/layout/src/node-renderers/tabs-node-renderer/TabsLayoutTabContent.tsxpackages/layout/src/node-renderers/utils.tspackages/layout/src/types.tspackages/room-shell/src/index.tspackages/ui/src/components/tab-strip.tsx
✅ Files skipped from review due to trivial changes (17)
- examples/canvas/src/components/InputApiKey.tsx
- examples/deckgl/package.json
- examples/canvas-sync/src/components/InputApiKey.tsx
- examples/notebook/src/components/NotebookPanel.tsx
- examples/kepler/src/components/DataPanel.tsx
- examples/ai/src/components/MainView.tsx
- examples/pivot/src/components/MainView.tsx
- examples/deckgl/src/components/DataPanel.tsx
- examples/layout/src/components/DataSourcesPanel.tsx
- examples/layout/src/components/ConsolePanel.tsx
- examples/nextjs/src/components/app-shell-client.tsx
- examples/layout/src/App.tsx
- examples/mosaic/src/components/MainView.tsx
- examples/layout/package.json
- apps/sqlrooms-cli-ui/src/artifactTypes.tsx
- packages/layout/src/docking/docking-types.ts
- packages/layout/src/types.ts
🚧 Files skipped from review as they are similar to previous changes (22)
- packages/layout/src/node-renderers/tabs-node-renderer/TabsLayoutTabContent.tsx
- examples/mosaic/src/room.tsx
- packages/layout/package.json
- packages/layout/src/node-renderers/split-node-renderer/SplitLayoutPanelContent.tsx
- apps/sqlrooms-cli-ui/src/layout.tsx
- examples/kepler/src/store.ts
- examples/deckgl/src/store.ts
- examples/query/src/store.ts
- examples/ai/src/store.ts
- packages/layout/src/node-renderers/dock-node-renderer/DockLayout.tsx
- apps/sqlrooms-cli-ui/src/workspace/ArtifactsContainerPanel.tsx
- packages/layout/src/LayoutRenderer.tsx
- examples/notebook/src/store.ts
- examples/query-pwa/src/store.ts
- examples/ai-rag/src/store.ts
- packages/layout/src/node-renderers/utils.ts
- examples/canvas/src/store.ts
- examples/cosmos/src/store.ts
- examples/query-websocket/src/store.ts
- docs/upgrade-guide.md
- packages/layout/src/docking/DockingContext.tsx
- examples/mosaic/src/store.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
examples/layout/src/components/DynamicChartHeader.tsx (1)
22-45: Nit: unnecessary Fragment wrapper.The
<>...</>wraps a singleLeafLayout.Headerchild and can be removed.♻️ Proposed cleanup
return ( - <> - <LeafLayout.Header> + <LeafLayout.Header> <div className="text-muted-foreground flex h-8 w-full border-b"> ... </div> - </LeafLayout.Header> - </> + </LeafLayout.Header> );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/layout/src/components/DynamicChartHeader.tsx` around lines 22 - 45, Remove the unnecessary React fragment wrapping the single LeafLayout.Header element in DynamicChartHeader: delete the surrounding <> and </> so the component returns the LeafLayout.Header directly (locate the return that currently renders <> <LeafLayout.Header> ... </LeafLayout.Header> </> and replace it with just <LeafLayout.Header> ... </LeafLayout.Header>). Ensure no other JSX siblings are left at the same return level and keep existing props and children (GripVerticalIcon, title, Button and handleRemove) unchanged.packages/layout/src/node-renderers/leaf-node-renderer/useIsDockable.ts (1)
1-13: Nit: filename doesn't match the exported hook.The file is
useIsDockable.tsbut the only export isuseIsDockablePanel. Renaming the file touseIsDockablePanel.tswould keep import paths and symbol names aligned and make the module easier to discover.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/layout/src/node-renderers/leaf-node-renderer/useIsDockable.ts` around lines 1 - 13, The exported hook is named useIsDockablePanel but the file is useIsDockable.ts; rename the module file to useIsDockablePanel.ts so the filename matches the exported symbol (useIsDockablePanel) and imports are discoverable; update any imports that reference useIsDockable.ts to the new filename if your tooling doesn't auto-update them.packages/layout/src/node-renderers/leaf-node-renderer/LeafLayoutPanel.tsx (1)
49-58: Redundantopacity-100class and dead branch when not dockable.
opacity-100is the default; the second object key only exists to cancel the first, so you can drop it.- When
isDockablePanelis false,isDraggingcan never be true (the draggable is disabled), so that condition is effectivelyisDraggingalone. Simplifying makes intent clearer.♻️ Suggested simplification
className={cn( 'group relative flex h-full w-full flex-col overflow-hidden', - { - 'opacity-30': isDockablePanel && isDragging, - 'opacity-100': isDockablePanel && !isDragging, - }, + isDragging && 'opacity-30', )}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/layout/src/node-renderers/leaf-node-renderer/LeafLayoutPanel.tsx` around lines 49 - 58, The className object in LeafLayoutPanel's JSX (the cn call using setNodeRef) contains a redundant 'opacity-100' branch and a dead branch when !isDockablePanel; remove the 'opacity-100' entry and simplify the opacity logic to only conditionally apply 'opacity-30' (e.g., 'opacity-30': isDockablePanel && isDragging) so when not dockable no opacity classes are toggled; update the className object in the component where setNodeRef, cn, isDockablePanel, and isDragging are used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@examples/layout/src/components/DynamicChart.tsx`:
- Around line 53-64: The chart markup in DynamicChart is currently unlabeled and
should be made accessible: update the top-level chart container (the inner div
with class "flex flex-1 items-end...") to either include role="img" and a
descriptive aria-label (or aria-labelledby pointing to a visible/visually-hidden
title) when the visualization conveys information, or mark it decorative by
adding aria-hidden="true" (or role="presentation") if it does not convey
meaningful data; ensure whichever approach you choose uses the container div as
the target so screen readers properly include or ignore the chart.
In
`@packages/layout/src/node-renderers/leaf-node-renderer/LeafLayoutDragHandle.tsx`:
- Around line 14-18: The drag-handle div in the LeafLayoutDragHandle component
currently relies on children for an accessible name but may be announced as an
unlabeled button by assistive tech; update the component signature
(LeafLayoutDragHandle) to accept an optional ariaLabel and ariaDescribedBy (or
aria-describedby) prop and spread them onto the root div along with attributes
and listeners so the role="button" element has an explicit accessible
name/description, or alternatively validate/document that callers must pass
labeled content if you prefer not to add props. Ensure you reference the props
in the component and include them in the JSX output for the div that currently
returns <div className={className} {...attributes} {...listeners}>.
---
Nitpick comments:
In `@examples/layout/src/components/DynamicChartHeader.tsx`:
- Around line 22-45: Remove the unnecessary React fragment wrapping the single
LeafLayout.Header element in DynamicChartHeader: delete the surrounding <> and
</> so the component returns the LeafLayout.Header directly (locate the return
that currently renders <> <LeafLayout.Header> ... </LeafLayout.Header> </> and
replace it with just <LeafLayout.Header> ... </LeafLayout.Header>). Ensure no
other JSX siblings are left at the same return level and keep existing props and
children (GripVerticalIcon, title, Button and handleRemove) unchanged.
In `@packages/layout/src/node-renderers/leaf-node-renderer/LeafLayoutPanel.tsx`:
- Around line 49-58: The className object in LeafLayoutPanel's JSX (the cn call
using setNodeRef) contains a redundant 'opacity-100' branch and a dead branch
when !isDockablePanel; remove the 'opacity-100' entry and simplify the opacity
logic to only conditionally apply 'opacity-30' (e.g., 'opacity-30':
isDockablePanel && isDragging) so when not dockable no opacity classes are
toggled; update the className object in the component where setNodeRef, cn,
isDockablePanel, and isDragging are used.
In `@packages/layout/src/node-renderers/leaf-node-renderer/useIsDockable.ts`:
- Around line 1-13: The exported hook is named useIsDockablePanel but the file
is useIsDockable.ts; rename the module file to useIsDockablePanel.ts so the
filename matches the exported symbol (useIsDockablePanel) and imports are
discoverable; update any imports that reference useIsDockable.ts to the new
filename if your tooling doesn't auto-update them.
🪄 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: 68effce0-2fac-4083-b29b-1ea628ccb648
📒 Files selected for processing (10)
examples/layout/src/components/DynamicChart.tsxexamples/layout/src/components/DynamicChartHeader.tsxexamples/layout/src/components/DynamicChartPanel.tsxpackages/layout/src/index.tspackages/layout/src/node-renderers/leaf-node-renderer/LeafLayout.tsxpackages/layout/src/node-renderers/leaf-node-renderer/LeafLayoutDragHandle.tsxpackages/layout/src/node-renderers/leaf-node-renderer/LeafLayoutHeader.tsxpackages/layout/src/node-renderers/leaf-node-renderer/LeafLayoutPanel.tsxpackages/layout/src/node-renderers/leaf-node-renderer/LeafLayoutPanelDraggableContext.tsxpackages/layout/src/node-renderers/leaf-node-renderer/useIsDockable.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- examples/layout/src/components/DynamicChartPanel.tsx
- packages/layout/src/node-renderers/leaf-node-renderer/LeafLayout.tsx
There was a problem hiding this comment.
🧹 Nitpick comments (2)
packages/room-shell/src/RoomShellSidebarButtons.tsx (2)
69-83: Minor: Inline fallback function creates new reference each render.The
(() => null)fallback creates a new function on every render wheninfo?.iconis undefined. Consider extracting a stable fallback.♻️ Suggested refactor
+const NoIcon: FC<{className?: string}> = () => null; + const RoomShellSidebarButton: FC<{roomPanelType: string}> = ({ // ... return ( <SidebarButton key={roomPanelType} title={info?.title ?? ''} isSelected={visibleRoomPanels.includes(roomPanelType)} isDisabled={!initialized} - icon={info?.icon ?? (() => null)} + icon={info?.icon ?? NoIcon} onClick={() => togglePanel(roomPanelType)} /> );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/room-shell/src/RoomShellSidebarButtons.tsx` around lines 69 - 83, The inline fallback (() => null) creates a new function each render; define a stable NOOP_ICON function (e.g., const NOOP_ICON = () => null) outside the component and replace the fallback in the SidebarButton prop from icon={info?.icon ?? (() => null)} to icon={info?.icon ?? NOOP_ICON} so the icon prop keeps a stable reference; update RoomShellSidebarButtons.tsx to declare NOOP_ICON near the top of the module and use it in the render.
92-118: Memoize the filtered panel lists.The filtering logic runs on every render, calling
resolvePanelDefinitionfor each panel even whenpanelshasn't changed. Per coding guidelines, derived arrays should be computed withuseMemo.♻️ Suggested refactor
const RoomShellSidebarButtons: FC<{className?: string}> = ({className}) => { const panels = useBaseRoomShellStore((state) => state.layout.panels); + const sidebarPanels = useMemo(() => { + if (!panels) return []; + return Object.keys(panels).filter((key) => { + const def = panels[key]; + const resolved = def ? resolvePanelDefinition(def, {panelId: key}) : undefined; + return resolved?.placement === 'sidebar'; + }); + }, [panels]); + + const sidebarBottomPanels = useMemo(() => { + if (!panels) return []; + return Object.keys(panels).filter((key) => { + const def = panels[key]; + const resolved = def ? resolvePanelDefinition(def, {panelId: key}) : undefined; + return resolved?.placement === 'sidebar-bottom'; + }); + }, [panels]); + return ( <div className={cn('flex h-full grow flex-col', className)}> <div className="flex flex-col gap-2"> - {panels - ? Object.keys(panels) - .filter((key) => { - const def = panels[key]; - const resolved = def - ? resolvePanelDefinition(def, {panelId: key}) - : undefined; - return resolved?.placement === 'sidebar'; - }) - .map((type) => ( - <RoomShellSidebarButton key={type} roomPanelType={type} /> - )) - : null} + {sidebarPanels.map((type) => ( + <RoomShellSidebarButton key={type} roomPanelType={type} /> + ))} </div> <div className="flex-1" /> <div className="flex flex-col gap-2"> - {Object.keys(panels) - .filter((key) => { - const def = panels[key]; - const resolved = def - ? resolvePanelDefinition(def, {panelId: key}) - : undefined; - return resolved?.placement === 'sidebar-bottom'; - }) - .map((type) => ( - <RoomShellSidebarButton key={type} roomPanelType={type} /> - ))} + {sidebarBottomPanels.map((type) => ( + <RoomShellSidebarButton key={type} roomPanelType={type} /> + ))} </div> </div> ); };As per coding guidelines: "When using Zustand/room-store selectors in React, do not create derived arrays/objects/functions inside the selector; instead select stable raw state first, then derive with
React.useMemoin the component".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/room-shell/src/RoomShellSidebarButtons.tsx` around lines 92 - 118, The component repeatedly filters `panels` and calls `resolvePanelDefinition` on every render; wrap the two derived arrays (sidebar and sidebar-bottom) in React.useMemo so they are recomputed only when `panels` changes. Specifically, compute memoized arrays (e.g. sidebarPanelTypes and sidebarBottomPanelTypes) using useMemo that iterate Object.keys(panels), call resolvePanelDefinition(def, {panelId: key}) and filter by resolved.placement === 'sidebar' and 'sidebar-bottom' respectively, then map those memo values to render <RoomShellSidebarButton roomPanelType={type} /> instead of filtering inline.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@packages/room-shell/src/RoomShellSidebarButtons.tsx`:
- Around line 69-83: The inline fallback (() => null) creates a new function
each render; define a stable NOOP_ICON function (e.g., const NOOP_ICON = () =>
null) outside the component and replace the fallback in the SidebarButton prop
from icon={info?.icon ?? (() => null)} to icon={info?.icon ?? NOOP_ICON} so the
icon prop keeps a stable reference; update RoomShellSidebarButtons.tsx to
declare NOOP_ICON near the top of the module and use it in the render.
- Around line 92-118: The component repeatedly filters `panels` and calls
`resolvePanelDefinition` on every render; wrap the two derived arrays (sidebar
and sidebar-bottom) in React.useMemo so they are recomputed only when `panels`
changes. Specifically, compute memoized arrays (e.g. sidebarPanelTypes and
sidebarBottomPanelTypes) using useMemo that iterate Object.keys(panels), call
resolvePanelDefinition(def, {panelId: key}) and filter by resolved.placement ===
'sidebar' and 'sidebar-bottom' respectively, then map those memo values to
render <RoomShellSidebarButton roomPanelType={type} /> instead of filtering
inline.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 771af8d7-0dbf-4cc8-9cca-3dcc4d213236
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (13)
apps/sqlrooms-cli-ui/package.jsondocs/upgrade-guide.mdexamples/deckgl/package.jsonpackages/deck/__tests__/prepareDeckDataset.test.tspackages/deck/src/prepare/detectGeometryColumn.tspackages/deck/src/prepare/geoarrow.tspackages/layout-config/package.jsonpackages/layout/package.jsonpackages/mosaic/src/profiler/useMosaicProfiler.tspackages/preset-jest/package.jsonpackages/room-shell/package.jsonpackages/room-shell/src/RoomShellSidebarButtons.tsxpackages/ui/package.json
💤 Files with no reviewable changes (2)
- packages/preset-jest/package.json
- apps/sqlrooms-cli-ui/package.json
✅ Files skipped from review due to trivial changes (7)
- packages/mosaic/src/profiler/useMosaicProfiler.ts
- packages/deck/src/prepare/geoarrow.ts
- packages/deck/src/prepare/detectGeometryColumn.ts
- packages/layout-config/package.json
- packages/ui/package.json
- packages/deck/tests/prepareDeckDataset.test.ts
- examples/deckgl/package.json
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/room-shell/package.json
- packages/layout/package.json
- docs/upgrade-guide.md
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Documentation