+
+ {authorAvatar}
-
-
- {formatShortTimestamp(comment.created_at)}
-
- {onViewTask ? (
+
+
- ) : null}
+ {onViewTask ? (
+
+ ) : null}
+
+
+ {authorName}
+ {authorRole ? (
+ <>
+ ·
+ {authorRole}
+ >
+ ) : null}
+ ·
+
+ {formatShortTimestamp(comment.created_at)}
+
+
{message ? (
-
);
@@ -519,9 +551,8 @@ export default function BoardDetailPage() {
setApprovalsError(null);
setChatError(null);
try {
- const snapshotResult = await getBoardSnapshotApiV1BoardsBoardIdSnapshotGet(
- boardId,
- );
+ const snapshotResult =
+ await getBoardSnapshotApiV1BoardsBoardIdSnapshotGet(boardId);
if (snapshotResult.status !== 200) {
throw new Error("Unable to load board snapshot.");
}
@@ -532,7 +563,8 @@ export default function BoardDetailPage() {
setApprovals((snapshot.approvals ?? []).map(normalizeApproval));
setChatMessages(snapshot.chat_messages ?? []);
} catch (err) {
- const message = err instanceof Error ? err.message : "Something went wrong.";
+ const message =
+ err instanceof Error ? err.message : "Something went wrong.";
setError(message);
setApprovalsError(message);
setChatError(message);
@@ -641,21 +673,23 @@ export default function BoardDetailPage() {
}
if (eventType === "memory" && data) {
try {
- const payload = JSON.parse(data) as { memory?: BoardChatMessage };
+ const payload = JSON.parse(data) as {
+ memory?: BoardChatMessage;
+ };
if (payload.memory?.tags?.includes("chat")) {
setChatMessages((prev) => {
const exists = prev.some(
(item) => item.id === payload.memory?.id,
);
if (exists) return prev;
- const next = [...prev, payload.memory as BoardChatMessage];
- next.sort((a, b) => {
- const aTime = apiDatetimeToMs(a.created_at) ?? 0;
- const bTime = apiDatetimeToMs(b.created_at) ?? 0;
- return aTime - bTime;
- });
- return next;
- });
+ const next = [...prev, payload.memory as BoardChatMessage];
+ next.sort((a, b) => {
+ const aTime = apiDatetimeToMs(a.created_at) ?? 0;
+ const bTime = apiDatetimeToMs(b.created_at) ?? 0;
+ return aTime - bTime;
+ });
+ return next;
+ });
}
} catch {
// ignore malformed
@@ -900,41 +934,53 @@ export default function BoardDetailPage() {
task?: TaskRead;
comment?: TaskCommentRead;
};
- if (payload.comment?.task_id && payload.type === "task.comment") {
+ if (
+ payload.comment?.task_id &&
+ payload.type === "task.comment"
+ ) {
pushLiveFeed(payload.comment);
setComments((prev) => {
- if (selectedTaskIdRef.current !== payload.comment?.task_id) {
+ if (
+ selectedTaskIdRef.current !== payload.comment?.task_id
+ ) {
return prev;
}
- const exists = prev.some((item) => item.id === payload.comment?.id);
+ const exists = prev.some(
+ (item) => item.id === payload.comment?.id,
+ );
if (exists) {
return prev;
- }
- const createdMs = apiDatetimeToMs(payload.comment?.created_at);
- if (prev.length === 0 || createdMs === null) {
- return [...prev, payload.comment as TaskComment];
- }
- const last = prev[prev.length - 1];
- const lastMs = apiDatetimeToMs(last?.created_at);
- if (lastMs !== null && createdMs >= lastMs) {
- return [...prev, payload.comment as TaskComment];
- }
- const next = [...prev, payload.comment as TaskComment];
- next.sort((a, b) => {
- const aTime = apiDatetimeToMs(a.created_at) ?? 0;
- const bTime = apiDatetimeToMs(b.created_at) ?? 0;
- return aTime - bTime;
- });
- return next;
- });
+ }
+ const createdMs = apiDatetimeToMs(
+ payload.comment?.created_at,
+ );
+ if (prev.length === 0 || createdMs === null) {
+ return [...prev, payload.comment as TaskComment];
+ }
+ const last = prev[prev.length - 1];
+ const lastMs = apiDatetimeToMs(last?.created_at);
+ if (lastMs !== null && createdMs >= lastMs) {
+ return [...prev, payload.comment as TaskComment];
+ }
+ const next = [...prev, payload.comment as TaskComment];
+ next.sort((a, b) => {
+ const aTime = apiDatetimeToMs(a.created_at) ?? 0;
+ const bTime = apiDatetimeToMs(b.created_at) ?? 0;
+ return aTime - bTime;
+ });
+ return next;
+ });
} else if (payload.task) {
setTasks((prev) => {
- const index = prev.findIndex((item) => item.id === payload.task?.id);
+ const index = prev.findIndex(
+ (item) => item.id === payload.task?.id,
+ );
if (index === -1) {
const assignee = payload.task?.assigned_agent_id
- ? agentsRef.current.find(
- (agent) => agent.id === payload.task?.assigned_agent_id,
- )?.name ?? null
+ ? (agentsRef.current.find(
+ (agent) =>
+ agent.id === payload.task?.assigned_agent_id,
+ )?.name ?? null)
: null;
const created = normalizeTask({
...payload.task,
@@ -947,9 +993,10 @@ export default function BoardDetailPage() {
const next = [...prev];
const existing = next[index];
const assignee = payload.task?.assigned_agent_id
- ? agentsRef.current.find(
- (agent) => agent.id === payload.task?.assigned_agent_id,
- )?.name ?? null
+ ? (agentsRef.current.find(
+ (agent) =>
+ agent.id === payload.task?.assigned_agent_id,
+ )?.name ?? null)
: null;
const updated = normalizeTask({
...existing,
@@ -1054,7 +1101,9 @@ export default function BoardDetailPage() {
if (payload.agent) {
const normalized = normalizeAgent(payload.agent);
setAgents((prev) => {
- const index = prev.findIndex((item) => item.id === normalized.id);
+ const index = prev.findIndex(
+ (item) => item.id === normalized.id,
+ );
if (index === -1) {
return [normalized, ...prev];
}
@@ -1127,7 +1176,7 @@ export default function BoardDetailPage() {
const created = normalizeTask({
...result.data,
assignee: result.data.assigned_agent_id
- ? assigneeById.get(result.data.assigned_agent_id) ?? null
+ ? (assigneeById.get(result.data.assigned_agent_id) ?? null)
: null,
approvals_count: 0,
approvals_pending_count: 0,
@@ -1136,50 +1185,58 @@ export default function BoardDetailPage() {
setIsDialogOpen(false);
resetForm();
} catch (err) {
- setCreateError(err instanceof Error ? err.message : "Something went wrong.");
+ setCreateError(
+ err instanceof Error ? err.message : "Something went wrong.",
+ );
} finally {
setIsCreating(false);
}
};
- const handleSendChat = useCallback(async (content: string): Promise
=> {
- if (!isSignedIn || !boardId) return false;
- const trimmed = content.trim();
- if (!trimmed) return false;
- setIsChatSending(true);
- setChatError(null);
- try {
- const result = await createBoardMemoryApiV1BoardsBoardIdMemoryPost(boardId, {
- content: trimmed,
- tags: ["chat"],
- });
- if (result.status !== 200) {
- throw new Error("Unable to send message.");
+ const handleSendChat = useCallback(
+ async (content: string): Promise => {
+ if (!isSignedIn || !boardId) return false;
+ const trimmed = content.trim();
+ if (!trimmed) return false;
+ setIsChatSending(true);
+ setChatError(null);
+ try {
+ const result = await createBoardMemoryApiV1BoardsBoardIdMemoryPost(
+ boardId,
+ {
+ content: trimmed,
+ tags: ["chat"],
+ },
+ );
+ if (result.status !== 200) {
+ throw new Error("Unable to send message.");
+ }
+ const created = result.data;
+ if (created.tags?.includes("chat")) {
+ setChatMessages((prev) => {
+ const exists = prev.some((item) => item.id === created.id);
+ if (exists) return prev;
+ const next = [...prev, created];
+ next.sort((a, b) => {
+ const aTime = apiDatetimeToMs(a.created_at) ?? 0;
+ const bTime = apiDatetimeToMs(b.created_at) ?? 0;
+ return aTime - bTime;
+ });
+ return next;
+ });
+ }
+ return true;
+ } catch (err) {
+ setChatError(
+ err instanceof Error ? err.message : "Unable to send message.",
+ );
+ return false;
+ } finally {
+ setIsChatSending(false);
}
- const created = result.data;
- if (created.tags?.includes("chat")) {
- setChatMessages((prev) => {
- const exists = prev.some((item) => item.id === created.id);
- if (exists) return prev;
- const next = [...prev, created];
- next.sort((a, b) => {
- const aTime = apiDatetimeToMs(a.created_at) ?? 0;
- const bTime = apiDatetimeToMs(b.created_at) ?? 0;
- return aTime - bTime;
- });
- return next;
- });
- }
- return true;
- } catch (err) {
- setChatError(
- err instanceof Error ? err.message : "Unable to send message.",
- );
- return false;
- } finally {
- setIsChatSending(false);
- }
- }, [boardId, isSignedIn]);
+ },
+ [boardId, isSignedIn],
+ );
const assigneeById = useMemo(() => {
const map = new Map();
@@ -1307,41 +1364,49 @@ export default function BoardDetailPage() {
});
}, [agents, workingAgentIds]);
- const loadComments = useCallback(async (taskId: string) => {
- if (!isSignedIn || !boardId) return;
- setIsCommentsLoading(true);
- setCommentsError(null);
- try {
- const result =
- await listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet(
- boardId,
- taskId,
+ const loadComments = useCallback(
+ async (taskId: string) => {
+ if (!isSignedIn || !boardId) return;
+ setIsCommentsLoading(true);
+ setCommentsError(null);
+ try {
+ const result =
+ await listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet(
+ boardId,
+ taskId,
+ );
+ if (result.status !== 200) throw new Error("Unable to load comments.");
+ const items = [...(result.data.items ?? [])];
+ items.sort((a, b) => {
+ const aTime = apiDatetimeToMs(a.created_at) ?? 0;
+ const bTime = apiDatetimeToMs(b.created_at) ?? 0;
+ return aTime - bTime;
+ });
+ setComments(items);
+ } catch (err) {
+ setCommentsError(
+ err instanceof Error ? err.message : "Something went wrong.",
);
- if (result.status !== 200) throw new Error("Unable to load comments.");
- const items = [...(result.data.items ?? [])];
- items.sort((a, b) => {
- const aTime = apiDatetimeToMs(a.created_at) ?? 0;
- const bTime = apiDatetimeToMs(b.created_at) ?? 0;
- return aTime - bTime;
- });
- setComments(items);
- } catch (err) {
- setCommentsError(err instanceof Error ? err.message : "Something went wrong.");
- } finally {
- setIsCommentsLoading(false);
- }
- }, [boardId, isSignedIn]);
+ } finally {
+ setIsCommentsLoading(false);
+ }
+ },
+ [boardId, isSignedIn],
+ );
- const openComments = useCallback((task: { id: string }) => {
- setIsChatOpen(false);
- setIsLiveFeedOpen(false);
- const fullTask = tasksRef.current.find((item) => item.id === task.id);
- if (!fullTask) return;
- selectedTaskIdRef.current = fullTask.id;
- setSelectedTask(fullTask);
- setIsDetailOpen(true);
- void loadComments(task.id);
- }, [loadComments]);
+ const openComments = useCallback(
+ (task: { id: string }) => {
+ setIsChatOpen(false);
+ setIsLiveFeedOpen(false);
+ const fullTask = tasksRef.current.find((item) => item.id === task.id);
+ if (!fullTask) return;
+ selectedTaskIdRef.current = fullTask.id;
+ setSelectedTask(fullTask);
+ setIsDetailOpen(true);
+ void loadComments(task.id);
+ },
+ [loadComments],
+ );
const closeComments = () => {
setIsDetailOpen(false);
@@ -1470,20 +1535,24 @@ export default function BoardDetailPage() {
...previous,
...result.data,
assignee: result.data.assigned_agent_id
- ? assigneeById.get(result.data.assigned_agent_id) ?? null
+ ? (assigneeById.get(result.data.assigned_agent_id) ?? null)
: null,
approvals_count: previous.approvals_count,
approvals_pending_count: previous.approvals_pending_count,
} as TaskCardRead);
setTasks((prev) =>
- prev.map((task) => (task.id === updated.id ? { ...task, ...updated } : task)),
+ prev.map((task) =>
+ task.id === updated.id ? { ...task, ...updated } : task,
+ ),
);
setSelectedTask(updated);
if (closeOnSuccess) {
setIsEditDialogOpen(false);
}
} catch (err) {
- setSaveTaskError(err instanceof Error ? err.message : "Something went wrong.");
+ setSaveTaskError(
+ err instanceof Error ? err.message : "Something went wrong.",
+ );
} finally {
setIsSavingTask(false);
}
@@ -1522,69 +1591,76 @@ export default function BoardDetailPage() {
}
};
- const handleTaskMove = useCallback(async (taskId: string, status: TaskStatus) => {
- if (!isSignedIn || !boardId) return;
- const currentTask = tasksRef.current.find((task) => task.id === taskId);
- if (!currentTask || currentTask.status === status) return;
- if (currentTask.is_blocked && status !== "inbox") {
- setError("Task is blocked by incomplete dependencies.");
- return;
- }
- const previousTasks = tasksRef.current;
- setTasks((prev) =>
- prev.map((task) =>
- task.id === taskId
- ? {
- ...task,
- status,
- assigned_agent_id:
- status === "inbox" ? null : task.assigned_agent_id,
- assignee: status === "inbox" ? null : task.assignee,
- }
- : task,
- ),
- );
- try {
- const result = await updateTaskApiV1BoardsBoardIdTasksTaskIdPatch(
- boardId,
- taskId,
- { status },
- );
- if (result.status === 409) {
- const blockedIds = result.data.detail.blocked_by_task_ids ?? [];
- const blockedTitles = blockedIds
- .map((id) => taskTitleById.get(id) ?? id)
- .join(", ");
- throw new Error(
- blockedTitles
- ? `${result.data.detail.message} Blocked by: ${blockedTitles}`
- : result.data.detail.message,
- );
+ const handleTaskMove = useCallback(
+ async (taskId: string, status: TaskStatus) => {
+ if (!isSignedIn || !boardId) return;
+ const currentTask = tasksRef.current.find((task) => task.id === taskId);
+ if (!currentTask || currentTask.status === status) return;
+ if (currentTask.is_blocked && status !== "inbox") {
+ setError("Task is blocked by incomplete dependencies.");
+ return;
}
- if (result.status === 422) {
- throw new Error(
- result.data.detail?.[0]?.msg ?? "Validation error while moving task.",
- );
- }
- const assignee = result.data.assigned_agent_id
- ? agentsRef.current.find((agent) => agent.id === result.data.assigned_agent_id)
- ?.name ?? null
- : null;
- const updated = normalizeTask({
- ...currentTask,
- ...result.data,
- assignee,
- approvals_count: currentTask.approvals_count,
- approvals_pending_count: currentTask.approvals_pending_count,
- } as TaskCardRead);
+ const previousTasks = tasksRef.current;
setTasks((prev) =>
- prev.map((task) => (task.id === updated.id ? { ...task, ...updated } : task)),
+ prev.map((task) =>
+ task.id === taskId
+ ? {
+ ...task,
+ status,
+ assigned_agent_id:
+ status === "inbox" ? null : task.assigned_agent_id,
+ assignee: status === "inbox" ? null : task.assignee,
+ }
+ : task,
+ ),
);
- } catch (err) {
- setTasks(previousTasks);
- setError(err instanceof Error ? err.message : "Unable to move task.");
- }
- }, [boardId, isSignedIn, taskTitleById]);
+ try {
+ const result = await updateTaskApiV1BoardsBoardIdTasksTaskIdPatch(
+ boardId,
+ taskId,
+ { status },
+ );
+ if (result.status === 409) {
+ const blockedIds = result.data.detail.blocked_by_task_ids ?? [];
+ const blockedTitles = blockedIds
+ .map((id) => taskTitleById.get(id) ?? id)
+ .join(", ");
+ throw new Error(
+ blockedTitles
+ ? `${result.data.detail.message} Blocked by: ${blockedTitles}`
+ : result.data.detail.message,
+ );
+ }
+ if (result.status === 422) {
+ throw new Error(
+ result.data.detail?.[0]?.msg ??
+ "Validation error while moving task.",
+ );
+ }
+ const assignee = result.data.assigned_agent_id
+ ? (agentsRef.current.find(
+ (agent) => agent.id === result.data.assigned_agent_id,
+ )?.name ?? null)
+ : null;
+ const updated = normalizeTask({
+ ...currentTask,
+ ...result.data,
+ assignee,
+ approvals_count: currentTask.approvals_count,
+ approvals_pending_count: currentTask.approvals_pending_count,
+ } as TaskCardRead);
+ setTasks((prev) =>
+ prev.map((task) =>
+ task.id === updated.id ? { ...task, ...updated } : task,
+ ),
+ );
+ } catch (err) {
+ setTasks(previousTasks);
+ setError(err instanceof Error ? err.message : "Unable to move task.");
+ }
+ },
+ [boardId, isSignedIn, taskTitleById],
+ );
const agentInitials = (agent: Agent) =>
agent.name
@@ -1608,7 +1684,8 @@ export default function BoardDetailPage() {
if (agent.is_board_lead) return "⚙️";
let emojiValue: string | null = null;
if (agent.identity_profile && typeof agent.identity_profile === "object") {
- const rawEmoji = (agent.identity_profile as Record).emoji;
+ const rawEmoji = (agent.identity_profile as Record)
+ .emoji;
emojiValue = typeof rawEmoji === "string" ? rawEmoji : null;
}
const emoji = resolveEmoji(emojiValue);
@@ -1683,16 +1760,11 @@ export default function BoardDetailPage() {
value
.split(".")
.map((part) =>
- part
- .replace(/_/g, " ")
- .replace(/\b\w/g, (char) => char.toUpperCase())
+ part.replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase()),
)
.join(" · ");
- const approvalPayloadValue = (
- payload: Approval["payload"],
- key: string,
- ) => {
+ const approvalPayloadValue = (payload: Approval["payload"], key: string) => {
if (!payload || typeof payload !== "object") return null;
const value = (payload as Record)[key];
if (typeof value === "string" || typeof value === "number") {
@@ -2001,7 +2073,8 @@ export default function BoardDetailPage() {
{task.approvals_pending_count ? (
- Approval needed · {task.approvals_pending_count}
+ Approval needed ·{" "}
+ {task.approvals_pending_count}
) : null}
) : null}
-