dsh-task-ledger 0.0.68 → 0.0.70

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.
package/dist/client.js CHANGED
@@ -1349,6 +1349,8 @@ body[data-ds-dark-theme]{
1349
1349
  title,
1350
1350
  interactive: false
1351
1351
  });
1352
+ /** 子集卡占比小字:分母为 0 或自己没有 ⇒ 不显示(不拿 0% 充数)。 */
1353
+ const share = (part, whole, key) => part > 0 && whole > 0 ? t(key, { n: Math.round(part / whole * 100) }) : "";
1352
1354
  /** 统计台卡:不可点(首页没有已结案可筛),hover 给具体日期区间。
1353
1355
  * 趋势未到 / 读挂 ⇒ 数字留 '—'(StatCard 对 0 的处理)、小字整条不显示,**不拿 0 充数**。 */
1354
1356
  const periodCard = (label, period) => (0, react.createElement)(StatCard, {
@@ -1422,7 +1424,7 @@ body[data-ds-dark-theme]{
1422
1424
  }, state.refreshing ? t("refreshing") : t("refresh")))), tab === "stats" ? (0, react.createElement)("div", { style: hintStyle }, t("statsPlaceholder")) : (0, react.createElement)("div", null, stats === void 0 ? null : (0, react.createElement)("div", {
1423
1425
  className: "dtl-stats",
1424
1426
  style: statsRowStyle
1425
- }, (0, react.createElement)("div", { className: "dtl-stats__cards" }, card("statMyActive", stats.myActive, [stats.myOverdue > 0 ? t("statMyOverdueHint", { n: stats.myOverdue }) : null, stats.myStale > 0 ? t("statMyStaleHint", { n: stats.myStale }) : null].filter((part) => part !== null).join(" | ")), card("statDoing", stats.doing, stats.doing - stats.myActive > 0 ? t("statDoingHint", { n: stats.doing - stats.myActive }) : t("statDoingAllMine")), card("statOpen", stats.open, stats.oldestOpenDays === null ? "" : t("statOpenHint", { n: stats.oldestOpenDays })), card("statBacklog", stats.backlog, stats.backlog > 0 && stats.open > 0 ? t("statBacklogHint", { n: Math.round(stats.backlog / stats.open * 100) }) : "", t("statBacklogTitle", { n: stats.backlogDays })), card("statOverdue", stats.overdue, stats.maxOverdueAge === null ? "" : t("statOverdueHint", { n: stats.maxOverdueAge }), t("statOverdueTitle", { n: stats.overdueDays })), card("statStale", stats.stale, stats.maxStaleIdle === null ? "" : t("statStaleHint", { n: stats.maxStaleIdle }), t("statStaleTitle", { n: stats.staleDays })), periodCard("statWeekDone", periods?.thisWeek), periodCard("statLastWeekDone", periods?.lastWeek)), (0, react.createElement)("div", {
1427
+ }, (0, react.createElement)("div", { className: "dtl-stats__cards" }, card("statMyActive", stats.myActive, [stats.myOverdue > 0 ? t("statMyOverdueHint", { n: stats.myOverdue }) : null, stats.myStale > 0 ? t("statMyStaleHint", { n: stats.myStale }) : null].filter((part) => part !== null).join(" | ")), card("statDoing", stats.doing, stats.doing - stats.myActive > 0 ? t("statDoingHint", { n: stats.doing - stats.myActive }) : t("statDoingAllMine")), card("statOpen", stats.open, stats.oldestOpenDays === null ? "" : t("statOpenHint", { n: stats.oldestOpenDays })), card("statBacklog", stats.backlog, share(stats.backlog, stats.open, "statBacklogHint"), t("statBacklogTitle", { n: stats.backlogDays })), card("statOverdue", stats.overdue, share(stats.overdue, stats.doing, "statOverdueHint"), t("statOverdueTitle", { n: stats.overdueDays })), card("statStale", stats.stale, share(stats.stale, stats.doing, "statStaleHint"), t("statStaleTitle", { n: stats.staleDays })), periodCard("statWeekDone", periods?.thisWeek), periodCard("statLastWeekDone", periods?.lastWeek)), (0, react.createElement)("div", {
1426
1428
  className: "dtl-card",
1427
1429
  style: chartCardStyle
1428
1430
  }, (0, react.createElement)("div", { style: {
@@ -1666,8 +1668,8 @@ body[data-ds-dark-theme]{
1666
1668
  statOverdue: "逾期",
1667
1669
  statStale: "停滞",
1668
1670
  statBacklogHint: "占待认领 {n}%",
1669
- statOverdueHint: "最长 {n} 天未结",
1670
- statStaleHint: "最长 {n} 天未更新",
1671
+ statOverdueHint: "占进行中 {n}%",
1672
+ statStaleHint: "占进行中 {n}%",
1671
1673
  statStaleTitle: "停滞 = 进行中满 {n} 天没更新",
1672
1674
  statOverdueTitle: "逾期 = 认领后满 {n} 天还没结案",
1673
1675
  statBacklogTitle: "积压 = 待认领满 {n} 天没人接",
@@ -1791,8 +1793,8 @@ body[data-ds-dark-theme]{
1791
1793
  statOverdue: "Overdue",
1792
1794
  statStale: "Stale",
1793
1795
  statBacklogHint: "{n}% of unclaimed",
1794
- statOverdueHint: "longest {n} days open",
1795
- statStaleHint: "longest {n} days idle",
1796
+ statOverdueHint: "{n}% of active",
1797
+ statStaleHint: "{n}% of active",
1796
1798
  statStaleTitle: "stale = active with no update for {n}+ days",
1797
1799
  statOverdueTitle: "overdue = claimed {n}+ days ago and still open",
1798
1800
  statBacklogTitle: "backlog = unclaimed for {n}+ days",
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["h","Tooltip","useState","h","Tooltip","IconSearchOutlineRegular","IconDownloadOutlineRegular","IconChevronRightOutlineRegular","IconCheckOutlineRegular","useSyncExternalStore","useState","useMemo","h","IconRefreshOutlineRegular","IconChecklistOutlineRegular","IconFlatListOutlineRegular","IconFolderOpenRegular","IconFolderCloseRegular","useSyncExternalStore","useRef","useState","h","h"],"sources":["../src/client/api.ts","../src/client/store.ts","../src/client/rows.ts","../src/client/format.ts","../src/client/tokens.ts","../src/client/ui.ts","../src/client/task-row.ts","../src/client/board-home.ts","../src/client/locales.ts","../src/client/overlay-entry.ts","../src/client/index.ts"],"sourcesContent":["/**\n * 数据面取数**单源**:所有 client → host 的 HTTP 都在这里,页面不许各写一份 fetch。\n *\n * 纪律:只做传输与形状校验,不造兜底假值(DSH 插件规则「数据真实」)——\n * 失败就抛,由调用方显示错误态;返回什么渲染什么。\n *\n * ⚠️ 必须带超时且 abort 要带 reason:一次挂起的请求会让面板永久停在「读取中」,\n * 而不带 reason 时浏览器把 error.message 填成「signal is aborted without reason」,\n * 看不出是超时(姊妹仓 2026-10-07 真机教训)。\n */\nimport type { BoardPayload, CloseHandoff, FilterOptions, HistoryRecord, TaskDetail, TrendPayload } from './types'\n\n/** host 路由前缀(全仓唯一;与 host/routes.ts 的 API_PREFIX 对应)。 */\nexport const API_PREFIX = 'api/task-ledger'\n\nconst DEFAULT_TIMEOUT_MS = 8_000\n/** 趋势端点读 history,比快路径慢一个量级(20 万条实测 4s+),超时单给。 */\nconst TREND_TIMEOUT_MS = 30_000\n\n/**\n * 错误响应榨人话:host 路由(routes.ts)发的是 `{ error: \"...\" }`,那里的话是**能直接上界面的**\n * (带工作区名、python3 起不来之类的原文)。不带它界面上只剩「HTTP 500」,等于没报错。\n */\nexport function httpErrorMessage(status: number, body: string): string {\n try {\n const parsed = JSON.parse(body) as { error?: unknown }\n if (typeof parsed.error === 'string' && parsed.error !== '') return `HTTP ${status}:${parsed.error}`\n } catch { /* 不是 JSON:走原文 */ }\n const text = body.trim().slice(0, 200)\n return text === '' ? `HTTP ${String(status)}` : `HTTP ${String(status)}:${text}`\n}\n\n/** 带超时的 JSON 取数(相对路径,同源)。 */\nexport async function fetchJson<T>(path: string, init: RequestInit = {}, timeoutMs = DEFAULT_TIMEOUT_MS): Promise<T> {\n const controller = new AbortController()\n const timer = setTimeout(() => {\n controller.abort(new DOMException(`请求超时(${timeoutMs}ms 未响应)`, 'TimeoutError'))\n }, timeoutMs)\n try {\n const res = await fetch(`${API_PREFIX}${path}`, { ...init, signal: controller.signal, cache: 'no-store' })\n if (!res.ok) throw new Error(httpErrorMessage(res.status, await res.text().catch(() => '')))\n return await res.json() as T\n } finally {\n clearTimeout(timer)\n }\n}\n\n/**\n * 首页快照(快路径:在办全量 + 统计 + 各工作区身份;**不含趋势**)。\n * `fresh` = 让 host 先清 1.5s 读缓存再算(手动刷新 / 首读都要最新,不吃上一屏的缓存)。\n */\nexport function fetchBoardSnapshot(opts: { fresh?: boolean } = {}): Promise<BoardPayload> {\n return fetchJson<BoardPayload>(`/board/snapshot${freshSuffix(opts)}`)\n}\n\n/**\n * 趋势(慢路径:近 8 周 + 四个自然周期,host 侧要读 history)。\n * 超时给得比默认宽:历史量大时读得久(实测 20 万条量级 4s+);\n * 旧宿主没有该端点 ⇒ 404,直接抛给调用方兜底,不阻塞列表。\n */\nexport function fetchTrend(opts: { fresh?: boolean } = {}): Promise<TrendPayload> {\n return fetchJson<TrendPayload>(`/board/trend${freshSuffix(opts)}`, {}, TREND_TIMEOUT_MS)\n}\n\n/** `?fresh=1` 只在真要最新值时带;不带就吃 host 的 1.5s 缓存。 */\nfunction freshSuffix(opts: { fresh?: boolean }): string {\n return opts.fresh === true ? '?fresh=1' : ''\n}\n\n/** 已结案列表(服务端按窗口 / 标签 / 分页过滤)。 */\nexport function fetchHistory(params: { since?: string; until?: string; tag?: string; limit?: number }): Promise<HistoryRecord[]> {\n const query = new URLSearchParams()\n if (params.since !== undefined) query.set('since', params.since)\n if (params.until !== undefined) query.set('until', params.until)\n if (params.tag !== undefined && params.tag !== '') query.set('tag', params.tag)\n if (params.limit !== undefined) query.set('limit', String(params.limit))\n const suffix = query.toString()\n return fetchJson<{ records: HistoryRecord[] }>(`/history${suffix === '' ? '' : `?${suffix}`}`)\n .then(body => body.records ?? [])\n}\n\n/** 单条在办 + 过程文档正文(展开行用)。 */\nexport function fetchTaskDetail(workspaceId: string, taskId: string): Promise<TaskDetail> {\n const query = new URLSearchParams({ ws: workspaceId, id: taskId })\n return fetchJson<TaskDetail>(`/task?${query.toString()}`)\n}\n\n/** 筛选项候选(在办全量 + 近 13 个月历史的去重 owner / category / tag)。 */\nexport function fetchFilters(): Promise<FilterOptions> {\n return fetchJson<FilterOptions>('/filters')\n}\n\n/**\n * 结束任务:请 host 起一个会话(首条 = 预置提示词 + 用户留言),返回 `sessionId` 供跳转。\n *\n * `message` 是用户在弹框里留的话(可空);`provider` / `model` 成对给 = 用用户选的模型,\n * 都不给 = 用宿主默认模型(只给一半会被 host 400 拒,不做猜测)。\n */\nexport function requestClose(body: {\n ws: string\n id: string\n message: string\n provider?: string\n model?: string\n}): Promise<CloseHandoff> {\n return fetchJson<CloseHandoff>('/close', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n}\n\n/** 认领 / 释放的写结果;成功后由调用方自行刷新快照(host 已清该工作区读缓存)。 */\nexport interface TaskMutationResult {\n readonly ok: true\n}\n\n/**\n * 认领:点一下即归当前用户(host 直跑脚本,不开会话)。\n * 只有 `open` 且无人认领才允许,忙时 host 回 500 + 人话(脚本原文)。\n */\nexport function requestClaim(body: { ws: string; id: string }): Promise<TaskMutationResult> {\n return fetchJson<TaskMutationResult>('/claim', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n}\n\n/** 释放:退回待认领(owner 清空),只许当前 owner 本人(host 侧脚本校验)。 */\nexport function requestRelease(body: { ws: string; id: string }): Promise<TaskMutationResult> {\n return fetchJson<TaskMutationResult>('/release', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n}\n","/**\n * 看板数据仓(client 侧**唯一**取数入口):浮标角标与整页共用同一份快照,\n * 谁先挂载谁触发首读;刷新按钮走同一个 `refresh()`,**不轮询**(2026-10-17 拍板:\n * 后端纯按需,刷新时机归前端 = 打开面板首读 + 手动刷新)。\n *\n * 消费方式:`useSyncExternalStore(boardSubscribe, boardSnapshot)`。\n * ⚠️ `getSnapshot` 必须返回**稳定引用**(每次新建对象会被判定为「一直在变」⇒ 无限重渲染),\n * 故只在真变了时才换 `state` 对象。\n */\nimport { fetchBoardSnapshot, fetchTrend } from './api'\nimport type { BoardPayload, TrendPayload } from './types'\n\n/** 范围:我的 / 全部工作区。 */\nexport type Scope = 'mine' | 'all'\n/** 状态档:进行中 = doing;积压 / 逾期 / 停滞 = host 按时间标记的 backlog / overdue / stale。 */\nexport type StatusFilter = 'all' | 'active' | 'open' | 'backlog' | 'overdue' | 'stale'\n\nexport interface Query {\n scope: Scope\n /** 「其他待办」的状态档过滤(segmented)。 */\n status: StatusFilter\n /** 工作区 id;'' = 全部工作区(只作用于「我的待办」)。 */\n workspace: string\n /** 「我的待办」的标题搜索。 */\n keyword: string\n /** 「其他待办」的搜索(标题 / 执行人)。 */\n othersKeyword: string\n}\n\nexport interface BoardState {\n status: 'loading' | 'ready' | 'error'\n board: BoardPayload | null\n error: string | null\n query: Query\n /** 手动刷新在途(按钮转圈 / 禁用)。 */\n refreshing: boolean\n /** 趋势(慢路径,`/board/trend`):null = 还没回来 / 旧宿主没有该端点。 */\n trend: TrendPayload | null\n /** 趋势加载态:首屏不阻塞列表,图表区自己显示占位 / 兜底文案。 */\n trendStatus: 'idle' | 'loading' | 'ready' | 'error'\n trendError: string | null\n}\n\nconst INITIAL_QUERY: Query = { scope: 'all', status: 'all', workspace: '', keyword: '', othersKeyword: '' }\n\nlet state: BoardState = {\n status: 'loading',\n board: null,\n error: null,\n query: INITIAL_QUERY,\n refreshing: false,\n trend: null,\n trendStatus: 'idle',\n trendError: null,\n}\n\nconst listeners = new Set<() => void>()\n/** 首读只触发一次(谁先订阅谁触发),之后一律靠 `refresh()` / `setQuery()`。 */\nlet booted = false\n/** 在途轮次:迟到的响应按 token 丢弃,避免旧结果盖新结果。 */\nlet runToken = 0\n\nfunction emit(): void {\n for (const listener of [...listeners]) listener()\n}\n\nfunction set(patch: Partial<BoardState>): void {\n state = { ...state, ...patch }\n emit()\n}\n\nexport function boardSnapshot(): BoardState {\n return state\n}\n\nexport function boardSubscribe(listener: () => void): () => void {\n listeners.add(listener)\n if (!booted) {\n booted = true\n void refresh()\n }\n return () => { listeners.delete(listener) }\n}\n\n/**\n * 重读(2026-10-18 拆端点):**两段**——先快路径 `/board/snapshot`(只读在办,首屏立即可渲染),\n * 再慢路径 `/board/trend`(读 history,后台拉;迟到按 token 丢弃,不盖新快照)。\n * 趋势失败(含旧宿主 404)不阻塞列表:trendStatus 置 error,图表区显示兜底文案。\n * 两段都带 `fresh=1`:本函数只由首读与手动刷新触发,都要最新值(host 缓存清两次无害,清是全局的)。\n */\nexport async function refresh(): Promise<void> {\n const token = ++runToken\n set({ refreshing: true })\n try {\n const board = await fetchBoardSnapshot({ fresh: true })\n if (token !== runToken) return\n set({ status: 'ready', board, error: null, trendStatus: 'loading', trendError: null })\n } catch (error) {\n if (token !== runToken) return\n set({ status: 'error', error: error instanceof Error ? error.message : String(error) })\n return\n } finally {\n if (token === runToken) set({ refreshing: false })\n }\n try {\n const trend = await fetchTrend({ fresh: true })\n if (token !== runToken) return\n set({ trend, trendStatus: 'ready', trendError: null })\n } catch (error) {\n if (token !== runToken) return\n set({ trend: null, trendStatus: 'error', trendError: error instanceof Error ? error.message : String(error) })\n }\n}\n\n/** 改筛选:全部在浏览器里完成(快照已含全部在办),不发请求。 */\nexport function setQuery(patch: Partial<Query>): void {\n set({ query: { ...state.query, ...patch } })\n}\n","/**\n * 列表视图的筛选 / 分区 / 排序(**唯一**实现,纯函数、不碰网络):页面只渲染,不各写一份过滤。\n * 定稿见 docs/design/features/board-home.md §二.3:两区结构(我的待办平铺 / 按工作区分组),\n * 已结案**不在首页**(去统计 tab)。\n *\n * 单独成文件是为了能被冒烟直测(store.ts 引 api.ts,Node 剥类型解析不了无扩展名相对导入)。\n */\nimport type { BoardPayload, BoardTask, HistoryRecord } from './types'\nimport type { Query } from './store'\n\n/** 列表行:在办与已结案两套形状,行件按 kind 分支(不做「统一成一个假结构」的归一化)。 */\nexport type Row =\n | { kind: 'task'; key: string; task: BoardTask; mine: boolean }\n | { kind: 'closed'; key: string; record: HistoryRecord }\n\n/** 一个工作区分组(计数口径:待认领 / 进行中 / 积压 / 逾期 / 停滞 / 总,供「全部待办」段头统计)。 */\nexport interface WorkspaceGroup {\n readonly workspaceId: string\n readonly title: string\n readonly doingCount: number\n readonly overdueCount: number\n readonly staleCount: number\n readonly backlogCount: number\n readonly openCount: number\n readonly total: number\n readonly rows: readonly Row[]\n}\n\n/** 两区视图:第一区我的待办 + 第二区按工作区分组(第二区含全部任务,会与第一区重叠——用户 2026-10-18 要「全部待办」)。 */\nexport interface BoardView {\n readonly mine: readonly Row[]\n readonly groups: readonly WorkspaceGroup[]\n /** 第二区(全部待办)任务数。 */\n readonly total: number\n /** 第二区全量计数(**不受状态过滤影响**,供 segmented 角标;异常标记相互独立;含我的)。 */\n readonly allCounts: {\n readonly total: number\n readonly doing: number\n readonly open: number\n readonly overdue: number\n readonly stale: number\n readonly backlog: number\n }\n}\n\n/** 关键字命中(标题 / 任务号 / 人 / 分类 / 标签;已结案另含「完成了什么」)。 */\nfunction hitKeyword(keyword: string, parts: readonly (string | null | undefined)[]): boolean {\n const needle = keyword.trim().toLowerCase()\n if (needle === '') return true\n return parts.some(part => part?.toLowerCase().includes(needle) === true)\n}\n\n/** 「我的」判定:owner = 该工作区的当前 git 身份,且状态是进行中(blocked 是旧数据,照收;与 host 同口径)。 */\nexport function isMine(task: BoardTask, board: BoardPayload | null): boolean {\n if (board === null) return false\n const identity = board.identities[task.workspace.id] ?? null\n if (identity === null || identity === '') return false\n return task.owner === identity && (task.status === 'doing' || task.status === 'blocked')\n}\n\n/**\n * 任务动作权限(**唯一**判定,两区共用——用户 2026-10-18:不许各判各的)。\n * 传任务 + 快照进去,返回「能做哪些动作」;界面只按返回值画按钮,不再自己判断是不是我的。\n * ⇒ 第二区(全部待办)里**我的**任务也能继续 / 结束 / 释放,跟第一区一模一样。\n */\nexport interface TaskActions {\n /** 查看详情(两区、所有任务都有)。 */\n readonly view: boolean\n /** 认领:只有**没人认领**(待认领)的任务能认领。 */\n readonly claim: boolean\n readonly resume: boolean\n readonly finish: boolean\n readonly release: boolean\n}\n\nexport function resolveActions(task: BoardTask, board: BoardPayload | null): TaskActions {\n const mine = isMine(task, board)\n return {\n view: true,\n claim: !mine && task.status === 'open',\n resume: mine,\n finish: mine,\n release: mine,\n }\n}\n\n/** 已结案行(统计 tab):只有查看,没有在办动作。 */\nexport const CLOSED_ACTIONS: TaskActions = { view: true, claim: false, resume: false, finish: false, release: false }\n\n/** 状态档命中(积压 / 逾期 / 停滞由 host 标记,前端不判阈值)——只作用于第二区。 */\nfunction statusHit(task: BoardTask, status: Query['status']): boolean {\n if (status === 'all') return true\n if (status === 'active') return task.status === 'doing' || task.status === 'blocked'\n if (status === 'open') return task.status === 'open'\n if (status === 'backlog') return task.backlog === true\n if (status === 'overdue') return task.overdue === true\n return task.stale === true\n}\n\n/** 组内排序:别人的进行中在前、待认领在后;各自按创建 / 开始时间倒序。 */\nfunction inGroupOrder(a: Row, b: Row): number {\n if (a.kind !== 'task' || b.kind !== 'task') return 0\n const rank = (task: BoardTask): number => (task.status === 'open' ? 1 : 0)\n if (rank(a.task) !== rank(b.task)) return rank(a.task) - rank(b.task)\n return String(b.task.created ?? b.task.started ?? '').localeCompare(String(a.task.created ?? a.task.started ?? ''))\n}\n\n/**\n * 当前筛选下的两区视图(用户 2026-10-18 调整):\n * - 第一区「我的待办」= 我名下 doing/blocked,吃工作区下拉 + 标题搜索,按创建时间倒序;\n * - 第二区「全部待办」= **所有**在办任务按工作区分组(含我的——用户明确不要过滤掉自己),\n * 吃**状态档**(segmented)+ **标题 / 执行人搜索**(othersKeyword);一个整表(外框连着)。\n * ⇒ 我的任务会同时出现在两个区,这是预期(顶部快看 / 底部全量)。\n */\nexport function selectView(current: { board: BoardPayload | null; query: Query }): BoardView {\n const { board, query } = current\n if (board === null) {\n return { mine: [], groups: [], total: 0, allCounts: { total: 0, doing: 0, open: 0, overdue: 0, stale: 0, backlog: 0 } }\n }\n const allTasks = board.tasks\n const allCounts = {\n total: allTasks.length,\n doing: allTasks.filter((task) => task.status === 'doing').length,\n open: allTasks.filter((task) => task.status === 'open').length,\n overdue: allTasks.filter((task) => task.overdue === true).length,\n stale: allTasks.filter((task) => task.stale === true).length,\n backlog: allTasks.filter((task) => task.backlog === true).length,\n }\n const mineRows: Row[] = board.tasks\n .filter((task) => isMine(task, board))\n .filter((task) => query.workspace === '' || task.workspace.id === query.workspace)\n .filter((task) => hitKeyword(query.keyword, [task.title, task.id, task.owner, task.creator, task.category, ...(task.tags ?? [])]))\n .sort((a, b) => String(b.created ?? b.started ?? '').localeCompare(String(a.created ?? a.started ?? '')))\n .map((task): Row => ({ kind: 'task', key: `${task.workspace.id}:${task.id}`, task, mine: true }))\n const rest = query.scope === 'mine'\n ? []\n : board.tasks\n .filter((task) => statusHit(task, query.status))\n .filter((task) => hitKeyword(query.othersKeyword, [task.title, task.owner]))\n const byWorkspace = new Map<string, { title: string; rows: Row[] }>()\n for (const task of rest) {\n const entry = byWorkspace.get(task.workspace.id) ?? { title: task.workspace.title, rows: [] }\n // mine 必须说实话(第二区现在含全部任务,连我的也在里面):按钮权限由 resolveActions 判,\n // 但这一位同样是数据面,不许一律写 false。\n entry.rows.push({ kind: 'task', key: `${task.workspace.id}:${task.id}`, task, mine: isMine(task, board) })\n byWorkspace.set(task.workspace.id, entry)\n }\n const groups: WorkspaceGroup[] = [...byWorkspace.entries()]\n .map(([workspaceId, entry]) => ({\n workspaceId,\n title: entry.title,\n doingCount: entry.rows.filter((row) => row.kind === 'task' && row.task.status === 'doing').length,\n overdueCount: entry.rows.filter((row) => row.kind === 'task' && row.task.overdue === true).length,\n staleCount: entry.rows.filter((row) => row.kind === 'task' && row.task.stale === true).length,\n backlogCount: entry.rows.filter((row) => row.kind === 'task' && row.task.backlog === true).length,\n openCount: entry.rows.filter((row) => row.kind === 'task' && row.task.status === 'open').length,\n total: entry.rows.length,\n rows: entry.rows.sort(inGroupOrder),\n }))\n .sort((a, b) => a.title.localeCompare(b.title, 'zh-Hans-CN'))\n return { mine: mineRows, groups, total: groups.reduce((sum, group) => sum + group.rows.length, 0), allCounts }\n}\n\n/**\n * 已结案列表(统计 tab 用;首页不渲染已结案 ⇒ 这里只留数据面,供后续统计 tab 复用同一套筛选口径)。\n */\nexport function selectClosed(current: { history: readonly HistoryRecord[]; query: Query }): Row[] {\n return current.history\n .filter((record) => hitKeyword(current.query.keyword, [record.summary, record.workpackage, record.owner, record.category, ...(record.tags ?? [])]))\n .map((record, index) => ({ kind: 'closed', key: `${record.src ?? ''}:${record.time ?? ''}:${index}`, record }))\n}\n","/**\n * 客户端时间口径**单源**:补零 / 时刻串(本机时区)。\n * 解析失败按调用方给的兜底返回(不编造时间)。\n *\n * 2026-10-17 改版后首页不再做时间范围过滤(时间只作用于统计卡与趋势图,由 host 聚合)\n * ⇒ 原来的 `rangeToDates` / `withinRange` / `RangeKey` 一并删除;统计 tab 要做时间筛选时\n * 按当时口径在此重建,别处不许再写一份。\n */\nexport function pad2(value: number | string): string {\n return String(value).padStart(2, '0')\n}\n\n/** ISO → `YYYY-MM-DD HH:mm`(`seconds: true` 时补 `:ss`);解析失败返回 `fallback`(缺省原串)。 */\nexport function formatDateTime(iso: string, opts?: { seconds?: boolean; fallback?: string }): string {\n const ms = Date.parse(iso)\n if (Number.isNaN(ms)) return opts?.fallback ?? iso\n const d = new Date(ms)\n const base = `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(d.getMinutes())}`\n return opts?.seconds === true ? `${base}:${pad2(d.getSeconds())}` : base\n}\n","/**\n * 语义 token 层:`--dtl-*`(dtl = dsh-task-ledger)。\n *\n * 规矩(docs/design/ui-style.md §三,2026-10-17 对齐 dispatch 表后收严):\n * - **只有本文件定义 `--dtl-*`**;其它文件一律 `var(--dtl-*)` 消费,不许直接写 `var(--dsw-*)`;\n * - 每个 token 都带兜底值 ⇒ 宿主升级换名时不炸(最坏退到兜底色,不会白屏);\n * - 明暗差异只写在本文件末尾的 `body[data-ds-dark-theme]` 段:能靠宿主 alias 表达的\n * 一律不写覆盖(alias 自己随主题变),只有「固定中性面 / 半透明底」这类 alias 表达不了的才进该段。\n * - **离散刻度**:圆角 4/8/12/16/20/28、间距 4/8/12/16、控件高 24/28/32、字号 11/12/13/14/16\n * ——业务文件不许出现 5/7/9px、不许自造高度档(要加档位改本文件)。\n *\n * 明暗判据 = `body[data-ds-dark-theme]`(宿主 0.2.0-rc.2 源码核实:启动脚本 toggleAttribute 写入)。\n * 插件**禁用** `prefers-color-scheme`(它跟系统、不跟用户在宿主里的选择)。\n * 因为判据是纯 CSS 选择器 ⇒ 宿主切主题即生效,**不需要 MutationObserver**。\n *\n * 挂载点 = `body`:插件界面(含将来 portal 到 body 的弹层)全在 body 内,零调用点成本。\n */\nexport const UI_TOKENS_CSS = `\nbody{\n /* ── 文字四档 + 反色面 ───────────────────────────────────────── */\n --dtl-fg:var(--dsw-alias-label-primary,#1f2328);\n --dtl-fg-2:var(--dsw-alias-label-secondary,#5c6370);\n --dtl-fg-3:var(--dsw-alias-label-tertiary,#767d87);\n --dtl-fg-4:var(--dsw-alias-label-caption,#9aa1ab);\n --dtl-fg-dim:var(--dsw-alias-label-dimmed,#b9bfc7);\n /* 反色面上的字(角标 / 实心钮):暗色下是 bluish-1000 ⇒ 用 -foreground 而非 -inverted */\n --dtl-fg-inverse:var(--dsw-alias-label-primary-foreground,#fff);\n\n /* ── 面 ──────────────────────────────────────────────────────── */\n --dtl-surface-1:var(--dsw-alias-bg-layer-1,#fff);\n --dtl-surface-2:var(--dsw-alias-bg-layer-2,#fff);\n --dtl-surface-base:var(--dsw-alias-bg-base,#fff);\n /* 「选中亮片」底盘(配 shadow-raised 用) */\n --dtl-surface-raised:var(--dsw-alias-bg-layer-1,#fff);\n /* 固定中性实面(自绘浮标 / 角标这类必须自己变的东西) */\n --dtl-solid:var(--dsw-static-neutral-00,#fff);\n --dtl-on-solid:var(--dsw-alias-label-primary,#1f2328);\n /* 斑马纹 / 次层面(浅色下近白、深色下深一档) */\n --dtl-plate:var(--dsw-static-neutral-50,#fafafa);\n /* 斑马纹:几乎贴着原色的一层(用户 2026-10-18:3% 还是太明显) */\n --dtl-zebra:color-mix(in srgb,var(--dtl-fg) 2%,transparent);\n --dtl-plate-hover:var(--dsw-static-neutral-100,#f5f5f5);\n /* 表头底:浅色比斑马纹再深一档(两者不能撞色) */\n --dtl-head-bg:var(--dsw-static-neutral-100,#f5f5f5);\n\n /* ── 描边四档(宿主真值 l1 4% / l2 10% / l3 12% / l4 16%)────── */\n --dtl-border-faint:var(--dsw-alias-border-l1,#0000000a);\n --dtl-border:var(--dsw-alias-border-l2,#0000001a);\n --dtl-border-strong:var(--dsw-alias-border-l3,#0000001f);\n --dtl-border-heavy:var(--dsw-alias-border-l4,#00000029);\n\n /* ── 语义色 ──────────────────────────────────────────────────── */\n /* 品牌「面」色:明色近黑、暗色近白(实心钮选中态即此),不是蓝 */\n --dtl-accent:var(--dsw-alias-brand-primary,#0f1115);\n /* 蓝色强调 / 选中 / 焦点一律走这条 */\n --dtl-business:var(--dsw-alias-state-business-primary,#3b5bdb);\n --dtl-success:var(--dsw-alias-state-success-primary,#22c55e);\n /* ⚠️ 宿主是 state-warn(不是 state-warning,后者无此定义) */\n --dtl-warning:var(--dsw-alias-state-warn-primary,#f59e0b);\n --dtl-danger:var(--dsw-alias-state-error-primary,#ec1313);\n /* 逾期橙:在「积压黄」与「停滞红」之间再取一档(语义阶梯 灰→黄→橙→红),\n 宿主没有现成的橙 token ⇒ 用 warn + error 两色现算,主题一换自动跟随 */\n --dtl-overdue:color-mix(in srgb,var(--dtl-warning) 55%,var(--dtl-danger));\n --dtl-link:var(--dsw-alias-link,#3b5bdb);\n /* 语义色实面上的字(角标 / 实心提示):红绿黄都是中调 ⇒ 白字两个主题都可读 */\n --dtl-on-signal:#fff;\n /* 语义色浅底:color-mix 现算 ⇒ 跟随宿主 alias,主题一换自动成立 */\n --dtl-business-soft:color-mix(in srgb,var(--dtl-business) 14%,transparent);\n /* 他人任务的 doing 色:比「我的」蓝灰一档(用户 2026-10-17:要和我的蓝区分) */\n --dtl-business-dim:color-mix(in srgb,var(--dtl-business) 55%,var(--dtl-fg-3));\n /* 组标题底:左深蓝竖条后面的那层**浅蓝**(用户 2026-10-18 照参考图) */\n --dtl-group-head-bg:#eaf1ff;\n --dtl-success-soft:color-mix(in srgb,var(--dtl-success) 8%,transparent);\n --dtl-warning-soft:color-mix(in srgb,var(--dtl-warning) 8%,transparent);\n --dtl-danger-soft:color-mix(in srgb,var(--dtl-danger) 8%,transparent);\n\n /* ── 交互底 ──────────────────────────────────────────────────── */\n --dtl-hover:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06));\n --dtl-active:var(--dsw-alias-interactive-bg-active,#2631481a);\n /* 卡 / 行 hover 高亮:淡蓝(灰高亮在卡底上几乎看不出来) */\n --dtl-card-hover:rgba(37,99,235,.07);\n /* 展开行底:带透明度的蓝,透出卡片底色(灰底跟斑马纹分不出来) */\n --dtl-open-bg:rgba(37,99,235,.10);\n --dtl-open-bg-soft:rgba(37,99,235,.05);\n /* chip / 标签小底板:浅色下要看得见 ⇒ 掺 7% 近黑;hover 加倍 */\n --dtl-chip-bg:rgba(15,15,15,.07);\n --dtl-chip-bg-hover:rgba(15,15,15,.14);\n /* 图表「新增」柱:**实底**中性灰。半透明底压在卡上会发糊(用户 2026-10-17:「模模糊糊」) */\n --dtl-chart-created:var(--dsw-static-neutral-300,#d4d4d8);\n /* 图表「没完成」柱:醒目琥珀,与蓝(完成)/ 灰(新增)区分(host 无此件,fallback 兜底) */\n --dtl-chart-remaining:var(--dsw-static-amber-400,#f59e0b);\n\n /* ── 圆角(宿主真值 4/8/12/16/20/28)──────────────────────────── */\n --dtl-radius-xs:var(--dsw-radius-xs,4px);\n --dtl-radius-sm:var(--dsw-radius-sm,8px);\n --dtl-radius-md:var(--dsw-radius-md,12px);\n --dtl-radius-lg:var(--dsw-radius-lg,16px);\n --dtl-radius-xl:var(--dsw-radius-xl,20px);\n --dtl-radius-panel:var(--dsw-radius-panel,28px);\n\n /* ── 间距四拍(只许这四档)────────────────────────────────────── */\n --dtl-space-1:4px;\n --dtl-space-2:8px;\n --dtl-space-3:12px;\n --dtl-space-4:16px;\n\n /* ── 控件高度:离散三档(调用点不许自定义高度)────────────────── */\n --dtl-control-h-sm:24px;\n --dtl-control-h-md:28px;\n --dtl-control-h-lg:32px;\n\n /* ── 字号 / 行高(直绑宿主字号族 11/12/13/14/16)──────────────── */\n --dtl-font-xs:var(--dsw-font-xxxs-11-font-size,11px);\n --dtl-font-sm:var(--dsw-font-xxs-12-font-size,12px);\n --dtl-font-md:var(--dsw-font-xs-13-font-size,13px);\n --dtl-font-lg:var(--dsw-font-s-14-font-size,14px);\n --dtl-font-xl:var(--dsw-font-base-16-font-size,16px);\n --dtl-line-xs:var(--dsw-font-xxxs-11-line-height,14px);\n --dtl-line-sm:var(--dsw-font-xxs-12-line-height,18px);\n --dtl-line-md:var(--dsw-font-xs-13-line-height,20px);\n --dtl-line-lg:var(--dsw-font-s-14-line-height,22px);\n --dtl-line-xl:var(--dsw-font-base-16-line-height,24px);\n --dtl-font-mono:var(--ds-font-family-code,monospace);\n /* 统计卡主数字:宿主字号族最大 16px,看板主数字要更大 ⇒ 单开一档(别处不许直接写 px) */\n --dtl-font-stat:26px;\n\n /* ── 投影 / 动效 / 层级 ──────────────────────────────────────── */\n --dtl-shadow-1:var(--dsw-elevation-soft,0 4px 16px 0 #00000008);\n --dtl-shadow-raised:var(--dsw-elevation-soft,0 4px 16px 0 #00000008);\n --dtl-shadow-2:var(--dsw-shadow-lv3,0 12px 32px 0 #00000014);\n --dtl-dur:var(--ds-transition-duration,.2s);\n --dtl-dur-fast:var(--ds-transition-duration-fast,.12s);\n --dtl-ease:var(--ds-ease-in-out,cubic-bezier(.4,0,.2,1));\n --dtl-z-badge:20;\n}\n\n/* ── 明暗差异的唯一落点 ─────────────────────────────────────────\n 只放 alias 表达不了的固定面 / 半透明底;颜色若能靠 alias 自动跟随,一律不写在这里。 */\nbody[data-ds-dark-theme]{\n --dtl-solid:var(--dsw-static-neutral-900,#0f0f0f);\n --dtl-on-solid:var(--dsw-static-neutral-00,#fff);\n --dtl-plate:var(--dsw-static-neutral-850,#212123);\n --dtl-zebra:color-mix(in srgb,var(--dtl-fg) 2.5%,transparent);\n --dtl-plate-hover:var(--dsw-static-neutral-800,#292929);\n /* 暗色表头反过来:比卡片面亮、比斑马纹浅一档 */\n --dtl-head-bg:var(--dtl-surface-2);\n /* 暗色下蓝要更亮、透明度更高才压得住深底 */\n --dtl-open-bg:rgba(96,165,250,.18);\n --dtl-open-bg-soft:rgba(96,165,250,.08);\n --dtl-card-hover:rgba(96,165,250,.13);\n /* 暗色 chip 底板反过来掺白 */\n --dtl-chip-bg:rgba(255,255,255,.08);\n --dtl-chip-bg-hover:rgba(255,255,255,.16);\n --dtl-chart-created:var(--dsw-static-neutral-600,#525252);\n --dtl-chart-remaining:var(--dsw-static-amber-500,#fbbf24);\n /* 语义色浅底在深色下压到 5%(8% 在深底上偏重) */\n --dtl-business-soft:color-mix(in srgb,var(--dtl-business) 12%,transparent);\n --dtl-business-dim:color-mix(in srgb,var(--dtl-business) 55%,var(--dtl-fg-3));\n /* 组标题底(暗):**实色**蓝(参考图不是半透明叠加出来的) */\n --dtl-group-head-bg:#1e3a5f;\n --dtl-success-soft:color-mix(in srgb,var(--dtl-success) 5%,transparent);\n --dtl-warning-soft:color-mix(in srgb,var(--dtl-warning) 5%,transparent);\n --dtl-danger-soft:color-mix(in srgb,var(--dtl-danger) 5%,transparent);\n}\n`\n\n/**\n * 控件皮肤(L2):**一类控件唯一实现**,抄 dispatch 表 controls-css 的规矩——\n * 只消费 var(--dtl-*),一个颜色 / 尺寸 / 圆角 / 字号字面量都不许出现;\n * 高度只取三档,有边框的变体用 1px 真边框 + 内部 padding 补回 ⇒ 有边 / 无边同高。\n */\nexport const UI_CONTROLS_CSS = `\n/* ── 分段控件(滑动块)────────────────────────────────────────────\n 结构一套 + 两个受控轴:size(sm|md|lg) × variant(default|inset)。\n 变体只覆盖两个颜色变量(--seg-track / --seg-thumb),结构 / 尺寸 / 交互零重复。 */\n.dtl-seg{display:inline-flex;align-items:center;gap:2px;padding:2px;border-radius:var(--dtl-radius-md);background:var(--seg-track);}\n.dtl-seg--default{--seg-track:var(--dtl-surface-2);--seg-thumb:var(--dtl-surface-raised);border:1px solid var(--dtl-border);}\n.dtl-seg--inset{--seg-track:var(--dtl-hover);--seg-thumb:var(--dtl-surface-raised);border:0;padding:3px;}\n.dtl-seg__item{appearance:none;display:inline-flex;align-items:center;gap:4px;box-sizing:border-box;\n height:calc(var(--dtl-control-h-sm) - 6px);padding:0 12px;border:0;border-radius:var(--dtl-radius-sm);\n background:transparent;color:var(--dtl-fg-2);font-family:inherit;font-size:var(--dtl-font-sm);line-height:var(--dtl-line-sm);\n font-weight:400;white-space:nowrap;cursor:pointer;\n transition:color var(--dtl-dur-fast) var(--dtl-ease),background-color var(--dtl-dur-fast) var(--dtl-ease),box-shadow var(--dtl-dur-fast) var(--dtl-ease);}\n.dtl-seg--md .dtl-seg__item{height:calc(var(--dtl-control-h-md) - 6px);}\n.dtl-seg--lg .dtl-seg__item{height:calc(var(--dtl-control-h-lg) - 6px);}\n.dtl-seg__item:hover:not([aria-pressed='true']):not(:disabled){color:var(--dtl-fg);}\n.dtl-seg__item[aria-pressed='true']{background:var(--seg-thumb);box-shadow:var(--dtl-shadow-raised);color:var(--dtl-fg);font-weight:600;}\n.dtl-seg__item:focus-visible{outline:2px solid var(--dtl-business);outline-offset:-2px;}\n.dtl-seg__item:disabled{cursor:default;opacity:.4;}\n/* 段内计数(如「待认领 3」):**纯文字,无底、无边、无圆角**(用户 2026-10-18:圆圈取了、字再小点)。\n 类名用 num 而不是旧 badge —— 万一宿主 DOM 里残留旧版 .dtl-seg__badge 圆圈规则,也匹配不中;0 不渲染 */\n.dtl-seg__num{display:inline;flex:none;margin-left:3px;padding:0;border:0;border-radius:0;\n background:none;color:var(--dtl-fg-3);font-size:10px;line-height:1;font-weight:400;font-variant-numeric:tabular-nums;}\n\n/* ── 按钮 / 图标钮 ───────────────────────────────────────────────\n 与 Segmented 同一条高度纪律;variant 只换颜色,结构 / 尺寸 / 交互只此一份。 */\n.dtl-btn{appearance:none;display:inline-flex;align-items:center;justify-content:center;gap:var(--dtl-space-1);\n box-sizing:border-box;border:1px solid transparent;border-radius:var(--dtl-radius-md);\n background:transparent;color:var(--dtl-fg);font-family:inherit;font-weight:500;white-space:nowrap;cursor:pointer;\n transition:background-color var(--dtl-dur-fast) var(--dtl-ease),border-color var(--dtl-dur-fast) var(--dtl-ease),color var(--dtl-dur-fast) var(--dtl-ease),opacity var(--dtl-dur-fast) var(--dtl-ease);}\n.dtl-btn--sm{height:var(--dtl-control-h-sm);padding:0 10px;font-size:var(--dtl-font-sm);line-height:var(--dtl-line-sm);}\n.dtl-btn--md{height:var(--dtl-control-h-md);padding:0 12px;font-size:var(--dtl-font-md);line-height:var(--dtl-line-md);}\n.dtl-btn--lg{height:var(--dtl-control-h-lg);padding:0 14px;font-size:var(--dtl-font-md);line-height:var(--dtl-line-md);}\n.dtl-btn--primary{background:var(--dtl-accent);color:var(--dtl-fg-inverse);}\n.dtl-btn--primary:hover:not(:disabled){opacity:.9;}\n.dtl-btn--outline{background:var(--dtl-surface-1);border-color:var(--dtl-border-strong);color:var(--dtl-fg);}\n.dtl-btn--outline:hover:not(:disabled){background:var(--dtl-hover);}\n.dtl-btn--ghost{color:var(--dtl-fg-2);}\n.dtl-btn--ghost:hover:not(:disabled){background:var(--dtl-hover);color:var(--dtl-fg);}\n.dtl-btn:focus-visible{outline:2px solid var(--dtl-business);outline-offset:2px;}\n.dtl-btn:disabled{cursor:default;opacity:.5;}\n.dtl-iconbtn{appearance:none;display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;\n border:1px solid transparent;border-radius:var(--dtl-radius-sm);background:transparent;color:var(--dtl-fg-2);\n font-family:inherit;cursor:pointer;flex:none;\n transition:background-color var(--dtl-dur-fast) var(--dtl-ease),border-color var(--dtl-dur-fast) var(--dtl-ease),color var(--dtl-dur-fast) var(--dtl-ease),opacity var(--dtl-dur-fast) var(--dtl-ease);}\n.dtl-iconbtn--sm{width:var(--dtl-control-h-sm);height:var(--dtl-control-h-sm);font-size:var(--dtl-font-sm);}\n.dtl-iconbtn--md{width:var(--dtl-control-h-md);height:var(--dtl-control-h-md);font-size:var(--dtl-font-md);}\n.dtl-iconbtn--lg{width:var(--dtl-control-h-lg);height:var(--dtl-control-h-lg);font-size:var(--dtl-font-md);}\n.dtl-iconbtn--outline{background:var(--dtl-surface-1);border-color:var(--dtl-border);color:var(--dtl-fg-2);}\n.dtl-iconbtn:hover:not(:disabled){background:var(--dtl-hover);color:var(--dtl-fg);}\n.dtl-iconbtn:focus-visible{outline:2px solid var(--dtl-business);outline-offset:1px;}\n.dtl-iconbtn:disabled{cursor:default;opacity:.4;}\n\n/* ── 输入 / 搜索框 / 下拉 ──────────────────────────────────────── */\n.dtl-input{box-sizing:border-box;width:100%;border:1px solid var(--dtl-border);border-radius:var(--dtl-radius-md);\n background:var(--dtl-surface-1);color:var(--dtl-fg);font-family:inherit;outline:none;\n transition:border-color var(--dtl-dur-fast) var(--dtl-ease),box-shadow var(--dtl-dur-fast) var(--dtl-ease);}\n.dtl-input--sm{height:var(--dtl-control-h-sm);padding:0 8px;font-size:var(--dtl-font-sm);line-height:var(--dtl-line-sm);}\n.dtl-input--md{height:var(--dtl-control-h-md);padding:0 10px;font-size:var(--dtl-font-md);line-height:var(--dtl-line-md);}\n.dtl-input--lg{height:var(--dtl-control-h-lg);padding:0 12px;font-size:var(--dtl-font-md);line-height:var(--dtl-line-md);}\n.dtl-input::placeholder{color:var(--dtl-fg-dim);}\n.dtl-input:focus{border-color:var(--dtl-business);box-shadow:0 0 0 1px var(--dtl-business);}\n.dtl-input:disabled{cursor:default;opacity:.5;}\n/* 原生 select: appearance:none + 自绘箭头 ⇒ 与输入框同宗(宿主没有廉价下拉件) */\n.dtl-select{appearance:none;-webkit-appearance:none;box-sizing:border-box;\n border:1px solid var(--dtl-border);border-radius:var(--dtl-radius-md);background:var(--dtl-surface-1);color:var(--dtl-fg);\n font-family:inherit;outline:none;cursor:pointer;\n background-image:url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23767d87' stroke-width='1.5'><path d='M4 6.5 8 10.5 12 6.5'/></svg>\");\n background-repeat:no-repeat;background-position:right 8px center;background-size:14px;\n transition:border-color var(--dtl-dur-fast) var(--dtl-ease),box-shadow var(--dtl-dur-fast) var(--dtl-ease);}\n.dtl-select--sm{height:var(--dtl-control-h-sm);padding:0 26px 0 8px;font-size:var(--dtl-font-sm);line-height:var(--dtl-line-sm);}\n.dtl-select--md{height:var(--dtl-control-h-md);padding:0 26px 0 10px;font-size:var(--dtl-font-md);line-height:var(--dtl-line-md);}\n.dtl-select--lg{height:var(--dtl-control-h-lg);padding:0 26px 0 12px;font-size:var(--dtl-font-md);line-height:var(--dtl-line-md);}\n.dtl-select:focus{border-color:var(--dtl-business);box-shadow:0 0 0 1px var(--dtl-business);}\n.dtl-select:disabled{cursor:default;opacity:.5;}\n\n/* ── 卡 / chip / 空态 / 列表行(业务壳,皮肤仍只消费 token)────── */\n.dtl-card{background:var(--dtl-surface-1);border:1px solid var(--dtl-border);border-radius:var(--dtl-radius-md);\n transition:background-color var(--dtl-dur-fast) var(--dtl-ease);}\n.dtl-card--clickable{cursor:pointer;text-align:left;font:inherit;color:inherit;width:100%;}\n.dtl-card--clickable:hover{background:var(--dtl-card-hover);}\n.dtl-card--clickable[data-active='true']{border-color:var(--dtl-business);}\n.dtl-card--clickable:focus-visible{outline:2px solid var(--dtl-business);outline-offset:1px;}\n.dtl-chip{display:inline-flex;align-items:center;gap:4px;height:20px;padding:0 8px;border-radius:var(--dtl-radius-sm);\n background:var(--dtl-chip-bg);color:var(--dtl-fg-2);font-size:var(--dtl-font-xs);line-height:var(--dtl-line-xs);white-space:nowrap;}\n.dtl-row{display:flex;align-items:center;gap:var(--dtl-space-2);width:100%;box-sizing:border-box;\n padding:var(--dtl-space-2) var(--dtl-space-3);border:0;border-bottom:1px solid var(--dtl-border-faint);\n background:transparent;font:inherit;color:inherit;text-align:left;cursor:pointer;\n transition:background-color var(--dtl-dur-fast) var(--dtl-ease);}\n.dtl-row:hover{background:var(--dtl-card-hover);}\n.dtl-row:focus-visible{outline:2px solid var(--dtl-business);outline-offset:-2px;}\n.dtl-row[data-open='true']{background:var(--dtl-open-bg);}\n.dtl-row-body{padding:var(--dtl-space-2) var(--dtl-space-3) var(--dtl-space-3);border-bottom:1px solid var(--dtl-border-faint);\n background:var(--dtl-open-bg-soft);font-size:var(--dtl-font-md);}\n.dtl-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}\n\n@media (prefers-reduced-motion: reduce){\n .dtl-seg__item,.dtl-btn,.dtl-iconbtn,.dtl-input,.dtl-select,.dtl-card,.dtl-row{transition:none;}\n}\n`\n","/**\n * UI 基础层:样式单源 + 共用元件(**一类控件只有一份实现**)。\n *\n * 纪律(用户 2026-10-17「样式与方法一律抽象,不要反复重写」;形态抄本机\n * `dsh-task-dispatch-table` 的 `ui/tokens.ts` + `ui/controls-css.ts` + `ui/*.tsx`):\n * 1. **样式**:颜色 / 圆角 / 字号 / 高度一律 `var(--dtl-*)`(定义只在 tokens.ts),\n * 业务文件不许再写第二份 `var(--dsw-*)`、不许自造 `--dtl-*`、不许自造高度档;\n * 2. **元件**:分段 / 按钮 / 图标钮 / 输入 / 下拉 / 卡 / chip / 空态 / 图表各一份,\n * 页面与浮标共用。官方 primitives 只留「官方做得更好」的(StateDot / MarkdownText / 图标)。\n */\nimport { createElement as h, useState, type ChangeEvent, type CSSProperties, type ReactNode } from 'react'\nimport { Tooltip } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { UI_CONTROLS_CSS, UI_TOKENS_CSS } from './tokens'\n\n/** 基础层样式(tokens + 控件皮肤;幂等注入)。 */\nconst UI_BASE_CSS = `\n/* 统计卡:可点、hover 淡蓝、选中蓝描边 */\n.dtl-stat{display:flex;flex-direction:column;gap:var(--dtl-space-1);\n padding:var(--dtl-space-3) var(--dtl-space-4);text-align:left;}\n.dtl-stat__value{font-size:var(--dtl-font-stat);font-weight:600;line-height:1.15;font-variant-numeric:tabular-nums;}\n.dtl-stat__hint{font-size:var(--dtl-font-xs);line-height:var(--dtl-line-xs);color:var(--dtl-fg-3);}\n\n/* 列表行:分组标题头仍走 .dtl-row-head(可点折叠);任务行走 .dtl-taskrow(两行式,不可点) */\n.dtl-row-head{display:flex;align-items:center;gap:var(--dtl-space-2);\n width:100%;padding:var(--dtl-space-2) var(--dtl-space-3);border:0;background:transparent;\n color:inherit;font:inherit;text-align:left;cursor:pointer;\n transition:background-color var(--dtl-dur-fast) var(--dtl-ease);}\n.dtl-row-head:hover{background:var(--dtl-card-hover);}\n.dtl-row-head:focus-visible{outline:2px solid var(--dtl-business);outline-offset:-2px;}\n\n/* 其他待办 = **一组一张表**(用户 2026-10-18 照参考图 Stripe Payments:Pending / Archived /\n Completed 各一组):组标题是**独立一行**——透明无底、远离上一组、贴近自己的表,\n 文字加粗放大;表体照旧(行下一条线 + 极浅斑马纹)。 */\n/* 组与组**零外距**、连体(用户 2026-10-18:上间距去掉,和上面连在一起) */\n.dtl-group{margin-bottom:0;}\n/* 组标题(照参考图):**左侧深蓝竖条 + 浅蓝底(实色)**;行高保持(≈42px);\n 工作区名稍小一号(14px);上缘无边 */\n.dtl-group__head{position:relative;display:flex;align-items:center;gap:8px;width:100%;box-sizing:border-box;\n padding:10px var(--dtl-space-3);border:0;border-left:5px solid var(--dtl-business);\n border-radius:0;background:var(--dtl-group-head-bg);\n color:inherit;font:inherit;text-align:left;cursor:pointer;}\n/* 下缘那条线**绝不用 border 画**:border-left(5px) 与 border-bottom(1px) 在左下角\n 按 miter 斜接,会切出一个斜缺角(用户 2026-10-18 实锤,不是视觉误差)⇒ 改用伪元素\n 独立 1px;定位基于 padding box ⇒ 自动从竖条内缘起笔,竖条保持整直。 */\n.dtl-group__head::after{content:'';position:absolute;left:0;right:0;bottom:0;height:1px;\n background:var(--dtl-border-faint);pointer-events:none;}\n.dtl-group__name{font-size:var(--dtl-font-lg);font-weight:700;line-height:var(--dtl-line-lg);color:var(--dtl-fg);}\n.dtl-group__stats{font-size:var(--dtl-font-xs);color:var(--dtl-fg-2);white-space:nowrap;}\n/* 组标题标识:官方文件夹图标(展开=开、收起=合);图标缺席时回落成竖线 */\n.dtl-group__bar{width:3px;height:14px;flex:none;border-radius:2px;background:var(--dtl-business);}\n/* 表格行(.dtl-orow):**零重用**「我的待办」卡片基类——无框、无圆角、无外边距,\n 一行 = 一条底部分隔线 + 极浅斑马纹(用户 2026-10-18:一行一格,不是一块一块的框) */\n.dtl-orow{padding:13px var(--dtl-space-3);border-bottom:1px solid var(--dtl-border-faint);}\n/* 斑马纹落在 2、4、6 行(用户 2026-10-18:原来 1、3、5 看着像从第一行就开始断层) */\n.dtl-orow:nth-of-type(even){background:var(--dtl-zebra);}\n.dtl-orow:hover{background:var(--dtl-card-hover);}\n.dtl-orow__row{display:flex;align-items:center;gap:12px;}\n.dtl-orow__title{display:flex;align-items:baseline;gap:6px;min-width:0;}\n.dtl-orow__name{font-size:var(--dtl-font-md);font-weight:500;line-height:var(--dtl-line-md);min-width:0;}\n/* 第二行:状态标签 + #标签(与标题行间距拉开,用户 2026-10-18;状态位与执行人**互换**后,\n 状态搬到这里、且永远有 ⇒ 这一行不再有「空一排」的情况) */\n.dtl-orow__tags{display:flex;align-items:center;gap:var(--dtl-space-2);margin-top:7px;min-width:0;}\n/* 列:任务(弹性)/ 执行人 / 右侧槽(创建信息 ⇄ 操作钮) */\n.dtl-tcell{min-width:0;}\n.dtl-tcell--title{flex:1 1 auto;}\n/* 执行人列(原状态列,用户 2026-10-18 互换):稍放宽一档(用户 2026-10-19:原来有点挤),\n 20 字的名字能基本摆全;再长由标签自己的省略号兜住,不许溢出压到右边的槽 */\n.dtl-tcell--status{flex:none;width:136px;}\n.dtl-tcell--status .dtl-tag{max-width:100%;}\n/* 悬停:**创建信息整条让位给操作钮**(用户 2026-10-18:不要「掉掉时分秒」那种半吊子,\n 要整条换掉)。两个都**常驻布局**(visibility 切换、不用 display),格子宽度 = 两者较宽的\n 那个 ⇒ hover 时行宽不变、**页面不跳**(跳了执行人的悬停提示就跟着飘) */\n.dtl-orow__slot{display:grid;align-items:center;justify-items:end;flex:none;\n font-size:var(--dtl-font-xs);color:var(--dtl-fg-3);white-space:nowrap;font-variant-numeric:tabular-nums;}\n.dtl-orow__slot>*{grid-area:1/1;}\n.dtl-orow__created{visibility:visible;}\n.dtl-orow__actions{visibility:hidden;display:flex;align-items:center;gap:6px;}\n.dtl-orow:hover .dtl-orow__created{visibility:hidden;}\n.dtl-orow:hover .dtl-orow__actions{visibility:visible;}\n/* 「查看」钮在表格行里也要常驻布局(display:none 会不算宽度 ⇒ hover 一出就又把行撑开) */\n.dtl-orow .dtl-viewbtn{display:inline-flex;visibility:hidden;}\n.dtl-orow:hover .dtl-viewbtn{visibility:visible;}\n/* 行首状态圆点:灰=待认领 / 浅蓝=进行中(比「我的」蓝灰一档)/ 黄=积压 / 橙=逾期 / 红=停滞;\n 6px(用户 2026-10-18:8px 太大) */\n.dtl-dot{display:inline-block;width:6px;height:6px;flex:none;border-radius:50%;}\n.dtl-dot--doing{background:var(--dtl-business-dim);}\n.dtl-dot--backlog{background:var(--dtl-warning);}\n.dtl-dot--overdue{background:var(--dtl-overdue);}\n.dtl-dot--stale{background:var(--dtl-danger);}\n.dtl-dot--open,.dtl-dot--closed{background:var(--dtl-fg-3);}\n\n/* 「查看」钮:与行内动作钮(.dtl-minibtn)同款皮肤、**正方形(26×26,宽=高)**、\n **默认不显、悬停才出来**(用户 2026-10-18:跟认领/继续那几个一起浮现) */\n.dtl-viewbtn{position:relative;display:none;align-items:center;justify-content:center;gap:5px;\n flex:none;width:26px;height:26px;padding:0;border:1px solid var(--dtl-border);\n border-radius:var(--dtl-radius-sm);background:var(--dtl-surface-1);color:var(--dtl-fg-2);\n font:inherit;font-size:var(--dtl-font-sm);line-height:18px;white-space:nowrap;cursor:default;}\n.dtl-viewbtn:hover{border-color:var(--dtl-border-strong);color:var(--dtl-fg);}\n.dtl-taskrow__row:hover .dtl-viewbtn,.dtl-orow:hover .dtl-viewbtn{display:inline-flex;}\n\n/* 任务行 = 一张卡(样式照抄 dispatch-table TaskCard:surface-1 底 + 1px 边框 + 左缘状态条;\n hover 只亮主行;右侧 时间⇄按钮排 互换) */\n.dtl-taskrow{background:var(--dtl-surface-1);border:1px solid var(--dtl-border);\n border-radius:var(--dtl-radius-sm);margin-bottom:10px;}\n.dtl-taskrow__row{display:flex;align-items:center;gap:12px;padding:12px 14px;border-radius:inherit;\n transition:background-color var(--dtl-dur-fast) var(--dtl-ease);}\n.dtl-taskrow__row:hover{background:var(--dtl-card-hover);}\n\n/* 状态条:贴卡片左缘、与正文两行等高的竖向色条(dispatch StatusRail 同款 6×36) */\n.dtl-rail{display:inline-block;width:6px;height:36px;flex:none;border-radius:var(--dtl-radius-xs);}\n.dtl-rail--doing{background:var(--dtl-business);}\n.dtl-rail--backlog{background:var(--dtl-warning);}\n.dtl-rail--overdue{background:var(--dtl-overdue);}\n.dtl-rail--stale{background:var(--dtl-danger);}\n.dtl-rail--open,.dtl-rail--closed{background:var(--dtl-fg-3);}\n\n/* 第一行:标题(上来就是标题)+ [YYYY-MM-DD 创建] 灰徽 + 异常 pill(baseline 对齐,dispatch 同款) */\n.dtl-taskrow__l1{display:flex;align-items:baseline;gap:6px;min-width:0;}\n.dtl-taskrow__title{font-size:var(--dtl-font-lg);font-weight:600;line-height:var(--dtl-line-md);min-width:0;flex:0 1 auto;}\n.dtl-taskrow__stamp{flex:none;font-size:var(--dtl-font-xs);color:var(--dtl-fg-3);white-space:nowrap;\n font-variant-numeric:tabular-nums;}\n/* 第二行小字:工作区 · #标签(行距拉开,用户 2026-10-17:第一二行别贴着) */\n.dtl-taskrow__l2{display:flex;align-items:center;gap:var(--dtl-space-2);margin-top:6px;\n font-size:var(--dtl-font-sm);color:var(--dtl-fg-2);min-width:0;}\n/* 右侧灰字:2026-10-17 开始丨已进行 12 天(用户 2026-10-17:不要蓝,一行灰字) */\n.dtl-taskrow__when{flex:none;font-size:var(--dtl-font-xs);color:var(--dtl-fg-3);white-space:nowrap;\n font-variant-numeric:tabular-nums;}\n.dtl-taskrow__actions{display:none;flex:none;gap:6px;}\n.dtl-taskrow__row:hover .dtl-taskrow__when{display:none;}\n.dtl-taskrow__row:hover .dtl-taskrow__actions{display:inline-flex;}\n\n/* 标签(用户 2026-10-18):状态 / 执行人 / #标签 **共用一枚件**,只换色调。\n 最大宽度 20ch(≈10 个中文或 20 个字符)⇒ 名字上限 20 字;超出**省略号(3 个点)**,\n 悬停把全名补出来(用户 2026-10-19:不跑马灯了)。**没被截 ⇒ 悬停无反应**\n (提示由 Tag 元件按实际宽度决定挂不挂,这里只管裁切)。 */\n.dtl-tag{display:inline-block;flex:none;max-width:20ch;padding:0 6px;border-radius:999px;\n background:var(--dtl-chip-bg);color:var(--dtl-fg-3);font-size:10px;line-height:16px;\n white-space:nowrap;overflow:hidden;text-overflow:ellipsis;vertical-align:middle;}\n/* 状态四色 = **实心色块**(用户 2026-10-18:彩色字看不清 ⇒ 改色块 + 反色字):\n 待认领灰 / 进行中蓝 / 积压黄 / 逾期橙 / 停滞红(与行首圆点同色系)。\n 红黄蓝都是中调 ⇒ 白字两个主题都可读;灰块用「字色底 + 面色字」⇒ 明暗自动反过来 */\n.dtl-tag--open{background:var(--dtl-fg-3);color:var(--dtl-surface-1);font-weight:600;}\n.dtl-tag--doing{background:var(--dtl-business);color:var(--dtl-on-signal);font-weight:600;}\n.dtl-tag--backlog{background:var(--dtl-warning);color:var(--dtl-on-signal);font-weight:600;}\n.dtl-tag--overdue{background:var(--dtl-overdue);color:var(--dtl-on-signal);font-weight:600;}\n.dtl-tag--stale{background:var(--dtl-danger);color:var(--dtl-on-signal);font-weight:600;}\n/* 执行人:浅蓝底(与状态色区分:状态是「事怎么样」,人是「谁在做」) */\n.dtl-tag--person{background:var(--dtl-open-bg-soft);color:var(--dtl-fg-2);}\n/* 分区标题里的计数:与日期徽同款中括号小字(用户 2026-10-17);0 条不渲染 */\n.dtl-count{font-size:var(--dtl-font-xs);color:var(--dtl-fg-3);font-variant-numeric:tabular-nums;}\n\n/* 工作区徽标:只铺蓝底(用户 2026-10-17:描边框太抢 ⇒ 去掉;底色已提亮一档见 tokens.ts\n business-soft 8%→14% / 5%→12%),字色回正文。 */\n.dtl-wstag{flex:none;max-width:220px;padding:1px 8px;border-radius:var(--dtl-radius-sm);\n background:var(--dtl-business-soft);color:var(--dtl-fg);\n font-size:var(--dtl-font-xs);line-height:16px;white-space:nowrap;}\n\n/* 行内动作按钮(icon + 字,加大;悬停说明走官方 Tooltip,不在 CSS 里手搓浮层)。\n 样子先行,点击行为归「任务按钮与会话交接」任务 */\n.dtl-minibtn{display:inline-flex;align-items:center;gap:5px;padding:4px 12px;\n border:1px solid var(--dtl-border);border-radius:var(--dtl-radius-sm);\n background:var(--dtl-surface-1);color:var(--dtl-fg-2);font:inherit;font-size:var(--dtl-font-sm);\n line-height:18px;white-space:nowrap;cursor:default;}\n.dtl-minibtn:hover{border-color:var(--dtl-border-strong);color:var(--dtl-fg);}\n\n/* 空态:真空 = 虚线引导卡(用户 2026-10-18:**加高到 180px + 内容居中**;\n 它贴在滚动条最底下,太窄用户根本看不见) */\n.dtl-empty-guide{display:flex;flex-direction:column;align-items:center;justify-content:center;\n gap:var(--dtl-space-2);min-height:270px;padding:var(--dtl-space-4);text-align:center;\n border:1px dashed var(--dtl-border-strong);border-radius:var(--dtl-radius-md);background:transparent;}\n.dtl-empty-filtered{padding:var(--dtl-space-4) 0;color:var(--dtl-fg-3);font-size:var(--dtl-font-md)}\n\n/* 浮标角标:0→n 跳变时一次脉冲,不自动消失 */\n.dtl-badge{position:absolute;top:-2px;right:-2px;min-width:16px;height:16px;padding:0 4px;\n border-radius:8px;background:var(--dtl-danger);color:var(--dtl-on-signal);\n font-size:10px;line-height:16px;text-align:center;font-weight:600;}\n.dtl-badge[data-pulse=\"true\"]{animation:dtl-pulse 900ms var(--dtl-ease) 1}\n@keyframes dtl-pulse{0%{transform:scale(.6);opacity:.4}60%{transform:scale(1.15)}100%{transform:scale(1)}}\n\n/* 统计区整体:左 8 块(2 行 × 4 列)+ 右趋势图,参考图同款分栏。\n ⚠️ 列数写死 4 → 2,绝不给 3:3 列会排出 3+1 的非对称(用户 2026-10-17)。\n 两栏**完全等高**:cards 两行 minmax(0,1fr) 均分 + 卡 height:100% ⇒ 图表卡与左边一样高。 */\n/* 左 8 块略收窄、右图表略放宽(用户 2026-10-17:左再短一丢丢、右长一丢丢) */\n.dtl-stats{display:grid;grid-template-columns:minmax(0,1.7fr) minmax(0,1fr);gap:var(--dtl-space-3);align-items:stretch;}\n.dtl-stats__cards{display:grid;grid-auto-flow:column;grid-template-columns:repeat(4,minmax(0,1fr));grid-template-rows:repeat(2,minmax(0,1fr));\n gap:var(--dtl-space-3);}\n.dtl-stats__cards > .dtl-stat{height:100%;box-sizing:border-box;}\n@media (max-width:960px){.dtl-stats{grid-template-columns:minmax(0,1fr);}}\n@media (max-width:640px){.dtl-stats__cards{grid-template-columns:repeat(2,minmax(0,1fr));grid-template-rows:repeat(4,minmax(0,1fr));}}\n\n/* 「只看我的」小复选框(替掉右上角的分段切换) */\n.dtl-check{display:inline-flex;align-items:center;gap:5px;height:20px;margin-top:-2px;\n font-size:var(--dtl-font-xs);color:var(--dtl-fg-3);cursor:pointer;user-select:none;white-space:nowrap;}\n.dtl-check input{width:12px;height:12px;margin:0;accent-color:var(--dtl-business);cursor:pointer;}\n.dtl-check:hover{color:var(--dtl-fg-2);}\n\n/* 趋势分组柱状图:绘图区高度**随卡拉伸**(flex),柱 / 刻度 / 网格全用百分比 ⇒ 与卡等高 */\n.dtl-gchart-wrap{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;padding-left:30px;padding-bottom:18px;}\n.dtl-gchart{position:relative;flex:1 1 auto;min-height:96px;border-bottom:1px solid var(--dtl-border);}\n.dtl-gchart__grid{position:absolute;left:0;right:0;border-top:1px solid var(--dtl-border-faint);pointer-events:none;}\n.dtl-gchart__ytick{position:absolute;left:-30px;width:26px;text-align:right;font-size:10px;line-height:12px;\n color:var(--dtl-fg-3);transform:translateY(50%);font-variant-numeric:tabular-nums;}\n.dtl-gchart__col{position:absolute;top:0;bottom:0;min-width:0;}\n/* ⚠️ pair 必须 top/bottom 双锚(或 height:100%):绝对定位 + 只给 bottom 时高度是 auto,\n 子元素的 height:% 没有可解析的包含块 ⇒ 整根柱子塌成 0(2026-10-17 真机教训:\n 改百分比柱高后「一个柱子都看不见,只剩数字」)。 */\n.dtl-gchart__pair{position:absolute;top:0;bottom:0;left:50%;transform:translateX(-50%);\n display:flex;align-items:flex-end;gap:1px;}\n/* 三柱:完成(蓝) / 没完成(琥珀) / 新增(灰),每根 5px、无圆角(用户 2026-10-17:太粗、不要圆弧) */\n.dtl-gchart__bar{width:5px;min-height:0;}\n.dtl-gchart__bar--done{background:var(--dtl-business);}\n.dtl-gchart__bar--remaining{background:var(--dtl-chart-remaining);}\n.dtl-gchart__bar--created{background:var(--dtl-chart-created);}\n/* hover:整列三根柱一起提亮(用户 2026-10-17:移上去应该两个都变色,不是只蓝的一根) */\n.dtl-gchart__col:hover .dtl-gchart__bar{filter:brightness(1.18);}\n.dtl-gchart__val{position:absolute;left:50%;transform:translateX(-50%);font-size:10px;line-height:12px;\n color:var(--dtl-fg-2);font-variant-numeric:tabular-nums;white-space:nowrap;}\n.dtl-gchart__label{position:absolute;left:0;right:0;bottom:-16px;text-align:center;\n font-size:10px;line-height:14px;color:var(--dtl-fg-3);white-space:nowrap;}\n/* hover 浮卡(参考图同款:日期 + 两个数;原生 title 太慢、长相不一) */\n.dtl-gchart__tip{position:absolute;top:0;z-index:2;min-width:104px;box-sizing:border-box;\n padding:6px 8px;border:1px solid var(--dtl-border);border-radius:var(--dtl-radius-sm);\n background:var(--dtl-surface-1);box-shadow:var(--dtl-shadow-2);\n font-size:var(--dtl-font-xs);line-height:var(--dtl-line-sm);color:var(--dtl-fg-2);pointer-events:none;}\n.dtl-gchart__tip b{display:block;color:var(--dtl-fg);font-weight:600;}\n.dtl-gchart__tip i{display:inline-block;width:8px;height:8px;border-radius:2px;margin-right:4px;}\n.dtl-gchart-legend{display:flex;align-items:center;gap:var(--dtl-space-3);font-size:var(--dtl-font-xs);color:var(--dtl-fg-2);}\n.dtl-gchart-legend i{display:inline-block;width:8px;height:8px;border-radius:2px;margin-right:4px;}\n\n/* 过程正文:宽度受限、长行折行 */\n.dtl-worklog{max-width:72ch;font-size:var(--dtl-font-md);line-height:1.6}\n`\n\nconst STYLE_ID = 'dtl-ui-base'\n\n/** 幂等注入 token 层 + 控件皮肤 + 基础层(面板与浮标谁先挂载都只注入一次)。\n * 已挂载时**就地比对刷新**:插件升级后宿主若复用旧面板 DOM,旧样式表会一直挂着,\n * 导致新版本样式不生效(用户 2026-10-18:0.0.31 装上了圆圈还在)—— 内容变了就原位替换,不必重启。 */\nexport function ensureUiBase(): void {\n if (typeof document === 'undefined') return\n const css = `${UI_TOKENS_CSS}\\n${UI_CONTROLS_CSS}\\n${UI_BASE_CSS}`\n const existing = document.getElementById(STYLE_ID)\n if (existing !== null) {\n if (existing.textContent !== css) existing.textContent = css\n return\n }\n const style = document.createElement('style')\n style.id = STYLE_ID\n style.textContent = css\n document.head.appendChild(style)\n}\n\n/* ── 分段控件 ─────────────────────────────────────────────────────── */\n\n/** 一段(一个可选值)。 */\nexport interface SegmentedItem<Value extends string = string> {\n readonly value: Value\n readonly label: string\n /** 段内角标数字;`undefined` 或 ≤0 不显示。 */\n readonly badge?: number\n}\n\n/** 分段控件(单选):同一份结构 + size 轴;外观差异只由 variant 的两个颜色变量表达。 */\nexport function Segmented<Value extends string>(props: {\n items: readonly SegmentedItem<Value>[]\n value: Value\n onChange: (next: Value) => void\n size?: 'sm' | 'md' | 'lg'\n variant?: 'default' | 'inset'\n label: string\n id?: string\n style?: CSSProperties\n}): ReactNode {\n ensureUiBase()\n const size = props.size ?? 'md'\n const variant = props.variant ?? 'default'\n return h('div', {\n role: 'group',\n 'aria-label': props.label,\n id: props.id,\n className: `dtl-seg dtl-seg--${variant} dtl-seg--${size}`,\n style: props.style,\n }, props.items.map(item => h('button', {\n key: item.value,\n type: 'button',\n className: 'dtl-seg__item',\n 'aria-pressed': item.value === props.value,\n onClick: () => { props.onChange(item.value) },\n }, item.label, item.badge !== undefined && item.badge > 0\n ? h('span', { className: 'dtl-seg__num' }, String(item.badge))\n : null)))\n}\n\n/* ── 按钮 / 图标钮 ────────────────────────────────────────────────── */\n\nexport function Button(props: {\n children?: ReactNode\n onClick: () => void\n variant?: 'primary' | 'outline' | 'ghost'\n size?: 'sm' | 'md' | 'lg'\n icon?: ReactNode\n disabled?: boolean\n title?: string\n}): ReactNode {\n ensureUiBase()\n return h('button', {\n type: 'button',\n className: `dtl-btn dtl-btn--${props.variant ?? 'outline'} dtl-btn--${props.size ?? 'md'}`,\n disabled: props.disabled === true,\n title: props.title,\n onClick: props.onClick,\n }, props.icon, props.children)\n}\n\nexport function IconButton(props: {\n icon: ReactNode\n label: string\n onClick: () => void\n variant?: 'plain' | 'outline'\n size?: 'sm' | 'md' | 'lg'\n disabled?: boolean\n title?: string\n}): ReactNode {\n ensureUiBase()\n return h('button', {\n type: 'button',\n className: `dtl-iconbtn dtl-iconbtn--${props.variant ?? 'plain'} dtl-iconbtn--${props.size ?? 'md'}`,\n 'aria-label': props.label,\n disabled: props.disabled === true,\n title: props.title ?? props.label,\n onClick: props.onClick,\n }, props.icon)\n}\n\n/* ── 输入 / 下拉 / 搜索 ───────────────────────────────────────────── */\n\n/** 原生下拉(工作区 / 状态共用一份;宿主没有廉价下拉件 ⇒ 原生 + token 皮肤)。\n * `width` 定宽(px);只给 `minWidth` = 内容自适应但有下限(工作区名长短不一时用)。 */\nexport function Select<Value extends string>(props: {\n value: Value\n options: readonly { value: Value; label: string }[]\n onChange: (next: Value) => void\n ariaLabel: string\n /** 定宽(px);不给就按内容。 */\n width?: number\n /** 最小宽度(px);内容自适应时的下限。 */\n minWidth?: number\n size?: 'sm' | 'md' | 'lg'\n}): ReactNode {\n ensureUiBase()\n const size = props.size ?? 'md'\n const style: CSSProperties | undefined = props.width === undefined\n ? (props.minWidth === undefined ? undefined : { minWidth: `${props.minWidth}px` })\n : { width: `${props.width}px` }\n return h('select', {\n className: `dtl-select dtl-select--${size}`,\n 'aria-label': props.ariaLabel,\n style,\n value: props.value,\n onChange: (event: ChangeEvent<HTMLSelectElement>) => { props.onChange(event.target.value as Value) },\n }, props.options.map(option => h('option', { key: option.value, value: option.value }, option.label)))\n}\n\n/** 搜索框(关键字过滤;占满剩余宽度由调用方的 flex 决定)。 */\nexport function SearchInput(props: {\n value: string\n placeholder: string\n onChange: (next: string) => void\n ariaLabel: string\n size?: 'sm' | 'md' | 'lg'\n}): ReactNode {\n ensureUiBase()\n const size = props.size ?? 'md'\n return h('input', {\n className: `dtl-input dtl-input--${size}`,\n type: 'search',\n 'aria-label': props.ariaLabel,\n placeholder: props.placeholder,\n value: props.value,\n spellCheck: false,\n onChange: (event: ChangeEvent<HTMLInputElement>) => { props.onChange(event.target.value) },\n })\n}\n\n/* ── 卡 / chip / 空态 ─────────────────────────────────────────────── */\n\n/** 统计卡:主数字 + 灰色口径小字;点一下联动下方列表筛选。\n * `value` 为 0 时显示「—」(用户 2026-10-17 定稿:0 会被读成「没数据 / 出错了」);\n * `hint` 可以是富文本(如把「停滞」两字标红);\n * `interactive: false` = 纯展示卡(统计台:首页没有已结案可筛,不装可点)。 */\nexport function StatCard(props: {\n label: string\n value: string | number\n hint?: ReactNode\n active?: boolean\n interactive?: boolean\n title?: string\n onClick?: () => void\n}): ReactNode {\n ensureUiBase()\n const interactive = props.interactive !== false && props.onClick !== undefined\n const shown = props.value === 0 || props.value === '0' ? '—' : String(props.value)\n return h(interactive ? 'button' : 'div', {\n type: interactive ? 'button' : undefined,\n className: `dtl-card dtl-stat${interactive ? ' dtl-card--clickable' : ''}`,\n 'data-active': props.active === true,\n title: props.title,\n onClick: interactive ? props.onClick : undefined,\n },\n h('span', { style: { fontSize: 'var(--dtl-font-sm)', color: 'var(--dtl-fg-2)' } }, props.label),\n h('span', { className: 'dtl-stat__value' }, shown),\n props.hint === undefined || props.hint === null || props.hint === ''\n ? null\n : h('span', { className: 'dtl-stat__hint' }, props.hint),\n )\n}\n\n/** chip(分类 / 标签 / 工作区名)。 */\nexport function Chip(props: { text: string; color?: string }): ReactNode {\n ensureUiBase()\n return h('span', {\n className: 'dtl-chip',\n style: props.color === undefined ? undefined : { background: props.color, color: 'var(--dtl-on-signal)' },\n title: props.text,\n }, props.text)\n}\n\n/** 标签上限宽度(字符数):1 个中文按 2 计 ⇒ 10 个中文 = 20 个字符(与 CSS 的 20ch 对齐)。 */\nconst TAG_MAX_WIDTH = 20\n\n/** 显示宽度:全角(中 / 日 / 韩 / 全角符号)一个顶两个英文字符。 */\nfunction displayWidth(text: string): number {\n let width = 0\n for (const char of text) width += (char.codePointAt(0) ?? 0) > 0x2e80 ? 2 : 1\n return width\n}\n\n/**\n * 标签(**唯一**一枚件,用户 2026-10-18):状态 / 执行人 / #标签 都走它,只换 tone。\n * 超过 20 个字符宽 ⇒ 省略号,**且只在被截时**挂悬停提示补出全文(用户 2026-10-19:\n * 不跑马灯;没被截 ⇒ 悬停无反应)。\n */\nexport function Tag(props: {\n text: string\n tone?: 'neutral' | 'open' | 'backlog' | 'doing' | 'overdue' | 'stale' | 'person'\n /** 悬停提示(**被截时**才显示;默认 = 文本本身)。 */\n title?: string\n /** 悬停提示(**始终**显示,状态标记的解释走它;优先于 title)。走官方 Tooltip。 */\n tip?: string\n}): ReactNode {\n ensureUiBase()\n const { text, tone = 'neutral', title, tip } = props\n const long = displayWidth(text) > TAG_MAX_WIDTH\n const label = tip ?? (long ? (title ?? text) : undefined)\n const tag = h('span', { className: `dtl-tag dtl-tag--${tone}` }, text)\n // 官方 Tooltip(悬停即出、支持 \\n 多行、portal 逃逸祖先 overflow 裁切)\n if (label === undefined) return tag\n return h(Tooltip, { label, side: 'bottom', portal: true, maxWidth: 240, children: tag })\n}\n\n/** 空态:`guide` = 真空引导卡(虚线),`filtered` = 筛选空小字。 */\nexport function EmptyState(props: { tone: 'guide' | 'filtered'; title: string; hint?: string }): ReactNode {\n ensureUiBase()\n if (props.tone === 'filtered') return h('div', { className: 'dtl-empty-filtered' }, props.title)\n return h('div', { className: 'dtl-empty-guide' },\n h('div', { style: { fontSize: 'var(--dtl-font-lg)', fontWeight: 600, marginBottom: 'var(--dtl-space-1)' } }, props.title),\n props.hint === undefined ? null : h('div', { style: { fontSize: 'var(--dtl-font-md)', color: 'var(--dtl-fg-2)' } }, props.hint),\n )\n}\n\n/** 台账图标(黑白线条,描边走 currentColor ⇒ 随主题 / 选中态反色)。\n * 侧栏条目与右上浮标共用这一份(图标只画一次,别处不许再画第二个)。 */\nexport function LedgerIcon(props: { size?: number }): ReactNode {\n const size = props.size ?? 18\n const stroke = { fill: 'none', stroke: 'currentColor', strokeWidth: 7, strokeLinecap: 'round', strokeLinejoin: 'round' } as const\n return h('svg', { width: size, height: size, viewBox: '0 0 128 128', 'aria-hidden': true },\n h('path', { d: 'M96 34 A10 10 0 0 1 106 44 V102 A10 10 0 0 1 96 112 H32 A10 10 0 0 1 22 102 V44 A10 10 0 0 1 32 34 Z', ...stroke }),\n h('path', { d: 'M50 22 V46 M78 22 V46', ...stroke }),\n h('path', { d: 'M44 66 H84 M44 86 H70', ...stroke }),\n )\n}\n\n/* ── 趋势分组柱状图 ───────────────────────────────────────────────────── */\n\n/** 一周三根柱(完成 / 没完成 / 新增)。 */\nexport interface GroupedBarWeek {\n /** 轴标签(MM-DD)。 */\n readonly label: string\n readonly done: number\n readonly remaining: number\n readonly created: number\n /** 悬停文案。 */\n readonly title: string\n}\n\n/** 千位压缩(1.2k / 12k):y 轴刻度与柱顶数值用,避免 4 位数把刻度挤宽。 */\nfunction compactCount(value: number): string {\n if (value >= 10_000) return `${Math.round(value / 1000)}k`\n if (value >= 1000) return `${(value / 1000).toFixed(1)}k`\n return String(value)\n}\n\n/** 分组柱状图(div 柱,无新依赖):完成(蓝)/ 没完成(琥珀)/ 新增(灰)各一细柱。\n * - 高度全用百分比 ⇒ 绘图区随卡片拉伸,图表卡与左边 8 块**完全等高**;\n * - y 轴三档刻度 + 淡网格线;柱顶只标「完成」(3 系列 × 8 周数值会糊成一片);\n * - hover 整列提亮 + 浮卡(日期 + 三个数)——原生 title 太慢;\n * - **没有**均值线与长注释(用户 2026-10-17:均值不要、注释太长不要)。 */\nexport function GroupedBarChart(props: {\n weeks: readonly GroupedBarWeek[]\n ariaLabel: string\n doneLabel: string\n remainingLabel: string\n createdLabel: string\n}): ReactNode {\n ensureUiBase()\n const [hover, setHover] = useState<number | null>(null)\n const top = Math.max(1, props.weeks.reduce((peak, week) => Math.max(peak, week.done, week.remaining, week.created), 0))\n const ticks = [0, top / 2, top]\n const pct = (value: number): string => `${(value / top) * 100}%`\n const columnStyle = (index: number): Record<string, string | number> => ({\n left: `${(index / props.weeks.length) * 100}%`,\n right: `${((props.weeks.length - index - 1) / props.weeks.length) * 100}%`,\n })\n // 浮卡定位:贴边两列向内收,其余居中(否则溢出图表卡)\n const tipStyle = (index: number): Record<string, string | number> => {\n if (index <= 1) return { left: 0 }\n if (index >= props.weeks.length - 2) return { right: 0 }\n return { left: `${((index + 0.5) / props.weeks.length) * 100}%`, transform: 'translateX(-50%)' }\n }\n return h('div', { className: 'dtl-gchart-wrap' },\n h('div', {\n className: 'dtl-gchart',\n role: 'img',\n 'aria-label': props.ariaLabel,\n onMouseLeave: () => { setHover(null) },\n },\n ticks.map((tick) => h('div', { key: `grid-${String(tick)}`, className: 'dtl-gchart__grid', style: { bottom: pct(tick) } })),\n ticks.map((tick) => h('span', { key: `tick-${String(tick)}`, className: 'dtl-gchart__ytick', style: { bottom: pct(tick) } }, compactCount(Math.round(tick)))),\n props.weeks.map((week, index) => h('div', {\n key: week.label,\n className: 'dtl-gchart__col',\n style: columnStyle(index),\n onMouseEnter: () => { setHover(index) },\n },\n h('div', { className: 'dtl-gchart__pair' },\n h('span', { className: 'dtl-gchart__bar dtl-gchart__bar--done', style: { height: pct(week.done), minHeight: week.done > 0 ? '2px' : undefined } }),\n h('span', { className: 'dtl-gchart__bar dtl-gchart__bar--remaining', style: { height: pct(week.remaining), minHeight: week.remaining > 0 ? '2px' : undefined } }),\n h('span', { className: 'dtl-gchart__bar dtl-gchart__bar--created', style: { height: pct(week.created), minHeight: week.created > 0 ? '2px' : undefined } }),\n ),\n week.done === 0\n ? null\n : h('span', { className: 'dtl-gchart__val', style: { bottom: `calc(${pct(week.done)} + 3px)` } }, String(week.done)),\n h('span', { className: 'dtl-gchart__label' }, week.label),\n )),\n hover === null\n ? null\n : h('div', { className: 'dtl-gchart__tip', style: tipStyle(hover) },\n h('b', null, props.weeks[hover].title),\n h('div', null, h('i', { style: { background: 'var(--dtl-business)' } }), `${props.doneLabel} ${props.weeks[hover].done}`),\n h('div', null, h('i', { style: { background: 'var(--dtl-chart-remaining)' } }), `${props.remainingLabel} ${props.weeks[hover].remaining}`),\n h('div', null, h('i', { style: { background: 'var(--dtl-chart-created)' } }), `${props.createdLabel} ${props.weeks[hover].created}`),\n ),\n ),\n )\n}\n\n/** 图例(完成 / 没完成 / 新增 三个色块)。 */\nexport function ChartLegend(props: { doneLabel: string; remainingLabel: string; createdLabel: string }): ReactNode {\n ensureUiBase()\n return h('div', { className: 'dtl-gchart-legend' },\n h('span', null, h('i', { style: { background: 'var(--dtl-business)' } }), props.doneLabel),\n h('span', null, h('i', { style: { background: 'var(--dtl-chart-remaining)' } }), props.remainingLabel),\n h('span', null, h('i', { style: { background: 'var(--dtl-chart-created)' } }), props.createdLabel),\n )\n}\n","/**\n * 任务行(宿主**无表格件** ⇒ 自绘),**两种形态**(用户 2026-10-17 定稿:两区视觉必须不同,\n * 且**互不重用**):\n *\n * A. `mine`(我的待办)= 整行一张卡,样式照抄 dispatch-table 任务配置卡:\n * 左缘 6×36 状态条 + 标题行 + 第二行工作区/标签;右侧灰字时长;悬停出\n * 「继续 / 结束 / 释放」;最右常驻「查看」放大镜钮。\n *\n * B. `others`(全部待办)= 连体长表里的一行(用户 2026-10-18 定稿):**无卡片、无外框**,\n * 行 = 一条淡分隔线格:上下居中的状态圆点 + 任务(标题行 + 标签行;**标签行最前是状态标签**)\n * + **执行人列**(原状态位,有执行人才摆 `@名字` 标签)+ 最右「@创建人 创建于 年-月-日 时:分:秒」\n * (悬停掉时分秒并让位给操作钮)+ 查看。\n * 状态一律**标签型、五色**(用户 2026-10-18:原来是一排素字 / pill,现在与人员**位置互换**):\n * 灰=待认领 / 蓝=进行中 / 黄=积压 / 橙=逾期 / 红=停滞;三个异常标签悬停给**配置出来的**\n * 阈值解释(staleDays / overdueDays / backlogDays 由 host 下发)。圆点颜色同色系。\n *\n * 按钮(含查看)**不各判各的**:权限一律问 `resolveActions()`(rows.ts),本文件只按返回值画。\n *\n * 按钮点击行为(含查看)归「任务按钮与会话交接」任务,本文件只做样子与提示。\n */\nimport { createElement as h, type ReactNode } from 'react'\nimport {\n IconCheckOutlineRegular, IconChevronRightOutlineRegular, IconDownloadOutlineRegular, IconSearchOutlineRegular,\n Tooltip,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport { formatDateTime } from './format'\nimport type { Translate } from './locales'\nimport { CLOSED_ACTIONS, resolveActions, type Row } from './rows'\nimport type { BoardPayload } from './types'\nimport { Tag } from './ui'\n\ntype Tone = 'doing' | 'overdue' | 'stale' | 'backlog' | 'open' | 'closed'\n\n/** 行标题:在办 = 任务名(缺则任务号);已结案 = 任务包名(缺则「完成了什么」首句)。 */\nfunction titleOf(row: Row): string {\n if (row.kind === 'task') return row.task.title ?? row.task.id\n const title = row.record.workpackage ?? row.record.summary\n return title !== undefined && title !== '' ? title : row.record.src ?? '—'\n}\n\n/** 状态语义(异常优先:停滞 > 逾期 > 积压;三者都是 host 按时间算出来的,不是状态)。\n * 只决定圆点 / 左缘色条这类**单色位**——标签可以多枚,见 MARKS。 */\nfunction toneOf(row: Row): Tone {\n if (row.kind !== 'task') return 'closed'\n if (row.task.stale === true) return 'stale'\n if (row.task.overdue === true) return 'overdue'\n if (row.task.backlog === true) return 'backlog'\n if (row.task.status === 'open') return 'open'\n return 'doing'\n}\n\n/** 三个异常标记**可叠加**(用户 2026-10-19):一条任务能同时逾期 + 停滞,两枚标签都要摆。\n * 积压与逾期 / 停滞不会共存(前者是待认领,后两者是进行中),故最多 2 枚。 */\nconst MARKS = ['backlog', 'overdue', 'stale'] as const\n\n/** 状态文案(已结案没有在办状态 ⇒ 不给标签)。 */\nfunction statusLabel(tone: Tone, t: Translate): string | null {\n if (tone === 'stale') return t('statusStale')\n if (tone === 'overdue') return t('statusOverdue')\n if (tone === 'backlog') return t('statusBacklog')\n if (tone === 'open') return t('statusOpen')\n if (tone === 'doing') return t('statusActive')\n return null\n}\n\nexport function TaskRowView(props: {\n t: Translate\n row: Row\n /** 形态:我的待办(卡片)/ 全部待办(表格行)——两区视觉必须不同、互不重用。 */\n variant: 'mine' | 'others'\n /** 快照:问动作权限(是不是我的 / 能不能认领),并取 host 下发的三个阈值做悬停解释。 */\n board: BoardPayload | null\n}): ReactNode {\n const { t, row, variant, board } = props\n const tone = toneOf(row)\n const isTask = row.kind === 'task'\n const tags = (row.kind === 'task' ? row.task.tags : row.record.tags) ?? []\n\n // 创建日期(第一行灰徽只给日期)。\n const created = row.kind === 'task' ? row.task.created ?? null : row.record.created ?? null\n const createdDay = created !== null && created !== undefined && Date.parse(created) >= 0\n ? formatDateTime(created).slice(0, 10)\n : null\n\n // 标签悬停解释(用户 2026-10-19):一律「谁 / 何时开始 / 最后更新 / 多少天」,天数由 host 下发,\n // 前端不自行推算;缺时间就报「时间未知」,不编。\n const stamp = (value: string | null | undefined): string =>\n value === null || value === undefined ? t('timeUnknown') : formatDateTime(value, { seconds: true, fallback: t('timeUnknown') })\n const dayText = (n: number | null | undefined): string =>\n n === null || n === undefined ? t('daysNone') : t('daysValue', { n })\n const detailTip = (which: Exclude<Tone, 'closed'>): string | undefined => {\n if (row.kind !== 'task') return undefined\n const { task } = row\n const owner = task.owner ?? ''\n const who = owner === '' ? t('ownerUnclaimed') : `@${owner}`\n const started = stamp(task.started ?? task.created)\n const updated = stamp(task.updated ?? task.started ?? task.created)\n if (which === 'open') return t('tipOpen', { created: stamp(task.created) })\n if (which === 'backlog') return t('tipBacklog', { created: stamp(task.created), d: dayText(task.waitingDays) })\n if (which === 'doing') return t('tipDoing', { who, started, updated })\n if (which === 'overdue') return t('tipOverdue', { who, started, updated, d: dayText(task.ageDays) })\n return t('tipStale', { who, started, updated, d: dayText(task.idleDays) })\n }\n // 动作权限:**唯一**判定(用户 2026-10-18)——两区同一套,不许各判各的;本文件只按返回值画。\n const actions = isTask ? resolveActions(row.task, board) : CLOSED_ACTIONS\n\n // 动作钮:四个 + 查看,各一份(两区共用,不许各画一套)。\n // 悬停说明统一走官方 Tooltip(portal 逃逸裁切、hover 即出),不在 CSS 里手搓 ::after 浮层。\n // 「查看」:两区都有、统一最右、放大镜图标(用户 2026-10-17)。\n const viewButton = h(Tooltip, { label: t('viewDetail'), side: 'bottom', portal: true, maxWidth: 240,\n children: h('button', { type: 'button', className: 'dtl-viewbtn' }, h(IconSearchOutlineRegular, { size: 14 })) })\n const tipBtn = (\n icon: ReactNode, actionKey: 'actionClaim' | 'actionResume' | 'actionFinish' | 'actionRelease',\n tipKey: 'tipClaim' | 'tipResume' | 'tipFinish' | 'tipRelease',\n ): ReactNode =>\n h(Tooltip, { label: t(tipKey), side: 'bottom', portal: true, maxWidth: 240,\n children: h('button', { type: 'button', className: 'dtl-minibtn' }, ...(icon ? [icon, t(actionKey)] : [t(actionKey)])) })\n const claimButton = tipBtn(h(IconDownloadOutlineRegular, { size: 14 }), 'actionClaim', 'tipClaim')\n const resumeButton = tipBtn(h(IconChevronRightOutlineRegular, { size: 14 }), 'actionResume', 'tipResume')\n const finishButton = tipBtn(h(IconCheckOutlineRegular, { size: 14 }), 'actionFinish', 'tipFinish')\n // 释放:官方 rc.2 无断链图标(已全量核实)⇒ 纯文字,不用叉\n const releaseButton = tipBtn(null, 'actionRelease', 'tipRelease')\n\n // 状态标签(五色实心块,用户 2026-10-18 定稿):已结案没有在办状态 ⇒ 不摆;\n // 三枚异常标签带 host 配置的阈值解释(没配就不解释,不编)。\n const tagOf = (which: Exclude<Tone, 'closed'>, tip?: string): ReactNode => {\n const text = statusLabel(which, t) ?? ''\n return h(Tag, { key: which, text, tone: which, tip: tip ?? detailTip(which) })\n }\n const markTones = isTask ? MARKS.filter((which) => row.task[which] === true) : []\n // 异常标记全部摆出来;一个都没有 ⇒ 退回归属状态(待认领 / 进行中)那一枚\n const statusTags = tone === 'closed'\n ? []\n : (markTones.length > 0 ? markTones : [tone as Exclude<Tone, 'closed'>]).map((which) => tagOf(which))\n // 「我的待办」卡只摆异常标记(进行中只靠左缘色条,不再重复一枚)\n const alarmTags = markTones.map((which) => tagOf(which))\n\n // 第一行(「我的待办」卡):标题 + 创建徽 + 异常标签\n const titleLine = h('div', { className: 'dtl-taskrow__l1' },\n h('span', { className: 'dtl-taskrow__title dtl-ellipsis', title: titleOf(row) }, titleOf(row)),\n createdDay === null ? null : h('span', { className: 'dtl-taskrow__stamp' }, `[${t('fieldCreatedStamp', { day: createdDay })}]`),\n ...alarmTags,\n )\n\n // 第一行(表格行):独立类族,不沾卡片基类(用户 2026-10-18)。\n // 只有标题 —— 创建信息挪到最右、状态进标签行,标题行不再重复一遍。\n const orowTitleLine = h('div', { className: 'dtl-orow__title' },\n h('span', { className: 'dtl-orow__name dtl-ellipsis', title: titleOf(row) }, titleOf(row)),\n )\n\n // ── 形态 B:全部待办 = 真表格行(无卡片、无外框,一行一格)──\n if (variant === 'others') {\n const creator = isTask ? row.task.creator : row.record.owner\n const owner = isTask ? row.task.owner : null\n const hasOwner = !(owner === null || owner === undefined || owner === '')\n const who = creator === null || creator === undefined || creator === '' ? '—' : `@${creator}`\n const createdOk = created !== null && created !== undefined && Date.parse(created) >= 0\n // 执行人的悬停提示(**只在名字被截断时才显示**,见 Tag):全名 + **什么时候开始执行**\n // (任务上的 started;没有就报「时间未知」,不编)\n const startedOk = isTask && row.task.started !== null && row.task.started !== undefined\n && Date.parse(row.task.started) >= 0\n const ownerTip = t('startedAt', {\n who: String(owner),\n when: startedOk ? formatDateTime(String(row.task.started)) : t('timeUnknown'),\n })\n // 标签行:**状态在最前**(原执行人位),#标签跟在后面(用户 2026-10-18 位置互换)\n const tagLine = h('div', { className: 'dtl-orow__tags' },\n ...statusTags,\n tags.map(tag => h(Tag, { key: tag, text: `#${tag}` })),\n )\n return h('div', { className: 'dtl-orow' },\n h('div', { className: 'dtl-orow__row' },\n // 行标识:上下居中圆点(段头是文件夹/竖线,两处刻意不同)\n h('span', { className: `dtl-dot dtl-dot--${tone}` }),\n h('div', { className: 'dtl-tcell dtl-tcell--title' },\n orowTitleLine,\n tagLine,\n ),\n // 执行人列(原状态位):有执行人 ⇒ 一枚 @名字 标签,悬停给「谁 什么时候开始执行」\n // (页面不许跳:右侧槽位宽度是常驻的,hover 只换可见性 ⇒ 这个提示不会跟着飘)\n h('span', { className: 'dtl-tcell dtl-tcell--status' },\n // 名字上限 20 字(Tag 的 20ch);超了 ⇒ 省略号 + 悬停补全名(ownerTip)\n hasOwner ? h(Tag, { text: `@${String(owner)}`, tone: 'person', title: ownerTip }) : null),\n // 最右**一个槽**:创建信息 ⇄ 操作钮二选一(用户 2026-10-18:悬停就把创建信息整条干掉,\n // 按钮顶上那一格;两个都常驻布局 ⇒ 行宽不变、页面不跳)\n h('span', { className: 'dtl-orow__slot' },\n h('span', { className: 'dtl-orow__created' },\n t('createdAt', { who, when: createdOk ? formatDateTime(String(created)) : t('timeUnknown') })),\n // 动作排:**同一排、同一间距**(用户 2026-10-18:查看钮不许单独一档间距);\n // 谁出来由 resolveActions 说了算 —— 我的 ⇒ 继续/结束/释放,待认领 ⇒ 认领\n h('span', { className: 'dtl-orow__actions' },\n actions.claim ? claimButton : null,\n actions.resume ? resumeButton : null,\n actions.finish ? finishButton : null,\n actions.release ? releaseButton : null,\n actions.view ? viewButton : null,\n ),\n ),\n ),\n )\n }\n\n // ── 形态 A:我的待办 = 卡片(dispatch 任务配置卡同款)──\n const parts: string[] = []\n if (isTask) {\n const startRaw = row.task.started ?? row.task.created\n if (startRaw !== undefined && startRaw !== null && startRaw !== '' && Date.parse(startRaw) >= 0) {\n parts.push(`${formatDateTime(startRaw).slice(0, 10)} ${t('fieldStarted')}`)\n }\n if (row.task.ageDays !== null && row.task.ageDays !== undefined) {\n parts.push(row.task.ageDays === 0 ? t('ageToday') : t('ageDaysText', { n: row.task.ageDays }))\n }\n } else {\n parts.push(`${t('fieldFinished')} ${formatDateTime(row.record.time ?? row.record.created ?? '', { fallback: t('timeUnknown') })}`)\n }\n return h('div', { className: 'dtl-taskrow' },\n h('div', { className: 'dtl-taskrow__row' },\n h('span', { className: `dtl-rail dtl-rail--${tone}` }),\n h('div', { style: { flex: '1 1 auto', minWidth: 0 } },\n titleLine,\n h('div', { className: 'dtl-taskrow__l2' },\n h('span', { className: 'dtl-wstag dtl-ellipsis', title: row.kind === 'task' ? row.task.workspace.title : '' },\n row.kind === 'task' ? row.task.workspace.title : ''),\n tags.map(tag => h(Tag, { key: tag, text: `#${tag}` })),\n ),\n ),\n h('span', { className: 'dtl-taskrow__when' }, parts.length === 0 ? t('timeUnknown') : parts.join('丨')),\n // 动作排:继续 / 结束 / 释放 + 查看 —— **同一排、同一间距**\n // (用户 2026-10-18:查看钮原来是独立的一格,离前面的按钮远一档)\n // 谁出来由 resolveActions 说了算(与下面「全部待办」**同一个方法**,不再各判各的)\n h('span', { className: 'dtl-taskrow__actions' },\n actions.resume ? resumeButton : null,\n actions.finish ? finishButton : null,\n actions.release ? releaseButton : null,\n actions.view ? viewButton : null,\n ),\n ),\n )\n}\n","/**\n * 看板首页(`main` 槽整页内容):8 统计卡 + 近 8 周趋势图 + 两区任务列表。\n * 定稿见 docs/design/features/board-home.md §二(2026-10-17 专家组改版);\n * 按钮矩阵归「任务按钮与会话交接」任务(写入路径 A/B 未拍板),本页**不画**任务动作按钮。\n *\n * 刷新纪律(2026-10-17 拍板):挂载首读 + 手动刷新按钮,**不轮询**;空态三分\n * (未就绪不渲染 / 真空引导卡 / 筛选空小字)。\n */\nimport { createElement as h, useEffect, useMemo, useState, useSyncExternalStore, type ChangeEvent, type ReactNode } from 'react'\nimport {\n IconChecklistOutlineRegular, IconFlatListOutlineRegular, IconFolderCloseRegular, IconFolderOpenRegular,\n IconRefreshOutlineRegular,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { LocaleKey, Translate } from './locales'\nimport { boardSnapshot, boardSubscribe, refresh, setQuery, type StatusFilter } from './store'\nimport { isMine, selectView, type WorkspaceGroup } from './rows'\nimport type { BoardPayload, PeriodCounts } from './types'\nimport { TaskRowView } from './task-row'\nimport {\n Button, ChartLegend, EmptyState, ensureUiBase, GroupedBarChart, SearchInput, Segmented, Select, StatCard,\n} from './ui'\n\ntype Tab = 'tasks' | 'stats'\n\n/** 整页容器:占满中栏、自己滚动。 */\nconst pageStyle: Record<string, string | number> = {\n height: '100%', width: '100%', boxSizing: 'border-box', overflow: 'auto',\n padding: '24px 22px', color: 'var(--dtl-fg)',\n}\nconst headerStyle: Record<string, string | number> = {\n display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 'var(--dtl-space-3)', flexWrap: 'wrap',\n marginBottom: 'var(--dtl-space-4)',\n}\nconst titleStyle: Record<string, string | number> = { fontSize: 'var(--dtl-font-xl)', fontWeight: 600 }\n/** 统计区:左 8 块(2 行 × 4 列)+ 右趋势图,参考图同款分栏。\n * 列数写死 4 → 2(媒体查询在 ui.ts 的 .dtl-stats / .dtl-stats__cards 里),\n * 绝不给 3 列——3 列会排出 3+1 的非对称(用户 2026-10-17:必须上下对称)。 */\nconst statsRowStyle: Record<string, string | number> = { marginBottom: 'var(--dtl-space-4)' }\nconst chartCardStyle: Record<string, string | number> = {\n display: 'flex', flexDirection: 'column', gap: 'var(--dtl-space-2)', padding: 'var(--dtl-space-2) var(--dtl-space-3)',\n}\nconst hintStyle: Record<string, string | number> = { fontSize: 'var(--dtl-font-sm)', color: 'var(--dtl-fg-2)' }\n/** 分区标题行:垂直居中(用户 2026-10-17:图标 / 标题 / 计数不许像下楼梯一样逐个低);\n * 上下留白**翻倍**(用户 2026-10-18:两个大标题太挤)。 */\nconst regionHeadRowStyle: Record<string, string | number> = {\n display: 'flex', alignItems: 'center', gap: 'var(--dtl-space-2)',\n margin: 'calc(var(--dtl-space-4) * 2) 0 calc(var(--dtl-space-2) * 2)',\n}\nconst regionHeadTitleStyle: Record<string, string | number> = { fontSize: 'var(--dtl-font-lg)', fontWeight: 600 }\n/** 工作区下拉最小宽度(用户 2026-10-17:内容自适应,下限 = 旧定宽 175 的 3/4 ≈ 131)。 */\nconst WORKSPACE_SELECT_MIN = 131\n/** 搜索框宽度(用户 2026-10-17:两区同款,96×2 = 192)。 */\nconst SEARCH_WIDTH = 192\n\n/** 分组默认折叠阈值(组内太多条时先收起,用户点开)。 */\nconst GROUP_COLLAPSE_OVER = 30\n\nexport function BoardHome(props: { t: Translate; onBack: () => void }): ReactNode {\n const { t, onBack } = props\n ensureUiBase()\n const state = useSyncExternalStore(boardSubscribe, boardSnapshot)\n const [tab, setTab] = useState<Tab>('tasks')\n\n // 打开面板首读(后端纯按需,无推送无轮询)。\n useEffect(() => { void refresh() }, [])\n\n const board = state.board\n const view = useMemo(() => selectView(state), [state])\n const stats = board?.stats\n // 趋势走慢路径(`/board/trend`,读 history):晚到不影响列表;未到 / 失败时由统计卡与图表区各自兜底。\n const trend = state.trend\n const periods = trend?.trend\n const trendPending = state.trendStatus === 'idle' || state.trendStatus === 'loading'\n /** 快照 + 趋势两段各自的降级工作区(op 不同,合并展示,别装没看见)。 */\n const degraded = [...(board?.degraded ?? []), ...(trend?.degraded ?? [])]\n // 工作区下拉候选(用户 2026-10-17):不读系统全量工作区,只从「我的」任务里提取工作区并排重\n // ⇒ 没有我任务的工作区不出现;口径与第一区一致(isMine 同源)。\n const workspaceOptions = useMemo(() => [\n { value: '', label: t('workspaceAll') },\n ...[...new Map((board?.tasks ?? [])\n .filter((task) => isMine(task, board))\n .map((task) => [task.workspace.id, task.workspace.title]))]\n .map(([id, title]) => ({ value: id, label: title })),\n ], [board, t])\n /** 统计卡:纯展示(用户 2026-10-17:先全去掉点击,界面定稿后再议滚动 / 筛选联动)。 */\n const card = (label: LocaleKey, value: number, hint: string, title?: string): ReactNode =>\n h(StatCard, {\n label: t(label),\n value,\n hint,\n title,\n interactive: false,\n })\n\n /** 统计台卡:不可点(首页没有已结案可筛),hover 给具体日期区间。\n * 趋势未到 / 读挂 ⇒ 数字留 '—'(StatCard 对 0 的处理)、小字整条不显示,**不拿 0 充数**。 */\n const periodCard = (label: LocaleKey, period: PeriodCounts | undefined): ReactNode =>\n h(StatCard, {\n label: t(label),\n value: period?.done ?? 0,\n hint: period === undefined ? undefined : t('statCreatedHint', { n: period.created }),\n interactive: false,\n title: period === undefined ? undefined : t('periodRange', { from: period.from, to: period.to }),\n })\n\n const [chartScope, setChartScope] = useState<'all' | 'mine'>('all')\n const chartWeeks = useMemo(() => (trend?.closed.weekly[chartScope] ?? []).map((bucket) => {\n // 容错:host 旧版可能不带 end(客户端/宿主版本错配时),退回用 week,绝不崩面板。\n const end = bucket.end ?? bucket.week ?? ''\n return {\n label: end.slice(5) || bucket.week?.slice(5) || '',\n done: bucket.done,\n remaining: bucket.remaining,\n created: bucket.created,\n title: chartScope === 'mine' ? `截至 ${end}(${t('chartOnlyMine')})` : `截至 ${end}`,\n }\n }), [trend, chartScope, t])\n const chartEmpty = chartWeeks.every((week) => week.done === 0 && week.created === 0 && week.remaining === 0)\n\n return h('div', { style: pageStyle },\n h('div', { style: { display: 'flex', justifyContent: 'center' } },\n h('div', { style: { width: '100%', maxWidth: '1280px', minWidth: 0, boxSizing: 'border-box' } },\n h('div', { style: headerStyle },\n h('div', { style: { display: 'flex', alignItems: 'center', gap: 'var(--dtl-space-3)', minWidth: 0 } },\n h(Button, { variant: 'outline', size: 'md', onClick: onBack }, `← ${t('backToConversation')}`),\n h('div', { style: titleStyle }, t('panelTitle')),\n ),\n h('div', { style: { display: 'flex', alignItems: 'center', gap: 'var(--dtl-space-2)' } },\n h(Segmented<Tab>, {\n id: 'dtl-tab',\n value: tab,\n label: t('panelTitle'),\n items: [\n { value: 'tasks', label: t('tabTasks') },\n { value: 'stats', label: t('tabStats') },\n ],\n onChange: setTab,\n }),\n h(Button, {\n variant: 'outline',\n size: 'md',\n icon: h(IconRefreshOutlineRegular, { size: 16 }),\n disabled: state.refreshing,\n title: t('refresh'),\n onClick: () => { void refresh() },\n }, state.refreshing ? t('refreshing') : t('refresh')),\n ),\n ),\n\n tab === 'stats'\n // 统计报表尚未开建(另立任务):占位说明,不画假图表。\n ? h('div', { style: hintStyle }, t('statsPlaceholder'))\n : h('div', null,\n // ── 统计区:左 8 块(操作台 4 + 统计台 4)+ 右趋势图 ──\n stats === undefined\n ? null\n : h('div', { className: 'dtl-stats', style: statsRowStyle },\n h('div', { className: 'dtl-stats__cards' },\n // 按列成对(用户 2026-10-19 选 A):每列上下是同一对象的两个视角,\n // 子集关系一眼可见 —— 积压 ⊂ 待认领、逾期 / 停滞 ⊂ 进行中。\n // DOM 按**列序**写,CSS 的 grid-auto-flow:column 保证 4 列 / 2 列两种排布下都成对。\n // 小字一律给本卡独有的增量信息,**不重复全集**(用户 2026-10-19):\n // 子集卡(积压 / 逾期 / 停滞)报占比或极值,0 条时整条不显示。\n card('statMyActive', stats.myActive, [\n stats.myOverdue > 0 ? t('statMyOverdueHint', { n: stats.myOverdue }) : null,\n stats.myStale > 0 ? t('statMyStaleHint', { n: stats.myStale }) : null,\n ].filter((part) => part !== null).join(' | ')),\n card('statDoing', stats.doing, stats.doing - stats.myActive > 0\n ? t('statDoingHint', { n: stats.doing - stats.myActive })\n : t('statDoingAllMine')),\n card('statOpen', stats.open, stats.oldestOpenDays === null\n ? ''\n : t('statOpenHint', { n: stats.oldestOpenDays })),\n card('statBacklog', stats.backlog, stats.backlog > 0 && stats.open > 0\n ? t('statBacklogHint', { n: Math.round((stats.backlog / stats.open) * 100) })\n : '', t('statBacklogTitle', { n: stats.backlogDays })),\n card('statOverdue', stats.overdue, stats.maxOverdueAge === null\n ? ''\n : t('statOverdueHint', { n: stats.maxOverdueAge }), t('statOverdueTitle', { n: stats.overdueDays })),\n card('statStale', stats.stale, stats.maxStaleIdle === null\n ? ''\n : t('statStaleHint', { n: stats.maxStaleIdle }), t('statStaleTitle', { n: stats.staleDays })),\n periodCard('statWeekDone', periods?.thisWeek),\n periodCard('statLastWeekDone', periods?.lastWeek),\n ),\n h('div', { className: 'dtl-card', style: chartCardStyle },\n // 右上角只留一个「只看我的」小复选框:勾上 = 我的,不勾 = 所有(用户 2026-10-17)\n h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end' } },\n h('label', { className: 'dtl-check' },\n h('input', {\n type: 'checkbox',\n checked: chartScope === 'mine',\n onChange: (event: ChangeEvent<HTMLInputElement>) => { setChartScope(event.target.checked ? 'mine' : 'all') },\n }),\n t('chartOnlyMine'),\n ),\n ),\n // 趋势是慢路径:未到给占位、读挂给兜底文案(带原因),就绪才画图——绝不画空图冒充「没有记录」。\n trendPending\n ? h('div', { style: { ...hintStyle, padding: 'var(--dtl-space-4) 0' } }, t('chartLoading'))\n : state.trendStatus === 'error'\n ? h('div', { style: { ...hintStyle, padding: 'var(--dtl-space-4) 0' } },\n `${t('chartUnavailable')}${state.trendError === null ? '' : `:${state.trendError}`}`)\n : chartEmpty\n ? h('div', { style: { ...hintStyle, padding: 'var(--dtl-space-4) 0' } }, t('chartEmpty'))\n : h(GroupedBarChart, {\n weeks: chartWeeks,\n ariaLabel: t('chartTitle'),\n doneLabel: t('chartLegendDone'),\n remainingLabel: t('chartLegendRemaining'),\n createdLabel: t('chartLegendCreated'),\n }),\n h(ChartLegend, { doneLabel: t('chartLegendDone'), remainingLabel: t('chartLegendRemaining'), createdLabel: t('chartLegendCreated') }),\n ),\n ),\n\n // ── 列表 ──\n state.status === 'loading' && board === null\n ? null // 未就绪:不渲染\n : state.status === 'error' && board === null\n ? h('div', { style: { display: 'flex', alignItems: 'center', gap: 'var(--dtl-space-2)' } },\n h('span', { style: { color: 'var(--dtl-danger)', fontSize: 'var(--dtl-font-md)' } }, `${t('loadFailed')}:${state.error ?? ''}`),\n h(Button, { variant: 'outline', size: 'sm', onClick: () => { void refresh() } }, t('retry')),\n )\n : board !== null && board.workspaces.length === 0\n ? h(EmptyState, { tone: 'guide', title: t('emptyNoWorkspace'), hint: t('emptyNoWorkspaceHint') })\n : h('div', null,\n // 降级横幅:有工作区读挂 ⇒ 红色警告 + 人话原因(单工作区坏不拖垮其余,别装没看见)\n board !== null && (board.degraded?.length ?? 0) > 0\n ? h('div', {\n style: {\n display: 'flex', alignItems: 'flex-start', gap: 'var(--dtl-space-2)',\n padding: 'var(--dtl-space-2) var(--dtl-space-3)', marginBottom: 'var(--dtl-space-3)',\n border: '1px solid var(--dtl-danger)', borderRadius: 'var(--dtl-radius-md)',\n background: 'var(--dtl-danger-soft)', color: 'var(--dtl-fg)',\n fontSize: 'var(--dtl-font-sm)', lineHeight: 'var(--dtl-line-sm)',\n },\n },\n h('span', { style: { color: 'var(--dtl-danger)', flex: 'none', fontWeight: 600 } }, t('degradedTitle')),\n h('span', null, degraded.map(item => t('degradedItem', {\n title: item.workspaceTitle, message: item.message,\n })).join(';')),\n )\n : null,\n // 第一区:我的待办——定死全量、不过滤(用户 2026-10-17),空了给引导卡;\n // 标题行右侧挂搜索框(只作用第三区),计数用小圆徽\n h('div', { style: regionHeadRowStyle },\n h('span', { style: { color: 'var(--dtl-fg-3)', display: 'inline-flex', flex: 'none' } },\n h(IconChecklistOutlineRegular, { size: 16 })),\n h('span', { style: regionHeadTitleStyle }, t('regionMine')),\n view.mine.length > 0\n ? h('span', { className: 'dtl-count' }, t('mineCount', { n: view.mine.length }))\n : null,\n h('span', { style: { flex: '1 1 auto' } }),\n // 工作区下拉:内容自适应、下限 131(用户 2026-10-17)\n h(Select<string>, {\n value: workspaceOptions.some((option) => option.value === state.query.workspace)\n ? state.query.workspace\n : '',\n options: workspaceOptions,\n ariaLabel: t('workspaceLabel'),\n minWidth: WORKSPACE_SELECT_MIN,\n onChange: (next) => { setQuery({ workspace: next }) },\n }),\n // 搜索框:在下拉右边、与「其他待办」同宽 192(用户 2026-10-17)\n h('div', { style: { width: `${SEARCH_WIDTH}px`, flex: 'none' } },\n h(SearchInput, {\n value: state.query.keyword,\n placeholder: t('searchPlaceholder'),\n ariaLabel: t('searchPlaceholder'),\n onChange: (next) => { setQuery({ keyword: next }) },\n }),\n ),\n ),\n view.mine.length === 0\n ? h(EmptyState, { tone: 'guide', title: t('mineEmpty'), hint: t('mineEmptyHint') })\n : view.mine.map(row => h(TaskRowView, {\n key: row.key, t, row, variant: 'mine', board,\n })),\n // 第二区:全部待办 —— **永远渲染**(用户 2026-10-18:选「进行中」没数据也不能整块消失;\n // 且不要过滤掉自己,所以含我的任务,会与第一区重叠——顶部快看 / 底部全量);\n // 标题行:图标 + 名 + 状态滑轨(带全量角标)+ 搜索(最右);表格区无数据给虚线空态\n h('div', null,\n h('div', { style: regionHeadRowStyle },\n h('span', { style: { color: 'var(--dtl-fg-3)', display: 'inline-flex', flex: 'none' } },\n h(IconFlatListOutlineRegular, { size: 16 })),\n h('span', { style: { ...regionHeadTitleStyle, fontSize: 'var(--dtl-font-md)' } }, t('regionAll')),\n h('span', { style: { flex: '1 1 auto' } }),\n // 状态滑轨:角标 = 第二区**全量**计数(不受滑轨自身影响);0 不标数字\n h(Segmented<StatusFilter>, {\n id: 'dtl-others-status',\n value: state.query.status,\n label: t('statusLabel'),\n items: [\n { value: 'all', label: t('statusAll'), badge: orZero(view.allCounts.total) },\n { value: 'active', label: t('statusActive'), badge: orZero(view.allCounts.doing) },\n { value: 'open', label: t('statusOpen'), badge: orZero(view.allCounts.open) },\n { value: 'backlog', label: t('statusBacklog'), badge: orZero(view.allCounts.backlog) },\n { value: 'overdue', label: t('statusOverdue'), badge: orZero(view.allCounts.overdue) },\n { value: 'stale', label: t('statusStale'), badge: orZero(view.allCounts.stale) },\n ],\n onChange: (next) => { setQuery({ status: next }) },\n }),\n h('div', { style: { width: `${SEARCH_WIDTH}px`, flex: 'none' } },\n h(SearchInput, {\n value: state.query.othersKeyword,\n placeholder: t('searchPlaceholder'),\n ariaLabel: t('searchPlaceholder'),\n onChange: (next) => { setQuery({ othersKeyword: next }) },\n }),\n ),\n ),\n view.groups.length === 0\n // 空态:虚线框 + 两排字 + 按钮(参考 dispatch 空态;按钮先接「回会话」,\n // 真正的「起会话建任务」归会话交接任务)\n ? h('div', { className: 'dtl-empty-guide', style: { marginTop: 'var(--dtl-space-2)' } },\n h('div', { style: { fontSize: 'var(--dtl-font-lg)', fontWeight: 600, marginBottom: 'var(--dtl-space-1)' } }, t('allEmptyTitle')),\n h('div', { style: { fontSize: 'var(--dtl-font-md)', color: 'var(--dtl-fg-2)', marginBottom: 'var(--dtl-space-3)' } }, t('allEmptyHint')),\n h(Button, { variant: 'primary', size: 'md', onClick: onBack }, t('allEmptyAction')),\n )\n : h('div', { style: { marginTop: 'var(--dtl-space-2)' } },\n view.groups.map(group => h(WorkspaceGroupView, {\n key: group.workspaceId, t, group, board,\n })),\n ),\n ),\n ),\n ),\n ),\n ),\n )\n}\n\n/** 角标:>0 才给数字,0 = undefined(不标)。 */\nfunction orZero(n: number): number | undefined {\n return n > 0 ? n : undefined\n}\n\n/** 一个工作区分组(全部待办表格段):段头(点+名+统计)→ 列头行 → 任务行。\n * 统计口径(用户 2026-10-18):共 N|待办 N|进行中 N 常显(0 也显);\n * 积压 / 逾期 / 停滞**只在 >0 时出现**。 */\nfunction WorkspaceGroupView(props: {\n t: Translate\n group: WorkspaceGroup\n /** 传下去只为问动作权限(是不是我的)——第二区含我的任务,按钮得跟第一区一致。 */\n board: BoardPayload | null\n}): ReactNode {\n const { t, group, board } = props\n const [open, setOpen] = useState(group.rows.length <= GROUP_COLLAPSE_OVER)\n const stats = [\n t('groupTotal', { n: group.total }),\n t('groupOpen', { n: group.openCount }),\n t('groupDoing', { n: group.doingCount }),\n ...(group.backlogCount > 0 ? [t('groupBacklog', { n: group.backlogCount })] : []),\n ...(group.overdueCount > 0 ? [t('groupOverdue', { n: group.overdueCount })] : []),\n ...(group.staleCount > 0 ? [t('groupStale', { n: group.staleCount })] : []),\n ].join(' | ')\n return h('div', { className: 'dtl-group' },\n h('button', {\n type: 'button',\n className: 'dtl-group__head',\n 'aria-expanded': open,\n title: open ? t('collapseAria') : t('expandAria'),\n onClick: () => { setOpen(!open) },\n },\n // 段头标识:展开 = 打开的文件夹、收起 = 合上的文件夹(跟官方工作区显示逻辑一致);\n // 包里没有这对图标 ⇒ 回落成竖线\n IconFolderOpenRegular === undefined || IconFolderCloseRegular === undefined\n ? h('span', { className: 'dtl-group__bar' })\n : h('span', { style: { display: 'inline-flex', flex: 'none', color: 'var(--dtl-fg-3)' } },\n h(open ? IconFolderOpenRegular : IconFolderCloseRegular, { size: 14 })),\n h('span', { className: 'dtl-group__name dtl-ellipsis' }, group.title),\n h('span', { style: { flex: '1 1 auto' } }),\n h('span', { className: 'dtl-group__stats' }, stats),\n ),\n open\n ? h('div', null,\n // 无列头行(用户 2026-10-18:表头就是段头——左工作区名、右统计,不放「任务/创建人」那排)\n group.rows.map(row => h(TaskRowView, {\n key: row.key, t, row, variant: 'others', board,\n })),\n )\n : null,\n )\n}\n\n","/**\n * 文案唯一真源:命名空间 `dsh-task-ledger`,zh / en 两档。\n *\n * 规矩(docs/design/ui-style.md §五):\n * - 两档**键必须完全对齐**(冒烟有断言),漏一种语言界面就露键名;\n * - 界面上不许出现未登记的硬编码中文;\n * - 占位符一律 `{name}`,由 `interpolateTranslate` 自己替换(不依赖宿主是否支持 params);\n * - 语言切换**跟随宿主**:插件不自备语言开关(注册字典 + 注册项声明 `locale:` ⇒ 宿主渲染器合成 `t` 席位)。\n */\n\n/** 文案键全集(增删键必须同时改 zh / en)。 */\nexport type LocaleKey =\n | 'backToConversation' | 'panelTitle' | 'tabTasks' | 'tabStats'\n | 'refresh' | 'refreshing' | 'loading' | 'retry' | 'loadFailed'\n | 'degradedTitle' | 'degradedItem'\n | 'statsPlaceholder'\n // 统计卡\n | 'statMyActive' | 'statOpen' | 'statOpenHint' | 'statDoing' | 'statDoingHint' | 'statDoingAllMine'\n | 'statBacklog' | 'statOverdue' | 'statStale'\n | 'statMyOverdueHint' | 'statMyStaleHint' | 'statBacklogHint' | 'statOverdueHint' | 'statStaleHint'\n | 'statStaleTitle' | 'statOverdueTitle' | 'statBacklogTitle'\n | 'statWeekDone' | 'statLastWeekDone' | 'statCreatedHint'\n | 'daysNone' | 'daysValue'\n // 趋势图\n | 'chartTitle' | 'chartCaption' | 'chartBucket' | 'chartEmpty' | 'chartLoading' | 'chartUnavailable'\n | 'chartScopeAll' | 'chartScopeMine' | 'chartOnlyMine' | 'chartLegendDone' | 'chartLegendCreated' | 'chartLegendRemaining' | 'chartAvg'\n | 'periodRange'\n // 筛选行\n | 'scopeLabel' | 'scopeMine' | 'scopeAll'\n | 'statusLabel' | 'statusAll' | 'statusActive' | 'statusOpen' | 'statusBacklog' | 'statusOverdue' | 'statusStale'\n | 'tipOpen' | 'tipBacklog' | 'tipDoing' | 'tipOverdue' | 'tipStale'\n | 'workspaceLabel' | 'workspaceAll'\n | 'searchPlaceholder'\n // 列表分区\n | 'regionMine' | 'regionAll' | 'showingCount'\n | 'groupTotal' | 'groupOpen' | 'groupDoing' | 'groupBacklog' | 'groupOverdue' | 'groupStale'\n | 'colTask' | 'colCreator' | 'colOwner' | 'colStatus' | 'colTime'\n | 'allEmptyTitle' | 'allEmptyHint' | 'allEmptyAction'\n | 'expandAria' | 'collapseAria'\n | 'ownerUnclaimed' | 'fieldCreator' | 'fieldOwner' | 'fieldCreated' | 'fieldStarted' | 'fieldFinished' | 'timeUnknown'\n | 'ageDaysText' | 'ageToday'\n | 'startedAt'\n | 'actionResume' | 'actionFinish' | 'actionRelease' | 'actionClaim'\n | 'tipResume' | 'tipFinish' | 'tipRelease' | 'tipClaim' | 'viewDetail'\n | 'createdAt'\n | 'fieldCreatedStamp' | 'mineEmpty' | 'mineEmptyHint' | 'mineCount'\n | 'worklogTitle' | 'worklogPath' | 'worklogMissing'\n | 'mdCopy' | 'mdCopied' | 'mdFootnotes'\n // 空态\n | 'emptyNoWorkspace' | 'emptyNoWorkspaceHint' | 'emptyNoTask' | 'emptyNoTaskHint' | 'emptyFiltered'\n // 浮标入口\n | 'badgeTitle' | 'openBoard'\n\n/** 中文(默认档)。 */\nexport const zh: Record<LocaleKey, string> = {\n backToConversation: '返回会话',\n panelTitle: '任务台账',\n tabTasks: '任务',\n tabStats: '统计',\n refresh: '刷新',\n refreshing: '刷新中',\n loading: '读取中…',\n retry: '重试',\n loadFailed: '读取失败',\n degradedTitle: '部分工作区读取失败',\n degradedItem: '{title}:{message}',\n statsPlaceholder: '统计报表尚未开建(另立任务):这里以后放分类分布、按人完成数、时间线。',\n\n statMyActive: '我的待办',\n statMyOverdueHint: '逾期 {n}',\n statMyStaleHint: '停滞 {n}',\n statOpen: '待认领',\n statOpenHint: '最长等待 {n} 天',\n statDoing: '进行中',\n statDoingHint: '他人在办 {n}',\n statDoingAllMine: '均为我负责',\n statBacklog: '积压',\n statOverdue: '逾期',\n statStale: '停滞',\n statBacklogHint: '占待认领 {n}%',\n statOverdueHint: '最长 {n} 天未结',\n statStaleHint: '最长 {n} 天未更新',\n statStaleTitle: '停滞 = 进行中满 {n} 天没更新',\n statOverdueTitle: '逾期 = 认领后满 {n} 天还没结案',\n statBacklogTitle: '积压 = 待认领满 {n} 天没人接',\n statWeekDone: '本周完成',\n statLastWeekDone: '上周完成',\n statCreatedHint: '新增 {n}',\n daysNone: '—',\n daysValue: '{n} 天',\n\n chartTitle: '近 8 周',\n chartCaption: '完成(按结案时间)/ 新增(按创建时间),周一起桶;我的 = owner 是我',\n chartBucket: '{week} 当周:完成 {done} 条,新增 {created} 条',\n chartEmpty: '近 8 周没有记录',\n chartLoading: '趋势读取中…(列表已就绪,统计稍后到)',\n chartUnavailable: '趋势暂时读不到(不影响上面的任务列表)',\n chartScopeAll: '所有',\n chartScopeMine: '我的',\n chartOnlyMine: '只看我的',\n chartLegendDone: '完成',\n chartLegendCreated: '新增',\n chartLegendRemaining: '未结',\n chartAvg: '周均 {n}',\n periodRange: '{from} ~ {to}',\n\n scopeLabel: '范围',\n scopeMine: '我的',\n scopeAll: '全部工作区',\n statusLabel: '状态',\n statusAll: '全部',\n statusActive: '进行中',\n statusOpen: '待认领',\n statusBacklog: '积压',\n statusOverdue: '逾期',\n statusStale: '停滞',\n tipOpen: '任务创建于 {created}\\n尚未有人认领',\n tipBacklog: '任务创建于 {created}\\n无人认领已满 {d}',\n tipDoing: '{who}\\n开始执行:{started}\\n最后更新:{updated}',\n tipOverdue: '{who}\\n开始执行:{started}\\n最后更新:{updated}\\n已逾期 {d}',\n tipStale: '{who}\\n开始执行:{started}\\n最后更新:{updated}\\n已停滞 {d}',\n workspaceLabel: '工作区',\n workspaceAll: '全部工作区',\n searchPlaceholder: '搜索',\n\n regionMine: '我的待办',\n regionAll: '全部待办',\n showingCount: '显示 {n} 条',\n groupTotal: '共 {n} 条',\n groupOpen: '待办 {n} 条',\n groupDoing: '进行中 {n} 条',\n groupBacklog: '积压 {n} 条',\n groupOverdue: '逾期 {n} 条',\n groupStale: '停滞 {n} 条',\n colTask: '任务',\n colCreator: '创建人',\n colOwner: '执行人',\n colStatus: '状态',\n colTime: '时长',\n allEmptyTitle: '没有可显示的事项',\n allEmptyHint: '切换上面的状态过滤试试,或新建一个任务。',\n allEmptyAction: '新建任务',\n expandAria: '展开过程文档',\n collapseAria: '收起',\n\n ownerUnclaimed: '待认领',\n fieldCreator: '创建',\n fieldOwner: '执行',\n fieldCreated: '创建',\n fieldStarted: '开始',\n fieldFinished: '结案',\n timeUnknown: '时间未知',\n ageDaysText: '已进行 {n} 天',\n ageToday: '今天',\n actionResume: '继续',\n actionFinish: '结束',\n actionRelease: '释放',\n tipResume: '继续推进这个任务',\n tipFinish: '结案并写入历史',\n tipRelease: '释放认领,让别人接手',\n actionClaim: '认领',\n tipClaim: '认领这个任务,变成我在做',\n createdAt: '{who} 创建于 {when}',\n startedAt: '{who} 开始于 {when}',\n viewDetail: '查看详情',\n fieldCreatedStamp: '{day} 创建',\n mineEmpty: '您现在没有正在进行的工作',\n mineEmptyHint: '在某个工作区 new 一个任务,或到下面认领一条待认领任务,这里就会出现。',\n mineCount: '{n} 条',\n worklogTitle: '过程文档',\n worklogPath: '路径',\n worklogMissing: '(缺失)',\n mdCopy: '复制',\n mdCopied: '已复制',\n mdFootnotes: '脚注',\n\n emptyNoWorkspace: '还没有台账工作区',\n emptyNoWorkspaceHint: '在某个项目里跑 task-ledger skill 的 init 建档(生成 ledger/progress.json),这里就会出现它。',\n emptyNoTask: '一条任务都没有',\n emptyNoTaskHint: '用 task-ledger skill 的 new 开一个任务,或让别人开在这里等你认领。',\n emptyFiltered: '没有符合当前筛选的任务',\n\n badgeTitle: '我名下待办',\n openBoard: '打开任务台账',\n}\n\n/** English(与 zh 键完全对齐)。 */\nexport const en: Record<LocaleKey, string> = {\n backToConversation: 'Back to chat',\n panelTitle: 'Task ledger',\n tabTasks: 'Tasks',\n tabStats: 'Stats',\n refresh: 'Refresh',\n refreshing: 'Refreshing',\n loading: 'Loading…',\n retry: 'Retry',\n loadFailed: 'Load failed',\n degradedTitle: 'Some workspaces failed to load',\n degradedItem: '{title}: {message}',\n statsPlaceholder: 'Stats reports are not built yet (separate task): category breakdown, completions by person, timeline.',\n\n statMyActive: 'My todos',\n statMyOverdueHint: 'overdue {n}',\n statMyStaleHint: 'stale {n}',\n statOpen: 'Unclaimed',\n statOpenHint: 'longest waiting {n} days',\n statDoing: 'In progress',\n statDoingHint: 'others: {n}',\n statDoingAllMine: 'all mine',\n statBacklog: 'Backlog',\n statOverdue: 'Overdue',\n statStale: 'Stale',\n statBacklogHint: '{n}% of unclaimed',\n statOverdueHint: 'longest {n} days open',\n statStaleHint: 'longest {n} days idle',\n statStaleTitle: 'stale = active with no update for {n}+ days',\n statOverdueTitle: 'overdue = claimed {n}+ days ago and still open',\n statBacklogTitle: 'backlog = unclaimed for {n}+ days',\n statWeekDone: 'Done this week',\n statLastWeekDone: 'Done last week',\n statCreatedHint: '{n} created',\n daysNone: '—',\n daysValue: '{n}d',\n\n chartTitle: 'Last 8 weeks',\n chartCaption: 'done (by close time) / created (by create time), weeks start Monday; mine = owner is me',\n chartBucket: 'week of {week}: {done} done, {created} created',\n chartEmpty: 'Nothing in the last 8 weeks',\n chartLoading: 'Loading trend… (list is ready, stats will follow)',\n chartUnavailable: 'Trend is unavailable right now (the task lists above are unaffected)',\n chartScopeAll: 'All',\n chartScopeMine: 'Mine',\n chartOnlyMine: 'Mine only',\n chartLegendDone: 'Done',\n chartLegendCreated: 'Created',\n chartLegendRemaining: 'Outstanding',\n chartAvg: 'avg {n}/wk',\n periodRange: '{from} ~ {to}',\n\n scopeLabel: 'Scope',\n scopeMine: 'Mine',\n scopeAll: 'All workspaces',\n statusLabel: 'Status',\n statusAll: 'All',\n statusActive: 'Active',\n statusOpen: 'Unclaimed',\n statusBacklog: 'Backlog',\n statusOverdue: 'Overdue',\n statusStale: 'Stale',\n tipOpen: 'Created on {created}\\nNot claimed yet',\n tipBacklog: 'Created on {created}\\nUnclaimed for {d}',\n tipDoing: '{who}\\nStarted: {started}\\nLast updated: {updated}',\n tipOverdue: '{who}\\nStarted: {started}\\nLast updated: {updated}\\nOverdue by {d}',\n tipStale: '{who}\\nStarted: {started}\\nLast updated: {updated}\\nStalled for {d}',\n workspaceLabel: 'Workspace',\n workspaceAll: 'All workspaces',\n searchPlaceholder: 'Search',\n\n regionMine: 'My todos',\n regionAll: 'All tasks',\n showingCount: '{n} shown',\n groupTotal: '{n} total',\n groupOpen: '{n} open',\n groupDoing: '{n} active',\n groupBacklog: '{n} backlog',\n groupOverdue: '{n} overdue',\n groupStale: '{n} stale',\n colTask: 'Task',\n colCreator: 'Created by',\n colOwner: 'Owner',\n colStatus: 'Status',\n colTime: 'Duration',\n allEmptyTitle: 'Nothing to show',\n allEmptyHint: 'Try another status filter above, or create a task.',\n allEmptyAction: 'New task',\n expandAria: 'Expand worklog',\n collapseAria: 'Collapse',\n\n ownerUnclaimed: 'unclaimed',\n fieldCreator: 'created by',\n fieldOwner: 'owner',\n fieldCreated: 'created',\n fieldStarted: 'started',\n fieldFinished: 'closed',\n timeUnknown: 'unknown time',\n ageDaysText: 'active {n}d',\n ageToday: 'today',\n actionResume: 'Resume',\n actionFinish: 'Close',\n actionRelease: 'Release',\n tipResume: 'keep working on this task',\n tipFinish: 'close and archive it',\n tipRelease: 'release the claim for others',\n actionClaim: 'Claim',\n tipClaim: 'claim this task for yourself',\n createdAt: 'created by {who} on {when}',\n startedAt: '{who} started on {when}',\n viewDetail: 'View details',\n fieldCreatedStamp: 'created {day}',\n mineEmpty: 'Nothing in progress right now',\n mineEmptyHint: 'Create a task in a workspace or claim one below, and it shows up here.',\n mineCount: '{n} items',\n worklogTitle: 'Worklog',\n worklogPath: 'path',\n worklogMissing: '(missing)',\n mdCopy: 'Copy',\n mdCopied: 'Copied',\n mdFootnotes: 'Footnotes',\n\n emptyNoWorkspace: 'No ledger workspace yet',\n emptyNoWorkspaceHint: 'Run the task-ledger skill init in a project (creates ledger/progress.json) and it shows up here.',\n emptyNoTask: 'No tasks yet',\n emptyNoTaskHint: 'Create one with the task-ledger skill new, or leave one here for others to claim.',\n emptyFiltered: 'No tasks match the current filters',\n\n badgeTitle: 'my todos',\n openBoard: 'Open task ledger',\n}\n\n/** 翻译函数类型(宿主渲染器按 `locale:` 声明合成的 `t` 席位同形)。 */\nexport type Translate = (key: LocaleKey, params?: Record<string, string | number>) => string\n\n/** 把宿主给的无参 t 包成带占位符替换的 t(模板一律 `{name}`,不依赖宿主是否支持 params)。 */\nexport function interpolateTranslate(base: Translate): Translate {\n return (key, params) => {\n const raw = base(key)\n if (params === undefined) return raw\n return raw.replace(/\\{(\\w+)\\}/g, (match, name: string) => (name in params ? String(params[name]) : match))\n }\n}\n\n/** 键名兜底:宿主没给 t 席位时用(键名绝不上界面)。 */\nexport function fallbackTranslate(key: LocaleKey, params?: Record<string, string | number>): string {\n return interpolateTranslate((name) => zh[name] ?? name)(key, params)\n}\n","/**\n * 右上浮标入口(`shell.overlay` list 条目,全帧浮层,条目并排添加不替换)。\n * 定位用宿主变量 `--dsh-frame-overlay-top`(标题栏净空 + 20px),**不写死 top 值**;\n * 浮层默认 `pointer-events:none`、子元素自动恢复 ⇒ 自绘条目不挡下方应用。\n *\n * 角标 = 我名下进行中计数(host 统计真值,SSE 已否 ⇒ 取自同一份快照,>0 才画);\n * 0→n 跳变时一次脉冲动画,不自动消失。宿主**无**通知 API ⇒ 「通知」就是这个角标。\n */\nimport { createElement as h, useEffect, useRef, useState, useSyncExternalStore, type ReactNode } from 'react'\nimport type { Translate } from './locales'\nimport { boardSnapshot, boardSubscribe } from './store'\nimport { ensureUiBase, LedgerIcon } from './ui'\n\nexport function OverlayEntry(props: { t: Translate; onOpen: () => void }): ReactNode {\n const { t, onOpen } = props\n ensureUiBase()\n const state = useSyncExternalStore(boardSubscribe, boardSnapshot)\n const count = state.board?.stats.myActive ?? 0\n const previous = useRef(0)\n const [pulse, setPulse] = useState(0)\n\n // 0→n 跳变播一次脉冲;计数归零不播(角标本就不渲染)。\n useEffect(() => {\n if (count > previous.current) setPulse(value => value + 1)\n previous.current = count\n }, [count])\n\n return h('div', {\n style: {\n position: 'absolute',\n top: 'var(--dsh-frame-overlay-top,20px)',\n right: '12px',\n zIndex: 'var(--dtl-z-badge,20)',\n pointerEvents: 'auto',\n },\n },\n h('button', {\n type: 'button',\n title: t('openBoard'),\n 'aria-label': t('openBoard'),\n onClick: onOpen,\n style: {\n position: 'relative',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: '32px',\n height: '32px',\n padding: 0,\n border: '1px solid var(--dtl-border)',\n borderRadius: 'var(--dtl-radius-sm)',\n background: 'var(--dtl-solid)',\n color: 'var(--dtl-on-solid)',\n boxShadow: 'var(--dtl-shadow-1)',\n cursor: 'pointer',\n },\n },\n h(LedgerIcon, { size: 18 }),\n count > 0\n ? h('span', { key: pulse, className: 'dtl-badge', 'data-pulse': pulse > 0, title: t('badgeTitle') }, String(count))\n : null,\n ),\n )\n}\n","/**\n * 浏览器半入口(client bundle,产物 dist/client.js):装配层,不写业务逻辑。\n *\n * 三处注册各自挂进「服务就位才触发」的 slots 注入(参考插件真机教训:顶层 inject 声明\n * 组合满足不了的依赖会让 entry 一直 pending、卡死整个 dsh 启动):\n * · locale 字典(zh / en)——界面语言跟随宿主,插件不自备开关;\n * · `main` keyed 槽整页(看板首页);\n * · `shell.overlay` list 槽浮标入口(点开 = `ctx.layout.selectPanel(<看板 id>)`)。\n *\n * 宿主能力一律**本地结构化声明**,不 import 官方包(rc 接口会无声变);\n * `t` 席位由宿主渲染器按注册项的 `locale:` 声明合成进组件 props。\n */\nimport { createElement as h } from 'react'\nimport { BoardHome } from './board-home'\nimport { en, fallbackTranslate, interpolateTranslate, zh, type LocaleKey, type Translate } from './locales'\nimport { LedgerIcon } from './ui'\nimport { OverlayEntry } from './overlay-entry'\n\n/** 命名空间(settings / locale / 面板 id 同名,便于对应)。 */\n/** 模块级 t 席位(侧栏 label 渲染期求值用;绑不上就回落键名,绝不上界面)。 */\nlet runtimeT: Translate = (key) => key\n\nconst NS = 'dsh-task-ledger'\n/** `main` 槽的 key(非 `conversation` ⇒ 不绑 Session)。 */\nconst PANEL_ID = NS\n/** `shell.overlay` list 条目的 id(新 id = 并排添加,不替换宿主条目)。 */\nconst OVERLAY_ID = `${NS}-badge`\n\n/** 浏览器插件上下文:只声明本文件实际用到的服务面。 */\ninterface ClientContext {\n inject(deps: readonly string[], callback: (ctx: ClientContext) => void): void\n effect(setup: () => void): void\n locale?: {\n register(ns: string, dictionaries: Record<string, Record<string, string>>): void\n /** 绑一份无参 t(侧栏 label 在渲染期求值,拿不到组件 props 的 t 席位)。 */\n bind(ns: string): Translate\n }\n layout?: {\n selectPanel(panelId: string | null): void\n }\n slots: {\n inject(slot: string, factory: () => () => void): () => void\n register(options: Record<string, unknown>, component: unknown): () => void\n }\n}\n\n/** 宿主没给 t 席位时的兜底(键名绝不上界面)。 */\nconst t: Translate = interpolateTranslate((key: LocaleKey, params?: Record<string, string | number>) =>\n fallbackTranslate(key, params))\n\nexport function apply(ctx: ClientContext): void {\n // ① 字典注册(宿主语言切换 ⇒ 渲染器换 t 引用 ⇒ 整页重渲染)\n ctx.inject(['locale'], (localeCtx) => {\n ctx.effect(() => localeCtx.locale?.register(NS, { zh, en }))\n // 模块级 t 席位:`sidebar.panellist` 的 label 由侧栏在渲染期求值,拿不到组件 props 的 t\n try { runtimeT = localeCtx.locale?.bind(NS) ?? runtimeT } catch { /* 保持键名兜底 */ }\n })\n\n // ② 布局服务:浮标点开整页 / 整页返回会话\n let selectPanel: (id: string | null) => void = () => {}\n ctx.inject(['layout'], (sub) => {\n const layout = sub.layout\n if (layout !== undefined) selectPanel = (id) => { layout.selectPanel(id) }\n })\n\n // ③ 槽位注册(只依赖 slots,自成一块:locale / layout 缺席也不该让入口消失)\n ctx.inject(['slots'], (sub) => {\n // 左侧入口:侧栏面板条目(list 槽)。**行按钮、点击、激活态全由侧栏 / 布局服务托管**,\n // 组件只画图标;id 必须与 main 槽的 key 一致(同一 MainPanelId)。\n // 事实见 external/client-ui-layout.md §一/§二(0.2.0-rc.2 亲验)。\n sub.slots.inject('sidebar.panellist', () =>\n sub.slots.register(\n { name: 'sidebar.panellist', id: PANEL_ID, order: 30, label: () => runtimeT('panelTitle'), locale: NS },\n (props: { size?: number }) => h(LedgerIcon, { size: props.size ?? 18 }),\n ),\n )\n sub.slots.inject('main', () =>\n sub.slots.register(\n { name: 'main', key: PANEL_ID, locale: NS },\n (props: { t?: Translate }) => h(BoardHome, {\n t: props.t ?? t,\n onBack: () => { selectPanel(null) },\n }),\n ),\n )\n sub.slots.inject('shell.overlay', () =>\n sub.slots.register(\n { name: 'shell.overlay', id: OVERLAY_ID, locale: NS },\n (props: { t?: Translate }) => h(OverlayEntry, {\n t: props.t ?? t,\n onOpen: () => { selectPanel(PANEL_ID) },\n }),\n ),\n )\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["h","Tooltip","useState","h","Tooltip","IconSearchOutlineRegular","IconDownloadOutlineRegular","IconChevronRightOutlineRegular","IconCheckOutlineRegular","useSyncExternalStore","useState","useMemo","h","IconRefreshOutlineRegular","IconChecklistOutlineRegular","IconFlatListOutlineRegular","IconFolderOpenRegular","IconFolderCloseRegular","useSyncExternalStore","useRef","useState","h","h"],"sources":["../src/client/api.ts","../src/client/store.ts","../src/client/rows.ts","../src/client/format.ts","../src/client/tokens.ts","../src/client/ui.ts","../src/client/task-row.ts","../src/client/board-home.ts","../src/client/locales.ts","../src/client/overlay-entry.ts","../src/client/index.ts"],"sourcesContent":["/**\n * 数据面取数**单源**:所有 client → host 的 HTTP 都在这里,页面不许各写一份 fetch。\n *\n * 纪律:只做传输与形状校验,不造兜底假值(DSH 插件规则「数据真实」)——\n * 失败就抛,由调用方显示错误态;返回什么渲染什么。\n *\n * ⚠️ 必须带超时且 abort 要带 reason:一次挂起的请求会让面板永久停在「读取中」,\n * 而不带 reason 时浏览器把 error.message 填成「signal is aborted without reason」,\n * 看不出是超时(姊妹仓 2026-10-07 真机教训)。\n */\nimport type { BoardPayload, CloseHandoff, FilterOptions, HistoryRecord, TaskDetail, TrendPayload } from './types'\n\n/** host 路由前缀(全仓唯一;与 host/routes.ts 的 API_PREFIX 对应)。 */\nexport const API_PREFIX = 'api/task-ledger'\n\nconst DEFAULT_TIMEOUT_MS = 8_000\n/** 趋势端点读 history,比快路径慢一个量级(20 万条实测 4s+),超时单给。 */\nconst TREND_TIMEOUT_MS = 30_000\n\n/**\n * 错误响应榨人话:host 路由(routes.ts)发的是 `{ error: \"...\" }`,那里的话是**能直接上界面的**\n * (带工作区名、python3 起不来之类的原文)。不带它界面上只剩「HTTP 500」,等于没报错。\n */\nexport function httpErrorMessage(status: number, body: string): string {\n try {\n const parsed = JSON.parse(body) as { error?: unknown }\n if (typeof parsed.error === 'string' && parsed.error !== '') return `HTTP ${status}:${parsed.error}`\n } catch { /* 不是 JSON:走原文 */ }\n const text = body.trim().slice(0, 200)\n return text === '' ? `HTTP ${String(status)}` : `HTTP ${String(status)}:${text}`\n}\n\n/** 带超时的 JSON 取数(相对路径,同源)。 */\nexport async function fetchJson<T>(path: string, init: RequestInit = {}, timeoutMs = DEFAULT_TIMEOUT_MS): Promise<T> {\n const controller = new AbortController()\n const timer = setTimeout(() => {\n controller.abort(new DOMException(`请求超时(${timeoutMs}ms 未响应)`, 'TimeoutError'))\n }, timeoutMs)\n try {\n const res = await fetch(`${API_PREFIX}${path}`, { ...init, signal: controller.signal, cache: 'no-store' })\n if (!res.ok) throw new Error(httpErrorMessage(res.status, await res.text().catch(() => '')))\n return await res.json() as T\n } finally {\n clearTimeout(timer)\n }\n}\n\n/**\n * 首页快照(快路径:在办全量 + 统计 + 各工作区身份;**不含趋势**)。\n * `fresh` = 让 host 先清 1.5s 读缓存再算(手动刷新 / 首读都要最新,不吃上一屏的缓存)。\n */\nexport function fetchBoardSnapshot(opts: { fresh?: boolean } = {}): Promise<BoardPayload> {\n return fetchJson<BoardPayload>(`/board/snapshot${freshSuffix(opts)}`)\n}\n\n/**\n * 趋势(慢路径:近 8 周 + 四个自然周期,host 侧要读 history)。\n * 超时给得比默认宽:历史量大时读得久(实测 20 万条量级 4s+);\n * 旧宿主没有该端点 ⇒ 404,直接抛给调用方兜底,不阻塞列表。\n */\nexport function fetchTrend(opts: { fresh?: boolean } = {}): Promise<TrendPayload> {\n return fetchJson<TrendPayload>(`/board/trend${freshSuffix(opts)}`, {}, TREND_TIMEOUT_MS)\n}\n\n/** `?fresh=1` 只在真要最新值时带;不带就吃 host 的 1.5s 缓存。 */\nfunction freshSuffix(opts: { fresh?: boolean }): string {\n return opts.fresh === true ? '?fresh=1' : ''\n}\n\n/** 已结案列表(服务端按窗口 / 标签 / 分页过滤)。 */\nexport function fetchHistory(params: { since?: string; until?: string; tag?: string; limit?: number }): Promise<HistoryRecord[]> {\n const query = new URLSearchParams()\n if (params.since !== undefined) query.set('since', params.since)\n if (params.until !== undefined) query.set('until', params.until)\n if (params.tag !== undefined && params.tag !== '') query.set('tag', params.tag)\n if (params.limit !== undefined) query.set('limit', String(params.limit))\n const suffix = query.toString()\n return fetchJson<{ records: HistoryRecord[] }>(`/history${suffix === '' ? '' : `?${suffix}`}`)\n .then(body => body.records ?? [])\n}\n\n/** 单条在办 + 过程文档正文(展开行用)。 */\nexport function fetchTaskDetail(workspaceId: string, taskId: string): Promise<TaskDetail> {\n const query = new URLSearchParams({ ws: workspaceId, id: taskId })\n return fetchJson<TaskDetail>(`/task?${query.toString()}`)\n}\n\n/** 筛选项候选(在办全量 + 近 13 个月历史的去重 owner / category / tag)。 */\nexport function fetchFilters(): Promise<FilterOptions> {\n return fetchJson<FilterOptions>('/filters')\n}\n\n/**\n * 结束任务:请 host 起一个会话(首条 = 预置提示词 + 用户留言),返回 `sessionId` 供跳转。\n *\n * `message` 是用户在弹框里留的话(可空);`provider` / `model` 成对给 = 用用户选的模型,\n * 都不给 = 用宿主默认模型(只给一半会被 host 400 拒,不做猜测)。\n */\nexport function requestClose(body: {\n ws: string\n id: string\n message: string\n provider?: string\n model?: string\n}): Promise<CloseHandoff> {\n return fetchJson<CloseHandoff>('/close', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n}\n\n/** 认领 / 释放的写结果;成功后由调用方自行刷新快照(host 已清该工作区读缓存)。 */\nexport interface TaskMutationResult {\n readonly ok: true\n}\n\n/**\n * 认领:点一下即归当前用户(host 直跑脚本,不开会话)。\n * 只有 `open` 且无人认领才允许,忙时 host 回 500 + 人话(脚本原文)。\n */\nexport function requestClaim(body: { ws: string; id: string }): Promise<TaskMutationResult> {\n return fetchJson<TaskMutationResult>('/claim', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n}\n\n/** 释放:退回待认领(owner 清空),只许当前 owner 本人(host 侧脚本校验)。 */\nexport function requestRelease(body: { ws: string; id: string }): Promise<TaskMutationResult> {\n return fetchJson<TaskMutationResult>('/release', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n}\n","/**\n * 看板数据仓(client 侧**唯一**取数入口):浮标角标与整页共用同一份快照,\n * 谁先挂载谁触发首读;刷新按钮走同一个 `refresh()`,**不轮询**(2026-10-17 拍板:\n * 后端纯按需,刷新时机归前端 = 打开面板首读 + 手动刷新)。\n *\n * 消费方式:`useSyncExternalStore(boardSubscribe, boardSnapshot)`。\n * ⚠️ `getSnapshot` 必须返回**稳定引用**(每次新建对象会被判定为「一直在变」⇒ 无限重渲染),\n * 故只在真变了时才换 `state` 对象。\n */\nimport { fetchBoardSnapshot, fetchTrend } from './api'\nimport type { BoardPayload, TrendPayload } from './types'\n\n/** 范围:我的 / 全部工作区。 */\nexport type Scope = 'mine' | 'all'\n/** 状态档:进行中 = doing;积压 / 逾期 / 停滞 = host 按时间标记的 backlog / overdue / stale。 */\nexport type StatusFilter = 'all' | 'active' | 'open' | 'backlog' | 'overdue' | 'stale'\n\nexport interface Query {\n scope: Scope\n /** 「其他待办」的状态档过滤(segmented)。 */\n status: StatusFilter\n /** 工作区 id;'' = 全部工作区(只作用于「我的待办」)。 */\n workspace: string\n /** 「我的待办」的标题搜索。 */\n keyword: string\n /** 「其他待办」的搜索(标题 / 执行人)。 */\n othersKeyword: string\n}\n\nexport interface BoardState {\n status: 'loading' | 'ready' | 'error'\n board: BoardPayload | null\n error: string | null\n query: Query\n /** 手动刷新在途(按钮转圈 / 禁用)。 */\n refreshing: boolean\n /** 趋势(慢路径,`/board/trend`):null = 还没回来 / 旧宿主没有该端点。 */\n trend: TrendPayload | null\n /** 趋势加载态:首屏不阻塞列表,图表区自己显示占位 / 兜底文案。 */\n trendStatus: 'idle' | 'loading' | 'ready' | 'error'\n trendError: string | null\n}\n\nconst INITIAL_QUERY: Query = { scope: 'all', status: 'all', workspace: '', keyword: '', othersKeyword: '' }\n\nlet state: BoardState = {\n status: 'loading',\n board: null,\n error: null,\n query: INITIAL_QUERY,\n refreshing: false,\n trend: null,\n trendStatus: 'idle',\n trendError: null,\n}\n\nconst listeners = new Set<() => void>()\n/** 首读只触发一次(谁先订阅谁触发),之后一律靠 `refresh()` / `setQuery()`。 */\nlet booted = false\n/** 在途轮次:迟到的响应按 token 丢弃,避免旧结果盖新结果。 */\nlet runToken = 0\n\nfunction emit(): void {\n for (const listener of [...listeners]) listener()\n}\n\nfunction set(patch: Partial<BoardState>): void {\n state = { ...state, ...patch }\n emit()\n}\n\nexport function boardSnapshot(): BoardState {\n return state\n}\n\nexport function boardSubscribe(listener: () => void): () => void {\n listeners.add(listener)\n if (!booted) {\n booted = true\n void refresh()\n }\n return () => { listeners.delete(listener) }\n}\n\n/**\n * 重读(2026-10-18 拆端点):**两段**——先快路径 `/board/snapshot`(只读在办,首屏立即可渲染),\n * 再慢路径 `/board/trend`(读 history,后台拉;迟到按 token 丢弃,不盖新快照)。\n * 趋势失败(含旧宿主 404)不阻塞列表:trendStatus 置 error,图表区显示兜底文案。\n * 两段都带 `fresh=1`:本函数只由首读与手动刷新触发,都要最新值(host 缓存清两次无害,清是全局的)。\n */\nexport async function refresh(): Promise<void> {\n const token = ++runToken\n set({ refreshing: true })\n try {\n const board = await fetchBoardSnapshot({ fresh: true })\n if (token !== runToken) return\n set({ status: 'ready', board, error: null, trendStatus: 'loading', trendError: null })\n } catch (error) {\n if (token !== runToken) return\n set({ status: 'error', error: error instanceof Error ? error.message : String(error) })\n return\n } finally {\n if (token === runToken) set({ refreshing: false })\n }\n try {\n const trend = await fetchTrend({ fresh: true })\n if (token !== runToken) return\n set({ trend, trendStatus: 'ready', trendError: null })\n } catch (error) {\n if (token !== runToken) return\n set({ trend: null, trendStatus: 'error', trendError: error instanceof Error ? error.message : String(error) })\n }\n}\n\n/** 改筛选:全部在浏览器里完成(快照已含全部在办),不发请求。 */\nexport function setQuery(patch: Partial<Query>): void {\n set({ query: { ...state.query, ...patch } })\n}\n","/**\n * 列表视图的筛选 / 分区 / 排序(**唯一**实现,纯函数、不碰网络):页面只渲染,不各写一份过滤。\n * 定稿见 docs/design/features/board-home.md §二.3:两区结构(我的待办平铺 / 按工作区分组),\n * 已结案**不在首页**(去统计 tab)。\n *\n * 单独成文件是为了能被冒烟直测(store.ts 引 api.ts,Node 剥类型解析不了无扩展名相对导入)。\n */\nimport type { BoardPayload, BoardTask, HistoryRecord } from './types'\nimport type { Query } from './store'\n\n/** 列表行:在办与已结案两套形状,行件按 kind 分支(不做「统一成一个假结构」的归一化)。 */\nexport type Row =\n | { kind: 'task'; key: string; task: BoardTask; mine: boolean }\n | { kind: 'closed'; key: string; record: HistoryRecord }\n\n/** 一个工作区分组(计数口径:待认领 / 进行中 / 积压 / 逾期 / 停滞 / 总,供「全部待办」段头统计)。 */\nexport interface WorkspaceGroup {\n readonly workspaceId: string\n readonly title: string\n readonly doingCount: number\n readonly overdueCount: number\n readonly staleCount: number\n readonly backlogCount: number\n readonly openCount: number\n readonly total: number\n readonly rows: readonly Row[]\n}\n\n/** 两区视图:第一区我的待办 + 第二区按工作区分组(第二区含全部任务,会与第一区重叠——用户 2026-10-18 要「全部待办」)。 */\nexport interface BoardView {\n readonly mine: readonly Row[]\n readonly groups: readonly WorkspaceGroup[]\n /** 第二区(全部待办)任务数。 */\n readonly total: number\n /** 第二区全量计数(**不受状态过滤影响**,供 segmented 角标;异常标记相互独立;含我的)。 */\n readonly allCounts: {\n readonly total: number\n readonly doing: number\n readonly open: number\n readonly overdue: number\n readonly stale: number\n readonly backlog: number\n }\n}\n\n/** 关键字命中(标题 / 任务号 / 人 / 分类 / 标签;已结案另含「完成了什么」)。 */\nfunction hitKeyword(keyword: string, parts: readonly (string | null | undefined)[]): boolean {\n const needle = keyword.trim().toLowerCase()\n if (needle === '') return true\n return parts.some(part => part?.toLowerCase().includes(needle) === true)\n}\n\n/** 「我的」判定:owner = 该工作区的当前 git 身份,且状态是进行中(blocked 是旧数据,照收;与 host 同口径)。 */\nexport function isMine(task: BoardTask, board: BoardPayload | null): boolean {\n if (board === null) return false\n const identity = board.identities[task.workspace.id] ?? null\n if (identity === null || identity === '') return false\n return task.owner === identity && (task.status === 'doing' || task.status === 'blocked')\n}\n\n/**\n * 任务动作权限(**唯一**判定,两区共用——用户 2026-10-18:不许各判各的)。\n * 传任务 + 快照进去,返回「能做哪些动作」;界面只按返回值画按钮,不再自己判断是不是我的。\n * ⇒ 第二区(全部待办)里**我的**任务也能继续 / 结束 / 释放,跟第一区一模一样。\n */\nexport interface TaskActions {\n /** 查看详情(两区、所有任务都有)。 */\n readonly view: boolean\n /** 认领:只有**没人认领**(待认领)的任务能认领。 */\n readonly claim: boolean\n readonly resume: boolean\n readonly finish: boolean\n readonly release: boolean\n}\n\nexport function resolveActions(task: BoardTask, board: BoardPayload | null): TaskActions {\n const mine = isMine(task, board)\n return {\n view: true,\n claim: !mine && task.status === 'open',\n resume: mine,\n finish: mine,\n release: mine,\n }\n}\n\n/** 已结案行(统计 tab):只有查看,没有在办动作。 */\nexport const CLOSED_ACTIONS: TaskActions = { view: true, claim: false, resume: false, finish: false, release: false }\n\n/** 状态档命中(积压 / 逾期 / 停滞由 host 标记,前端不判阈值)——只作用于第二区。 */\nfunction statusHit(task: BoardTask, status: Query['status']): boolean {\n if (status === 'all') return true\n if (status === 'active') return task.status === 'doing' || task.status === 'blocked'\n if (status === 'open') return task.status === 'open'\n if (status === 'backlog') return task.backlog === true\n if (status === 'overdue') return task.overdue === true\n return task.stale === true\n}\n\n/** 组内排序:别人的进行中在前、待认领在后;各自按创建 / 开始时间倒序。 */\nfunction inGroupOrder(a: Row, b: Row): number {\n if (a.kind !== 'task' || b.kind !== 'task') return 0\n const rank = (task: BoardTask): number => (task.status === 'open' ? 1 : 0)\n if (rank(a.task) !== rank(b.task)) return rank(a.task) - rank(b.task)\n return String(b.task.created ?? b.task.started ?? '').localeCompare(String(a.task.created ?? a.task.started ?? ''))\n}\n\n/**\n * 当前筛选下的两区视图(用户 2026-10-18 调整):\n * - 第一区「我的待办」= 我名下 doing/blocked,吃工作区下拉 + 标题搜索,按创建时间倒序;\n * - 第二区「全部待办」= **所有**在办任务按工作区分组(含我的——用户明确不要过滤掉自己),\n * 吃**状态档**(segmented)+ **标题 / 执行人搜索**(othersKeyword);一个整表(外框连着)。\n * ⇒ 我的任务会同时出现在两个区,这是预期(顶部快看 / 底部全量)。\n */\nexport function selectView(current: { board: BoardPayload | null; query: Query }): BoardView {\n const { board, query } = current\n if (board === null) {\n return { mine: [], groups: [], total: 0, allCounts: { total: 0, doing: 0, open: 0, overdue: 0, stale: 0, backlog: 0 } }\n }\n const allTasks = board.tasks\n const allCounts = {\n total: allTasks.length,\n doing: allTasks.filter((task) => task.status === 'doing').length,\n open: allTasks.filter((task) => task.status === 'open').length,\n overdue: allTasks.filter((task) => task.overdue === true).length,\n stale: allTasks.filter((task) => task.stale === true).length,\n backlog: allTasks.filter((task) => task.backlog === true).length,\n }\n const mineRows: Row[] = board.tasks\n .filter((task) => isMine(task, board))\n .filter((task) => query.workspace === '' || task.workspace.id === query.workspace)\n .filter((task) => hitKeyword(query.keyword, [task.title, task.id, task.owner, task.creator, task.category, ...(task.tags ?? [])]))\n .sort((a, b) => String(b.created ?? b.started ?? '').localeCompare(String(a.created ?? a.started ?? '')))\n .map((task): Row => ({ kind: 'task', key: `${task.workspace.id}:${task.id}`, task, mine: true }))\n const rest = query.scope === 'mine'\n ? []\n : board.tasks\n .filter((task) => statusHit(task, query.status))\n .filter((task) => hitKeyword(query.othersKeyword, [task.title, task.owner]))\n const byWorkspace = new Map<string, { title: string; rows: Row[] }>()\n for (const task of rest) {\n const entry = byWorkspace.get(task.workspace.id) ?? { title: task.workspace.title, rows: [] }\n // mine 必须说实话(第二区现在含全部任务,连我的也在里面):按钮权限由 resolveActions 判,\n // 但这一位同样是数据面,不许一律写 false。\n entry.rows.push({ kind: 'task', key: `${task.workspace.id}:${task.id}`, task, mine: isMine(task, board) })\n byWorkspace.set(task.workspace.id, entry)\n }\n const groups: WorkspaceGroup[] = [...byWorkspace.entries()]\n .map(([workspaceId, entry]) => ({\n workspaceId,\n title: entry.title,\n doingCount: entry.rows.filter((row) => row.kind === 'task' && row.task.status === 'doing').length,\n overdueCount: entry.rows.filter((row) => row.kind === 'task' && row.task.overdue === true).length,\n staleCount: entry.rows.filter((row) => row.kind === 'task' && row.task.stale === true).length,\n backlogCount: entry.rows.filter((row) => row.kind === 'task' && row.task.backlog === true).length,\n openCount: entry.rows.filter((row) => row.kind === 'task' && row.task.status === 'open').length,\n total: entry.rows.length,\n rows: entry.rows.sort(inGroupOrder),\n }))\n .sort((a, b) => a.title.localeCompare(b.title, 'zh-Hans-CN'))\n return { mine: mineRows, groups, total: groups.reduce((sum, group) => sum + group.rows.length, 0), allCounts }\n}\n\n/**\n * 已结案列表(统计 tab 用;首页不渲染已结案 ⇒ 这里只留数据面,供后续统计 tab 复用同一套筛选口径)。\n */\nexport function selectClosed(current: { history: readonly HistoryRecord[]; query: Query }): Row[] {\n return current.history\n .filter((record) => hitKeyword(current.query.keyword, [record.summary, record.workpackage, record.owner, record.category, ...(record.tags ?? [])]))\n .map((record, index) => ({ kind: 'closed', key: `${record.src ?? ''}:${record.time ?? ''}:${index}`, record }))\n}\n","/**\n * 客户端时间口径**单源**:补零 / 时刻串(本机时区)。\n * 解析失败按调用方给的兜底返回(不编造时间)。\n *\n * 2026-10-17 改版后首页不再做时间范围过滤(时间只作用于统计卡与趋势图,由 host 聚合)\n * ⇒ 原来的 `rangeToDates` / `withinRange` / `RangeKey` 一并删除;统计 tab 要做时间筛选时\n * 按当时口径在此重建,别处不许再写一份。\n */\nexport function pad2(value: number | string): string {\n return String(value).padStart(2, '0')\n}\n\n/** ISO → `YYYY-MM-DD HH:mm`(`seconds: true` 时补 `:ss`);解析失败返回 `fallback`(缺省原串)。 */\nexport function formatDateTime(iso: string, opts?: { seconds?: boolean; fallback?: string }): string {\n const ms = Date.parse(iso)\n if (Number.isNaN(ms)) return opts?.fallback ?? iso\n const d = new Date(ms)\n const base = `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(d.getMinutes())}`\n return opts?.seconds === true ? `${base}:${pad2(d.getSeconds())}` : base\n}\n","/**\n * 语义 token 层:`--dtl-*`(dtl = dsh-task-ledger)。\n *\n * 规矩(docs/design/ui-style.md §三,2026-10-17 对齐 dispatch 表后收严):\n * - **只有本文件定义 `--dtl-*`**;其它文件一律 `var(--dtl-*)` 消费,不许直接写 `var(--dsw-*)`;\n * - 每个 token 都带兜底值 ⇒ 宿主升级换名时不炸(最坏退到兜底色,不会白屏);\n * - 明暗差异只写在本文件末尾的 `body[data-ds-dark-theme]` 段:能靠宿主 alias 表达的\n * 一律不写覆盖(alias 自己随主题变),只有「固定中性面 / 半透明底」这类 alias 表达不了的才进该段。\n * - **离散刻度**:圆角 4/8/12/16/20/28、间距 4/8/12/16、控件高 24/28/32、字号 11/12/13/14/16\n * ——业务文件不许出现 5/7/9px、不许自造高度档(要加档位改本文件)。\n *\n * 明暗判据 = `body[data-ds-dark-theme]`(宿主 0.2.0-rc.2 源码核实:启动脚本 toggleAttribute 写入)。\n * 插件**禁用** `prefers-color-scheme`(它跟系统、不跟用户在宿主里的选择)。\n * 因为判据是纯 CSS 选择器 ⇒ 宿主切主题即生效,**不需要 MutationObserver**。\n *\n * 挂载点 = `body`:插件界面(含将来 portal 到 body 的弹层)全在 body 内,零调用点成本。\n */\nexport const UI_TOKENS_CSS = `\nbody{\n /* ── 文字四档 + 反色面 ───────────────────────────────────────── */\n --dtl-fg:var(--dsw-alias-label-primary,#1f2328);\n --dtl-fg-2:var(--dsw-alias-label-secondary,#5c6370);\n --dtl-fg-3:var(--dsw-alias-label-tertiary,#767d87);\n --dtl-fg-4:var(--dsw-alias-label-caption,#9aa1ab);\n --dtl-fg-dim:var(--dsw-alias-label-dimmed,#b9bfc7);\n /* 反色面上的字(角标 / 实心钮):暗色下是 bluish-1000 ⇒ 用 -foreground 而非 -inverted */\n --dtl-fg-inverse:var(--dsw-alias-label-primary-foreground,#fff);\n\n /* ── 面 ──────────────────────────────────────────────────────── */\n --dtl-surface-1:var(--dsw-alias-bg-layer-1,#fff);\n --dtl-surface-2:var(--dsw-alias-bg-layer-2,#fff);\n --dtl-surface-base:var(--dsw-alias-bg-base,#fff);\n /* 「选中亮片」底盘(配 shadow-raised 用) */\n --dtl-surface-raised:var(--dsw-alias-bg-layer-1,#fff);\n /* 固定中性实面(自绘浮标 / 角标这类必须自己变的东西) */\n --dtl-solid:var(--dsw-static-neutral-00,#fff);\n --dtl-on-solid:var(--dsw-alias-label-primary,#1f2328);\n /* 斑马纹 / 次层面(浅色下近白、深色下深一档) */\n --dtl-plate:var(--dsw-static-neutral-50,#fafafa);\n /* 斑马纹:几乎贴着原色的一层(用户 2026-10-18:3% 还是太明显) */\n --dtl-zebra:color-mix(in srgb,var(--dtl-fg) 2%,transparent);\n --dtl-plate-hover:var(--dsw-static-neutral-100,#f5f5f5);\n /* 表头底:浅色比斑马纹再深一档(两者不能撞色) */\n --dtl-head-bg:var(--dsw-static-neutral-100,#f5f5f5);\n\n /* ── 描边四档(宿主真值 l1 4% / l2 10% / l3 12% / l4 16%)────── */\n --dtl-border-faint:var(--dsw-alias-border-l1,#0000000a);\n --dtl-border:var(--dsw-alias-border-l2,#0000001a);\n --dtl-border-strong:var(--dsw-alias-border-l3,#0000001f);\n --dtl-border-heavy:var(--dsw-alias-border-l4,#00000029);\n\n /* ── 语义色 ──────────────────────────────────────────────────── */\n /* 品牌「面」色:明色近黑、暗色近白(实心钮选中态即此),不是蓝 */\n --dtl-accent:var(--dsw-alias-brand-primary,#0f1115);\n /* 蓝色强调 / 选中 / 焦点一律走这条 */\n --dtl-business:var(--dsw-alias-state-business-primary,#3b5bdb);\n --dtl-success:var(--dsw-alias-state-success-primary,#22c55e);\n /* ⚠️ 宿主是 state-warn(不是 state-warning,后者无此定义) */\n --dtl-warning:var(--dsw-alias-state-warn-primary,#f59e0b);\n --dtl-danger:var(--dsw-alias-state-error-primary,#ec1313);\n /* 逾期橙:在「积压黄」与「停滞红」之间再取一档(语义阶梯 灰→黄→橙→红),\n 宿主没有现成的橙 token ⇒ 用 warn + error 两色现算,主题一换自动跟随 */\n --dtl-overdue:color-mix(in srgb,var(--dtl-warning) 55%,var(--dtl-danger));\n --dtl-link:var(--dsw-alias-link,#3b5bdb);\n /* 语义色实面上的字(角标 / 实心提示):红绿黄都是中调 ⇒ 白字两个主题都可读 */\n --dtl-on-signal:#fff;\n /* 语义色浅底:color-mix 现算 ⇒ 跟随宿主 alias,主题一换自动成立 */\n --dtl-business-soft:color-mix(in srgb,var(--dtl-business) 14%,transparent);\n /* 他人任务的 doing 色:比「我的」蓝灰一档(用户 2026-10-17:要和我的蓝区分) */\n --dtl-business-dim:color-mix(in srgb,var(--dtl-business) 55%,var(--dtl-fg-3));\n /* 组标题底:左深蓝竖条后面的那层**浅蓝**(用户 2026-10-18 照参考图) */\n --dtl-group-head-bg:#eaf1ff;\n --dtl-success-soft:color-mix(in srgb,var(--dtl-success) 8%,transparent);\n --dtl-warning-soft:color-mix(in srgb,var(--dtl-warning) 8%,transparent);\n --dtl-danger-soft:color-mix(in srgb,var(--dtl-danger) 8%,transparent);\n\n /* ── 交互底 ──────────────────────────────────────────────────── */\n --dtl-hover:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06));\n --dtl-active:var(--dsw-alias-interactive-bg-active,#2631481a);\n /* 卡 / 行 hover 高亮:淡蓝(灰高亮在卡底上几乎看不出来) */\n --dtl-card-hover:rgba(37,99,235,.07);\n /* 展开行底:带透明度的蓝,透出卡片底色(灰底跟斑马纹分不出来) */\n --dtl-open-bg:rgba(37,99,235,.10);\n --dtl-open-bg-soft:rgba(37,99,235,.05);\n /* chip / 标签小底板:浅色下要看得见 ⇒ 掺 7% 近黑;hover 加倍 */\n --dtl-chip-bg:rgba(15,15,15,.07);\n --dtl-chip-bg-hover:rgba(15,15,15,.14);\n /* 图表「新增」柱:**实底**中性灰。半透明底压在卡上会发糊(用户 2026-10-17:「模模糊糊」) */\n --dtl-chart-created:var(--dsw-static-neutral-300,#d4d4d8);\n /* 图表「没完成」柱:醒目琥珀,与蓝(完成)/ 灰(新增)区分(host 无此件,fallback 兜底) */\n --dtl-chart-remaining:var(--dsw-static-amber-400,#f59e0b);\n\n /* ── 圆角(宿主真值 4/8/12/16/20/28)──────────────────────────── */\n --dtl-radius-xs:var(--dsw-radius-xs,4px);\n --dtl-radius-sm:var(--dsw-radius-sm,8px);\n --dtl-radius-md:var(--dsw-radius-md,12px);\n --dtl-radius-lg:var(--dsw-radius-lg,16px);\n --dtl-radius-xl:var(--dsw-radius-xl,20px);\n --dtl-radius-panel:var(--dsw-radius-panel,28px);\n\n /* ── 间距四拍(只许这四档)────────────────────────────────────── */\n --dtl-space-1:4px;\n --dtl-space-2:8px;\n --dtl-space-3:12px;\n --dtl-space-4:16px;\n\n /* ── 控件高度:离散三档(调用点不许自定义高度)────────────────── */\n --dtl-control-h-sm:24px;\n --dtl-control-h-md:28px;\n --dtl-control-h-lg:32px;\n\n /* ── 字号 / 行高(直绑宿主字号族 11/12/13/14/16)──────────────── */\n --dtl-font-xs:var(--dsw-font-xxxs-11-font-size,11px);\n --dtl-font-sm:var(--dsw-font-xxs-12-font-size,12px);\n --dtl-font-md:var(--dsw-font-xs-13-font-size,13px);\n --dtl-font-lg:var(--dsw-font-s-14-font-size,14px);\n --dtl-font-xl:var(--dsw-font-base-16-font-size,16px);\n --dtl-line-xs:var(--dsw-font-xxxs-11-line-height,14px);\n --dtl-line-sm:var(--dsw-font-xxs-12-line-height,18px);\n --dtl-line-md:var(--dsw-font-xs-13-line-height,20px);\n --dtl-line-lg:var(--dsw-font-s-14-line-height,22px);\n --dtl-line-xl:var(--dsw-font-base-16-line-height,24px);\n --dtl-font-mono:var(--ds-font-family-code,monospace);\n /* 统计卡主数字:宿主字号族最大 16px,看板主数字要更大 ⇒ 单开一档(别处不许直接写 px) */\n --dtl-font-stat:26px;\n\n /* ── 投影 / 动效 / 层级 ──────────────────────────────────────── */\n --dtl-shadow-1:var(--dsw-elevation-soft,0 4px 16px 0 #00000008);\n --dtl-shadow-raised:var(--dsw-elevation-soft,0 4px 16px 0 #00000008);\n --dtl-shadow-2:var(--dsw-shadow-lv3,0 12px 32px 0 #00000014);\n --dtl-dur:var(--ds-transition-duration,.2s);\n --dtl-dur-fast:var(--ds-transition-duration-fast,.12s);\n --dtl-ease:var(--ds-ease-in-out,cubic-bezier(.4,0,.2,1));\n --dtl-z-badge:20;\n}\n\n/* ── 明暗差异的唯一落点 ─────────────────────────────────────────\n 只放 alias 表达不了的固定面 / 半透明底;颜色若能靠 alias 自动跟随,一律不写在这里。 */\nbody[data-ds-dark-theme]{\n --dtl-solid:var(--dsw-static-neutral-900,#0f0f0f);\n --dtl-on-solid:var(--dsw-static-neutral-00,#fff);\n --dtl-plate:var(--dsw-static-neutral-850,#212123);\n --dtl-zebra:color-mix(in srgb,var(--dtl-fg) 2.5%,transparent);\n --dtl-plate-hover:var(--dsw-static-neutral-800,#292929);\n /* 暗色表头反过来:比卡片面亮、比斑马纹浅一档 */\n --dtl-head-bg:var(--dtl-surface-2);\n /* 暗色下蓝要更亮、透明度更高才压得住深底 */\n --dtl-open-bg:rgba(96,165,250,.18);\n --dtl-open-bg-soft:rgba(96,165,250,.08);\n --dtl-card-hover:rgba(96,165,250,.13);\n /* 暗色 chip 底板反过来掺白 */\n --dtl-chip-bg:rgba(255,255,255,.08);\n --dtl-chip-bg-hover:rgba(255,255,255,.16);\n --dtl-chart-created:var(--dsw-static-neutral-600,#525252);\n --dtl-chart-remaining:var(--dsw-static-amber-500,#fbbf24);\n /* 语义色浅底在深色下压到 5%(8% 在深底上偏重) */\n --dtl-business-soft:color-mix(in srgb,var(--dtl-business) 12%,transparent);\n --dtl-business-dim:color-mix(in srgb,var(--dtl-business) 55%,var(--dtl-fg-3));\n /* 组标题底(暗):**实色**蓝(参考图不是半透明叠加出来的) */\n --dtl-group-head-bg:#1e3a5f;\n --dtl-success-soft:color-mix(in srgb,var(--dtl-success) 5%,transparent);\n --dtl-warning-soft:color-mix(in srgb,var(--dtl-warning) 5%,transparent);\n --dtl-danger-soft:color-mix(in srgb,var(--dtl-danger) 5%,transparent);\n}\n`\n\n/**\n * 控件皮肤(L2):**一类控件唯一实现**,抄 dispatch 表 controls-css 的规矩——\n * 只消费 var(--dtl-*),一个颜色 / 尺寸 / 圆角 / 字号字面量都不许出现;\n * 高度只取三档,有边框的变体用 1px 真边框 + 内部 padding 补回 ⇒ 有边 / 无边同高。\n */\nexport const UI_CONTROLS_CSS = `\n/* ── 分段控件(滑动块)────────────────────────────────────────────\n 结构一套 + 两个受控轴:size(sm|md|lg) × variant(default|inset)。\n 变体只覆盖两个颜色变量(--seg-track / --seg-thumb),结构 / 尺寸 / 交互零重复。 */\n.dtl-seg{display:inline-flex;align-items:center;gap:2px;padding:2px;border-radius:var(--dtl-radius-md);background:var(--seg-track);}\n.dtl-seg--default{--seg-track:var(--dtl-surface-2);--seg-thumb:var(--dtl-surface-raised);border:1px solid var(--dtl-border);}\n.dtl-seg--inset{--seg-track:var(--dtl-hover);--seg-thumb:var(--dtl-surface-raised);border:0;padding:3px;}\n.dtl-seg__item{appearance:none;display:inline-flex;align-items:center;gap:4px;box-sizing:border-box;\n height:calc(var(--dtl-control-h-sm) - 6px);padding:0 12px;border:0;border-radius:var(--dtl-radius-sm);\n background:transparent;color:var(--dtl-fg-2);font-family:inherit;font-size:var(--dtl-font-sm);line-height:var(--dtl-line-sm);\n font-weight:400;white-space:nowrap;cursor:pointer;\n transition:color var(--dtl-dur-fast) var(--dtl-ease),background-color var(--dtl-dur-fast) var(--dtl-ease),box-shadow var(--dtl-dur-fast) var(--dtl-ease);}\n.dtl-seg--md .dtl-seg__item{height:calc(var(--dtl-control-h-md) - 6px);}\n.dtl-seg--lg .dtl-seg__item{height:calc(var(--dtl-control-h-lg) - 6px);}\n.dtl-seg__item:hover:not([aria-pressed='true']):not(:disabled){color:var(--dtl-fg);}\n.dtl-seg__item[aria-pressed='true']{background:var(--seg-thumb);box-shadow:var(--dtl-shadow-raised);color:var(--dtl-fg);font-weight:600;}\n.dtl-seg__item:focus-visible{outline:2px solid var(--dtl-business);outline-offset:-2px;}\n.dtl-seg__item:disabled{cursor:default;opacity:.4;}\n/* 段内计数(如「待认领 3」):**纯文字,无底、无边、无圆角**(用户 2026-10-18:圆圈取了、字再小点)。\n 类名用 num 而不是旧 badge —— 万一宿主 DOM 里残留旧版 .dtl-seg__badge 圆圈规则,也匹配不中;0 不渲染 */\n.dtl-seg__num{display:inline;flex:none;margin-left:3px;padding:0;border:0;border-radius:0;\n background:none;color:var(--dtl-fg-3);font-size:10px;line-height:1;font-weight:400;font-variant-numeric:tabular-nums;}\n\n/* ── 按钮 / 图标钮 ───────────────────────────────────────────────\n 与 Segmented 同一条高度纪律;variant 只换颜色,结构 / 尺寸 / 交互只此一份。 */\n.dtl-btn{appearance:none;display:inline-flex;align-items:center;justify-content:center;gap:var(--dtl-space-1);\n box-sizing:border-box;border:1px solid transparent;border-radius:var(--dtl-radius-md);\n background:transparent;color:var(--dtl-fg);font-family:inherit;font-weight:500;white-space:nowrap;cursor:pointer;\n transition:background-color var(--dtl-dur-fast) var(--dtl-ease),border-color var(--dtl-dur-fast) var(--dtl-ease),color var(--dtl-dur-fast) var(--dtl-ease),opacity var(--dtl-dur-fast) var(--dtl-ease);}\n.dtl-btn--sm{height:var(--dtl-control-h-sm);padding:0 10px;font-size:var(--dtl-font-sm);line-height:var(--dtl-line-sm);}\n.dtl-btn--md{height:var(--dtl-control-h-md);padding:0 12px;font-size:var(--dtl-font-md);line-height:var(--dtl-line-md);}\n.dtl-btn--lg{height:var(--dtl-control-h-lg);padding:0 14px;font-size:var(--dtl-font-md);line-height:var(--dtl-line-md);}\n.dtl-btn--primary{background:var(--dtl-accent);color:var(--dtl-fg-inverse);}\n.dtl-btn--primary:hover:not(:disabled){opacity:.9;}\n.dtl-btn--outline{background:var(--dtl-surface-1);border-color:var(--dtl-border-strong);color:var(--dtl-fg);}\n.dtl-btn--outline:hover:not(:disabled){background:var(--dtl-hover);}\n.dtl-btn--ghost{color:var(--dtl-fg-2);}\n.dtl-btn--ghost:hover:not(:disabled){background:var(--dtl-hover);color:var(--dtl-fg);}\n.dtl-btn:focus-visible{outline:2px solid var(--dtl-business);outline-offset:2px;}\n.dtl-btn:disabled{cursor:default;opacity:.5;}\n.dtl-iconbtn{appearance:none;display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;\n border:1px solid transparent;border-radius:var(--dtl-radius-sm);background:transparent;color:var(--dtl-fg-2);\n font-family:inherit;cursor:pointer;flex:none;\n transition:background-color var(--dtl-dur-fast) var(--dtl-ease),border-color var(--dtl-dur-fast) var(--dtl-ease),color var(--dtl-dur-fast) var(--dtl-ease),opacity var(--dtl-dur-fast) var(--dtl-ease);}\n.dtl-iconbtn--sm{width:var(--dtl-control-h-sm);height:var(--dtl-control-h-sm);font-size:var(--dtl-font-sm);}\n.dtl-iconbtn--md{width:var(--dtl-control-h-md);height:var(--dtl-control-h-md);font-size:var(--dtl-font-md);}\n.dtl-iconbtn--lg{width:var(--dtl-control-h-lg);height:var(--dtl-control-h-lg);font-size:var(--dtl-font-md);}\n.dtl-iconbtn--outline{background:var(--dtl-surface-1);border-color:var(--dtl-border);color:var(--dtl-fg-2);}\n.dtl-iconbtn:hover:not(:disabled){background:var(--dtl-hover);color:var(--dtl-fg);}\n.dtl-iconbtn:focus-visible{outline:2px solid var(--dtl-business);outline-offset:1px;}\n.dtl-iconbtn:disabled{cursor:default;opacity:.4;}\n\n/* ── 输入 / 搜索框 / 下拉 ──────────────────────────────────────── */\n.dtl-input{box-sizing:border-box;width:100%;border:1px solid var(--dtl-border);border-radius:var(--dtl-radius-md);\n background:var(--dtl-surface-1);color:var(--dtl-fg);font-family:inherit;outline:none;\n transition:border-color var(--dtl-dur-fast) var(--dtl-ease),box-shadow var(--dtl-dur-fast) var(--dtl-ease);}\n.dtl-input--sm{height:var(--dtl-control-h-sm);padding:0 8px;font-size:var(--dtl-font-sm);line-height:var(--dtl-line-sm);}\n.dtl-input--md{height:var(--dtl-control-h-md);padding:0 10px;font-size:var(--dtl-font-md);line-height:var(--dtl-line-md);}\n.dtl-input--lg{height:var(--dtl-control-h-lg);padding:0 12px;font-size:var(--dtl-font-md);line-height:var(--dtl-line-md);}\n.dtl-input::placeholder{color:var(--dtl-fg-dim);}\n.dtl-input:focus{border-color:var(--dtl-business);box-shadow:0 0 0 1px var(--dtl-business);}\n.dtl-input:disabled{cursor:default;opacity:.5;}\n/* 原生 select: appearance:none + 自绘箭头 ⇒ 与输入框同宗(宿主没有廉价下拉件) */\n.dtl-select{appearance:none;-webkit-appearance:none;box-sizing:border-box;\n border:1px solid var(--dtl-border);border-radius:var(--dtl-radius-md);background:var(--dtl-surface-1);color:var(--dtl-fg);\n font-family:inherit;outline:none;cursor:pointer;\n background-image:url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23767d87' stroke-width='1.5'><path d='M4 6.5 8 10.5 12 6.5'/></svg>\");\n background-repeat:no-repeat;background-position:right 8px center;background-size:14px;\n transition:border-color var(--dtl-dur-fast) var(--dtl-ease),box-shadow var(--dtl-dur-fast) var(--dtl-ease);}\n.dtl-select--sm{height:var(--dtl-control-h-sm);padding:0 26px 0 8px;font-size:var(--dtl-font-sm);line-height:var(--dtl-line-sm);}\n.dtl-select--md{height:var(--dtl-control-h-md);padding:0 26px 0 10px;font-size:var(--dtl-font-md);line-height:var(--dtl-line-md);}\n.dtl-select--lg{height:var(--dtl-control-h-lg);padding:0 26px 0 12px;font-size:var(--dtl-font-md);line-height:var(--dtl-line-md);}\n.dtl-select:focus{border-color:var(--dtl-business);box-shadow:0 0 0 1px var(--dtl-business);}\n.dtl-select:disabled{cursor:default;opacity:.5;}\n\n/* ── 卡 / chip / 空态 / 列表行(业务壳,皮肤仍只消费 token)────── */\n.dtl-card{background:var(--dtl-surface-1);border:1px solid var(--dtl-border);border-radius:var(--dtl-radius-md);\n transition:background-color var(--dtl-dur-fast) var(--dtl-ease);}\n.dtl-card--clickable{cursor:pointer;text-align:left;font:inherit;color:inherit;width:100%;}\n.dtl-card--clickable:hover{background:var(--dtl-card-hover);}\n.dtl-card--clickable[data-active='true']{border-color:var(--dtl-business);}\n.dtl-card--clickable:focus-visible{outline:2px solid var(--dtl-business);outline-offset:1px;}\n.dtl-chip{display:inline-flex;align-items:center;gap:4px;height:20px;padding:0 8px;border-radius:var(--dtl-radius-sm);\n background:var(--dtl-chip-bg);color:var(--dtl-fg-2);font-size:var(--dtl-font-xs);line-height:var(--dtl-line-xs);white-space:nowrap;}\n.dtl-row{display:flex;align-items:center;gap:var(--dtl-space-2);width:100%;box-sizing:border-box;\n padding:var(--dtl-space-2) var(--dtl-space-3);border:0;border-bottom:1px solid var(--dtl-border-faint);\n background:transparent;font:inherit;color:inherit;text-align:left;cursor:pointer;\n transition:background-color var(--dtl-dur-fast) var(--dtl-ease);}\n.dtl-row:hover{background:var(--dtl-card-hover);}\n.dtl-row:focus-visible{outline:2px solid var(--dtl-business);outline-offset:-2px;}\n.dtl-row[data-open='true']{background:var(--dtl-open-bg);}\n.dtl-row-body{padding:var(--dtl-space-2) var(--dtl-space-3) var(--dtl-space-3);border-bottom:1px solid var(--dtl-border-faint);\n background:var(--dtl-open-bg-soft);font-size:var(--dtl-font-md);}\n.dtl-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}\n\n@media (prefers-reduced-motion: reduce){\n .dtl-seg__item,.dtl-btn,.dtl-iconbtn,.dtl-input,.dtl-select,.dtl-card,.dtl-row{transition:none;}\n}\n`\n","/**\n * UI 基础层:样式单源 + 共用元件(**一类控件只有一份实现**)。\n *\n * 纪律(用户 2026-10-17「样式与方法一律抽象,不要反复重写」;形态抄本机\n * `dsh-task-dispatch-table` 的 `ui/tokens.ts` + `ui/controls-css.ts` + `ui/*.tsx`):\n * 1. **样式**:颜色 / 圆角 / 字号 / 高度一律 `var(--dtl-*)`(定义只在 tokens.ts),\n * 业务文件不许再写第二份 `var(--dsw-*)`、不许自造 `--dtl-*`、不许自造高度档;\n * 2. **元件**:分段 / 按钮 / 图标钮 / 输入 / 下拉 / 卡 / chip / 空态 / 图表各一份,\n * 页面与浮标共用。官方 primitives 只留「官方做得更好」的(StateDot / MarkdownText / 图标)。\n */\nimport { createElement as h, useState, type ChangeEvent, type CSSProperties, type ReactNode } from 'react'\nimport { Tooltip } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { UI_CONTROLS_CSS, UI_TOKENS_CSS } from './tokens'\n\n/** 基础层样式(tokens + 控件皮肤;幂等注入)。 */\nconst UI_BASE_CSS = `\n/* 统计卡:可点、hover 淡蓝、选中蓝描边 */\n.dtl-stat{display:flex;flex-direction:column;gap:var(--dtl-space-1);\n padding:var(--dtl-space-3) var(--dtl-space-4);text-align:left;}\n.dtl-stat__value{font-size:var(--dtl-font-stat);font-weight:600;line-height:1.15;font-variant-numeric:tabular-nums;}\n.dtl-stat__hint{font-size:var(--dtl-font-xs);line-height:var(--dtl-line-xs);color:var(--dtl-fg-3);}\n\n/* 列表行:分组标题头仍走 .dtl-row-head(可点折叠);任务行走 .dtl-taskrow(两行式,不可点) */\n.dtl-row-head{display:flex;align-items:center;gap:var(--dtl-space-2);\n width:100%;padding:var(--dtl-space-2) var(--dtl-space-3);border:0;background:transparent;\n color:inherit;font:inherit;text-align:left;cursor:pointer;\n transition:background-color var(--dtl-dur-fast) var(--dtl-ease);}\n.dtl-row-head:hover{background:var(--dtl-card-hover);}\n.dtl-row-head:focus-visible{outline:2px solid var(--dtl-business);outline-offset:-2px;}\n\n/* 其他待办 = **一组一张表**(用户 2026-10-18 照参考图 Stripe Payments:Pending / Archived /\n Completed 各一组):组标题是**独立一行**——透明无底、远离上一组、贴近自己的表,\n 文字加粗放大;表体照旧(行下一条线 + 极浅斑马纹)。 */\n/* 组与组**零外距**、连体(用户 2026-10-18:上间距去掉,和上面连在一起) */\n.dtl-group{margin-bottom:0;}\n/* 组标题(照参考图):**左侧深蓝竖条 + 浅蓝底(实色)**;行高保持(≈42px);\n 工作区名稍小一号(14px);上缘无边 */\n.dtl-group__head{position:relative;display:flex;align-items:center;gap:8px;width:100%;box-sizing:border-box;\n padding:10px var(--dtl-space-3);border:0;border-left:5px solid var(--dtl-business);\n border-radius:0;background:var(--dtl-group-head-bg);\n color:inherit;font:inherit;text-align:left;cursor:pointer;}\n/* 下缘那条线**绝不用 border 画**:border-left(5px) 与 border-bottom(1px) 在左下角\n 按 miter 斜接,会切出一个斜缺角(用户 2026-10-18 实锤,不是视觉误差)⇒ 改用伪元素\n 独立 1px;定位基于 padding box ⇒ 自动从竖条内缘起笔,竖条保持整直。 */\n.dtl-group__head::after{content:'';position:absolute;left:0;right:0;bottom:0;height:1px;\n background:var(--dtl-border-faint);pointer-events:none;}\n.dtl-group__name{font-size:var(--dtl-font-lg);font-weight:700;line-height:var(--dtl-line-lg);color:var(--dtl-fg);}\n.dtl-group__stats{font-size:var(--dtl-font-xs);color:var(--dtl-fg-2);white-space:nowrap;}\n/* 组标题标识:官方文件夹图标(展开=开、收起=合);图标缺席时回落成竖线 */\n.dtl-group__bar{width:3px;height:14px;flex:none;border-radius:2px;background:var(--dtl-business);}\n/* 表格行(.dtl-orow):**零重用**「我的待办」卡片基类——无框、无圆角、无外边距,\n 一行 = 一条底部分隔线 + 极浅斑马纹(用户 2026-10-18:一行一格,不是一块一块的框) */\n.dtl-orow{padding:13px var(--dtl-space-3);border-bottom:1px solid var(--dtl-border-faint);}\n/* 斑马纹落在 2、4、6 行(用户 2026-10-18:原来 1、3、5 看着像从第一行就开始断层) */\n.dtl-orow:nth-of-type(even){background:var(--dtl-zebra);}\n.dtl-orow:hover{background:var(--dtl-card-hover);}\n.dtl-orow__row{display:flex;align-items:center;gap:12px;}\n.dtl-orow__title{display:flex;align-items:baseline;gap:6px;min-width:0;}\n.dtl-orow__name{font-size:var(--dtl-font-md);font-weight:500;line-height:var(--dtl-line-md);min-width:0;}\n/* 第二行:状态标签 + #标签(与标题行间距拉开,用户 2026-10-18;状态位与执行人**互换**后,\n 状态搬到这里、且永远有 ⇒ 这一行不再有「空一排」的情况) */\n.dtl-orow__tags{display:flex;align-items:center;gap:var(--dtl-space-2);margin-top:7px;min-width:0;}\n/* 列:任务(弹性)/ 执行人 / 右侧槽(创建信息 ⇄ 操作钮) */\n.dtl-tcell{min-width:0;}\n.dtl-tcell--title{flex:1 1 auto;}\n/* 执行人列(原状态列,用户 2026-10-18 互换):稍放宽一档(用户 2026-10-19:原来有点挤),\n 20 字的名字能基本摆全;再长由标签自己的省略号兜住,不许溢出压到右边的槽 */\n.dtl-tcell--status{flex:none;width:136px;}\n.dtl-tcell--status .dtl-tag{max-width:100%;}\n/* 悬停:**创建信息整条让位给操作钮**(用户 2026-10-18:不要「掉掉时分秒」那种半吊子,\n 要整条换掉)。两个都**常驻布局**(visibility 切换、不用 display),格子宽度 = 两者较宽的\n 那个 ⇒ hover 时行宽不变、**页面不跳**(跳了执行人的悬停提示就跟着飘) */\n.dtl-orow__slot{display:grid;align-items:center;justify-items:end;flex:none;\n font-size:var(--dtl-font-xs);color:var(--dtl-fg-3);white-space:nowrap;font-variant-numeric:tabular-nums;}\n.dtl-orow__slot>*{grid-area:1/1;}\n.dtl-orow__created{visibility:visible;}\n.dtl-orow__actions{visibility:hidden;display:flex;align-items:center;gap:6px;}\n.dtl-orow:hover .dtl-orow__created{visibility:hidden;}\n.dtl-orow:hover .dtl-orow__actions{visibility:visible;}\n/* 「查看」钮在表格行里也要常驻布局(display:none 会不算宽度 ⇒ hover 一出就又把行撑开) */\n.dtl-orow .dtl-viewbtn{display:inline-flex;visibility:hidden;}\n.dtl-orow:hover .dtl-viewbtn{visibility:visible;}\n/* 行首状态圆点:灰=待认领 / 浅蓝=进行中(比「我的」蓝灰一档)/ 黄=积压 / 橙=逾期 / 红=停滞;\n 6px(用户 2026-10-18:8px 太大) */\n.dtl-dot{display:inline-block;width:6px;height:6px;flex:none;border-radius:50%;}\n.dtl-dot--doing{background:var(--dtl-business-dim);}\n.dtl-dot--backlog{background:var(--dtl-warning);}\n.dtl-dot--overdue{background:var(--dtl-overdue);}\n.dtl-dot--stale{background:var(--dtl-danger);}\n.dtl-dot--open,.dtl-dot--closed{background:var(--dtl-fg-3);}\n\n/* 「查看」钮:与行内动作钮(.dtl-minibtn)同款皮肤、**正方形(26×26,宽=高)**、\n **默认不显、悬停才出来**(用户 2026-10-18:跟认领/继续那几个一起浮现) */\n.dtl-viewbtn{position:relative;display:none;align-items:center;justify-content:center;gap:5px;\n flex:none;width:26px;height:26px;padding:0;border:1px solid var(--dtl-border);\n border-radius:var(--dtl-radius-sm);background:var(--dtl-surface-1);color:var(--dtl-fg-2);\n font:inherit;font-size:var(--dtl-font-sm);line-height:18px;white-space:nowrap;cursor:default;}\n.dtl-viewbtn:hover{border-color:var(--dtl-border-strong);color:var(--dtl-fg);}\n.dtl-taskrow__row:hover .dtl-viewbtn,.dtl-orow:hover .dtl-viewbtn{display:inline-flex;}\n\n/* 任务行 = 一张卡(样式照抄 dispatch-table TaskCard:surface-1 底 + 1px 边框 + 左缘状态条;\n hover 只亮主行;右侧 时间⇄按钮排 互换) */\n.dtl-taskrow{background:var(--dtl-surface-1);border:1px solid var(--dtl-border);\n border-radius:var(--dtl-radius-sm);margin-bottom:10px;}\n.dtl-taskrow__row{display:flex;align-items:center;gap:12px;padding:12px 14px;border-radius:inherit;\n transition:background-color var(--dtl-dur-fast) var(--dtl-ease);}\n.dtl-taskrow__row:hover{background:var(--dtl-card-hover);}\n\n/* 状态条:贴卡片左缘、与正文两行等高的竖向色条(dispatch StatusRail 同款 6×36) */\n.dtl-rail{display:inline-block;width:6px;height:36px;flex:none;border-radius:var(--dtl-radius-xs);}\n.dtl-rail--doing{background:var(--dtl-business);}\n.dtl-rail--backlog{background:var(--dtl-warning);}\n.dtl-rail--overdue{background:var(--dtl-overdue);}\n.dtl-rail--stale{background:var(--dtl-danger);}\n.dtl-rail--open,.dtl-rail--closed{background:var(--dtl-fg-3);}\n\n/* 第一行:标题(上来就是标题)+ [YYYY-MM-DD 创建] 灰徽 + 异常 pill(baseline 对齐,dispatch 同款) */\n.dtl-taskrow__l1{display:flex;align-items:baseline;gap:6px;min-width:0;}\n.dtl-taskrow__title{font-size:var(--dtl-font-lg);font-weight:600;line-height:var(--dtl-line-md);min-width:0;flex:0 1 auto;}\n.dtl-taskrow__stamp{flex:none;font-size:var(--dtl-font-xs);color:var(--dtl-fg-3);white-space:nowrap;\n font-variant-numeric:tabular-nums;}\n/* 第二行小字:工作区 · #标签(行距拉开,用户 2026-10-17:第一二行别贴着) */\n.dtl-taskrow__l2{display:flex;align-items:center;gap:var(--dtl-space-2);margin-top:6px;\n font-size:var(--dtl-font-sm);color:var(--dtl-fg-2);min-width:0;}\n/* 右侧灰字:2026-10-17 开始丨已进行 12 天(用户 2026-10-17:不要蓝,一行灰字) */\n.dtl-taskrow__when{flex:none;font-size:var(--dtl-font-xs);color:var(--dtl-fg-3);white-space:nowrap;\n font-variant-numeric:tabular-nums;}\n.dtl-taskrow__actions{display:none;flex:none;gap:6px;}\n.dtl-taskrow__row:hover .dtl-taskrow__when{display:none;}\n.dtl-taskrow__row:hover .dtl-taskrow__actions{display:inline-flex;}\n\n/* 标签(用户 2026-10-18):状态 / 执行人 / #标签 **共用一枚件**,只换色调。\n 最大宽度 20ch(≈10 个中文或 20 个字符)⇒ 名字上限 20 字;超出**省略号(3 个点)**,\n 悬停把全名补出来(用户 2026-10-19:不跑马灯了)。**没被截 ⇒ 悬停无反应**\n (提示由 Tag 元件按实际宽度决定挂不挂,这里只管裁切)。 */\n.dtl-tag{display:inline-block;flex:none;max-width:20ch;padding:0 6px;border-radius:999px;\n background:var(--dtl-chip-bg);color:var(--dtl-fg-3);font-size:10px;line-height:16px;\n white-space:nowrap;overflow:hidden;text-overflow:ellipsis;vertical-align:middle;}\n/* 状态四色 = **实心色块**(用户 2026-10-18:彩色字看不清 ⇒ 改色块 + 反色字):\n 待认领灰 / 进行中蓝 / 积压黄 / 逾期橙 / 停滞红(与行首圆点同色系)。\n 红黄蓝都是中调 ⇒ 白字两个主题都可读;灰块用「字色底 + 面色字」⇒ 明暗自动反过来 */\n.dtl-tag--open{background:var(--dtl-fg-3);color:var(--dtl-surface-1);font-weight:600;}\n.dtl-tag--doing{background:var(--dtl-business);color:var(--dtl-on-signal);font-weight:600;}\n.dtl-tag--backlog{background:var(--dtl-warning);color:var(--dtl-on-signal);font-weight:600;}\n.dtl-tag--overdue{background:var(--dtl-overdue);color:var(--dtl-on-signal);font-weight:600;}\n.dtl-tag--stale{background:var(--dtl-danger);color:var(--dtl-on-signal);font-weight:600;}\n/* 执行人:浅蓝底(与状态色区分:状态是「事怎么样」,人是「谁在做」) */\n.dtl-tag--person{background:var(--dtl-open-bg-soft);color:var(--dtl-fg-2);}\n/* 分区标题里的计数:与日期徽同款中括号小字(用户 2026-10-17);0 条不渲染 */\n.dtl-count{font-size:var(--dtl-font-xs);color:var(--dtl-fg-3);font-variant-numeric:tabular-nums;}\n\n/* 工作区徽标:只铺蓝底(用户 2026-10-17:描边框太抢 ⇒ 去掉;底色已提亮一档见 tokens.ts\n business-soft 8%→14% / 5%→12%),字色回正文。 */\n.dtl-wstag{flex:none;max-width:220px;padding:1px 8px;border-radius:var(--dtl-radius-sm);\n background:var(--dtl-business-soft);color:var(--dtl-fg);\n font-size:var(--dtl-font-xs);line-height:16px;white-space:nowrap;}\n\n/* 行内动作按钮(icon + 字,加大;悬停说明走官方 Tooltip,不在 CSS 里手搓浮层)。\n 样子先行,点击行为归「任务按钮与会话交接」任务 */\n.dtl-minibtn{display:inline-flex;align-items:center;gap:5px;padding:4px 12px;\n border:1px solid var(--dtl-border);border-radius:var(--dtl-radius-sm);\n background:var(--dtl-surface-1);color:var(--dtl-fg-2);font:inherit;font-size:var(--dtl-font-sm);\n line-height:18px;white-space:nowrap;cursor:default;}\n.dtl-minibtn:hover{border-color:var(--dtl-border-strong);color:var(--dtl-fg);}\n\n/* 空态:真空 = 虚线引导卡(用户 2026-10-18:**加高到 180px + 内容居中**;\n 它贴在滚动条最底下,太窄用户根本看不见) */\n.dtl-empty-guide{display:flex;flex-direction:column;align-items:center;justify-content:center;\n gap:var(--dtl-space-2);min-height:270px;padding:var(--dtl-space-4);text-align:center;\n border:1px dashed var(--dtl-border-strong);border-radius:var(--dtl-radius-md);background:transparent;}\n.dtl-empty-filtered{padding:var(--dtl-space-4) 0;color:var(--dtl-fg-3);font-size:var(--dtl-font-md)}\n\n/* 浮标角标:0→n 跳变时一次脉冲,不自动消失 */\n.dtl-badge{position:absolute;top:-2px;right:-2px;min-width:16px;height:16px;padding:0 4px;\n border-radius:8px;background:var(--dtl-danger);color:var(--dtl-on-signal);\n font-size:10px;line-height:16px;text-align:center;font-weight:600;}\n.dtl-badge[data-pulse=\"true\"]{animation:dtl-pulse 900ms var(--dtl-ease) 1}\n@keyframes dtl-pulse{0%{transform:scale(.6);opacity:.4}60%{transform:scale(1.15)}100%{transform:scale(1)}}\n\n/* 统计区整体:左 8 块(2 行 × 4 列)+ 右趋势图,参考图同款分栏。\n ⚠️ 列数写死 4 → 2,绝不给 3:3 列会排出 3+1 的非对称(用户 2026-10-17)。\n 两栏**完全等高**:cards 两行 minmax(0,1fr) 均分 + 卡 height:100% ⇒ 图表卡与左边一样高。 */\n/* 左 8 块略收窄、右图表略放宽(用户 2026-10-17:左再短一丢丢、右长一丢丢) */\n.dtl-stats{display:grid;grid-template-columns:minmax(0,1.7fr) minmax(0,1fr);gap:var(--dtl-space-3);align-items:stretch;}\n.dtl-stats__cards{display:grid;grid-auto-flow:column;grid-template-columns:repeat(4,minmax(0,1fr));grid-template-rows:repeat(2,minmax(0,1fr));\n gap:var(--dtl-space-3);}\n.dtl-stats__cards > .dtl-stat{height:100%;box-sizing:border-box;}\n@media (max-width:960px){.dtl-stats{grid-template-columns:minmax(0,1fr);}}\n@media (max-width:640px){.dtl-stats__cards{grid-template-columns:repeat(2,minmax(0,1fr));grid-template-rows:repeat(4,minmax(0,1fr));}}\n\n/* 「只看我的」小复选框(替掉右上角的分段切换) */\n.dtl-check{display:inline-flex;align-items:center;gap:5px;height:20px;margin-top:-2px;\n font-size:var(--dtl-font-xs);color:var(--dtl-fg-3);cursor:pointer;user-select:none;white-space:nowrap;}\n.dtl-check input{width:12px;height:12px;margin:0;accent-color:var(--dtl-business);cursor:pointer;}\n.dtl-check:hover{color:var(--dtl-fg-2);}\n\n/* 趋势分组柱状图:绘图区高度**随卡拉伸**(flex),柱 / 刻度 / 网格全用百分比 ⇒ 与卡等高 */\n.dtl-gchart-wrap{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;padding-left:30px;padding-bottom:18px;}\n.dtl-gchart{position:relative;flex:1 1 auto;min-height:96px;border-bottom:1px solid var(--dtl-border);}\n.dtl-gchart__grid{position:absolute;left:0;right:0;border-top:1px solid var(--dtl-border-faint);pointer-events:none;}\n.dtl-gchart__ytick{position:absolute;left:-30px;width:26px;text-align:right;font-size:10px;line-height:12px;\n color:var(--dtl-fg-3);transform:translateY(50%);font-variant-numeric:tabular-nums;}\n.dtl-gchart__col{position:absolute;top:0;bottom:0;min-width:0;}\n/* ⚠️ pair 必须 top/bottom 双锚(或 height:100%):绝对定位 + 只给 bottom 时高度是 auto,\n 子元素的 height:% 没有可解析的包含块 ⇒ 整根柱子塌成 0(2026-10-17 真机教训:\n 改百分比柱高后「一个柱子都看不见,只剩数字」)。 */\n.dtl-gchart__pair{position:absolute;top:0;bottom:0;left:50%;transform:translateX(-50%);\n display:flex;align-items:flex-end;gap:1px;}\n/* 三柱:完成(蓝) / 没完成(琥珀) / 新增(灰),每根 5px、无圆角(用户 2026-10-17:太粗、不要圆弧) */\n.dtl-gchart__bar{width:5px;min-height:0;}\n.dtl-gchart__bar--done{background:var(--dtl-business);}\n.dtl-gchart__bar--remaining{background:var(--dtl-chart-remaining);}\n.dtl-gchart__bar--created{background:var(--dtl-chart-created);}\n/* hover:整列三根柱一起提亮(用户 2026-10-17:移上去应该两个都变色,不是只蓝的一根) */\n.dtl-gchart__col:hover .dtl-gchart__bar{filter:brightness(1.18);}\n.dtl-gchart__val{position:absolute;left:50%;transform:translateX(-50%);font-size:10px;line-height:12px;\n color:var(--dtl-fg-2);font-variant-numeric:tabular-nums;white-space:nowrap;}\n.dtl-gchart__label{position:absolute;left:0;right:0;bottom:-16px;text-align:center;\n font-size:10px;line-height:14px;color:var(--dtl-fg-3);white-space:nowrap;}\n/* hover 浮卡(参考图同款:日期 + 两个数;原生 title 太慢、长相不一) */\n.dtl-gchart__tip{position:absolute;top:0;z-index:2;min-width:104px;box-sizing:border-box;\n padding:6px 8px;border:1px solid var(--dtl-border);border-radius:var(--dtl-radius-sm);\n background:var(--dtl-surface-1);box-shadow:var(--dtl-shadow-2);\n font-size:var(--dtl-font-xs);line-height:var(--dtl-line-sm);color:var(--dtl-fg-2);pointer-events:none;}\n.dtl-gchart__tip b{display:block;color:var(--dtl-fg);font-weight:600;}\n.dtl-gchart__tip i{display:inline-block;width:8px;height:8px;border-radius:2px;margin-right:4px;}\n.dtl-gchart-legend{display:flex;align-items:center;gap:var(--dtl-space-3);font-size:var(--dtl-font-xs);color:var(--dtl-fg-2);}\n.dtl-gchart-legend i{display:inline-block;width:8px;height:8px;border-radius:2px;margin-right:4px;}\n\n/* 过程正文:宽度受限、长行折行 */\n.dtl-worklog{max-width:72ch;font-size:var(--dtl-font-md);line-height:1.6}\n`\n\nconst STYLE_ID = 'dtl-ui-base'\n\n/** 幂等注入 token 层 + 控件皮肤 + 基础层(面板与浮标谁先挂载都只注入一次)。\n * 已挂载时**就地比对刷新**:插件升级后宿主若复用旧面板 DOM,旧样式表会一直挂着,\n * 导致新版本样式不生效(用户 2026-10-18:0.0.31 装上了圆圈还在)—— 内容变了就原位替换,不必重启。 */\nexport function ensureUiBase(): void {\n if (typeof document === 'undefined') return\n const css = `${UI_TOKENS_CSS}\\n${UI_CONTROLS_CSS}\\n${UI_BASE_CSS}`\n const existing = document.getElementById(STYLE_ID)\n if (existing !== null) {\n if (existing.textContent !== css) existing.textContent = css\n return\n }\n const style = document.createElement('style')\n style.id = STYLE_ID\n style.textContent = css\n document.head.appendChild(style)\n}\n\n/* ── 分段控件 ─────────────────────────────────────────────────────── */\n\n/** 一段(一个可选值)。 */\nexport interface SegmentedItem<Value extends string = string> {\n readonly value: Value\n readonly label: string\n /** 段内角标数字;`undefined` 或 ≤0 不显示。 */\n readonly badge?: number\n}\n\n/** 分段控件(单选):同一份结构 + size 轴;外观差异只由 variant 的两个颜色变量表达。 */\nexport function Segmented<Value extends string>(props: {\n items: readonly SegmentedItem<Value>[]\n value: Value\n onChange: (next: Value) => void\n size?: 'sm' | 'md' | 'lg'\n variant?: 'default' | 'inset'\n label: string\n id?: string\n style?: CSSProperties\n}): ReactNode {\n ensureUiBase()\n const size = props.size ?? 'md'\n const variant = props.variant ?? 'default'\n return h('div', {\n role: 'group',\n 'aria-label': props.label,\n id: props.id,\n className: `dtl-seg dtl-seg--${variant} dtl-seg--${size}`,\n style: props.style,\n }, props.items.map(item => h('button', {\n key: item.value,\n type: 'button',\n className: 'dtl-seg__item',\n 'aria-pressed': item.value === props.value,\n onClick: () => { props.onChange(item.value) },\n }, item.label, item.badge !== undefined && item.badge > 0\n ? h('span', { className: 'dtl-seg__num' }, String(item.badge))\n : null)))\n}\n\n/* ── 按钮 / 图标钮 ────────────────────────────────────────────────── */\n\nexport function Button(props: {\n children?: ReactNode\n onClick: () => void\n variant?: 'primary' | 'outline' | 'ghost'\n size?: 'sm' | 'md' | 'lg'\n icon?: ReactNode\n disabled?: boolean\n title?: string\n}): ReactNode {\n ensureUiBase()\n return h('button', {\n type: 'button',\n className: `dtl-btn dtl-btn--${props.variant ?? 'outline'} dtl-btn--${props.size ?? 'md'}`,\n disabled: props.disabled === true,\n title: props.title,\n onClick: props.onClick,\n }, props.icon, props.children)\n}\n\nexport function IconButton(props: {\n icon: ReactNode\n label: string\n onClick: () => void\n variant?: 'plain' | 'outline'\n size?: 'sm' | 'md' | 'lg'\n disabled?: boolean\n title?: string\n}): ReactNode {\n ensureUiBase()\n return h('button', {\n type: 'button',\n className: `dtl-iconbtn dtl-iconbtn--${props.variant ?? 'plain'} dtl-iconbtn--${props.size ?? 'md'}`,\n 'aria-label': props.label,\n disabled: props.disabled === true,\n title: props.title ?? props.label,\n onClick: props.onClick,\n }, props.icon)\n}\n\n/* ── 输入 / 下拉 / 搜索 ───────────────────────────────────────────── */\n\n/** 原生下拉(工作区 / 状态共用一份;宿主没有廉价下拉件 ⇒ 原生 + token 皮肤)。\n * `width` 定宽(px);只给 `minWidth` = 内容自适应但有下限(工作区名长短不一时用)。 */\nexport function Select<Value extends string>(props: {\n value: Value\n options: readonly { value: Value; label: string }[]\n onChange: (next: Value) => void\n ariaLabel: string\n /** 定宽(px);不给就按内容。 */\n width?: number\n /** 最小宽度(px);内容自适应时的下限。 */\n minWidth?: number\n size?: 'sm' | 'md' | 'lg'\n}): ReactNode {\n ensureUiBase()\n const size = props.size ?? 'md'\n const style: CSSProperties | undefined = props.width === undefined\n ? (props.minWidth === undefined ? undefined : { minWidth: `${props.minWidth}px` })\n : { width: `${props.width}px` }\n return h('select', {\n className: `dtl-select dtl-select--${size}`,\n 'aria-label': props.ariaLabel,\n style,\n value: props.value,\n onChange: (event: ChangeEvent<HTMLSelectElement>) => { props.onChange(event.target.value as Value) },\n }, props.options.map(option => h('option', { key: option.value, value: option.value }, option.label)))\n}\n\n/** 搜索框(关键字过滤;占满剩余宽度由调用方的 flex 决定)。 */\nexport function SearchInput(props: {\n value: string\n placeholder: string\n onChange: (next: string) => void\n ariaLabel: string\n size?: 'sm' | 'md' | 'lg'\n}): ReactNode {\n ensureUiBase()\n const size = props.size ?? 'md'\n return h('input', {\n className: `dtl-input dtl-input--${size}`,\n type: 'search',\n 'aria-label': props.ariaLabel,\n placeholder: props.placeholder,\n value: props.value,\n spellCheck: false,\n onChange: (event: ChangeEvent<HTMLInputElement>) => { props.onChange(event.target.value) },\n })\n}\n\n/* ── 卡 / chip / 空态 ─────────────────────────────────────────────── */\n\n/** 统计卡:主数字 + 灰色口径小字;点一下联动下方列表筛选。\n * `value` 为 0 时显示「—」(用户 2026-10-17 定稿:0 会被读成「没数据 / 出错了」);\n * `hint` 可以是富文本(如把「停滞」两字标红);\n * `interactive: false` = 纯展示卡(统计台:首页没有已结案可筛,不装可点)。 */\nexport function StatCard(props: {\n label: string\n value: string | number\n hint?: ReactNode\n active?: boolean\n interactive?: boolean\n title?: string\n onClick?: () => void\n}): ReactNode {\n ensureUiBase()\n const interactive = props.interactive !== false && props.onClick !== undefined\n const shown = props.value === 0 || props.value === '0' ? '—' : String(props.value)\n return h(interactive ? 'button' : 'div', {\n type: interactive ? 'button' : undefined,\n className: `dtl-card dtl-stat${interactive ? ' dtl-card--clickable' : ''}`,\n 'data-active': props.active === true,\n title: props.title,\n onClick: interactive ? props.onClick : undefined,\n },\n h('span', { style: { fontSize: 'var(--dtl-font-sm)', color: 'var(--dtl-fg-2)' } }, props.label),\n h('span', { className: 'dtl-stat__value' }, shown),\n props.hint === undefined || props.hint === null || props.hint === ''\n ? null\n : h('span', { className: 'dtl-stat__hint' }, props.hint),\n )\n}\n\n/** chip(分类 / 标签 / 工作区名)。 */\nexport function Chip(props: { text: string; color?: string }): ReactNode {\n ensureUiBase()\n return h('span', {\n className: 'dtl-chip',\n style: props.color === undefined ? undefined : { background: props.color, color: 'var(--dtl-on-signal)' },\n title: props.text,\n }, props.text)\n}\n\n/** 标签上限宽度(字符数):1 个中文按 2 计 ⇒ 10 个中文 = 20 个字符(与 CSS 的 20ch 对齐)。 */\nconst TAG_MAX_WIDTH = 20\n\n/** 显示宽度:全角(中 / 日 / 韩 / 全角符号)一个顶两个英文字符。 */\nfunction displayWidth(text: string): number {\n let width = 0\n for (const char of text) width += (char.codePointAt(0) ?? 0) > 0x2e80 ? 2 : 1\n return width\n}\n\n/**\n * 标签(**唯一**一枚件,用户 2026-10-18):状态 / 执行人 / #标签 都走它,只换 tone。\n * 超过 20 个字符宽 ⇒ 省略号,**且只在被截时**挂悬停提示补出全文(用户 2026-10-19:\n * 不跑马灯;没被截 ⇒ 悬停无反应)。\n */\nexport function Tag(props: {\n text: string\n tone?: 'neutral' | 'open' | 'backlog' | 'doing' | 'overdue' | 'stale' | 'person'\n /** 悬停提示(**被截时**才显示;默认 = 文本本身)。 */\n title?: string\n /** 悬停提示(**始终**显示,状态标记的解释走它;优先于 title)。走官方 Tooltip。 */\n tip?: string\n}): ReactNode {\n ensureUiBase()\n const { text, tone = 'neutral', title, tip } = props\n const long = displayWidth(text) > TAG_MAX_WIDTH\n const label = tip ?? (long ? (title ?? text) : undefined)\n const tag = h('span', { className: `dtl-tag dtl-tag--${tone}` }, text)\n // 官方 Tooltip(悬停即出、支持 \\n 多行、portal 逃逸祖先 overflow 裁切)\n if (label === undefined) return tag\n return h(Tooltip, { label, side: 'bottom', portal: true, maxWidth: 240, children: tag })\n}\n\n/** 空态:`guide` = 真空引导卡(虚线),`filtered` = 筛选空小字。 */\nexport function EmptyState(props: { tone: 'guide' | 'filtered'; title: string; hint?: string }): ReactNode {\n ensureUiBase()\n if (props.tone === 'filtered') return h('div', { className: 'dtl-empty-filtered' }, props.title)\n return h('div', { className: 'dtl-empty-guide' },\n h('div', { style: { fontSize: 'var(--dtl-font-lg)', fontWeight: 600, marginBottom: 'var(--dtl-space-1)' } }, props.title),\n props.hint === undefined ? null : h('div', { style: { fontSize: 'var(--dtl-font-md)', color: 'var(--dtl-fg-2)' } }, props.hint),\n )\n}\n\n/** 台账图标(黑白线条,描边走 currentColor ⇒ 随主题 / 选中态反色)。\n * 侧栏条目与右上浮标共用这一份(图标只画一次,别处不许再画第二个)。 */\nexport function LedgerIcon(props: { size?: number }): ReactNode {\n const size = props.size ?? 18\n const stroke = { fill: 'none', stroke: 'currentColor', strokeWidth: 7, strokeLinecap: 'round', strokeLinejoin: 'round' } as const\n return h('svg', { width: size, height: size, viewBox: '0 0 128 128', 'aria-hidden': true },\n h('path', { d: 'M96 34 A10 10 0 0 1 106 44 V102 A10 10 0 0 1 96 112 H32 A10 10 0 0 1 22 102 V44 A10 10 0 0 1 32 34 Z', ...stroke }),\n h('path', { d: 'M50 22 V46 M78 22 V46', ...stroke }),\n h('path', { d: 'M44 66 H84 M44 86 H70', ...stroke }),\n )\n}\n\n/* ── 趋势分组柱状图 ───────────────────────────────────────────────────── */\n\n/** 一周三根柱(完成 / 没完成 / 新增)。 */\nexport interface GroupedBarWeek {\n /** 轴标签(MM-DD)。 */\n readonly label: string\n readonly done: number\n readonly remaining: number\n readonly created: number\n /** 悬停文案。 */\n readonly title: string\n}\n\n/** 千位压缩(1.2k / 12k):y 轴刻度与柱顶数值用,避免 4 位数把刻度挤宽。 */\nfunction compactCount(value: number): string {\n if (value >= 10_000) return `${Math.round(value / 1000)}k`\n if (value >= 1000) return `${(value / 1000).toFixed(1)}k`\n return String(value)\n}\n\n/** 分组柱状图(div 柱,无新依赖):完成(蓝)/ 没完成(琥珀)/ 新增(灰)各一细柱。\n * - 高度全用百分比 ⇒ 绘图区随卡片拉伸,图表卡与左边 8 块**完全等高**;\n * - y 轴三档刻度 + 淡网格线;柱顶只标「完成」(3 系列 × 8 周数值会糊成一片);\n * - hover 整列提亮 + 浮卡(日期 + 三个数)——原生 title 太慢;\n * - **没有**均值线与长注释(用户 2026-10-17:均值不要、注释太长不要)。 */\nexport function GroupedBarChart(props: {\n weeks: readonly GroupedBarWeek[]\n ariaLabel: string\n doneLabel: string\n remainingLabel: string\n createdLabel: string\n}): ReactNode {\n ensureUiBase()\n const [hover, setHover] = useState<number | null>(null)\n const top = Math.max(1, props.weeks.reduce((peak, week) => Math.max(peak, week.done, week.remaining, week.created), 0))\n const ticks = [0, top / 2, top]\n const pct = (value: number): string => `${(value / top) * 100}%`\n const columnStyle = (index: number): Record<string, string | number> => ({\n left: `${(index / props.weeks.length) * 100}%`,\n right: `${((props.weeks.length - index - 1) / props.weeks.length) * 100}%`,\n })\n // 浮卡定位:贴边两列向内收,其余居中(否则溢出图表卡)\n const tipStyle = (index: number): Record<string, string | number> => {\n if (index <= 1) return { left: 0 }\n if (index >= props.weeks.length - 2) return { right: 0 }\n return { left: `${((index + 0.5) / props.weeks.length) * 100}%`, transform: 'translateX(-50%)' }\n }\n return h('div', { className: 'dtl-gchart-wrap' },\n h('div', {\n className: 'dtl-gchart',\n role: 'img',\n 'aria-label': props.ariaLabel,\n onMouseLeave: () => { setHover(null) },\n },\n ticks.map((tick) => h('div', { key: `grid-${String(tick)}`, className: 'dtl-gchart__grid', style: { bottom: pct(tick) } })),\n ticks.map((tick) => h('span', { key: `tick-${String(tick)}`, className: 'dtl-gchart__ytick', style: { bottom: pct(tick) } }, compactCount(Math.round(tick)))),\n props.weeks.map((week, index) => h('div', {\n key: week.label,\n className: 'dtl-gchart__col',\n style: columnStyle(index),\n onMouseEnter: () => { setHover(index) },\n },\n h('div', { className: 'dtl-gchart__pair' },\n h('span', { className: 'dtl-gchart__bar dtl-gchart__bar--done', style: { height: pct(week.done), minHeight: week.done > 0 ? '2px' : undefined } }),\n h('span', { className: 'dtl-gchart__bar dtl-gchart__bar--remaining', style: { height: pct(week.remaining), minHeight: week.remaining > 0 ? '2px' : undefined } }),\n h('span', { className: 'dtl-gchart__bar dtl-gchart__bar--created', style: { height: pct(week.created), minHeight: week.created > 0 ? '2px' : undefined } }),\n ),\n week.done === 0\n ? null\n : h('span', { className: 'dtl-gchart__val', style: { bottom: `calc(${pct(week.done)} + 3px)` } }, String(week.done)),\n h('span', { className: 'dtl-gchart__label' }, week.label),\n )),\n hover === null\n ? null\n : h('div', { className: 'dtl-gchart__tip', style: tipStyle(hover) },\n h('b', null, props.weeks[hover].title),\n h('div', null, h('i', { style: { background: 'var(--dtl-business)' } }), `${props.doneLabel} ${props.weeks[hover].done}`),\n h('div', null, h('i', { style: { background: 'var(--dtl-chart-remaining)' } }), `${props.remainingLabel} ${props.weeks[hover].remaining}`),\n h('div', null, h('i', { style: { background: 'var(--dtl-chart-created)' } }), `${props.createdLabel} ${props.weeks[hover].created}`),\n ),\n ),\n )\n}\n\n/** 图例(完成 / 没完成 / 新增 三个色块)。 */\nexport function ChartLegend(props: { doneLabel: string; remainingLabel: string; createdLabel: string }): ReactNode {\n ensureUiBase()\n return h('div', { className: 'dtl-gchart-legend' },\n h('span', null, h('i', { style: { background: 'var(--dtl-business)' } }), props.doneLabel),\n h('span', null, h('i', { style: { background: 'var(--dtl-chart-remaining)' } }), props.remainingLabel),\n h('span', null, h('i', { style: { background: 'var(--dtl-chart-created)' } }), props.createdLabel),\n )\n}\n","/**\n * 任务行(宿主**无表格件** ⇒ 自绘),**两种形态**(用户 2026-10-17 定稿:两区视觉必须不同,\n * 且**互不重用**):\n *\n * A. `mine`(我的待办)= 整行一张卡,样式照抄 dispatch-table 任务配置卡:\n * 左缘 6×36 状态条 + 标题行 + 第二行工作区/标签;右侧灰字时长;悬停出\n * 「继续 / 结束 / 释放」;最右常驻「查看」放大镜钮。\n *\n * B. `others`(全部待办)= 连体长表里的一行(用户 2026-10-18 定稿):**无卡片、无外框**,\n * 行 = 一条淡分隔线格:上下居中的状态圆点 + 任务(标题行 + 标签行;**标签行最前是状态标签**)\n * + **执行人列**(原状态位,有执行人才摆 `@名字` 标签)+ 最右「@创建人 创建于 年-月-日 时:分:秒」\n * (悬停掉时分秒并让位给操作钮)+ 查看。\n * 状态一律**标签型、五色**(用户 2026-10-18:原来是一排素字 / pill,现在与人员**位置互换**):\n * 灰=待认领 / 蓝=进行中 / 黄=积压 / 橙=逾期 / 红=停滞;三个异常标签悬停给**配置出来的**\n * 阈值解释(staleDays / overdueDays / backlogDays 由 host 下发)。圆点颜色同色系。\n *\n * 按钮(含查看)**不各判各的**:权限一律问 `resolveActions()`(rows.ts),本文件只按返回值画。\n *\n * 按钮点击行为(含查看)归「任务按钮与会话交接」任务,本文件只做样子与提示。\n */\nimport { createElement as h, type ReactNode } from 'react'\nimport {\n IconCheckOutlineRegular, IconChevronRightOutlineRegular, IconDownloadOutlineRegular, IconSearchOutlineRegular,\n Tooltip,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport { formatDateTime } from './format'\nimport type { Translate } from './locales'\nimport { CLOSED_ACTIONS, resolveActions, type Row } from './rows'\nimport type { BoardPayload } from './types'\nimport { Tag } from './ui'\n\ntype Tone = 'doing' | 'overdue' | 'stale' | 'backlog' | 'open' | 'closed'\n\n/** 行标题:在办 = 任务名(缺则任务号);已结案 = 任务包名(缺则「完成了什么」首句)。 */\nfunction titleOf(row: Row): string {\n if (row.kind === 'task') return row.task.title ?? row.task.id\n const title = row.record.workpackage ?? row.record.summary\n return title !== undefined && title !== '' ? title : row.record.src ?? '—'\n}\n\n/** 状态语义(异常优先:停滞 > 逾期 > 积压;三者都是 host 按时间算出来的,不是状态)。\n * 只决定圆点 / 左缘色条这类**单色位**——标签可以多枚,见 MARKS。 */\nfunction toneOf(row: Row): Tone {\n if (row.kind !== 'task') return 'closed'\n if (row.task.stale === true) return 'stale'\n if (row.task.overdue === true) return 'overdue'\n if (row.task.backlog === true) return 'backlog'\n if (row.task.status === 'open') return 'open'\n return 'doing'\n}\n\n/** 三个异常标记**可叠加**(用户 2026-10-19):一条任务能同时逾期 + 停滞,两枚标签都要摆。\n * 积压与逾期 / 停滞不会共存(前者是待认领,后两者是进行中),故最多 2 枚。 */\nconst MARKS = ['backlog', 'overdue', 'stale'] as const\n\n/** 状态文案(已结案没有在办状态 ⇒ 不给标签)。 */\nfunction statusLabel(tone: Tone, t: Translate): string | null {\n if (tone === 'stale') return t('statusStale')\n if (tone === 'overdue') return t('statusOverdue')\n if (tone === 'backlog') return t('statusBacklog')\n if (tone === 'open') return t('statusOpen')\n if (tone === 'doing') return t('statusActive')\n return null\n}\n\nexport function TaskRowView(props: {\n t: Translate\n row: Row\n /** 形态:我的待办(卡片)/ 全部待办(表格行)——两区视觉必须不同、互不重用。 */\n variant: 'mine' | 'others'\n /** 快照:问动作权限(是不是我的 / 能不能认领),并取 host 下发的三个阈值做悬停解释。 */\n board: BoardPayload | null\n}): ReactNode {\n const { t, row, variant, board } = props\n const tone = toneOf(row)\n const isTask = row.kind === 'task'\n const tags = (row.kind === 'task' ? row.task.tags : row.record.tags) ?? []\n\n // 创建日期(第一行灰徽只给日期)。\n const created = row.kind === 'task' ? row.task.created ?? null : row.record.created ?? null\n const createdDay = created !== null && created !== undefined && Date.parse(created) >= 0\n ? formatDateTime(created).slice(0, 10)\n : null\n\n // 标签悬停解释(用户 2026-10-19):一律「谁 / 何时开始 / 最后更新 / 多少天」,天数由 host 下发,\n // 前端不自行推算;缺时间就报「时间未知」,不编。\n const stamp = (value: string | null | undefined): string =>\n value === null || value === undefined ? t('timeUnknown') : formatDateTime(value, { seconds: true, fallback: t('timeUnknown') })\n const dayText = (n: number | null | undefined): string =>\n n === null || n === undefined ? t('daysNone') : t('daysValue', { n })\n const detailTip = (which: Exclude<Tone, 'closed'>): string | undefined => {\n if (row.kind !== 'task') return undefined\n const { task } = row\n const owner = task.owner ?? ''\n const who = owner === '' ? t('ownerUnclaimed') : `@${owner}`\n const started = stamp(task.started ?? task.created)\n const updated = stamp(task.updated ?? task.started ?? task.created)\n if (which === 'open') return t('tipOpen', { created: stamp(task.created) })\n if (which === 'backlog') return t('tipBacklog', { created: stamp(task.created), d: dayText(task.waitingDays) })\n if (which === 'doing') return t('tipDoing', { who, started, updated })\n if (which === 'overdue') return t('tipOverdue', { who, started, updated, d: dayText(task.ageDays) })\n return t('tipStale', { who, started, updated, d: dayText(task.idleDays) })\n }\n // 动作权限:**唯一**判定(用户 2026-10-18)——两区同一套,不许各判各的;本文件只按返回值画。\n const actions = isTask ? resolveActions(row.task, board) : CLOSED_ACTIONS\n\n // 动作钮:四个 + 查看,各一份(两区共用,不许各画一套)。\n // 悬停说明统一走官方 Tooltip(portal 逃逸裁切、hover 即出),不在 CSS 里手搓 ::after 浮层。\n // 「查看」:两区都有、统一最右、放大镜图标(用户 2026-10-17)。\n const viewButton = h(Tooltip, { label: t('viewDetail'), side: 'bottom', portal: true, maxWidth: 240,\n children: h('button', { type: 'button', className: 'dtl-viewbtn' }, h(IconSearchOutlineRegular, { size: 14 })) })\n const tipBtn = (\n icon: ReactNode, actionKey: 'actionClaim' | 'actionResume' | 'actionFinish' | 'actionRelease',\n tipKey: 'tipClaim' | 'tipResume' | 'tipFinish' | 'tipRelease',\n ): ReactNode =>\n h(Tooltip, { label: t(tipKey), side: 'bottom', portal: true, maxWidth: 240,\n children: h('button', { type: 'button', className: 'dtl-minibtn' }, ...(icon ? [icon, t(actionKey)] : [t(actionKey)])) })\n const claimButton = tipBtn(h(IconDownloadOutlineRegular, { size: 14 }), 'actionClaim', 'tipClaim')\n const resumeButton = tipBtn(h(IconChevronRightOutlineRegular, { size: 14 }), 'actionResume', 'tipResume')\n const finishButton = tipBtn(h(IconCheckOutlineRegular, { size: 14 }), 'actionFinish', 'tipFinish')\n // 释放:官方 rc.2 无断链图标(已全量核实)⇒ 纯文字,不用叉\n const releaseButton = tipBtn(null, 'actionRelease', 'tipRelease')\n\n // 状态标签(五色实心块,用户 2026-10-18 定稿):已结案没有在办状态 ⇒ 不摆;\n // 三枚异常标签带 host 配置的阈值解释(没配就不解释,不编)。\n const tagOf = (which: Exclude<Tone, 'closed'>, tip?: string): ReactNode => {\n const text = statusLabel(which, t) ?? ''\n return h(Tag, { key: which, text, tone: which, tip: tip ?? detailTip(which) })\n }\n const markTones = isTask ? MARKS.filter((which) => row.task[which] === true) : []\n // 异常标记全部摆出来;一个都没有 ⇒ 退回归属状态(待认领 / 进行中)那一枚\n const statusTags = tone === 'closed'\n ? []\n : (markTones.length > 0 ? markTones : [tone as Exclude<Tone, 'closed'>]).map((which) => tagOf(which))\n // 「我的待办」卡只摆异常标记(进行中只靠左缘色条,不再重复一枚)\n const alarmTags = markTones.map((which) => tagOf(which))\n\n // 第一行(「我的待办」卡):标题 + 创建徽 + 异常标签\n const titleLine = h('div', { className: 'dtl-taskrow__l1' },\n h('span', { className: 'dtl-taskrow__title dtl-ellipsis', title: titleOf(row) }, titleOf(row)),\n createdDay === null ? null : h('span', { className: 'dtl-taskrow__stamp' }, `[${t('fieldCreatedStamp', { day: createdDay })}]`),\n ...alarmTags,\n )\n\n // 第一行(表格行):独立类族,不沾卡片基类(用户 2026-10-18)。\n // 只有标题 —— 创建信息挪到最右、状态进标签行,标题行不再重复一遍。\n const orowTitleLine = h('div', { className: 'dtl-orow__title' },\n h('span', { className: 'dtl-orow__name dtl-ellipsis', title: titleOf(row) }, titleOf(row)),\n )\n\n // ── 形态 B:全部待办 = 真表格行(无卡片、无外框,一行一格)──\n if (variant === 'others') {\n const creator = isTask ? row.task.creator : row.record.owner\n const owner = isTask ? row.task.owner : null\n const hasOwner = !(owner === null || owner === undefined || owner === '')\n const who = creator === null || creator === undefined || creator === '' ? '—' : `@${creator}`\n const createdOk = created !== null && created !== undefined && Date.parse(created) >= 0\n // 执行人的悬停提示(**只在名字被截断时才显示**,见 Tag):全名 + **什么时候开始执行**\n // (任务上的 started;没有就报「时间未知」,不编)\n const startedOk = isTask && row.task.started !== null && row.task.started !== undefined\n && Date.parse(row.task.started) >= 0\n const ownerTip = t('startedAt', {\n who: String(owner),\n when: startedOk ? formatDateTime(String(row.task.started)) : t('timeUnknown'),\n })\n // 标签行:**状态在最前**(原执行人位),#标签跟在后面(用户 2026-10-18 位置互换)\n const tagLine = h('div', { className: 'dtl-orow__tags' },\n ...statusTags,\n tags.map(tag => h(Tag, { key: tag, text: `#${tag}` })),\n )\n return h('div', { className: 'dtl-orow' },\n h('div', { className: 'dtl-orow__row' },\n // 行标识:上下居中圆点(段头是文件夹/竖线,两处刻意不同)\n h('span', { className: `dtl-dot dtl-dot--${tone}` }),\n h('div', { className: 'dtl-tcell dtl-tcell--title' },\n orowTitleLine,\n tagLine,\n ),\n // 执行人列(原状态位):有执行人 ⇒ 一枚 @名字 标签,悬停给「谁 什么时候开始执行」\n // (页面不许跳:右侧槽位宽度是常驻的,hover 只换可见性 ⇒ 这个提示不会跟着飘)\n h('span', { className: 'dtl-tcell dtl-tcell--status' },\n // 名字上限 20 字(Tag 的 20ch);超了 ⇒ 省略号 + 悬停补全名(ownerTip)\n hasOwner ? h(Tag, { text: `@${String(owner)}`, tone: 'person', title: ownerTip }) : null),\n // 最右**一个槽**:创建信息 ⇄ 操作钮二选一(用户 2026-10-18:悬停就把创建信息整条干掉,\n // 按钮顶上那一格;两个都常驻布局 ⇒ 行宽不变、页面不跳)\n h('span', { className: 'dtl-orow__slot' },\n h('span', { className: 'dtl-orow__created' },\n t('createdAt', { who, when: createdOk ? formatDateTime(String(created)) : t('timeUnknown') })),\n // 动作排:**同一排、同一间距**(用户 2026-10-18:查看钮不许单独一档间距);\n // 谁出来由 resolveActions 说了算 —— 我的 ⇒ 继续/结束/释放,待认领 ⇒ 认领\n h('span', { className: 'dtl-orow__actions' },\n actions.claim ? claimButton : null,\n actions.resume ? resumeButton : null,\n actions.finish ? finishButton : null,\n actions.release ? releaseButton : null,\n actions.view ? viewButton : null,\n ),\n ),\n ),\n )\n }\n\n // ── 形态 A:我的待办 = 卡片(dispatch 任务配置卡同款)──\n const parts: string[] = []\n if (isTask) {\n const startRaw = row.task.started ?? row.task.created\n if (startRaw !== undefined && startRaw !== null && startRaw !== '' && Date.parse(startRaw) >= 0) {\n parts.push(`${formatDateTime(startRaw).slice(0, 10)} ${t('fieldStarted')}`)\n }\n if (row.task.ageDays !== null && row.task.ageDays !== undefined) {\n parts.push(row.task.ageDays === 0 ? t('ageToday') : t('ageDaysText', { n: row.task.ageDays }))\n }\n } else {\n parts.push(`${t('fieldFinished')} ${formatDateTime(row.record.time ?? row.record.created ?? '', { fallback: t('timeUnknown') })}`)\n }\n return h('div', { className: 'dtl-taskrow' },\n h('div', { className: 'dtl-taskrow__row' },\n h('span', { className: `dtl-rail dtl-rail--${tone}` }),\n h('div', { style: { flex: '1 1 auto', minWidth: 0 } },\n titleLine,\n h('div', { className: 'dtl-taskrow__l2' },\n h('span', { className: 'dtl-wstag dtl-ellipsis', title: row.kind === 'task' ? row.task.workspace.title : '' },\n row.kind === 'task' ? row.task.workspace.title : ''),\n tags.map(tag => h(Tag, { key: tag, text: `#${tag}` })),\n ),\n ),\n h('span', { className: 'dtl-taskrow__when' }, parts.length === 0 ? t('timeUnknown') : parts.join('丨')),\n // 动作排:继续 / 结束 / 释放 + 查看 —— **同一排、同一间距**\n // (用户 2026-10-18:查看钮原来是独立的一格,离前面的按钮远一档)\n // 谁出来由 resolveActions 说了算(与下面「全部待办」**同一个方法**,不再各判各的)\n h('span', { className: 'dtl-taskrow__actions' },\n actions.resume ? resumeButton : null,\n actions.finish ? finishButton : null,\n actions.release ? releaseButton : null,\n actions.view ? viewButton : null,\n ),\n ),\n )\n}\n","/**\n * 看板首页(`main` 槽整页内容):8 统计卡 + 近 8 周趋势图 + 两区任务列表。\n * 定稿见 docs/design/features/board-home.md §二(2026-10-17 专家组改版);\n * 按钮矩阵归「任务按钮与会话交接」任务(写入路径 A/B 未拍板),本页**不画**任务动作按钮。\n *\n * 刷新纪律(2026-10-17 拍板):挂载首读 + 手动刷新按钮,**不轮询**;空态三分\n * (未就绪不渲染 / 真空引导卡 / 筛选空小字)。\n */\nimport { createElement as h, useEffect, useMemo, useState, useSyncExternalStore, type ChangeEvent, type ReactNode } from 'react'\nimport {\n IconChecklistOutlineRegular, IconFlatListOutlineRegular, IconFolderCloseRegular, IconFolderOpenRegular,\n IconRefreshOutlineRegular,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { LocaleKey, Translate } from './locales'\nimport { boardSnapshot, boardSubscribe, refresh, setQuery, type StatusFilter } from './store'\nimport { isMine, selectView, type WorkspaceGroup } from './rows'\nimport type { BoardPayload, PeriodCounts } from './types'\nimport { TaskRowView } from './task-row'\nimport {\n Button, ChartLegend, EmptyState, ensureUiBase, GroupedBarChart, SearchInput, Segmented, Select, StatCard,\n} from './ui'\n\ntype Tab = 'tasks' | 'stats'\n\n/** 整页容器:占满中栏、自己滚动。 */\nconst pageStyle: Record<string, string | number> = {\n height: '100%', width: '100%', boxSizing: 'border-box', overflow: 'auto',\n padding: '24px 22px', color: 'var(--dtl-fg)',\n}\nconst headerStyle: Record<string, string | number> = {\n display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 'var(--dtl-space-3)', flexWrap: 'wrap',\n marginBottom: 'var(--dtl-space-4)',\n}\nconst titleStyle: Record<string, string | number> = { fontSize: 'var(--dtl-font-xl)', fontWeight: 600 }\n/** 统计区:左 8 块(2 行 × 4 列)+ 右趋势图,参考图同款分栏。\n * 列数写死 4 → 2(媒体查询在 ui.ts 的 .dtl-stats / .dtl-stats__cards 里),\n * 绝不给 3 列——3 列会排出 3+1 的非对称(用户 2026-10-17:必须上下对称)。 */\nconst statsRowStyle: Record<string, string | number> = { marginBottom: 'var(--dtl-space-4)' }\nconst chartCardStyle: Record<string, string | number> = {\n display: 'flex', flexDirection: 'column', gap: 'var(--dtl-space-2)', padding: 'var(--dtl-space-2) var(--dtl-space-3)',\n}\nconst hintStyle: Record<string, string | number> = { fontSize: 'var(--dtl-font-sm)', color: 'var(--dtl-fg-2)' }\n/** 分区标题行:垂直居中(用户 2026-10-17:图标 / 标题 / 计数不许像下楼梯一样逐个低);\n * 上下留白**翻倍**(用户 2026-10-18:两个大标题太挤)。 */\nconst regionHeadRowStyle: Record<string, string | number> = {\n display: 'flex', alignItems: 'center', gap: 'var(--dtl-space-2)',\n margin: 'calc(var(--dtl-space-4) * 2) 0 calc(var(--dtl-space-2) * 2)',\n}\nconst regionHeadTitleStyle: Record<string, string | number> = { fontSize: 'var(--dtl-font-lg)', fontWeight: 600 }\n/** 工作区下拉最小宽度(用户 2026-10-17:内容自适应,下限 = 旧定宽 175 的 3/4 ≈ 131)。 */\nconst WORKSPACE_SELECT_MIN = 131\n/** 搜索框宽度(用户 2026-10-17:两区同款,96×2 = 192)。 */\nconst SEARCH_WIDTH = 192\n\n/** 分组默认折叠阈值(组内太多条时先收起,用户点开)。 */\nconst GROUP_COLLAPSE_OVER = 30\n\nexport function BoardHome(props: { t: Translate; onBack: () => void }): ReactNode {\n const { t, onBack } = props\n ensureUiBase()\n const state = useSyncExternalStore(boardSubscribe, boardSnapshot)\n const [tab, setTab] = useState<Tab>('tasks')\n\n // 打开面板首读(后端纯按需,无推送无轮询)。\n useEffect(() => { void refresh() }, [])\n\n const board = state.board\n const view = useMemo(() => selectView(state), [state])\n const stats = board?.stats\n // 趋势走慢路径(`/board/trend`,读 history):晚到不影响列表;未到 / 失败时由统计卡与图表区各自兜底。\n const trend = state.trend\n const periods = trend?.trend\n const trendPending = state.trendStatus === 'idle' || state.trendStatus === 'loading'\n /** 快照 + 趋势两段各自的降级工作区(op 不同,合并展示,别装没看见)。 */\n const degraded = [...(board?.degraded ?? []), ...(trend?.degraded ?? [])]\n // 工作区下拉候选(用户 2026-10-17):不读系统全量工作区,只从「我的」任务里提取工作区并排重\n // ⇒ 没有我任务的工作区不出现;口径与第一区一致(isMine 同源)。\n const workspaceOptions = useMemo(() => [\n { value: '', label: t('workspaceAll') },\n ...[...new Map((board?.tasks ?? [])\n .filter((task) => isMine(task, board))\n .map((task) => [task.workspace.id, task.workspace.title]))]\n .map(([id, title]) => ({ value: id, label: title })),\n ], [board, t])\n /** 统计卡:纯展示(用户 2026-10-17:先全去掉点击,界面定稿后再议滚动 / 筛选联动)。 */\n const card = (label: LocaleKey, value: number, hint: string, title?: string): ReactNode =>\n h(StatCard, {\n label: t(label),\n value,\n hint,\n title,\n interactive: false,\n })\n\n /** 子集卡占比小字:分母为 0 或自己没有 ⇒ 不显示(不拿 0% 充数)。 */\n const share = (part: number, whole: number, key: LocaleKey): string =>\n part > 0 && whole > 0 ? t(key, { n: Math.round((part / whole) * 100) }) : ''\n\n /** 统计台卡:不可点(首页没有已结案可筛),hover 给具体日期区间。\n * 趋势未到 / 读挂 ⇒ 数字留 '—'(StatCard 对 0 的处理)、小字整条不显示,**不拿 0 充数**。 */\n const periodCard = (label: LocaleKey, period: PeriodCounts | undefined): ReactNode =>\n h(StatCard, {\n label: t(label),\n value: period?.done ?? 0,\n hint: period === undefined ? undefined : t('statCreatedHint', { n: period.created }),\n interactive: false,\n title: period === undefined ? undefined : t('periodRange', { from: period.from, to: period.to }),\n })\n\n const [chartScope, setChartScope] = useState<'all' | 'mine'>('all')\n const chartWeeks = useMemo(() => (trend?.closed.weekly[chartScope] ?? []).map((bucket) => {\n // 容错:host 旧版可能不带 end(客户端/宿主版本错配时),退回用 week,绝不崩面板。\n const end = bucket.end ?? bucket.week ?? ''\n return {\n label: end.slice(5) || bucket.week?.slice(5) || '',\n done: bucket.done,\n remaining: bucket.remaining,\n created: bucket.created,\n title: chartScope === 'mine' ? `截至 ${end}(${t('chartOnlyMine')})` : `截至 ${end}`,\n }\n }), [trend, chartScope, t])\n const chartEmpty = chartWeeks.every((week) => week.done === 0 && week.created === 0 && week.remaining === 0)\n\n return h('div', { style: pageStyle },\n h('div', { style: { display: 'flex', justifyContent: 'center' } },\n h('div', { style: { width: '100%', maxWidth: '1280px', minWidth: 0, boxSizing: 'border-box' } },\n h('div', { style: headerStyle },\n h('div', { style: { display: 'flex', alignItems: 'center', gap: 'var(--dtl-space-3)', minWidth: 0 } },\n h(Button, { variant: 'outline', size: 'md', onClick: onBack }, `← ${t('backToConversation')}`),\n h('div', { style: titleStyle }, t('panelTitle')),\n ),\n h('div', { style: { display: 'flex', alignItems: 'center', gap: 'var(--dtl-space-2)' } },\n h(Segmented<Tab>, {\n id: 'dtl-tab',\n value: tab,\n label: t('panelTitle'),\n items: [\n { value: 'tasks', label: t('tabTasks') },\n { value: 'stats', label: t('tabStats') },\n ],\n onChange: setTab,\n }),\n h(Button, {\n variant: 'outline',\n size: 'md',\n icon: h(IconRefreshOutlineRegular, { size: 16 }),\n disabled: state.refreshing,\n title: t('refresh'),\n onClick: () => { void refresh() },\n }, state.refreshing ? t('refreshing') : t('refresh')),\n ),\n ),\n\n tab === 'stats'\n // 统计报表尚未开建(另立任务):占位说明,不画假图表。\n ? h('div', { style: hintStyle }, t('statsPlaceholder'))\n : h('div', null,\n // ── 统计区:左 8 块(操作台 4 + 统计台 4)+ 右趋势图 ──\n stats === undefined\n ? null\n : h('div', { className: 'dtl-stats', style: statsRowStyle },\n h('div', { className: 'dtl-stats__cards' },\n // 按列成对(用户 2026-10-19 选 A):每列上下是同一对象的两个视角,\n // 子集关系一眼可见 —— 积压 ⊂ 待认领、逾期 / 停滞 ⊂ 进行中。\n // DOM 按**列序**写,CSS 的 grid-auto-flow:column 保证 4 列 / 2 列两种排布下都成对。\n // 小字一律给本卡独有的增量信息,**不重复全集**(用户 2026-10-19):\n // 三块子集卡一律报**占全集的比例**(用户 2026-10-19 二选一时定占比),0 条时整条不显示。\n card('statMyActive', stats.myActive, [\n stats.myOverdue > 0 ? t('statMyOverdueHint', { n: stats.myOverdue }) : null,\n stats.myStale > 0 ? t('statMyStaleHint', { n: stats.myStale }) : null,\n ].filter((part) => part !== null).join(' | ')),\n card('statDoing', stats.doing, stats.doing - stats.myActive > 0\n ? t('statDoingHint', { n: stats.doing - stats.myActive })\n : t('statDoingAllMine')),\n card('statOpen', stats.open, stats.oldestOpenDays === null\n ? ''\n : t('statOpenHint', { n: stats.oldestOpenDays })),\n card('statBacklog', stats.backlog, share(stats.backlog, stats.open, 'statBacklogHint'), t('statBacklogTitle', { n: stats.backlogDays })),\n card('statOverdue', stats.overdue, share(stats.overdue, stats.doing, 'statOverdueHint'), t('statOverdueTitle', { n: stats.overdueDays })),\n card('statStale', stats.stale, share(stats.stale, stats.doing, 'statStaleHint'), t('statStaleTitle', { n: stats.staleDays })),\n periodCard('statWeekDone', periods?.thisWeek),\n periodCard('statLastWeekDone', periods?.lastWeek),\n ),\n h('div', { className: 'dtl-card', style: chartCardStyle },\n // 右上角只留一个「只看我的」小复选框:勾上 = 我的,不勾 = 所有(用户 2026-10-17)\n h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end' } },\n h('label', { className: 'dtl-check' },\n h('input', {\n type: 'checkbox',\n checked: chartScope === 'mine',\n onChange: (event: ChangeEvent<HTMLInputElement>) => { setChartScope(event.target.checked ? 'mine' : 'all') },\n }),\n t('chartOnlyMine'),\n ),\n ),\n // 趋势是慢路径:未到给占位、读挂给兜底文案(带原因),就绪才画图——绝不画空图冒充「没有记录」。\n trendPending\n ? h('div', { style: { ...hintStyle, padding: 'var(--dtl-space-4) 0' } }, t('chartLoading'))\n : state.trendStatus === 'error'\n ? h('div', { style: { ...hintStyle, padding: 'var(--dtl-space-4) 0' } },\n `${t('chartUnavailable')}${state.trendError === null ? '' : `:${state.trendError}`}`)\n : chartEmpty\n ? h('div', { style: { ...hintStyle, padding: 'var(--dtl-space-4) 0' } }, t('chartEmpty'))\n : h(GroupedBarChart, {\n weeks: chartWeeks,\n ariaLabel: t('chartTitle'),\n doneLabel: t('chartLegendDone'),\n remainingLabel: t('chartLegendRemaining'),\n createdLabel: t('chartLegendCreated'),\n }),\n h(ChartLegend, { doneLabel: t('chartLegendDone'), remainingLabel: t('chartLegendRemaining'), createdLabel: t('chartLegendCreated') }),\n ),\n ),\n\n // ── 列表 ──\n state.status === 'loading' && board === null\n ? null // 未就绪:不渲染\n : state.status === 'error' && board === null\n ? h('div', { style: { display: 'flex', alignItems: 'center', gap: 'var(--dtl-space-2)' } },\n h('span', { style: { color: 'var(--dtl-danger)', fontSize: 'var(--dtl-font-md)' } }, `${t('loadFailed')}:${state.error ?? ''}`),\n h(Button, { variant: 'outline', size: 'sm', onClick: () => { void refresh() } }, t('retry')),\n )\n : board !== null && board.workspaces.length === 0\n ? h(EmptyState, { tone: 'guide', title: t('emptyNoWorkspace'), hint: t('emptyNoWorkspaceHint') })\n : h('div', null,\n // 降级横幅:有工作区读挂 ⇒ 红色警告 + 人话原因(单工作区坏不拖垮其余,别装没看见)\n board !== null && (board.degraded?.length ?? 0) > 0\n ? h('div', {\n style: {\n display: 'flex', alignItems: 'flex-start', gap: 'var(--dtl-space-2)',\n padding: 'var(--dtl-space-2) var(--dtl-space-3)', marginBottom: 'var(--dtl-space-3)',\n border: '1px solid var(--dtl-danger)', borderRadius: 'var(--dtl-radius-md)',\n background: 'var(--dtl-danger-soft)', color: 'var(--dtl-fg)',\n fontSize: 'var(--dtl-font-sm)', lineHeight: 'var(--dtl-line-sm)',\n },\n },\n h('span', { style: { color: 'var(--dtl-danger)', flex: 'none', fontWeight: 600 } }, t('degradedTitle')),\n h('span', null, degraded.map(item => t('degradedItem', {\n title: item.workspaceTitle, message: item.message,\n })).join(';')),\n )\n : null,\n // 第一区:我的待办——定死全量、不过滤(用户 2026-10-17),空了给引导卡;\n // 标题行右侧挂搜索框(只作用第三区),计数用小圆徽\n h('div', { style: regionHeadRowStyle },\n h('span', { style: { color: 'var(--dtl-fg-3)', display: 'inline-flex', flex: 'none' } },\n h(IconChecklistOutlineRegular, { size: 16 })),\n h('span', { style: regionHeadTitleStyle }, t('regionMine')),\n view.mine.length > 0\n ? h('span', { className: 'dtl-count' }, t('mineCount', { n: view.mine.length }))\n : null,\n h('span', { style: { flex: '1 1 auto' } }),\n // 工作区下拉:内容自适应、下限 131(用户 2026-10-17)\n h(Select<string>, {\n value: workspaceOptions.some((option) => option.value === state.query.workspace)\n ? state.query.workspace\n : '',\n options: workspaceOptions,\n ariaLabel: t('workspaceLabel'),\n minWidth: WORKSPACE_SELECT_MIN,\n onChange: (next) => { setQuery({ workspace: next }) },\n }),\n // 搜索框:在下拉右边、与「其他待办」同宽 192(用户 2026-10-17)\n h('div', { style: { width: `${SEARCH_WIDTH}px`, flex: 'none' } },\n h(SearchInput, {\n value: state.query.keyword,\n placeholder: t('searchPlaceholder'),\n ariaLabel: t('searchPlaceholder'),\n onChange: (next) => { setQuery({ keyword: next }) },\n }),\n ),\n ),\n view.mine.length === 0\n ? h(EmptyState, { tone: 'guide', title: t('mineEmpty'), hint: t('mineEmptyHint') })\n : view.mine.map(row => h(TaskRowView, {\n key: row.key, t, row, variant: 'mine', board,\n })),\n // 第二区:全部待办 —— **永远渲染**(用户 2026-10-18:选「进行中」没数据也不能整块消失;\n // 且不要过滤掉自己,所以含我的任务,会与第一区重叠——顶部快看 / 底部全量);\n // 标题行:图标 + 名 + 状态滑轨(带全量角标)+ 搜索(最右);表格区无数据给虚线空态\n h('div', null,\n h('div', { style: regionHeadRowStyle },\n h('span', { style: { color: 'var(--dtl-fg-3)', display: 'inline-flex', flex: 'none' } },\n h(IconFlatListOutlineRegular, { size: 16 })),\n h('span', { style: { ...regionHeadTitleStyle, fontSize: 'var(--dtl-font-md)' } }, t('regionAll')),\n h('span', { style: { flex: '1 1 auto' } }),\n // 状态滑轨:角标 = 第二区**全量**计数(不受滑轨自身影响);0 不标数字\n h(Segmented<StatusFilter>, {\n id: 'dtl-others-status',\n value: state.query.status,\n label: t('statusLabel'),\n items: [\n { value: 'all', label: t('statusAll'), badge: orZero(view.allCounts.total) },\n { value: 'active', label: t('statusActive'), badge: orZero(view.allCounts.doing) },\n { value: 'open', label: t('statusOpen'), badge: orZero(view.allCounts.open) },\n { value: 'backlog', label: t('statusBacklog'), badge: orZero(view.allCounts.backlog) },\n { value: 'overdue', label: t('statusOverdue'), badge: orZero(view.allCounts.overdue) },\n { value: 'stale', label: t('statusStale'), badge: orZero(view.allCounts.stale) },\n ],\n onChange: (next) => { setQuery({ status: next }) },\n }),\n h('div', { style: { width: `${SEARCH_WIDTH}px`, flex: 'none' } },\n h(SearchInput, {\n value: state.query.othersKeyword,\n placeholder: t('searchPlaceholder'),\n ariaLabel: t('searchPlaceholder'),\n onChange: (next) => { setQuery({ othersKeyword: next }) },\n }),\n ),\n ),\n view.groups.length === 0\n // 空态:虚线框 + 两排字 + 按钮(参考 dispatch 空态;按钮先接「回会话」,\n // 真正的「起会话建任务」归会话交接任务)\n ? h('div', { className: 'dtl-empty-guide', style: { marginTop: 'var(--dtl-space-2)' } },\n h('div', { style: { fontSize: 'var(--dtl-font-lg)', fontWeight: 600, marginBottom: 'var(--dtl-space-1)' } }, t('allEmptyTitle')),\n h('div', { style: { fontSize: 'var(--dtl-font-md)', color: 'var(--dtl-fg-2)', marginBottom: 'var(--dtl-space-3)' } }, t('allEmptyHint')),\n h(Button, { variant: 'primary', size: 'md', onClick: onBack }, t('allEmptyAction')),\n )\n : h('div', { style: { marginTop: 'var(--dtl-space-2)' } },\n view.groups.map(group => h(WorkspaceGroupView, {\n key: group.workspaceId, t, group, board,\n })),\n ),\n ),\n ),\n ),\n ),\n ),\n )\n}\n\n/** 角标:>0 才给数字,0 = undefined(不标)。 */\nfunction orZero(n: number): number | undefined {\n return n > 0 ? n : undefined\n}\n\n/** 一个工作区分组(全部待办表格段):段头(点+名+统计)→ 列头行 → 任务行。\n * 统计口径(用户 2026-10-18):共 N|待办 N|进行中 N 常显(0 也显);\n * 积压 / 逾期 / 停滞**只在 >0 时出现**。 */\nfunction WorkspaceGroupView(props: {\n t: Translate\n group: WorkspaceGroup\n /** 传下去只为问动作权限(是不是我的)——第二区含我的任务,按钮得跟第一区一致。 */\n board: BoardPayload | null\n}): ReactNode {\n const { t, group, board } = props\n const [open, setOpen] = useState(group.rows.length <= GROUP_COLLAPSE_OVER)\n const stats = [\n t('groupTotal', { n: group.total }),\n t('groupOpen', { n: group.openCount }),\n t('groupDoing', { n: group.doingCount }),\n ...(group.backlogCount > 0 ? [t('groupBacklog', { n: group.backlogCount })] : []),\n ...(group.overdueCount > 0 ? [t('groupOverdue', { n: group.overdueCount })] : []),\n ...(group.staleCount > 0 ? [t('groupStale', { n: group.staleCount })] : []),\n ].join(' | ')\n return h('div', { className: 'dtl-group' },\n h('button', {\n type: 'button',\n className: 'dtl-group__head',\n 'aria-expanded': open,\n title: open ? t('collapseAria') : t('expandAria'),\n onClick: () => { setOpen(!open) },\n },\n // 段头标识:展开 = 打开的文件夹、收起 = 合上的文件夹(跟官方工作区显示逻辑一致);\n // 包里没有这对图标 ⇒ 回落成竖线\n IconFolderOpenRegular === undefined || IconFolderCloseRegular === undefined\n ? h('span', { className: 'dtl-group__bar' })\n : h('span', { style: { display: 'inline-flex', flex: 'none', color: 'var(--dtl-fg-3)' } },\n h(open ? IconFolderOpenRegular : IconFolderCloseRegular, { size: 14 })),\n h('span', { className: 'dtl-group__name dtl-ellipsis' }, group.title),\n h('span', { style: { flex: '1 1 auto' } }),\n h('span', { className: 'dtl-group__stats' }, stats),\n ),\n open\n ? h('div', null,\n // 无列头行(用户 2026-10-18:表头就是段头——左工作区名、右统计,不放「任务/创建人」那排)\n group.rows.map(row => h(TaskRowView, {\n key: row.key, t, row, variant: 'others', board,\n })),\n )\n : null,\n )\n}\n\n","/**\n * 文案唯一真源:命名空间 `dsh-task-ledger`,zh / en 两档。\n *\n * 规矩(docs/design/ui-style.md §五):\n * - 两档**键必须完全对齐**(冒烟有断言),漏一种语言界面就露键名;\n * - 界面上不许出现未登记的硬编码中文;\n * - 占位符一律 `{name}`,由 `interpolateTranslate` 自己替换(不依赖宿主是否支持 params);\n * - 语言切换**跟随宿主**:插件不自备语言开关(注册字典 + 注册项声明 `locale:` ⇒ 宿主渲染器合成 `t` 席位)。\n */\n\n/** 文案键全集(增删键必须同时改 zh / en)。 */\nexport type LocaleKey =\n | 'backToConversation' | 'panelTitle' | 'tabTasks' | 'tabStats'\n | 'refresh' | 'refreshing' | 'loading' | 'retry' | 'loadFailed'\n | 'degradedTitle' | 'degradedItem'\n | 'statsPlaceholder'\n // 统计卡\n | 'statMyActive' | 'statOpen' | 'statOpenHint' | 'statDoing' | 'statDoingHint' | 'statDoingAllMine'\n | 'statBacklog' | 'statOverdue' | 'statStale'\n | 'statMyOverdueHint' | 'statMyStaleHint' | 'statBacklogHint' | 'statOverdueHint' | 'statStaleHint'\n | 'statStaleTitle' | 'statOverdueTitle' | 'statBacklogTitle'\n | 'statWeekDone' | 'statLastWeekDone' | 'statCreatedHint'\n | 'daysNone' | 'daysValue'\n // 趋势图\n | 'chartTitle' | 'chartCaption' | 'chartBucket' | 'chartEmpty' | 'chartLoading' | 'chartUnavailable'\n | 'chartScopeAll' | 'chartScopeMine' | 'chartOnlyMine' | 'chartLegendDone' | 'chartLegendCreated' | 'chartLegendRemaining' | 'chartAvg'\n | 'periodRange'\n // 筛选行\n | 'scopeLabel' | 'scopeMine' | 'scopeAll'\n | 'statusLabel' | 'statusAll' | 'statusActive' | 'statusOpen' | 'statusBacklog' | 'statusOverdue' | 'statusStale'\n | 'tipOpen' | 'tipBacklog' | 'tipDoing' | 'tipOverdue' | 'tipStale'\n | 'workspaceLabel' | 'workspaceAll'\n | 'searchPlaceholder'\n // 列表分区\n | 'regionMine' | 'regionAll' | 'showingCount'\n | 'groupTotal' | 'groupOpen' | 'groupDoing' | 'groupBacklog' | 'groupOverdue' | 'groupStale'\n | 'colTask' | 'colCreator' | 'colOwner' | 'colStatus' | 'colTime'\n | 'allEmptyTitle' | 'allEmptyHint' | 'allEmptyAction'\n | 'expandAria' | 'collapseAria'\n | 'ownerUnclaimed' | 'fieldCreator' | 'fieldOwner' | 'fieldCreated' | 'fieldStarted' | 'fieldFinished' | 'timeUnknown'\n | 'ageDaysText' | 'ageToday'\n | 'startedAt'\n | 'actionResume' | 'actionFinish' | 'actionRelease' | 'actionClaim'\n | 'tipResume' | 'tipFinish' | 'tipRelease' | 'tipClaim' | 'viewDetail'\n | 'createdAt'\n | 'fieldCreatedStamp' | 'mineEmpty' | 'mineEmptyHint' | 'mineCount'\n | 'worklogTitle' | 'worklogPath' | 'worklogMissing'\n | 'mdCopy' | 'mdCopied' | 'mdFootnotes'\n // 空态\n | 'emptyNoWorkspace' | 'emptyNoWorkspaceHint' | 'emptyNoTask' | 'emptyNoTaskHint' | 'emptyFiltered'\n // 浮标入口\n | 'badgeTitle' | 'openBoard'\n\n/** 中文(默认档)。 */\nexport const zh: Record<LocaleKey, string> = {\n backToConversation: '返回会话',\n panelTitle: '任务台账',\n tabTasks: '任务',\n tabStats: '统计',\n refresh: '刷新',\n refreshing: '刷新中',\n loading: '读取中…',\n retry: '重试',\n loadFailed: '读取失败',\n degradedTitle: '部分工作区读取失败',\n degradedItem: '{title}:{message}',\n statsPlaceholder: '统计报表尚未开建(另立任务):这里以后放分类分布、按人完成数、时间线。',\n\n statMyActive: '我的待办',\n statMyOverdueHint: '逾期 {n}',\n statMyStaleHint: '停滞 {n}',\n statOpen: '待认领',\n statOpenHint: '最长等待 {n} 天',\n statDoing: '进行中',\n statDoingHint: '他人在办 {n}',\n statDoingAllMine: '均为我负责',\n statBacklog: '积压',\n statOverdue: '逾期',\n statStale: '停滞',\n statBacklogHint: '占待认领 {n}%',\n statOverdueHint: '占进行中 {n}%',\n statStaleHint: '占进行中 {n}%',\n statStaleTitle: '停滞 = 进行中满 {n} 天没更新',\n statOverdueTitle: '逾期 = 认领后满 {n} 天还没结案',\n statBacklogTitle: '积压 = 待认领满 {n} 天没人接',\n statWeekDone: '本周完成',\n statLastWeekDone: '上周完成',\n statCreatedHint: '新增 {n}',\n daysNone: '—',\n daysValue: '{n} 天',\n\n chartTitle: '近 8 周',\n chartCaption: '完成(按结案时间)/ 新增(按创建时间),周一起桶;我的 = owner 是我',\n chartBucket: '{week} 当周:完成 {done} 条,新增 {created} 条',\n chartEmpty: '近 8 周没有记录',\n chartLoading: '趋势读取中…(列表已就绪,统计稍后到)',\n chartUnavailable: '趋势暂时读不到(不影响上面的任务列表)',\n chartScopeAll: '所有',\n chartScopeMine: '我的',\n chartOnlyMine: '只看我的',\n chartLegendDone: '完成',\n chartLegendCreated: '新增',\n chartLegendRemaining: '未结',\n chartAvg: '周均 {n}',\n periodRange: '{from} ~ {to}',\n\n scopeLabel: '范围',\n scopeMine: '我的',\n scopeAll: '全部工作区',\n statusLabel: '状态',\n statusAll: '全部',\n statusActive: '进行中',\n statusOpen: '待认领',\n statusBacklog: '积压',\n statusOverdue: '逾期',\n statusStale: '停滞',\n tipOpen: '任务创建于 {created}\\n尚未有人认领',\n tipBacklog: '任务创建于 {created}\\n无人认领已满 {d}',\n tipDoing: '{who}\\n开始执行:{started}\\n最后更新:{updated}',\n tipOverdue: '{who}\\n开始执行:{started}\\n最后更新:{updated}\\n已逾期 {d}',\n tipStale: '{who}\\n开始执行:{started}\\n最后更新:{updated}\\n已停滞 {d}',\n workspaceLabel: '工作区',\n workspaceAll: '全部工作区',\n searchPlaceholder: '搜索',\n\n regionMine: '我的待办',\n regionAll: '全部待办',\n showingCount: '显示 {n} 条',\n groupTotal: '共 {n} 条',\n groupOpen: '待办 {n} 条',\n groupDoing: '进行中 {n} 条',\n groupBacklog: '积压 {n} 条',\n groupOverdue: '逾期 {n} 条',\n groupStale: '停滞 {n} 条',\n colTask: '任务',\n colCreator: '创建人',\n colOwner: '执行人',\n colStatus: '状态',\n colTime: '时长',\n allEmptyTitle: '没有可显示的事项',\n allEmptyHint: '切换上面的状态过滤试试,或新建一个任务。',\n allEmptyAction: '新建任务',\n expandAria: '展开过程文档',\n collapseAria: '收起',\n\n ownerUnclaimed: '待认领',\n fieldCreator: '创建',\n fieldOwner: '执行',\n fieldCreated: '创建',\n fieldStarted: '开始',\n fieldFinished: '结案',\n timeUnknown: '时间未知',\n ageDaysText: '已进行 {n} 天',\n ageToday: '今天',\n actionResume: '继续',\n actionFinish: '结束',\n actionRelease: '释放',\n tipResume: '继续推进这个任务',\n tipFinish: '结案并写入历史',\n tipRelease: '释放认领,让别人接手',\n actionClaim: '认领',\n tipClaim: '认领这个任务,变成我在做',\n createdAt: '{who} 创建于 {when}',\n startedAt: '{who} 开始于 {when}',\n viewDetail: '查看详情',\n fieldCreatedStamp: '{day} 创建',\n mineEmpty: '您现在没有正在进行的工作',\n mineEmptyHint: '在某个工作区 new 一个任务,或到下面认领一条待认领任务,这里就会出现。',\n mineCount: '{n} 条',\n worklogTitle: '过程文档',\n worklogPath: '路径',\n worklogMissing: '(缺失)',\n mdCopy: '复制',\n mdCopied: '已复制',\n mdFootnotes: '脚注',\n\n emptyNoWorkspace: '还没有台账工作区',\n emptyNoWorkspaceHint: '在某个项目里跑 task-ledger skill 的 init 建档(生成 ledger/progress.json),这里就会出现它。',\n emptyNoTask: '一条任务都没有',\n emptyNoTaskHint: '用 task-ledger skill 的 new 开一个任务,或让别人开在这里等你认领。',\n emptyFiltered: '没有符合当前筛选的任务',\n\n badgeTitle: '我名下待办',\n openBoard: '打开任务台账',\n}\n\n/** English(与 zh 键完全对齐)。 */\nexport const en: Record<LocaleKey, string> = {\n backToConversation: 'Back to chat',\n panelTitle: 'Task ledger',\n tabTasks: 'Tasks',\n tabStats: 'Stats',\n refresh: 'Refresh',\n refreshing: 'Refreshing',\n loading: 'Loading…',\n retry: 'Retry',\n loadFailed: 'Load failed',\n degradedTitle: 'Some workspaces failed to load',\n degradedItem: '{title}: {message}',\n statsPlaceholder: 'Stats reports are not built yet (separate task): category breakdown, completions by person, timeline.',\n\n statMyActive: 'My todos',\n statMyOverdueHint: 'overdue {n}',\n statMyStaleHint: 'stale {n}',\n statOpen: 'Unclaimed',\n statOpenHint: 'longest waiting {n} days',\n statDoing: 'In progress',\n statDoingHint: 'others: {n}',\n statDoingAllMine: 'all mine',\n statBacklog: 'Backlog',\n statOverdue: 'Overdue',\n statStale: 'Stale',\n statBacklogHint: '{n}% of unclaimed',\n statOverdueHint: '{n}% of active',\n statStaleHint: '{n}% of active',\n statStaleTitle: 'stale = active with no update for {n}+ days',\n statOverdueTitle: 'overdue = claimed {n}+ days ago and still open',\n statBacklogTitle: 'backlog = unclaimed for {n}+ days',\n statWeekDone: 'Done this week',\n statLastWeekDone: 'Done last week',\n statCreatedHint: '{n} created',\n daysNone: '—',\n daysValue: '{n}d',\n\n chartTitle: 'Last 8 weeks',\n chartCaption: 'done (by close time) / created (by create time), weeks start Monday; mine = owner is me',\n chartBucket: 'week of {week}: {done} done, {created} created',\n chartEmpty: 'Nothing in the last 8 weeks',\n chartLoading: 'Loading trend… (list is ready, stats will follow)',\n chartUnavailable: 'Trend is unavailable right now (the task lists above are unaffected)',\n chartScopeAll: 'All',\n chartScopeMine: 'Mine',\n chartOnlyMine: 'Mine only',\n chartLegendDone: 'Done',\n chartLegendCreated: 'Created',\n chartLegendRemaining: 'Outstanding',\n chartAvg: 'avg {n}/wk',\n periodRange: '{from} ~ {to}',\n\n scopeLabel: 'Scope',\n scopeMine: 'Mine',\n scopeAll: 'All workspaces',\n statusLabel: 'Status',\n statusAll: 'All',\n statusActive: 'Active',\n statusOpen: 'Unclaimed',\n statusBacklog: 'Backlog',\n statusOverdue: 'Overdue',\n statusStale: 'Stale',\n tipOpen: 'Created on {created}\\nNot claimed yet',\n tipBacklog: 'Created on {created}\\nUnclaimed for {d}',\n tipDoing: '{who}\\nStarted: {started}\\nLast updated: {updated}',\n tipOverdue: '{who}\\nStarted: {started}\\nLast updated: {updated}\\nOverdue by {d}',\n tipStale: '{who}\\nStarted: {started}\\nLast updated: {updated}\\nStalled for {d}',\n workspaceLabel: 'Workspace',\n workspaceAll: 'All workspaces',\n searchPlaceholder: 'Search',\n\n regionMine: 'My todos',\n regionAll: 'All tasks',\n showingCount: '{n} shown',\n groupTotal: '{n} total',\n groupOpen: '{n} open',\n groupDoing: '{n} active',\n groupBacklog: '{n} backlog',\n groupOverdue: '{n} overdue',\n groupStale: '{n} stale',\n colTask: 'Task',\n colCreator: 'Created by',\n colOwner: 'Owner',\n colStatus: 'Status',\n colTime: 'Duration',\n allEmptyTitle: 'Nothing to show',\n allEmptyHint: 'Try another status filter above, or create a task.',\n allEmptyAction: 'New task',\n expandAria: 'Expand worklog',\n collapseAria: 'Collapse',\n\n ownerUnclaimed: 'unclaimed',\n fieldCreator: 'created by',\n fieldOwner: 'owner',\n fieldCreated: 'created',\n fieldStarted: 'started',\n fieldFinished: 'closed',\n timeUnknown: 'unknown time',\n ageDaysText: 'active {n}d',\n ageToday: 'today',\n actionResume: 'Resume',\n actionFinish: 'Close',\n actionRelease: 'Release',\n tipResume: 'keep working on this task',\n tipFinish: 'close and archive it',\n tipRelease: 'release the claim for others',\n actionClaim: 'Claim',\n tipClaim: 'claim this task for yourself',\n createdAt: 'created by {who} on {when}',\n startedAt: '{who} started on {when}',\n viewDetail: 'View details',\n fieldCreatedStamp: 'created {day}',\n mineEmpty: 'Nothing in progress right now',\n mineEmptyHint: 'Create a task in a workspace or claim one below, and it shows up here.',\n mineCount: '{n} items',\n worklogTitle: 'Worklog',\n worklogPath: 'path',\n worklogMissing: '(missing)',\n mdCopy: 'Copy',\n mdCopied: 'Copied',\n mdFootnotes: 'Footnotes',\n\n emptyNoWorkspace: 'No ledger workspace yet',\n emptyNoWorkspaceHint: 'Run the task-ledger skill init in a project (creates ledger/progress.json) and it shows up here.',\n emptyNoTask: 'No tasks yet',\n emptyNoTaskHint: 'Create one with the task-ledger skill new, or leave one here for others to claim.',\n emptyFiltered: 'No tasks match the current filters',\n\n badgeTitle: 'my todos',\n openBoard: 'Open task ledger',\n}\n\n/** 翻译函数类型(宿主渲染器按 `locale:` 声明合成的 `t` 席位同形)。 */\nexport type Translate = (key: LocaleKey, params?: Record<string, string | number>) => string\n\n/** 把宿主给的无参 t 包成带占位符替换的 t(模板一律 `{name}`,不依赖宿主是否支持 params)。 */\nexport function interpolateTranslate(base: Translate): Translate {\n return (key, params) => {\n const raw = base(key)\n if (params === undefined) return raw\n return raw.replace(/\\{(\\w+)\\}/g, (match, name: string) => (name in params ? String(params[name]) : match))\n }\n}\n\n/** 键名兜底:宿主没给 t 席位时用(键名绝不上界面)。 */\nexport function fallbackTranslate(key: LocaleKey, params?: Record<string, string | number>): string {\n return interpolateTranslate((name) => zh[name] ?? name)(key, params)\n}\n","/**\n * 右上浮标入口(`shell.overlay` list 条目,全帧浮层,条目并排添加不替换)。\n * 定位用宿主变量 `--dsh-frame-overlay-top`(标题栏净空 + 20px),**不写死 top 值**;\n * 浮层默认 `pointer-events:none`、子元素自动恢复 ⇒ 自绘条目不挡下方应用。\n *\n * 角标 = 我名下进行中计数(host 统计真值,SSE 已否 ⇒ 取自同一份快照,>0 才画);\n * 0→n 跳变时一次脉冲动画,不自动消失。宿主**无**通知 API ⇒ 「通知」就是这个角标。\n */\nimport { createElement as h, useEffect, useRef, useState, useSyncExternalStore, type ReactNode } from 'react'\nimport type { Translate } from './locales'\nimport { boardSnapshot, boardSubscribe } from './store'\nimport { ensureUiBase, LedgerIcon } from './ui'\n\nexport function OverlayEntry(props: { t: Translate; onOpen: () => void }): ReactNode {\n const { t, onOpen } = props\n ensureUiBase()\n const state = useSyncExternalStore(boardSubscribe, boardSnapshot)\n const count = state.board?.stats.myActive ?? 0\n const previous = useRef(0)\n const [pulse, setPulse] = useState(0)\n\n // 0→n 跳变播一次脉冲;计数归零不播(角标本就不渲染)。\n useEffect(() => {\n if (count > previous.current) setPulse(value => value + 1)\n previous.current = count\n }, [count])\n\n return h('div', {\n style: {\n position: 'absolute',\n top: 'var(--dsh-frame-overlay-top,20px)',\n right: '12px',\n zIndex: 'var(--dtl-z-badge,20)',\n pointerEvents: 'auto',\n },\n },\n h('button', {\n type: 'button',\n title: t('openBoard'),\n 'aria-label': t('openBoard'),\n onClick: onOpen,\n style: {\n position: 'relative',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: '32px',\n height: '32px',\n padding: 0,\n border: '1px solid var(--dtl-border)',\n borderRadius: 'var(--dtl-radius-sm)',\n background: 'var(--dtl-solid)',\n color: 'var(--dtl-on-solid)',\n boxShadow: 'var(--dtl-shadow-1)',\n cursor: 'pointer',\n },\n },\n h(LedgerIcon, { size: 18 }),\n count > 0\n ? h('span', { key: pulse, className: 'dtl-badge', 'data-pulse': pulse > 0, title: t('badgeTitle') }, String(count))\n : null,\n ),\n )\n}\n","/**\n * 浏览器半入口(client bundle,产物 dist/client.js):装配层,不写业务逻辑。\n *\n * 三处注册各自挂进「服务就位才触发」的 slots 注入(参考插件真机教训:顶层 inject 声明\n * 组合满足不了的依赖会让 entry 一直 pending、卡死整个 dsh 启动):\n * · locale 字典(zh / en)——界面语言跟随宿主,插件不自备开关;\n * · `main` keyed 槽整页(看板首页);\n * · `shell.overlay` list 槽浮标入口(点开 = `ctx.layout.selectPanel(<看板 id>)`)。\n *\n * 宿主能力一律**本地结构化声明**,不 import 官方包(rc 接口会无声变);\n * `t` 席位由宿主渲染器按注册项的 `locale:` 声明合成进组件 props。\n */\nimport { createElement as h } from 'react'\nimport { BoardHome } from './board-home'\nimport { en, fallbackTranslate, interpolateTranslate, zh, type LocaleKey, type Translate } from './locales'\nimport { LedgerIcon } from './ui'\nimport { OverlayEntry } from './overlay-entry'\n\n/** 命名空间(settings / locale / 面板 id 同名,便于对应)。 */\n/** 模块级 t 席位(侧栏 label 渲染期求值用;绑不上就回落键名,绝不上界面)。 */\nlet runtimeT: Translate = (key) => key\n\nconst NS = 'dsh-task-ledger'\n/** `main` 槽的 key(非 `conversation` ⇒ 不绑 Session)。 */\nconst PANEL_ID = NS\n/** `shell.overlay` list 条目的 id(新 id = 并排添加,不替换宿主条目)。 */\nconst OVERLAY_ID = `${NS}-badge`\n\n/** 浏览器插件上下文:只声明本文件实际用到的服务面。 */\ninterface ClientContext {\n inject(deps: readonly string[], callback: (ctx: ClientContext) => void): void\n effect(setup: () => void): void\n locale?: {\n register(ns: string, dictionaries: Record<string, Record<string, string>>): void\n /** 绑一份无参 t(侧栏 label 在渲染期求值,拿不到组件 props 的 t 席位)。 */\n bind(ns: string): Translate\n }\n layout?: {\n selectPanel(panelId: string | null): void\n }\n slots: {\n inject(slot: string, factory: () => () => void): () => void\n register(options: Record<string, unknown>, component: unknown): () => void\n }\n}\n\n/** 宿主没给 t 席位时的兜底(键名绝不上界面)。 */\nconst t: Translate = interpolateTranslate((key: LocaleKey, params?: Record<string, string | number>) =>\n fallbackTranslate(key, params))\n\nexport function apply(ctx: ClientContext): void {\n // ① 字典注册(宿主语言切换 ⇒ 渲染器换 t 引用 ⇒ 整页重渲染)\n ctx.inject(['locale'], (localeCtx) => {\n ctx.effect(() => localeCtx.locale?.register(NS, { zh, en }))\n // 模块级 t 席位:`sidebar.panellist` 的 label 由侧栏在渲染期求值,拿不到组件 props 的 t\n try { runtimeT = localeCtx.locale?.bind(NS) ?? runtimeT } catch { /* 保持键名兜底 */ }\n })\n\n // ② 布局服务:浮标点开整页 / 整页返回会话\n let selectPanel: (id: string | null) => void = () => {}\n ctx.inject(['layout'], (sub) => {\n const layout = sub.layout\n if (layout !== undefined) selectPanel = (id) => { layout.selectPanel(id) }\n })\n\n // ③ 槽位注册(只依赖 slots,自成一块:locale / layout 缺席也不该让入口消失)\n ctx.inject(['slots'], (sub) => {\n // 左侧入口:侧栏面板条目(list 槽)。**行按钮、点击、激活态全由侧栏 / 布局服务托管**,\n // 组件只画图标;id 必须与 main 槽的 key 一致(同一 MainPanelId)。\n // 事实见 external/client-ui-layout.md §一/§二(0.2.0-rc.2 亲验)。\n sub.slots.inject('sidebar.panellist', () =>\n sub.slots.register(\n { name: 'sidebar.panellist', id: PANEL_ID, order: 30, label: () => runtimeT('panelTitle'), locale: NS },\n (props: { size?: number }) => h(LedgerIcon, { size: props.size ?? 18 }),\n ),\n )\n sub.slots.inject('main', () =>\n sub.slots.register(\n { name: 'main', key: PANEL_ID, locale: NS },\n (props: { t?: Translate }) => h(BoardHome, {\n t: props.t ?? t,\n onBack: () => { selectPanel(null) },\n }),\n ),\n )\n sub.slots.inject('shell.overlay', () =>\n sub.slots.register(\n { name: 'shell.overlay', id: OVERLAY_ID, locale: NS },\n (props: { t?: Translate }) => h(OverlayEntry, {\n t: props.t ?? t,\n onOpen: () => { selectPanel(PANEL_ID) },\n }),\n ),\n )\n })\n}\n"],"mappings":"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"}
@@ -48,8 +48,6 @@ export function computeSnapshot(scopes, now) {
48
48
  const backlogTasks = withMeta.filter((task) => task.backlog);
49
49
  const overdueTasks = withMeta.filter((task) => task.overdue);
50
50
  const staleTasks = withMeta.filter((task) => task.stale);
51
- // 「最长 N 天」小字:本块 0 条时给 null(前端不显示小字),不拿 0 充数
52
- const maxOf = (tasks, pick) => tasks.length === 0 ? null : tasks.reduce((max, task) => Math.max(max, pick(task) ?? 0), 0);
53
51
  const stats = {
54
52
  myActive,
55
53
  myOverdue: overdueTasks.filter(mine).length,
@@ -65,8 +63,6 @@ export function computeSnapshot(scopes, now) {
65
63
  oldestOpenDays: openTasks.length === 0
66
64
  ? null
67
65
  : openTasks.reduce((max, task) => Math.max(max, daysSince(task.created, now) ?? 0), 0),
68
- maxOverdueAge: maxOf(overdueTasks, (task) => task.ageDays),
69
- maxStaleIdle: maxOf(staleTasks, (task) => task.idleDays),
70
66
  };
71
67
  return { at: now.toISOString(), workspaces: scopes.map((scope) => scope.workspace), tasks: withMeta, stats, identities };
72
68
  }
@@ -126,9 +126,6 @@ export interface BoardStats {
126
126
  readonly backlogDays: number;
127
127
  /** 最老一条待认领躺了多少天(没有待认领 = null)。 */
128
128
  readonly oldestOpenDays: number | null;
129
- /** 逾期 / 停滞各自的最长天数(该异常 0 条 = null)——卡片小字「最长 N 天」。 */
130
- readonly maxOverdueAge: number | null;
131
- readonly maxStaleIdle: number | null;
132
129
  }
133
130
  /** 列表行附加工资字段(host 算,前端不判阈值)。 */
134
131
  export interface BoardTask extends TaskRow {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-task-ledger",
3
- "version": "0.0.68",
3
+ "version": "0.0.70",
4
4
  "description": "Task ledger board for dsh (DeepSeek Harness): read-only board over task-ledger skill workspaces. The task-ledger skill is bundled — install the plugin, get the skill globally.",
5
5
  "keywords": [
6
6
  "deepseek",
package/skill/README.md CHANGED
@@ -118,7 +118,8 @@
118
118
  - 临时文件名唯一(`mkstemp`),写完 `fsync` 再 `os.replace`,无论成败都清理临时文件
119
119
  - key 按字母序落盘,减少两人同时新增时的文本冲突
120
120
  - 冲突时**报错要求重跑**,不静默覆盖
121
- - 写入成功后脚本会**自动同步**:把本次改动的台账文件 `git add <精确文件>` + `commit`(消息 `ledger(<命令>): <提交者>`);当前分支有上游跟踪分支时再 `push`。全程确定性脚本,不依赖大模型
121
+ - 写入成功后脚本会**自动同步**:把本次改动的台账文件 `git add <精确文件>` + `commit`(消息 `chore(ledger): <命令> <提交者>`,即 conventional commits 格式,多数仓库装了 commitlint 也认);当前分支有上游跟踪分支时再 `push`。全程确定性脚本,不依赖大模型
122
+ - 仓库若装了自定义提交检查并拒了这条消息,脚本会**再用 `--no-verify` 提交一次**(台账是数据文件,不过 lint);仍失败则把 git 的报错打到 stderr 并要求手动提交 —— **同步失败绝不静默**,但退出码保持 0(台账本身已写成功)
122
123
  - 无 git 仓库 / 无上游跟踪 / 被 `.gitignore` 排除 / 无实际变更 都会静默跳过;`--no-sync` 或环境变量 `TASK_LEDGER_NO_SYNC=1` 可关闭
123
124
  - 冲突**仍不靠 `git pull` / `rebase` 解决**:并发靠指纹复查 + 原子写防覆盖,冲突时**报错要求重跑**,不静默合并
124
125
 
package/skill/SKILL.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: task-ledger
3
3
  description: 本地任务台账,跨会话记录在办任务的归属、进度与起止。当用户要登记一件新的事(新建任务 / 建个坑 / 这活我领了)、记录已有任务的进展(接着做 / 记一条进展 / 这轮先记下)、查看任务状态或历史(现在该做什么 / 这活谁在做 / 上周做过什么)时加载;对某个具体任务要求结案或退回时,只认用户的明确指令;只是改代码、讨论方案时不要用。
4
- version: 1.2.2
4
+ version: 1.2.6
5
5
  allowed-tools: Read,Write,Bash,Grep
6
6
  ---
7
7
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "task-ledger",
3
- "version": "1.2.5",
3
+ "version": "1.2.6",
4
4
  "description": "Task-ledger skill for coding agents: per-workspace progress tracking (ledger/progress.json, history, worklog) driven by a CLI script. Standalone install for CodeBuddy and other agents; also bundled inside the dsh-task-ledger DSH plugin.",
5
5
  "keywords": [
6
6
  "skill",
@@ -354,9 +354,17 @@ def commit_guarded(
354
354
  write_json(path, data)
355
355
 
356
356
 
357
- def _git(*args: str, timeout: int | None = None) -> subprocess.CompletedProcess:
358
- """跑一条 git 命令,吞掉输出(同步用,不污染 stdout)。"""
359
- return subprocess.run(list(args), capture_output=True, text=True, timeout=timeout)
357
+ def _git(
358
+ *args: str, timeout: int | None = None, check: bool = False
359
+ ) -> subprocess.CompletedProcess:
360
+ """跑一条 git 命令,吞掉输出(同步用,不污染 stdout)。
361
+
362
+ check=True 让失败抛异常(推送要能被下面的兜底捕获);提交则靠返回码分辨,
363
+ 因为不同仓库的提交检查(commitlint 等)会拒掉消息,需要看 stderr 原文。
364
+ """
365
+ return subprocess.run(
366
+ list(args), capture_output=True, text=True, timeout=timeout, check=check
367
+ )
360
368
 
361
369
 
362
370
  def _git_actor(repo_root: str) -> str:
@@ -369,7 +377,8 @@ def git_sync(action: str) -> None:
369
377
  """写成功后调用一次:把本次改动的台账文件提交;有上游跟踪分支则推送。
370
378
 
371
379
  纯脚本、确定性,不依赖大模型。无 git / 无上游 / 全被 ignore / 无实际变更 都静默跳过。
372
- 同步是尽力而为:任何异常都不影响已完成的台账写入(只打印提示)。
380
+ 同步失败(提交被仓库钩子拒后连 --no-verify 也失败、推送被拒、意外异常)一律打到
381
+ stderr 提示,绝不静默;但退出码不变 —— 台账本身已经写成功,别让调用方误判去重做。
373
382
  """
374
383
  if os.environ.get("TASK_LEDGER_NO_SYNC"):
375
384
  return
@@ -382,29 +391,49 @@ def git_sync(action: str) -> None:
382
391
  if top.returncode != 0:
383
392
  return # 不是 git 项目 → 跳过(规则1:有才提交)
384
393
  root = top.stdout.strip()
394
+ g = ("git", "-C", root)
385
395
  # 只 add 本次真实改动、且未被 ignore 的文件
386
396
  to_add = [
387
397
  str(f)
388
398
  for f in files
389
- if _git("git", "-C", root, "check-ignore", "-q", str(f)).returncode != 0
399
+ if _git(*g, "check-ignore", "-q", str(f)).returncode != 0
390
400
  ]
391
401
  if not to_add:
392
402
  return # 全被 ignore → 尊重用户选择,不强行提交
393
- _git("git", "-C", root, "add", *to_add)
394
- if _git("git", "-C", root, "diff", "--cached", "--quiet").returncode == 0:
403
+ _git(*g, "add", *to_add)
404
+ if _git(*g, "diff", "--cached", "--quiet").returncode == 0:
395
405
  return # 无实际变更
396
406
  actor = _git_actor(root)
397
- _git("git", "-C", root, "commit", "-m", f"ledger({action}): {actor}")
407
+ # 消息按 conventional commits 写:多数仓库装了 commitlint,只认白名单里的 type
408
+ msg = f"chore(ledger): {action} {actor}"
409
+ done = _git(*g, "commit", "-m", msg)
410
+ if done.returncode != 0:
411
+ # 各仓库提交规则各不相同(type 白名单、大小写、scope 枚举……),台账是数据文件,
412
+ # 过不了就让这一次跳过钩子提交,别把台账永久锁在本地暂存区。
413
+ done = _git(*g, "commit", "--no-verify", "-m", msg)
414
+ if done.returncode == 0:
415
+ print(
416
+ f"提示:仓库提交检查拒绝了消息「{msg}」,已用 --no-verify 提交本次台账改动。",
417
+ file=sys.stderr,
418
+ )
419
+ if done.returncode != 0:
420
+ print(
421
+ "提示:台账已写入本地,但自动提交失败,请手动提交 ledger/:"
422
+ f"{(done.stderr or done.stdout or '').strip()}",
423
+ file=sys.stderr,
424
+ )
425
+ return
398
426
  # 规则2:有上游跟踪分支才推
399
- if _git("git", "-C", root, "rev-parse", "--abbrev-ref", "@{upstream}").returncode != 0:
427
+ upstream = _git(*g, "rev-parse", "--abbrev-ref", "@{upstream}")
428
+ if upstream.returncode != 0:
400
429
  return
401
430
  try:
402
- _git("git", "-C", root, "push", timeout=30)
431
+ _git(*g, "push", timeout=30, check=True)
403
432
  except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
404
433
  # 非快进:自动 rebase 一次再推;冲突 / 超时则放弃,绝不 --force
405
434
  try:
406
- _git("git", "-C", root, "pull", "--rebase", "--autostash", timeout=30)
407
- _git("git", "-C", root, "push", timeout=30)
435
+ _git(*g, "pull", "--rebase", "--autostash", timeout=30, check=True)
436
+ _git(*g, "push", timeout=30, check=True)
408
437
  except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
409
438
  print(
410
439
  "提示:本地已提交,但推送被拒(他人已推 / 需认证 / 超时)。"
@@ -412,8 +441,10 @@ def git_sync(action: str) -> None:
412
441
  file=sys.stderr,
413
442
  )
414
443
  except Exception as exc: # noqa: BLE001 —— 同步失败不应污染台账主流程
415
- print(f"提示:写后自动同步未执行({type(exc).__name__}: {exc})。数据已写入本地。",
416
- file=sys.stderr)
444
+ print(
445
+ f"提示:写后自动同步未执行({type(exc).__name__}: {exc})。数据已写入本地。",
446
+ file=sys.stderr,
447
+ )
417
448
 
418
449
 
419
450
  def require_task_id(tid: str) -> str: