Skip to content

Workspace header above sidebar#134

Merged
urjitc merged 5 commits intomainfrom
workspace-header-above-sidebar
Jan 28, 2026
Merged

Workspace header above sidebar#134
urjitc merged 5 commits intomainfrom
workspace-header-above-sidebar

Conversation

@urjitc
Copy link
Member

@urjitc urjitc commented Jan 28, 2026

Important

This PR adds a workspace header above the sidebar, enabling workspace settings and sharing modals, and enhances dashboard layout with PDF uploads and resizable panels.

  • New Features:
    • Add WorkspaceHeader component to page.tsx for workspace settings and sharing modals.
    • Implement multi-file PDF upload in DashboardContent and WorkspaceSection.
    • Add resizable dashboard panels in DashboardLayout.
    • Introduce sidebar embedding mode in sidebar.tsx.
  • Improvements:
    • Show workspace header and Home/logo link on workspace routes in WorkspaceHeader.
    • Enhance layout modes and workspace context consistency in DashboardLayout.
    • Improve history tracking and modal wiring in DashboardContent.
  • Misc:
    • Refactor WorkspaceSidebar to conditionally render header based on route.
    • Update WorkspaceSection to handle PDF uploads and folder operations.
    • Adjust Sidebar component to support embedded mode.

This description was created by Ellipsis for 9638c98. You can customize this summary. It will automatically update as commits are pushed.


Summary by CodeRabbit

  • New Features

    • Workspace settings and sharing modals accessible from the workspace header
    • Multi-file PDF upload that auto-creates PDF cards
  • Improvements

    • Resizable, split and maximized dashboard layout for flexible workspace organization
    • Contextual workspace header and navigation behavior (logo/home link adjusted by route)
    • Sidebar adapts rendering based on workspace context
    • Improved history tracking and version-view toggling

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

- Remove ThinkEx logo and home icon from workspace sidebar
- Add ThinkEx logo to workspace header (workspace routes only)
- Logo scales on hover instead of background highlight
- Hide sidebar header completely on workspace routes
- Add embedded prop to Sidebar for layout-embedded positioning
- When embedded, sidebar uses absolute positioning within its container
- Sidebar wrapper becomes relative when embedded for proper positioning
- Enables sidebar to sit below headers in flex layouts
- Add workspaceHeader prop to DashboardLayout
- Move WorkspaceHeader rendering from WorkspaceSection to DashboardLayout
- Header now spans full width of sidebar + workspace canvas area
- Sidebar positioned below header using embedded mode
- Item panels start at top (full height) instead of below header
- Use nested ResizablePanelGroups: outer for chat, inner for workspace/panels
- Move workspace settings/share modals to dashboard/page.tsx
- Move PDF upload handler to dashboard/page.tsx
- Preserve all existing functionality (chat resizing, maximized mode, etc.)
@vercel
Copy link

vercel bot commented Jan 28, 2026

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

Project Deployment Review Updated (UTC)
thinkex Ready Ready Preview, Comment Jan 28, 2026 11:41pm

@coderabbitai
Copy link

coderabbitai bot commented Jan 28, 2026

Warning

Rate limit exceeded

@urjitc has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 39 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Workspace header, settings and share dialogs were moved into the dashboard; DashboardLayout was restructured to a resizable grid with a new workspaceHeader slot; Sidebar gained an embedded prop; workspace-route checks adjust header/sidebar rendering; PDF upload and history handlers were added to dashboard flow.

Changes

Cohort / File(s) Summary
Dashboard Page & Content
src/app/dashboard/page.tsx
Replaced per-field workspace props with `currentWorkspace: WorkspaceWithState
Layout Restructuring
src/components/layout/DashboardLayout.tsx
Added workspaceHeader?: React.ReactNode prop; replaced fixed sidebar/main layout with ResizablePanelGroup/ResizablePanel/ResizableHandle grid; introduced maximized (chat-only) and normal split rendering; header can span sidebar+workspace area.
Sidebar API change
src/components/ui/sidebar.tsx
Added public prop embedded?: boolean; when embedded is true the sidebar uses in-flow positioning (relative/absolute) and adjusted height/gap classes; non-embedded behavior unchanged.
Workspace Canvas UI
src/components/workspace-canvas/WorkspaceHeader.tsx, src/components/workspace-canvas/WorkspaceSidebar.tsx, src/components/workspace-canvas/WorkspaceSection.tsx
WorkspaceHeader: adds usePathname-based logo/link and conditional workspace-route rendering. WorkspaceSidebar: header rendering hidden on /workspace routes and home link adjusted. WorkspaceSection: removed header and modal state/JSX (moved to dashboard level).

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Header as WorkspaceHeader (UI)
  participant API as /api/upload-file
  participant Service as WorkspaceService
  participant Store as WorkspaceState/DB

  User->>Header: Select PDFs & trigger upload
  Header->>API: POST multipart/form-data (files)
  API-->>Header: 200 OK (uploaded file metadata/URLs)
  Header->>Service: map responses -> PDF card defs, create items
  Service->>Store: persist new PDF items to workspace
  Store-->>Header: updated workspace state
  Header-->>User: render new PDF cards
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement

Poem

🐰 I hopped through panels, nimble and spry,
Headers found new places beneath the sky,
PDFs tumbled in with a jubilant thump,
Panels stretch and shrink with a happy jump,
Hooray — the dashboard makes rabbits fly! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Workspace header above sidebar' directly and accurately describes the main change—introducing a WorkspaceHeader component that spans above the sidebar in the layout structure.

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


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

❤️ Share

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

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

Important

Looks good to me! 👍

Reviewed everything up to ae379fc in 31 seconds. Click for details.
  • Reviewed 784 lines of code in 6 files
  • Skipped 0 files when reviewing.
  • Skipped posting 0 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.

Workflow ID: wflow_b2Um2NztKsR3IlKa

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

import Link from "next/link";
import { Button } from "@/components/ui/button";
import { MoreVertical, LogOut, Layers, User, Mail, Play, Users, Globe, Plus, Upload, Tag, Home } from "lucide-react";
import { MoreVertical, LogOut, Layers, User, Mail, Play, Users, Globe, Plus, Upload, Tag } from "lucide-react";
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 28, 2026

Greptile Overview

Greptile Summary

Restructured the dashboard layout to position the workspace header above the sidebar, creating a more unified interface. The ThinkEx logo was moved from the sidebar header to the workspace header when on workspace routes.

Key Changes:

  • Added embedded prop to Sidebar component enabling absolute positioning within layout flow instead of fixed viewport positioning
  • Lifted WorkspaceHeader rendering from WorkspaceSection to DashboardLayout as a top-level element
  • Moved workspace settings and share modal state management to parent component for proper scope
  • Conditionally render sidebar header based on route (hidden on workspace routes)
  • Added ThinkEx logo to workspace header with link to home page

Architecture Impact:
The layout hierarchy changed from sidebar as a fixed viewport element to an embedded element that sits below the workspace header. This enables the header to span the full width of the interface including the sidebar area.

Confidence Score: 5/5

  • This PR is safe to merge with no identified issues
  • The refactoring is clean and well-structured. Component responsibilities are properly separated, props are correctly threaded through the hierarchy, and the new embedded prop is a sensible extension to the Sidebar API. No logic bugs, type errors, or security issues detected.
  • No files require special attention

Important Files Changed

Filename Overview
src/app/dashboard/page.tsx Lifted WorkspaceHeader and modal states to parent, added PDF upload handler - clean refactoring
src/components/layout/DashboardLayout.tsx Restructured layout to place header above sidebar+workspace area with new embedded sidebar prop
src/components/ui/sidebar.tsx Added embedded prop to render sidebar with absolute positioning instead of fixed

Sequence Diagram

sequenceDiagram
    participant Page as DashboardPage
    participant Layout as DashboardLayout
    participant Header as WorkspaceHeader
    participant Sidebar as Sidebar (embedded)
    participant WorkspaceSidebar as WorkspaceSidebar
    participant Section as WorkspaceSection
    
    Page->>Layout: Pass workspaceHeader prop
    Note over Page,Layout: Header now passed as prop<br/>with modal handlers
    
    Layout->>Layout: Render header at top level
    Note over Layout: Header spans full width<br/>above sidebar + workspace
    
    Layout->>Sidebar: embedded=true
    Note over Sidebar: Renders with absolute<br/>positioning (not fixed)
    
    Sidebar->>WorkspaceSidebar: Conditionally hide header
    Note over WorkspaceSidebar: Logo moved to WorkspaceHeader<br/>when on workspace route
    
    Layout->>Section: Render workspace content
    Note over Section: No longer renders header<br/>delegated to parent
    
    Header->>Page: onOpenSettings/onOpenShare
    Note over Header,Page: Modal state lifted to parent<br/>for proper scope
Loading

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 6 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/components/layout/DashboardLayout.tsx">

<violation number="1" location="src/components/layout/DashboardLayout.tsx:208">
P2: When two item panels are open, the workspace-area panel still uses order=1 (even though size=0) and the first item panel also gets order=1. Duplicate order values in a ResizablePanelGroup can break panel sizing/resizing. Offset item panel order when panels.length>=2 (or conditionally remove the workspace panel) so each panel has a unique order.</violation>
</file>

<file name="src/app/dashboard/page.tsx">

<violation number="1" location="src/app/dashboard/page.tsx:343">
P3: The regex used to strip the `.pdf` suffix is incorrect, so normal filenames like `report.pdf` will not have their extension removed. Use `/\.pdf$/i` to match a literal dot before `pdf`.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/app/dashboard/page.tsx`:
- Line 343: The regex used to strip the PDF extension in the object property
(the expression using file.name.replace in page.tsx) is written as /\\.pdf$/i
which matches a literal backslash + ".pdf"; change it to /\.pdf$/i (or
equivalent RegExp) so it correctly matches and removes a trailing ".pdf"
case-insensitively from file.name.

In `@src/components/workspace-canvas/WorkspaceSidebar.tsx`:
- Around line 180-203: The SidebarHeader rendering currently uses the logo Link
to "/" which causes inconsistent navigation on the /home route; use the existing
isHomeRoute variable to adjust behavior in the conditional that renders
SidebarHeader (or inside the Link) so that when isHomeRoute is true the logo
either doesn't render or its href becomes "/home" instead of "/", e.g., update
the Link href logic in WorkspaceSidebar (the block rendering SidebarHeader and
the Link component) to choose between "/" and "/home" based on isHomeRoute or
skip rendering the logo when isHomeRoute is true.
🧹 Nitpick comments (2)
src/app/dashboard/page.tsx (1)

318-365: Consider handling partial upload failures gracefully.

Promise.all will reject entirely if any single upload fails. For a better UX when uploading multiple PDFs, consider using Promise.allSettled to allow successful uploads to proceed even if some fail.

♻️ Optional: Handle partial failures
-      const uploadResults = await Promise.all(uploadPromises);
+      const settledResults = await Promise.allSettled(uploadPromises);
+      const uploadResults = settledResults
+        .filter((r): r is PromiseFulfilledResult<typeof uploadPromises[0] extends Promise<infer T> ? T : never> => r.status === 'fulfilled')
+        .map(r => r.value);
+      
+      const failedCount = settledResults.filter(r => r.status === 'rejected').length;
+      if (failedCount > 0 && uploadResults.length > 0) {
+        // Some succeeded, some failed - still create cards for successful uploads
+        console.warn(`${failedCount} PDF upload(s) failed`);
+      } else if (uploadResults.length === 0) {
+        throw new Error('All PDF uploads failed');
+      }
src/components/layout/DashboardLayout.tsx (1)

196-216: Panel key extraction could use improvement for stability.

The pattern React.isValidElement(panel) ? panel.key : \panel-${index}`` falls back to index-based keys when panel elements don't have explicit keys. This could cause unnecessary remounts if panels are reordered.

Consider ensuring panels always have stable keys at the source (in DashboardContent's panels memo) rather than extracting/generating keys here. The current implementation in page.tsx Line 278 uses item.id as the key, which is correct, so this should work fine in practice.

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

Important

Looks good to me! 👍

Reviewed 9712eef in 34 seconds. Click for details.
  • Reviewed 183 lines of code in 2 files
  • Skipped 0 files when reviewing.
  • Skipped posting 0 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.

Workflow ID: wflow_uDbftTbg30lN7Qvy

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
10.6% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

Important

Looks good to me! 👍

Reviewed 9638c98 in 28 seconds. Click for details.
  • Reviewed 13 lines of code in 1 files
  • Skipped 0 files when reviewing.
  • Skipped posting 0 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.

Workflow ID: wflow_chA1Icld2zmIknl4

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

@urjitc urjitc merged commit 1835c52 into main Jan 28, 2026
5 of 7 checks passed
@urjitc urjitc deleted the workspace-header-above-sidebar branch January 28, 2026 23:40
@github-project-automation github-project-automation bot moved this from Backlog to Done in Dev Board Jan 28, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/components/layout/DashboardLayout.tsx`:
- Around line 119-158: The workspace size callback is using the outer left-area
size so it ignores inner splits when item panels open; update the onLayout
handler (handlePanelLayout) to read the size of the inner workspace panel (id
"workspace-area-panel") instead of the outer left-area-panel and pass that value
to onWorkspaceSizeChange; specifically, locate handlePanelLayout and change its
logic to find the panel entry for "workspace-area-panel" (or derive its computed
percentage) and call onWorkspaceSizeChange with that inner-panel size so column
calculations reflect the actual workspace canvas width when item panels are
present.
🧹 Nitpick comments (1)
src/app/dashboard/page.tsx (1)

313-358: Consider partial-success handling for multi-PDF uploads.
Promise.all drops all successful uploads if a single file fails. Using allSettled lets you create items for successful uploads while still surfacing failures.

🔧 Suggested fix (allow partial success)
-      const uploadResults = await Promise.all(uploadPromises);
-      const pdfCardDefinitions = uploadResults.map((result) => {
+      const uploadResults = await Promise.allSettled(uploadPromises);
+      const successfulUploads = uploadResults.flatMap((r) =>
+        r.status === "fulfilled" ? [r.value] : []
+      );
+
+      if (successfulUploads.length === 0) {
+        throw new Error("Failed to upload PDFs");
+      }
+
+      const pdfCardDefinitions = successfulUploads.map((result) => {
         const pdfData: Partial<PdfData> = {
           fileUrl: result.fileUrl,
           filename: result.filename,
           fileSize: result.fileSize,
         };
@@
-      operations.createItems(pdfCardDefinitions);
+      operations.createItems(pdfCardDefinitions);

Comment on lines +119 to +158
<ResizablePanelGroup
key={`layout-${effectiveChatExpanded ? "chat" : "no-chat"}`}
id={`layout-${effectiveChatExpanded ? "chat" : "no-chat"}`}
direction="horizontal"
className="flex-1 z-10"
onLayout={handlePanelLayout}
>
{/* Left Area: Split between workspace area (with header) and item panels */}
<ResizablePanel
id="left-area-panel"
order={1}
defaultSize={(() => {
if (!effectiveChatExpanded) return 100;
return 100 - PANEL_DEFAULTS.CHAT;
})()}
minSize={effectiveChatExpanded ? PANEL_DEFAULTS.WORKSPACE_MIN : 100}
>
<ResizablePanelGroup
key={`layout-${effectiveChatExpanded ? 'chat' : 'no-chat'}`}
id={`layout-${effectiveChatExpanded ? 'chat' : 'no-chat'}`}
id={`left-area-split-${panels.length}`}
// Force re-mount when panel count changes to ensure defaultSize is respected
// and to prevent "Panel data not found" errors from react-resizable-panels
key={`left-area-split-${panels.length}`}
direction="horizontal"
className="flex-1"
onLayout={handlePanelLayout}
className="flex-1 h-full"
>
{/* Workspace Panel - hidden when 2 item panels are open (split view) */}
{panels.length < 2 && (
<ResizablePanel
id="workspace-panel"
order={1}
defaultSize={(() => {
if (panels.length === 0) {
return effectiveChatExpanded ? PANEL_DEFAULTS.WORKSPACE_WITH_CHAT : 100;
}
// 1 panel + workspace = split based on ratio (workspace gets the smaller share)
const availableSpace = effectiveChatExpanded ? (100 - PANEL_DEFAULTS.CHAT_MIN) : 100;
return availableSpace * (1 - PANEL_DEFAULTS.ITEM_PANEL_SPLIT_RATIO);
})()}
minSize={panels.length > 0 ? 20 : (effectiveChatExpanded ? PANEL_DEFAULTS.WORKSPACE_MIN : 100)}
>
<WorkspaceCanvasDropzone>
{workspaceSection}
</WorkspaceCanvasDropzone>
</ResizablePanel>
)}
{/* Workspace area: header + sidebar + workspace canvas */}
<ResizablePanel
id="workspace-area-panel"
order={0}
defaultSize={(() => {
if (panels.length === 0) {
return 100;
}
if (panels.length >= 2) {
return 0; // Hide workspace when 2 panels are open
}
return 100 * (1 - PANEL_DEFAULTS.ITEM_PANEL_SPLIT_RATIO);
})()}
minSize={panels.length >= 2 ? 0 : (panels.length > 0 ? 20 : 100)}
>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Workspace size tracking may drift when item panels open.
With the new inner split, onWorkspaceSizeChange still tracks the outer left-area size, so column calculations can ignore the workspace-area shrink when item panels appear. Consider updating the callback to use the inner panel’s workspace size.

🔧 Suggested fix (track inner workspace panel size)
@@
-  const handlePanelLayout = useCallback((sizes: number[]) => {
-    // Track workspace size for column calculations
-    const workspaceSize = sizes[0];
-    onWorkspaceSizeChange?.(workspaceSize);
+  const leftAreaSizeRef = useRef(100);
+
+  const handlePanelLayout = useCallback((sizes: number[]) => {
+    // Track left-area size for column calculations
+    const leftAreaSize = sizes[0];
+    leftAreaSizeRef.current = leftAreaSize;
+    // If there are no item panels, left area == workspace area
+    if (panels.length === 0) {
+      onWorkspaceSizeChange?.(leftAreaSize);
+    }
@@
-  }, [onWorkspaceSizeChange]);
+  }, [onWorkspaceSizeChange, panels.length]);
+
+  const handleWorkspaceLayout = useCallback((sizes: number[]) => {
+    const workspacePctOfLeft = sizes[0];
+    const workspacePctOfTotal = (leftAreaSizeRef.current * workspacePctOfLeft) / 100;
+    onWorkspaceSizeChange?.(workspacePctOfTotal);
+  }, [onWorkspaceSizeChange]);
@@
-            <ResizablePanelGroup
+            <ResizablePanelGroup
               id={`left-area-split-${panels.length}`}
               // Force re-mount when panel count changes to ensure defaultSize is respected
               // and to prevent "Panel data not found" errors from react-resizable-panels
               key={`left-area-split-${panels.length}`}
               direction="horizontal"
               className="flex-1 h-full"
+              onLayout={handleWorkspaceLayout}
             >
🤖 Prompt for AI Agents
In `@src/components/layout/DashboardLayout.tsx` around lines 119 - 158, The
workspace size callback is using the outer left-area size so it ignores inner
splits when item panels open; update the onLayout handler (handlePanelLayout) to
read the size of the inner workspace panel (id "workspace-area-panel") instead
of the outer left-area-panel and pass that value to onWorkspaceSizeChange;
specifically, locate handlePanelLayout and change its logic to find the panel
entry for "workspace-area-panel" (or derive its computed percentage) and call
onWorkspaceSizeChange with that inner-panel size so column calculations reflect
the actual workspace canvas width when item panels are present.

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

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant