Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
cacheComponents: true,
reactCompiler: true,
serverExternalPackages: ["pg", "@neondatabase/serverless", "postgres"],
allowedDevOrigins: [
Expand Down
12 changes: 8 additions & 4 deletions src/app/login/page.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
// @vitest-environment jsdom
import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import LoginPage from "./page";
vi.mock("next/server", () => ({
connection: async () => {},
}))

import { LoginContent } from "./page";

describe("LoginPage", () => {
const originalDashboardOrigin = process.env.DASHBOARD_ORIGIN;
Expand Down Expand Up @@ -30,7 +34,7 @@ describe("LoginPage", () => {
});

it("links directly to Dashboard login with the Notebook callback URL", async () => {
render(await LoginPage());
render(await LoginContent());

const link = screen.getByRole("link", { name: "Sign in" });

Expand All @@ -41,7 +45,7 @@ describe("LoginPage", () => {
});

it("uses account language instead of implementation details", async () => {
const { container } = render(await LoginPage());
const { container } = render(await LoginContent());

expect(screen.getByRole("link", { name: "Sign in" })).toBeTruthy();
expect(screen.getByText("Use your Knowhere account to continue.")).toBeTruthy();
Expand Down
17 changes: 12 additions & 5 deletions src/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import { Suspense } from "react"
import Link from "next/link";
import { NotebookLogoMark } from "@/components/notebook-logo-mark";
import { headers } from "next/headers";
import { Card, CardContent } from "@/components/ui/card";
import { authURLs } from "@/infrastructure/auth/urls";
import { connection } from "next/server";

/**
* Login gate preview for the MVP shell. The real auth redirect is handled by
* server-side guards; this page keeps direct `/login` visits user-friendly.
*/
export default async function LoginPage() {
export default function LoginPage() {
return (
<Suspense>
<LoginContent />
</Suspense>
)
}

export async function LoginContent() {
await connection()
const notebookPublicURL =
process.env.NOTEBOOK_PUBLIC_URL ??
authURLs.resolveNotebookPublicURLFromHeaders(await headers());
Expand Down
8 changes: 6 additions & 2 deletions src/app/page.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import React from "react"
import { describe, expect, it, vi } from "vitest"

vi.mock("next/server", () => ({
connection: async () => {},
}))

const mocks = vi.hoisted(() => ({
loadWorkspaceShellInitialState: vi.fn(),
}))
Expand All @@ -9,7 +13,7 @@ vi.mock("@/domains/workspace/initial-state", () => ({
loadWorkspaceShellInitialState: mocks.loadWorkspaceShellInitialState,
}))

import Home from "./page"
import { HomeContent } from "./page"

describe("Home", () => {
it("renders the workspace shell from the API-backed initial state", async () => {
Expand All @@ -20,7 +24,7 @@ describe("Home", () => {
chatMessages: [],
})

const element = await Home()
const element = await HomeContent()

expect(React.isValidElement(element)).toBe(true)
expect(mocks.loadWorkspaceShellInitialState).toHaveBeenCalledOnce()
Expand Down
13 changes: 11 additions & 2 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { Suspense } from "react"
import { WorkspaceShell } from "@/components/workspace-shell"
import { loadWorkspaceShellInitialState } from "@/domains/workspace/initial-state"
import { connection } from "next/server"

export const dynamic = "force-dynamic"
export default function Home() {
return (
<Suspense>
<HomeContent />
</Suspense>
)
}

export default async function Home() {
export async function HomeContent() {
await connection()
return <WorkspaceShell {...(await loadWorkspaceShellInitialState())} />
}
9 changes: 8 additions & 1 deletion src/components/workspace-selected-chunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ export function useWorkspaceSelectedChunks({
keepPreviousData: false,
},
)
const resolvedPrefetchedChunks = useMemo(
() =>
prefetchedSelectedChunks
? resolveChunkConnectionTargets(prefetchedSelectedChunks)
: undefined,
[prefetchedSelectedChunks],
)
const pagedSelectedChunks = useMemo(
() =>
resolveChunkConnectionTargets(
Expand All @@ -67,7 +74,7 @@ export function useWorkspaceSelectedChunks({
[selectedChunkPages],
)
const selectedChunks = selectedSourceId
? (prefetchedSelectedChunks ?? pagedSelectedChunks)
? (resolvedPrefetchedChunks ?? pagedSelectedChunks)
: []
const hasMoreSelectedChunks =
!prefetchedSelectedChunks &&
Expand Down
2 changes: 1 addition & 1 deletion src/domains/demo/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function toChatMessages(catalog: DemoCatalog): ChatMessageView[] {
? { description: citation.description }
: {}),
source: {
documentId: citation.source.documentId,
documentId: citation.canonicalDocumentId,
sourceFileName: citation.source.sourceFileName,
sectionPath: citation.source.sectionPath,
},
Expand Down
61 changes: 28 additions & 33 deletions src/domains/workspace/initial-state.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import "server-only"

import { unstable_cache } from "next/cache"
import { Effect } from "effect"

import type { ChatMessageView } from "@/domains/chat/types"
Expand Down Expand Up @@ -50,37 +49,33 @@ type WorkspaceShellInitialState = {
}
}

const getCachedDemoChunksForSource = (demoSourceId: string) =>
unstable_cache(
async (): Promise<ParsedChunkView[]> => {
const chunkPage = await knowhereDemoApi.fetchChunkPage({
demoSourceId,
page: 1,
pageSize: 100,
})
const sourceView = demoView.toSourceView({
demoSourceId: chunkPage.demoSourceId,
canonicalDocumentId: chunkPage.canonicalDocumentId,
title: chunkPage.title,
mimeType: chunkPage.mimeType,
sizeBytes: 0,
status: "ready",
chunkCount: chunkPage.pagination.total,
originalFile: {
url: "",
mimeType: "",
sizeBytes: 0,
canDownload: false,
},
examples: [],
})
return chunkPage.chunks.map((chunk) =>
demoView.toParsedChunkView(sourceView, chunk),
)
},
["demo-chunks", demoSourceId],
{ revalidate: false },
)()
// Aligned with workspaceClientConfig.sourceChunkPageSize so the SSR
// prefetch doesn't overlap with the first client-side page request.
const DEMO_CHUNK_PREFETCH_PAGE_SIZE = 50

async function getDemoChunksForSource(
demoSourceId: string,
): Promise<ParsedChunkView[]> {
const chunkPage = await knowhereDemoApi.fetchChunkPage({
demoSourceId,
page: 1,
pageSize: DEMO_CHUNK_PREFETCH_PAGE_SIZE,
})
// Only title and documentId are consumed by toParsedChunkView,
// so a minimal SourceView is sufficient.
const sourceView: SourceView = {
id: chunkPage.demoSourceId,
kind: "demo",
demoSourceId: chunkPage.demoSourceId,
title: chunkPage.title,
mimeType: chunkPage.mimeType,
status: "ready",
documentId: chunkPage.canonicalDocumentId,
}
return chunkPage.chunks.map((chunk) =>
demoView.toParsedChunkView(sourceView, chunk),
)
}

type WorkspaceShellInitialStateClient =
Parameters<typeof getSourceViewOptionsBySourceId>[1]
Expand Down Expand Up @@ -161,7 +156,7 @@ export const loadWorkspaceShellInitialStateEffect = (
if (firstDemoSource) {
const chunks = yield* Effect.catchAll(
Effect.tryPromise(() =>
getCachedDemoChunksForSource(firstDemoSource.demoSourceId),
getDemoChunksForSource(firstDemoSource.demoSourceId),
),
() => Effect.succeed([] as ParsedChunkView[]),
)
Expand Down
5 changes: 5 additions & 0 deletions src/infrastructure/auth/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"

vi.mock("next/cache", () => ({
cacheLife: () => {},
cacheTag: () => {},
}))

/**
* Tests for the auth module.
*
Expand Down
Loading
Loading