Repository navigation
feat(layout): Add grid node type for scrollable dashboard layouts - #575
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds first-class scrollable grid dashboards: new LayoutGridNode types and renderer (react-grid-layout), grid-aware layout traversal/context, Mosaic dashboards now persist a layoutType (dock|grid) with grid placement logic, CLI/tools/store thread layoutType through dashboard creation, plus tests, docs, and dependency updates. ChangesGrid dashboard end-to-end
Sequence DiagramsequenceDiagram
participant User
participant CLI as CLI UI
participant Store as Room Store
participant Mosaic as Mosaic Slice
participant Layout as Layout Renderer
participant Grid as react-grid-layout
User->>CLI: Create dashboard (title, layoutType='grid')
CLI->>Store: createDashboardArtifact(title, 'grid')
Store->>Mosaic: createDashboard(title, layoutType='grid')
Mosaic->>Mosaic: Initialize LayoutGridNode root & layouts
Mosaic-->>Store: dashboardId
Store-->>CLI: dashboardId
CLI->>Layout: Render dashboard artifact
Layout->>Mosaic: Request dashboard layout
Mosaic-->>Layout: LayoutGridNode root
Layout->>Grid: Render responsive grid items
Grid-->>User: Interactive grid (drag/resize)
User->>Grid: Drag/resize item
Grid->>Layout: onLayoutChange(normalized)
Layout->>Mosaic: Persist updated grid layouts
Mosaic->>Store: Update dashboard layout state
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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. Review rate limit: 0/1 reviews remaining, refill in 60 minutes.Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/mosaic/src/dashboard/MosaicDashboardSlice.ts (1)
403-425:⚠️ Potential issue | 🟠 Major | ⚡ Quick winNormalize invalid grid layouts before repairing panel membership.
For
layoutType === 'grid', this path only appends missing panels. IfsetLayout()receives a non-grid layout that already contains some dashboard panels, the first missing panel rebuilds it as a fresh grid with only that missing panel, so the panels already present in the incoming layout are lost; and when no panels are missing, the dashboard can stay in a non-grid shape.Suggested fix
function ensureLayoutContainsDashboardPanels( layout: LayoutNode | null, dashboardId: string, panelIds: string[], layoutType: MosaicDashboardLayoutType = 'dock', ): LayoutNode | null { - let nextLayout = layout; + let nextLayout = + layoutType === 'grid' && !isLayoutGridNode(layout) + ? createDashboardGridLayout(dashboardId) + : layout; const existing = collectPanelIds(nextLayout); for (const panelId of panelIds) { const layoutPanelId = getMosaicDashboardPanelId(dashboardId, panelId); if (!existing.has(layoutPanelId)) {Also applies to: 796-810
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/mosaic/src/dashboard/MosaicDashboardSlice.ts` around lines 403 - 425, ensureLayoutContainsDashboardPanels currently only appends missing panels and when layoutType === 'grid' it may rebuild a fresh grid containing only the first missing panel (losing dashboard panels that were present in a non-grid incoming layout) or leave non-grid shapes unchanged; fix by normalizing any incoming layout to a proper grid before adding panels: when layoutType === 'grid' gather existing dashboard panel nodes (using collectPanelIds and getMosaicDashboardPanelId to identify them), transform the current layout into a grid container preserving those existing panel nodes, then append any missing panels via appendPanelToGridLayout; keep appendPanelToLayout for non-grid paths and reuse createDashboardPanelNode for new nodes so no panels are lost.
🧹 Nitpick comments (2)
apps/sqlrooms-cli-ui/src/createDashboardCommands.ts (1)
22-27: ⚡ Quick winMake
layoutTypedefault to'grid'in command schema for backward compatibility.Line 24 currently makes
layoutTypemandatory, which can break existingdashboard.create-artifactcallers that only send a title or empty input.Proposed diff
-const DashboardCreateArtifactCommandInput = z.object({ +const DashboardCreateArtifactCommandInput = z + .object({ title: z.string().optional().describe('Optional artifact title.'), layoutType: z .enum(['dock', 'grid']) + .default('grid') .describe('Dashboard layout node type to use at creation time.'), -}); + }) + .default({});Also applies to: 130-133
🤖 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 22 - 27, The DashboardCreateArtifactCommandInput schema currently makes layoutType required; change the z.enum(['dock','grid']) call in DashboardCreateArtifactCommandInput to include .optional().default('grid') (e.g., z.enum([...]).optional().default('grid').describe(...)) so callers that omit layoutType get 'grid' by default; apply the same change to the other layoutType schema occurrence in the file (the second layoutType definition around lines 130-133) to ensure backward compatibility across both command schemas.packages/layout/src/node-renderers/leaf-node-renderer/LeafLayoutHeader.tsx (1)
12-14: ⚡ Quick winMove ancestor derivation out of the Zustand selector.
The selector currently performs a derived lookup (
findAncestorOfType). Select stable raw state first, then derive viauseMemoin-component to follow the selector stability rule.Suggested refactor
-import {FC, PropsWithChildren} from 'react'; +import {FC, PropsWithChildren, useMemo} from 'react'; @@ - const gridAncestor = useStoreWithLayout((state) => - state.layout.findAncestorOfType(nodeId, 'grid'), - ); + const layout = useStoreWithLayout((state) => state.layout); + const gridAncestor = useMemo( + () => layout.findAncestorOfType(nodeId, 'grid'), + [layout, nodeId], + );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/layout/src/node-renderers/leaf-node-renderer/LeafLayoutHeader.tsx` around lines 12 - 14, The selector is performing derived work by calling state.layout.findAncestorOfType inside useStoreWithLayout; instead, select the stable raw piece (e.g., select state.layout or a getter like layout) with useStoreWithLayout and then compute gridAncestor inside the LeafLayoutHeader component using React.useMemo calling layout.findAncestorOfType(nodeId, 'grid'); update references to gridAncestor to use the memoized value and keep nodeId and useStoreWithLayout as the lookup anchors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/sqlrooms-cli-ui/src/createDashboardAiTools.ts`:
- Around line 14-17: The schema field layoutType is currently required and
causes minimal create_dashboard_artifact calls (e.g., {} or { title }) to fail;
update the Zod definition for layoutType to be optional with a sensible default
(e.g., .optional().default('dock')) so validation succeeds when callers omit
it—modify the layoutType declaration in the dashboard schema (the symbol
layoutType) and ensure create_dashboard_artifact consumers rely on that default
rather than passing a value.
In `@examples/layout/src/store.tsx`:
- Around line 86-103: createGridLayoutItem always returns x: 0 causing new
charts to stack in the left column; change it to compute x from existing layout
so items fill the next grid slot (matching createDashboardGridNode's two-column
behavior). Inside createGridLayoutItem, compute colsPerRow = Math.max(1,
Math.floor(cols / w)), derive an index from the current layout (e.g.,
layout.length or count of items at that y), and set x = (index % colsPerRow) * w
so new items alternate into the next column(s) instead of always x: 0; keep y,
w, h logic the same and reference the layout array, cols, w, and
createGridLayoutItem.
In `@packages/layout/src/node-renderers/grid-node-renderer/GridLayout.tsx`:
- Around line 123-126: createDefaultItem and the layouts fallback use a fixed
12-column and increment y by 1 which causes overlap because each item has h: 2
and ignores node.cols; update createDefaultItem to read the effective column
count (use node.cols || 12) and compute x = (index * w) % cols and y =
Math.floor((index * w) / cols) * h so rows advance by item height; likewise,
when generating the fallback layouts (the code that maps breakpoints into
LayoutGridItem arrays), pass the resolved cols for that breakpoint into the
layout generator so each breakpoint uses its correct column count instead of
hardcoded 12. Ensure you reference and update createDefaultItem and the
layouts-generation block that produces the LayoutGridItem arrays to use the same
cols logic and formula.
---
Outside diff comments:
In `@packages/mosaic/src/dashboard/MosaicDashboardSlice.ts`:
- Around line 403-425: ensureLayoutContainsDashboardPanels currently only
appends missing panels and when layoutType === 'grid' it may rebuild a fresh
grid containing only the first missing panel (losing dashboard panels that were
present in a non-grid incoming layout) or leave non-grid shapes unchanged; fix
by normalizing any incoming layout to a proper grid before adding panels: when
layoutType === 'grid' gather existing dashboard panel nodes (using
collectPanelIds and getMosaicDashboardPanelId to identify them), transform the
current layout into a grid container preserving those existing panel nodes, then
append any missing panels via appendPanelToGridLayout; keep appendPanelToLayout
for non-grid paths and reuse createDashboardPanelNode for new nodes so no panels
are lost.
---
Nitpick comments:
In `@apps/sqlrooms-cli-ui/src/createDashboardCommands.ts`:
- Around line 22-27: The DashboardCreateArtifactCommandInput schema currently
makes layoutType required; change the z.enum(['dock','grid']) call in
DashboardCreateArtifactCommandInput to include .optional().default('grid')
(e.g., z.enum([...]).optional().default('grid').describe(...)) so callers that
omit layoutType get 'grid' by default; apply the same change to the other
layoutType schema occurrence in the file (the second layoutType definition
around lines 130-133) to ensure backward compatibility across both command
schemas.
In `@packages/layout/src/node-renderers/leaf-node-renderer/LeafLayoutHeader.tsx`:
- Around line 12-14: The selector is performing derived work by calling
state.layout.findAncestorOfType inside useStoreWithLayout; instead, select the
stable raw piece (e.g., select state.layout or a getter like layout) with
useStoreWithLayout and then compute gridAncestor inside the LeafLayoutHeader
component using React.useMemo calling layout.findAncestorOfType(nodeId, 'grid');
update references to gridAncestor to use the memoized value and keep nodeId and
useStoreWithLayout as the lookup anchors.
🪄 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: 6447aefd-aea9-4f7a-8ad3-ea3595497cdc
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (38)
apps/sqlrooms-cli-ui/README.mdapps/sqlrooms-cli-ui/src/artifactTypes.tsxapps/sqlrooms-cli-ui/src/createDashboardAiTools.tsapps/sqlrooms-cli-ui/src/createDashboardChartTemplateTool.tsapps/sqlrooms-cli-ui/src/createDashboardCommands.tsapps/sqlrooms-cli-ui/src/store-types.tsapps/sqlrooms-cli-ui/src/store.tsapps/sqlrooms-cli-ui/src/workspace/ArtifactsContainerPanel.tsxexamples/layout/src/components/DashboardTabs.tsxexamples/layout/src/store.tsxpackages/layout-config/__tests__/LayoutConfig.test.tspackages/layout-config/src/LayoutConfig.tspackages/layout-config/src/index.tspackages/layout-config/src/tabs-helpers.tspackages/layout/README.mdpackages/layout/package.jsonpackages/layout/src/LayoutNodeContext.tsxpackages/layout/src/__tests__/layout-tree.test.tspackages/layout/src/__tests__/resolvePanelIdentity.test.tspackages/layout/src/index.tspackages/layout/src/layout-base-types.tspackages/layout/src/layout-slice-types.tspackages/layout/src/layout-tree.tspackages/layout/src/node-renderers/grid-node-renderer/GridLayout.tsxpackages/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/renderLayoutNode.tsxpackages/layout/src/resolvePanelIdentity.tspackages/layout/src/types.tspackages/mosaic/README.mdpackages/mosaic/__tests__/MosaicDashboardSlice.test.tspackages/mosaic/__tests__/mocks/style.jspackages/mosaic/jest.config.jspackages/mosaic/src/dashboard/MosaicDashboardPanels.tsxpackages/mosaic/src/dashboard/MosaicDashboardSlice.tspackages/mosaic/src/index.tspackages/room-shell/src/panels/RoomPanelHeader.tsx
💤 Files with no reviewable changes (1)
- apps/sqlrooms-cli-ui/src/artifactTypes.tsx
…id-layout-node-type-with-draggable-panels 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]>
…s using the bottom row 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.
Actionable comments posted: 3
🧹 Nitpick comments (3)
packages/layout/src/node-renderers/grid-node-renderer/useScrollNewGridChildIntoView.ts (1)
6-13: 💤 Low value
getGridItemElementallocates a full array to find a single element by known attribute value
querySelectorAll+Array.from+.find()collects every[data-layout-grid-item-id]descendant into memory before scanning. A direct attribute-value CSS selector reaches the target element in a single DOM pass with no intermediate allocations.♻️ Proposed refactor
function getGridItemElement( scrollContainer: HTMLElement, itemId: string, ): HTMLElement | undefined { - return Array.from( - scrollContainer.querySelectorAll<HTMLElement>('[data-layout-grid-item-id]'), - ).find((element) => element.dataset.layoutGridItemId === itemId); + return ( + scrollContainer.querySelector<HTMLElement>( + `[data-layout-grid-item-id="${CSS.escape(itemId)}"]`, + ) ?? undefined + ); }Note:
CSS.escape()is available in all modern browsers and guards against IDs containing characters that are special in CSS selectors.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/layout/src/node-renderers/grid-node-renderer/useScrollNewGridChildIntoView.ts` around lines 6 - 13, getGridItemElement currently builds an array of all [data-layout-grid-item-id] elements then .finds the match, which unnecessarily allocates memory; change it to query for the specific attribute value using a single DOM query (use scrollContainer.querySelector with an attribute-value selector) and protect the itemId by escaping it with CSS.escape() so you perform one pass and return the matching HTMLElement or undefined without creating an intermediate array.packages/layout-config/src/LayoutConfig.ts (1)
164-166: 💤 Low value
.strict()is legacy in Zod 4 — considerz.strictObject()for new schemas.
.strict()is still available for backwards compatibility and will not be removed, but the preferred Zod 4 approach for new schemas is the top-levelz.strictObject(). The pattern is consistent with the existingLayoutSplitNode/LayoutTabsNodeschemas, so this is only worth addressing if the codebase ever migrates those existing nodes too.♻️ Proposed refactor
-export const LayoutGridNode = BaseLayoutGridNode.extend({ - children: z.lazy(() => z.array(LayoutNode)), -}).strict(); +export const LayoutGridNode = z.strictObject({ + ...BaseLayoutGridNode.shape, + children: z.lazy(() => z.array(LayoutNode)), +});🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/layout-config/src/LayoutConfig.ts` around lines 164 - 166, Replace the legacy .strict() call on the LayoutGridNode schema by using the Zod top-level z.strictObject pattern to match the project's existing schemas (e.g., LayoutSplitNode/LayoutTabsNode); specifically update the LayoutGridNode definition that currently extends BaseLayoutGridNode and lazily includes LayoutNode children to be created with z.strictObject so the schema is created via z.strictObject({...}) rather than calling .strict() on the result.examples/layout/src/store.tsx (1)
71-73: RemovegetLayoutNodeKeyhelper and usegetLayoutNodeIdfrom@sqlrooms/layout.This local function duplicates the canonical
getLayoutNodeIdutility already exported by the@sqlrooms/layoutpackage (which the file already imports from). Replace the local definition with the standard helper to reduce duplication and improve consistency with the rest of the codebase.♻️ Proposed refactor
Add
getLayoutNodeIdto the import:import { findNodeById, isLayoutDockNode, LayoutGridNode, isLayoutGridNode, isLayoutSplitNode, isLayoutNodeKey, LayoutDockNode, + getLayoutNodeId, } from '@sqlrooms/layout';Remove the local helper (lines 71-73):
-function getLayoutNodeKey(node: LayoutNode): string { - return isLayoutNodeKey(node) ? node : node.id; -} - function getGridColsForBreakpoint(Replace the call in
createDashboardGridNode:- i: getLayoutNodeKey(child), + i: getLayoutNodeId(child),🤖 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 71 - 73, The file defines a redundant local helper getLayoutNodeKey that duplicates the canonical getLayoutNodeId from `@sqlrooms/layout`; remove the local getLayoutNodeKey, add getLayoutNodeId to the existing import list, and update calls (e.g., inside createDashboardGridNode) to use getLayoutNodeId instead of getLayoutNodeKey so the file uses the shared utility.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/sqlrooms-cli-ui/src/createDashboardAiTools.ts`:
- Line 56: Update the instruction string in DASHBOARD_AI_INSTRUCTIONS to reflect
the actual default layout type: change the text that says omitted values default
to "dock" to state they default to "grid" so the LLM will assume the correct
default when calling create_dashboard_artifact; this should match the Zod schema
default ('grid') and the store function createDashboardArtifact which sets
layoutType = 'grid'.
In
`@packages/layout/src/node-renderers/grid-node-renderer/useScrollNewGridChildIntoView.ts`:
- Around line 3-4: The module-level Maps previousGridChildIdsByNodeId and
pendingGridScrollItemByNodeId are never pruned on unmount; update the
useScrollNewGridChildIntoView hook to remove the current node's entries from
both Maps when the component unmounts: inside the effect that currently cancels
the animation frame (the cleanup associated with the hook), also call
previousGridChildIdsByNodeId.delete(nodeId) and
pendingGridScrollItemByNodeId.delete(nodeId) (using the same nodeId captured by
the hook) so entries are removed alongside cancelAnimationFrame, ensuring no
memory leak or test cross-talk.
- Around line 30-42: The two back-to-back calls in useScrollNewGridChildIntoView
(scrollContainer.scrollTo(...) and addedElement.scrollIntoView(...)) conflict
and cancel the precise scroll; remove the addedElement.scrollIntoView(...) call
and keep the computed scrollContainer.scrollTo(...) call so the exact offset is
applied (ensure you only call scrollTo with behavior:'smooth' and then return
true).
---
Nitpick comments:
In `@examples/layout/src/store.tsx`:
- Around line 71-73: The file defines a redundant local helper getLayoutNodeKey
that duplicates the canonical getLayoutNodeId from `@sqlrooms/layout`; remove the
local getLayoutNodeKey, add getLayoutNodeId to the existing import list, and
update calls (e.g., inside createDashboardGridNode) to use getLayoutNodeId
instead of getLayoutNodeKey so the file uses the shared utility.
In `@packages/layout-config/src/LayoutConfig.ts`:
- Around line 164-166: Replace the legacy .strict() call on the LayoutGridNode
schema by using the Zod top-level z.strictObject pattern to match the project's
existing schemas (e.g., LayoutSplitNode/LayoutTabsNode); specifically update the
LayoutGridNode definition that currently extends BaseLayoutGridNode and lazily
includes LayoutNode children to be created with z.strictObject so the schema is
created via z.strictObject({...}) rather than calling .strict() on the result.
In
`@packages/layout/src/node-renderers/grid-node-renderer/useScrollNewGridChildIntoView.ts`:
- Around line 6-13: getGridItemElement currently builds an array of all
[data-layout-grid-item-id] elements then .finds the match, which unnecessarily
allocates memory; change it to query for the specific attribute value using a
single DOM query (use scrollContainer.querySelector with an attribute-value
selector) and protect the itemId by escaping it with CSS.escape() so you perform
one pass and return the matching HTMLElement or undefined without creating an
intermediate array.
🪄 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: 3241b222-6c26-4582-b923-69040af80107
📒 Files selected for processing (13)
apps/sqlrooms-cli-ui/src/createDashboardAiTools.tsapps/sqlrooms-cli-ui/src/createDashboardCommands.tsexamples/layout/src/store.tsxknip.jsonpackages/layout-config/__tests__/LayoutConfig.test.tspackages/layout-config/src/LayoutConfig.tspackages/layout/src/LayoutRenderer.tsxpackages/layout/src/node-renderers/grid-node-renderer/GridLayout.tsxpackages/layout/src/node-renderers/grid-node-renderer/useScrollNewGridChildIntoView.tspackages/layout/src/node-renderers/leaf-node-renderer/LeafLayoutHeader.tsxpackages/mosaic/__tests__/MosaicDashboardSlice.test.tspackages/mosaic/src/dashboard/MosaicDashboardSlice.tspackages/preset-jest/node.js
✅ Files skipped from review due to trivial changes (5)
- knip.json
- packages/layout-config/tests/LayoutConfig.test.ts
- packages/mosaic/tests/MosaicDashboardSlice.test.ts
- packages/layout/src/node-renderers/grid-node-renderer/GridLayout.tsx
- packages/mosaic/src/dashboard/MosaicDashboardSlice.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/sqlrooms-cli-ui/src/createDashboardCommands.ts
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
…of github.com:sqlrooms/sqlrooms into codex/add-grid-layout-node-type-with-draggable-panels Signed-off-by: Ilya Boyandin <[email protected]>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/store.tsx`:
- Around line 51-69: getDashboardTitle currently returns generic "Grid layout"
or "Dock layout" for any dashboard with layoutType 'grid' or 'dock', which makes
user-created dashboards indistinguishable; update getDashboardTitle to return
the friendly names only for the seeded/known dashboardIds (e.g., 'growth-grid'
-> 'Grid layout', 'overview' -> 'Dock layout') and otherwise prefer an explicit
meta.title or fall back to the dashboardId (meta.title ?? `Dashboard
${meta.dashboardId}`) for user-created dashboards; locate and update
getDashboardTitle and any duplicate implementation (the other instance around
the later block) to check meta.dashboardId against the seeded IDs rather than
switching solely on meta.layoutType, and ensure addDashboard-created items keep
unique titles.
- Around line 83-125: createGridLayoutItem currently returns layout items
missing the required resizeHandles field; update the function to read
resizeHandles from the LayoutGridNode (e.g., const { resizeHandles } = node or
node.resizeHandles ?? default) and include that resizeHandles property on both
the early return item and the fallback return item (the objects with i, x, y, w,
h). Also ensure any seeded layouts.lg entries elsewhere in the file are updated
to include the same resizeHandles value extracted from the node so all
generated/seeded LayoutGridItem objects conform to the LayoutGridItem schema.
🪄 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: 229e8208-fc9b-4e18-92dc-15ebc6033943
📒 Files selected for processing (5)
apps/sqlrooms-cli-ui/src/createDashboardAiTools.tsexamples/layout/src/store.tsxpackages/layout-config/src/LayoutConfig.tspackages/layout/package.jsonpackages/layout/src/node-renderers/grid-node-renderer/useScrollNewGridChildIntoView.ts
✅ Files skipped from review due to trivial changes (1)
- packages/layout/package.json
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/sqlrooms-cli-ui/src/createDashboardAiTools.ts
- packages/layout-config/src/LayoutConfig.ts
- packages/layout/src/node-renderers/grid-node-renderer/useScrollNewGridChildIntoView.ts
| function createGridLayoutItem( | ||
| node: LayoutGridNode, | ||
| breakpoint: string, | ||
| chartId: string, | ||
| ) { | ||
| const layout = node.layouts?.[breakpoint] ?? []; | ||
| const cols = Math.max(1, getGridColsForBreakpoint(node, breakpoint)); | ||
| const w = Math.min(6, cols); | ||
| const h = 2; | ||
| const bottom = layout.reduce( | ||
| (maxY, item) => Math.max(maxY, item.y + item.h), | ||
| 0, | ||
| ); | ||
|
|
||
| for (let y = 0; y <= bottom; y += 1) { | ||
| for (let x = 0; x <= cols - w; x += 1) { | ||
| const overlaps = layout.some( | ||
| (item) => | ||
| x < item.x + item.w && | ||
| x + w > item.x && | ||
| y < item.y + item.h && | ||
| y + h > item.y, | ||
| ); | ||
| if (!overlaps) { | ||
| return { | ||
| i: chartId, | ||
| x, | ||
| y, | ||
| w, | ||
| h, | ||
| }; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| i: chartId, | ||
| x: 0, | ||
| y: bottom, | ||
| w, | ||
| h, | ||
| }; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== Generated grid items in examples/layout/src/store.tsx ==\n'
sed -n '83,168p' examples/layout/src/store.tsx
printf '\n== LayoutGridItem contract in packages/layout-config/src/LayoutConfig.ts ==\n'
sed -n '126,171p' packages/layout-config/src/LayoutConfig.tsRepository: sqlrooms/sqlrooms
Length of output: 3493
🏁 Script executed:
rg "LayoutGridResizeHandles" packages/layout-config/src/ -A 3Repository: sqlrooms/sqlrooms
Length of output: 896
🏁 Script executed:
# Check if node.resizeHandles is always populated in createGridLayoutItem calls
rg "createGridLayoutItem" examples/layout/src/ -B 3 -A 3Repository: sqlrooms/sqlrooms
Length of output: 878
Add resizeHandles to every generated grid item.
Generated grid items in createGridLayoutItem() and seeded layouts.lg entries omit the resizeHandles field required by the LayoutGridItem schema in packages/layout-config/src/LayoutConfig.ts. Extract the field from the node and propagate it to all returned items.
Proposed fix
function createGridLayoutItem(
node: LayoutGridNode,
breakpoint: string,
chartId: string,
) {
+ const resizeHandles = node.resizeHandles;
const layout = node.layouts?.[breakpoint] ?? [];
const cols = Math.max(1, getGridColsForBreakpoint(node, breakpoint));
const w = Math.min(6, cols);
const h = 2;
const bottom = layout.reduce(
@@
if (!overlaps) {
return {
i: chartId,
x,
y,
w,
h,
+ resizeHandles,
};
}
}
}
return {
i: chartId,
x: 0,
y: bottom,
w,
h,
+ resizeHandles,
};
}
function createDashboardGridNode(
dashboardId: string,
children: LayoutNode[],
): LayoutGridNode {
+ const resizeHandles = ['e', 's', 'se'] as const;
return {
type: 'grid',
id: dashboardId,
panel: {key: 'dashboard', meta: {dashboardId, layoutType: 'grid'}},
cols: 12,
rowHeight: 220,
margin: [12, 12],
compactType: 'vertical',
- resizeHandles: ['e', 's', 'se'],
+ resizeHandles,
children,
layouts: {
lg: children.map((child, index) => ({
i: getLayoutNodeId(child),
x: (index * 6) % 12,
y: Math.floor(index / 2) * 2,
w: 6,
h: 2,
+ resizeHandles,
})),
},
};
}🤖 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 83 - 125, createGridLayoutItem
currently returns layout items missing the required resizeHandles field; update
the function to read resizeHandles from the LayoutGridNode (e.g., const {
resizeHandles } = node or node.resizeHandles ?? default) and include that
resizeHandles property on both the early return item and the fallback return
item (the objects with i, x, y, w, h). Also ensure any seeded layouts.lg entries
elsewhere in the file are updated to include the same resizeHandles value
extracted from the node so all generated/seeded LayoutGridItem objects conform
to the LayoutGridItem schema.
There was a problem hiding this comment.
resizeHandles is intentionally optional on LayoutGridItem: LayoutConfig.ts defines LayoutGridResizeHandles as optional, and item-level resizeHandles is only for overrides. The grid-level setting is the source of truth: GridLayout.tsx resolves node.resizeHandles with defaults, then passes it to ResponsiveGridLayout at GridLayout.tsx.
Also, we explicitly normalize saved layouts to avoid storing redundant per-item handles: GridLayout.tsx only preserves item.resizeHandles when it differs from the grid-level handles. So adding resizeHandles to every generated item in store.tsx would be a step backward and would bloat persisted layout state.
The feedback is based on the premise that LayoutGridItem.resizeHandles is required, but in the current schema it isn’t.
There was a problem hiding this comment.
@ilyabo, you're absolutely right — I apologize for the incorrect review. The design is clear and intentional:
LayoutGridItem.resizeHandlesis optional and serves only as a per-item override.- The grid-level
node.resizeHandlesis the authoritative source, resolved inGridLayout.tsxand passed directly toResponsiveGridLayout. - The normalization logic deliberately strips redundant per-item handles to keep persisted state lean.
My suggestion was based on a false premise. The current implementation is correct as-is.
✏️ Learnings added
Learnt from: ilyabo
Repo: sqlrooms/sqlrooms PR: 575
File: examples/layout/src/store.tsx:83-125
Timestamp: 2026-05-04T14:34:29.038Z
Learning: In `packages/layout-config/src/LayoutConfig.ts`, `LayoutGridItem.resizeHandles` is intentionally optional. It is only used for per-item overrides. The grid-level `LayoutGridNode.resizeHandles` is the source of truth and is resolved/passed to `ResponsiveGridLayout` in `packages/layout/src/node-renderers/grid-node-renderer/GridLayout.tsx`. The normalization logic in `GridLayout.tsx` explicitly strips `item.resizeHandles` from saved layout state when it matches the grid-level setting, to avoid bloat. Do not flag missing `resizeHandles` on generated `LayoutGridItem` objects in `examples/layout/src/store.tsx`.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
Signed-off-by: Ilya Boyandin <[email protected]>
layout example:
--
CLI app:
Motivation
gridlayout node for dashboard-style, scrollable grid pages (Notion / BI dashboards) so dashboards can be created as eitherdockorgridlayouts without runtime conversion.react-grid-layoutas the implementation to enable draggable and resizable panels in a scrollable container while keeping the layout API terminology consistent with existing nodes.e) and bottom (s) edges to match the requested UX.Description
gridnode schema and types in@sqlrooms/layout-configincludingLayoutGridNode,LayoutGridItem,cols,layouts,breakpoints,compactType,margin,rowHeight,resizeHandles, and anisLayoutGridNodetype guard.GridLayoutrenderer (packages/layout/src/node-renderers/grid-node-renderer/GridLayout.tsx) that usesreact-grid-layout(responsive + width provider) and reads per-node configuration to render scrollable, draggable, resizable dashboard panels; CSS imports forreact-grid-layout/react-resizableare included.renderLayoutNode) and extendLayoutNodeContext,PanelContainerType, and public exports sogridnodes are first-class in the layout package.data-layout-drag-handle="true"to the panel drag handle component soreact-grid-layoutcan restrict dragging to header handles, and default resize handles are limited to['e','s'].react-grid-layout,react-resizable, and@types/react-grid-layouttopackages/layout/package.jsonand update the package README to document grid support.examples/layoutto include a newgrowth-griddashboard (helpercreateDashboardGridNode) that demonstrates thegridnode beside the existingdockdashboard.Testing
pnpm --filter @sqlrooms/layout buildin the workspace and the layout package build completed after adding typings forreact-grid-layout. (success)pnpm --filter @sqlrooms/layout testand all layout test suites passed (4test suites,37tests). (success)layoutexample withpnpm --filter layout-example build, which fails in this environment because broader workspace build artifacts / type availability for@sqlrooms/room-shellare not present; this failure is environment-specific and unrelated to the layout package changes. (environment-dependent failure)Codex Task
Summary by CodeRabbit