From e8229cd290e595ad7633dc8c9e837bf5011927f9 Mon Sep 17 00:00:00 2001 From: nyk <93952610+0xNyk@users.noreply.github.com> Date: Wed, 4 Mar 2026 22:26:06 +0700 Subject: [PATCH] feat: add URL-based task deep links and agent task links --- src/components/panels/agent-detail-tabs.tsx | 6 ++- src/components/panels/task-board-panel.tsx | 60 +++++++++++++++++++-- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/components/panels/agent-detail-tabs.tsx b/src/components/panels/agent-detail-tabs.tsx index 1714fc9..ea42600 100644 --- a/src/components/panels/agent-detail-tabs.tsx +++ b/src/components/panels/agent-detail-tabs.tsx @@ -1,6 +1,7 @@ 'use client' import { useState, useEffect } from 'react' +import Link from 'next/link' interface Agent { id: number @@ -665,7 +666,10 @@ export function TasksTab({ agent }: { agent: Agent }) {
-
{task.title}
+ + {task.title} + +
Task #{task.id}
{task.description && (

{task.description}

)} diff --git a/src/components/panels/task-board-panel.tsx b/src/components/panels/task-board-panel.tsx index 355b6ce..1dda867 100644 --- a/src/components/panels/task-board-panel.tsx +++ b/src/components/panels/task-board-panel.tsx @@ -1,6 +1,7 @@ 'use client' import { useState, useEffect, useCallback, useRef } from 'react' +import { usePathname, useRouter, useSearchParams } from 'next/navigation' import { useMissionControl } from '@/store' import { useSmartPoll } from '@/lib/use-smart-poll' import { useFocusTrap } from '@/lib/use-focus-trap' @@ -67,6 +68,9 @@ const priorityColors: Record = { export function TaskBoardPanel() { const { tasks: storeTasks, setTasks: storeSetTasks, selectedTask, setSelectedTask } = useMissionControl() + const router = useRouter() + const pathname = usePathname() + const searchParams = useSearchParams() const [agents, setAgents] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) @@ -75,6 +79,23 @@ export function TaskBoardPanel() { const [showCreateModal, setShowCreateModal] = useState(false) const [editingTask, setEditingTask] = useState(null) const dragCounter = useRef(0) + const selectedTaskIdFromUrl = Number.parseInt(searchParams.get('taskId') || '', 10) + + const updateTaskUrl = useCallback((taskId: number | null, mode: 'push' | 'replace' = 'push') => { + const params = new URLSearchParams(searchParams.toString()) + if (typeof taskId === 'number' && Number.isFinite(taskId)) { + params.set('taskId', String(taskId)) + } else { + params.delete('taskId') + } + const query = params.toString() + const href = query ? `${pathname}?${query}` : pathname + if (mode === 'replace') { + router.replace(href) + return + } + router.push(href) + }, [pathname, router, searchParams]) // Augment store tasks with aegisApproved flag (computed, not stored) const tasks: Task[] = storeTasks.map(t => ({ @@ -136,6 +157,26 @@ export function TaskBoardPanel() { fetchData() }, [fetchData]) + useEffect(() => { + if (!Number.isFinite(selectedTaskIdFromUrl)) { + if (selectedTask) setSelectedTask(null) + return + } + + const match = tasks.find((task) => task.id === selectedTaskIdFromUrl) + if (match) { + if (selectedTask?.id !== match.id) { + setSelectedTask(match) + } + return + } + + if (!loading) { + setError(`Task #${selectedTaskIdFromUrl} not found in current workspace`) + setSelectedTask(null) + } + }, [loading, selectedTask, selectedTaskIdFromUrl, setSelectedTask, tasks]) + // Poll as SSE fallback — pauses when SSE is delivering events useSmartPoll(fetchData, 30000, { pauseWhenSseConnected: true }) @@ -342,8 +383,17 @@ export function TaskBoardPanel() { tabIndex={0} aria-label={`${task.title}, ${task.priority} priority, ${task.status}`} onDragStart={(e) => handleDragStart(e, task)} - onClick={() => setSelectedTask(task)} - onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setSelectedTask(task) } }} + onClick={() => { + setSelectedTask(task) + updateTaskUrl(task.id) + }} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + setSelectedTask(task) + updateTaskUrl(task.id) + } + }} className={`bg-surface-1 rounded-lg p-3 cursor-pointer hover:bg-surface-2 transition-smooth border-l-4 ${priorityColors[task.priority]} ${ draggedTask?.id === task.id ? 'opacity-50' : '' }`} @@ -440,11 +490,15 @@ export function TaskBoardPanel() { setSelectedTask(null)} + onClose={() => { + setSelectedTask(null) + updateTaskUrl(null) + }} onUpdate={fetchData} onEdit={(taskToEdit) => { setEditingTask(taskToEdit) setSelectedTask(null) + updateTaskUrl(null, 'replace') }} /> )}