feat(ui): add Lighthouse side panel#11872
Conversation
|
✅ No Conflicts No conflict markers, and the branch merges cleanly into its base. |
📝 WalkthroughWalkthroughThis PR adds a shared Lighthouse chat store used by full-page and panel chat surfaces, introduces a global side panel with detail portals and routing/layout wiring, and updates finding titles, the floating mute button, and table toolbar layout. ChangesLighthouse chat store and side panel
Estimated code review effort: 5 (Critical) | ~120 minutes Findings and table UI polish
Estimated code review effort: 2 (Simple) | ~10 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant SidePanelTrigger
participant SidePanelStore as useSidePanelStore
participant GlobalSidePanel
participant DetailSidePanel
participant LighthousePanelChat
User->>SidePanelTrigger: click
SidePanelTrigger->>SidePanelStore: openPanel(AI_CHAT)
SidePanelStore-->>GlobalSidePanel: selectedTab and isOpen
GlobalSidePanel->>LighthousePanelChat: mount lazy tab content
DetailSidePanel->>SidePanelStore: registerContextTab("Details")
GlobalSidePanel-->>DetailSidePanel: context outlet available
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
🔒 Container Security ScanImage: ✅ No Vulnerabilities DetectedThe container image passed all security checks. No known CVEs were found.📋 Resources:
|
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
ui/app/(prowler)/lighthouse/_components/panel/index.ts (1)
1-5: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueVerify
resetPanelChatConfigCacheForTestsre-export is actually consumed via this barrel.The corresponding test (
lighthouse-panel-chat.test.tsx) importsresetPanelChatConfigCacheForTestsdirectly from./lighthouse-panel-chat, not from this index. If nothing else imports the test helper via this public barrel, re-exporting it here needlessly widens the module's public surface with a test-only utility.#!/bin/bash rg -n "resetPanelChatConfigCacheForTests" ui --type=ts --type=tsx🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ui/app/`(prowler)/lighthouse/_components/panel/index.ts around lines 1 - 5, The barrel in LighthousePanel’s index is re-exporting resetPanelChatConfigCacheForTests even though the only known usage imports it directly from LighthousePanelChat, so this unnecessarily expands the public API with a test-only helper. Check whether any production or external consumer imports the helper through this index, and if not, remove the resetPanelChatConfigCacheForTests export from the index barrel while keeping LighthousePanelChat exported.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@ui/app/`(prowler)/lighthouse/_components/panel/lighthouse-panel-chat.tsx:
- Around line 149-174: The panel chat currently listens for
LIGHTHOUSE_V2_SESSION_ARCHIVED_EVENT but does not refresh the session list, so
an archived chat can remain visible in Recent chats until another refresh
occurs. Update the useMountEffect setup in lighthouse-panel-chat.tsx so that
archiving also triggers refreshSessions, either by calling it inside
handleSessionArchived in addition to
store.getState().handleSessionArchived(archivedId) or by dispatching/handling
LIGHTHOUSE_V2_SESSIONS_CHANGED_EVENT from the archive flow. Keep the existing
refresh and listener wiring aligned with handleArchiveSession and the session
archive handling logic.
- Around line 81-134: The READY cache in LighthousePanelChat is not invalidated
when provider config changes, so reopen can reuse stale config/models or a stale
modelsError. Update the config mutation flows to emit a config-changed/session
event or explicitly clear cachedReadyState and refresh the panel state after
add/update/delete operations. Use LighthousePanelChat, cachedReadyState,
resetPanelChatConfigCacheForTests, and loadPanelChatState to keep the cached
READY state in sync.
In `@ui/app/`(prowler)/lighthouse/_lib/chat-store.ts:
- Around line 313-339: The sendLighthouseV2Message response handling in the
chat-store needs two fixes: add a staleness guard before any set() calls so the
block only updates state when sessionId still matches get().activeSessionId, and
reconcile optimistic messages on all non-409 errors. In the post-send
success/error path, use the existing sendMessage/refreshMessages flow around the
sendLighthouseV2Message call to avoid stale updates, and make sure non-409
failures also trigger a refresh or equivalent rollback so the optimistic bubble
does not remain in messages. Keep the existing blockedByConflict behavior for
409, but extend the error handling so every failure path leaves streamState,
feedback, and messages consistent with the active session.
In `@ui/app/`(prowler)/lighthouse/_lib/panel-chat-store.ts:
- Around line 17-29: `getOrCreatePanelChatStore` currently returns the existing
`panelChatStore` even when the incoming `config` changes, so the panel can keep
stale provider/model settings. Update the singleton logic in
`panel-chat-store.ts` to detect when `LighthouseChatConfig` differs from the
cached store config and either recreate the store or add a refresh path that
reapplies the new config. Use the `getOrCreatePanelChatStore` and
`createLighthouseChatStore` flow to ensure later `LighthousePanelChat` renders
always use the latest settings.
In `@ui/components/side-panel/detail-drawer-tab-strip.tsx`:
- Around line 19-23: The lazy-loaded Ask AI chat in LazyLighthousePanelChat is
only wrapped in Suspense, so chunk-load failures can bubble up and take down the
whole drawer/page. Add a local error boundary around the LazyLighthousePanelChat
rendering in DetailDrawerTabStrip (where the Suspense wrapper is used) and
provide a tab-scoped fallback so import() rejections are contained to the Ask AI
tab instead of relying on a higher-level route boundary.
---
Outside diff comments:
In `@ui/app/`(prowler)/lighthouse/_components/panel/index.ts:
- Around line 1-5: The barrel in LighthousePanel’s index is re-exporting
resetPanelChatConfigCacheForTests even though the only known usage imports it
directly from LighthousePanelChat, so this unnecessarily expands the public API
with a test-only helper. Check whether any production or external consumer
imports the helper through this index, and if not, remove the
resetPanelChatConfigCacheForTests export from the index barrel while keeping
LighthousePanelChat exported.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 0758af4a-1817-4e2f-837d-fbb0c06ff79c
📒 Files selected for processing (24)
ui/app/(prowler)/layout.tsxui/app/(prowler)/lighthouse/_components/chat/empty-state.tsxui/app/(prowler)/lighthouse/_components/chat/lighthouse-chat-store-provider.tsxui/app/(prowler)/lighthouse/_components/chat/lighthouse-v2-chat-page.test.tsxui/app/(prowler)/lighthouse/_components/chat/lighthouse-v2-chat-page.tsxui/app/(prowler)/lighthouse/_components/chat/lighthouse-v2-chat-view.tsxui/app/(prowler)/lighthouse/_components/panel/index.tsui/app/(prowler)/lighthouse/_components/panel/lighthouse-panel-chat.test.tsxui/app/(prowler)/lighthouse/_components/panel/lighthouse-panel-chat.tsxui/app/(prowler)/lighthouse/_lib/chat-store.test.tsui/app/(prowler)/lighthouse/_lib/chat-store.tsui/app/(prowler)/lighthouse/_lib/panel-chat-store.tsui/app/(prowler)/lighthouse/_lib/testing/event-source-mock.tsui/components/side-panel/detail-drawer-tab-strip.tsxui/components/side-panel/global-side-panel.test.tsxui/components/side-panel/global-side-panel.tsxui/components/side-panel/index.tsui/components/side-panel/side-panel-tabs.tsxui/components/side-panel/side-panel-trigger.tsxui/components/ui/nav-bar/navbar-client.test.tsxui/components/ui/nav-bar/navbar-client.tsxui/lib/ui-layout.tsui/store/__tests__/side-panel.test.tsui/store/side-panel.ts
Redefine Tailwind's sm..2xl variants as @container queries so content squeezed by the global side panel adopts its tablet/mobile forms without the viewport changing. The <body> is the fallback query container for chrome and portaled overlays. Portal the findings floating mute button to body so layout containment on <main> no longer captures its fixed position.
Introduce a single non-modal push side panel available from any view: a navbar trigger opens Lighthouse AI (cloud-only), and finding/resource detail views register a Details context tab and portal their content into the same panel, so the AI and the detail share one surface (PostHog-style). The panel is user-resizable, pushes the page instead of covering it, and is mutually exclusive with the full-page chat route. Chat state lives in a store factory whose EventSource survives panel close and navigation.
Cap finding group and standalone titles at a min width, truncate the overflow with an ellipsis and surface the full name in a tooltip, so rows stay single-line when the side panel narrows the table.
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@ui/app/`(prowler)/lighthouse/_components/chat/empty-state.tsx:
- Around line 65-70: Refactor the heading and subheading className logic in the
empty-state component to use cn() with shared base styles plus the compact-only
size variant, instead of duplicating nearly identical full class strings in each
branch. Update the h1 and the subheading in empty-state.tsx so the base text
color/weight/italic classes stay shared and only the size class changes based on
compact, using the existing cn() helper.
In `@ui/components/findings/table/column-finding-groups.test.tsx`:
- Around line 411-415: The assertion in the table test is too loose and can miss
a broken rendering path because “Fallback IaC Check” is expected twice due to
the inline TooltipContent mock. Update the expectation in
column-finding-groups.test.tsx to use the same getAllByText result from the
current test and assert an exact length of 2 so both the title and tooltip copy
are verified.
In `@ui/components/shadcn/side-panel/side-panel.tsx`:
- Around line 71-105: The SidePanelResizeHandle component only supports pointer
interactions, so keyboard users cannot resize the panel. Update
SidePanelResizeHandle to follow the splitter/separator pattern by making the
separator focusable, exposing the current size via ARIA value attributes, and
handling arrow-key resizing in the same resizing flow used by
handlePointerDown/handlePointerMove/endResize. Keep the existing pointer
behavior intact while adding keyboard support and state updates through
onResize, onResizeStart, and onResizeEnd.
In `@ui/components/side-panel/global-side-panel.tsx`:
- Around line 163-170: The `SidePanelBody` in `GlobalSidePanel` is using an
inline callback ref that changes on every render and causes `contextOutlet` to
be reset, remounting the portaled content and losing state/scroll position.
Update the `ref` on `SidePanelBody` to use the stable `setContextOutlet` action
from `useSidePanelStore` directly instead of wrapping it in a new callback each
render.
In `@ui/components/ui/main-layout/main-layout.tsx`:
- Around line 19-28: The three separate subscriptions in main-layout’s
useSidePanelStore usage should be consolidated into one selector. Update
main-layout so sidePanelOpen, sidePanelWidth, and sidePanelResizing are read
from a single useStore(useSidePanelStore, ...) call instead of three, keeping
the same values but reducing extra mount/update render passes.
- Around line 25-36: The push margin in MainLayout can use a persisted
sidePanelWidth that is no longer valid for the current viewport, causing the
main content to collapse on smaller screens. Update the pushWidth calculation in
main-layout.tsx to reuse clampSidePanelWidth(...) before assigning the width,
while keeping the existing sidePanelOpen, isPushViewport, and pathname checks
intact.
In `@ui/lib/ui-layout.ts`:
- Around line 16-25: clampSidePanelWidth currently assumes viewportCap will
always stay above SIDE_PANEL_MIN_WIDTH, which only holds because of the current
media-query/constant coupling. Update the logic in clampSidePanelWidth to
explicitly enforce the minimum before applying the upper cap, so the returned
width can never fall below SIDE_PANEL_MIN_WIDTH even if window.innerWidth or the
related constants change later.
In `@ui/styles/globals.css`:
- Around line 17-21: The Tailwind v4 `@custom-variant` rules in `globals.css`
are being flagged as invalid by Biome and Stylelint even though they are valid
CSS-first syntax. Update the lint configuration or file-level allowlist to treat
`@custom-variant` and the container-query range syntax as accepted, matching how
`@theme` and `@import "tailwindcss"` are already handled. Use the existing CSS
lint setup and any relevant ignore/allow patterns to prevent false-positive
parse/unknown-at-rule errors.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 27dbf1ba-9543-435d-92b5-fd8ad3b3cafb
📒 Files selected for processing (29)
ui/app/(auth)/layout.tsxui/app/(prowler)/layout.tsxui/app/(prowler)/lighthouse/_components/chat/empty-state.tsxui/app/(prowler)/lighthouse/_components/chat/lighthouse-v2-chat-view.tsxui/app/(prowler)/lighthouse/_components/navigation/lighthouse-v2-navigation-mode-sync.tsxui/app/(prowler)/lighthouse/_components/panel/lighthouse-panel-chat-skeleton.tsxui/app/(prowler)/lighthouse/_components/panel/lighthouse-panel-chat.tsxui/components/findings/floating-mute-button.tsxui/components/findings/table/column-finding-groups.test.tsxui/components/findings/table/column-finding-groups.tsxui/components/findings/table/column-standalone-findings.tsxui/components/findings/table/resource-detail-drawer/resource-detail-drawer.tsxui/components/resources/resource-details-sheet.tsxui/components/shadcn/side-panel/side-panel.tsxui/components/side-panel/detail-side-panel.test.tsxui/components/side-panel/detail-side-panel.tsxui/components/side-panel/global-side-panel.test.tsxui/components/side-panel/global-side-panel.tsxui/components/side-panel/index.tsui/components/side-panel/side-panel-tabs.tsxui/components/side-panel/side-panel-trigger.tsxui/components/ui/main-layout/main-layout.tsxui/components/ui/table/data-table.test.tsxui/components/ui/table/data-table.tsxui/hooks/use-media-query.tsui/lib/ui-layout.tsui/store/__tests__/side-panel.test.tsui/store/side-panel.tsui/styles/globals.css
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@ui/components/side-panel/detail-side-panel.tsx`:
- Around line 43-50: The `useMountEffect` in `detail-side-panel.tsx` stores an
`onRequestClose` callback that can capture a stale `onOpenChange` from the first
render. Update the `DetailSidePanel` registration flow so the stored close
handler always invokes the latest `onOpenChange` (for example via a ref), and
verify the callers in `resource-detail-drawer` and `resource-details-sheet` to
confirm whether they pass stable setters or inline callbacks before deciding if
the fix is required.
In `@ui/components/side-panel/global-side-panel.tsx`:
- Around line 186-197: Add a stable key to SidePanelErrorBoundary so it remounts
when the active tab changes; in global-side-panel.tsx, update the render around
hasBeenOpened and activeRegistryTab to pass key={activeRegistryTab.id} to
SidePanelErrorBoundary, ensuring a previous hasError state does not persist
across tab switches and block the new tab’s SidePanelErrorBoundary/Suspense
content from rendering.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 83ffcabb-8a71-48cc-9c92-9f4607b3ac6e
📒 Files selected for processing (26)
ui/app/(prowler)/lighthouse/_components/chat/empty-state.tsxui/app/(prowler)/lighthouse/_components/chat/lighthouse-v2-chat-page.tsxui/app/(prowler)/lighthouse/_components/chat/lighthouse-v2-chat-view.tsxui/app/(prowler)/lighthouse/_components/config/configuration-form.tsxui/app/(prowler)/lighthouse/_components/panel/lighthouse-panel-chat-skeleton.tsxui/app/(prowler)/lighthouse/_components/panel/lighthouse-panel-chat.test.tsxui/app/(prowler)/lighthouse/_components/panel/lighthouse-panel-chat.tsxui/app/(prowler)/lighthouse/_lib/chat-store.test.tsui/app/(prowler)/lighthouse/_lib/chat-store.tsui/app/(prowler)/lighthouse/_lib/load-chat-config.tsui/app/(prowler)/lighthouse/_lib/panel-chat-store.tsui/app/(prowler)/lighthouse/_lib/session-events.tsui/app/(prowler)/lighthouse/page.tsxui/components/shadcn/side-panel/side-panel.tsxui/components/side-panel/detail-side-panel.test.tsxui/components/side-panel/detail-side-panel.tsxui/components/side-panel/global-side-panel.test.tsxui/components/side-panel/global-side-panel.tsxui/components/side-panel/side-panel-error-boundary.tsxui/components/side-panel/side-panel-trigger.tsxui/components/ui/main-layout/main-layout.tsxui/components/ui/table/data-table.tsxui/lib/lighthouse-routes.tsui/lib/ui-layout.tsui/store/__tests__/side-panel.test.tsui/store/side-panel.ts
💤 Files with no reviewable changes (1)
- ui/components/ui/table/data-table.tsx
Context
This PR is the second iteration of the Lighthouse AI experience. It makes the existing chat available from the global right side panel and unifies finding and resource details on the same side-panel shell.
The shared Details side panel is available in both OSS and Cloud. The Lighthouse AI launcher and tab remain Cloud-only.
No additional changelog entry is included for the side-panel iteration.
Description
Tabs,TabsList,TabsTrigger, andTabsContentpresentation for switching between Details and Lighthouse AI. No panel-specific tabs variant is introduced.React.lazyinstance after a rejected chunk import.classNameoverrides at call sites.masterchanges, including Overview prompt prefill without automatic submission and the shared sidebar navigation-mode synchronizer.Steps to review
cd ui && pnpm run healthcheck.cd ui && pnpm exec vitest run.Checklist
Community Checklist
UI
License
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.