Skip to content
Closed
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
2 changes: 1 addition & 1 deletion src/app/auth/[path]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default async function AuthPage({
const { path } = await params;
const { redirect_url } = await searchParams;

// Default to /dashboard if no redirect_url is provided
// Default to /onboarding if no redirect_url is provided
// Otherwise use the redirect_url from query params (e.g., from share links)
const redirectTo = redirect_url || "/onboarding";

Expand Down
18 changes: 15 additions & 3 deletions src/app/dashboard/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
/**
* Dynamic route for workspace slugs: /dashboard/[slug]
* This imports and renders the same dashboard content as /dashboard
* Renders the dashboard shell for an active workspace.
*/
import DashboardPage from "../page";
import { SEO } from "@/components/seo/SEO";
import { DashboardShell } from "../page";

export default function WorkspacePage() {
return <DashboardPage />;
return (
<>
<SEO
title="Dashboard"
description="Manage your workspaces, create new projects, and organize knowledge effortlessly in your ThinkEx dashboard."
keywords="dashboard, workspace management, AI workspace, productivity tools"
url="https://thinkex.app/dashboard"

@cubic-dev-ai cubic-dev-ai Bot Jan 17, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: The dynamic workspace page now emits a fixed canonical/URL for "/dashboard" instead of including the current slug. This makes every workspace page share the same canonical metadata, which is incorrect for per-workspace routes. Pass the slug from params and build the URL/canonical with it.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/app/dashboard/[slug]/page.tsx, line 15:

<comment>The dynamic workspace page now emits a fixed canonical/URL for "/dashboard" instead of including the current slug. This makes every workspace page share the same canonical metadata, which is incorrect for per-workspace routes. Pass the slug from params and build the URL/canonical with it.</comment>

<file context>
@@ -1,10 +1,22 @@
+        title="Dashboard"
+        description="Manage your workspaces, create new projects, and organize knowledge effortlessly in your ThinkEx dashboard."
+        keywords="dashboard, workspace management, AI workspace, productivity tools"
+        url="https://thinkex.app/dashboard"
+        canonical="https://thinkex.app/dashboard"
+      />
</file context>
Fix with Cubic

canonical="https://thinkex.app/dashboard"
/>
Comment on lines +11 to +17

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

SEO metadata has hardcoded URLs that don't reflect the dynamic slug.

The url and canonical props point to /dashboard rather than the actual workspace URL /dashboard/[slug]. This could negatively impact SEO for individual workspaces.

🐛 Suggested fix for dynamic SEO

Consider making the page a server component with dynamic metadata, or pass the slug to generate the correct URL:

 export default function WorkspacePage({ params }: { params: Promise<{ slug: string }> }) {
+  const { slug } = await params;
   return (
     <>
       <SEO
         title="Dashboard"
         description="Manage your workspaces..."
         keywords="dashboard, workspace management..."
-        url="https://thinkex.app/dashboard"
-        canonical="https://thinkex.app/dashboard"
+        url={`https://thinkex.app/dashboard/${slug}`}
+        canonical={`https://thinkex.app/dashboard/${slug}`}
       />
       <DashboardShell />
     </>
   );
 }
🤖 Prompt for AI Agents
In `@src/app/dashboard/`[slug]/page.tsx around lines 11 - 17, The SEO component
currently hardcodes url and canonical to "/dashboard"; update the page to supply
the dynamic slug so URLs point to /dashboard/[slug]. Either make this a server
component and read the slug from the route params passed into the default page
component (use params.slug) and pass the computed url/canonical to the SEO
component, or implement/export an async generateMetadata({params}) that returns
metadata using params.slug and ensure the SEO component receives those values;
update references to the SEO component in page.tsx to use the constructed
workspaceUrl (e.g., `https://thinkex.app/dashboard/${slug}`) instead of the
static "/dashboard".

<DashboardShell />
</>
);
}

26 changes: 14 additions & 12 deletions src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Loader2 } from "lucide-react";
import { useRouter } from "next/navigation";
import { usePostHog } from 'posthog-js/react';
import { SEO } from "@/components/seo/SEO";
import type { AgentState } from "@/lib/workspace-state/types";
import { initialState } from "@/lib/workspace-state/state";
import { useScrollHeader } from "@/hooks/ui/use-scroll-header";
Expand Down Expand Up @@ -364,7 +363,7 @@ function DashboardContent({

// Main page component
// Main page component
function DashboardPage() {
export function DashboardPage() {
const router = useRouter();
// Get workspace context
const {
Expand Down Expand Up @@ -431,7 +430,7 @@ function DashboardPage() {
}

// Wrapper for sidebar provider
function SidebarCoordinator({ children }: { children: React.ReactNode }) {
export function SidebarCoordinator({ children }: { children: React.ReactNode }) {
return (
<SidebarProvider defaultOpen={true}>
{children}
Expand All @@ -440,7 +439,7 @@ function SidebarCoordinator({ children }: { children: React.ReactNode }) {
}

// Component to handle anonymous users - redirect to guest-setup if no session
function AnonymousSessionHandler({ children }: { children: React.ReactNode }) {
export function AnonymousSessionHandler({ children }: { children: React.ReactNode }) {
const { data: session, isPending } = useSession();
const router = useRouter();

Expand Down Expand Up @@ -483,16 +482,9 @@ function AnonymousSessionHandler({ children }: { children: React.ReactNode }) {
return <>{children}</>;
}

export default function Page() {
export function DashboardShell() {
return (
<>
<SEO
title="Dashboard"
description="Manage your workspaces, create new projects, and organize knowledge effortlessly in your ThinkEx dashboard."
keywords="dashboard, workspace management, AI workspace, productivity tools"
url="https://thinkex.app/dashboard"
canonical="https://thinkex.app/dashboard"
/>
<MobileWarning />
<AnonymousSessionHandler>
<WorkspaceProvider>
Expand All @@ -506,3 +498,13 @@ export default function Page() {
</>
);
}

export default function Page() {
const router = useRouter();

useEffect(() => {
router.replace("/home");

@cubic-dev-ai cubic-dev-ai Bot Jan 17, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Client-side redirect means /dashboard still ships and hydrates the full dashboard bundle before navigating, leaving a blank screen and unnecessary payload. Consider moving the redirect to a lightweight server route/config so the redirect happens before client JS loads.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/app/dashboard/page.tsx, line 506:

<comment>Client-side redirect means `/dashboard` still ships and hydrates the full dashboard bundle before navigating, leaving a blank screen and unnecessary payload. Consider moving the redirect to a lightweight server route/config so the redirect happens before client JS loads.</comment>

<file context>
@@ -506,3 +498,13 @@ export default function Page() {
+  const router = useRouter();
+
+  useEffect(() => {
+    router.replace("/home");
+  }, [router]);
+
</file context>
Fix with Cubic

}, [router]);

return null;
}
4 changes: 2 additions & 2 deletions src/app/guest-setup/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default function GuestSetupPage() {
try {
// Step 1: Check if already authenticated (not anonymous)
if (session && !session.user.isAnonymous) {
router.replace("/dashboard");
router.replace("/home");
return;
}

Expand All @@ -55,7 +55,7 @@ export default function GuestSetupPage() {
if (data.slug) {
router.replace(`/dashboard/${data.slug}`);
} else {
router.replace("/dashboard");
router.replace("/home");
}
} catch (error) {
console.error("Guest setup error:", error);
Expand Down
17 changes: 17 additions & 0 deletions src/app/home/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { SEO } from "@/components/seo/SEO";
import { DashboardShell } from "../dashboard/page";

export default function HomePage() {
return (
<>
<SEO
title="Home"
description="Choose a workspace or create a new one to start organizing your knowledge in ThinkEx."
keywords="home, workspaces, dashboard, productivity tools"
url="https://thinkex.app/home"
canonical="https://thinkex.app/home"
/>
<DashboardShell />
</>
);
}
11 changes: 8 additions & 3 deletions src/app/onboarding/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,20 @@ export default function OnboardingPage() {

// If there's a redirect_url that's NOT a dashboard route (e.g., share links),
// prioritize that over the onboarding redirect
if (redirectUrl && !redirectUrl.startsWith("/dashboard") && !redirectUrl.startsWith("/onboarding")) {
if (
redirectUrl &&
!redirectUrl.startsWith("/dashboard") &&
!redirectUrl.startsWith("/onboarding") &&
!redirectUrl.startsWith("/home")
) {
sessionStorage.removeItem("auth_redirect_url");
router.replace(redirectUrl);
} else if (data.redirectTo) {
// Use the onboarding redirect (demo workspace)
router.replace(data.redirectTo);
} else {
// Existing user, go to dashboard
router.replace("/dashboard");
// Existing user, go to home
router.replace("/home");
}
} catch (error) {
if ((error as Error).name !== "AbortError") {
Expand Down
2 changes: 1 addition & 1 deletion src/app/share/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export default function SharePage() {
const handleModalClose = (open: boolean) => {
if (!open) {
// Redirect to dashboard when modal is closed without importing
router.push("/dashboard");
router.push("/home");
}
Comment on lines 170 to 174

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update the inline comment to match the new redirect target.

The comment still says “dashboard” but the code now routes to /home.

📝 Suggested edit
-        // Redirect to dashboard when modal is closed without importing
+        // Redirect to home when modal is closed without importing
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleModalClose = (open: boolean) => {
if (!open) {
// Redirect to dashboard when modal is closed without importing
router.push("/dashboard");
router.push("/home");
}
const handleModalClose = (open: boolean) => {
if (!open) {
// Redirect to home when modal is closed without importing
router.push("/home");
}
🤖 Prompt for AI Agents
In `@src/app/share/`[id]/page.tsx around lines 170 - 174, Update the inline
comment inside the handleModalClose function to reflect the actual redirect
target: replace "Redirect to dashboard when modal is closed without importing"
with a comment that says the modal redirects to "/home" (e.g., "Redirect to
/home when modal is closed without importing") near the router.push("/home")
call.

};

Expand Down
1 change: 0 additions & 1 deletion src/components/chat/AppChatHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,6 @@ export function AppChatHeader({
closeAllPanels();
}
}}
className="shrink-0"
/>
</TooltipTrigger>
<TooltipContent side="right">
Expand Down
4 changes: 2 additions & 2 deletions src/components/landing/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export function Navbar() {
variant="outline"
className="rounded-md font-medium transition-all hover:bg-foreground/5"
>
<Link href="/dashboard" prefetch>
<Link href="/home" prefetch>
Dashboard
</Link>
</Button>
Expand Down Expand Up @@ -235,7 +235,7 @@ export function Navbar() {
<DropdownMenuSeparator />
<DropdownMenuItem asChild>
<Link
href="/dashboard"
href="/home"
onClick={() => posthog.capture('navbar-dashboard-clicked', { location: 'mobile' })}
className="cursor-pointer"
>
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ function SidebarTrigger({
data-slot="sidebar-trigger"
aria-label="Toggle Sidebar"
className={cn(
"inline-flex h-8 w-8 items-center justify-center rounded-md border border-sidebar-border text-muted-foreground hover:text-sidebar-foreground hover:bg-sidebar-accent transition-colors cursor-pointer",
"inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md border border-sidebar-border text-muted-foreground hover:text-sidebar-foreground hover:bg-sidebar-accent transition-colors cursor-pointer",
className
)}
onClick={(event) => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/workspace-canvas/WorkspaceSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ function WorkspaceSidebar({
<h2 className="text-lg font-medium whitespace-nowrap -mb-0.5">ThinkEx</h2>
</Link>
<Link
href="/dashboard"
href="/home"
className={cn(
"inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:text-sidebar-foreground hover:bg-sidebar-accent transition-colors cursor-pointer group-data-[collapsible=icon]:hidden"
)}
Expand Down
2 changes: 1 addition & 1 deletion src/components/workspace/SharedWorkspaceModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export default function SharedWorkspaceModal({
if (!isCreating) {
onOpenChange(newOpen);
if (!newOpen) {
router.push("/dashboard");
router.push("/home");
}
}
};
Expand Down
2 changes: 1 addition & 1 deletion src/contexts/WorkspaceContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export function WorkspaceProvider({ children }: { children: React.ReactNode }) {
if (remainingWorkspaces.length > 0) {
switchWorkspace(remainingWorkspaces[0].slug || remainingWorkspaces[0].id);
} else {
router.push("/dashboard");
router.push("/home");
}
}

Expand Down
10 changes: 5 additions & 5 deletions src/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,20 @@ export async function proxy(request: NextRequest) {
const sessionCookie = getSessionCookie(request);
const { pathname } = request.nextUrl;

// Redirect authenticated users from root to dashboard
// Redirect authenticated users from root to home
if (sessionCookie && pathname === "/") {
return NextResponse.redirect(new URL("/dashboard", request.url));
return NextResponse.redirect(new URL("/home", request.url));
}

// Redirect authenticated users away from auth pages
if (sessionCookie && ["/sign-in", "/sign-up"].includes(pathname)) {
// Preserve redirect_url if present
const redirectUrl = request.nextUrl.searchParams.get('redirect_url') || '/dashboard';
const redirectUrl = request.nextUrl.searchParams.get('redirect_url') || '/home';
return NextResponse.redirect(new URL(redirectUrl, request.url));
}

// Allow anonymous users to access dashboard - they'll get an anonymous session created automatically
// NOTE: Dashboard page component handles creating anonymous sessions for unauthenticated users
// Allow anonymous users to access home - they'll get an anonymous session created automatically
// NOTE: Home page component handles creating anonymous sessions for unauthenticated users
// Actual auth validation happens in API routes, not in middleware

return NextResponse.next();
Expand Down