-
-
{
- track("tasks_panel_search_opened");
- setSearchEverOpened(true);
- setSearchOpen(true);
- }}
- >
-
-
-
{
- const next: GroupBy = groupBy === "agent" ? "status" : "agent";
- track("tasks_panel_group_by_changed", { to_value: next });
- setGroupBy(next);
- }}
+
+
{
+ track("tasks_panel_search_opened");
+ setSearchEverOpened(true);
+ setSearchOpen(true);
+ }}
+ >
+
+
+
+
+
+
+ {filtersActive && (
+
+ )}
+
+
+
- {groupBy === "agent" ? (
-
- ) : (
-
- )}
-
-
-
-
-
- {filtersActive && (
-
- )}
-
-
-
-
-
-
-
-
-
-
+ ),
+ )}
+
+
+
+
+ setTypeFilter(v as TypeFilter)}
+ >
+
+
+
+
+ {(Object.keys(TYPE_LABELS) as TypeFilter[]).map((opt) => (
+
+ {TYPE_LABELS[opt]}
+
+ ))}
+
+
+
+
+
+
- {groupBy === "status" ? (
- <>
- {groupThreadsByStatus(
- typeFiltered(memberFiltered(sortedThreads)),
- ).map((group) => (
-
setTaskId(t.id, t.virtual_mcp_id)}
- onArchiveTask={handleArchive}
- filters={filters}
- />
- ))}
- >
+ {tasks.length === 0 ? (
+
+ No tasks yet
+
) : (
- setLocalOrderRevision((n) => n + 1)}
- renderGroup={(group) => ({
- ...buildAgentGroupRenderProps(group),
- })}
- />
+ tasks.map((task) => (
+ setTaskId(task.id, task.virtual_mcp_id)}
+ onArchive={() => handleArchive(task)}
+ showAutomationBadge={Boolean(task.trigger_id)}
+ />
+ ))
)}
{searchEverOpened && (
diff --git a/apps/mesh/src/web/hooks/use-project-sidebar-items.tsx b/apps/mesh/src/web/hooks/use-project-sidebar-items.tsx
index e6915eb9ef..d7a5dad9d5 100644
--- a/apps/mesh/src/web/hooks/use-project-sidebar-items.tsx
+++ b/apps/mesh/src/web/hooks/use-project-sidebar-items.tsx
@@ -4,14 +4,7 @@ import type {
SidebarSection,
} from "@/web/components/sidebar/types";
import { useNavigate, useRouterState } from "@tanstack/react-router";
-import { Home01 } from "@untitledui/icons";
-import {
- SidebarGroup,
- SidebarGroupContent,
- SidebarMenu,
- useSidebar,
-} from "@deco/ui/components/sidebar.tsx";
-import { BrowseAgentsButton } from "@/web/components/sidebar/browse-agents-button";
+import { Home01, Inbox01, LayoutAlt01, Target04 } from "@untitledui/icons";
export function useProjectSidebarItems(): SidebarSection[] {
const { org } = useProjectContext();
@@ -19,8 +12,6 @@ export function useProjectSidebarItems(): SidebarSection[] {
const routerState = useRouterState();
const pathname = routerState.location.pathname;
const slug = org.slug;
- const { state, isMobile } = useSidebar();
- const isCollapsed = !isMobile && state === "collapsed";
const homeItem: NavigationSidebarItem = {
key: "home",
@@ -32,23 +23,37 @@ export function useProjectSidebarItems(): SidebarSection[] {
},
};
- const sections: SidebarSection[] = [{ type: "items", items: [homeItem] }];
+ const goalsItem: NavigationSidebarItem = {
+ key: "goals",
+ label: "Goals",
+ icon:
,
+ isActive: pathname.startsWith(`/${slug}/goal`),
+ onClick: () => {
+ navigate({ to: "/$org/goal", params: { org: slug }, search: {} });
+ },
+ };
+
+ const inboxItem: NavigationSidebarItem = {
+ key: "inbox",
+ label: "Inbox",
+ icon:
,
+ isActive: pathname === `/${slug}/inbox`,
+ onClick: () => {
+ navigate({ to: "/$org/inbox", params: { org: slug } });
+ },
+ };
- if (isCollapsed) {
- sections.push({
- type: "custom",
- key: "new-task",
- content: (
-
-
-
-
-
-
-
- ),
- });
- }
+ const contentItem: NavigationSidebarItem = {
+ key: "content",
+ label: "Content",
+ icon:
,
+ isActive: pathname === `/${slug}/content`,
+ onClick: () => {
+ navigate({ to: "/$org/content", params: { org: slug } });
+ },
+ };
- return sections;
+ return [
+ { type: "items", items: [homeItem, goalsItem, inboxItem, contentItem] },
+ ];
}
diff --git a/apps/mesh/src/web/index.tsx b/apps/mesh/src/web/index.tsx
index b3c2ed040a..c4c0cfdc9d 100644
--- a/apps/mesh/src/web/index.tsx
+++ b/apps/mesh/src/web/index.tsx
@@ -19,7 +19,6 @@ import type { ReactNode } from "react";
import "../../index.css";
import { listOrganizationsCached } from "@/web/lib/auth-client";
-import { LOCALSTORAGE_KEYS } from "@/web/lib/localstorage-keys";
import { sourcePlugins } from "./plugins.ts";
import type {
@@ -114,42 +113,13 @@ const shellLayout = createRoute({
component: lazyRouteComponent(() => import("./layouts/shell-layout.tsx")),
});
-// Home route (landing, redirects to last or only org)
+// Home route (landing) — redesign: you land in your personal space (/me),
+// above any org (like chatgpt.com). Entering an Agent from there goes to /$org.
const homeRoute = createRoute({
getParentRoute: () => shellLayout,
path: "/",
- beforeLoad: async () => {
- // Fast path: redirect returning users immediately from the cached slug,
- // WITHOUT awaiting the org-list network call. This is what keeps a cold
- // load from blocking on a round-trip (the previous blank/white screen).
- // The org layout validates membership via getFullOrganization, and a stale
- // slug self-heals in OrgAccessGate (clears the slug + bounces back to "/").
- const lastOrgSlug = localStorage.getItem(LOCALSTORAGE_KEYS.lastOrgSlug());
- if (lastOrgSlug) {
- throw redirect({
- to: "/$org",
- params: { org: lastOrgSlug },
- });
- }
-
- // No cached slug — fetch the list (cached) to pick a destination.
- const { data: orgs } = await listOrganizationsCached();
-
- // If the list call failed, skip redirect logic to avoid a misfire on a
- // transient API failure. Archived orgs are already filtered by the helper.
- if (!orgs) return;
-
- // Redirect to first available org (every user gets a default org on signup)
- const firstOrg = orgs[0];
- if (firstOrg) {
- throw redirect({
- to: "/$org",
- params: { org: firstOrg.slug },
- });
- }
-
- // No orgs at all — send to onboarding
- throw redirect({ to: "/onboarding" });
+ beforeLoad: () => {
+ throw redirect({ to: "/me" });
},
});
@@ -167,6 +137,15 @@ const onboardingRoute = createRoute({
component: lazyRouteComponent(() => import("./routes/onboarding.tsx")),
});
+// Redesign: the USER's personal space (/me) — above any org, like chatgpt.com.
+// Standalone full-screen (under rootRoute, no org/ProjectContext needed). Your
+// personal agent + your connections + your Agents (entering one → /$org).
+const personalHomeRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: "/me",
+ component: lazyRouteComponent(() => import("./routes/me.tsx")),
+});
+
// ============================================
// ORG LAYOUT
// ============================================
@@ -177,6 +156,14 @@ const orgLayout = createRoute({
component: lazyRouteComponent(() => import("./layouts/org-layout.tsx")),
});
+// Redesign mock: a standalone onboarding flow at /$org/onboarding (full-screen,
+// no shell chrome). Static path → never collides with /$org/$taskId.
+const onboardingDemoRoute = createRoute({
+ getParentRoute: () => orgLayout,
+ path: "/onboarding",
+ component: lazyRouteComponent(() => import("./routes/orgs/onboarding.tsx")),
+});
+
// ============================================
// ORG SHELL LAYOUT (pathless — sidebar + Toolbar + ChatPrefsProvider for / and /$taskId)
// ============================================
@@ -233,9 +220,44 @@ const unifiedChatRoute = createRoute({
const orgIndexRoute = createRoute({
getParentRoute: () => orgShellLayout,
path: "/",
+ validateSearch: z.lazy(() =>
+ z.object({
+ // Redesign: when set, the home opens this finding as an incident task in place.
+ task: z.string().optional(),
+ // Redesign: when "1", the home opens the New Task composer dialog.
+ new: z.string().optional(),
+ }),
+ ),
component: lazyRouteComponent(() => import("./layouts/org-home/index.tsx")),
});
+// Redesign: the Inbox / Findings page (/$org/inbox) — inside the org shell, so
+// it has the sidebar + toolbar. Static path, never collides with /$org/$taskId.
+const inboxRoute = createRoute({
+ getParentRoute: () => orgShellLayout,
+ path: "/inbox",
+ component: lazyRouteComponent(() => import("./routes/orgs/inbox.tsx")),
+});
+
+// Redesign: the storefront content / CMS (/$org/content) — inside the shell.
+const contentRoute = createRoute({
+ getParentRoute: () => orgShellLayout,
+ path: "/content",
+ component: lazyRouteComponent(() => import("./routes/orgs/content.tsx")),
+});
+
+// Redesign: a goal's closed-loop detail (/$org/goal?g=
) — inside the org
+// shell. SINGLE static segment + a search param for the id, so it ranks like
+// `/inbox` and never collides with the 2-segment `/$taskId/$pluginId` chat route
+// (a `/goal/$goalId` path loses that precedence fight and falls through to the
+// empty task view).
+const goalRoute = createRoute({
+ getParentRoute: () => orgShellLayout,
+ path: "/goal",
+ validateSearch: z.lazy(() => z.object({ g: z.string().optional() })),
+ component: lazyRouteComponent(() => import("./routes/orgs/goal.tsx")),
+});
+
// ============================================
// SETTINGS LAYOUT (/$org/settings)
// ============================================
@@ -319,6 +341,71 @@ const monitoringRoute = createRoute({
),
});
+// Agent settings overview — the home of everything about one agent
+// `?agent` selects which of the user's agents to show (current org by default).
+const agentSearchSchema = z.lazy(() =>
+ z.object({ agent: z.string().optional() }),
+);
+
+const settingsAgentRoute = createRoute({
+ getParentRoute: () => settingsLayout,
+ path: "/agent",
+ component: lazyRouteComponent(
+ () => import("./routes/orgs/settings/agent.tsx"),
+ ),
+ validateSearch: agentSearchSchema,
+});
+
+// Agent personalization — the editable user layer (guidance, skills, connections)
+const settingsAgentPersonalizationRoute = createRoute({
+ getParentRoute: () => settingsLayout,
+ path: "/agent/personalization",
+ component: lazyRouteComponent(
+ () => import("./routes/orgs/settings/agent-personalization.tsx"),
+ ),
+ validateSearch: agentSearchSchema,
+});
+
+// Agent automations — Studio automations split System (managed) / Yours
+const settingsAgentAutomationsRoute = createRoute({
+ getParentRoute: () => settingsLayout,
+ path: "/agent/automations",
+ component: lazyRouteComponent(
+ () => import("./routes/orgs/settings/agent-automations.tsx"),
+ ),
+ validateSearch: agentSearchSchema,
+});
+
+// Agent findings — what the agent watches and how far it acts (agent-wide)
+const settingsAgentFindingsRoute = createRoute({
+ getParentRoute: () => settingsLayout,
+ path: "/agent/findings",
+ component: lazyRouteComponent(
+ () => import("./routes/orgs/settings/agent-findings.tsx"),
+ ),
+ validateSearch: agentSearchSchema,
+});
+
+// Agent memory — what the agent remembers about you and the work
+const settingsAgentMemoryRoute = createRoute({
+ getParentRoute: () => settingsLayout,
+ path: "/agent/memory",
+ component: lazyRouteComponent(
+ () => import("./routes/orgs/settings/agent-memory.tsx"),
+ ),
+ validateSearch: agentSearchSchema,
+});
+
+// Agent files — what the agent reads and what it produces
+const settingsAgentFilesRoute = createRoute({
+ getParentRoute: () => settingsLayout,
+ path: "/agent/files",
+ component: lazyRouteComponent(
+ () => import("./routes/orgs/settings/agent-files.tsx"),
+ ),
+ validateSearch: agentSearchSchema,
+});
+
// Organization settings pages
const settingsGeneralRoute = createRoute({
getParentRoute: () => settingsLayout,
@@ -328,6 +415,14 @@ const settingsGeneralRoute = createRoute({
),
});
+const settingsFindingsRoute = createRoute({
+ getParentRoute: () => settingsLayout,
+ path: "/findings",
+ component: lazyRouteComponent(
+ () => import("./routes/orgs/settings/findings.tsx"),
+ ),
+});
+
const settingsFeaturesRoute = createRoute({
getParentRoute: () => settingsLayout,
path: "/features",
@@ -540,12 +635,19 @@ const unifiedPluginWithChildren = unifiedPluginRoute.addChildren(pluginRoutes);
const settingsWithChildren = settingsLayout.addChildren([
settingsIndexRoute,
+ settingsAgentRoute,
+ settingsAgentPersonalizationRoute,
+ settingsAgentAutomationsRoute,
+ settingsAgentFindingsRoute,
+ settingsAgentMemoryRoute,
+ settingsAgentFilesRoute,
connectionsRoute,
connectionDetailRoute,
collectionDetailRoute,
settingsAgentsRoute,
settingsAutomationsRoute,
monitoringRoute,
+ settingsFindingsRoute,
settingsGeneralRoute,
settingsFeaturesRoute,
settingsBrandContextRoute,
@@ -574,12 +676,16 @@ const agentShellWithChildren = agentShellLayout.addChildren([
const orgShellWithChildren = orgShellLayout.addChildren([
orgIndexRoute,
+ inboxRoute,
+ contentRoute,
+ goalRoute,
agentShellWithChildren,
]);
const orgLayoutWithChildren = orgLayout.addChildren([
orgShellWithChildren,
settingsWithChildren,
+ onboardingDemoRoute,
]);
const shellRouteTree = shellLayout.addChildren([
@@ -590,6 +696,7 @@ const shellRouteTree = shellLayout.addChildren([
const routeTree = rootRoute.addChildren([
shellRouteTree,
onboardingRoute,
+ personalHomeRoute,
loginRoute,
cliAuthSuccessRoute,
resetPasswordRoute,
diff --git a/apps/mesh/src/web/layouts/home-page/background.tsx b/apps/mesh/src/web/layouts/home-page/background.tsx
index 741c816eb0..dfea0f404c 100644
--- a/apps/mesh/src/web/layouts/home-page/background.tsx
+++ b/apps/mesh/src/web/layouts/home-page/background.tsx
@@ -1,14 +1,45 @@
/**
- * Faded decorative corners for the home. Two SVGs (top-left and
- * bottom-right) anchored to their respective corners, rendered at their
- * natural viewBox size — no stretching or cropping by CSS, and crisp at
- * any zoom level since SVG. Light/dark variants swap via Tailwind.
+ * Faded decorative graphics for the home, rendered at their natural viewBox
+ * size — crisp at any zoom since SVG. Light/dark variants swap via Tailwind.
+ *
+ * `variant`:
+ * - "corners" (default): top-left + bottom-right corner motifs.
+ * - "left": a single accent hugging the LEFT edge, vertically centered and
+ * faint, so it never sits behind the heading text (used by the redesign
+ * home where the brief reads over it).
*/
const TOP_LEFT_WIDTH_PX = 420; // ~50% of viewBox (834)
const BOTTOM_RIGHT_WIDTH_PX = 305; // ~50% of viewBox (610)
+const LEFT_WIDTH_PX = 360;
+
+export function HomeBackground({
+ variant = "corners",
+}: {
+ variant?: "corners" | "left";
+}) {
+ if (variant === "left") {
+ return (
+
+

+

+
+ );
+ }
-export function HomeBackground() {
return (
diff --git a/apps/mesh/src/web/layouts/org-home/index.tsx b/apps/mesh/src/web/layouts/org-home/index.tsx
index c56f98e731..94f1256d6c 100644
--- a/apps/mesh/src/web/layouts/org-home/index.tsx
+++ b/apps/mesh/src/web/layouts/org-home/index.tsx
@@ -1,22 +1,56 @@
/**
- * OrgHome — leaf component for /$org/. Renders HomePage inside the same
- * panel chrome the chat surface uses, full-bleed (no chat-main split).
+ * OrgHome — leaf component for /$org/.
*
- * No Chat.Provider, no ActiveTaskProvider — the home composer is wired
- * to the home submit path (URL autosend handoff) via Chat.Input's
- * optional-context fallback.
+ * Redesign: Deco's brief (System Health). Findings are REAL threads (seeded
+ * into the thread manager), so opening one navigates to the real chat route
+ * `/$org/$taskId` — the normal task/thread UI, rendering the finding's seeded
+ * messages + tool UIs. `?new=1` opens the New Task composer (real Chat.Input).
*/
+import { useNavigate, useParams, useSearch } from "@tanstack/react-router";
import { useIsMobile } from "@deco/ui/hooks/use-mobile.ts";
-import { HomePage } from "@/web/layouts/home-page";
+import { HomeBackground } from "@/web/layouts/home-page/background";
+import { RedesignHome } from "@/web/views/deco-redesign/home";
+import { NewTaskDialog } from "@/web/views/deco-redesign/new-task-dialog";
export default function OrgHome() {
const isMobile = useIsMobile();
+ const navigate = useNavigate();
+ const { org } = useParams({ strict: false }) as { org?: string };
+ const search = useSearch({ strict: false }) as { new?: string };
+
+ const openTask = (id: string) => {
+ if (org) navigate({ to: "/$org/$taskId", params: { org, taskId: id } });
+ };
+ const openNew = () => {
+ if (org) navigate({ to: "/$org", params: { org }, search: { new: "1" } });
+ };
+ const closeNew = () => {
+ if (org) navigate({ to: "/$org", params: { org }, search: {} });
+ };
+ const openInbox = () => {
+ if (org) navigate({ to: "/$org/inbox", params: { org } });
+ };
+ const openGoal = (id: string) => {
+ if (org) navigate({ to: "/$org/goal", params: { org }, search: { g: id } });
+ };
+
+ const content = (
+