diff --git a/ui/app/(prowler)/lighthouse/page.test.ts b/ui/app/(prowler)/lighthouse/page.test.ts
index e7260e2506..8283b20551 100644
--- a/ui/app/(prowler)/lighthouse/page.test.ts
+++ b/ui/app/(prowler)/lighthouse/page.test.ts
@@ -16,4 +16,13 @@ describe("Lighthouse page", () => {
);
expect(source).toContain("key={chatRouteKey}");
});
+
+ it("renders the Cloud chat inside the shared application layout", () => {
+ // Given / When / Then
+ expect(source).toContain(
+ '}>',
+ );
+ expect(source).toContain('className="h-[calc(100dvh-6.5rem)] min-h-0"');
+ expect(source).not.toContain('className="h-dvh min-h-0 p-4 pr-6"');
+ });
});
diff --git a/ui/app/(prowler)/lighthouse/page.tsx b/ui/app/(prowler)/lighthouse/page.tsx
index 396b9c243f..a5b8e4d05a 100644
--- a/ui/app/(prowler)/lighthouse/page.tsx
+++ b/ui/app/(prowler)/lighthouse/page.tsx
@@ -42,7 +42,7 @@ export default async function AIChatbot({
);
if (connectedConfigurations.length === 0) {
- return redirect("/lighthouse/config");
+ return redirect("/lighthouse/settings");
}
const modelsEntries = await Promise.all(
@@ -69,26 +69,28 @@ export default async function AIChatbot({
const chatRouteKey = activeSessionId ?? initialPrompt ?? "new";
return (
-
+ }>
-
-
+
+
+
+
);
}
const hasConfig = await isLighthouseConfigured();
if (!hasConfig) {
- return redirect("/lighthouse/config");
+ return redirect("/lighthouse/settings");
}
// Fetch provider configuration with default models
@@ -96,7 +98,7 @@ export default async function AIChatbot({
// Handle errors or missing configuration
if (providersConfig.errors || !providersConfig.providers) {
- return redirect("/lighthouse/config");
+ return redirect("/lighthouse/settings");
}
return (
diff --git a/ui/components/ai-elements/conversation.tsx b/ui/components/ai-elements/conversation.tsx
index 44916f06d1..ec96807893 100644
--- a/ui/components/ai-elements/conversation.tsx
+++ b/ui/components/ai-elements/conversation.tsx
@@ -19,19 +19,39 @@ export const Conversation = ({ className, ...props }: ConversationProps) => (
/>
);
-export type ConversationContentProps = ComponentProps<
- typeof StickToBottom.Content
->;
+type ConversationContentChildren =
+ | ReactNode
+ | ((context: ReturnType) => ReactNode);
+
+export type ConversationContentProps = Omit<
+ ComponentProps<"div">,
+ "children" | "ref"
+> & {
+ children?: ConversationContentChildren;
+ scrollClassName?: string;
+};
export const ConversationContent = ({
+ children,
className,
+ scrollClassName,
...props
-}: ConversationContentProps) => (
-
-);
+}: ConversationContentProps) => {
+ const context = useStickToBottomContext();
+ const { contentRef, scrollRef } = context;
+
+ return (
+
+
+ {typeof children === "function" ? children(context) : children}
+
+
+ );
+};
export type ConversationEmptyStateProps = ComponentProps<"div"> & {
title?: string;
diff --git a/ui/components/lighthouse-v2/chat/lighthouse-v2-chat-page.test.tsx b/ui/components/lighthouse-v2/chat/lighthouse-v2-chat-page.test.tsx
index f0e2d76bd3..9318ce7305 100644
--- a/ui/components/lighthouse-v2/chat/lighthouse-v2-chat-page.test.tsx
+++ b/ui/components/lighthouse-v2/chat/lighthouse-v2-chat-page.test.tsx
@@ -4,6 +4,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type {
LighthouseV2Configuration,
+ LighthouseV2Message,
LighthouseV2SupportedModel,
} from "@/types/lighthouse-v2";
@@ -109,15 +110,6 @@ describe("LighthouseV2ChatPage", () => {
vi.unstubAllGlobals();
});
- it("uses the neutral page background instead of the global app background token", () => {
- // Given / When
- const { container } = renderPage();
-
- // Then
- expect(container.firstElementChild).toHaveClass("bg-bg-neutral-primary");
- expect(container.firstElementChild).not.toHaveClass("bg-background");
- });
-
it("does not render provider or model selectors in the chat composer", () => {
// Given / When
renderPage();
@@ -126,7 +118,46 @@ describe("LighthouseV2ChatPage", () => {
expect(screen.queryByRole("combobox")).not.toBeInTheDocument();
expect(
screen.getByRole("link", { name: "Lighthouse settings" }),
- ).toHaveAttribute("href", "/lighthouse/config");
+ ).toHaveAttribute("href", "/lighthouse/settings");
+ });
+
+ it("uses the tuned scrollbar and bottom fade without a composer separator", () => {
+ // Given / When
+ const { container } = renderPage({
+ initialMessages: [message("message-1", "assistant", "Existing answer")],
+ });
+
+ // Then
+ const conversation = screen.getByRole("log");
+ const scrollViewport = conversation.firstElementChild as HTMLElement;
+ const content = scrollViewport.firstElementChild as HTMLElement;
+ const scrollFade = container.querySelector(
+ '[data-slot="lighthouse-v2-chat-scroll-fade"]',
+ );
+
+ expect(conversation).toHaveClass("h-full", "min-h-0");
+ expect(conversation.parentElement).toHaveClass("flex", "overflow-hidden");
+ expect(scrollViewport).toHaveClass(
+ "minimal-scrollbar",
+ "overflow-x-hidden",
+ "overflow-y-auto",
+ );
+ expect(content).toHaveClass("pb-20");
+ expect(scrollFade).toHaveClass(
+ "pointer-events-none",
+ "absolute",
+ "bottom-0",
+ "right-2",
+ "h-16",
+ "bg-gradient-to-t",
+ "from-bg-neutral-secondary",
+ "to-transparent",
+ );
+ expect(
+ container.querySelector(
+ '[data-slot="lighthouse-v2-chat-composer-panel"]',
+ ),
+ ).not.toHaveClass("border-t");
});
it("sends messages with the connected default provider and model from configuration", async () => {
@@ -215,3 +246,27 @@ function model(id: string): LighthouseV2SupportedModel {
supportsReasoning: null,
};
}
+
+function message(
+ id: string,
+ role: LighthouseV2Message["role"],
+ content: string,
+): LighthouseV2Message {
+ return {
+ id,
+ role,
+ model: null,
+ tokenUsage: null,
+ insertedAt: "2026-06-25T10:00:00Z",
+ parts: [
+ {
+ id: `${id}-part`,
+ type: "text",
+ content,
+ toolCallOutcome: null,
+ insertedAt: "2026-06-25T10:00:00Z",
+ updatedAt: "2026-06-25T10:00:00Z",
+ },
+ ],
+ };
+}
diff --git a/ui/components/lighthouse-v2/chat/lighthouse-v2-chat-page.tsx b/ui/components/lighthouse-v2/chat/lighthouse-v2-chat-page.tsx
index dca6f11f3b..f3677bb5c1 100644
--- a/ui/components/lighthouse-v2/chat/lighthouse-v2-chat-page.tsx
+++ b/ui/components/lighthouse-v2/chat/lighthouse-v2-chat-page.tsx
@@ -50,6 +50,8 @@ import {
type LighthouseV2SupportedModel,
} from "@/types/lighthouse-v2";
+import { Card } from "../../shadcn";
+
interface LighthouseV2ChatPageProps {
configurations: LighthouseV2Configuration[];
modelsByProvider: Record<
@@ -295,21 +297,36 @@ export function LighthouseV2ChatPage({
messages.length > 0 || Boolean(streamState.assistantText);
return (
-
+
{hasConversation ? (
-
-
- {messages.map((message) => (
-
- ))}
- {streamState.assistantText && (
-
- )}
-
-
-
-
+
+
+
+ {messages.map((message) => (
+
+ ))}
+ {streamState.assistantText && (
+
+ )}
+
+
+
+
+
+
@@ -407,7 +424,7 @@ export function LighthouseV2ChatPage({
)}
-
+
);
}