feat(tasks-ui): one task window everywhere, cards are their own detail entry
Round-three review feedback: - NO PER-CARD DETAIL BUTTON: the ellipsis control is gone from graph cards and tree rows — the card IS the affordance, and clicking it opens the detail window (the transcript jump moved inside as the primary button). Fold aggregates keep click-to-expand. - ONE TASK WINDOW: every task surface (board row, agent node task line, the node detail list, and creating a task) now opens the same non-fullscreen DRAGGABLE window (TaskWindow.tsx + its TaskPopover shell): markdown description by default, 编辑 switches the same card to multi-line editing, plus owner reassignment, complete/reopen and a two-step delete. The board lost its overflow menu and its Modal entirely; create uses the same window in create mode. - REUSE: TaskPopover (4 call sites), MultilineField (the plugin's own multi-line input — the host primitive set has none), OwnerPicker, TaskCreateButton, and the shared glyph/TaskLine helpers across both modes. - 3 new copy keys across zh/en/ja and the 18 third-language dictionaries. Tests: the node-click spec now asserts the detail window (and that the jump button inside it navigates), a new spec pins 'no nested control inside a card', and two more cover the shared window: markdown-first view then multi-line edit posting an 'edit' with the current revision, and immediate owner reassignment. Full suite 1406 passed / 133 files.
This commit is contained in:
@@ -24,8 +24,7 @@ import {
|
||||
} from 'react'
|
||||
import clsx from 'clsx'
|
||||
import {
|
||||
Button, IconChecklistOutline14, IconEllipsisOutline16, IconFullscreenOutline16, IconTreeCorner8x10,
|
||||
StateDot,
|
||||
Button, IconChecklistOutline14, IconFullscreenOutline16, IconTreeCorner8x10, StateDot,
|
||||
} from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type { TasksAgentNode, TasksFoldNode, TasksNode, TasksWorkflowNode } from './tasks-model.ts'
|
||||
import { tasksEdges } from './tasks-model.ts'
|
||||
@@ -63,9 +62,10 @@ export interface TasksGraphProps {
|
||||
folded: boolean
|
||||
/** Re-fit trigger: the topology root id (a reroot re-centers). */
|
||||
rootId: string | undefined
|
||||
onActivate(node: TasksAgentNode): void
|
||||
onNodeInfo(node: TasksAgentNode, anchor: HTMLElement): void
|
||||
onWorkflowInfo(node: TasksWorkflowNode, anchor: HTMLElement): void
|
||||
/** Open the shared task window for one task id. */
|
||||
onOpenTask(taskId: string, anchor: HTMLElement): void
|
||||
onToggleFold(): void
|
||||
mode: 'graph' | 'tree'
|
||||
onModeChange(mode: 'graph' | 'tree'): void
|
||||
@@ -112,7 +112,7 @@ export function FoldToggleButton(props: { folded: boolean; onToggleFold(): void
|
||||
}
|
||||
|
||||
export function TasksGraph(props: TasksGraphProps): ReactNode {
|
||||
const { nodes, folded, rootId, onActivate, onNodeInfo, onWorkflowInfo, onToggleFold, mode, onModeChange } = props
|
||||
const { nodes, folded, rootId, onNodeInfo, onWorkflowInfo, onOpenTask, onToggleFold, mode, onModeChange } = props
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const [tf, setTf] = useState({ x: 0, y: 0, k: 1 })
|
||||
const [dragging, setDragging] = useState(false)
|
||||
@@ -335,7 +335,7 @@ export function TasksGraph(props: TasksGraphProps): ReactNode {
|
||||
if (node.kind === 'workflow') {
|
||||
return renderWorkflowNode(node, style, onWorkflowInfo, clickAllowed)
|
||||
}
|
||||
return renderAgentNode(node, style, onActivate, onNodeInfo, clickAllowed)
|
||||
return renderAgentNode(node, style, onNodeInfo, onOpenTask, clickAllowed)
|
||||
})}
|
||||
</div>
|
||||
<div className={css.controls} data-graph-controls>
|
||||
@@ -381,8 +381,8 @@ export function TasksGraph(props: TasksGraphProps): ReactNode {
|
||||
function renderAgentNode(
|
||||
node: TasksAgentNode,
|
||||
style: { left: number; top: number; width: number; minHeight: number },
|
||||
onActivate: (node: TasksAgentNode) => void,
|
||||
onNodeInfo: (node: TasksAgentNode, anchor: HTMLElement) => void,
|
||||
onOpenTask: (taskId: string, anchor: HTMLElement) => void,
|
||||
clickAllowed: () => boolean,
|
||||
): ReactNode {
|
||||
return (
|
||||
@@ -402,9 +402,9 @@ function renderAgentNode(
|
||||
node.state === 'error' && css.nodeError,
|
||||
)}
|
||||
style={style}
|
||||
onClick={() => {
|
||||
onClick={(event) => {
|
||||
if (!clickAllowed()) return
|
||||
onActivate(node)
|
||||
onNodeInfo(node, event.currentTarget)
|
||||
}}
|
||||
>
|
||||
<span className={css.nodeHeader}>
|
||||
@@ -414,19 +414,7 @@ function renderAgentNode(
|
||||
</span>
|
||||
<span className={css.nodeMeta} title={agentMeta(node)}>{agentMeta(node)}</span>
|
||||
{node.state === 'running' && <LiveLine live={node.live} />}
|
||||
<TaskLine tasks={node.tasks} />
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className={css.nodeInfo}
|
||||
icon={<IconEllipsisOutline16 size={12} />}
|
||||
aria-label={t('tasksNodeDetail')}
|
||||
title={t('tasksNodeDetail')}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
onNodeInfo(node, event.currentTarget)
|
||||
}}
|
||||
/>
|
||||
<TaskLine tasks={node.tasks} onOpenTask={onOpenTask} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user