Skip to content

Commit 438192c

Browse files
vibeguiclaude
andauthored
feat(web): Tasks board opens next to chat as a top-bar toggle (#4565)
* feat(web): Tasks board opens next to chat as a top-bar toggle Add a "Tasks" top-toolbar toggle beside Chat and Library (order: Chat · Tasks · Library) that opens the org task board next to the chat via `?main=board`, mirroring the Library overlay — instead of the sidebar "Board" entry that navigated to a separate route. - New shared useMainOverlayToggle hook backs both Library and Tasks: toggling an overlay OFF restores the tab that was showing when it opened (falling back to the agent's default view) instead of blanking the main panel into a full-width chat. - LibraryToggle refactored onto the shared hook (fixes the "close Library → giant chat" regression). - Task board inner view exported as TaskBoardPage and rendered as the `board` main-panel tab; the standalone /$org/board route stays for deep links. - Removed the sidebar "Board" item (useProjectSidebarItems now empty). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): rename task board heading to "Tasks" Match the "Tasks" toggle label — the board panel header now reads "Tasks" instead of "Board", in both the side-by-side panel and the standalone route. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ecc47ee commit 438192c

7 files changed

Lines changed: 122 additions & 50 deletions

File tree

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,13 @@
1-
import { useNavigate } from "@tanstack/react-router";
2-
import { useProjectContext } from "@decocms/mesh-sdk";
3-
import { Columns03 } from "@untitledui/icons";
41
import type { SidebarSection } from "@/web/components/sidebar/types";
5-
import { useTaskBoardEnabled } from "@/web/hooks/use-organization-settings";
62

73
/**
84
* Sidebar sections rendered above the thread list. Currently empty — the
95
* collapsed rail's new-thread button lives inside the thread list itself
106
* (see task-groups-list), alongside the collapse toggle and thread icons.
7+
*
8+
* The task board ("Tasks") now lives in the top toolbar next to Chat and
9+
* Library (see `TasksTab`), not here.
1110
*/
1211
export function useProjectSidebarItems(): SidebarSection[] {
13-
const { org } = useProjectContext();
14-
const navigate = useNavigate();
15-
const taskBoardEnabled = useTaskBoardEnabled();
16-
17-
const sections: SidebarSection[] = [];
18-
19-
if (taskBoardEnabled) {
20-
sections.push({
21-
type: "items",
22-
items: [
23-
{
24-
key: "board",
25-
label: "Board",
26-
icon: <Columns03 className="size-4!" />,
27-
onClick: () =>
28-
navigate({ to: "/$org/board", params: { org: org.slug } }),
29-
},
30-
],
31-
});
32-
}
33-
34-
return sections;
12+
return [];
3513
}

apps/mesh/src/web/layouts/agent-shell-layout/library-toggle.tsx

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Folder } from "@untitledui/icons";
2-
import { useNavigate, useParams, useSearch } from "@tanstack/react-router";
32
import { HeaderTabButton } from "@/web/layouts/main-panel-tabs/header-tab-button";
43
import { track } from "@/web/lib/posthog-client";
4+
import { useMainOverlayToggle } from "./use-main-overlay-toggle";
55

66
/**
77
* Library toggle — sits in the LEFT toolbar group beside the Chat toggle.
@@ -10,21 +10,15 @@ import { track } from "@/web/lib/posthog-client";
1010
* every agent — so it doesn't belong in the per-agent view tab bar on the
1111
* right. Pinning it next to Chat keeps a stable, always-present entry point
1212
* that never shuffles as agent-specific tabs (Overview / Preview / …) come and
13-
* go. Opens `?main=files`; clicking while open closes it (main=0).
13+
* go. Opens `?main=files`; clicking while open restores the previously shown
14+
* tab (see useMainOverlayToggle) rather than closing the panel outright.
1415
*/
1516
export function LibraryToggle() {
16-
const navigate = useNavigate();
17-
const params = useParams({ strict: false }) as {
18-
org?: string;
19-
taskId?: string;
20-
};
21-
const search = useSearch({ strict: false }) as { main?: string };
22-
const active = search.main === "files";
17+
const { active, enabled, toggle } = useMainOverlayToggle("files");
2318

2419
// Needs a task route to toggle the main panel against; render nothing on
2520
// routes without one.
26-
if (!params.org || !params.taskId) return null;
27-
const { org, taskId } = params;
21+
if (!enabled) return null;
2822

2923
return (
3024
<HeaderTabButton
@@ -37,15 +31,7 @@ export function LibraryToggle() {
3731
button: "library",
3832
next_state: active ? "closed" : "open",
3933
});
40-
navigate({
41-
to: "/$org/$taskId",
42-
params: { org, taskId },
43-
search: (prev: Record<string, unknown>) => ({
44-
...prev,
45-
main: active ? "0" : "files",
46-
}),
47-
replace: true,
48-
});
34+
toggle();
4935
}}
5036
/>
5137
);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Columns03 } from "@untitledui/icons";
2+
import { HeaderTabButton } from "@/web/layouts/main-panel-tabs/header-tab-button";
3+
import { useTaskBoardEnabled } from "@/web/hooks/use-organization-settings";
4+
import { track } from "@/web/lib/posthog-client";
5+
import { useMainOverlayToggle } from "./use-main-overlay-toggle";
6+
7+
/**
8+
* Tasks toggle — the org task board, opened next to the chat like the Library
9+
* (via `?main=board`) rather than as a separate route. Sits in the LEFT toolbar
10+
* group after Chat and Library. Agent-independent, so it's a left-group toggle,
11+
* not a per-agent view tab. Gated on the org's `task_board_enabled` setting.
12+
*/
13+
export function TasksToggle() {
14+
const taskBoardEnabled = useTaskBoardEnabled();
15+
const { active, enabled, toggle } = useMainOverlayToggle("board");
16+
17+
if (!taskBoardEnabled || !enabled) return null;
18+
19+
return (
20+
<HeaderTabButton
21+
title="Tasks"
22+
icon={{ kind: "component", Component: Columns03 }}
23+
active={active}
24+
className="wco-no-drag h-10 md:h-8 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
25+
onClick={() => {
26+
track("agent_toolbar_toggled", {
27+
button: "tasks",
28+
next_state: active ? "closed" : "open",
29+
});
30+
toggle();
31+
}}
32+
/>
33+
);
34+
}

apps/mesh/src/web/layouts/agent-shell-layout/toggle-buttons.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { MessageCircle01 } from "@untitledui/icons";
22
import { HeaderTabButton } from "@/web/layouts/main-panel-tabs/header-tab-button";
33
import { track } from "@/web/lib/posthog-client";
44
import { LibraryToggle } from "./library-toggle";
5+
import { TasksToggle } from "./tasks-toggle";
56

67
export interface ToggleButtonsProps {
78
chatOpen: boolean;
@@ -41,8 +42,11 @@ export function ToggleButtons({
4142
toggleChat();
4243
}}
4344
/>
44-
{/* Library is agent-independent, so it lives here in the left group next
45-
to Chat rather than in the per-agent tab bar on the right. */}
45+
{/* Tasks and Library are agent-independent, so they live here in the left
46+
group next to Chat rather than in the per-agent tab bar on the right.
47+
Both open next to the chat as main-panel overlays. Order: Chat · Tasks
48+
· Library. */}
49+
<TasksToggle />
4650
<LibraryToggle />
4751
</>
4852
);
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { useRef } from "react";
2+
import { useNavigate, useParams, useSearch } from "@tanstack/react-router";
3+
4+
/**
5+
* A left-toolbar overlay toggle (Library, Tasks) — swaps the main panel's
6+
* active tab for an agent-independent view (`?main=<overlayTabId>`).
7+
*
8+
* Toggling OFF restores whatever tab was showing when the overlay opened
9+
* instead of blanking the panel — closing the Library over a "chat + Overview"
10+
* layout must return to the Overview, not leave a full-width chat. When there's
11+
* no remembered tab (e.g. the user deep-linked straight into the overlay), it
12+
* falls back to the agent's default view by dropping the `main` param.
13+
*
14+
* The remembered tab lives in a ref, which survives `?main` changes because the
15+
* toolbar stays mounted for the life of the task route.
16+
*/
17+
export function useMainOverlayToggle(overlayTabId: string): {
18+
active: boolean;
19+
enabled: boolean;
20+
toggle: () => void;
21+
} {
22+
const navigate = useNavigate();
23+
const params = useParams({ strict: false }) as {
24+
org?: string;
25+
taskId?: string;
26+
};
27+
const search = useSearch({ strict: false }) as { main?: string };
28+
const active = search.main === overlayTabId;
29+
const prevTab = useRef<string | undefined>(undefined);
30+
31+
const enabled = !!(params.org && params.taskId);
32+
33+
const toggle = () => {
34+
if (!params.org || !params.taskId) return;
35+
const org = params.org;
36+
const taskId = params.taskId;
37+
navigate({
38+
to: "/$org/$taskId",
39+
params: { org, taskId },
40+
search: (prev: Record<string, unknown>) => {
41+
const next = { ...prev };
42+
if (active) {
43+
// Restore the tab we replaced. A missing value means "the agent's
44+
// default view", represented by dropping `main` entirely.
45+
const restore = prevTab.current;
46+
if (restore && restore !== overlayTabId) next.main = restore;
47+
else delete next.main;
48+
} else {
49+
prevTab.current = search.main;
50+
next.main = overlayTabId;
51+
}
52+
return next;
53+
},
54+
replace: true,
55+
});
56+
};
57+
58+
return { active, enabled, toggle };
59+
}

apps/mesh/src/web/layouts/main-panel-tabs/index.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Suspense, lazy } from "react";
1212
import { useMainPanelTabs } from "./use-main-panel-tabs";
1313
import { SettingsTab } from "./settings-tab";
1414
import { OverviewTab } from "./overview-tab";
15+
import { TaskBoardPage } from "@/web/layouts/task-board";
1516
import { GitTab } from "@/web/components/thread/github/git-tab";
1617
import { PreviewBlocksTab } from "./preview-blocks-tab";
1718
import { CodeTab } from "./code-tab";
@@ -72,6 +73,16 @@ function TabBody({
7273
if (activeTab === "overview") {
7374
return <OverviewTab />;
7475
}
76+
if (activeTab === "board") {
77+
// Task board opened next to chat via the Tasks toggle (`?main=board`).
78+
// The main panel already supplies the card chrome, so render the inner
79+
// page inside a full-height flex column (mirrors the standalone route).
80+
return (
81+
<div className="flex h-full min-h-0 flex-col overflow-hidden">
82+
<TaskBoardPage />
83+
</div>
84+
);
85+
}
7586
if (isLegacySettingsTab(activeTab)) {
7687
return <SettingsTab virtualMcpId={virtualMcpId} />;
7788
}

apps/mesh/src/web/layouts/task-board/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ function DueDatePill({ iso }: { iso: string }) {
7777
);
7878
}
7979

80-
function TaskBoardPage() {
80+
export function TaskBoardPage() {
8181
const { items, isLoading } = useTaskBoardItems();
8282
const actions = useTaskBoardItemActions();
8383
const { data: membersData } = useMembers();
@@ -114,7 +114,7 @@ function TaskBoardPage() {
114114
layout === "board" ? "max-w-[1400px]" : "max-w-[900px]",
115115
)}
116116
>
117-
<h1 className="text-xl font-medium text-foreground">Board</h1>
117+
<h1 className="text-xl font-medium text-foreground">Tasks</h1>
118118

119119
<div className="flex flex-wrap items-center gap-2">
120120
<Button size="sm" onClick={openCreate}>

0 commit comments

Comments
 (0)