diff --git a/apps/web/src/hooks/useHandleNewThread.ts b/apps/web/src/hooks/useHandleNewThread.ts
index 1b1c07b31c9..232551349db 100644
--- a/apps/web/src/hooks/useHandleNewThread.ts
+++ b/apps/web/src/hooks/useHandleNewThread.ts
@@ -47,6 +47,7 @@ export function useNewThreadHandler() {
worktreePath?: string | null;
envMode?: DraftThreadEnvMode;
startFromOrigin?: boolean;
+ replace?: boolean;
},
): Promise => {
const {
@@ -120,6 +121,7 @@ export function useNewThreadHandler() {
await router.navigate({
to: "/draft/$draftId",
params: { draftId: reusableStoredDraftThread.draftId },
+ replace: options?.replace ?? false,
});
})();
}
@@ -180,6 +182,7 @@ export function useNewThreadHandler() {
await router.navigate({
to: "/draft/$draftId",
params: { draftId },
+ replace: options?.replace ?? false,
});
})();
},
diff --git a/apps/web/src/routes/_chat.index.tsx b/apps/web/src/routes/_chat.index.tsx
index 94d49d00afe..9e88f9379e8 100644
--- a/apps/web/src/routes/_chat.index.tsx
+++ b/apps/web/src/routes/_chat.index.tsx
@@ -1,10 +1,16 @@
+import { scopeProjectRef } from "@t3tools/client-runtime/environment";
import { createFileRoute, Link } from "@tanstack/react-router";
import { LinkIcon, PlusIcon } from "lucide-react";
+import { useEffect, useMemo, useRef } from "react";
+import { useOpenAddProjectCommandPalette } from "../commandPaletteContext";
import { NoActiveThreadState } from "../components/NoActiveThreadState";
+import { sortProjectsForSidebar } from "../components/Sidebar.logic";
import { Button } from "../components/ui/button";
import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from "../components/ui/empty";
import { SidebarInset } from "../components/ui/sidebar";
+import { useNewThreadHandler } from "../hooks/useHandleNewThread";
+import { useProjects, useThreadShells } from "../state/entities";
import { useEnvironments } from "../state/environments";
import { APP_DISPLAY_NAME } from "~/branding";
import { hasCloudPublicConfig } from "~/cloud/publicConfig";
@@ -19,9 +25,70 @@ function ChatIndexRouteView() {
return ;
}
+ return ;
+}
+
+/**
+ * Landing on the index route drops straight into a draft thread for the most
+ * recently active project, so the first screen is a prompt instead of a dead
+ * end. Falls back to an add-project hero when no project exists yet.
+ */
+function IndexDraftLanding() {
+ const projects = useProjects();
+ const threads = useThreadShells();
+ const handleNewThread = useNewThreadHandler();
+ const startedRef = useRef(false);
+
+ const mostRecentProject = useMemo(
+ () => sortProjectsForSidebar(projects, threads, "updated_at")[0] ?? null,
+ [projects, threads],
+ );
+
+ useEffect(() => {
+ if (mostRecentProject === null || startedRef.current) {
+ return;
+ }
+ startedRef.current = true;
+ void handleNewThread(scopeProjectRef(mostRecentProject.environmentId, mostRecentProject.id), {
+ replace: true,
+ });
+ }, [handleNewThread, mostRecentProject]);
+
+ if (mostRecentProject === null) {
+ return ;
+ }
return ;
}
+function NoProjectsHero() {
+ const openAddProject = useOpenAddProjectCommandPalette();
+
+ return (
+
+
+
+
+
+
+ What should we work on?
+
+
+ Add a project to start your first thread.
+
+
+
+
+
+
+
+ );
+}
+
export const Route = createFileRoute("/_chat/")({
component: ChatIndexRouteView,
});
diff --git a/docs/fork/0002-draft-hero-landing.md b/docs/fork/0002-draft-hero-landing.md
new file mode 100644
index 00000000000..82e6dd70dcc
--- /dev/null
+++ b/docs/fork/0002-draft-hero-landing.md
@@ -0,0 +1,32 @@
+# 0002: Draft hero landing on the index route
+
+- PR: [TrogonStack/t3code#2](https://github.com/TrogonStack/t3code/pull/2)
+- Status: active
+
+## What you can do now
+
+- Opening the app (or landing anywhere without an active thread) drops you
+ straight into a ready-to-send composer for your most recently active
+ project, under a centered "What should we do in {project}?" headline. Type
+ and send; the thread is created as usual.
+- The project name in the headline is a dropdown: pick another project and
+ the screen swaps to that project's draft, keeping each project's composer
+ text, model selection, and branch settings intact.
+- The hero stays visually stable while the app connects: the branch toolbar
+ and connection banners appear below the composer without shifting the
+ headline or composer.
+- With no projects yet, the screen offers a single Add project action that
+ opens the existing add-project flow.
+
+## Why
+
+The index route used to be a dead end that told you to go click something
+else. The first screen should be a prompt, so launching the app and typing is
+the whole flow, with zero clicks in between.
+
+## Upstream considerations
+
+Built entirely on the existing draft-thread machinery and public hooks, so
+the rebase burden is low. This is a product-opinion change to the landing
+experience; upstream may prefer their neutral empty state, so it may stay a
+fork divergence permanently.
diff --git a/docs/fork/README.md b/docs/fork/README.md
index d73a005d602..4ad82be547a 100644
--- a/docs/fork/README.md
+++ b/docs/fork/README.md
@@ -32,3 +32,4 @@ Each entry uses these sections:
| # | Divergence | PR | Status |
| ---- | ----------------------------------------------------------------------------------------------------- | -------------------------------------------------- | ------ |
| 0001 | [File explorer mention actions and zoom-aware context menus](./0001-file-explorer-mention-actions.md) | [#1](https://github.com/TrogonStack/t3code/pull/1) | active |
+| 0002 | [Draft hero landing on the index route](./0002-draft-hero-landing.md) | [#2](https://github.com/TrogonStack/t3code/pull/2) | active |