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
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
"use client";

import { Button } from "@/components/ui/button";
import { Typography } from "@/components/ui/typography";
import type { SidebarNavItem } from "@/components/web-shell/types/web-shell.types";
import { navItems } from "@/components/web-shell/web-shell.constants";
import { cn } from "@/lib/utils";
import {
ArrowLeft,
BookOpen,
Expand All @@ -23,9 +26,6 @@ import {
import Link from "next/link";
import { usePathname } from "next/navigation";
import type { ComponentType, ReactElement } from "react";
import { Button } from "@/components/ui/button";
import { Typography } from "@/components/ui/typography";
import { cn } from "@/lib/utils";
import type { ChatRoomSettingsSidebarProps } from "./types/chat-room.types";

const iconByKey: Record<
Expand Down
66 changes: 66 additions & 0 deletions packages/web/src/components/chat-room/chat-room-sidebar-nav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"use client";

import { ListChecks, Settings } from "lucide-react";
import Link from "next/link";
import type { ReactElement } from "react";

import { Button } from "@/components/ui/button";
import { Typography } from "@/components/ui/typography";
import { cn } from "@/lib/utils";
import type { ChatRoomSidebarNavProps } from "./types/chat-room-sidebar.types";

export function ChatRoomSidebarNav({
isCollapsed,
onCloseSidebar,
onSettingsClick,
}: ChatRoomSidebarNavProps): ReactElement {
return (
<nav className="grid gap-1 border-t border-border p-3">
<Button
asChild
aria-label="Issues"
className={cn(
"h-9 w-full justify-start gap-2 px-2 text-xs text-zinc-400 hover:bg-surface-hover hover:text-zinc-200",
isCollapsed && "md:justify-center md:px-0",
)}
size="sm"
variant="ghost"
>
<Link href="/issues" onClick={onCloseSidebar}>
<ListChecks size={15} />
<Typography
as="span"
className={cn(
"min-w-0 flex-1 truncate text-left",
isCollapsed && "md:sr-only",
)}
>
Issues
</Typography>
</Link>
</Button>
<Button
aria-label="Settings"
className={cn(
"h-9 w-full justify-start gap-2 px-2 text-xs text-zinc-400 hover:bg-surface-hover hover:text-zinc-200",
isCollapsed && "md:justify-center md:px-0",
)}
onClick={onSettingsClick}
size="sm"
type="button"
variant="ghost"
>
<Settings size={15} />
<Typography
as="span"
className={cn(
"min-w-0 flex-1 truncate text-left",
isCollapsed && "md:sr-only",
)}
>
Settings
</Typography>
</Button>
</nav>
);
}
33 changes: 7 additions & 26 deletions packages/web/src/components/chat-room/chat-room-sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { MessageSquarePlus, Search, Settings, X } from "lucide-react";
import { MessageSquarePlus, Search, X } from "lucide-react";
import { type ReactElement, useState } from "react";

import { Button } from "@/components/ui/button";
Expand All @@ -10,6 +10,7 @@ import { cn } from "@/lib/utils";
import { ChatRoomSessionList } from "./chat-room-session-list";
import { ChatRoomSettingsSidebar } from "./chat-room-settings-sidebar";
import { ChatRoomSidebarHeader } from "./chat-room-sidebar-header";
import { ChatRoomSidebarNav } from "./chat-room-sidebar-nav";
import { buildChatSessionSidebarContent } from "./chat-room-sidebar-utils";
import type { ChatRoomSidebarProps } from "./types/chat-room-sidebar.types";
import type { ChatRoomSidebarView } from "./types/chat-room.types";
Expand Down Expand Up @@ -64,7 +65,6 @@ export function ChatRoomSidebar({
}

function handleSettingsClick(): void {
console.log("sidebarView", sidebarView)
if (isCollapsed) {
onToggleCollapsed();
}
Expand Down Expand Up @@ -199,30 +199,11 @@ export function ChatRoomSidebar({
onUnpinSession={unpinSession}
/>
</div>
<nav className="border-t border-border p-3">
<Button
aria-label="Settings"
className={cn(
"h-9 w-full justify-start gap-2 px-2 text-xs text-zinc-400 hover:bg-surface-hover hover:text-zinc-200",
isCollapsed && "md:justify-center md:px-0",
)}
onClick={handleSettingsClick}
size="sm"
type="button"
variant="ghost"
>
<Settings size={15} />
<Typography
as="span"
className={cn(
"min-w-0 flex-1 truncate text-left",
isCollapsed && "md:sr-only",
)}
>
Settings
</Typography>
</Button>
</nav>
<ChatRoomSidebarNav
isCollapsed={isCollapsed}
onCloseSidebar={onCloseSidebar}
onSettingsClick={handleSettingsClick}
/>
</div>
<ChatRoomSettingsSidebar
isActive={isSettingsView}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ export interface ChatRoomSidebarHeaderProps {
onToggleCollapsed: () => void;
}

export interface ChatRoomSidebarNavProps {
isCollapsed: boolean;
onCloseSidebar: () => void;
onSettingsClick: () => void;
}

export interface ChatRoomSessionListProps {
activeSessionId: string;
collapsedProjectIds: Set<string>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { ReactElement, ReactNode } from "react";
import { Typography } from "@/components/ui/typography";
import type { ProjectBoardTaskRecord, TaskMutationRequest } from "@/lib/api";

import { normalizeDueDate } from "./issues-board-utils";
import { normalizeBoardStatus, normalizeDueDate } from "./issues-board-utils";
import type { IssueDetailDraft } from "./types/issues-board.types";

export function DetailField({
Expand Down Expand Up @@ -53,7 +53,7 @@ export function createDetailDraft(
title: task.title,
content: task.content,
priority: String(task.priority),
status: task.status,
status: normalizeBoardStatus(task.status),
creatorId: task.creatorId,
dueDate: toDateInputValue(task.dueDate),
linkedPr: task.linkedPr ?? "",
Expand Down
25 changes: 12 additions & 13 deletions packages/web/src/components/issues-board/issues-board-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ import {
import type { IssueDraft, IssueTab } from "./types/issues-board.types";

const LEGACY_PR_CREATED_STATUS = "pr_created";
const LEGACY_PLANNING_STATUS = "planning";
const LEGACY_PLAN_STATUS = "todo";
const LEGACY_IN_PROGRESS_STATUS = "implementing";
const TODO_STATUSES = ["planning", "plan", "todo"] as const;
const RUNNING_STATUSES = ["implementing", "in_progress", "running"] as const;
const LEGACY_REVIEW_STATUSES = ["reviewing", "testing"] as const;

export function getStatusLabel(status: string): string {
Expand Down Expand Up @@ -147,17 +146,17 @@ function normalizeIndex(index: number): number {
}

export function normalizeBoardStatus(status: string): string {
if (status === LEGACY_PLANNING_STATUS) {
return "plan";
if ((TODO_STATUSES as readonly string[]).includes(status)) {
return "todo";
}
if (status === LEGACY_PLAN_STATUS) {
return "plan";
if ((RUNNING_STATUSES as readonly string[]).includes(status)) {
return "running";
}
if (status === LEGACY_IN_PROGRESS_STATUS) {
return "in_progress";
}
return status === LEGACY_PR_CREATED_STATUS ||
if (
status === LEGACY_PR_CREATED_STATUS ||
(LEGACY_REVIEW_STATUSES as readonly string[]).includes(status)
? "in_review"
: status;
) {
return "in_review";
}
return status === "failed" ? "canceled" : status;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,18 @@ export const DEFAULT_CREATOR_ID = "member-1";

export const STATUS_ORDER = [
"backlog",
"plan",
"in_progress",
"todo",
"running",
"in_review",
"done",
"canceled",
"failed",
] as const;

export const STATUS_PRESENTATION: Record<string, StatusPresentation> = {
backlog: { label: "Backlog", tone: "border-slate-700/70 bg-surface-panel" },
plan: { label: "Plan", tone: "border-slate-700/70 bg-surface-panel" },
in_progress: {
label: "In Progress",
todo: { label: "To Do", tone: "border-slate-700/70 bg-surface-panel" },
running: {
label: "Running",
tone: "border-yellow-900/50 bg-yellow-950/35",
},
in_review: {
Expand All @@ -29,7 +28,6 @@ export const STATUS_PRESENTATION: Record<string, StatusPresentation> = {
},
done: { label: "Done", tone: "border-indigo-900/50 bg-indigo-950/35" },
canceled: { label: "Canceled", tone: "border-zinc-700/70 bg-zinc-950/35" },
failed: { label: "Failed", tone: "border-red-900/50 bg-red-950/35" },
};

export const PRIORITY_OPTIONS: readonly PriorityOption[] = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
export const navItems: SidebarNavItem[] = [
{ key: "agents", label: "Agents", href: "/agents" },
{ key: "projects", label: "Projects", href: "/projects" },
{ key: "issues", label: "Issues", href: "/issues" },
{ key: "integrations", label: "Integrations", href: "/integrations" },
{ key: "git", label: "Git", href: "/git" },
{ key: "usage", label: "Usage", href: "/usage" },
Expand Down
36 changes: 35 additions & 1 deletion packages/web/tests/issue-detail-editor-utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
import { describe, expect, it } from "bun:test";

import { createSaveRequest } from "../src/components/issues-board/issue-detail-editor-utils";
import {
createDetailDraft,
createSaveRequest,
} from "../src/components/issues-board/issue-detail-editor-utils";
import type { ProjectBoardTaskRecord } from "../src/lib/api";

describe("issue detail editor utilities", () => {
it("normalizes existing task status values for the issue status picker", () => {
expect(createDetailDraft(buildTask({ status: "plan" })).status).toBe(
"todo",
);
expect(createDetailDraft(buildTask({ status: "in_progress" })).status).toBe(
"running",
);
});

it("allows saving tasks with empty description content", () => {
const result = createSaveRequest(
{
Expand Down Expand Up @@ -32,3 +45,24 @@ describe("issue detail editor utilities", () => {
});
});
});

function buildTask(
overrides: Partial<ProjectBoardTaskRecord> = {},
): ProjectBoardTaskRecord {
return {
id: "task-1",
taskKey: "TASK-1",
projectId: "project-1",
title: "Task",
content: "Do the work",
priority: 1,
status: "backlog",
dueDate: null,
creatorId: "member-1",
assigneeId: null,
linkedPr: null,
createdAt: "2026-06-01T00:00:00.000Z",
updatedAt: "2026-06-01T00:00:00.000Z",
...overrides,
};
}
77 changes: 77 additions & 0 deletions packages/web/tests/issues-board-utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { describe, expect, it } from "bun:test";

import {
buildStatusColumns,
getStatusLabel,
normalizeBoardStatus,
} from "../src/components/issues-board/issues-board-utils";
import { STATUS_ORDER } from "../src/components/issues-board/issues-board.constants";
import type { ProjectBoardTaskRecord } from "../src/lib/api";

describe("issues board utilities", () => {
it("uses the requested issue status columns in order", () => {
expect([...STATUS_ORDER]).toEqual([
"backlog",
"todo",
"running",
"in_review",
"done",
"canceled",
]);
});

it("normalizes existing task statuses into the six board columns", () => {
const columns = buildStatusColumns([
buildTask({ id: "task-plan", status: "plan" }),
buildTask({ id: "task-progress", status: "in_progress" }),
buildTask({ id: "task-failed", status: "failed" }),
buildTask({ id: "task-reviewing", status: "reviewing" }),
]);

expect(columns.map((column) => column.status)).toEqual([
"backlog",
"todo",
"running",
"in_review",
"done",
"canceled",
]);
expect(columns.find((column) => column.status === "todo")?.tasks).toEqual([
expect.objectContaining({ id: "task-plan", status: "todo" }),
]);
expect(
columns.find((column) => column.status === "running")?.tasks,
).toEqual([
expect.objectContaining({ id: "task-progress", status: "running" }),
]);
expect(
columns.find((column) => column.status === "canceled")?.tasks,
).toEqual([
expect.objectContaining({ id: "task-failed", status: "canceled" }),
]);
expect(normalizeBoardStatus("todo")).toBe("todo");
expect(getStatusLabel("running")).toBe("Running");
expect(getStatusLabel("plan")).toBe("To Do");
});
});

function buildTask(
overrides: Partial<ProjectBoardTaskRecord> = {},
): ProjectBoardTaskRecord {
return {
id: "task-1",
taskKey: "TASK-1",
projectId: "project-1",
title: "Task",
content: "Do the work",
priority: 1,
status: "backlog",
dueDate: null,
creatorId: "member-1",
assigneeId: null,
linkedPr: null,
createdAt: "2026-06-01T00:00:00.000Z",
updatedAt: "2026-06-01T00:00:00.000Z",
...overrides,
};
}
9 changes: 9 additions & 0 deletions packages/web/tests/web-shell-navigation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,13 @@ describe("web shell navigation", () => {
});
expect(hrefForNavKey("git")).toBe("/git");
});

it("shows Issues in workspace navigation and routes it to the task board", () => {
expect(navItems).toContainEqual({
key: "issues",
label: "Issues",
href: "/issues",
});
expect(hrefForNavKey("issues")).toBe("/issues");
});
});
Loading