Skip to content

Feat/1957 ai left panel#1970

Open
DSanich wants to merge 5 commits intomainfrom
feat/1957-ai-left-panel
Open

Feat/1957 ai left panel#1970
DSanich wants to merge 5 commits intomainfrom
feat/1957-ai-left-panel

Conversation

@DSanich
Copy link
Member

@DSanich DSanich commented Feb 26, 2026

Summary by CodeRabbit

  • New Features

    • Added a resizable left panel with drag-to-resize on desktop and slide-out drawer on mobile
    • Integrated the left-panel layout into the main app shell for consistent placement of content and footer
    • Added a UI drawer component and a viewport-aware hook to improve mobile behavior and controls
  • Style

    • Updated footer styling for improved spacing and visual consistency

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 26, 2026

📝 Walkthrough

Walkthrough

Refactors the app layout to integrate a new responsive AiLeftPanelLayout and AiLeftPanel components (desktop resizable panel and mobile drawer), adds a useIsMobile hook, introduces a Drawer component wrapping vaul primitives, and updates exports and minor footer styling.

Changes

Cohort / File(s) Summary
App Layout
apps/web/src/app/layout.tsx
Replaced previous layout with a flex full-height structure and wrapped content in AiLeftPanelLayout; moved Footer inside the new layout and adjusted MenuTop/ConnectedButtonProfile placement.
Left Panel UI
packages/epics/src/common/ai-left-panel-layout.tsx, packages/epics/src/common/ai-left-panel.tsx, packages/epics/src/common/index.ts
Added AiLeftPanelLayout (desktop resizable panel 240–600px with drag handle; mobile drawer) and AiLeftPanel (panel UI with close button); re-exported both in common index.
Mobile Hook
packages/epics/src/hooks/use-is-mobile.tsx, packages/epics/src/hooks/index.ts
New useIsMobile hook (breakpoint 768px) with media query listener; re-exported from hooks index.
Drawer Primitives
packages/ui/src/drawer.tsx, packages/ui/src/index.ts, packages/ui/package.json
Added Drawer components built on vaul (overlay, content, header/footer/title/description, trigger/close, portal) and added vaul dependency; exported via UI index.
Styling
packages/ui/src/organisms/footer.tsx
Added w-full to footer container (presentational layout change).

Sequence Diagram

sequenceDiagram
    participant User
    participant Layout as AiLeftPanelLayout
    participant Panel as AiLeftPanel
    participant Desktop
    participant Mobile

    User->>Layout: Render / Interact (open/close/drag)
    Layout->>Layout: useIsMobile() check
    alt Desktop (isMobile = false)
        Layout->>Desktop: Render left panel with resizer
        User->>Desktop: Drag resizer
        Desktop->>Layout: Update panelWidth
        Layout->>Desktop: Re-render with new width
    else Mobile (isMobile = true)
        Layout->>Mobile: Render Drawer (panelOpen state)
        User->>Mobile: Tap trigger / close
        Mobile->>Panel: Open / Close
        Panel->>Layout: onClose -> update panelOpen
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • evgenibir
  • sergey3bv
  • plitzenberger

Poem

🐰 I dug a panel, left and bright,
Drag it wide by day, tuck it in by night,
A hook to sense the screen, a drawer to show,
Flex boxes hum, the layout flows—hooray, let's go! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 clearly and specifically describes the main feature being added: an AI left panel component with related layout and hook functionality.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/1957-ai-left-panel

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.

@DSanich DSanich force-pushed the feat/1957-ai-left-panel branch from 24d0c31 to d60f45c Compare February 26, 2026 09:04
Copy link
Contributor

@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: 3

🧹 Nitpick comments (2)
packages/epics/src/common/ai-left-panel-layout.tsx (1)

69-73: Resizable separator should support keyboard interaction.

This control is currently pointer-only. Consider adding tabIndex, aria-valuemin, aria-valuemax, and Arrow key resizing to keep it operable for keyboard users.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/epics/src/common/ai-left-panel-layout.tsx` around lines 69 - 73, The
separator is pointer-only; add keyboard support by making it focusable (add
tabIndex=0), expose range limits (aria-valuemin and aria-valuemax) alongside the
existing aria-valuenow={panelWidth}, and implement an onKeyDown handler (e.g.,
onResizeKeyDown) that listens for ArrowLeft/ArrowRight (and optionally
Home/End/PageUp/PageDown) to increment/decrement panelWidth within the min/max
bounds; wire that handler into the same element and reuse the existing resize
logic or call the same state-updating function used by onResizeMouseDown so
keyboard changes mirror pointer behavior.
apps/web/src/app/layout.tsx (1)

138-141: Consider removing the unnecessary Fragment wrapper.

The Fragment (<>...</>) around the content and Footer is not required since AiLeftPanelLayout accepts ReactNode children directly. You can simplify by removing it.

♻️ Suggested simplification
                 <AiLeftPanelLayout>
-                  <>
-                    <div className="w-full shrink-0 pb-8">{children}</div>
-                    <Footer />
-                  </>
+                  <div className="w-full shrink-0 pb-8">{children}</div>
+                  <Footer />
                 </AiLeftPanelLayout>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/layout.tsx` around lines 138 - 141, Remove the unnecessary
fragment around the children and Footer by using a single parent element
instead: eliminate the <>...</> wrapper and either move Footer inside the
existing div with className "w-full shrink-0 pb-8" or replace the fragment with
one parent container so AiLeftPanelLayout receives one ReactNode (keep
references to children and Footer intact).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/epics/src/common/ai-left-panel-layout.tsx`:
- Around line 34-50: The drag listeners can remain active if a mouseup is
missed; update the drag teardown in the drag-start logic so mouseup is
registered with { once: true } and add a window 'blur' handler to perform the
same cleanup as onMouseUp; specifically, keep your existing onMouseMove and
onMouseUp functions (referenced by dragStartX, dragStartWidth, setPanelWidth,
setIsDragging) but register window.addEventListener('mouseup', onMouseUp, {
once: true }) and also add a window.addEventListener('blur', onMouseUp) so the
blur event will call the same cleanup removing the mousemove listener and
resetting isDragging; ensure the same onMouseMove and onMouseUp references are
used when removing listeners.

In `@packages/epics/src/common/ai-left-panel.tsx`:
- Line 3: Add "lucide-react" as a direct dependency of the packages/epics
package.json because ai-left-panel.tsx imports PanelLeftClose from
'lucide-react'; update the dependencies section in packages/epics/package.json
to include the same lucide-react version used by the workspace (or a compatible
semver), then run install/build to verify the import resolves without relying on
transitive root workspace deps.

In `@packages/ui/package.json`:
- Around line 19-21: package.json was updated to add the "vaul" dependency but
the lockfile was not updated causing CI mismatch; run pnpm install in the
repository root to regenerate/synchronize pnpm-lock.yaml (or the project's
lockfile), verify the lockfile changes include the "vaul" entry, and commit the
updated lockfile alongside the package.json update so CI can pass.

---

Nitpick comments:
In `@apps/web/src/app/layout.tsx`:
- Around line 138-141: Remove the unnecessary fragment around the children and
Footer by using a single parent element instead: eliminate the <>...</> wrapper
and either move Footer inside the existing div with className "w-full shrink-0
pb-8" or replace the fragment with one parent container so AiLeftPanelLayout
receives one ReactNode (keep references to children and Footer intact).

In `@packages/epics/src/common/ai-left-panel-layout.tsx`:
- Around line 69-73: The separator is pointer-only; add keyboard support by
making it focusable (add tabIndex=0), expose range limits (aria-valuemin and
aria-valuemax) alongside the existing aria-valuenow={panelWidth}, and implement
an onKeyDown handler (e.g., onResizeKeyDown) that listens for
ArrowLeft/ArrowRight (and optionally Home/End/PageUp/PageDown) to
increment/decrement panelWidth within the min/max bounds; wire that handler into
the same element and reuse the existing resize logic or call the same
state-updating function used by onResizeMouseDown so keyboard changes mirror
pointer behavior.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a432be and 24d0c31.

📒 Files selected for processing (10)
  • apps/web/src/app/layout.tsx
  • packages/epics/src/common/ai-left-panel-layout.tsx
  • packages/epics/src/common/ai-left-panel.tsx
  • packages/epics/src/common/index.ts
  • packages/epics/src/hooks/index.ts
  • packages/epics/src/hooks/use-is-mobile.tsx
  • packages/ui/package.json
  • packages/ui/src/drawer.tsx
  • packages/ui/src/index.ts
  • packages/ui/src/organisms/footer.tsx

Comment on lines +34 to +50
const onMouseMove = (e: MouseEvent) => {
const delta = e.clientX - dragStartX.current;
const newWidth = Math.min(
MAX_WIDTH,
Math.max(MIN_WIDTH, dragStartWidth.current + delta),
);
setPanelWidth(newWidth);
};

const onMouseUp = () => {
setIsDragging(false);
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', onMouseUp);
};

window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', onMouseUp);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

FILE="packages/epics/src/common/ai-left-panel-layout.tsx"
wc -l "$FILE"

Repository: hypha-dao/hypha-web

Length of output: 117


🏁 Script executed:

FILE="packages/epics/src/common/ai-left-panel-layout.tsx"
sed -n '1,80p' "$FILE"

Repository: hypha-dao/hypha-web

Length of output: 2739


🏁 Script executed:

FILE="packages/epics/src/common/ai-left-panel-layout.tsx"
rg -n "addEventListener|removeEventListener|useEffect|useRef|isDragging" "$FILE" -A 2 -B 1

Repository: hypha-dao/hypha-web

Length of output: 1298


Drag listener cleanup lacks robustness for missed mouseup events.

If the user releases the mouse outside the window or the tab loses focus mid-drag, mouseup may not fire on that window. The listeners and isDragging state would persist, and subsequent interactions could start in an inconsistent state. Protect against this by using { once: true } on one-shot listeners and adding a blur event handler as a secondary cleanup trigger.

💡 Hardening listener teardown
       const onMouseUp = () => {
         setIsDragging(false);
         window.removeEventListener('mousemove', onMouseMove);
         window.removeEventListener('mouseup', onMouseUp);
+        window.removeEventListener('blur', onMouseUp);
       };

       window.addEventListener('mousemove', onMouseMove);
-      window.addEventListener('mouseup', onMouseUp);
+      window.addEventListener('mouseup', onMouseUp, { once: true });
+      window.addEventListener('blur', onMouseUp, { once: true });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/epics/src/common/ai-left-panel-layout.tsx` around lines 34 - 50, The
drag listeners can remain active if a mouseup is missed; update the drag
teardown in the drag-start logic so mouseup is registered with { once: true }
and add a window 'blur' handler to perform the same cleanup as onMouseUp;
specifically, keep your existing onMouseMove and onMouseUp functions (referenced
by dragStartX, dragStartWidth, setPanelWidth, setIsDragging) but register
window.addEventListener('mouseup', onMouseUp, { once: true }) and also add a
window.addEventListener('blur', onMouseUp) so the blur event will call the same
cleanup removing the mousemove listener and resetting isDragging; ensure the
same onMouseMove and onMouseUp references are used when removing listeners.

@@ -0,0 +1,34 @@
'use client';

import { PanelLeftClose } from 'lucide-react';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd package.json | while read -r manifest; do
  echo "== $manifest =="
  jq -r '"name=\(.name // "unknown") | dep=\(.dependencies["lucide-react"] // "null") | devDep=\(.devDependencies["lucide-react"] // "null") | peerDep=\(.peerDependencies["lucide-react"] // "null")"' "$manifest"
done

Repository: hypha-dao/hypha-web

Length of output: 2299


Add lucide-react to packages/epics dependencies.

This file directly imports from lucide-react, but the package does not explicitly declare it as a dependency. Currently relying on the transitive dependency from the root workspace. Each package should declare its own dependencies to avoid strict-install/build breaks and maintain proper package boundaries.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/epics/src/common/ai-left-panel.tsx` at line 3, Add "lucide-react" as
a direct dependency of the packages/epics package.json because ai-left-panel.tsx
imports PanelLeftClose from 'lucide-react'; update the dependencies section in
packages/epics/package.json to include the same lucide-react version used by the
workspace (or a compatible semver), then run install/build to verify the import
resolves without relying on transitive root workspace deps.

Copy link
Contributor

@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.

🧹 Nitpick comments (2)
packages/ui/src/drawer.tsx (1)

45-49: Default bottom-drawer styles may need override for alternative directions.

The default classes (inset-x-0 bottom-0 mt-24 rounded-t-[10px]) assume a bottom-positioned drawer. When using direction="left" (as in AiLeftPanelLayout), consumers must override these via className and style props. This works but requires careful attention.

Consider documenting this behavior or providing variant props for different directions if left/right drawers become common.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/drawer.tsx` around lines 45 - 49, The Drawer component
currently hardcodes bottom-oriented Tailwind classes in the className
concatenation (e.g., 'inset-x-0 bottom-0 mt-24 rounded-t-[10px]') which forces
consumers to override styles for left/right drawers; update Drawer to accept a
direction prop (or variants) and map directions to appropriate class sets (e.g.,
bottom vs left/right positioning and rounded corners) instead of always using
bottom classes, and update the className merge logic in the Drawer component so
consumer-provided className still overrides defaults when needed; reference the
Drawer component and its className concatenation logic to implement this change
and add a brief note in the component docs about the direction prop behavior.
apps/web/src/app/layout.tsx (1)

137-142: Consider removing the unnecessary fragment wrapper.

The AiLeftPanelLayout accepts ReactNode as children, so you can pass the content directly without the extra fragment.

♻️ Suggested simplification
                   <AiLeftPanelLayout>
-                    <>
-                      <div className="w-full shrink-0 pb-8">{children}</div>
-                      <Footer />
-                    </>
+                    <div className="w-full shrink-0 pb-8">{children}</div>
+                    <Footer />
                   </AiLeftPanelLayout>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/layout.tsx` around lines 137 - 142, Remove the unnecessary
fragment around the layout content: inside AiLeftPanelLayout replace the
<>...</> wrapper by a single container element that holds the page content and
Footer (e.g., wrap the children and <Footer /> in one div), so update the JSX
around AiLeftPanelLayout to render a single parent node containing {children}
and Footer instead of using the fragment shorthand.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/web/src/app/layout.tsx`:
- Around line 137-142: Remove the unnecessary fragment around the layout
content: inside AiLeftPanelLayout replace the <>...</> wrapper by a single
container element that holds the page content and Footer (e.g., wrap the
children and <Footer /> in one div), so update the JSX around AiLeftPanelLayout
to render a single parent node containing {children} and Footer instead of using
the fragment shorthand.

In `@packages/ui/src/drawer.tsx`:
- Around line 45-49: The Drawer component currently hardcodes bottom-oriented
Tailwind classes in the className concatenation (e.g., 'inset-x-0 bottom-0 mt-24
rounded-t-[10px]') which forces consumers to override styles for left/right
drawers; update Drawer to accept a direction prop (or variants) and map
directions to appropriate class sets (e.g., bottom vs left/right positioning and
rounded corners) instead of always using bottom classes, and update the
className merge logic in the Drawer component so consumer-provided className
still overrides defaults when needed; reference the Drawer component and its
className concatenation logic to implement this change and add a brief note in
the component docs about the direction prop behavior.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 24d0c31 and d60f45c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • apps/web/src/app/layout.tsx
  • packages/epics/src/common/ai-left-panel-layout.tsx
  • packages/epics/src/common/ai-left-panel.tsx
  • packages/epics/src/common/index.ts
  • packages/epics/src/hooks/index.ts
  • packages/epics/src/hooks/use-is-mobile.tsx
  • packages/ui/package.json
  • packages/ui/src/drawer.tsx
  • packages/ui/src/index.ts
  • packages/ui/src/organisms/footer.tsx
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/ui/src/index.ts
  • packages/epics/src/common/ai-left-panel-layout.tsx
  • packages/ui/package.json
  • packages/epics/src/common/index.ts
  • packages/epics/src/hooks/use-is-mobile.tsx
  • packages/epics/src/common/ai-left-panel.tsx

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant