diff --git a/apps/web/app/(dashboard)/issues/[id]/page.tsx b/apps/web/app/(dashboard)/issues/[id]/page.tsx
index 06441140..38af2c1a 100644
--- a/apps/web/app/(dashboard)/issues/[id]/page.tsx
+++ b/apps/web/app/(dashboard)/issues/[id]/page.tsx
@@ -3,11 +3,11 @@
import { use } from "react";
import Link from "next/link";
import {
- ArrowLeft,
Bot,
Calendar,
ChevronRight,
User,
+ MessageSquare,
} from "lucide-react";
import {
MOCK_ISSUES,
@@ -24,6 +24,7 @@ import { StatusIcon, PriorityIcon } from "../page";
function timeAgo(dateStr: string): string {
const diff = Date.now() - new Date(dateStr).getTime();
const minutes = Math.floor(diff / 60000);
+ if (minutes < 1) return "just now";
if (minutes < 60) return `${minutes}m ago`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours}h ago`;
@@ -32,7 +33,7 @@ function timeAgo(dateStr: string): string {
}
function formatDate(date: string | null): string {
- if (!date) return "None";
+ if (!date) return "—";
return new Date(date).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
@@ -40,30 +41,45 @@ function formatDate(date: string | null): string {
});
}
-function ActorAvatar({ actor, size = "sm" }: { actor: MockAssignee; size?: "sm" | "md" }) {
- const sizeClass = size === "sm" ? "h-5 w-5 text-[10px]" : "h-6 w-6 text-xs";
+function shortDate(date: string): string {
+ return new Date(date).toLocaleDateString("en-US", {
+ month: "short",
+ day: "numeric",
+ });
+}
+
+// ---------------------------------------------------------------------------
+// Avatar
+// ---------------------------------------------------------------------------
+
+function Avatar({
+ person,
+ size = 20,
+}: {
+ person: MockAssignee;
+ size?: number;
+}) {
+ const isAgent = person.type === "agent";
return (
- {actor.type === "agent" ? (
-
- ) : (
- actor.avatar.charAt(0)
- )}
+ {isAgent ? : person.avatar.charAt(0)}
);
}
// ---------------------------------------------------------------------------
-// Properties Sidebar
+// Property row (Linear-style: label left, clickable value right)
// ---------------------------------------------------------------------------
-function PropertyRow({
+function PropRow({
label,
children,
}: {
@@ -71,9 +87,11 @@ function PropertyRow({
children: React.ReactNode;
}) {
return (
-
-
{label}
-
{children}
+
+
{label}
+
+ {children}
+
);
}
@@ -103,6 +121,7 @@ export default function IssueDetailPage({
const isOverdue =
issue.dueDate && new Date(issue.dueDate) < new Date() && issue.status !== "done";
+ // Merge activity + comments into timeline
const timeline = [
...issue.activity.map((a) => ({
id: a.id,
@@ -118,148 +137,160 @@ export default function IssueDetailPage({
content: c.body,
createdAt: c.createdAt,
})),
- ].sort(
- (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
- );
+ ].sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
return (
- {/* ---- Left: Content ---- */}
+ {/* ================================================================
+ LEFT: Content area
+ ================================================================ */}
- {/* Breadcrumb bar */}
-
+ {/* Header bar */}
+
Issues
-
- {issue.key}
+
+ {issue.key}
-
+ {/* Content */}
+
+ {/* Issue key */}
+
{issue.key}
+
{/* Title */}
-
{issue.title}
+
+ {issue.title}
+
{/* Description */}
{issue.description && (
-
+
{issue.description}
)}
- {/* Activity */}
-
-
-
- Activity
-
-
+ {/* Separator */}
+
-
+ {/* Activity */}
+
+
Activity
+
+
{timeline.map((entry) =>
entry.kind === "comment" ? (
-
-
+ /* ---- Comment ---- */
+
+
{entry.actor.name}
-
+
{timeAgo(entry.createdAt)}
-
+
) : (
+ /* ---- Activity entry ---- */
-
-
-
-
- {entry.actor.name}
-
-
{entry.content}
-
- {timeAgo(entry.createdAt)}
+
+
+
+ {entry.actor.name}
+ {" "}
+ {entry.content}
+ {timeAgo(entry.createdAt)}
)
)}
+
- {/* Comment placeholder */}
-
-
-
-
-
- Leave a comment...
-
+ {/* Comment input */}
+
+
+
+
+
+ Leave a comment...
- {/* ---- Right: Properties ---- */}
-
-
- Properties
-
+ {/* ================================================================
+ RIGHT: Properties sidebar
+ ================================================================ */}
+
+
+
+ Properties
+
-
-
-
-
- {statusCfg.label}
-
-
+
+
+
+ {statusCfg.label}
+
-
-
- {priorityCfg.label}
-
+
+
+ {priorityCfg.label}
+
-
- {issue.assignee ? (
- <>
-
- {issue.assignee.name}
- >
- ) : (
- Unassigned
- )}
-
+
+ {issue.assignee ? (
+ <>
+
+ {issue.assignee.name}
+ >
+ ) : (
+ Unassigned
+ )}
+
-
-
-
- {formatDate(issue.dueDate)}
-
-
+
+ {issue.dueDate ? (
+
+ {shortDate(issue.dueDate)}
+
+ ) : (
+ None
+ )}
+
-
-
- {issue.creator.name}
-
+
+
+ {issue.creator.name}
+
+
-
- {formatDate(issue.createdAt)}
-
-
-
- {formatDate(issue.updatedAt)}
-
+
+
+ {shortDate(issue.createdAt)}
+
+
+ {shortDate(issue.updatedAt)}
+
+