@dxos/react-ui-task 0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/dist/lib/index.mjs +2024 -572
  2. package/dist/lib/index.mjs.map +1 -1
  3. package/dist/lib/translations.mjs +31 -1
  4. package/dist/lib/translations.mjs.map +1 -1
  5. package/dist/types/src/components/TaskHistory/TaskHistory.d.ts +13 -0
  6. package/dist/types/src/components/TaskHistory/TaskHistory.d.ts.map +1 -0
  7. package/dist/types/src/components/TaskHistory/TaskHistory.stories.d.ts +68 -0
  8. package/dist/types/src/components/TaskHistory/TaskHistory.stories.d.ts.map +1 -0
  9. package/dist/types/src/components/TaskHistory/index.d.ts +2 -0
  10. package/dist/types/src/components/TaskHistory/index.d.ts.map +1 -0
  11. package/dist/types/src/components/TaskList/TaskDescription.d.ts +20 -0
  12. package/dist/types/src/components/TaskList/TaskDescription.d.ts.map +1 -0
  13. package/dist/types/src/components/TaskList/TaskList.d.ts +122 -57
  14. package/dist/types/src/components/TaskList/TaskList.d.ts.map +1 -1
  15. package/dist/types/src/components/TaskList/TaskList.stories.d.ts +163 -5
  16. package/dist/types/src/components/TaskList/TaskList.stories.d.ts.map +1 -1
  17. package/dist/types/src/components/TaskList/TaskListContext.d.ts +53 -0
  18. package/dist/types/src/components/TaskList/TaskListContext.d.ts.map +1 -0
  19. package/dist/types/src/components/TaskList/TaskListEditor.d.ts +42 -0
  20. package/dist/types/src/components/TaskList/TaskListEditor.d.ts.map +1 -0
  21. package/dist/types/src/components/TaskList/TaskRowCells.d.ts +86 -0
  22. package/dist/types/src/components/TaskList/TaskRowCells.d.ts.map +1 -0
  23. package/dist/types/src/components/TaskList/TaskTreeNode.d.ts +65 -0
  24. package/dist/types/src/components/TaskList/TaskTreeNode.d.ts.map +1 -0
  25. package/dist/types/src/components/TaskList/anchor-hover.d.ts +21 -0
  26. package/dist/types/src/components/TaskList/anchor-hover.d.ts.map +1 -0
  27. package/dist/types/src/components/TaskList/anchor-hover.test.d.ts +2 -0
  28. package/dist/types/src/components/TaskList/anchor-hover.test.d.ts.map +1 -0
  29. package/dist/types/src/components/TaskList/hierarchy.d.ts +13 -26
  30. package/dist/types/src/components/TaskList/hierarchy.d.ts.map +1 -1
  31. package/dist/types/src/components/TaskList/index.d.ts +4 -2
  32. package/dist/types/src/components/TaskList/index.d.ts.map +1 -1
  33. package/dist/types/src/components/TaskList/tracks.d.ts +16 -0
  34. package/dist/types/src/components/TaskList/tracks.d.ts.map +1 -0
  35. package/dist/types/src/components/TaskList/tree-model.d.ts +107 -0
  36. package/dist/types/src/components/TaskList/tree-model.d.ts.map +1 -0
  37. package/dist/types/src/components/TaskList/tree-model.test.d.ts +2 -0
  38. package/dist/types/src/components/TaskList/tree-model.test.d.ts.map +1 -0
  39. package/dist/types/src/components/TaskList/usePreviewAnchor.d.ts +21 -0
  40. package/dist/types/src/components/TaskList/usePreviewAnchor.d.ts.map +1 -0
  41. package/dist/types/src/components/TaskProperties/TaskProperties.d.ts +23 -0
  42. package/dist/types/src/components/TaskProperties/TaskProperties.d.ts.map +1 -0
  43. package/dist/types/src/components/TaskProperties/TaskProperties.stories.d.ts +76 -0
  44. package/dist/types/src/components/TaskProperties/TaskProperties.stories.d.ts.map +1 -0
  45. package/dist/types/src/components/TaskProperties/index.d.ts +2 -0
  46. package/dist/types/src/components/TaskProperties/index.d.ts.map +1 -0
  47. package/dist/types/src/components/TaskQuestion/TaskQuestion.d.ts +22 -0
  48. package/dist/types/src/components/TaskQuestion/TaskQuestion.d.ts.map +1 -0
  49. package/dist/types/src/components/TaskQuestion/index.d.ts +2 -0
  50. package/dist/types/src/components/TaskQuestion/index.d.ts.map +1 -0
  51. package/dist/types/src/components/task-grid.d.ts +18 -0
  52. package/dist/types/src/components/task-grid.d.ts.map +1 -0
  53. package/dist/types/src/hooks/index.d.ts +2 -0
  54. package/dist/types/src/hooks/index.d.ts.map +1 -0
  55. package/dist/types/src/hooks/useAssigneeDisplay.d.ts +8 -0
  56. package/dist/types/src/hooks/useAssigneeDisplay.d.ts.map +1 -0
  57. package/dist/types/src/index.d.ts +6 -1
  58. package/dist/types/src/index.d.ts.map +1 -1
  59. package/dist/types/src/translations.d.ts +31 -1
  60. package/dist/types/src/translations.d.ts.map +1 -1
  61. package/dist/types/src/util/assignee.d.ts +34 -0
  62. package/dist/types/src/util/assignee.d.ts.map +1 -0
  63. package/dist/types/src/util/assignee.test.d.ts +2 -0
  64. package/dist/types/src/util/assignee.test.d.ts.map +1 -0
  65. package/dist/types/src/util/format-date.d.ts +18 -0
  66. package/dist/types/src/util/format-date.d.ts.map +1 -0
  67. package/dist/types/src/util/format-date.test.d.ts +2 -0
  68. package/dist/types/src/util/format-date.test.d.ts.map +1 -0
  69. package/dist/types/src/util/index.d.ts +3 -0
  70. package/dist/types/src/util/index.d.ts.map +1 -0
  71. package/dist/types/src/util/status-icons.d.ts +28 -0
  72. package/dist/types/src/util/status-icons.d.ts.map +1 -0
  73. package/dist/types/tsconfig.tsbuildinfo +1 -1
  74. package/package.json +27 -20
  75. package/src/components/TaskHistory/TaskHistory.stories.tsx +103 -0
  76. package/src/components/TaskHistory/TaskHistory.tsx +169 -0
  77. package/src/components/TaskHistory/index.ts +5 -0
  78. package/src/components/TaskList/TaskDescription.tsx +39 -0
  79. package/src/components/TaskList/TaskList.stories.tsx +1882 -181
  80. package/src/components/TaskList/TaskList.tsx +670 -789
  81. package/src/components/TaskList/TaskListContext.ts +68 -0
  82. package/src/components/TaskList/TaskListEditor.tsx +510 -0
  83. package/src/components/TaskList/TaskRowCells.tsx +362 -0
  84. package/src/components/TaskList/TaskTreeNode.tsx +387 -0
  85. package/src/components/TaskList/anchor-hover.test.ts +84 -0
  86. package/src/components/TaskList/anchor-hover.ts +129 -0
  87. package/src/components/TaskList/hierarchy.test.ts +92 -51
  88. package/src/components/TaskList/hierarchy.ts +45 -63
  89. package/src/components/TaskList/index.ts +5 -2
  90. package/src/components/TaskList/tracks.ts +19 -0
  91. package/src/components/TaskList/tree-model.test.ts +233 -0
  92. package/src/components/TaskList/tree-model.ts +238 -0
  93. package/src/components/TaskList/usePreviewAnchor.ts +85 -0
  94. package/src/components/TaskProperties/TaskProperties.stories.tsx +121 -0
  95. package/src/components/TaskProperties/TaskProperties.tsx +281 -0
  96. package/src/components/TaskProperties/index.ts +5 -0
  97. package/src/components/TaskQuestion/TaskQuestion.tsx +223 -0
  98. package/src/components/TaskQuestion/index.ts +5 -0
  99. package/src/components/task-grid.ts +23 -0
  100. package/src/hooks/index.ts +5 -0
  101. package/src/hooks/useAssigneeDisplay.ts +26 -0
  102. package/src/index.ts +6 -1
  103. package/src/translations.ts +33 -3
  104. package/src/util/assignee.test.ts +73 -0
  105. package/src/util/assignee.ts +69 -0
  106. package/src/util/format-date.test.ts +36 -0
  107. package/src/util/format-date.ts +43 -0
  108. package/src/util/index.ts +6 -0
  109. package/src/util/status-icons.ts +54 -0
  110. package/dist/types/src/components/TaskList/dnd.d.ts +0 -27
  111. package/dist/types/src/components/TaskList/dnd.d.ts.map +0 -1
  112. package/src/components/TaskList/dnd.ts +0 -46
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/components/TaskList/hierarchy.ts","../../src/components/TaskList/dnd.ts","../../src/components/TaskList/TaskList.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport { type Task, TaskSet } from '@dxos/types';\n\n/**\n * The tree the list renders, and where a gesture puts a task in it. Kept out of the components\n * because this is the part a regression would break: a drop cannot be driven synthetically\n * (pragmatic-drag-and-drop uses native HTML5 drag events), so the placement calculation is tested\n * directly instead.\n *\n * `TaskSet.tasks` is flat and holds every task; `Task.parentTask` is the hierarchy. The array is\n * NOT a pre-order traversal and is deliberately not made into one: array order decides SIBLING\n * order only, and the walk supplies the rest. A parent therefore moves without dragging its\n * subtree's entries along, and a peer reordering one branch cannot corrupt another.\n */\n\n/** One rendered row: the task, its depth, and where it sits among its siblings. */\nexport type TaskTreeRow = {\n task: Task.Task;\n /** 1 for a root task, incrementing per level. */\n level: number;\n /** Whether the task has sub-tasks — a leaf renders its ordinal where a branch renders a toggle. */\n branch: boolean;\n /** 1-based position among its siblings, and how many siblings there are (`aria-posinset`/`setsize`). */\n position: number;\n setSize: number;\n /** Ids from the root down to (and excluding) the task, so a row knows what to collapse into. */\n ancestors: string[];\n};\n\n/**\n * Walks the set's tree in sibling order, omitting the descendants of any collapsed task.\n * Cycle-safe: a malformed `parentTask` loop is visited once and then skipped, so a corrupt set\n * renders short rather than hanging the list.\n */\nexport const walkTaskTree = (tasks: readonly Task.Task[], collapsed?: ReadonlySet<string>): TaskTreeRow[] => {\n const rows: TaskTreeRow[] = [];\n const seen = new Set<string>();\n\n const visit = (siblings: Task.Task[], level: number, ancestors: string[]): void => {\n siblings.forEach((task, index) => {\n if (seen.has(task.id)) {\n return;\n }\n seen.add(task.id);\n const children = TaskSet.subTasks(tasks, task);\n rows.push({\n task,\n level,\n branch: children.length > 0,\n position: index + 1,\n setSize: siblings.length,\n ancestors,\n });\n if (children.length > 0 && !collapsed?.has(task.id)) {\n visit(children, level + 1, [...ancestors, task.id]);\n }\n });\n };\n\n visit(TaskSet.rootTasks(tasks), 1, []);\n return rows;\n};\n\n/** Every task transitively under `task`, including `task` itself. Cycle-safe. */\nexport const subtreeIds = (tasks: readonly Task.Task[], task: Task.Task): Set<string> => {\n const ids = new Set<string>();\n const visit = (current: Task.Task): void => {\n if (ids.has(current.id)) {\n return;\n }\n ids.add(current.id);\n for (const child of TaskSet.subTasks(tasks, current)) {\n visit(child);\n }\n };\n visit(task);\n return ids;\n};\n\n/** The three tree-item intents, matching `@atlaskit/pragmatic-drag-and-drop-hitbox`. */\nexport type TaskDropIntent = 'reorder-above' | 'reorder-below' | 'make-child';\n\n/**\n * Where a task lands, in the two terms `MoveTask` takes. `parentTask` is `null` for a root task\n * (matching the verb's convention) and `before` is undefined when the task goes last — which is\n * also last among its new siblings, since nothing after it in the array shares the parent.\n */\nexport type TaskPlacement = {\n parentTask: Task.Task | null;\n before?: Task.Task;\n};\n\n/**\n * Resolves a drop into a single `MoveTask` call, or `undefined` when the gesture is rejected.\n *\n * Rejected rather than silently ignored, so the caller can say no with the cursor:\n * - onto itself, or into its own subtree (the cycle `UpdateTask` rejects);\n * - onto a task from another set — a membership transfer, not a reorder.\n */\nexport const resolveTaskPlacement = ({\n tasks,\n source,\n target,\n intent,\n}: {\n tasks: readonly Task.Task[];\n source: Task.Task;\n target: Task.Task;\n intent: TaskDropIntent;\n}): TaskPlacement | undefined => {\n const present = new Set(tasks.map((task) => task.id));\n if (!present.has(source.id) || !present.has(target.id)) {\n return undefined;\n }\n if (subtreeIds(tasks, source).has(target.id)) {\n return undefined;\n }\n\n if (intent === 'make-child') {\n // Appended as the last child (the navtree's convention): with no anchor the task goes to the\n // end of the array, where nothing following it can be an earlier sibling.\n return { parentTask: target, before: undefined };\n }\n\n const parentId = TaskSet.parentTaskId(target);\n const parent = parentId === undefined ? null : (tasks.find((task) => task.id === parentId) ?? null);\n const siblings = parent === null ? TaskSet.rootTasks(tasks) : TaskSet.subTasks(tasks, parent);\n const index = siblings.findIndex((task) => task.id === target.id);\n\n if (intent === 'reorder-above') {\n return { parentTask: parent, before: target };\n }\n\n // Below the target is before the sibling that follows it; with none, the task goes last.\n const next = siblings.slice(index + 1).find((task) => task.id !== source.id);\n return { parentTask: parent, before: next };\n};\n\n/** A task's siblings (in sibling order) and its index among them. */\nconst siblingsOf = (\n tasks: readonly Task.Task[],\n task: Task.Task,\n): { parent: Task.Task | null; siblings: Task.Task[]; index: number } => {\n const parentId = TaskSet.parentTaskId(task);\n const parent = parentId === undefined ? null : (tasks.find((candidate) => candidate.id === parentId) ?? null);\n const siblings = parent === null ? TaskSet.rootTasks(tasks) : TaskSet.subTasks(tasks, parent);\n return { parent, siblings, index: siblings.findIndex((candidate) => candidate.id === task.id) };\n};\n\n/**\n * Indent: the task becomes the last child of its previous sibling — the outliner's `Tab`. Undefined\n * when it is the first among its siblings and so has nothing to indent under.\n */\nexport const resolveIndent = (tasks: readonly Task.Task[], task: Task.Task): TaskPlacement | undefined => {\n const { siblings, index } = siblingsOf(tasks, task);\n const previous = index > 0 ? siblings[index - 1] : undefined;\n return previous ? { parentTask: previous, before: undefined } : undefined;\n};\n\n/**\n * Outdent: the task becomes the next sibling of its parent — the outliner's `Shift-Tab`. Undefined\n * for a task already at the root.\n */\nexport const resolveOutdent = (tasks: readonly Task.Task[], task: Task.Task): TaskPlacement | undefined => {\n const { parent } = siblingsOf(tasks, task);\n if (!parent) {\n return undefined;\n }\n const { parent: grandparent, siblings, index } = siblingsOf(tasks, parent);\n return { parentTask: grandparent, before: siblings[index + 1] };\n};\n\n/**\n * Move within the current parent — `Alt-ArrowUp` / `Alt-ArrowDown`. Undefined at either end, so the\n * key is a no-op rather than silently jumping the task out of its branch.\n */\nexport const resolveNudge = (\n tasks: readonly Task.Task[],\n task: Task.Task,\n direction: 'up' | 'down',\n): TaskPlacement | undefined => {\n const { parent, siblings, index } = siblingsOf(tasks, task);\n if (direction === 'up') {\n return index > 0 ? { parentTask: parent, before: siblings[index - 1] } : undefined;\n }\n // Past the next sibling: anchored on the one after it, or unanchored to land last.\n return index < siblings.length - 1 ? { parentTask: parent, before: siblings[index + 2] } : undefined;\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { type Instruction, type ItemMode } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';\n\nimport { type TaskDropIntent } from './hierarchy';\n\n/**\n * Drag payload for a task row. Tagged so a drop target can tell a task from any other draggable\n * sharing the page (the navtree, a mosaic tile) rather than reading a bare `id` off it.\n */\nexport const TASK_DRAG_TYPE = 'dxos/task';\n\nexport type TaskDragData = {\n type: typeof TASK_DRAG_TYPE;\n taskId: string;\n};\n\nexport const isTaskDragData = (data: Record<string | symbol, unknown>): data is TaskDragData =>\n data.type === TASK_DRAG_TYPE && typeof data.taskId === 'string';\n\n/** Indentation per level, matching the navtree so the two trees step identically. */\nexport const INDENT_PER_LEVEL = 8;\n\n/**\n * The hitbox's item mode, which decides where its bands sit: the last row of a group gets a\n * shallower `reorder-below` band (there is nothing beneath it to reorder against), and an expanded\n * branch gets none at all — below it is its own first child.\n */\nexport const itemMode = ({ branch, open, last }: { branch: boolean; open: boolean; last: boolean }): ItemMode =>\n branch && open ? 'expanded' : last ? 'last-in-group' : 'standard';\n\n/** The drop intent an instruction expresses, or undefined when it carries none (blocked). */\nexport const dropIntent = (instruction: Instruction | null): TaskDropIntent | undefined => {\n switch (instruction?.type) {\n case 'reorder-above':\n case 'reorder-below':\n case 'make-child':\n return instruction.type;\n default:\n // `reparent` (dropping past the end of a branch onto an outer level) is not offered: the list\n // has no gutter to aim at, and `make-child` plus a nudge expresses the same move.\n return undefined;\n }\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport {\n type Instruction,\n attachInstruction,\n extractInstruction,\n} from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';\nimport { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';\nimport { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';\nimport { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview';\nimport { useComposedRefs } from '@radix-ui/react-compose-refs';\nimport { createContext } from '@radix-ui/react-context';\nimport React, {\n type CSSProperties,\n Fragment,\n type KeyboardEvent,\n type PropsWithChildren,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\n\nimport { useObject } from '@dxos/echo-react';\nimport {\n Icon,\n IconButton,\n IconButtonProps,\n Input,\n Tag,\n composable,\n composableProps,\n useTranslation,\n} from '@dxos/react-ui';\nimport { Listbox, TreeDropIndicator, TreeItemToggle, paddingIndentation, useListDisclosure } from '@dxos/react-ui-list';\nimport { MarkdownView } from '@dxos/react-ui-markdown';\nimport { type Actor, type Task, TaskSet } from '@dxos/types';\nimport { mx } from '@dxos/ui-theme';\nimport { type ComposableProps } from '@dxos/ui-types';\n\nimport { translationKey } from '#translations';\n\nimport { INDENT_PER_LEVEL, TASK_DRAG_TYPE, type TaskDragData, dropIntent, isTaskDragData, itemMode } from './dnd';\nimport {\n type TaskPlacement,\n type TaskTreeRow,\n resolveIndent,\n resolveNudge,\n resolveOutdent,\n resolveTaskPlacement,\n subtreeIds,\n walkTaskTree,\n} from './hierarchy';\n\nconst TASK_LIST_NAME = 'TaskList.Root';\n\n/** Linear-style status groups, most active first. */\nexport const STATUS_ORDER: Task.Status[] = ['started', 'todo', 'done', 'failed', 'cancelled'];\n\n/** Fallback status labels. */\nconst DEFAULT_STATUS_LABELS: Record<Task.Status, string> = {\n started: 'Started',\n todo: 'To Do',\n done: 'Done',\n failed: 'Failed',\n cancelled: 'Cancelled',\n};\n\nexport type TaskPatch = Partial<Pick<Task.Task, 'title' | 'status' | 'priority' | 'estimate' | 'assignee'>>;\n\n//\n// Context — plain Radix context (un-scoped); nesting task lists has no meaning today.\n//\n\ntype TaskListContextValue = {\n tasks: readonly Task.Task[];\n groupByStatus: boolean;\n showGroupLabels: boolean;\n showOrdinals: boolean;\n showDescriptions: boolean;\n hierarchical: boolean;\n /** Whether the leading gutter is rendered at all — it holds the ordinal and the drag handle. */\n showGutter: boolean;\n statusLabel: (status: Task.Status) => string;\n selected?: string;\n /** Whether a branch's sub-tasks are hidden, and the toggle that flips it. */\n isCollapsed: (id: string) => boolean;\n onCollapseToggle: (id: string) => void;\n /** Ids of the task being dragged and its sub-tasks — lifted out of the list for the drag's duration. */\n dragging: ReadonlySet<string>;\n onDraggingChange: (task: Task.Task | undefined) => void;\n onTaskCreate?: (title: string) => void;\n onTaskUpdate?: (task: Task.Task, patch: TaskPatch) => void;\n onTaskDelete?: (task: Task.Task) => void;\n onTaskSelect?: (task: Task.Task) => void;\n onTaskMove?: (task: Task.Task, placement: TaskPlacement) => void;\n};\n\nconst [TaskListProvider, useTaskListContext] = createContext<TaskListContextValue>(TASK_LIST_NAME);\n\n/** Shared empty set, so a list with nothing in flight does not allocate one per render. */\nconst EMPTY_IDS: ReadonlySet<string> = new Set<string>();\n\n//\n// Root — headless context provider. Renders no DOM.\n//\n\ntype TaskListRootProps = PropsWithChildren<{\n tasks: readonly Task.Task[];\n /** Group rows into status sections (Linear order); flat list otherwise. */\n groupByStatus?: boolean;\n /** Render the status heading above each group; grouping order is kept either way. */\n showGroupLabels?: boolean;\n /** Number rows by their position in `tasks` (set order), so tasks can be referenced by ordinal. */\n showOrdinals?: boolean;\n /** Render each task's description under its title; rows grow to fit. Off by default, so a\n * single-line list (e.g. the chat strip) keeps one row per task. */\n showDescriptions?: boolean;\n /** i18n hook for group headings; defaults to English labels. */\n statusLabel?: (status: Task.Status) => string;\n /** Enables `Create`; called with the trimmed title. */\n onTaskCreate?: (title: string) => void;\n /** Enables the done toggle. Every mutation is delegated — the list never writes. */\n onTaskUpdate?: (task: Task.Task, patch: TaskPatch) => void;\n /** Enables the per-row delete affordance. */\n onTaskDelete?: (task: Task.Task) => void;\n /** Row click. Wiring it (or `selected`) makes the list selectable, so the row shows as selected. */\n onTaskSelect?: (task: Task.Task) => void;\n /** Selected task id (controlled); omit to let the list track the last row clicked. */\n selected?: string;\n /**\n * Render the set as the tree it stores (`Task.parentTask`), not as status groups — the two are\n * mutually exclusive, since a tree regrouped by status is no longer a tree.\n */\n hierarchical?: boolean;\n /**\n * Enables restructuring by drag and by keyboard; called with the one move the gesture means.\n * `MoveTask` takes exactly this pair, so a drop is a single mutation rather than a re-parent\n * followed by a reposition.\n */\n onTaskMove?: (task: Task.Task, placement: TaskPlacement) => void;\n /**\n * Ids of the branches whose sub-tasks are hidden (controlled). Collapsed rather than expanded\n * ids, because a branch is open by default: tracking the expanded set would render a task's new\n * first sub-task hidden, the moment adding it made its parent a branch. Per viewer and per list —\n * a collapsed branch is not a property of the work — so this is state, not stored on the object.\n */\n collapsed?: ReadonlySet<string>;\n onCollapsedChange?: (collapsed: ReadonlySet<string>) => void;\n}>;\n\nconst TaskListRoot = ({\n children,\n tasks,\n groupByStatus = true,\n showGroupLabels = true,\n showOrdinals = false,\n showDescriptions = false,\n statusLabel = (status) => DEFAULT_STATUS_LABELS[status],\n onTaskCreate,\n onTaskUpdate,\n onTaskDelete,\n onTaskSelect,\n onTaskMove,\n selected: selectedProp,\n hierarchical = false,\n collapsed,\n onCollapsedChange,\n}: TaskListRootProps) => {\n // Uncontrolled by default: a host that only wants the click callback still gets the selected\n // styling, and one that owns the selection passes `selected`.\n const [selectedState, setSelectedState] = useState<string | undefined>(selectedProp);\n const selected = selectedProp ?? selectedState;\n const selectable = !!onTaskSelect || selectedProp !== undefined;\n const handleValueChange = useCallback(\n (id: string) => {\n setSelectedState(id);\n const task = tasks.find((task) => task.id === id);\n if (task) {\n onTaskSelect?.(task);\n }\n },\n [tasks, onTaskSelect],\n );\n\n // The hook owns the controlled/uncontrolled Set state machine; its trigger/panel ids are not\n // used, because a sub-task is a sibling row in the same grid rather than a region the toggle\n // could point `aria-controls` at — `aria-expanded` on the row carries the disclosure instead.\n const disclosure = useListDisclosure({\n mode: 'multi',\n ...(collapsed !== undefined || onCollapsedChange ? { value: collapsed } : {}),\n defaultValue: new Set<string>(),\n onValueChange: onCollapsedChange,\n });\n const isCollapsed = useCallback((id: string) => disclosure.bind(id).expanded, [disclosure]);\n const onCollapseToggle = useCallback((id: string) => disclosure.bind(id).toggle(), [disclosure]);\n\n // A dragged task leaves the list while it is in flight — it is in the reader's hand, shown in the\n // drag preview — so the rows close up over the gap it came from. Its sub-tasks go with it: they\n // travel with their parent and are part of the same preview.\n const [draggingTask, setDraggingTask] = useState<Task.Task>();\n const dragging = useMemo(() => (draggingTask ? subtreeIds(tasks, draggingTask) : EMPTY_IDS), [tasks, draggingTask]);\n\n return (\n <TaskListProvider\n tasks={tasks}\n groupByStatus={groupByStatus && !hierarchical}\n showGroupLabels={showGroupLabels}\n showOrdinals={showOrdinals}\n showDescriptions={showDescriptions}\n hierarchical={hierarchical}\n // The handle lives in the ordinal's gutter, so a movable list reserves the track even when it\n // shows no numbers.\n showGutter={showOrdinals || !!onTaskMove}\n statusLabel={statusLabel}\n isCollapsed={isCollapsed}\n onCollapseToggle={onCollapseToggle}\n dragging={dragging}\n onDraggingChange={setDraggingTask}\n onTaskCreate={onTaskCreate}\n onTaskUpdate={onTaskUpdate}\n onTaskDelete={onTaskDelete}\n onTaskSelect={onTaskSelect}\n onTaskMove={onTaskMove}\n selected={selected}\n >\n {/* Both roots are headless, so the pair renders no DOM of its own. */}\n <Listbox.Root {...(selectable ? { value: selected, onValueChange: handleValueChange } : {})}>\n {children}\n </Listbox.Root>\n </TaskListProvider>\n );\n};\n\nTaskListRoot.displayName = 'TaskList.Root';\n\n//\n// Viewport — the scrolling region (the listbox's own viewport). `Create` sits outside it, so the\n// add row stays pinned while the rows scroll.\n//\n\ntype TaskListViewportProps = ComposableProps;\n\nconst TaskListViewport = composable<HTMLDivElement>(({ children, ...props }, forwardedRef) => {\n const { className, ...rest } = composableProps(props);\n return (\n <Listbox.Viewport {...rest} classNames={mx('min-w-0 min-h-0', className)} ref={forwardedRef}>\n {children}\n </Listbox.Viewport>\n );\n});\n\nTaskListViewport.displayName = 'TaskList.Viewport';\n\n//\n// Content — the rows, grouped by status when the root says so.\n//\n\n/**\n * The rows and the create row are separate grids (the create row sits outside the scrolling\n * viewport), so their leading gutters — ordinal, then status — are declared once here: only\n * matching templates keep the `+` under the status control and the input under the titles.\n * Tailwind scans for whole class names, hence four literals rather than a composed prefix. The\n * status gutter is `1.5rem` because that is the width of the `density='sm'` icon button it holds;\n * a narrower track makes the button overflow it and align left instead of centring.\n */\nconst GRID_COLS = {\n content: 'grid-cols-[1.5rem_1fr_min-content_min-content_2rem]',\n contentWithOrdinals: 'grid-cols-[2rem_1.5rem_1fr_min-content_min-content_2rem]',\n create: 'grid-cols-[1.5rem_1fr]',\n createWithOrdinals: 'grid-cols-[2rem_1.5rem_1fr]',\n};\n\ntype TaskListContentProps = ComposableProps;\n\nconst TaskListContent = composable<HTMLUListElement>((props, forwardedRef) => {\n const {\n tasks,\n groupByStatus,\n showGroupLabels,\n showOrdinals,\n showDescriptions,\n hierarchical,\n showGutter,\n statusLabel,\n isCollapsed,\n } = useTaskListContext('TaskList.Content');\n // Ordinals follow the set's canonical order, not the display order, so a task keeps its number as\n // it moves between status groups — and, in a tree, as branches collapse around it. Flat by\n // design: an ordinal names a task (\"run 3\"), and a `1.2.1` path renumbers a whole branch every\n // time anything above it changes.\n const ordinals = useMemo(() => new Map(tasks.map((task, index) => [task.id, index + 1])), [tasks]);\n // Collapsed ids are read through the context callback rather than held here, so the walk still\n // re-runs when one flips; the set itself lives in `Root`.\n const collapsed = useMemo(() => new Set(tasks.map((task) => task.id).filter(isCollapsed)), [tasks, isCollapsed]);\n const rows = useMemo(\n () => (hierarchical ? walkTaskTree(tasks, collapsed) : undefined),\n [hierarchical, tasks, collapsed],\n );\n const groups = useMemo(() => {\n if (!groupByStatus) {\n return tasks.length > 0 ? [{ status: undefined, tasks }] : [];\n }\n\n return STATUS_ORDER.map((status) => ({\n status,\n tasks: tasks.filter((task) => (task.status ?? 'todo') === status),\n })).filter((group) => group.tasks.length > 0);\n }, [tasks, groupByStatus]);\n\n return (\n <Listbox.Content\n {...composableProps(props, {\n // Row height lives on the auto rows — an `h-8` on the grid itself would size the whole\n // list to one row and let the rest overflow invisibly. A described row is taller than one\n // line, so the tracks size to content and the cells align to the first line rather than to\n // the middle of a two-line row.\n classNames: mx(\n 'group grid gap-x-1 w-full min-w-0',\n showDescriptions ? 'auto-rows-min items-start' : 'auto-rows-[2rem] items-center',\n showGutter ? GRID_COLS.contentWithOrdinals : GRID_COLS.content,\n ),\n })}\n aria-label='Tasks'\n ref={forwardedRef}\n >\n {rows\n ? rows.map((row) => (\n <TaskListItem\n key={row.task.id}\n task={row.task}\n ordinal={showOrdinals ? ordinals.get(row.task.id) : undefined}\n row={row}\n />\n ))\n : groups.map(({ status, tasks }) => (\n <Fragment key={status ?? 'all'}>\n {status && showGroupLabels && <TaskListGroupLabel>{statusLabel(status)}</TaskListGroupLabel>}\n {tasks.map((task) => (\n <TaskListItem key={task.id} task={task} ordinal={showOrdinals ? ordinals.get(task.id) : undefined} />\n ))}\n </Fragment>\n ))}\n </Listbox.Content>\n );\n});\n\nTaskListContent.displayName = 'TaskList.Content';\n\n//\n// GroupLabel\n//\n\ntype TaskListGroupLabelProps = ComposableProps;\n\nconst TaskListGroupLabel = composable<HTMLDivElement>(({ children, ...props }, forwardedRef) => {\n const { className, ...rest } = composableProps(props);\n return (\n <div\n {...rest}\n className={mx('col-span-full min-h-(--dx-control) flex items-center text-sm text-description', className)}\n ref={forwardedRef}\n >\n <span>{children}</span>\n </div>\n );\n});\n\nTaskListGroupLabel.displayName = 'TaskList.GroupLabel';\n\n//\n// Drag and drop. The mechanics come from `react-ui-list` (the tree-item hitbox, its instructions,\n// and the drop indicator), so a task tree behaves like the navtree; the row itself stays a listbox\n// option rather than being re-expressed as a treegrid row.\n//\n\n/** How long the cursor must rest on a collapsed branch before it opens, so crossing one does not. */\nconst EXPAND_DWELL = 600;\n\n/**\n * Distance from the title cell's own inline start to the title text: the disclosure toggle (`w-6`)\n * plus the cell's `gap-1`. The description sits in a different grid row and has to clear the same\n * distance to line up under the title.\n */\nconst TOGGLE_INSET = '1.75rem';\n\nconst useTaskDrag = ({\n task,\n row,\n tasks,\n onTaskMove,\n isCollapsed,\n onCollapseToggle,\n onDraggingChange,\n}: {\n task: Task.Task;\n row?: TaskTreeRow;\n tasks: readonly Task.Task[];\n onTaskMove?: (task: Task.Task, placement: TaskPlacement) => void;\n isCollapsed: (id: string) => boolean;\n onCollapseToggle: (id: string) => void;\n onDraggingChange: (task: Task.Task | undefined) => void;\n}) => {\n const rowRef = useRef<HTMLLIElement | null>(null);\n const dragHandleRef = useRef<HTMLSpanElement | null>(null);\n const [instruction, setInstruction] = useState<Instruction | null>(null);\n const expandTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);\n const liftFrame = useRef<number | null>(null);\n\n // Read through a ref so the listeners are registered once per row rather than re-registered on\n // every keystroke elsewhere in the list; a drag in flight must not lose its target.\n const latest = useRef({ tasks, onTaskMove, isCollapsed, onCollapseToggle, onDraggingChange, row });\n latest.current = { tasks, onTaskMove, isCollapsed, onCollapseToggle, onDraggingChange, row };\n\n const cancelLift = useCallback(() => {\n if (liftFrame.current !== null) {\n cancelAnimationFrame(liftFrame.current);\n liftFrame.current = null;\n }\n }, []);\n\n const cancelExpand = useCallback(() => {\n if (expandTimeout.current) {\n clearTimeout(expandTimeout.current);\n expandTimeout.current = null;\n }\n }, []);\n\n const enabled = !!onTaskMove && !!row;\n\n useEffect(() => {\n const element = rowRef.current;\n const handle = dragHandleRef.current;\n if (!enabled || !element || !handle) {\n return;\n }\n\n const data: TaskDragData = { type: TASK_DRAG_TYPE, taskId: task.id };\n\n /** The placement a drag over this row currently means, or undefined when the drop is refused. */\n const placementFor = (instruction: Instruction | null): TaskPlacement | undefined => {\n const intent = dropIntent(instruction);\n const source = latest.current.tasks.find(({ id }) => id === draggingId.current);\n if (!intent || !source) {\n return undefined;\n }\n return resolveTaskPlacement({ tasks: latest.current.tasks, source, target: task, intent });\n };\n\n return combine(\n draggable({\n // Dragging is started from the handle alone: a whole-row drag would fight text selection and\n // the row's own click-to-select.\n element: handle,\n getInitialData: () => data,\n // ...but what is dragged is the task AND its sub-tasks, which travel with it, so the preview\n // is the whole subtree. The browser's default drag image is the dragged element, which here\n // would be the grip alone.\n onGenerateDragPreview: ({ location, nativeSetDragImage }) => {\n const rows = subtreeRows(element, latest.current.tasks, task);\n const { left, top } = element.getBoundingClientRect();\n const { clientX, clientY } = location.initial.input;\n // Offset by where the grip was grabbed, so the preview does not jump under the cursor.\n const offset = { x: clientX - left, y: clientY - top };\n if (rows.length < 2) {\n // A leaf (or a collapsed branch) is one row: the live element is a better image than a\n // clone, since it keeps the subgrid tracks it inherits from the list.\n nativeSetDragImage?.(element, offset.x, offset.y);\n return;\n }\n setCustomNativeDragPreview({\n nativeSetDragImage,\n getOffset: () => offset,\n render: ({ container }) => renderSubtreePreview(container, element, rows),\n });\n },\n onDragStart: () => {\n draggingId.current = task.id;\n // Deferred a frame: the browser snapshots the drag image as it finishes dispatching\n // `dragstart`, and hiding the rows before that would hand it an empty picture.\n liftFrame.current = requestAnimationFrame(() => latest.current.onDraggingChange(task));\n },\n // Also fires when the drag is cancelled, so the rows always come back.\n onDrop: () => {\n draggingId.current = undefined;\n cancelLift();\n latest.current.onDraggingChange(undefined);\n },\n }),\n dropTargetForElements({\n element,\n getData: ({ input, element }) =>\n attachInstruction(data, {\n input,\n element,\n indentPerLevel: INDENT_PER_LEVEL,\n currentLevel: (latest.current.row?.level ?? 1) - 1,\n mode: itemMode({\n branch: latest.current.row?.branch ?? false,\n open: !latest.current.isCollapsed(task.id),\n last: (latest.current.row?.position ?? 1) === (latest.current.row?.setSize ?? 1),\n }),\n // `make-child` is offered on a leaf too: dropping onto one is how a sub-task is made.\n block: [],\n }),\n canDrop: ({ source }) => isTaskDragData(source.data) && source.data.taskId !== task.id,\n getIsSticky: () => true,\n onDrag: ({ self }) => {\n const desired = extractInstruction(self.data);\n // Refused drops are shown as blocked rather than silently ignored, so the cursor says no.\n const next: Instruction | null =\n desired !== null && desired.type !== 'instruction-blocked' && !placementFor(desired)\n ? { type: 'instruction-blocked', desired }\n : desired;\n setInstruction(next);\n\n const { row, isCollapsed, onCollapseToggle } = latest.current;\n if (next?.type === 'make-child' && row?.branch && isCollapsed(task.id) && !expandTimeout.current) {\n expandTimeout.current = setTimeout(() => {\n expandTimeout.current = null;\n onCollapseToggle(task.id);\n }, EXPAND_DWELL);\n } else if (next?.type !== 'make-child') {\n cancelExpand();\n }\n },\n onDragLeave: () => {\n cancelExpand();\n setInstruction(null);\n },\n onDrop: ({ self, source }) => {\n cancelExpand();\n setInstruction(null);\n if (!isTaskDragData(source.data)) {\n return;\n }\n const moved = latest.current.tasks.find(({ id }) => id === source.data.taskId);\n const intent = dropIntent(extractInstruction(self.data));\n if (!moved || !intent) {\n return;\n }\n const placement = resolveTaskPlacement({ tasks: latest.current.tasks, source: moved, target: task, intent });\n if (placement) {\n latest.current.onTaskMove?.(moved, placement);\n }\n },\n }),\n );\n }, [enabled, task.id, cancelExpand, cancelLift]);\n\n useEffect(\n () => () => {\n cancelExpand();\n cancelLift();\n },\n [cancelExpand, cancelLift],\n );\n\n return { instruction, rowRef, dragHandleRef, dragHandle: enabled };\n};\n\n/**\n * The rendered rows of `task`'s subtree, in document order: the row itself plus every visible\n * descendant. A collapsed branch contributes only its own row, which is what the reader sees.\n */\nconst subtreeRows = (element: HTMLElement, tasks: readonly Task.Task[], task: Task.Task): HTMLElement[] => {\n const list = element.parentElement;\n const ids = subtreeIds(tasks, task);\n return list\n ? Array.from(list.querySelectorAll<HTMLElement>('[data-task-id]')).filter((row) =>\n ids.has(row.dataset.taskId ?? ''),\n )\n : [element];\n};\n\n/**\n * Clones the subtree into the drag preview. The container is made a grid carrying the list's own\n * track sizes: the rows are `grid-cols-subgrid`, so without them a detached copy collapses to its\n * content width and the columns no longer line up.\n */\nconst renderSubtreePreview = (container: HTMLElement, element: HTMLElement, rows: HTMLElement[]): void => {\n const list = element.parentElement;\n const listStyle = list && getComputedStyle(list);\n container.style.display = 'grid';\n container.style.width = `${element.getBoundingClientRect().width}px`;\n if (listStyle) {\n container.style.gridTemplateColumns = listStyle.gridTemplateColumns;\n container.style.columnGap = listStyle.columnGap;\n container.style.gridAutoRows = listStyle.gridAutoRows;\n container.style.alignItems = listStyle.alignItems;\n }\n // The preview is torn off the page, so it needs its own ground to sit on.\n container.classList.add('bg-base-surface');\n for (const row of rows) {\n container.appendChild(row.cloneNode(true));\n }\n};\n\n/**\n * Id of the task currently being dragged. Module-level because the drag source and the row under\n * the cursor are different components, and the hitbox reports only the target's own data mid-drag;\n * a rejected drop has to be refused while the cursor is over it, not after the drop.\n */\nconst draggingId = { current: undefined as string | undefined };\n\n//\n// Item — one row. Exported so a host can render its own selection of tasks.\n//\n\n/** A description is a line in a row, not a document: no paragraph block, no heading scale. */\nconst DESCRIPTION_COMPONENTS = {\n p: ({ children }: PropsWithChildren) => <span>{children}</span>,\n};\n\nconst STATUS_ICONS: Record<Task.Status, { icon: string; classNames?: string }> = {\n todo: { icon: 'ph--square--regular', classNames: 'text-subdued' },\n started: { icon: 'ph--hourglass--regular', classNames: 'text-info-text' },\n done: { icon: 'ph--check--regular', classNames: 'text-success-text' },\n failed: { icon: 'ph--x--regular', classNames: 'text-error-text' },\n cancelled: { icon: 'ph--x--regular', classNames: 'text-error-text' },\n};\n\ntype TaskListItemProps = ComposableProps<{ task: Task.Task; ordinal?: number; row?: TaskTreeRow }>;\n\nconst TaskListItem = composable<HTMLLIElement, { task: Task.Task; ordinal?: number; row?: TaskTreeRow }>(\n ({ task, ordinal, row, ...props }, forwardedRef) => {\n const { t } = useTranslation(translationKey);\n const {\n tasks,\n showDescriptions,\n showGutter,\n onTaskUpdate,\n onTaskDelete,\n onTaskSelect,\n onTaskMove,\n isCollapsed,\n onCollapseToggle,\n dragging,\n onDraggingChange,\n } = useTaskListContext('TaskList.Item');\n const { className, ...rest } = composableProps(props);\n const { instruction, rowRef, dragHandleRef, dragHandle } = useTaskDrag({\n task,\n row,\n tasks,\n onTaskMove,\n onCollapseToggle,\n isCollapsed,\n onDraggingChange,\n });\n const open = row ? !isCollapsed(task.id) : undefined;\n\n // Subscribe per row: a query re-emits when membership changes, not when a task's own fields do,\n // so a rename elsewhere (task form, agent, sync) would otherwise leave the row stale.\n const [snapshot] = useObject(task);\n const current = snapshot ?? task;\n\n const done = current.status === 'done';\n const error = current.status === 'failed';\n\n // Only when the list asks for it, and only when there is something to show — an empty second\n // line would make every row taller for nothing.\n const description = showDescriptions ? current.description?.trim() || undefined : undefined;\n\n // Virtual: an open task whose dependencies (resolved within the set) are not all done.\n const blocked = (current.status ?? 'todo') === 'todo' && !TaskSet.isTaskReady(tasks, task);\n // A started agent task is actively being worked by a sub-agent (started is stamped at spawn),\n // so it spins; a human-started task keeps the static glyph.\n const active = current.status === 'started' && current.assignee?.role === 'assistant';\n const { icon, classNames: iconClassNames } = active\n ? { icon: 'ph--spinner--regular', classNames: 'text-info-text animate-spin' }\n : STATUS_ICONS[current.status ?? 'todo'];\n\n const handleToggle = useCallback(\n () => onTaskUpdate?.(task, { status: done ? 'todo' : 'done' }),\n [onTaskUpdate, task, done],\n );\n\n // Restructuring keys. `Alt` rather than the outliner's bare `Tab`/`Shift-Tab`: a row is a\n // listbox option, not a text field, and consuming `Tab` there would remove the only way to move\n // focus out of the list. One modifier covers all four moves.\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLLIElement>) => {\n if (!onTaskMove || !row || !event.altKey) {\n return;\n }\n const placement = (() => {\n switch (event.key) {\n case 'ArrowRight':\n return resolveIndent(tasks, task);\n case 'ArrowLeft':\n return resolveOutdent(tasks, task);\n case 'ArrowUp':\n return resolveNudge(tasks, task, 'up');\n case 'ArrowDown':\n return resolveNudge(tasks, task, 'down');\n default:\n return undefined;\n }\n })();\n if (placement) {\n event.preventDefault();\n event.stopPropagation();\n onTaskMove(task, placement);\n }\n },\n [onTaskMove, row, tasks, task],\n );\n\n return (\n <Listbox.Item\n {...rest}\n id={task.id}\n data-testid='taskList.item'\n // The drag preview has to find this row's descendants in the DOM, and `Listbox.Item`'s `id`\n // is its selection key rather than a DOM id.\n data-task-id={task.id}\n // `px-0`: a subgrid's own inline padding shrinks its first and last tracks, so the listbox\n // item's default inset would push the status control off the column the create row's `+`\n // sits in. The list's inset belongs to the host, not the row.\n classNames={mx(\n 'group/row col-span-full grid grid-cols-subgrid px-0 items-start relative',\n // Hidden rather than unmounted: the drag is anchored to this row's handle, and removing it\n // from the DOM mid-flight would cancel the gesture in some browsers.\n dragging.has(task.id) && 'hidden',\n className,\n )}\n // A row stays `role=option` (that is what carries selection and roving focus), so nesting is\n // announced by these rather than by treegrid semantics.\n aria-level={row?.level}\n aria-posinset={row && row.position}\n aria-setsize={row && row.setSize}\n aria-expanded={row?.branch ? open : undefined}\n // Depth is published once as variables: the title cell and the description sit in different\n // grid rows and both step in by it, and the description additionally clears the toggle so it\n // starts under the title text rather than under the toggle.\n style={\n row\n ? ({\n '--task-indent': paddingIndentation(row.level, INDENT_PER_LEVEL).paddingInlineStart,\n '--task-title-inset': `calc(var(--task-indent) + ${TOGGLE_INSET})`,\n } as CSSProperties)\n : undefined\n }\n onKeyDown={handleKeyDown}\n ref={useComposedRefs(rowRef, forwardedRef)}\n >\n {showGutter && (\n // Handle and ordinal share one cell: the handle takes the ordinal's place on hover rather\n // than claiming a column of its own, so no row shifts when the cursor crosses it.\n <div className='relative flex h-8 items-center justify-center'>\n {ordinal !== undefined && (\n <Tag\n hue={done ? 'green' : error ? 'rose' : 'neutral'}\n classNames={mx(\n 'tabular-nums',\n dragHandle && 'group-hover/row:invisible group-has-[:focus-visible]/row:invisible',\n )}\n >\n {ordinal}\n </Tag>\n )}\n {dragHandle && (\n <span\n data-testid='taskList.dragHandle'\n aria-hidden\n className={mx(\n 'absolute inset-0 grid place-items-center text-subdued cursor-grab active:cursor-grabbing',\n // A handle with no ordinal beneath it is the cell's only content, so it stays put;\n // otherwise it appears only while the row is under the cursor or holds focus.\n ordinal !== undefined && 'invisible group-hover/row:visible group-has-[:focus-visible]/row:visible',\n )}\n ref={dragHandleRef}\n >\n <Icon icon='ph--dots-six-vertical--regular' size={4} />\n </span>\n )}\n </div>\n )}\n {onTaskUpdate ? (\n <IconButton\n classNames={mx('justify-self-center my-1', iconClassNames)}\n variant='ghost'\n density='sm'\n icon={icon}\n iconOnly\n label={done ? t('mark-todo.label') : t('mark-done.label')}\n onClick={handleToggle}\n />\n ) : (\n <span className='grid h-8 place-items-center justify-self-center'>\n <Icon icon={icon} classNames={iconClassNames} size={4} />\n <span className='sr-only'>{t(`status-${current.status ?? 'todo'}.label`)}</span>\n </span>\n )}\n <span\n // Depth pads the title cell alone, so the status control and every trailing cell stay in\n // their subgrid columns and the rows keep one geometry however deep the tree goes.\n className={mx(\n 'flex h-8 items-center gap-1 min-w-0',\n row && 'ps-(--task-indent)',\n onTaskSelect && 'cursor-pointer',\n )}\n >\n {row && (\n <TreeItemToggle\n isBranch={row.branch}\n open={open}\n onClick={(event) => {\n // The row is the selection target; toggling a branch must not also re-select it.\n event.stopPropagation();\n onCollapseToggle(task.id);\n }}\n />\n )}\n <span className='truncate'>{current.title}</span>\n </span>\n {current.assignee ? (\n <span className='flex h-8 items-center'>\n <TaskListAssignee assignee={current.assignee} />\n </span>\n ) : (\n <div />\n )}\n <div className='flex h-8 items-center gap-1'>\n {blocked && <Tag hue='indigo'>{t('task-blocked.label')}</Tag>}\n {current.priority && current.priority !== 'none' && <Tag hue='neutral'>{current.priority}</Tag>}\n </div>\n {onTaskDelete && (\n <CompactIconButton\n variant='ghost'\n icon='ph--x--regular'\n label={t('delete-task.label')}\n classNames='invisible group-hover/row:visible group-has-[:focus-visible]/row:visible'\n onClick={() => onTaskDelete(task)}\n />\n )}\n {instruction && <TreeDropIndicator instruction={instruction} gap={0} />}\n {description && (\n // Its own row in the subgrid, starting under the title and spanning the label columns.\n <MarkdownView\n content={description}\n classNames={mx(\n showGutter ? 'col-start-3' : 'col-start-2',\n // Aligned under its own title — which sits past the disclosure toggle — so a\n // sub-task's description does not read as belonging to the row above it.\n row && 'ps-(--task-title-inset)',\n 'col-span-3 pb-1 text-sm text-description line-clamp-3',\n )}\n // The row supplies the type scale and the clamp, so the description renders as one\n // inline run rather than the block paragraph the default component wraps it in.\n components={DESCRIPTION_COMPONENTS}\n />\n )}\n </Listbox.Item>\n );\n },\n);\n\nTaskListItem.displayName = 'TaskList.Item';\n\n// TODO(burdon): Reconcile with `CompactIconButton` from `react-ui-form`.\nconst CompactIconButton = (props: IconButtonProps) => {\n return (\n <span className='grid size-8 shrink-0 place-items-center'>\n <IconButton variant='ghost' iconOnly density='sm' {...props} />\n </span>\n );\n};\n\n//\n// Create — the add row; renders nothing unless the root supplies `onTaskCreate`.\n//\n\ntype TaskListCreateProps = ComposableProps<{ placeholder?: string }>;\n\nconst TaskListCreate = composable<HTMLDivElement, { placeholder?: string }>(\n ({ placeholder = 'Add task', ...props }, forwardedRef) => {\n const { onTaskCreate, showGutter } = useTaskListContext('TaskList.Create');\n const { className, ...rest } = composableProps(props);\n const [title, setTitle] = useState('');\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLInputElement>) => {\n const trimmed = title.trim();\n if (event.key === 'Enter' && trimmed.length > 0) {\n onTaskCreate?.(trimmed);\n setTitle('');\n }\n },\n [title, onTaskCreate],\n );\n\n if (!onTaskCreate) {\n return null;\n }\n\n return (\n <div\n {...rest}\n data-testid='taskList.create'\n className={mx(\n 'grid gap-x-1 items-center w-full min-w-0 h-8 shrink-0',\n showGutter ? GRID_COLS.createWithOrdinals : GRID_COLS.create,\n className,\n )}\n ref={forwardedRef}\n >\n {showGutter && <span />}\n <Icon icon='ph--plus--regular' size={4} classNames='justify-self-center text-subdued' />\n <Input.Root>\n <Input.TextInput\n variant='subdued'\n classNames='px-0'\n placeholder={placeholder}\n value={title}\n onChange={(event) => setTitle(event.target.value)}\n onKeyDown={handleKeyDown}\n />\n </Input.Root>\n </div>\n );\n },\n);\n\nTaskListCreate.displayName = 'TaskList.Create';\n\n//\n// Assignee — actor-aware chip: a Person ref resolves to the contact's name; otherwise fall back to\n// name, email, or a shortened DID; agents (`role: 'assistant'`) are marked with a sparkle.\n//\n\ntype TaskListAssigneeProps = { assignee: Actor.Actor };\n\nconst TaskListAssignee = composable<HTMLSpanElement, TaskListAssigneeProps>(({ assignee }, _forwardedRef) => {\n const [contact] = useObject(assignee.contact);\n const label =\n contact?.fullName ??\n assignee.name ??\n assignee.email ??\n (assignee.identityDid ? shortDid(assignee.identityDid) : undefined);\n const agent = assignee.role === 'assistant';\n if (!label && !agent) {\n return null;\n }\n\n return (\n <Tag hue={agent ? 'purple' : 'indigo'}>\n {agent && <Icon icon='ph--sparkle--regular' size={3} classNames='inline-block me-1' />}\n {label ?? 'agent'}\n </Tag>\n );\n});\n\nTaskListAssignee.displayName = 'TaskList.Assignee';\n\nconst shortDid = (did: string): string => `${did.slice(0, 12)}…`;\n\n//\n// TaskList\n//\n\nexport const TaskList = {\n Root: TaskListRoot,\n Viewport: TaskListViewport,\n Content: TaskListContent,\n GroupLabel: TaskListGroupLabel,\n Item: TaskListItem,\n Create: TaskListCreate,\n Assignee: TaskListAssignee,\n};\n\nexport type {\n TaskListAssigneeProps,\n TaskListContentProps,\n TaskListCreateProps,\n TaskListGroupLabelProps,\n TaskListItemProps,\n TaskListRootProps,\n TaskListViewportProps,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/components/task-grid.ts","../../src/util/status-icons.ts","../../src/components/TaskHistory/TaskHistory.tsx","../../src/components/TaskList/hierarchy.ts","../../src/util/assignee.ts","../../src/hooks/useAssigneeDisplay.ts","../../src/components/TaskList/TaskDescription.tsx","../../src/components/TaskList/TaskListContext.ts","../../src/components/TaskList/TaskRowCells.tsx","../../src/components/TaskList/tracks.ts","../../src/components/TaskList/TaskListEditor.tsx","../../src/components/TaskList/tree-model.ts","../../src/components/TaskList/TaskTreeNode.tsx","../../src/components/TaskList/anchor-hover.ts","../../src/components/TaskList/usePreviewAnchor.ts","../../src/components/TaskList/TaskList.tsx","../../src/components/TaskProperties/TaskProperties.tsx","../../src/components/TaskQuestion/TaskQuestion.tsx","../../src/util/format-date.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n/**\n * The geometry a task's sections share: a fixed glyph column, then the content.\n *\n * Shared as a class list rather than as one grid, because the sections do not nest — properties,\n * questions and history each own their own element and could not subgrid onto a common parent\n * without becoming one component. A fixed first column is what makes them line up anyway: 24px is\n * 24px in every section, where an `auto` or `min-content` column would size to each section's own\n * widest glyph and land them a pixel or two apart.\n */\nexport const TASK_GRID = 'grid grid-cols-[1.5rem_1fr] gap-x-1 items-start';\n\n/** Content in the second column: a row with no glyph of its own (a question's options, its context). */\nexport const TASK_GRID_CONTENT = 'col-start-2';\n\n/**\n * The glyph cell: one line box tall, so what it centres is the centre of its row's FIRST line — a\n * cell as tall as the row would float the glyph down a wrapped paragraph.\n */\nexport const TASK_GRID_ICON = 'col-start-1 grid place-items-center h-[1lh]';\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { Task } from '@dxos/types';\nimport { getStyles } from '@dxos/ui-theme';\n\n/**\n * Every status, most active first — the order the picker offers and the order groups render in.\n *\n * It must stay exhaustive: `buildStatusGroups` renders only the statuses named here, so a status\n * left off this list would take its tasks out of a grouped view entirely rather than merely sorting\n * them late.\n */\nexport const STATUS_ORDER: Task.Status[] = Task.StatusOptions.map(({ id }) => id);\n\n/**\n * The glyph for a field with no value set, whatever the field.\n *\n * A dot rather than a dash or an empty cell: every trailing control renders on every row so setting\n * a value never depends on discovering a hover affordance, which means an unset one still has to\n * occupy its column and read as a control. One glyph across priority, estimate and anything added\n * later is what keeps a column of unset rows reading as one column.\n */\nexport const UNSET_ICON = 'ph--asterisk--regular';\n\nexport const priorityIcon = (priority: Task.Priority | undefined): string =>\n priority != null ? (PRIORITY_ICONS.get(priority) ?? UNSET_ICON) : UNSET_ICON;\n\n/**\n * A status's glyph, from the same table that gives it a title and a hue, so the flat row, the tree,\n * the picker and the form's select cannot drift apart.\n */\nexport const statusIcon = (status: Task.Status): string => STATUS_ICONS.get(status) ?? UNSET_ICON;\n\n/**\n * Hue lookup over a schema option table.\n *\n * Colour comes from the table rather than from a map of our own so a row, its picker and the form's\n * select all paint a value the same way; `getStyles` falls back to neutral for a colour outside the\n * palette (the tables use `gray`, which is not one).\n */\nconst textStyleFor = <T extends string>(options: readonly Task.Option<any>[]) => {\n const colors = new Map(options.map(({ id, color }) => [id, color]));\n return (id: T | undefined): string => getStyles(colors.get(id as T) ?? 'neutral').text;\n};\n\nexport const statusTextStyle = textStyleFor(Task.StatusOptions);\nexport const priorityTextStyle = textStyleFor(Task.PriorityOptions);\nexport const estimateTextStyle = textStyleFor(Task.EstimateOptions);\n\n/** A priority's glyph, from the same table that gives it a title and a hue. */\nconst PRIORITY_ICONS = new Map(Task.PriorityOptions.map(({ id, icon }) => [id, icon]));\nconst STATUS_ICONS = new Map(Task.StatusOptions.map(({ id, icon }) => [id, icon]));\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { useMemo } from 'react';\n\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport * as Icon from '@dxos/react-ui/Icon';\nimport * as Layout from '@dxos/react-ui/Layout';\nimport * as Typography from '@dxos/react-ui/Typography';\nimport type * as Util from '@dxos/react-ui/Util';\nimport { Task } from '@dxos/types';\nimport { getStyles, mx } from '@dxos/ui-theme';\n\nimport { translationKey } from '#translations';\n\nimport { UNSET_ICON } from '../../util/status-icons.ts';\nimport { TASK_GRID, TASK_GRID_ICON } from '../task-grid.ts';\n\n/**\n * The glyph per event, keyed by the `Task.Event` the entry records — a table rather than a ternary,\n * so a new event kind is one line here instead of a condition to find in the markup.\n */\ntype EventIcon = { icon: string; hue: string };\n\nconst EVENT_ICONS: Record<Task.Event, EventIcon> = {\n created: { icon: 'ph--plant--regular', hue: 'emerald' },\n updated: { icon: 'ph--pencil-simple--regular', hue: 'indigo' },\n question: { icon: 'ph--question--regular', hue: 'amber' },\n answer: { icon: 'ph--check-circle--regular', hue: 'emerald' },\n};\n\n/** The line an entry reads as: a change's own note. */\nconst entryText = (entry: Task.HistoryEntry): string =>\n entry.event === 'question'\n ? entry.text\n : entry.event === 'answer'\n ? entry.answer\n : (entry.description ?? entry.event);\n\n/** Falls back to the unset glyph: an entry written by an older schema still renders as a row. */\nconst eventIcon = (event: Task.Event): EventIcon => EVENT_ICONS[event] ?? { icon: UNSET_ICON, hue: 'neutral' };\n\nexport type TaskHistoryProps = Util.ThemedClassName<{\n entries: readonly Task.HistoryEntry[];\n /** Entries to show, newest first; the rest are left to a surface with room for them. */\n limit?: number;\n}>;\n\ntype HistoryItem = {\n key: string;\n icon: string;\n hue: string;\n text: string;\n /** The answer, for an exchange: shown under its question as one item. */\n answer?: string;\n date: string;\n};\n\n/**\n * The log as the reader sees it, newest first. A question is an item only once answered — an open\n * one is waiting on the reader and belongs with the task's open questions, not in its record — and\n * then question and answer are one item, dated when the exchange closed.\n */\nconst buildItems = (entries: readonly Task.HistoryEntry[], limit: number): HistoryItem[] => {\n const threads = new Map(Task.getQuestions(entries).map((thread) => [thread.question.id, thread]));\n const items: HistoryItem[] = [];\n for (const [index, entry] of [...entries].reverse().entries()) {\n if (Task.isQuestionEntry(entry)) {\n continue;\n }\n\n const thread = Task.isAnswerEntry(entry) ? threads.get(entry.questionId) : undefined;\n // The answer that closed its question stands for the exchange; a second answer to the same\n // question is not the one that closed it, so it reads as an entry of its own.\n if (thread && thread.answer === entry) {\n const { icon, hue } = eventIcon('question');\n items.push({\n key: entry.id,\n icon,\n hue: getStyles(hue).text,\n text: thread.question.text,\n answer: entry.answer,\n date: entry.date,\n });\n continue;\n }\n\n const { icon, hue } = eventIcon(entry.event);\n items.push({\n key: entry.id ?? `${entry.date}-${index}`,\n icon,\n hue: getStyles(hue).text,\n text: entryText(entry),\n date: entry.date,\n });\n }\n\n return items.slice(0, limit);\n};\n\n/**\n * A task's activity log, newest first.\n *\n * Each entry already carries the human-readable record of what happened, so a line is that sentence\n * plus when it happened — the pane adds no interpretation of its own. An answered question reads as\n * one line with its answer under it; an open one is not part of the record yet.\n */\nexport const TaskHistory = ({ entries, limit = 5, classNames }: TaskHistoryProps) => {\n const { t } = Hooks.useTranslation(translationKey);\n const items = useMemo(() => buildItems(entries, limit), [entries, limit]);\n\n if (items.length === 0) {\n return null;\n }\n\n return (\n // The log spans the host Column's tracks and re-exposes them, so each entry's glyph sits in the\n // gutter with the pane's other glyphs and its text in the content track with the pane's text —\n // rather than in a second set of columns that happens to look similar.\n // A section of the host Container: it inherits the host's tracks, so its glyphs share the pane's gutter.\n <Layout.Container\n asChild\n gutter='inherit'\n gap='sm'\n role='list'\n aria-label={t('task-history.label')}\n data-testid='taskList.history'\n classNames={mx('text-sm text-fg-muted', classNames)}\n >\n <section>\n {/* Set as the form's field labels are, so the article's section headings read as one with them. */}\n <Typography.Text asChild tone='subtle' classNames='dx-label py-0'>\n <h2>{t('task-history.label')}</h2>\n </Typography.Text>\n {items.map((item) => (\n // The section's geometry, a grid rather than a flex row: the glyph column is a fixed 24px,\n // so a history glyph sits on the same axis as a property's however wide each section's text runs.\n <div key={item.key} role='listitem' className={mx(TASK_GRID, 'min-w-0')}>\n {/* The hue comes from the event table, through the same palette the status and priority\n glyphs read. */}\n <div className={TASK_GRID_ICON}>\n <Icon.Icon icon={item.icon} classNames={item.hue} size='md' />\n </div>\n {/* The time rides with the description rather than in a column of its own: flush right\n against the content's edge is where the eye reads it, and a third track would make the\n log a different shape from the sections above it. */}\n <div className='flex gap-2 min-w-0'>\n {/* Wraps: an entry is a sentence, and truncating it hides what actually happened. */}\n <span className='grow min-w-0'>\n {item.text}\n {item.answer && (\n <span className='block text-fg' data-testid='taskList.history.answer'>\n {item.answer}\n </span>\n )}\n </span>\n {/* Compact and live, because the log is read as \"what has been happening\" rather than\n as a record to cite — and the record is a hover away, in the tooltip. */}\n <Typography.Timestamp date={item.date} classNames='shrink-0 text-right' />\n </div>\n </div>\n ))}\n </section>\n </Layout.Container>\n );\n};\n\nTaskHistory.displayName = 'TaskList.History';\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { Task } from '@dxos/types';\n\n/**\n * The tree the list renders, and where a gesture puts a task in it. Kept out of the components\n * because this is the part a regression would break: a drop cannot be driven synthetically\n * (pragmatic-drag-and-drop uses native HTML5 drag events), so the placement calculation is tested\n * directly instead.\n *\n * `TaskSet.tasks` lists the roots and each `Task.subtasks` its own children, so every list orders\n * one set of siblings only: a parent moves without touching its subtree's entries, and a peer\n * reordering one branch cannot corrupt another.\n */\n\n/** One rendered row: the task, its depth, and where it sits among its siblings. */\nexport const subtreeIds = (tasks: readonly Task.Task[], task: Task.Task): Set<string> => {\n const childrenOf = Task.childIndex(tasks);\n const ids = new Set<string>();\n const visit = (current: Task.Task): void => {\n if (ids.has(current.id)) {\n return;\n }\n ids.add(current.id);\n for (const child of childrenOf(current)) {\n visit(child);\n }\n };\n visit(task);\n return ids;\n};\n\n/** The three tree-item intents, matching `@atlaskit/pragmatic-drag-and-drop-hitbox`. */\nexport type TaskDropIntent = 'reorder-above' | 'reorder-below' | 'make-child';\n\n/**\n * Places a task after `target`'s ancestor `levels` steps up — the tree hitbox's `reparent`, which\n * is how a drop past the last child escapes the subtree it would otherwise join. `levels` of 1 is\n * \"after my parent\", 2 \"after my grandparent\", and so on; running out of ancestors rejects rather\n * than silently landing at the root.\n */\nexport const resolveReparent = (\n tasks: readonly Task.Task[],\n source: Task.Task,\n target: Task.Task,\n levels: number,\n): TaskPlacement | undefined => {\n if (levels < 1) {\n return undefined;\n }\n let ancestor: Task.Task | undefined = target;\n for (let step = 0; step < levels; step++) {\n const parentId: string | undefined = ancestor && Task.parentTaskId(ancestor);\n ancestor = parentId === undefined ? undefined : tasks.find((task) => task.id === parentId);\n if (!ancestor) {\n return undefined;\n }\n }\n return subtreeIds(tasks, source).has(ancestor.id)\n ? undefined\n : resolveTaskPlacement({ tasks, source, target: ancestor, intent: 'reorder-below' });\n};\n\n/**\n * Where a task lands, in the two terms `MoveTask` takes. `parentTask` is `null` for a root task\n * (matching the verb's convention) and `before` is undefined when the task goes last among its new\n * siblings.\n */\nexport type TaskPlacement = {\n parentTask: Task.Task | null;\n before?: Task.Task;\n};\n\n/**\n * Resolves a drop into a single `MoveTask` call, or `undefined` when the gesture is rejected.\n *\n * Rejected rather than silently ignored, so the caller can say no with the cursor:\n * - onto itself, or into its own subtree (the cycle `UpdateTask` rejects);\n * - onto a task from another set — a membership transfer, not a reorder.\n */\nexport const resolveTaskPlacement = ({\n tasks,\n source,\n target,\n intent,\n}: {\n tasks: readonly Task.Task[];\n source: Task.Task;\n target: Task.Task;\n intent: TaskDropIntent;\n}): TaskPlacement | undefined => {\n const present = new Set(tasks.map((task) => task.id));\n if (!present.has(source.id) || !present.has(target.id)) {\n return undefined;\n }\n if (subtreeIds(tasks, source).has(target.id)) {\n return undefined;\n }\n\n if (intent === 'make-child') {\n // Inserted as the FIRST child: the pointer is on the parent's own row, and the place directly\n // under that row is where the reader expects the task to appear. Appending it last puts it\n // somewhere off-screen for any parent with a few children.\n const firstChild = Task.subTasks(tasks, target).find((child) => child.id !== source.id);\n return { parentTask: target, before: firstChild };\n }\n\n const parentId = Task.parentTaskId(target);\n const parent = parentId === undefined ? null : (tasks.find((task) => task.id === parentId) ?? null);\n const siblings = parent === null ? Task.rootTasks(tasks) : Task.subTasks(tasks, parent);\n const index = siblings.findIndex((task) => task.id === target.id);\n\n if (intent === 'reorder-above') {\n return { parentTask: parent, before: target };\n }\n\n // Below the target is before the sibling that follows it; with none, the task goes last.\n const next = siblings.slice(index + 1).find((task) => task.id !== source.id);\n return { parentTask: parent, before: next };\n};\n\n/** A task's siblings (in sibling order) and its index among them. */\nconst siblingsOf = (\n tasks: readonly Task.Task[],\n task: Task.Task,\n): { parent: Task.Task | null; siblings: Task.Task[]; index: number } => {\n const parentId = Task.parentTaskId(task);\n const parent = parentId === undefined ? null : (tasks.find((candidate) => candidate.id === parentId) ?? null);\n const siblings = parent === null ? Task.rootTasks(tasks) : Task.subTasks(tasks, parent);\n return { parent, siblings, index: siblings.findIndex((candidate) => candidate.id === task.id) };\n};\n\n/**\n * Indent: the task becomes the last child of its previous sibling — the outliner's `Tab`. Undefined\n * when it is the first among its siblings and so has nothing to indent under.\n */\nexport const resolveIndent = (tasks: readonly Task.Task[], task: Task.Task): TaskPlacement | undefined => {\n const { siblings, index } = siblingsOf(tasks, task);\n const previous = index > 0 ? siblings[index - 1] : undefined;\n return previous ? { parentTask: previous, before: undefined } : undefined;\n};\n\n/**\n * Outdent: the task becomes the next sibling of its parent — the outliner's `Shift-Tab`. Undefined\n * for a task already at the root.\n */\nexport const resolveOutdent = (tasks: readonly Task.Task[], task: Task.Task): TaskPlacement | undefined => {\n const { parent } = siblingsOf(tasks, task);\n if (!parent) {\n return undefined;\n }\n const { parent: grandparent, siblings, index } = siblingsOf(tasks, parent);\n return { parentTask: grandparent, before: siblings[index + 1] };\n};\n\n/**\n * Move within the current parent — `Alt-ArrowUp` / `Alt-ArrowDown`. Undefined at either end, so the\n * key is a no-op rather than silently jumping the task out of its branch.\n */\nexport const resolveNudge = (\n tasks: readonly Task.Task[],\n task: Task.Task,\n direction: 'up' | 'down',\n): TaskPlacement | undefined => {\n const { parent, siblings, index } = siblingsOf(tasks, task);\n if (direction === 'up') {\n return index > 0 ? { parentTask: parent, before: siblings[index - 1] } : undefined;\n }\n // Past the next sibling: anchored on the one after it, or unanchored to land last.\n return index < siblings.length - 1 ? { parentTask: parent, before: siblings[index + 2] } : undefined;\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { Obj } from '@dxos/echo';\nimport { type Actor, type Person, RemoteSession, Task } from '@dxos/types';\n\n/** The glyph for a person assignee. */\nexport const PERSON_ICON = 'ph--user--regular';\n\n/** The glyph for an assistant whose harness has no mark of its own. */\nexport const AGENT_ICON = 'ph--robot--regular';\n\nexport type AssigneeDisplay = {\n /** Absent only for a non-agent actor that carries nothing to name it by. */\n label?: string;\n icon: string;\n /** Whether an assistant, rather than a person, holds the task. */\n agent: boolean;\n /** The harness session the actor stands for, once its subject resolves to one. */\n session?: RemoteSession.RemoteSession;\n};\n\nexport type GetAssigneeDisplayProps = {\n assignee: Actor.Actor;\n /** The actor's resolved contact, as `useObject` reads it. */\n contact?: Pick<Person.Person, 'fullName'>;\n /** The actor's resolved subject, whatever type it turns out to be. */\n subject?: unknown;\n /** The translated last resort for an agent with nothing else to name it by. */\n agentLabel: string;\n};\n\n/** A DID is long and mostly shared prefix, so the start is the part that tells two apart. */\nexport const shortDid = (did: string): string => `${did.slice(0, 12)}…`;\n\n/** An id's tail rather than its head: object ids share a timestamp prefix and harness ids a `session_` one. */\nexport const shortId = (id: string): string => `…${id.slice(-8)}`;\n\n/**\n * How an assignee reads wherever a task names it. An assistant's actor stands for a session rather\n * than a person, so its name falls through the session's own fields before the actor's, and it\n * always has one — an agent that reads as \"Agent\" says an assistant holds the task but never which.\n */\nexport const getAssigneeDisplay = ({\n assignee,\n contact,\n subject,\n agentLabel,\n}: GetAssigneeDisplayProps): AssigneeDisplay => {\n const session = Obj.instanceOf(RemoteSession.RemoteSession, subject) ? subject : undefined;\n const agent = assignee.role === 'assistant';\n // The id tail stands in for a session's name, so only a session — or a subject not yet loaded, which\n // may be one — is named by it; a resolved chat or service is an agent like any other.\n const sessionId =\n (session && RemoteSession.getSessionId(session)) ??\n (session || subject === undefined ? Task.refEntityId(assignee.subject) : undefined);\n const label =\n contact?.fullName ??\n session?.title ??\n (session && RemoteSession.harnessName(session)) ??\n assignee.name ??\n assignee.email ??\n (assignee.identityDid ? shortDid(assignee.identityDid) : undefined) ??\n (sessionId ? shortId(sessionId) : undefined) ??\n (agent ? agentLabel : undefined);\n const icon = (session && RemoteSession.harnessIcon(session)) ?? (agent ? AGENT_ICON : PERSON_ICON);\n return { label, icon, agent, session };\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { useObject } from '@dxos/echo-react';\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport { type Actor } from '@dxos/types';\n\nimport { translationKey } from '#translations';\n\nimport { type AssigneeDisplay, PERSON_ICON, getAssigneeDisplay } from '../util/assignee.ts';\n\n/**\n * The label and glyph for a task's assignee, resolving its contact and subject refs — shared by the\n * list's pill and the properties row so the two cannot name the same assignee differently.\n */\nexport const useAssigneeDisplay = (assignee?: Actor.Actor): AssigneeDisplay => {\n const { t } = Hooks.useTranslation(translationKey);\n const [contact] = useObject(assignee?.contact);\n const [subject] = useObject(assignee?.subject);\n if (!assignee) {\n return { icon: PERSON_ICON, agent: false };\n }\n\n return getAssigneeDisplay({ assignee, contact, subject, agentLabel: t('assignee-agent.label') });\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { type PropsWithChildren } from 'react';\n\nimport { MarkdownView, type MarkdownViewProps } from '@dxos/react-ui-markdown';\nimport type * as Util from '@dxos/react-ui/Util';\nimport { mx } from '@dxos/ui-theme';\n\n/** A description is a line in a row, not a document: no paragraph block, no heading scale. */\nexport const DESCRIPTION_COMPONENTS = {\n p: ({ children }: PropsWithChildren) => <span>{children}</span>,\n};\n\nexport type TaskDescriptionProps = Util.ThemedClassName<{\n content: string;\n /** Renderers beyond the row's own — a host's link anchor, say. */\n components?: MarkdownViewProps['components'];\n}>;\n\n/**\n * A task's description as it appears in a row. Shared by the flat list and the tree so the two\n * paths cannot drift on type scale or clamping; only the placement differs, which is the caller's\n * to supply — the flat row puts it in its own subgrid cell, the tree stacks it under the title\n * inside the heading.\n */\nexport const TaskDescription = ({ content, components, classNames }: TaskDescriptionProps) => (\n <MarkdownView\n data-testid='taskList.item.description'\n content={content}\n classNames={mx('text-sm text-fg-muted line-clamp-3', classNames)}\n // Every block at the row's line height, so the clamp ends on a whole line.\n uniformLineHeight\n // The row supplies the type scale and the clamp, so the description renders as one inline run\n // rather than the block paragraph the default component wraps it in.\n components={{ ...DESCRIPTION_COMPONENTS, ...components }}\n />\n);\n","//\n// Copyright 2026 DXOS.org\n//\n\n// Kept out of the components: react-refresh only fast-refreshes a module whose exports are all\n// components, so a context exported beside one forces a full reload on every edit — and the edit\n// pane, which lives in its own file, needs the same context the list provides.\n\nimport type { MenuItem } from '@dxos/react-ui-menu';\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport { type Task } from '@dxos/types';\n\nimport { type TaskPlacement } from './hierarchy.ts';\nimport { type TaskDescriptionProps } from './TaskDescription.tsx';\nimport { type TaskCreateHandler } from './TaskList.tsx';\nimport { type TaskSelectModifiers } from './TaskTreeNode.tsx';\nimport { type TaskGroup } from './tree-model.ts';\n\n//\n// Context — a plain `createContext` context from `@dxos/react-hooks` (un-scoped); nesting task lists has no meaning today.\n//\n\nconst TASK_LIST_NAME = 'TaskList.Root';\n\nexport type TaskListContextValue = {\n tasks: readonly Task.Task[];\n groupByStatus: boolean;\n /** Host-defined groups, rendered as collapsible headers; supersedes `groupByStatus`. */\n groups?: readonly TaskGroup[];\n showGroupLabels: boolean;\n showOrdinals: boolean;\n showDescription: boolean;\n /** Renderers for a row's description beyond its own — a host's link anchor, say. */\n descriptionComponents?: TaskDescriptionProps['components'];\n /** Render each task's estimate beside the priority control. */\n showEstimates: boolean;\n /** Render the questions in each task's history under its title. */\n hierarchical: boolean;\n /** Whether the leading gutter is rendered at all — it holds the ordinal or the checkbox. */\n showGutter: boolean;\n /** Render who holds each task; off collapses the assignee track. */\n showAssignees: boolean;\n /** Render each task's mnemonic in the leading column, beside its ordinal. */\n showMnemonics: boolean;\n /** Rows and the edit pane run to the host's edges, with no inline inset. */\n flush: boolean;\n /**\n * The column template, built once from the options so the tree's rows and the edit pane lay out on the same named\n * tracks (`gutter`, `status`, `title`, `artifacts`, `assignee`, `estimate`, `priority`, `actions`).\n */\n columns: string;\n selected?: string;\n /** Ids of the checked rows — the set an action acts on, distinct from the current row. */\n checked: ReadonlySet<string>;\n /** Whether a branch's sub-tasks are hidden, and the toggle that flips it. */\n isCollapsed: (id: string) => boolean;\n onCollapseToggle: (id: string) => void;\n onTaskCreate?: TaskCreateHandler;\n onTaskUpdate?: (task: Task.Task, patch: Task.Edit) => void;\n getTaskActions?: (task: Task.Task) => MenuItem[];\n /** Selects a task, or clears the selection with `undefined`; defined only when the list is selectable. */\n onTaskSelect?: (task: Task.Task | undefined, modifiers?: TaskSelectModifiers) => void;\n /** Toggles a row's membership of the checked set; defined only when the host wired checkboxes. */\n onTaskCheck?: (task: Task.Task) => void;\n onTaskMove?: (task: Task.Task, placement: TaskPlacement) => void;\n};\n\nexport const [TaskListProvider, useTaskListContext] = Hooks.createContext<TaskListContextValue>(TASK_LIST_NAME);\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { type MouseEvent, useCallback, useEffect, useRef, useState } from 'react';\n\nimport { Obj } from '@dxos/echo';\nimport { ActionMenu, createMenuAction } from '@dxos/react-ui-menu';\nimport * as Button from '@dxos/react-ui/Button';\nimport * as Field from '@dxos/react-ui/Field';\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport * as Icon from '@dxos/react-ui/Icon';\nimport * as Input from '@dxos/react-ui/Input';\nimport * as Layout from '@dxos/react-ui/Layout';\nimport * as Tooltip from '@dxos/react-ui/Tooltip';\nimport { Task } from '@dxos/types';\nimport { getHashHue, mx } from '@dxos/ui-theme';\n\nimport { translationKey } from '#translations';\n\nimport {\n UNSET_ICON,\n estimateTextStyle,\n priorityIcon,\n priorityTextStyle,\n statusIcon,\n statusTextStyle,\n} from '../../util/status-icons.ts';\nimport { useTaskListContext } from './TaskListContext.ts';\n\n/**\n * Cells shared by the flat row and the tree row.\n *\n * The two paths differ only in their container — a `Listbox.Item` in a grid the list owns, versus an\n * Ark tree row whose anatomy splits content across a heading and a columns slot. That is a\n * difference in where cells are handed to the renderer, not in what a cell contains, so the cells\n * live here and both paths render the same ones. Written after the tree's own copies drifted:\n * a cruder status map, and a selected row whose icons faded.\n */\n\nexport type TaskStatusControlProps = {\n classNames?: string;\n /**\n * Overrides whether the glyph spins. Defaults to {@link Task.isAgentWorking} — a host passes this\n * only when it knows something the task does not, e.g. that the session behind it has stopped.\n */\n active?: boolean;\n task: Task.Task;\n /** Absent for a readonly list, which renders the glyph without the control. */\n onTaskUpdate?: (task: Task.Task, patch: Task.Edit) => void;\n};\n\n/** The status glyph, which is also the control that completes the task. */\nexport const TaskStatusControl = ({ task, onTaskUpdate, active, classNames }: TaskStatusControlProps) => {\n const { t } = Hooks.useTranslation(translationKey);\n const status = task.status ?? 'todo';\n // Derived from the task rather than wired down from the list: a task an agent has taken and\n // started is being worked right now whoever renders it, and the row is the only place that says\n // so. A human-started task keeps the static glyph.\n const working = active ?? Task.isAgentWorking(task);\n const { icon, classNames: iconClassNames } = working\n ? { icon: 'ph--spinner--regular', classNames: 'text-info-text animate-spin' }\n : { icon: statusIcon(status), classNames: statusTextStyle(status) };\n\n // Sourced from the schema's own option table, so the picker offers exactly what the field accepts\n // and carries the same hue the form's select paints it with. A thunk, so a row that is never\n // opened builds neither the options nor their labels.\n const actions = useCallback(\n () =>\n Task.StatusOptions.map(({ id }) =>\n createMenuAction(`status-${id}`, () => onTaskUpdate?.(task, { status: id }), {\n label: t(`status-${id}.label`),\n icon: statusIcon(id),\n iconClassNames: statusTextStyle(id),\n checked: status === id,\n }),\n ),\n [onTaskUpdate, task, status, t],\n );\n\n if (!onTaskUpdate) {\n // `IconBlock square` rather than a bare span: the glyph must hold the same square an\n // `IconButton iconOnly` occupies, or the readonly list's status column collapses to the glyph's\n // own width and stops lining up with the editable list's.\n return (\n <Layout.Block aria-hidden={false} data-testid='taskList.item.status' classNames={classNames}>\n <Icon.Icon icon={icon} classNames={iconClassNames} />\n <span className='sr-only'>{t(`status-${status}.label`)}</span>\n </Layout.Block>\n );\n }\n\n // The button is the trigger, not the block: the button stops the click so the row is not selected\n // too, and a trigger above it would never receive it. The block still gives every control in the\n // row one rail-item square.\n const trigger = (\n <Button.Root\n data-testid='taskList.item.status'\n // The hue goes on the icon, not the button: the row dims icons through `--icons-color`,\n // which the `Icon` root reads, so a colour set on the button is overridden at rest and\n // only reappears once selection invalidates the variable.\n iconClassNames={iconClassNames}\n variant='ghost'\n icon={icon}\n iconOnly\n label={t('task-status.label')}\n // The row is the selection target; opening the menu must not also select it.\n onClick={(event) => event.stopPropagation()}\n />\n );\n\n return (\n <Layout.Block classNames={classNames}>\n {/* Deferred: a list renders one of these per task, and the menu is opened for at most one. */}\n <ActionMenu deferUntilOpen actions={actions}>\n {trigger}\n </ActionMenu>\n </Layout.Block>\n );\n};\n\nTaskStatusControl.displayName = 'TaskList.StatusControl';\n\nexport type TaskMnemonicProps = {\n task: Obj.Unknown | Obj.Snapshot;\n /** The row's number down the list, shown in place of the clipboard until the button is hovered. */\n ordinal?: number;\n classNames?: string;\n};\n\n/**\n * The task's reference: a button in the task's mnemonic hue that copies it. With an ordinal it shows the number, and\n * the clipboard only on hover; without one, the clipboard. At least as wide as the clipboard button, so a one-digit\n * number does not make a narrower target.\n *\n * Copies the task's full `echo://<space>/<id>` URI rather than the mnemonic it names: a mnemonic is only unique enough\n * to read, while the URI resolves the task from anywhere it is pasted — a prompt, an MCP call, another space.\n */\nexport const TaskMnemonic = ({ task, ordinal, classNames }: TaskMnemonicProps) => {\n const mnemonic = Obj.getMnemonic(task);\n const [copied, setCopied] = useState(false);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n // The pending reset would otherwise set state on an unmounted component.\n useEffect(() => () => clearTimeout(timeoutRef.current), []);\n\n // Confirmed only once the write resolves: `writeText` rejects when the document is unfocused or permission is\n // refused, and a check shown before that would report a copy that never happened.\n const handleClick = (event: MouseEvent) => {\n event.stopPropagation();\n void navigator.clipboard\n .writeText(Obj.getURI(task, { prefer: 'absolute' }).toString())\n .then(() => {\n setCopied(true);\n clearTimeout(timeoutRef.current);\n timeoutRef.current = setTimeout(() => setCopied(false), 1_000);\n })\n .catch(() => setCopied(false));\n };\n\n const icon = <Icon.Icon icon={copied ? 'ph--check--regular' : 'ph--clipboard--regular'} />;\n return (\n <Tooltip.Trigger asChild content={mnemonic}>\n <Button.Root\n size='sm'\n compact\n // Hashed from the mnemonic so the task's Gantt lane, which hashes the same string, shares its hue.\n hue={getHashHue(mnemonic)}\n aria-label={mnemonic}\n data-testid='taskList.item.mnemonic'\n classNames={mx('group justify-center min-w-(--dx-control-size) font-mono tabular-nums', classNames)}\n onClick={handleClick}\n >\n {ordinal === undefined || copied ? (\n icon\n ) : (\n <>\n <span data-testid='taskList.item.ordinal' className='group-hover:hidden'>\n {ordinal}\n </span>\n <span className='hidden group-hover:contents'>{icon}</span>\n </>\n )}\n </Button.Root>\n </Tooltip.Trigger>\n );\n};\n\nTaskMnemonic.displayName = 'TaskList.Mnemonic';\n\nexport type TaskCheckboxProps = {\n classNames?: string;\n task: Task.Task;\n checked: boolean;\n onCheckedChange: (task: Task.Task) => void;\n};\n\n/**\n * The gutter's checkbox: which rows an action will act on, never a status write — completing a task\n * is what the status control does. It takes the ordinal's cell rather than a column of its own, so a\n * list that offers it keeps one row geometry and the trailing controls do not shift.\n */\nexport const TaskCheckbox = ({ task, checked, onCheckedChange, classNames }: TaskCheckboxProps) => {\n const { t } = Hooks.useTranslation(translationKey);\n return (\n // `IconBlock square` so the box is centred in the same square an `IconButton iconOnly` occupies;\n // bare, the 1rem box hugged the start of a 2rem track beside 2rem controls.\n <Layout.Block aria-hidden={false} classNames={classNames}>\n <Field.Root>\n <Input.Checkbox\n checked={checked}\n data-testid='taskList.item.checkbox'\n aria-label={t('task-check.label')}\n onCheckedChange={() => onCheckedChange(task)}\n // The row is the selection target; checking it must not also make it the current row.\n onClick={(event) => event.stopPropagation()}\n />\n </Field.Root>\n </Layout.Block>\n );\n};\n\nTaskCheckbox.displayName = 'TaskList.Checkbox';\n\n/**\n * Estimate as its own label rather than a glyph: the sizes are a vocabulary a reader already knows\n * (`XS`…`XL`), and two ordinal ramps side by side would be read as one. Rendered on every row so\n * setting an estimate never depends on discovering a hover affordance, and falling back to\n * {@link UNSET_ICON} when unset — the same dot the priority column shows, so a row with neither set\n * reads as two empty controls rather than a dash beside a dot.\n */\nexport const TaskEstimateControl = ({ task, classNames }: { task: Task.Task; classNames?: string }) => {\n const { onTaskUpdate } = useTaskListContext('TaskList.EstimateControl');\n return (\n <TaskEstimatePicker\n estimate={task.estimate}\n onChange={onTaskUpdate && ((estimate) => onTaskUpdate(task, { estimate: estimate ?? null }))}\n testId='taskList.item.estimate'\n classNames={classNames}\n />\n );\n};\n\nexport type TaskEstimatePickerProps = {\n estimate?: Task.Estimate;\n /** Omitted for a readonly label. */\n onChange?: (estimate: Task.Estimate | undefined) => void;\n testId?: string;\n classNames?: string;\n};\n\n/** The estimate label and its menu over a bare value, for a row's task or a draft that has none yet. */\nexport const TaskEstimatePicker = ({ estimate, onChange, testId, classNames }: TaskEstimatePickerProps) => {\n const label = estimate?.toUpperCase() ?? <Icon.Icon icon={UNSET_ICON} classNames='text-neutral-500' />;\n\n if (!onChange) {\n return <Layout.Block classNames={mx(estimateTextStyle(estimate), classNames)}>{label}</Layout.Block>;\n }\n\n return (\n <Layout.Block classNames={classNames}>\n {/* Deferred: a list renders one of these per task, and the menu is opened for at most one. */}\n <ActionMenu\n deferUntilOpen\n actions={() =>\n [Task.NullOption, ...Task.EstimateOptions].map(({ id, title }) =>\n createMenuAction(`estimate-${id}`, () => onChange(id === 'none' ? undefined : id), {\n label: title,\n checked: (estimate ?? 'none') === id,\n }),\n )\n }\n >\n <Button.Root\n variant='ghost'\n data-testid={testId}\n classNames={mx('w-8 px-0 text-xs tabular-nums', estimateTextStyle(estimate))}\n onClick={(event: MouseEvent) => event.stopPropagation()}\n >\n {label}\n </Button.Root>\n </ActionMenu>\n </Layout.Block>\n );\n};\n\nTaskEstimateControl.displayName = 'TaskList.EstimateControl';\n\n/**\n * Priority as a signal-strength glyph rather than a word: the four levels are ordinal, so a ramp\n * reads at a glance where four differently-worded tags do not. `urgent` breaks the ramp deliberately\n * — it is a different kind of statement from \"how much\", and a filled mark carries that.\n *\n * The glyph is also the control: it opens a menu to set the level. It renders on every row —\n * including one with no priority, which shows a dot — so setting a priority never depends on\n * discovering a hover affordance.\n */\nexport const TaskPriorityIcon = ({ task, classNames }: { task: Task.Task; classNames?: string }) => {\n const { onTaskUpdate } = useTaskListContext('TaskList.PriorityIcon');\n return (\n <TaskPriorityPicker\n priority={task.priority ?? undefined}\n onChange={onTaskUpdate && ((priority) => onTaskUpdate(task, { priority: priority ?? null }))}\n testId='taskList.item.priority'\n classNames={classNames}\n />\n );\n};\n\nexport type TaskPriorityPickerProps = {\n priority?: Task.Priority;\n /** Omitted for a readonly glyph. */\n onChange?: (priority: Task.Priority | undefined) => void;\n testId?: string;\n classNames?: string;\n};\n\n/** The priority glyph and its menu over a bare value, for a row's task or a draft that has none yet. */\nexport const TaskPriorityPicker = ({ priority, onChange, testId, classNames }: TaskPriorityPickerProps) => {\n const { t } = Hooks.useTranslation(translationKey);\n const icon = priorityIcon(priority);\n const styles = priorityTextStyle(priority);\n\n if (!onChange) {\n // Falls back to the dot rather than rendering nothing: a readonly row still says \"no priority\"\n // in the same column its neighbours use, so the list reads as one column and not a ragged one.\n return (\n <Layout.Block classNames={classNames}>\n <Icon.Icon icon={icon} classNames={mx(styles)} />\n </Layout.Block>\n );\n }\n\n return (\n <Layout.Block classNames={classNames}>\n {/* Deferred: a list renders one of these per task, and the menu is opened for at most one. */}\n <ActionMenu\n deferUntilOpen\n actions={() =>\n [Task.NullOption, ...Task.PriorityOptions].map(({ id, icon: optionIcon }) =>\n createMenuAction(`priority-${id}`, () => onChange(id === 'none' ? undefined : id), {\n label: t(`priority-${id}.label`),\n // `None` takes the row's unset glyph, so every option has an icon and the labels align.\n icon: optionIcon ?? UNSET_ICON,\n iconClassNames: priorityTextStyle(id),\n checked: (priority ?? 'none') === id,\n }),\n )\n }\n >\n <Button.Root\n variant='ghost'\n icon={icon}\n iconOnly\n label={t('task-priority.label')}\n data-testid={testId}\n iconClassNames={styles}\n onClick={(event) => event.stopPropagation()}\n />\n </ActionMenu>\n </Layout.Block>\n );\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\n/**\n * A cell's place on the list's column template, by track name, on the title line. Shared by the tree's rows and the\n * edit pane, which lay out on the same template, so a field sits exactly where the value it edits is read; a cell\n * that is absent leaves its track empty. Literal so Tailwind finds the classes.\n */\nexport const TRACK = {\n gutter: 'row-start-1 col-[gutter]',\n status: 'row-start-1 col-[status]',\n title: 'row-start-1 col-[title]',\n artifacts: 'row-start-1 col-[artifacts]',\n assignee: 'row-start-1 col-[assignee]',\n estimate: 'row-start-1 col-[estimate]',\n priority: 'row-start-1 col-[priority]',\n actions: 'row-start-1 col-[actions]',\n} as const;\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, {\n type ClipboardEvent,\n type CSSProperties,\n type DragEvent,\n type KeyboardEvent,\n useCallback,\n useMemo,\n useRef,\n useState,\n} from 'react';\n\nimport { useObject } from '@dxos/echo-react';\nimport { log } from '@dxos/log';\nimport { MarkdownEditable, type MarkdownEditableController, type MarkdownEditableProps } from '@dxos/react-ui-markdown';\nimport * as Button from '@dxos/react-ui/Button';\nimport * as Field from '@dxos/react-ui/Field';\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport * as Icon from '@dxos/react-ui/Icon';\nimport * as Input from '@dxos/react-ui/Input';\nimport * as SystemButton from '@dxos/react-ui/SystemButton';\nimport * as Tag from '@dxos/react-ui/Tag';\nimport * as Util from '@dxos/react-ui/Util';\nimport { type Task } from '@dxos/types';\nimport { submitOnModEnter } from '@dxos/ui-editor';\nimport { mx } from '@dxos/ui-theme';\nimport { type ComposableProps } from '@dxos/ui-types';\n\nimport { translationKey } from '#translations';\n\nimport { type TaskCreateHandler, type TaskCreateResult } from './TaskList.tsx';\nimport { useTaskListContext } from './TaskListContext.ts';\nimport {\n TaskEstimateControl,\n TaskEstimatePicker,\n TaskPriorityIcon,\n TaskPriorityPicker,\n TaskStatusControl,\n} from './TaskRowCells.tsx';\nimport { TRACK } from './tracks.ts';\n\nexport type TaskListEditorProps = ComposableProps<{\n /** Placeholder for the title field when nothing is selected (the create case); translated by default. */\n placeholder?: string;\n /**\n * Edit a description under the title — the selected task's, or the new task's when creating, so a\n * task can be added with one. Off by default, matching `Root`'s `showDescription`: a markdown\n * field is several rows tall wherever it appears, which a single-line strip has no room for.\n */\n showDescription?: boolean;\n /** Placeholder for the description field; translated by default. */\n descriptionPlaceholder?: string;\n /** Editor extensions for the description field beyond its own — what the host's plugins contribute. */\n descriptionExtensions?: MarkdownEditableProps['extensions'];\n /**\n * Only ever create — the pane ignores the selection instead of editing it. For a host whose detail\n * lives elsewhere (a task plank opened from the row): there, a selected row would otherwise turn\n * the only create affordance into an editor, leaving no way to type a new task.\n */\n createOnly?: boolean;\n /**\n * Take files dropped or pasted on the pane while creating, held as chips until the task is created\n * and then handed to `onTaskCreate` with it. A host sets this only when it can store a file.\n */\n acceptFiles?: boolean;\n}>;\n\n/**\n * The detail half of the list: it edits whichever task is selected, and creates one when none is.\n *\n * Editing lives here rather than in the row because a row is 32px of shared subgrid — a field\n * opening inside it moves everything around it. A pane below the list has room to be a field.\n *\n * The fields, and nothing else. A task's questions and its history belong to the surface that has\n * room to answer and to read — the detail article — and under a list they grew the strip by a line\n * per entry, pushing the list itself off the screen.\n */\nexport const TaskListEditor = Util.composable<HTMLDivElement, TaskListEditorProps>(\n (\n {\n placeholder,\n showDescription = false,\n descriptionPlaceholder,\n descriptionExtensions,\n createOnly = false,\n acceptFiles = false,\n ...props\n },\n forwardedRef,\n ) => {\n const { t } = Hooks.useTranslation(translationKey);\n const { className, ...rest } = Util.composableProps(props);\n const descriptionRef = useRef<MarkdownEditableController>(null);\n const { tasks, selected, columns, showEstimates, flush, onTaskCreate, onTaskUpdate, onTaskSelect } =\n useTaskListContext('TaskList.Editor');\n\n const task = useMemo(\n () => (createOnly ? undefined : tasks.find(({ id }) => id === selected)),\n [createOnly, tasks, selected],\n );\n // Subscribe to the selected task so the pane follows a rename made anywhere else.\n const [snapshot] = useObject(task);\n const current = snapshot ?? task;\n\n // The create row's description, mirrored out of the field. A ref rather than state because the\n // create reads it in the same tick it commits the field, and `useEditable` calls back\n // synchronously — a `setState` would still hold the previous render's text.\n const draftDescription = useRef('');\n const [draft, setDraft] = useState('');\n // Set on the create row before the task exists, and sent with its draft.\n const [draftPriority, setDraftPriority] = useState<Task.Priority>();\n const [draftEstimate, setDraftEstimate] = useState<Task.Estimate>();\n // Held by the pane, not the host: there is no task to attach them to until the create lands.\n const [files, setFiles] = useState<readonly File[]>([]);\n const [dragOver, setDragOver] = useState(false);\n // Counted because `dragleave` fires on every child the pointer crosses, not only on leaving.\n const dragDepth = useRef(0);\n\n // Bumped after a create, to rebuild the held-open editor empty. The field is uncontrolled while\n // creating (there is no task to read from), so clearing it means remounting it.\n const [createEpoch, setCreateEpoch] = useState(0);\n // Set while a create is in flight, so a second Enter on the still-shown draft does not file it twice.\n const creating = useRef(false);\n\n // The pane is a view onto whichever task is selected, so switching tasks replaces the text it\n // holds rather than carrying the previous one's across. The description ref is cleared here too:\n // the field remounts empty on the way back to creating, but `commit()` on an already-empty field\n // never calls back — so text abandoned before a selection would otherwise ride along, unseen,\n // into the next task created.\n //\n // State rather than a ref for the previous id (React's adjust-state-on-prop-change pattern): a\n // render React abandons leaves a ref already mutated, so the retry would skip the reset and the\n // pane would keep the previous task's text.\n const [editingId, setEditingId] = useState<string | undefined>(undefined);\n if (editingId !== current?.id) {\n setEditingId(current?.id);\n setDraft(current?.title ?? '');\n draftDescription.current = '';\n }\n\n const commitTitle = useCallback(() => {\n const title = draft.trim();\n if (task && current) {\n if (title.length > 0 && title !== current.title) {\n onTaskUpdate?.(task, { title });\n }\n } else if (title.length > 0 && !creating.current) {\n // Nothing has committed the description yet — it is held open and the reader is in the\n // title — so commit it here, before assembling the draft it belongs to.\n descriptionRef.current?.commit();\n const sentDescription = draftDescription.current;\n const description = sentDescription.trim();\n const sentTitle = draft;\n const sent = files;\n // The draft is cleared only once the host reports the task created: a refused create keeps\n // what was typed, and each field is cleared only if it still holds what was sent, so a create\n // that lands late does not wipe text typed while it was pending.\n const settle = (result: TaskCreateResult | void) => {\n creating.current = false;\n if (result?.error) {\n log.warn('task create failed', { error: result.error });\n return;\n }\n const kept = new Set(result?.rejectedFiles ?? []);\n setFiles((files) => files.filter((file) => !sent.includes(file) || kept.has(file)));\n setDraft((draft) => (draft === sentTitle ? '' : draft));\n setDraftPriority(undefined);\n setDraftEstimate(undefined);\n if (draftDescription.current === sentDescription) {\n draftDescription.current = '';\n setCreateEpoch((epoch) => epoch + 1);\n }\n };\n let result: ReturnType<TaskCreateHandler> | undefined;\n creating.current = true;\n try {\n result = onTaskCreate?.(\n {\n title,\n ...(description.length > 0 && { description }),\n ...(draftPriority && { priority: draftPriority }),\n ...(draftEstimate && { estimate: draftEstimate }),\n },\n sent.length > 0 ? sent : undefined,\n );\n } catch (error) {\n // Keeps the whole draft and every file, as a reported failure does.\n creating.current = false;\n log.catch(error);\n return;\n }\n void Promise.resolve(result).then(settle, (error) => {\n creating.current = false;\n log.catch(error);\n });\n }\n }, [draft, draftPriority, draftEstimate, files, task, current, onTaskCreate, onTaskUpdate]);\n\n // Blur commits a rename but never a create: leaving the field is not a decision to add a task,\n // and half a title would become one — clicking the list, the thread, or anywhere else would\n // leave a stray behind. Creating takes Enter or Save, which are the deliberate acts.\n const handleTitleBlur = useCallback(() => {\n if (task && current) {\n commitTitle();\n }\n }, [task, current, commitTitle]);\n\n const handleTitleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'Enter') {\n commitTitle();\n }\n },\n [commitTitle],\n );\n\n // Writes both fields and leaves, as cancelling does — the pane drops back to creating either\n // way, and only what it did with the pending text differs. Creating commits the description\n // itself (it is part of the draft), so this only has to for an edit.\n const handleSave = useCallback(() => {\n commitTitle();\n if (task && current) {\n descriptionRef.current?.commit();\n }\n onTaskSelect?.(undefined);\n }, [commitTitle, task, current, onTaskSelect]);\n\n // Unlike the Save button, the key is a no-op on an untitled create row: the button is hidden\n // there, and the key must not clear the draft the reader is still writing.\n const handleSubmit = useCallback(() => {\n if (!(task && current) && draft.trim().length === 0) {\n return;\n }\n handleSave();\n }, [task, current, draft, handleSave]);\n\n // Read through a ref so the extension is built once: a new extensions array rebuilds the editor\n // and drops focus, and `handleSubmit` changes on every keystroke of the title. Synced in an effect\n // so the keymap only ever sees a committed render's handler.\n const submitRef = Hooks.useDynamicRef(handleSubmit);\n const extensions = useMemo(\n () => [...(descriptionExtensions ?? []), submitOnModEnter({ onSubmit: () => submitRef.current() })],\n [descriptionExtensions],\n );\n\n // Throws away the pending edit and leaves: the pane drops back to creating, which is the same\n // exit Escape on a row gives. Reverting first, since deselecting unmounts the fields. An\n // abandoned create is cleared rather than reverted — a blur may already have committed text into\n // the field, and reverting would restore exactly that.\n const handleCancel = useCallback(() => {\n if (task && current) {\n descriptionRef.current?.revert();\n } else {\n draftDescription.current = '';\n setCreateEpoch((epoch) => epoch + 1);\n }\n setDraft('');\n setFiles([]);\n onTaskSelect?.(undefined);\n }, [task, current, onTaskSelect]);\n\n // Only while creating: an existing task's attachments are its article's to manage. Captured, so\n // the description editor does not take a dropped file and insert its bytes as text.\n const takesFiles = acceptFiles && !!onTaskCreate && !(task && current);\n const handleFiles = useCallback((added: File[]) => setFiles((files) => [...files, ...added]), []);\n const handleDragEnter = useCallback((event: DragEvent) => {\n if (isFileDrag(event)) {\n dragDepth.current++;\n setDragOver(true);\n }\n }, []);\n const handleDragLeave = useCallback((event: DragEvent) => {\n if (isFileDrag(event) && --dragDepth.current <= 0) {\n dragDepth.current = 0;\n setDragOver(false);\n }\n }, []);\n const handleDragOver = useCallback((event: DragEvent) => {\n if (isFileDrag(event)) {\n event.preventDefault();\n event.stopPropagation();\n event.dataTransfer.dropEffect = 'copy';\n }\n }, []);\n const handleDrop = useCallback(\n (event: DragEvent) => {\n dragDepth.current = 0;\n setDragOver(false);\n const dropped = Array.from(event.dataTransfer.files);\n if (dropped.length > 0) {\n event.preventDefault();\n event.stopPropagation();\n handleFiles(dropped);\n }\n },\n [handleFiles],\n );\n // Only a paste that is nothing but files: a paste carrying text as well belongs to the field.\n const handlePaste = useCallback(\n (event: ClipboardEvent) => {\n const pasted = Array.from(event.clipboardData.files);\n if (pasted.length > 0 && !event.clipboardData.types.includes('text/plain')) {\n event.preventDefault();\n event.stopPropagation();\n handleFiles(pasted);\n }\n },\n [handleFiles],\n );\n\n // Nothing to create with and nothing to edit: the pane has no purpose.\n if (!onTaskCreate && !(current && onTaskUpdate)) {\n return null;\n }\n\n // On the list's template the pane has the rows' columns: the ordinal gutter it leaves empty, the\n // status column takes the icon, and the title column takes the field — which is what puts the\n // caret where the rows' titles start. Off it, the pane keeps a template of its own.\n const hasDescription = !!(showDescription && (current ? onTaskUpdate : onTaskCreate));\n // Valid only with a title: a task cannot be created, or renamed, to nothing.\n const canSave = draft.trim().length > 0;\n // Something to throw away: an edit in progress, or a draft.\n const canCancel = !!current || canSave;\n // Editing, the task's own controls; creating, the same pickers over the draft, so a task can be sized and ranked\n // as it is added.\n const editing = !!(task && current);\n\n return (\n // The pane's own grid on the list's template, so its cells sit in the rows' columns: the field where the titles\n // are read, the pickers under the rows' estimate and priority.\n <div\n {...rest}\n className={mx(\n // The gap between the lines is the grid's, not a margin on each cell: a margin has to be repeated on every\n // cell that might start a line, and is missed by whichever one is added next.\n 'grid w-full min-w-0 shrink-0',\n (hasDescription || (takesFiles && files.length > 0)) && 'gap-y-2',\n // The drop target is the pane itself, marked while files are held over it.\n dragOver && 'ring-2 ring-inset ring-accent-bg',\n // The tree's rows pad their ends by the same gap, so the pane's fields start where the rows' cells do.\n !flush && 'px-(--dx-gap-size)',\n className,\n )}\n // `--dx-col` is reset the way `ScrollArea.Viewport` resets it: inside a host `Column` the variable says \"the\n // content track\", and `Field.Root` hands it to the field it wraps — which in THIS grid names a different\n // column, and put the title in the controls' track.\n style={{ 'gridTemplateColumns': columns, '--dx-col': 'auto' } as CSSProperties}\n data-testid='taskList.edit'\n {...(takesFiles && {\n onDragEnterCapture: handleDragEnter,\n onDragLeaveCapture: handleDragLeave,\n onDragOverCapture: handleDragOver,\n onDropCapture: handleDrop,\n onPasteCapture: handlePaste,\n })}\n ref={forwardedRef}\n >\n {/* Each cell placed by its track's name, as the rows' are. In the DOM the description follows the title, though\n the pickers sit beside it, so Tab runs from the title into the description before the trailing controls. */}\n {editing ? (\n // The row's own status control — the same glyph and menu, so status is set where the task is read.\n <TaskStatusControl task={task} classNames={mx(TRACK.status, 'self-start')} onTaskUpdate={onTaskUpdate} />\n ) : (\n // Creating, there is no task to carry a status yet.\n <span className={mx(TRACK.status, 'flex items-center justify-center h-(--dx-control) self-start')}>\n <Icon.Icon icon='ph--plus--regular' tone='subtle' />\n </span>\n )}\n {/* The field's root is the grid item, so it takes the placement. */}\n <Field.Root classNames='row-start-1 col-start-[title] col-end-[assignee] min-w-0'>\n <Input.Root\n // An input clips its overflow rather than wrapping it, so a long title ends mid-word against the trailing\n // controls with nothing to say it continues; the ellipsis says so.\n classNames='grow text-ellipsis'\n data-testid='taskList.edit.title'\n // A host may name the row (\"Add a step\"), but the default is the package's own string: an English literal\n // in the component is a string no translation can reach.\n placeholder={current ? t('task-title.placeholder') : (placeholder ?? t('add-task.placeholder'))}\n value={draft}\n onChange={(event) => setDraft(event.target.value)}\n onKeyDown={handleTitleKeyDown}\n onBlur={handleTitleBlur}\n // Only when there is something valid to save. `false` rather than nothing, so the field keeps its frame (and\n // its test id) either way.\n end={\n canSave && (\n <SystemButton.Save\n variant='primary'\n data-testid='taskList.edit.save'\n // Out of the tab order: Enter in the field saves, and Tab goes on to the description.\n tabIndex={-1}\n onClick={handleSave}\n onMouseDown={(event) => event.preventDefault()}\n />\n )\n }\n />\n </Field.Root>\n {hasDescription && (\n // A control frame, as the title's Input is: the well, and the focus ring while the editor has focus.\n <Input.Frame\n rows={2}\n data-testid='taskList.edit.description'\n classNames='row-start-2 col-[title/assignee] min-w-0'\n >\n {/* A description is markdown, so it is edited as markdown. `editing` is held open — the pane IS the editor,\n so there is nothing to click into — and the key remounts it per task, since a field held open never\n re-reads its subject. Creating, the field is uncontrolled: there is no task to read a value from, so it\n holds the draft itself until the create collects it. */}\n <MarkdownEditable\n key={current?.id ?? `create-${createEpoch}`}\n ref={descriptionRef}\n // A long description scrolls within the field rather than growing the pane past the list it edits from:\n // eight lines, with the scroller's line-height set to the lines' (CodeMirror's base theme gives it a\n // smaller one) so `lh` measures a real line.\n classNames='[&_.cm-scroller]:!leading-normal [&_.cm-scroller]:max-h-[8lh] [&_.cm-scroller]:overflow-y-auto'\n editing\n multiline\n placeholder={descriptionPlaceholder ?? t('task-description.placeholder')}\n extensions={extensions}\n // Held open, so it must not pull focus: selecting a row by keyboard would otherwise land the reader in\n // the description instead of the list.\n autoFocus={false}\n {...(current && { value: current.description ?? '' })}\n onValueChange={(description) => {\n if (task && current) {\n onTaskUpdate?.(task, { description });\n } else {\n draftDescription.current = description;\n }\n }}\n />\n </Input.Frame>\n )}\n {showEstimates &&\n (editing ? (\n <TaskEstimateControl task={task} classNames={TRACK.estimate} />\n ) : (\n <TaskEstimatePicker\n estimate={draftEstimate}\n onChange={setDraftEstimate}\n testId='taskList.edit.estimate'\n classNames={TRACK.estimate}\n />\n ))}\n {editing ? (\n <TaskPriorityIcon task={task} classNames={TRACK.priority} />\n ) : (\n <TaskPriorityPicker\n priority={draftPriority}\n onChange={setDraftPriority}\n testId='taskList.edit.priority'\n classNames={TRACK.priority}\n />\n )}\n {canCancel && (\n // In the rows' menu column: the way out of the pane, there while it holds something to throw away.\n <SystemButton.Cancel\n variant='ghost'\n classNames={TRACK.actions}\n data-testid='taskList.edit.cancel'\n onClick={handleCancel}\n onMouseDown={(event) => event.preventDefault()}\n />\n )}\n\n {takesFiles &&\n files.length > 0 && (\n // Under the description: what the task will be created with.\n <div\n className={mx(\n 'col-start-[title] -col-end-1 flex flex-wrap items-center gap-1 min-w-0',\n hasDescription ? 'row-start-3' : 'row-start-2',\n )}\n >\n {files.map((file, index) => (\n <Tag.Tag\n key={`${file.name}-${index}`}\n hue='neutral'\n classNames='inline-flex items-center gap-1'\n data-testid='taskList.edit.file'\n >\n <Icon.Icon icon='ph--paperclip--regular' size='xs' />\n <span data-testid='taskList.edit.file.name'>{file.name}</span>\n <Button.Root\n variant='ghost'\n size='sm'\n iconOnly\n icon='ph--x--regular'\n iconSize='xs'\n label={t('remove-file.label', { name: file.name })}\n classNames='p-0 min-h-0 h-auto'\n onClick={() => setFiles((files) => files.filter((_, position) => position !== index))}\n />\n </Tag.Tag>\n ))}\n </div>\n )}\n </div>\n );\n },\n);\n\n/** Whether a drag carries files from outside the page, rather than an element dragged within it. */\nconst isFileDrag = (event: DragEvent): boolean => Array.from(event.dataTransfer.types).includes('Files');\n\nTaskListEditor.displayName = 'TaskList.Editor';\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { type StaticTreeModel, createStaticTreeModel } from '@dxos/react-ui-list';\nimport { Task } from '@dxos/types';\nimport { type Label } from '@dxos/ui-types';\n\n/**\n * Adapts a set's tasks to the `Tree`'s model.\n *\n * The list is taken flat (every task the set holds) and the forest rebuilt from\n * `rootTasks`/`subTasks`: parent edges place each task, `subtasks` order its siblings.\n *\n * A task has exactly one parent, so it appears at exactly one path. That is what lets collapsed\n * state stay keyed by id (as `TaskList` already keys it) while `Tree` addresses rows by path.\n */\nexport type TaskNode = {\n id: string;\n /** Absent on the synthetic root and on a status group, neither of which is a task. */\n task?: Task.Task;\n /** Set on a status group, whose children are the tasks in that status. */\n status?: Task.Status;\n /** Set on a host-defined group (see {@link TaskGroup}), whose row is a collapsible header. */\n group?: TaskGroupHeader;\n children: TaskNode[];\n};\n\n/**\n * A section of a grouped task list, as the host computed it — by status, priority, assignee,\n * milestone or anything else. The list only renders the partition; deciding it is the host's.\n */\nexport type TaskGroup = {\n /** Unique among the list's groups. */\n id: string;\n label: Label;\n icon?: string;\n iconClassNames?: string;\n /** The group's tasks, in the order they render; sub-tasks nest under a parent in the same group. */\n tasks: readonly Task.Task[];\n};\n\n/** What a group's header row shows. */\nexport type TaskGroupHeader = Omit<TaskGroup, 'tasks'> & { count: number };\n\n/** Prefix so a host group's node id cannot collide with a task's, nor with a status group's. */\nexport const TASK_GROUP_ID_PREFIX = 'group:';\n\n/** The tree node id of a host group, which is also its key in the list's collapsed set. */\nexport const taskGroupNodeId = (group: Pick<TaskGroup, 'id'>): string => `${TASK_GROUP_ID_PREFIX}${group.id}`;\n\n/**\n * One collapsible header per non-empty group, its tasks beneath. Nested within a group when\n * `hierarchical`: a sub-task whose parent landed in another group is a root of its own group, since\n * the forest is built from the group's tasks alone.\n */\nexport const buildTaskGroups = (groups: readonly TaskGroup[], hierarchical = true): TaskNode => ({\n id: TASK_TREE_ROOT_ID,\n children: groups\n .filter((group) => group.tasks.length > 0)\n .map(({ tasks, ...header }) => ({\n id: taskGroupNodeId(header),\n group: { ...header, count: tasks.length },\n children: (hierarchical ? buildTaskForest(tasks) : buildFlatList(tasks)).children,\n })),\n});\n\n/** Synthetic root; the tree renders its children. */\nexport const TASK_TREE_ROOT_ID = 'tasks';\n\n/**\n * Builds the task forest. Cycle-safe: a malformed parent loop is visited once and then\n * skipped, so a corrupt set renders short rather than hanging.\n */\nexport const buildTaskForest = (tasks: readonly Task.Task[]): TaskNode => {\n const childrenOf = Task.childIndex(tasks);\n const seen = new Set<string>();\n\n const visit = (task: Task.Task): TaskNode => {\n seen.add(task.id);\n const children = childrenOf(task).filter((child) => !seen.has(child.id));\n return { id: task.id, task, children: children.map(visit) };\n };\n\n return {\n id: TASK_TREE_ROOT_ID,\n children: Task.rootTasks(tasks)\n .filter((task) => !seen.has(task.id))\n .map(visit),\n };\n};\n\n/**\n * Path to each task, keyed by id and including the synthetic root — what `Tree` addresses rows by.\n * Unambiguous because a task has exactly one parent, so it appears at exactly one path.\n */\nexport const buildTaskPaths = (root: TaskNode): Map<string, string[]> => {\n const paths = new Map<string, string[]>();\n const visit = (node: TaskNode, path: string[]): void => {\n const next = [...path, node.id];\n if (node.task) {\n paths.set(node.id, next);\n }\n node.children.forEach((child) => visit(child, next));\n };\n visit(root, []);\n return paths;\n};\n\nexport type TaskTreeModelOptions = {\n /** Ids whose sub-tasks are hidden. Keyed by id, matching `TaskList`'s own collapsed set. */\n collapsed?: ReadonlySet<string>;\n /**\n * Group the tasks under status headers instead of rendering the hierarchy.\n *\n * Grouping and hierarchy are alternatives, not layers: a status group holds the tasks in that\n * status flat, because a sub-task's status need not match its parent's and nesting one inside a\n * group its parent does not belong to reads as a contradiction.\n */\n groupByStatus?: readonly Task.Status[];\n /**\n * Host-defined groups, rendered as collapsible headers with counts. Takes precedence over\n * `groupByStatus`, and keeps the hierarchy within each group when `hierarchical`.\n */\n groups?: readonly TaskGroup[];\n /** Namespace for the group headers' labels; without it a header reads as a raw status id. */\n translationKey?: string;\n /**\n * Nest sub-tasks under their parent. Off renders one row per task, which is the flat list — a\n * tree of depth one rather than a second rendering path.\n */\n hierarchical?: boolean;\n};\n\n/** Every task as a root: the flat list, expressed as a tree of depth one. */\nexport const buildFlatList = (tasks: readonly Task.Task[]): TaskNode => ({\n id: TASK_TREE_ROOT_ID,\n children: tasks.map((task) => ({ id: task.id, task, children: [] })),\n});\n\n/** Prefix so a group's node id cannot collide with a task's. */\nconst GROUP_ID_PREFIX = 'status:';\n\n/** Builds the status-grouped forest: one group node per non-empty status, tasks flat within it. */\nexport const buildStatusGroups = (tasks: readonly Task.Task[], order: readonly Task.Status[]): TaskNode => ({\n id: TASK_TREE_ROOT_ID,\n children: order\n .map((status) => ({\n id: `${GROUP_ID_PREFIX}${status}`,\n status,\n children: tasks\n .filter((task) => (task.status ?? 'todo') === status)\n .map((task) => ({ id: task.id, task, children: [] })),\n }))\n .filter((group) => group.children.length > 0),\n});\n\n/** The forest a list renders under `options` — shared by the model and the paths into it. */\nexport const buildTaskTree = (\n tasks: readonly Task.Task[],\n {\n groups,\n groupByStatus,\n hierarchical = true,\n }: Pick<TaskTreeModelOptions, 'groups' | 'groupByStatus' | 'hierarchical'> = {},\n): TaskNode =>\n groups\n ? buildTaskGroups(groups, hierarchical)\n : groupByStatus\n ? buildStatusGroups(tasks, groupByStatus)\n : hierarchical\n ? buildTaskForest(tasks)\n : buildFlatList(tasks);\n\n/** A task's title, a group's label or translated status, or the synthetic root's id. */\nconst groupLabel = (node: TaskNode, translationKey?: string): Label => {\n if (node.task) {\n return node.task.title ?? node.id;\n }\n if (node.group) {\n return node.group.label;\n }\n if (node.status) {\n // Without a namespace the key cannot resolve, so the raw status is the honest fallback.\n return translationKey ? [`status-${node.status}.label`, { ns: translationKey }] : node.status;\n }\n return node.id;\n};\n\n/**\n * A {@link StaticTreeModel} over the task forest.\n *\n * The model is rebuilt whenever the task array changes, so open state is seeded from `collapsed`\n * rather than held inside it — a collapse kept only in the model would be dropped by the next\n * rebuild, and tasks are live ECHO objects.\n */\nexport const createTaskTreeModel = (\n tasks: readonly Task.Task[],\n { collapsed, groups, groupByStatus, translationKey, hierarchical = true }: TaskTreeModelOptions = {},\n): StaticTreeModel<TaskNode> =>\n createStaticTreeModel(buildTaskTree(tasks, { groups, groupByStatus, hierarchical }), {\n getChildren: (node) => node.children,\n getProps: (node) => ({\n label: groupLabel(node, translationKey),\n // `group` makes `Tree` render a section header and splice the node out of the collection's\n // topology, so the keyboard never lands on a header.\n ...(node.status\n ? { disposition: 'group' as const }\n : node.group\n ? // A branch (its children make it one) rather than a `group` disposition: a section\n // header cannot be collapsed, and folding away the `Done` group is half the point.\n { testId: 'taskList.group' }\n : { testId: 'taskList.item' }),\n }),\n isOpen: (node) => !collapsed?.has(node.id),\n });\n\n/**\n * Tasks in the order the tree shows them, a collapsed branch contributing only its own row.\n *\n * Derived from the forest the tree renders rather than from a second walk, so the ordinals a reader\n * counts down cannot drift from the rows they number.\n */\nexport const flattenVisibleTasks = (root: TaskNode, collapsed?: ReadonlySet<string>): Task.Task[] => {\n const tasks: Task.Task[] = [];\n const visit = (nodes: readonly TaskNode[]): void => {\n for (const node of nodes) {\n if (node.task) {\n tasks.push(node.task);\n }\n if (!collapsed?.has(node.id)) {\n visit(node.children);\n }\n }\n };\n visit(root.children);\n return tasks;\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { RegistryContext } from '@effect/atom-react/RegistryContext';\nimport React, {\n type FC,\n type KeyboardEvent,\n type ReactNode,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n} from 'react';\n\nimport { Tree, type TreeDropEvent, type TreeNode, type TreeSelectEvent } from '@dxos/react-ui-list';\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport * as Icon from '@dxos/react-ui/Icon';\nimport * as Theme from '@dxos/react-ui/Theme';\nimport { type Task } from '@dxos/types';\n\nimport {\n type TaskPlacement,\n resolveIndent,\n resolveNudge,\n resolveOutdent,\n resolveReparent,\n resolveTaskPlacement,\n} from './hierarchy.ts';\nimport { type TaskDescriptionProps } from './TaskDescription.tsx';\nimport { useTaskListContext } from './TaskListContext.ts';\nimport {\n TASK_TREE_ROOT_ID,\n type TaskGroup,\n type TaskGroupHeader,\n type TaskNode,\n buildTaskPaths,\n buildTaskTree,\n createTaskTreeModel,\n} from './tree-model.ts';\n\n/**\n * The task list rendered as a Next `Tree`, so the machine owns disclosure, roving focus and the APG keymap. A row's\n * cells lay out on the Root's `columns` in DOM order: the disclosure (when the list can disclose), then the gutter,\n * status and title, then the trailing cells; the chips and the description are placed on the row's later lines.\n *\n * `Shift+Arrow` and `Tab` restructuring reach the consumer's key handler before the machine's, which ignores modified\n * arrows anyway; a taken key keeps focus on the moved row as it remounts under its new path.\n */\n\n/**\n * How a row was activated, so a host can tell a plain click from a modified one — e.g. opening the\n * task in a plank of its own rather than reusing the one the list reads into.\n */\nexport type TaskSelectModifiers = { meta?: boolean };\n\nexport type TaskItemProps = {\n node: TaskNode;\n /** The tree's disclosure cell, when the list discloses branches. */\n indicator?: ReactNode;\n ordinal?: number;\n checked: boolean;\n translationKey: string;\n showDescription: boolean;\n descriptionComponents?: TaskDescriptionProps['components'];\n onTaskCheck?: (task: Task.Task) => void;\n onTaskUpdate?: (task: Task.Task, patch: Task.Edit) => void;\n};\n\n/** A task's whole row, rendered into the tree's row; injected so this module need not know its cells. */\nexport type TaskItemRenderer = FC<TaskItemProps>;\n\nexport type TaskTreeNodeProps = {\n /** Render status headers with their tasks flat beneath, instead of the hierarchy. */\n groupByStatus?: readonly Task.Status[];\n /** Host-defined groups, rendered as collapsible headers with counts (see {@link TaskGroup}). */\n groups?: readonly TaskGroup[];\n /** Nest sub-tasks under their parent; off renders one row per task. */\n hierarchical?: boolean;\n tasks: readonly Task.Task[];\n collapsed: ReadonlySet<string>;\n /** Whether rows lead with a disclosure cell: a flat list has no branch to disclose. */\n toggle: boolean;\n ordinals: ReadonlyMap<string, number>;\n selected?: string;\n /** Ids of the checked rows; the gutter renders a checkbox instead of an ordinal once wired. */\n checked?: ReadonlySet<string>;\n translationKey: string;\n /** Render each task's description under its title; rows grow to fit. */\n showDescription?: boolean;\n /** Renderers for the description beyond the row's own. */\n descriptionComponents?: TaskDescriptionProps['components'];\n onCollapseToggle: (id: string) => void;\n onTaskCheck?: (task: Task.Task) => void;\n onTaskSelect?: (task: Task.Task | undefined, modifiers?: TaskSelectModifiers) => void;\n onTaskUpdate?: (task: Task.Task, patch: Task.Edit) => void;\n onTaskMove?: (task: Task.Task, placement: TaskPlacement) => void;\n /** The rows' column template (see `buildGridTemplate`); the edit pane lays out on the same tracks. */\n columns: string;\n renderItem: TaskItemRenderer;\n};\n\nexport const TaskTreeNode = ({\n groupByStatus,\n groups,\n hierarchical,\n tasks,\n collapsed,\n toggle,\n ordinals,\n selected,\n checked,\n columns,\n renderItem: Item,\n translationKey,\n showDescription = false,\n descriptionComponents,\n onCollapseToggle,\n onTaskCheck,\n onTaskSelect,\n onTaskUpdate,\n onTaskMove,\n}: TaskTreeNodeProps) => {\n const { flush } = useTaskListContext('TaskList.TreeNode');\n const { t } = Hooks.useTranslation(translationKey);\n const registry = useContext(RegistryContext);\n\n // Read at construction only. Keeping `collapsed` out of the memo's dependencies is what makes the\n // model identity stable across a toggle: `Tree` memoizes its walk on the model, so a new model on\n // every collapse rebuilt the collection and the branch never got to run its conceal animation.\n const collapsedRef = useRef(collapsed);\n collapsedRef.current = collapsed;\n const model = useMemo(\n () =>\n createTaskTreeModel(tasks, {\n collapsed: collapsedRef.current,\n groups,\n groupByStatus,\n translationKey,\n hierarchical,\n }),\n [tasks, groups, groupByStatus, translationKey, hierarchical],\n );\n // From the forest the model renders, so a grouped task's path runs through its group's header.\n const paths = useMemo(\n () => buildTaskPaths(buildTaskTree(tasks, { groups, groupByStatus, hierarchical })),\n [tasks, groups, groupByStatus, hierarchical],\n );\n\n // Selection is owned by `TaskList.Root`, so it is driven into the model rather than held there —\n // otherwise selecting a task elsewhere (or clearing it from the edit pane) leaves the tree's own\n // current state stale.\n const previousSelected = useRef<string | undefined>(undefined);\n useEffect(() => {\n const setCurrent = (id: string | undefined, current: boolean) => {\n const path = id && paths.get(id);\n if (!path) {\n return;\n }\n const atom = model.stateAtom(path);\n registry.set(atom, { ...registry.get(atom), current });\n };\n\n if (previousSelected.current !== selected) {\n setCurrent(previousSelected.current, false);\n previousSelected.current = selected;\n }\n setCurrent(selected, true);\n }, [selected, model, paths, registry]);\n\n // Written into the model rather than left to a rebuild, so the tree keeps its identity through the\n // disclosure animation, and mirrored onto the list's own collapsed set, which survives the model\n // being rebuilt when the task array changes. Collapse is keyed by id, which is unambiguous here:\n // a task has one parent, so it appears at exactly one path.\n const handleOpenChange = useCallback(\n ({ item, path, open }: { item: TaskNode; path: string[]; open: boolean }) => {\n const atom = model.stateAtom(path);\n registry.set(atom, { ...registry.get(atom), open });\n if (open === collapsedRef.current.has(item.id)) {\n onCollapseToggle(item.id);\n }\n },\n [model, registry, onCollapseToggle],\n );\n\n // A list without a selection handler highlights nothing, and a group header is never selected —\n // activating one discloses it instead.\n const canSelect = useCallback(({ item }: { item: TaskNode }) => !!onTaskSelect && !!item.task, [onTaskSelect]);\n\n // `meta` rides along so a host can distinguish a plain activation (read into the pane the list\n // reads into) from a modified one (open in its own plank), the way the nav tree does.\n const handleSelect = useCallback(\n ({ item, meta }: TreeSelectEvent<TaskNode>) => item.task && onTaskSelect?.(item.task, { meta }),\n [onTaskSelect],\n );\n\n // Restructuring is keyboard-driven: `Shift` moves the row where an unmodified arrow navigates —\n // up/down reorder among siblings, left/right change depth — and `Tab`/`Shift+Tab` change depth\n // too, as in an outliner. The focused row names its task through `data-object-id`, which is what\n // lets one tree-level handler serve every depth.\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLDivElement>) => {\n // A reader needs a way back out of a selection, and `Escape` is where they look for it.\n if (event.key === 'Escape' && selected) {\n event.preventDefault();\n onTaskSelect?.(undefined);\n return;\n }\n\n const tab = event.key === 'Tab';\n if (!onTaskMove || !(event.shiftKey || tab)) {\n return;\n }\n const target = event.target instanceof HTMLElement ? event.target : undefined;\n const row = target?.closest<HTMLElement>('[data-object-id]');\n // `Tab` only from the row itself: from a control inside it, `Tab` is how focus reaches the\n // next control, and taking it there would strand the reader in the row.\n if (!row || (tab && row !== target)) {\n return;\n }\n const id = row.getAttribute('data-object-id');\n const task = id ? tasks.find((task) => task.id === id) : undefined;\n if (!task) {\n return;\n }\n const placement = (() => {\n switch (event.key) {\n case 'Tab':\n return event.shiftKey ? resolveOutdent(tasks, task) : resolveIndent(tasks, task);\n case 'ArrowRight':\n return resolveIndent(tasks, task);\n case 'ArrowLeft':\n return resolveOutdent(tasks, task);\n case 'ArrowUp':\n return resolveNudge(tasks, task, 'up');\n case 'ArrowDown':\n return resolveNudge(tasks, task, 'down');\n default:\n return undefined;\n }\n })();\n // A key that moves nothing is left alone — for `Tab`, so focus can still leave the list\n // rather than being trapped on a row that cannot indent.\n if (placement) {\n event.preventDefault();\n event.stopPropagation();\n onTaskMove(task, placement);\n }\n },\n [tasks, selected, onTaskMove, onTaskSelect],\n );\n\n // The drop half of the gesture; the placement is resolved here because only the list knows the\n // task set the move is relative to. The source is looked up by id, since the drag payload's item\n // is untyped.\n const handleDrop = useCallback(\n ({ instruction, source, item, atEnd }: TreeDropEvent<TaskNode>) => {\n const sourceTask = tasks.find((task) => task.id === source.id);\n if (!onTaskMove || !sourceTask) {\n return;\n }\n\n // The end strip means one thing, which is \"last among the roots\".\n if (atEnd) {\n onTaskMove(sourceTask, { parentTask: null, before: undefined });\n return;\n }\n\n // The synthetic root and the group headers have no task, so a drop onto one is not a move.\n const targetTask = item.task;\n if (!targetTask || instruction.type === 'instruction-blocked') {\n return;\n }\n\n // The hitbox's instruction is taken as given: dropping onto a row makes the task its child,\n // and the reorder zones at the row's edges are what place it before or after instead.\n // `reparent` is the shallow band under a last child, and is the only way out of a subtree\n // for a drop past its final row — without it the task can only ever join that subtree.\n const placement =\n instruction.type === 'reparent'\n ? // `desiredLevel` is absolute, so the number of ancestors to climb is the drop in depth.\n resolveReparent(tasks, sourceTask, targetTask, instruction.currentLevel - instruction.desiredLevel)\n : resolveTaskPlacement({ tasks, source: sourceTask, target: targetTask, intent: instruction.type });\n if (placement) {\n onTaskMove(sourceTask, placement);\n }\n },\n [tasks, onTaskMove],\n );\n\n const renderRow = useCallback(\n (node: TreeNode<TaskNode>) => {\n const header = node.item?.group;\n if (node.group || !node.item) {\n return <Tree.Item node={node} />;\n }\n if (header) {\n return (\n <Tree.Item node={node}>\n <Tree.ItemIndicator />\n <TaskGroupHeading group={header} translationKey={translationKey} />\n </Tree.Item>\n );\n }\n\n const task = node.item.task;\n return (\n <Tree.Item node={node}>\n <Item\n node={node.item}\n indicator={toggle ? <Tree.ItemIndicator /> : undefined}\n ordinal={task && ordinals.get(task.id)}\n checked={!!task && !!checked?.has(task.id)}\n translationKey={translationKey}\n showDescription={showDescription}\n descriptionComponents={descriptionComponents}\n onTaskCheck={onTaskCheck}\n onTaskUpdate={onTaskUpdate}\n />\n </Tree.Item>\n );\n },\n [\n toggle,\n ordinals,\n checked,\n translationKey,\n showDescription,\n descriptionComponents,\n onTaskCheck,\n onTaskUpdate,\n Item,\n ],\n );\n\n return (\n <Tree.Root<TaskNode>\n id={TASK_TREE_ROOT_ID}\n model={model}\n columns={columns}\n // Chips and the description sit on the row's later lines, so a row grows to fit them; the window\n // measures each row rather than assuming one pitch, and mounts only those in view, since every\n // row carries several menus and tooltips and a project's list runs to hundreds of tasks.\n multiline\n virtual='measured'\n draggable={!!onTaskMove}\n // Any task can gain a sub-task, so a childless peer is still a drop target — without this the\n // hitbox offers no make-child zone on one, and so no drop indicator either.\n leavesAcceptChildren\n // Dragging past the last row is the obvious way to say \"put it last\"; without a target there\n // the sticky rows keep the previous instruction and the drop lands somewhere else entirely.\n dropAtEnd\n indentGuides\n // Deliberately NOT `selectionFollowsFocus`: selecting a task opens its detail (a companion, or\n // a plank), so arrows that selected as they travelled would open every row the reader passes\n // on the way to the one they want. The arrows move focus and `Enter` commits it.\n canSelect={canSelect}\n onOpenChange={handleOpenChange}\n onSelect={handleSelect}\n onKeyDown={handleKeyDown}\n onDrop={handleDrop}\n >\n <Tree.Label srOnly>{t('task-list.label')}</Tree.Label>\n {/* Rows pad their ends as any tree's do, so the highlight runs edge to edge; a flush list's host frames it. */}\n <Tree.Content rowInset={!flush}>{renderRow}</Tree.Content>\n </Tree.Root>\n );\n};\n\n/**\n * A group's header: its icon, label and how many tasks it holds, spanning the row from after the\n * disclosure up to the trailing controls. The count is of the group's tasks, sub-tasks included, so\n * it matches what expanding shows.\n */\nconst TaskGroupHeading = ({ group, translationKey }: { group: TaskGroupHeader; translationKey: string }) => {\n const { t } = Hooks.useTranslation(translationKey);\n return (\n <div className='col-[2/assignee] flex min-w-0 items-center gap-2' data-testid='taskList.group.header'>\n {group.icon && <Icon.Icon icon={group.icon} size='md' classNames={group.iconClassNames} />}\n <span className='truncate font-medium'>{Theme.toLocalizedString(group.label, t)}</span>\n <span className='text-sm text-fg-muted' data-testid='taskList.group.count'>\n {group.count}\n </span>\n </div>\n );\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { DX_POPOVER_CONTENT_ATTR } from '@dxos/ui-types';\n\n/** Delay before hover opens the preview — long enough that crossing the anchor en route elsewhere does not fire it. */\nconst HOVER_OPEN_DELAY = 100;\n\n/** Grace period after the pointer leaves the anchor/card before the preview closes, so it can travel between them. */\nconst HOVER_CLOSE_DELAY = 300;\n\nexport type AnchorHoverOptions = {\n /** The element the preview is anchored to; the pointer over it or over the open card keeps the card open. */\n anchor: Element;\n open: () => void;\n close: () => void;\n};\n\n/**\n * Hover intent for an element that previews an object in a popover card: opens after a short hover,\n * closes once the pointer has left both the anchor and the card, and stops closing on leave once the\n * reader interacts with the card.\n */\n// TODO(wittjosiah): Move to a shared UI package and use it from `dx-anchor`, which carries a copy of this logic.\nexport class AnchorHover {\n readonly #anchor: Element;\n readonly #open: () => void;\n readonly #close: () => void;\n\n #openTimer: ReturnType<typeof setTimeout> | undefined;\n #closeTimer: ReturnType<typeof setTimeout> | undefined;\n\n /** True while a card opened by hover (not click) is showing; only then does leaving dismiss it. */\n #hoverOpen = false;\n\n constructor({ anchor, open, close }: AnchorHoverOptions) {\n this.#anchor = anchor;\n this.#open = open;\n this.#close = close;\n }\n\n enter(pointerType?: string): void {\n if (pointerType === 'touch') {\n return;\n }\n this.#cancelClose();\n if (this.#hoverOpen || this.#openTimer !== undefined) {\n return;\n }\n this.#openTimer = setTimeout(() => {\n this.#openTimer = undefined;\n this.#openFromHover();\n }, HOVER_OPEN_DELAY);\n }\n\n leave(): void {\n this.#cancelOpen();\n if (this.#hoverOpen) {\n this.#scheduleClose();\n }\n }\n\n blur(): void {\n if (this.#hoverOpen) {\n this.#scheduleClose();\n }\n }\n\n /** Cancels pending timers and stops tracking the pointer; a card already showing stays open. */\n reset(): void {\n this.#cancelOpen();\n this.#cancelClose();\n this.#hoverOpen = false;\n document.removeEventListener('pointerover', this.#handleDocumentPointerOver);\n document.removeEventListener('pointerdown', this.#handleDocumentPointerDown, { capture: true });\n }\n\n #openFromHover(): void {\n this.#hoverOpen = true;\n document.addEventListener('pointerover', this.#handleDocumentPointerOver);\n document.addEventListener('pointerdown', this.#handleDocumentPointerDown, { capture: true });\n this.#open();\n }\n\n #cancelOpen(): void {\n if (this.#openTimer !== undefined) {\n clearTimeout(this.#openTimer);\n this.#openTimer = undefined;\n }\n }\n\n #cancelClose(): void {\n if (this.#closeTimer !== undefined) {\n clearTimeout(this.#closeTimer);\n this.#closeTimer = undefined;\n }\n }\n\n #scheduleClose(): void {\n if (this.#closeTimer === undefined) {\n this.#closeTimer = setTimeout(() => {\n this.#closeTimer = undefined;\n this.reset();\n this.#close();\n }, HOVER_CLOSE_DELAY);\n }\n }\n\n #handleDocumentPointerOver = (event: PointerEvent): void => {\n const target = event.target;\n const inside =\n target instanceof Element && (this.#anchor.contains(target) || !!target.closest(`[${DX_POPOVER_CONTENT_ATTR}]`));\n if (inside) {\n this.#cancelClose();\n } else {\n this.#scheduleClose();\n }\n };\n\n #handleDocumentPointerDown = (event: PointerEvent): void => {\n // Interacting with the card pins it: content opened from it (a toolbar menu, a dialog) portals\n // outside the card element, so leave-to-close must stop the moment the reader starts using it.\n const target = event.target;\n if (target instanceof Element && target.closest(`[${DX_POPOVER_CONTENT_ATTR}]`)) {\n this.reset();\n }\n };\n}\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { type KeyboardEvent, type MouseEvent, type PointerEvent, useCallback, useEffect, useMemo, useRef } from 'react';\n\nimport { DxAnchorActivate } from '@dxos/ui-types';\n\nimport { AnchorHover } from './anchor-hover.ts';\n\nexport type PreviewAnchorOptions = {\n /** URI of the object the chip names; without one the chip does nothing. */\n eid?: string;\n label: string;\n /** Card title, for an object whose own label would fall back to its type's placeholder. */\n title?: string;\n};\n\n/**\n * Wires a chip to the object it names: hovering shows the object's card, leaving closes it, and a\n * click (or Enter/Space) opens the object itself. Spread the result onto the chip.\n */\nexport const usePreviewAnchor = ({ eid, label, title }: PreviewAnchorOptions) => {\n const elementRef = useRef<HTMLElement | null>(null);\n const hoverRef = useRef<AnchorHover | undefined>(undefined);\n\n const dispatch = useCallback(\n (props?: { state?: false; navigate?: true }) => {\n const trigger = elementRef.current;\n if (trigger && eid) {\n trigger.dispatchEvent(new DxAnchorActivate({ trigger, eid, label, kind: 'card', title, ...props }));\n }\n },\n [eid, label, title],\n );\n\n // The controller lives as long as the element, so it reads the latest `dispatch` through a ref.\n const dispatchRef = useRef(dispatch);\n useEffect(() => {\n dispatchRef.current = dispatch;\n }, [dispatch]);\n\n const ref = useCallback((element: HTMLElement | null) => {\n hoverRef.current?.reset();\n elementRef.current = element;\n hoverRef.current = element\n ? new AnchorHover({\n anchor: element,\n open: () => dispatchRef.current(),\n close: () => dispatchRef.current({ state: false }),\n })\n : undefined;\n }, []);\n\n const openObject = useCallback(() => {\n hoverRef.current?.reset();\n dispatch({ navigate: true });\n }, [dispatch]);\n\n return useMemo(\n () => ({\n ref,\n onPointerEnter: (event: PointerEvent<HTMLElement>) => {\n if (eid) {\n hoverRef.current?.enter(event.pointerType);\n }\n },\n onPointerLeave: () => hoverRef.current?.leave(),\n onBlur: () => hoverRef.current?.blur(),\n onClick: (event: MouseEvent<HTMLElement>) => {\n // The row is an option: without this the click selects the task as well as opening the object.\n event.stopPropagation();\n openObject();\n },\n onKeyDown: (event: KeyboardEvent<HTMLElement>) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n event.stopPropagation();\n openObject();\n }\n },\n }),\n [ref, eid, openObject],\n );\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { type PropsWithChildren, useCallback, useMemo, useRef, useState } from 'react';\n\nimport { Tag as EchoTag, Filter, Obj, type Ref } from '@dxos/echo';\nimport { useObject, useQuery } from '@dxos/echo-react';\nimport { Tree } from '@dxos/react-ui-list';\nimport { ActionMenu, type MenuAction, type MenuItem, executeMenuAction, fallbackIcon } from '@dxos/react-ui-menu';\nimport * as Button from '@dxos/react-ui/Button';\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport * as Icon from '@dxos/react-ui/Icon';\nimport * as Layout from '@dxos/react-ui/Layout';\nimport * as Tag from '@dxos/react-ui/Tag';\nimport * as Theme from '@dxos/react-ui/Theme';\nimport * as Util from '@dxos/react-ui/Util';\nimport { type Actor, PullRequest, Task } from '@dxos/types';\nimport { mx, toHue } from '@dxos/ui-theme';\nimport { type ComposableProps } from '@dxos/ui-types';\n\nimport { translationKey } from '#translations';\n\nimport { useAssigneeDisplay } from '../../hooks/index.ts';\nimport { STATUS_ORDER } from '../../util/status-icons.ts';\nimport { type TaskPlacement } from './hierarchy.ts';\nimport { TaskDescription, type TaskDescriptionProps } from './TaskDescription.tsx';\nimport { TaskListProvider, useTaskListContext } from './TaskListContext.ts';\nimport { TaskListEditor, type TaskListEditorProps } from './TaskListEditor.tsx';\nimport {\n TaskCheckbox,\n TaskEstimateControl,\n TaskMnemonic,\n TaskPriorityIcon,\n TaskStatusControl,\n} from './TaskRowCells.tsx';\nimport { type TaskItemProps, type TaskSelectModifiers, TaskTreeNode } from './TaskTreeNode.tsx';\nimport { TRACK } from './tracks.ts';\nimport {\n type TaskGroup,\n buildTaskForest,\n buildTaskGroups,\n flattenVisibleTasks,\n taskGroupNodeId,\n} from './tree-model.ts';\nimport { usePreviewAnchor } from './usePreviewAnchor.ts';\n\n/** Shared empty set, so a list with nothing in flight does not allocate one per render. */\nconst EMPTY_IDS: ReadonlySet<string> = new Set<string>();\n\n/** Shared empty map, for a tree rendered without the ordinal gutter. */\nconst EMPTY_ORDINALS: ReadonlyMap<string, number> = new Map<string, number>();\n\n//\n// Root — headless context provider. Renders no DOM.\n//\n\ntype TaskListRootProps = PropsWithChildren<{\n tasks: readonly Task.Task[];\n\n //\n // Structure.\n //\n\n /**\n * Group rows into status sections (Linear order); flat list otherwise.\n */\n groupByStatus?: boolean;\n /**\n * Partition the rows under collapsible group headers, each with its count — the host decides the\n * partition (by status, priority, assignee, milestone…) and the order, of groups and within each.\n * Supersedes `groupByStatus`. With `hierarchical`, sub-tasks still nest under a parent in the same\n * group. A group's collapsed state lives in `collapsed` beside the branches', under the group's\n * node id (`taskGroupNodeId`).\n */\n groups?: readonly TaskGroup[];\n /**\n * Render the set as the tree it stores (`Task.subtasks`), not as status groups — the two are\n * mutually exclusive, since a tree regrouped by status is no longer a tree.\n */\n hierarchical?: boolean;\n /**\n * Ids of the branches whose sub-tasks are hidden (controlled). Collapsed rather than expanded\n * ids, because a branch is open by default: tracking the expanded set would render a task's new\n * first sub-task hidden, the moment adding it made its parent a branch. Per viewer and per list —\n * a collapsed branch is not a property of the work — so this is state, not stored on the object.\n */\n collapsed?: ReadonlySet<string>;\n\n //\n // Selection.\n //\n\n /**\n * Selected task id (controlled); omit to let the list track the last row clicked.\n */\n selected?: string;\n /**\n * Makes the list selectable without a controlled `selected` or an `onTaskSelect` — for a host\n * whose selection consumers (e.g. `Edit`) live inside the list's own context.\n */\n selectable?: boolean;\n /**\n * Ids of the checked rows (controlled, and only meaningful with `onTaskCheck`). Deliberately\n * separate from `selected`: the current row is where the reader is, the checked set is what an\n * action will act on, and a row is routinely both.\n */\n checked?: ReadonlySet<string>;\n\n //\n // What a row shows.\n //\n\n /** Render the status heading above each group; grouping order is kept either way. */\n showGroupLabels?: boolean;\n /** Number rows 1..N down the list as rendered, so tasks can be referenced by ordinal. */\n showOrdinals?: boolean;\n /** Render each task's estimate beside the priority control. Off by default. */\n showEstimates?: boolean;\n /** Render who holds each task; off collapses the assignee track. On by default. */\n showAssignees?: boolean;\n /** Render each task's mnemonic before its title. On by default. */\n showMnemonics?: boolean;\n /** Rows and the edit pane run to the host's edges, with no inline inset (a list inside a framed host). */\n flush?: boolean;\n /**\n * Render each task's description under its title; rows grow to fit. Off by default, so a\n * single-line list (e.g. the chat strip) keeps one row per task.\n */\n showDescription?: boolean;\n /** Renderers for a row's description beyond its own — a host's link anchor, say. */\n descriptionComponents?: TaskDescriptionProps['components'];\n\n //\n // Callbacks. Wiring one is what enables the affordance that calls it — the list never writes.\n //\n\n /**\n * Trailing menu for a row. One item renders as a plain icon button, several as a `…` menu, none as\n * nothing — so delete is an ordinary contributed action rather than a special case of its own.\n */\n getTaskActions?: (task: Task.Task) => MenuItem[];\n /**\n * Enables `Create`; called with a draft carrying at least the trimmed title, and the files dropped\n * on the create pane (`Editor`'s `acceptFiles`) for the host to store and attach once it exists.\n */\n onTaskCreate?: TaskCreateHandler;\n /**\n * Enables the row's edit controls. Every mutation is delegated.\n */\n onTaskUpdate?: (task: Task.Task, patch: Task.Edit) => void;\n /**\n * Row click, and `Escape` — which passes `undefined`, since a reader needs a way back out of a\n * selection. Wiring it (or `selected`) makes the list selectable, so the row shows as selected.\n */\n onTaskSelect?: (task: Task.Task | undefined, modifiers?: TaskSelectModifiers) => void;\n /**\n * Enables the gutter checkbox, called with the row toggled. The host owns the set — this list is\n * embedded in surfaces whose toolbars read the same selection — so nothing is tracked here.\n */\n onTaskCheck?: (task: Task.Task) => void;\n /**\n * Enables restructuring by drag and by keyboard; called with the one move the gesture means.\n * `MoveTask` takes exactly this pair, so a drop is a single mutation rather than a re-parent\n * followed by a reposition.\n */\n onTaskMove?: (task: Task.Task, placement: TaskPlacement) => void;\n /**\n * Enables collapsing/expanding a task's sub-tasks; called with the new set of collapsed ids.\n */\n onCollapsedChange?: (collapsed: ReadonlySet<string>) => void;\n}>;\n\n/**\n * What a create came to. `error` means no task was created, so the pane keeps the whole draft;\n * `rejectedFiles` are files the task was created without, which the pane keeps to retry. Nothing\n * (or neither field) means the task and every file were taken.\n */\nexport type TaskCreateResult = { error?: unknown; rejectedFiles?: readonly File[] };\n\n/** Creates a task from the pane's draft; see {@link TaskCreateResult} for what it may report back. */\nexport type TaskCreateHandler = (\n task: Task.Draft,\n files?: readonly File[],\n) => void | TaskCreateResult | Promise<void | TaskCreateResult>;\n\nconst TaskListRoot = ({\n children,\n tasks,\n groupByStatus = true,\n groups,\n showGroupLabels = true,\n showOrdinals = false,\n showDescription = false,\n descriptionComponents,\n showEstimates = false,\n showAssignees = true,\n showMnemonics = true,\n flush = false,\n hierarchical = false,\n collapsed,\n selected: selectedProp,\n selectable: selectableProp,\n checked = EMPTY_IDS,\n getTaskActions,\n onTaskCreate,\n onTaskUpdate,\n onTaskSelect,\n onTaskCheck,\n onTaskMove,\n onCollapsedChange,\n}: TaskListRootProps) => {\n // Uncontrolled by default: a host that only wants the click callback still gets the selected\n // styling, and one that owns the selection passes `selected`.\n const [selectedState, setSelectedState] = useState<string | undefined>(selectedProp);\n const selected = selectedProp ?? selectedState;\n const selectable = selectableProp ?? (!!onTaskSelect || selectedProp !== undefined);\n\n // Passing `undefined` clears the selection — what `Escape` on a row and the edit pane's buttons do.\n const handleSelect = useCallback(\n (task: Task.Task | undefined, modifiers?: TaskSelectModifiers) => {\n setSelectedState(task?.id);\n onTaskSelect?.(task, modifiers);\n },\n [onTaskSelect],\n );\n\n // Controlled once the host passes the set or listens for it, even while the set is still\n // undefined, so a host clearing it is not mistaken for handing control back to the list.\n const collapsedControlled = collapsed !== undefined || !!onCollapsedChange;\n const [collapsedState, setCollapsedState] = useState<ReadonlySet<string>>(EMPTY_IDS);\n const collapsedIds = collapsedControlled ? (collapsed ?? EMPTY_IDS) : collapsedState;\n // Read through a ref so two toggles before a re-render (a drag collapsing and reopening its\n // branch) each build on the other.\n const collapsedIdsRef = useRef(collapsedIds);\n collapsedIdsRef.current = collapsedIds;\n const isCollapsed = useCallback((id: string) => collapsedIds.has(id), [collapsedIds]);\n const onCollapseToggle = useCallback(\n (id: string) => {\n const next = new Set(collapsedIdsRef.current);\n if (next.has(id)) {\n next.delete(id);\n } else {\n next.add(id);\n }\n collapsedIdsRef.current = next;\n if (!collapsedControlled) {\n setCollapsedState(next);\n }\n onCollapsedChange?.(next);\n },\n [collapsedControlled, onCollapsedChange],\n );\n\n // The checkbox shares the ordinal's gutter, so a checkable list reserves the track even when it\n // shows no numbers. A movable one does not: the whole row is the drag source, and a track held\n // for a handle that no longer exists only pushed every title one square right.\n // The leading column holds the ordinal (or the checkbox in its place) and the mnemonic.\n const showGutter = showOrdinals || showMnemonics || !!onTaskCheck;\n // The menu column takes the rows' actions, and the edit pane's cancel in an editable list.\n const hasActions = !!getTaskActions || !!onTaskCreate || !!onTaskUpdate;\n const columns = useMemo(\n () =>\n buildGridTemplate({\n toggle: hierarchical || !!groups,\n // The checkbox, and the one button that shows the number and copies the reference.\n gutter: (onTaskCheck ? 1 : 0) + (showOrdinals || showMnemonics ? 1 : 0),\n showEstimates,\n showAssignees,\n hasActions,\n }),\n [hierarchical, groups, showOrdinals, onTaskCheck, showMnemonics, showEstimates, showAssignees, hasActions],\n );\n\n return (\n <TaskListProvider\n tasks={tasks}\n columns={columns}\n // Not gated on `!hierarchical` any more: the tree expresses a status group as a `group` node,\n // so grouping and hierarchy are a choice rather than mutually exclusive capabilities.\n groupByStatus={groupByStatus}\n groups={groups}\n showGroupLabels={showGroupLabels}\n showOrdinals={showOrdinals}\n showDescription={showDescription}\n descriptionComponents={descriptionComponents}\n showEstimates={showEstimates}\n hierarchical={hierarchical}\n showGutter={showGutter}\n showAssignees={showAssignees}\n showMnemonics={showMnemonics}\n flush={flush}\n isCollapsed={isCollapsed}\n selected={selected}\n checked={checked}\n getTaskActions={getTaskActions}\n onCollapseToggle={onCollapseToggle}\n onTaskCreate={onTaskCreate}\n onTaskUpdate={onTaskUpdate}\n onTaskSelect={selectable ? handleSelect : undefined}\n onTaskCheck={onTaskCheck}\n onTaskMove={onTaskMove}\n >\n {children}\n </TaskListProvider>\n );\n};\n\nTaskListRoot.displayName = 'TaskList.Root';\n\n//\n// Viewport — bounds the rows, which scroll inside it (the tree's content is its own scroll area).\n// `Create` sits outside it, so the add row stays pinned while the rows scroll.\n//\n\ntype TaskListViewportProps = ComposableProps<{\n /** Caps the height at exactly this many rows, so a longer list scrolls without showing a partial row. */\n rows?: number;\n}>;\n\nconst TaskListViewport = Util.composable<HTMLDivElement, TaskListViewportProps>(\n ({ children, rows: rowsProp, ...props }, forwardedRef) => {\n const { className, style, ...rest } = Util.composableProps(props);\n // Whole rows only: a fractional count would cut through the next row.\n const rows = rowsProp === undefined ? undefined : Math.max(Math.floor(rowsProp), 0);\n return (\n <div\n {...rest}\n className={mx('flex flex-col min-h-0 dx-shrink', className)}\n // Each one-line row is one block tall, with no gap between rows.\n style={rows === undefined ? style : { ...style, maxHeight: `calc(${rows} * var(--dx-block-size))` }}\n ref={forwardedRef}\n >\n {children}\n </div>\n );\n },\n);\n\nTaskListViewport.displayName = 'TaskList.Viewport';\n\n//\n// Content — the rows, grouped by status when the root says so.\n//\n\n/** Ordinals stop at 99: the gutter is sized for two digits. */\nconst MAX_ORDINAL = 99;\n\n/**\n * One column template per list, built from its options and shared by the tree's rows and the edit pane (the create row\n * sits outside the scrolling tree, in a grid of its own), so the pane's icon sits under the rows' status controls and\n * its field starts where their titles do.\n *\n * Every cell is placed by its track's name ({@link TRACK}), so a track exists only when its option is on and an absent\n * cell leaves its track empty. The first track is the tree's disclosure — at no width in a flat list — and goes unnamed:\n * the row wraps its columns in `[content-start] …`, and two bracketed names with no track between them invalidate the\n * template. The disclosure is the one cell not placed by name; it flows into that first track.\n */\nconst buildGridTemplate = ({\n toggle,\n gutter,\n showEstimates,\n showAssignees,\n hasActions,\n}: {\n /** A hierarchical list has branches to disclose; a flat one holds no square for a chevron. */\n toggle: boolean;\n /** How many blocks the leading column holds: the ordinal (or the checkbox) and the mnemonic, each a block. */\n gutter: number;\n showEstimates: boolean;\n showAssignees: boolean;\n hasActions: boolean;\n}): string => {\n const candidates: (readonly [name: string | undefined, size: string] | false)[] = [\n // The tree's disclosure cell and indent step are half a block, so a guide lands under its branch's chevron.\n [undefined, toggle ? 'var(--dx-half-block-size)' : '0px'],\n // Fixed rather than `auto`, so the edit pane, which leaves it empty, keeps the rows' width for it.\n gutter > 0 && ['gutter', `calc(${gutter} * var(--dx-block-size))`],\n ['status', 'var(--dx-block-size)'],\n ['title', 'minmax(0, 1fr)'],\n // Sized by its content: a row with no pull request holds no width for one.\n ['artifacts', 'auto'],\n // Kept, at no width, when hidden: the description spans `title / assignee` and so needs the line.\n ['assignee', showAssignees ? 'var(--dx-block-size)' : '0px'],\n showEstimates && ['estimate', 'var(--dx-block-size)'],\n ['priority', 'var(--dx-block-size)'],\n hasActions && ['actions', 'var(--dx-block-size)'],\n ];\n return candidates\n .filter((track) => track !== false)\n .map(([name, size]) => (name ? `[${name}] ${size}` : size))\n .join(' ');\n};\n\n/**\n * No props: the part renders no element of its own — it is the tree, which takes neither a class\n * list nor a ref, so anything accepted here would be dropped silently.\n */\ntype TaskListContentProps = {};\n\nconst TaskListContent = (_props: TaskListContentProps) => {\n const {\n tasks,\n groupByStatus,\n groups,\n hierarchical,\n selected,\n checked,\n showGroupLabels,\n showOrdinals,\n showDescription,\n descriptionComponents,\n showGutter,\n columns,\n isCollapsed,\n onCollapseToggle,\n onTaskCheck,\n onTaskSelect,\n onTaskUpdate,\n onTaskMove,\n } = useTaskListContext('TaskList.Content');\n // Collapsed ids live in `Root`; read through the callback so a flip still recomputes.\n // A group's header collapses like a branch, so its node id joins the tasks' in the same set.\n const collapsed = useMemo(\n () =>\n new Set(\n [...tasks.map((task) => task.id), ...(groups ?? []).map((group) => taskGroupNodeId(group))].filter(isCollapsed),\n ),\n [tasks, groups, isCollapsed],\n );\n\n // Grouping and hierarchy are alternatives: a status group holds its tasks flat, because a\n // sub-task's status need not match its parent's.\n const grouping = !groups && !hierarchical && groupByStatus && showGroupLabels ? STATUS_ORDER : undefined;\n\n // Numbered down the list as rendered, 1..N. Flat either way: an ordinal names a task (\"run 3\"),\n // where a `1.2.1` path would renumber a whole branch.\n const ordinals = useMemo(() => {\n const ordered = groups\n ? flattenVisibleTasks(buildTaskGroups(groups, hierarchical), collapsed)\n : grouping\n ? grouping.flatMap((status) => tasks.filter((task) => (task.status ?? 'todo') === status))\n : hierarchical\n ? flattenVisibleTasks(buildTaskForest(tasks), collapsed)\n : tasks;\n // Past 99 the number outgrows the gutter, so it is dropped rather than shrunk.\n return new Map(ordered.flatMap((task, index) => (index < MAX_ORDINAL ? [[task.id, index + 1] as const] : [])));\n }, [tasks, collapsed, groups, grouping, hierarchical]);\n\n // One path: every mode renders through `Tree`. A flat list is a tree of depth one, and a status\n // group is a `group` node the machine splices out of its own topology.\n return (\n <TaskTreeNode\n descriptionComponents={descriptionComponents}\n hierarchical={hierarchical}\n groupByStatus={grouping}\n groups={groups}\n tasks={tasks}\n collapsed={collapsed}\n toggle={hierarchical || !!groups}\n columns={columns}\n ordinals={showOrdinals ? ordinals : EMPTY_ORDINALS}\n selected={selected}\n checked={checked}\n showDescription={showDescription}\n renderItem={TaskListItem}\n translationKey={translationKey}\n onCollapseToggle={onCollapseToggle}\n onTaskCheck={onTaskCheck}\n onTaskSelect={onTaskSelect}\n onTaskUpdate={onTaskUpdate}\n onTaskMove={onTaskMove}\n />\n );\n};\n\nTaskListContent.displayName = 'TaskList.Content';\n\n//\n// GroupLabel\n//\n\ntype TaskListGroupLabelProps = ComposableProps;\n\nconst TaskListGroupLabel = Util.composable<HTMLDivElement>(({ children, ...props }, forwardedRef) => {\n const { className, ...rest } = Util.composableProps(props);\n return (\n <div\n {...rest}\n className={mx('col-span-full min-h-(--dx-control) flex items-center text-sm text-fg-muted', className)}\n ref={forwardedRef}\n >\n <span>{children}</span>\n </div>\n );\n});\n\nTaskListGroupLabel.displayName = 'TaskList.GroupLabel';\n\n/**\n * A task's row in the tree, read-only apart from its own controls: each cell placed by its track's name, as the edit\n * pane's are, so the pane's fields sit where these values are read.\n */\nconst TaskListItem = ({\n node,\n indicator,\n ordinal,\n checked,\n translationKey: itemTranslationKey,\n showDescription,\n descriptionComponents,\n onTaskCheck,\n onTaskUpdate,\n}: TaskItemProps) => {\n const { t } = Hooks.useTranslation(itemTranslationKey);\n const { showGutter, showMnemonics, showAssignees, showEstimates } = useTaskListContext('TaskList.Item');\n const task = node.task;\n // Subscribed per row: the model is rebuilt from the task array, whose identity a property edit does not change, so\n // a rename made anywhere else would leave the row showing its old title. Read through the snapshot; the controls\n // still take the live object, which is what they write to.\n const [snapshot] = useObject(task);\n const current = snapshot ?? task;\n if (!task || !current) {\n return null;\n }\n\n const description = showDescription ? current.description?.trim() || undefined : undefined;\n\n return (\n <>\n {/* The one cell not placed by name: it flows into the template's unnamed first track. */}\n {indicator}\n {showGutter && (\n <Layout.Flex center gap='xs' classNames={TRACK.gutter}>\n {onTaskCheck && <TaskCheckbox task={task} checked={checked} onCheckedChange={onTaskCheck} />}\n {/* One button for the number and the reference it copies. Not beside a checkbox: a number beside a box reads\n as two ways to act on the row. The live task, not the snapshot: only the live object knows its space,\n which the copied URI names. */}\n {(showMnemonics || ordinal !== undefined) && (\n <TaskMnemonic task={task} ordinal={onTaskCheck ? undefined : ordinal} />\n )}\n </Layout.Flex>\n )}\n <TaskStatusControl task={task} onTaskUpdate={onTaskUpdate} classNames={TRACK.status} />\n {/* Inset as the editor's fields are, so a title reads at the x the field below types it. The placeholder is\n drawn by CSS so the element's text stays the title itself. */}\n <span\n data-testid='taskList.item.title'\n data-placeholder={t('task-title.placeholder')}\n className={mx(\n TRACK.title,\n 'min-w-0 truncate px-(--dx-gap-size) self-center',\n 'empty:before:text-placeholder empty:before:content-[attr(data-placeholder)]',\n )}\n >\n {current.title}\n </span>\n {/* On the title line, beside who has the task: the pull request is what the row is scanned for once work is under\n way, and on a line of its own it pushed the description down. */}\n <Layout.Flex\n align='center'\n gap='xs'\n classNames={mx(TRACK.artifacts, 'ps-1')}\n data-testid='taskList.item.artifacts'\n >\n <TaskListItemArtifacts task={task} filter={(artifact) => PullRequest.instanceOf(artifact)} />\n </Layout.Flex>\n {showAssignees && current.assignee && (\n <Layout.Grid center classNames={TRACK.assignee}>\n <TaskListAssignee assignee={current.assignee} iconOnly />\n </Layout.Grid>\n )}\n {showEstimates && <TaskEstimateControl task={task} classNames={TRACK.estimate} />}\n <TaskPriorityIcon task={task} classNames={TRACK.priority} />\n <TaskListItemActions task={task} classNames={TRACK.actions} />\n\n {/* The row's second line, under the title; it takes no height when the task has no chips. */}\n <Layout.Flex align='center' classNames='col-[title] row-start-2 empty:hidden' data-testid='taskList.item.chips'>\n <TaskListItemTags task={task} tags={Obj.getMeta(task).tags} />\n </Layout.Flex>\n {/* Under the title and the chips, clearing the gutter and the status control so it does not read as the row\n above's, and stopping short of the trailing controls. What the task says, and nothing the log recorded. */}\n {description && (\n <TaskDescription\n content={description}\n components={descriptionComponents}\n // A margin, not padding: the box is the clamp, three lines tall.\n classNames='col-[title/assignee] row-start-3 min-w-0 px-(--dx-gap-size) mb-1'\n />\n )}\n </>\n );\n};\n\n//\n// Item actions — the trailing cell of a row.\n//\n\nconst isMenuAction = (item: MenuItem): item is MenuAction => 'data' in item && typeof item.data === 'function';\n\n/**\n * A row's contributed actions. One is a plain button — a `…` menu hiding a single item costs a click\n * to discover nothing — and several collapse into the overflow menu, matching the nav tree's rows.\n */\nconst TaskListItemActions = ({ task, classNames }: { task: Task.Task; classNames?: string }) => {\n const { t } = Hooks.useTranslation(translationKey);\n const { getTaskActions } = useTaskListContext('TaskList.ItemActions');\n const actions = useMemo(() => getTaskActions?.(task) ?? [], [getTaskActions, task]);\n\n if (actions.length === 0) {\n return null;\n }\n\n const [only] = actions;\n if (actions.length === 1 && isMenuAction(only)) {\n return (\n <Tree.ItemActions>\n <Layout.Block classNames={classNames}>\n <Button.Root\n variant='ghost'\n iconOnly\n icon={only.properties?.icon ?? fallbackIcon}\n label={Theme.toLocalizedString(only.properties?.label, t)}\n data-testid={only.properties?.testId}\n onClick={(event) => {\n // The row is the selection target; running its action must not also select it.\n event.stopPropagation();\n void executeMenuAction(only);\n }}\n />\n </Layout.Block>\n </Tree.ItemActions>\n );\n }\n\n return (\n <Tree.ItemActions>\n <Layout.Block classNames={classNames}>\n {/* The button is the trigger, not the block: the button stops the click so the row is not\n selected too, and a trigger above it would never receive it. */}\n <ActionMenu deferUntilOpen actions={actions}>\n <Button.Root\n variant='ghost'\n iconOnly\n icon='ph--dots-three-vertical--regular'\n label={t('task-actions.label')}\n data-testid='taskList.item.actions'\n onClick={(event) => event.stopPropagation()}\n />\n </ActionMenu>\n </Layout.Block>\n </Tree.ItemActions>\n );\n};\n\nTaskListItemActions.displayName = 'TaskList.ItemActions';\n\n/**\n * What a task produced, one tag each. Queried rather than read off `ref.target`: on a cold load the\n * targets are not in memory yet, and a sync read would leave the row permanently empty.\n */\nconst TaskListItemArtifacts = ({ task, filter }: { task: Task.Task; filter?: (obj: Obj.Unknown) => boolean }) => {\n const db = Obj.getDatabase(task);\n const ids = useMemo(\n () =>\n (task.artifacts ?? []).flatMap((ref) => {\n const id = Task.refEntityId(ref);\n return id ? [id] : [];\n }),\n [task.artifacts],\n );\n const queried = useQuery(ids.length > 0 ? db : undefined, Filter.id(...ids));\n // Without a database — a story, a preview — the refs were made from objects already in hand,\n // so their targets resolve synchronously and the row still shows what the task produced.\n const resolved = db ? queried : (task.artifacts ?? []).flatMap((ref) => (ref.target ? [ref.target] : []));\n const artifacts = filter ? resolved.filter(filter) : resolved;\n\n return (\n <>\n {artifacts.map((artifact) => (\n <ArtifactTag key={artifact.id} artifact={artifact} />\n ))}\n </>\n );\n};\n\nTaskListItemArtifacts.displayName = 'TaskList.ItemArtifacts';\n\n/**\n * Everything a task carries as a chip: its tags, what it produced, and who has it.\n */\nexport const TaskTags = ({ task }: { task: Task.Task }) => {\n return (\n <>\n <TaskListItemTags task={task} tags={Obj.getMeta(task).tags} />\n <TaskListItemArtifacts task={task} filter={(artifact) => PullRequest.instanceOf(artifact)} />\n </>\n );\n};\n\nTaskTags.displayName = 'TaskList.Tags';\n\n/**\n * The task's tags, as chips in the same cell as its artifacts and assignee. Queried by id for the\n * reason artifacts are: a tag's target is not in memory on a cold load.\n */\nconst TaskListItemTags = ({ task, tags }: { task: Task.Task; tags: readonly Ref.Ref<EchoTag.Tag>[] }) => {\n const db = Obj.getDatabase(task);\n const ids = useMemo(\n () =>\n tags.flatMap((ref) => {\n const id = Task.refEntityId(ref);\n return id ? [id] : [];\n }),\n [tags],\n );\n const queried = useQuery(ids.length > 0 ? db : undefined, Filter.id(...ids));\n const resolved = db ? queried : tags.flatMap((ref) => (ref.target ? [ref.target] : []));\n const labelled = useMemo(\n () => resolved.filter((object) => Obj.instanceOf(EchoTag.Tag, object)).sort(EchoTag.sortTags),\n [resolved],\n );\n\n return (\n <>\n {labelled.map((tag) => (\n <Tag.Tag key={tag.id} hue={toHue(tag.hue)} data-testid='taskList.item.tag'>\n {tag.label}\n </Tag.Tag>\n ))}\n </>\n );\n};\n\nTaskListItemTags.displayName = 'TaskList.ItemTags';\n\n/**\n * One artifact, as a tag: hovering shows the object's card and clicking opens the object. The tag\n * takes no tab stop of its own, since inside a listbox option it would split the row into several\n * arrow-key stops.\n *\n * A {@link PullRequest.PullRequest} renders as its `#number` pill — the form a PR link takes in\n * markdown — so a row reads the same as the text that references it.\n */\nconst ArtifactTag = ({ artifact }: { artifact: Obj.Unknown }) => {\n const label = Obj.getLabel(artifact) ?? Obj.getTypename(artifact) ?? '';\n const anchor = usePreviewAnchor({ eid: Obj.getURI(artifact).toString(), label });\n\n if (PullRequest.instanceOf(artifact)) {\n return (\n <Button.Root\n {...anchor}\n hue='neutral'\n size='sm'\n // The anchor chip's outlined look (`.dx-tag-anchor`), so the pill matches a PR link in a description.\n classNames='bg-input-surface text-fg font-normal ring-inset ring ring-neutral-border hover:bg-hover-surface hover:ring-info-border'\n icon='ph--git-pull-request--regular'\n iconClassNames={pullRequestStateStyle[artifact.state]}\n label={`#${artifact.number}`}\n tabIndex={-1}\n />\n );\n }\n\n return (\n <Tag.Tag {...anchor} hue='amber' classNames='cursor-pointer'>\n {label}\n </Tag.Tag>\n );\n};\n\nArtifactTag.displayName = 'TaskList.ArtifactTag';\n\n/** GitHub's own state colours, so the icon reads as open, merged or closed at a glance. */\nconst pullRequestStateStyle: Record<PullRequest.State, string> = {\n open: 'text-green-500',\n merged: 'text-violet-500',\n closed: 'text-red-500',\n draft: 'text-fg-muted',\n};\n\n//\n// Assignee — actor-aware chip, named and marked by `useAssigneeDisplay` so it agrees with the\n// properties row.\n//\n\ntype TaskListAssigneeProps = {\n assignee: Actor.Actor;\n /** Shows the glyph alone, naming the assignee on hover — for a row where the name crowds the title. */\n iconOnly?: boolean;\n};\n\nconst TaskListAssignee = Util.composable<HTMLSpanElement, TaskListAssigneeProps>(\n ({ assignee, iconOnly }, _forwardedRef) => {\n const { label, icon, agent, session: harness } = useAssigneeDisplay(assignee);\n const [session] = useObject(assignee.subject);\n const anchor = usePreviewAnchor({\n eid: session && Obj.getURI(session).toString(),\n label: label ?? '',\n // Without this the card falls back to the type's placeholder (\"New item\"), since a session's\n // label prop is its title and the harness reports none.\n title: harness?.title ?? label,\n });\n\n if (!label && !agent) {\n return null;\n }\n\n const hue = agent ? 'purple' : 'indigo';\n\n // Icon-only, a square button in the row's controls; its tooltip names the assignee, unless a session shows its card\n // on hover, which already names the run and a tooltip would stack on.\n if (iconOnly && label) {\n return (\n <Button.Root\n iconOnly\n icon={icon}\n hue={hue}\n label={label}\n showTooltip={!session}\n data-testid='taskList.item.assignee'\n {...(session && anchor)}\n />\n );\n }\n\n return (\n <Tag.Tag\n hue={hue}\n data-testid='taskList.item.assignee'\n // A button when there is a session to open, so the keyboard reaches it as the pointer does.\n {...(session && { ...anchor, role: 'button', tabIndex: 0 })}\n classNames={session && 'cursor-pointer'}\n >\n {agent && <Icon.Icon icon={icon} classNames='inline-block size-4 me-1' />}\n {label}\n </Tag.Tag>\n );\n },\n);\n\nTaskListAssignee.displayName = 'TaskList.Assignee';\n\n//\n// TaskList\n//\n\nexport const TaskList = {\n Root: TaskListRoot,\n Viewport: TaskListViewport,\n Content: TaskListContent,\n GroupLabel: TaskListGroupLabel,\n Assignee: TaskListAssignee,\n Editor: TaskListEditor,\n};\n\nexport type {\n TaskListAssigneeProps,\n TaskListContentProps,\n TaskListEditorProps,\n TaskListGroupLabelProps,\n TaskListRootProps,\n TaskListViewportProps,\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { type ReactNode } from 'react';\n\nimport { Filter, Obj, Ref } from '@dxos/echo';\nimport { useQuery } from '@dxos/echo-react';\nimport { ActionMenu, type MenuAction, createMenuAction } from '@dxos/react-ui-menu';\nimport * as Button from '@dxos/react-ui/Button';\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport * as Icon from '@dxos/react-ui/Icon';\nimport * as Layout from '@dxos/react-ui/Layout';\nimport * as Typography from '@dxos/react-ui/Typography';\nimport type * as Util from '@dxos/react-ui/Util';\nimport { Person, Task } from '@dxos/types';\nimport { mx } from '@dxos/ui-theme';\n\nimport { translationKey } from '#translations';\n\nimport { useAssigneeDisplay } from '../../hooks/index.ts';\nimport { PERSON_ICON, shortDid } from '../../util/assignee.ts';\nimport {\n UNSET_ICON,\n estimateTextStyle,\n priorityIcon,\n priorityTextStyle,\n statusIcon,\n statusTextStyle,\n} from '../../util/status-icons.ts';\nimport { TASK_GRID, TASK_GRID_ICON } from '../task-grid.ts';\nimport { TaskMnemonic } from '../TaskList/TaskRowCells.tsx';\n\n/** The glyph for an estimate, which the list renders as letters and has none of its own. */\nconst ESTIMATE_ICON = 'ph--ruler--regular';\n\n/** A member of the task's space, offered as an assignee by identity. */\nexport type TaskMember = { did: string; name?: string };\n\nexport type TaskPropertiesProps = Util.ThemedClassName<{\n task: Task.Task;\n /**\n * The space's members, the owner among them. Passed in rather than read here: membership lives in\n * HALO, which this package does not depend on.\n */\n members?: readonly TaskMember[];\n /** Absent renders the properties read-only, as the list's readonly cells do. */\n onTaskUpdate?: (task: Task.Task, patch: Task.Edit) => void;\n}>;\n\n/**\n * A task's own fields as a labelled list: one row each for status, priority and estimate, the glyph\n * naming the field and the value beside it.\n *\n * A list rather than the row of icon buttons the task list uses. A row has one line and many tasks,\n * so a glyph alone is the only thing that fits and the reader learns the ramp; a detail pane has one\n * task and the room to say what the glyph means — and an unset field can then invite the value\n * (\"Set estimate\") instead of showing a dot that reads as a value of its own.\n */\nexport const TaskProperties = ({ task, members = [], onTaskUpdate, classNames }: TaskPropertiesProps) => {\n const { t } = Hooks.useTranslation(translationKey);\n const status = task.status ?? 'todo';\n const priority = task.priority ?? undefined;\n const estimate = task.estimate ?? undefined;\n const assignee = task.assignee ?? undefined;\n\n // The people the space knows, for the picker. Queried rather than read off refs: a contact's\n // target is not in memory on a cold load, and the picker must offer everyone, not only whoever\n // some task already points at.\n const db = Obj.getDatabase(task);\n const people = useQuery(onTaskUpdate ? db : undefined, Filter.type(Person.Person));\n const { label: assigneeLabel, icon: assigneeIcon } = useAssigneeDisplay(assignee);\n const { createdAt } = Obj.getMeta(task);\n\n return (\n // A section of the host Container: it inherits the host's tracks, so its glyphs share the pane's gutter.\n <Layout.Container asChild gutter='inherit' gap='sm' classNames={classNames} data-testid='taskList.properties'>\n <section>\n {/* Set as the form's field labels are, so the article's section headings read as one with them. */}\n <Typography.Text asChild tone='subtle' classNames='dx-label py-0'>\n <h2>{t('task-properties.label')}</h2>\n </Typography.Text>\n {/* The task's reference first: what names it, and the button that copies it, in the glyph column. */}\n <div\n className={mx(TASK_GRID, 'items-center min-h-(--dx-control-sm) min-w-0')}\n data-testid='taskList.property.reference'\n >\n <div className={TASK_GRID_ICON}>\n <TaskMnemonic task={task} />\n </div>\n <Typography.Text tone='muted' classNames='text-sm'>\n {Obj.getMnemonic(task)}\n </Typography.Text>\n </div>\n {createdAt !== undefined && (\n <TaskProperty\n icon='ph--calendar-plus--regular'\n label={<Typography.Timestamp date={createdAt} />}\n unset\n testId='taskList.property.created'\n />\n )}\n\n <TaskProperty\n icon={statusIcon(status)}\n iconClassNames={statusTextStyle(status)}\n label={t(`status-${status}.label`)}\n testId='taskList.property.status'\n actions={\n onTaskUpdate &&\n (() =>\n Task.StatusOptions.map(({ id }) =>\n createMenuAction(`status-${id}`, () => onTaskUpdate(task, { status: id }), {\n label: t(`status-${id}.label`),\n icon: statusIcon(id),\n iconClassNames: statusTextStyle(id),\n checked: status === id,\n }),\n ))\n }\n />\n\n <TaskProperty\n icon={assignee ? assigneeIcon : UNSET_ICON}\n label={assigneeLabel ?? t('set-assignee.label')}\n unset={!assignee}\n testId='taskList.property.assignee'\n actions={\n onTaskUpdate &&\n (() => [\n createMenuAction('assignee-none', () => onTaskUpdate(task, { assignee: null }), {\n label: t('assignee-none.label'),\n // The unset glyph, as the priority picker's `None` takes, so every option's label aligns.\n icon: UNSET_ICON,\n checked: !assignee,\n }),\n // An assignee the people list cannot show — an agent, or an actor with no contact — is\n // listed as itself, so the picker says who holds the task before it is switched away.\n ...(assignee &&\n !assignee.contact &&\n !members.some((member) => member.did === assignee.identityDid) &&\n assigneeLabel\n ? [\n createMenuAction('assignee-current', () => {}, {\n label: assigneeLabel,\n icon: assigneeIcon,\n checked: true,\n testId: 'taskList.assignee.current',\n }),\n ]\n : []),\n // The space's members first — the people who can actually pick the task up — then its\n // contacts. Both are what the field accepts, as the status and priority pickers offer.\n ...members.map((member) =>\n createMenuAction(\n `assignee-member-${member.did}`,\n () => onTaskUpdate(task, { assignee: { identityDid: member.did, name: member.name } }),\n {\n label: member.name ?? shortDid(member.did),\n icon: PERSON_ICON,\n checked: assignee?.identityDid === member.did,\n testId: 'taskList.assignee.member',\n },\n ),\n ),\n ...people.map((person) =>\n createMenuAction(\n `assignee-${person.id}`,\n () => onTaskUpdate(task, { assignee: { contact: Ref.make(person) } }),\n {\n label: Obj.getLabel(person) ?? person.id,\n icon: PERSON_ICON,\n checked: Task.refEntityId(assignee?.contact) === person.id,\n },\n ),\n ),\n ])\n }\n />\n\n <TaskProperty\n icon={priorityIcon(priority)}\n iconClassNames={priorityTextStyle(priority)}\n label={priority ? t(`priority-${priority}.label`) : t('set-priority.label')}\n unset={!priority}\n testId='taskList.property.priority'\n actions={\n onTaskUpdate &&\n (() =>\n [Task.NullOption, ...Task.PriorityOptions].map(({ id, icon }) =>\n createMenuAction(`priority-${id}`, () => onTaskUpdate(task, { priority: id === 'none' ? null : id }), {\n label: t(`priority-${id}.label`),\n // `None` takes the unset glyph, so every option has an icon and the labels align.\n icon: icon ?? UNSET_ICON,\n iconClassNames: priorityTextStyle(id),\n checked: (priority ?? 'none') === id,\n }),\n ))\n }\n />\n\n <TaskProperty\n icon={estimate ? ESTIMATE_ICON : UNSET_ICON}\n iconClassNames={estimateTextStyle(estimate)}\n label={estimate ? estimate.toUpperCase() : t('set-estimate.label')}\n unset={!estimate}\n testId='taskList.property.estimate'\n actions={\n onTaskUpdate &&\n (() =>\n [Task.NullOption, ...Task.EstimateOptions].map(({ id, title }) =>\n createMenuAction(`estimate-${id}`, () => onTaskUpdate(task, { estimate: id === 'none' ? null : id }), {\n label: title,\n checked: (estimate ?? 'none') === id,\n }),\n ))\n }\n />\n </section>\n </Layout.Container>\n );\n};\n\nTaskProperties.displayName = 'TaskList.Properties';\n\ntype TaskPropertyProps = {\n icon: string;\n iconClassNames?: string;\n label: ReactNode;\n /** No value yet: the label invites one, so it reads as a prompt rather than as the value. */\n unset?: boolean;\n testId: string;\n /** Absent renders the row as text — the readonly case, and the row is then not a button. */\n actions?: () => MenuAction[];\n};\n\nconst TaskProperty = ({ icon, iconClassNames, label, unset, testId, actions }: TaskPropertyProps) => {\n const content = (\n <>\n {/* Unset takes the label's own hue, not the value palette's neutral: they are different\n greys, and with the same asterisk on both rows the mismatch read as a meaning the rows do\n not carry. A value keeps the hue its option table gives it. */}\n <div className={TASK_GRID_ICON}>\n <Icon.Icon icon={icon} classNames={mx(unset ? 'text-fg-muted' : iconClassNames)} />\n </div>\n <span className={mx('min-w-0 pe-1.5 text-sm truncate', unset && 'text-fg-muted')}>{label}</span>\n </>\n );\n\n if (!actions) {\n return (\n // The button's own height and centring, so a read-only row lines up with the editable ones.\n <div className={mx(TASK_GRID, 'items-center min-h-(--dx-control-sm) min-w-0')} data-testid={testId}>\n {content}\n </div>\n );\n }\n\n return (\n // Deferred, as the row's controls are: the menu is built when it is opened, not when the pane\n // renders three of them.\n <ActionMenu deferUntilOpen actions={actions}>\n {/* The button IS the row, so its own box carries the section's grid: a flex button wrapping a\n grid would size the glyph column to the glyph instead of to the shared 24px. `w-fit`, since\n a property is as wide as its value and a full-width button would paint a bar across the\n pane on hover. */}\n <Button.Root\n variant='ghost'\n size='sm'\n // `items-center`, overriding the shared grid's `items-start`: a property is one line, and the\n // button's box is taller than it — top-aligned, its glyph and label sat against the top of\n // the hover surface rather than in it. The wrapping rows (history, a question) keep\n // `items-start`, where a glyph must stay on the first line.\n classNames={mx(TASK_GRID, 'items-center w-fit min-w-0 justify-start px-0')}\n data-testid={testId}\n >\n {content}\n </Button.Root>\n </ActionMenu>\n );\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { type KeyboardEvent, type SyntheticEvent, useCallback, useState } from 'react';\n\nimport * as Button from '@dxos/react-ui/Button';\nimport * as Field from '@dxos/react-ui/Field';\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport * as Icon from '@dxos/react-ui/Icon';\nimport * as Input from '@dxos/react-ui/Input';\nimport type * as Util from '@dxos/react-ui/Util';\nimport { type Task } from '@dxos/types';\nimport { mx } from '@dxos/ui-theme';\n\nimport { translationKey } from '#translations';\n\nimport { TASK_GRID, TASK_GRID_CONTENT, TASK_GRID_ICON } from '../task-grid.ts';\n\n/**\n * Stops an event at the question: it sits inside a listbox row, whose click selects the task and\n * whose arrow keys move the selection, and typing an answer must do neither.\n */\nconst stop = (event: SyntheticEvent) => event.stopPropagation();\n\nexport type TaskQuestionProps = Util.ThemedClassName<{\n thread: Task.QuestionThread;\n /** An answer is in flight; the controls are disabled until it settles. */\n busy?: boolean;\n /** A line under the controls — a failed write, or an answer that landed but woke nobody. */\n message?: string;\n /**\n * One line for the question and one for its answer, with no context or controls — for a list row,\n * where the full prompt would crowd out the tasks; the host's detail surface renders it in full.\n */\n // TODO(burdon): Remove. This should be a different component.\n compact?: boolean;\n /** Enables answering; absent renders the question read-only. */\n onAnswer?: (answer: string) => void;\n}>;\n\n/**\n * A question from a task's history: why it was asked, and the means to answer it. Answered, it\n * collapses to the question and the answer, since the exchange is then a record rather than a\n * prompt.\n *\n * The free-form field is always present, never a fallback revealed by a \"something else\" option:\n * the options are the asker's guesses, and making the reader hunt for the escape hatch pressures\n * them into picking a wrong one.\n */\n// TODO(burdon): Rewrite/move to react-ui-assistant widgets.\nexport const TaskQuestion = ({\n classNames,\n thread: { question, answer },\n busy,\n message,\n compact,\n onAnswer,\n}: TaskQuestionProps) => {\n const { t } = Hooks.useTranslation(translationKey);\n const [text, setText] = useState('');\n\n const handleSubmit = useCallback<NonNullable<TaskQuestionProps['onAnswer']>>(\n (value) => {\n if (!busy && value.trim().length > 0) {\n onAnswer?.(value);\n }\n },\n [busy, onAnswer],\n );\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLInputElement>) => {\n event.stopPropagation();\n if (event.key === 'Enter') {\n event.preventDefault();\n handleSubmit(text);\n }\n },\n [handleSubmit, text],\n );\n\n if (compact) {\n // No event stopping: nothing here takes input, so a click falls through to the row and selects\n // the task — which is how the reader reaches the full question.\n return (\n <div\n role='group'\n aria-label={question.text}\n className={mx('flex flex-col w-full gap-1 text-sm', classNames)}\n data-testid='task-question'\n >\n <div className='flex items-center gap-2 min-w-0'>\n <Icon.Icon icon='ph--question--regular' valence='warning' />\n <span className='font-medium truncate' title={question.text}>\n {question.text}\n </span>\n </div>\n {answer && (\n <div className='flex items-center gap-2 min-w-0' data-testid='task-question.answer'>\n <Icon.Icon icon='ph--check-circle--regular' valence='success' />\n <span className='truncate' title={answer.answer}>\n {answer.answer}\n </span>\n </div>\n )}\n </div>\n );\n }\n\n return (\n // An element of its own, so the question can stop the events below: it sits inside a listbox\n // row, whose click selects the task and whose arrow keys move the selection, and typing an\n // answer must do neither.\n <div\n role='group'\n aria-label={question.text}\n // Its own two columns, not the host Column's tracks: the glyph is the question's own first\n // cell now, so re-exposing the host's tracks (`withColumn.propagate`) would replace this\n // template with a subgrid and drop the text into whatever the host's second track happens to\n // be. The host places the question as a whole; the question places what is inside it.\n className={mx(TASK_GRID, 'gap-y-2 text-sm', classNames)}\n data-testid='task-question'\n onClick={stop}\n onPointerDown={stop}\n onKeyDown={stop}\n >\n {/* The question's own row: its glyph in the shared column, its text beside it. Everything\n below — the context, the options, the answer field — is that same second column, so the\n question reads as one block hanging off one glyph rather than as four indented things. */}\n <div className={TASK_GRID_ICON}>\n <Icon.Icon icon='ph--question--regular' valence='warning' />\n </div>\n <span className='font-medium wrap-break-word min-w-0'>{question.text}</span>\n\n {question.context && !answer && (\n <p className={mx(TASK_GRID_CONTENT, 'text-fg-muted wrap-break-word line-clamp-3 min-w-0')}>\n {question.context}\n </p>\n )}\n\n {answer ? (\n <>\n <div className={TASK_GRID_ICON}>\n <Icon.Icon icon='ph--check-circle--regular' valence='success' />\n </div>\n <span className='wrap-break-word min-w-0' data-testid='task-question.answer'>\n {answer.answer}\n </span>\n </>\n ) : (\n onAnswer && (\n <div className={mx(TASK_GRID_CONTENT, 'flex flex-col gap-2 min-w-0')}>\n {/* A list of its own, one item per option: each is a separate choice with its own\n description, and read as one block of buttons they ran together. */}\n {question.options && question.options.length > 0 && (\n <div role='list' aria-label={question.text} className='flex flex-col gap-1 min-w-0'>\n {question.options.map((option, index) => (\n <div key={option.title} role='listitem' className='min-w-0'>\n <Button.Root\n variant='default'\n disabled={busy}\n // `h-auto` and wrapping: an option is a sentence, not a label, so the button\n // grows to its text instead of clipping it.\n classNames='w-full min-w-0 h-auto py-1.5 justify-start text-start whitespace-normal'\n data-testid='task-question.option'\n onClick={() => handleSubmit(option.title)}\n >\n {/* Numbered, so the options can be referred to — an agent asking again, a\n person saying \"the second one\" — rather than quoted back in full. On the\n first line and top-aligned, since an option's text wraps. */}\n <div className='shrink-0 tabular-nums text-fg-muted self-start'>{index + 1}.</div>\n {/* `div`, not `span`: `Button` carries `[&_span]:truncate`. */}\n <div className='grow min-w-0 flex flex-col gap-0.5 text-start'>\n <div className='font-medium wrap-break-word'>{option.title}</div>\n {option.description && (\n <div className='text-xs text-fg-muted wrap-break-word leading-snug'>{option.description}</div>\n )}\n </div>\n </Button.Root>\n </div>\n ))}\n </div>\n )}\n {/* Wraps rather than squeezes: in a narrow host (a popover card) the field keeps a usable\n width and the button drops below it. */}\n <div className='flex flex-wrap gap-1'>\n <div className='flex-[1_1_10rem] min-w-0'>\n <Field.Root>\n <Field.Label srOnly>{t('question-answer.label')}</Field.Label>\n <Input.Root\n value={text}\n disabled={busy}\n placeholder={t('question-answer.placeholder')}\n data-testid='task-question.input'\n onChange={(event) => setText(event.target.value)}\n onKeyDown={handleKeyDown}\n />\n </Field.Root>\n </div>\n <Button.Root\n variant='primary'\n disabled={busy || text.trim() === ''}\n data-testid='task-question.submit'\n onClick={() => handleSubmit(text)}\n >\n {t('question-submit.label')}\n </Button.Root>\n </div>\n </div>\n )\n )}\n\n {message && (\n <p className={mx(TASK_GRID_CONTENT, 'text-fg-muted min-w-0')} data-testid='task-question.message'>\n {message}\n </p>\n )}\n </div>\n );\n};\n\nTaskQuestion.displayName = 'TaskQuestion';\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { differenceInCalendarDays, formatDistance } from 'date-fns';\n\n/** Beyond this, \"x days ago\" stops locating an event and the calendar date does it better. */\nexport const RELATIVE_DAYS = 3;\n\nexport type FormatRelativeOptions = {\n /** Days before the date is shown as a calendar date rather than a distance. */\n relativeDays?: number;\n /** The instant to measure against; supplied by a test so the result does not move with the clock. */\n now?: Date;\n};\n\n/**\n * A timestamp as a reader scans it: recent activity as a distance (\"2 hours ago\"), older activity as\n * a local calendar date.\n *\n * The date is formatted in the reader's own locale and zone (`toLocaleDateString`), since it is read\n * as \"when did this happen to me\" rather than as a record to cite; an unparseable value is returned\n * as it came, because a log line is decoration and never the source of truth.\n */\nexport const formatRelative = (\n date: string | Date,\n { relativeDays = RELATIVE_DAYS, now }: FormatRelativeOptions = {},\n): string => {\n const parsed = typeof date === 'string' ? new Date(date) : date;\n if (Number.isNaN(parsed.getTime())) {\n return typeof date === 'string' ? date : '';\n }\n\n // One instant for both halves, so the cut-off and the distance cannot disagree about when \"now\"\n // is — which is also what makes the boundary testable without the wall clock.\n const reference = now ?? new Date();\n // Calendar days, not elapsed hours: yesterday evening reads as \"1 day\", not \"20 hours\", which is\n // what makes the cut-off land on the same boundary a reader would draw.\n const days = Math.abs(differenceInCalendarDays(reference, parsed));\n return days > relativeDays\n ? parsed.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })\n : formatDistance(parsed, reference, { addSuffix: true });\n};\n"],"mappings":"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"}