dsh-task-ledger 0.0.6 → 0.0.7
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 +13 -1
- package/dist/client.js.map +1 -1
- package/package.json +1 -1
- package/skill/package.json +1 -1
- package/skill/scripts/progress.py +29 -13
package/dist/client.js
CHANGED
|
@@ -10,6 +10,18 @@ window.__ModuleLoader__.load({
|
|
|
10
10
|
/** host 路由前缀(全仓唯一;与 host/routes.ts 的 API_PREFIX 对应)。 */
|
|
11
11
|
const API_PREFIX = "api/task-ledger";
|
|
12
12
|
const DEFAULT_TIMEOUT_MS = 8e3;
|
|
13
|
+
/**
|
|
14
|
+
* 错误响应榨人话:host 路由(routes.ts)发的是 `{ error: "..." }`,那里的话是**能直接上界面的**
|
|
15
|
+
* (带工作区名、python3 起不来之类的原文)。不带它界面上只剩「HTTP 500」,等于没报错。
|
|
16
|
+
*/
|
|
17
|
+
function httpErrorMessage(status, body) {
|
|
18
|
+
try {
|
|
19
|
+
const parsed = JSON.parse(body);
|
|
20
|
+
if (typeof parsed.error === "string" && parsed.error !== "") return `HTTP ${status}:${parsed.error}`;
|
|
21
|
+
} catch {}
|
|
22
|
+
const text = body.trim().slice(0, 200);
|
|
23
|
+
return text === "" ? `HTTP ${String(status)}` : `HTTP ${String(status)}:${text}`;
|
|
24
|
+
}
|
|
13
25
|
/** 带超时的 JSON 取数(相对路径,同源)。 */
|
|
14
26
|
async function fetchJson(path, init = {}, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
15
27
|
const controller = new AbortController();
|
|
@@ -22,7 +34,7 @@ window.__ModuleLoader__.load({
|
|
|
22
34
|
signal: controller.signal,
|
|
23
35
|
cache: "no-store"
|
|
24
36
|
});
|
|
25
|
-
if (!res.ok) throw new Error(
|
|
37
|
+
if (!res.ok) throw new Error(httpErrorMessage(res.status, await res.text().catch(() => "")));
|
|
26
38
|
return await res.json();
|
|
27
39
|
} finally {
|
|
28
40
|
clearTimeout(timer);
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["h","useState","useMemo","h","StateDot","IconChevronDownOutlineRegular","IconChevronRightOutlineRegular","MarkdownText","useSyncExternalStore","useState","useMemo","h","Button","SegmentedControl","IconRefreshOutlineRegular","h","useSyncExternalStore","useRef","useState","h"],"sources":["../src/client/api.ts","../src/client/format.ts","../src/client/store.ts","../src/client/rows.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, FilterOptions, HistoryRecord, TaskDetail } 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\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(`HTTP ${res.status}`)\n return await res.json() as T\n } finally {\n clearTimeout(timer)\n }\n}\n\n/** 首页快照(在办全量 + 两个完成类统计 + 各工作区身份)。 */\nexport function fetchBoard(): Promise<BoardPayload> {\n return fetchJson<BoardPayload>('/board')\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 * 客户端时间口径**单源**:补零 / 时刻串 / 时间范围 → `--since` / `--until`。\n * 走**本机时区**;解析失败按调用方给的兜底返回(不编造时间)。\n *\n * ⚠️ 与 skill 脚本的边界一致:`--since` 取当天 00:00、`--until` 只给日期时含当天整天\n * (脚本 `normalize_time` 的 `date_only_ref=\"23:59:59\"`),故这里只拼 `YYYY-MM-DD`。\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/** ISO → `YYYY-MM-DD`。 */\nexport function formatYmd(iso: string): string {\n const ms = Date.parse(iso)\n if (Number.isNaN(ms)) return iso\n const d = new Date(ms)\n return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`\n}\n\n/** 时间范围档 → 脚本参数(只给日期;`all` = 不过滤)。 */\nexport type RangeKey = 'all' | 'week' | 'month' | 'year'\n\n/** 周一 00:00 所在日期(与 host `board.ts` 的 `mondayDateString` 同口径:周一为一周之始)。 */\nfunction mondayOf(now: Date): Date {\n const date = new Date(now)\n date.setDate(date.getDate() - ((date.getDay() + 6) % 7))\n return date\n}\n\n/**\n * 时间范围 → `{ since, until }`(`YYYY-MM-DD`,含边界)。`all` 返回空对象 = 不拼参数。\n * 近一年 = 今天往前推 365 天(与统计卡「近 13 个月文件」不同口径:那是文件窗口,这是创建时间过滤)。\n */\nexport function rangeToDates(range: RangeKey, now: Date = new Date()): { since?: string; until?: string } {\n const ymd = (date: Date): string => `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`\n if (range === 'week') return { since: ymd(mondayOf(now)), until: ymd(now) }\n if (range === 'month') return { since: ymd(new Date(now.getFullYear(), now.getMonth(), 1)), until: ymd(now) }\n if (range === 'year') {\n const start = new Date(now)\n start.setDate(start.getDate() - 365)\n return { since: ymd(start), until: ymd(now) }\n }\n return {}\n}\n\n/** 任务行是否落在时间范围内(在办侧在浏览器里筛;无创建时间的行按「不带时间过滤」处理)。 */\nexport function withinRange(created: string | null | undefined, since: string | undefined, until: string | undefined): boolean {\n if (since === undefined && until === undefined) return true\n if (created === null || created === undefined || created === '') return false\n const day = created.slice(0, 10)\n if (since !== undefined && day < since) return false\n if (until !== undefined && day > until) return false\n return true\n}\n","/**\n * 看板数据仓(client 侧**唯一**取数入口):浮标角标与整页共用同一份快照,\n * 谁先挂载谁触发首读;刷新按钮走同一个 `refresh()`,**不轮询**(2026-10-17 拍板:\n * 后端纯按需,刷新时机归前端 = 打开面板首读 + 手动刷新)。\n *\n * 消费方式:`useSyncExternalStore(boardSubscribe, boardSnapshot)`。\n * ⚠️ `getSnapshot` 必须返回**稳定引用**(每次新建对象会被判定为「一直在变」⇒ 无限重渲染),\n * 故只在真变了时才换 `state` 对象。\n */\nimport { fetchBoard, fetchFilters, fetchHistory } from './api'\nimport { rangeToDates, type RangeKey } from './format'\nimport type { BoardPayload, FilterOptions, HistoryRecord } from './types'\n\n/** 状态档(历史查询 = 「已结案」档,不单开 tab)。 */\nexport type Bucket = 'all' | 'mine' | 'open' | 'closed'\n/** 已结案列表分页大小(「加载更多」= 加大 limit 重取)。 */\nexport const HISTORY_PAGE = 500\n\nexport interface Query {\n bucket: Bucket\n range: RangeKey\n tag: string\n keyword: string\n limit: number\n}\n\nexport interface BoardState {\n status: 'loading' | 'ready' | 'error'\n board: BoardPayload | null\n error: string | null\n history: readonly HistoryRecord[]\n historyStatus: 'idle' | 'loading' | 'ready' | 'error'\n historyError: string | null\n filters: FilterOptions | null\n query: Query\n /** 手动刷新在途(按钮转圈 / 禁用)。 */\n refreshing: boolean\n}\n\nconst INITIAL_QUERY: Query = { bucket: 'all', range: 'all', tag: '', keyword: '', limit: HISTORY_PAGE }\n\nlet state: BoardState = {\n status: 'loading',\n board: null,\n error: null,\n history: [],\n historyStatus: 'idle',\n historyError: null,\n filters: null,\n query: INITIAL_QUERY,\n refreshing: false,\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/** 重读:在办快照 + 筛选项 +(已结案档时)历史列表。 */\nexport async function refresh(): Promise<void> {\n const token = ++runToken\n set({ refreshing: true })\n try {\n const board = await fetchBoard()\n if (token !== runToken) return\n set({ status: 'ready', board, error: null })\n // 筛选项非首屏必需:失败不影响首页(下拉显示「全部标签」即可),故不进 error 态。\n void loadFilters(token)\n if (state.query.bucket === 'closed') await loadHistory(token)\n } catch (error) {\n if (token !== runToken) return\n set({ status: 'error', error: error instanceof Error ? error.message : String(error) })\n } finally {\n if (token === runToken) set({ refreshing: false })\n }\n}\n\nasync function loadFilters(token: number): Promise<void> {\n try {\n const filters = await fetchFilters()\n if (token === runToken) set({ filters })\n } catch { /* 候选拿不到就保持 null:下拉只有「全部标签」,不谎报选项 */ }\n}\n\nasync function loadHistory(token: number): Promise<void> {\n const { range, tag, limit } = state.query\n const dates = rangeToDates(range)\n set({ historyStatus: 'loading', historyError: null })\n try {\n const records = await fetchHistory({ ...dates, tag, limit })\n if (token !== runToken) return\n set({ history: records, historyStatus: 'ready' })\n } catch (error) {\n if (token !== runToken) return\n set({ historyStatus: 'error', historyError: error instanceof Error ? error.message : String(error) })\n }\n}\n\n/** 改筛选:已结案档要重取历史(服务端过滤);在办档只在浏览器里重筛(数据已全)。 */\nexport function setQuery(patch: Partial<Query>): void {\n const query: Query = { ...state.query, ...patch }\n set({ query })\n if (query.bucket === 'closed') void loadHistory(++runToken)\n}\n\n/** 「加载更多」:加大 limit 重取(skill 脚本无 offset,笨办法;500 条一页无感)。 */\nexport function loadMoreHistory(): void {\n setQuery({ limit: state.query.limit + HISTORY_PAGE })\n}\n\n\n","/**\n * 列表行的筛选 / 排序(**唯一**实现,纯函数、不碰网络):页面只渲染,不各写一份过滤。\n *\n * 单独成文件是为了能被冒烟直测(store.ts 引 api.ts,Node 剥类型解析不了无扩展名相对导入)。\n */\n// ⚠️ 相对导入一律带 `.ts` 扩展名:client 侧 tsconfig 开 `allowImportingTsExtensions`,\n// tsdown 单文件打包认得,Node 原生剥类型也认得 —— 冒烟才能直测本文件的纯筛选逻辑\n// (不带扩展名的话 Node 的 ESM 解析器找不到模块)。\nimport { rangeToDates, withinRange } from './format.ts'\nimport type { BoardPayload, HistoryRecord, TaskRow } from './types.ts'\nimport type { Query } from './store.ts'\n\n/** 列表行:在办与已结案两套形状,行件按 kind 分支(不做「统一成一个假结构」的归一化)。 */\nexport type Row =\n | { kind: 'task'; key: string; task: TaskRow; mine: boolean }\n | { kind: 'closed'; key: string; record: HistoryRecord }\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/**\n * 当前筛选下的列表行:\n * - 已结案档:history 由服务端按窗口 / 标签 / 分页过滤,浏览器只补关键字;\n * - 在办三档:快照已含全部在办 ⇒ 全在浏览器里筛(档位 / 时间 / 标签 / 关键字)。\n */\nexport function selectRows(current: { board: BoardPayload | null; history: readonly HistoryRecord[]; query: Query }): Row[] {\n const { board, history, query } = current\n if (query.bucket === 'closed') {\n return history\n .filter(record => hitKeyword(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 const dates = rangeToDates(query.range)\n const tasks = board?.tasks ?? []\n return tasks\n .filter((task) => {\n if (query.bucket === 'open' && task.status !== 'open') return false\n if (query.bucket === 'mine' && !isMine(task, board)) return false\n if (!withinRange(task.created, dates.since, dates.until)) return false\n if (query.tag !== '' && !(task.tags ?? []).includes(query.tag)) return false\n return hitKeyword(query.keyword, [task.title, task.id, task.owner, task.creator, task.category, ...(task.tags ?? [])])\n })\n // 按创建 / 开始时间倒序(最新的在上)\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: isMine(task, board) }))\n}\n\n/** 「我的」判定:owner = 该工作区的当前 git 身份,且状态是 doing / blocked(与 host 统计同口径)。 */\nexport function isMine(task: TaskRow, 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 * 语义 token 层:`--dtl-*`(dtl = dsh-task-ledger)。\n *\n * 规矩(与 docs/design/ui-style.md 两条硬约束一致):\n * - **只有本文件定义 `--dtl-*`**;其它文件一律 `var(--dtl-*)` 消费,不许直接写 `var(--dsw-*)`;\n * - 每个 token 都带兜底值 ⇒ 宿主升级换名时不炸(最坏退到兜底色,不会白屏);\n * - 明暗差异只写在本文件末尾的 `body[data-ds-dark-theme]` 段:能靠宿主 alias 表达的\n * 一律不写覆盖(alias 自己随主题变),只有「固定中性面 / 半透明底」这类 alias 表达不了的才进该段。\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 /* 反色面上的字(角标 / 实心钮):暗色下是 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 /* 固定中性实面(自绘浮标 / 角标这类必须自己变的东西) */\n --dtl-solid:var(--dsw-static-neutral-00,#fff);\n --dtl-on-solid:var(--dsw-alias-label-primary,#1f2328);\n\n /* ── 描边 ────────────────────────────────────────────────────── */\n --dtl-border:var(--dsw-alias-border-l2,#0000001a);\n --dtl-border-strong:var(--dsw-alias-border-l3,#0000001f);\n\n /* ── 语义色 ──────────────────────────────────────────────────── */\n /* 蓝色强调 / 选中一律走这条(不是 --dsw-alias-brand-primary:它明色近黑、暗色近白) */\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 --dtl-on-signal:#fff;\n /* 语义色浅底:用 color-mix 现算 ⇒ 状态色跟随宿主 alias,主题一换自动成立 */\n --dtl-business-soft:color-mix(in srgb,var(--dtl-business) 8%,transparent);\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 /* 展开行底:带透明度的蓝,透出卡片底色(灰底跟斑马纹分不出来) */\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\n /* ── 圆角 / 间距 / 控件高度 / 字号 ────────────────────────────── */\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-space-1:4px;\n --dtl-space-2:8px;\n --dtl-space-3:12px;\n --dtl-space-4:16px;\n --dtl-control-h-sm:24px;\n --dtl-control-h-md:28px;\n --dtl-control-h-lg:32px;\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-font-mono:var(--ds-font-family-code,monospace);\n\n /* ── 投影 / 动效 / 层级 ──────────────────────────────────────── */\n --dtl-shadow-1: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-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 /* 暗色下蓝要更亮、透明度更高才压得住深底 */\n --dtl-open-bg:rgba(96,165,250,.18);\n --dtl-open-bg-soft:rgba(96,165,250,.08);\n /* 暗色 chip 底板反过来掺白 */\n --dtl-chip-bg:rgba(255,255,255,.08);\n --dtl-chip-bg-hover:rgba(255,255,255,.16);\n /* 语义色浅底在深色下压到 5%(8% 在深底上偏重) */\n --dtl-business-soft:color-mix(in srgb,var(--dtl-business) 5%,transparent);\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 * UI 基础层:样式单源 + 共用元件。\n *\n * 两条纪律(用户 2026-10-17 要求「该抽象的必须抽象」):\n * 1. **样式**:所有颜色 / 圆角 / 字号一律 `var(--dtl-*)`(定义只在 tokens.ts),\n * 任何文件不许再写第二份 `var(--dsw-*)`、也不许自造 `--dtl-*`;\n * 需要 hover / focus / 动画的(内联样式表达不了)才有资格进这里的 `UI_BASE_CSS`。\n * 2. **元件**:卡 / chip / 下拉 / 空态 / 搜索框各只有一份实现,页面与浮标共用。\n */\nimport { createElement as h, type ChangeEvent, type ReactNode } from 'react'\nimport { UI_TOKENS_CSS } from './tokens'\n\n/** 基础层样式(幂等注入;只装 hover / focus / 动画这些内联样式写不了的部分)。 */\nconst UI_BASE_CSS = `\n/* 统计卡:可点、hover 抬底、选中走蓝描边 + 浅蓝底 */\n.dtl-card{\n display:flex;flex-direction:column;gap:var(--dtl-space-1);\n padding:var(--dtl-space-3) var(--dtl-space-4);\n border:1px solid var(--dtl-border);border-radius:var(--dtl-radius-md);\n background:var(--dtl-surface-1);color:var(--dtl-fg);\n cursor:pointer;text-align:left;font:inherit;\n transition:background var(--dtl-dur) var(--dtl-ease),border-color var(--dtl-dur) var(--dtl-ease);\n}\n.dtl-card:hover{background:var(--dtl-hover)}\n.dtl-card[aria-pressed=\"true\"]{border-color:var(--dtl-business);background:var(--dtl-business-soft)}\n.dtl-card:focus-visible{outline:2px solid var(--dtl-business);outline-offset:2px}\n\n/* 列表行:头部可点,hover 抬底;展开行走 --dtl-open-bg */\n.dtl-row{border:1px solid var(--dtl-border);border-radius:var(--dtl-radius-md);overflow:hidden}\n.dtl-row + .dtl-row{margin-top:var(--dtl-space-2)}\n.dtl-row-head{\n display:flex;align-items:center;gap:var(--dtl-space-2);\n width:100%;padding:var(--dtl-space-3);border:0;background:transparent;\n color:inherit;font:inherit;text-align:left;cursor:pointer;\n transition:background var(--dtl-dur) var(--dtl-ease);\n}\n.dtl-row-head:hover{background:var(--dtl-hover)}\n.dtl-row-head: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:0 var(--dtl-space-3) var(--dtl-space-3);border-top:1px solid var(--dtl-border)}\n\n/* chip(分类 / 标签):小底板走 --dtl-chip-bg,hover 加深 */\n.dtl-chip{\n display:inline-flex;align-items:center;gap:var(--dtl-space-1);\n padding:0 var(--dtl-space-2);height:20px;border-radius:var(--dtl-radius-sm);\n background:var(--dtl-chip-bg);color:var(--dtl-fg-2);\n font-size:var(--dtl-font-xs);white-space:nowrap;max-width:20ch;overflow:hidden;text-overflow:ellipsis;\n}\n\n/* 原生下拉 / 输入框:宿主没有廉价的选择器,用原生控件 + token 配色(不用自绘菜单) */\n.dtl-select,.dtl-input{\n height:var(--dtl-control-h-md);box-sizing:border-box;\n padding:0 var(--dtl-space-2);border:1px solid var(--dtl-border);border-radius:var(--dtl-radius-sm);\n background:var(--dtl-surface-1);color:var(--dtl-fg);\n font-family:inherit;font-size:var(--dtl-font-md);\n}\n.dtl-input{padding-left:var(--dtl-space-3);min-width:220px}\n.dtl-select:focus-visible,.dtl-input:focus-visible{outline:2px solid var(--dtl-business);outline-offset:1px}\n\n/* 空态:真空 = 虚线引导卡;筛选空 = 小字 */\n.dtl-empty-guide{\n padding:var(--dtl-space-4);border:1px dashed var(--dtl-border-strong);border-radius:var(--dtl-radius-md);\n background:transparent;\n}\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{\n 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}\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/* 过程正文:宽度受限、长行折行 */\n.dtl-worklog{max-width:72ch;font-size:var(--dtl-font-md);line-height:1.6}\n`\nconst STYLE_ID = 'dtl-ui-base'\n\n/** 幂等注入 token 层 + 基础层(面板与浮标谁先挂载都只注入一次)。 */\nexport function ensureUiBase(): void {\n if (typeof document === 'undefined') return\n if (document.getElementById(STYLE_ID) !== null) return\n const style = document.createElement('style')\n style.id = STYLE_ID\n style.textContent = `${UI_TOKENS_CSS}\\n${UI_BASE_CSS}`\n document.head.appendChild(style)\n}\n\n/** 统计卡:点一下联动下方列表筛选(aria-pressed = 选中态)。 */\nexport function StatCard(props: {\n label: string\n value: string\n active: boolean\n title?: string\n onClick: () => void\n}): ReactNode {\n return h('button', {\n type: 'button',\n className: 'dtl-card',\n 'aria-pressed': props.active,\n title: props.title,\n onClick: props.onClick,\n },\n h('span', { style: { fontSize: 'var(--dtl-font-sm)', color: 'var(--dtl-fg-2)' } }, props.label),\n h('span', { style: { fontSize: '24px', fontWeight: 600, lineHeight: 1.2 } }, props.value),\n )\n}\n\n/** chip(分类 / 标签 / 工作区名)。 */\nexport function Chip(props: { text: string; tone?: 'plain' | 'signal'; color?: string }): ReactNode {\n const style: Record<string, string> = {}\n if (props.tone === 'signal' && props.color !== undefined) {\n style.background = props.color\n style.color = 'var(--dtl-on-signal)'\n }\n return h('span', { className: 'dtl-chip', style, title: props.text }, props.text)\n}\n\n/** 原生下拉(时间 / 标签筛选共用一份)。 */\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}): ReactNode {\n return h('select', {\n className: 'dtl-select',\n 'aria-label': props.ariaLabel,\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/** 搜索框(关键字过滤)。 */\nexport function SearchInput(props: {\n value: string\n placeholder: string\n onChange: (next: string) => void\n ariaLabel: string\n}): ReactNode {\n return h('input', {\n className: 'dtl-input',\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/** 空态:`guide` = 真空引导卡(虚线),`filtered` = 筛选空小字。 */\nexport function EmptyState(props: { tone: 'guide' | 'filtered'; title: string; hint?: string }): ReactNode {\n if (props.tone === 'filtered') {\n return h('div', { className: 'dtl-empty-filtered' }, props.title)\n }\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 * 任务列表行(宿主**无表格件** ⇒ 自绘,几何参照官方 `DisclosureRow`,状态走官方 `StateDot`)。\n * 定稿见 docs/design/features/board-home.md §二 与 docs/design/ui-style.md:\n * 状态语义 open=idle / doing=ongoing / blocked=warning / 已结案=done。\n *\n * 行展开 = 过程文档路径 + 过程正文(在办走 `GET /task` 取正文;已结案只有路径——\n * host 没有按历史行取正文的路由,不编造内容)。\n */\nimport { createElement as h, useEffect, useMemo, useState, type ReactNode } from 'react'\nimport {\n IconChevronDownOutlineRegular, IconChevronRightOutlineRegular, MarkdownText, StateDot,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport { fetchTaskDetail } from './api'\nimport { formatDateTime } from './format'\nimport type { Translate } from './locales'\nimport type { Row } from './rows'\nimport { Chip } from './ui'\nimport type { TaskDetail } from './types'\n\n/** 行的状态点语义(在办三态 + 已结案)。 */\nfunction dotOf(row: Row): 'idle' | 'ongoing' | 'warning' | 'done' {\n if (row.kind === 'closed') return 'done'\n if (row.task.status === 'blocked') return 'warning'\n if (row.task.status === 'doing') return 'ongoing'\n return 'idle'\n}\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/** 行的「谁」:负责人优先,没有就创建人,都没有 = 未认领。 */\nfunction whoOf(row: Row): string | null {\n if (row.kind === 'task') return row.task.owner ?? row.task.creator ?? null\n return row.record.owner ?? null\n}\n\nexport function TaskRowView(props: { t: Translate; row: Row }): ReactNode {\n const { t, row } = props\n const [open, setOpen] = useState(false)\n\n // 官方 MarkdownText 要求 labels 引用稳定(新身份会丢弃渲染缓存)⇒ 按 t 记忆一次。\n const mdLabels = useMemo(() => ({ code: { copyLabel: t('mdCopy'), copiedLabel: t('mdCopied') }, footnotes: t('mdFootnotes') }), [t])\n\n /** 行的时间:在办 doing/blocked 看开始、其余看创建;已结案看结案。 */\n const stamp = row.kind === 'closed'\n ? { value: row.record.time ?? row.record.created ?? null, label: t('fieldFinished') }\n : row.task.status === 'open'\n ? { value: row.task.created ?? null, label: t('fieldCreated') }\n : { value: row.task.started ?? row.task.created ?? null, label: t('fieldStarted') }\n const when = stamp.value === null || stamp.value === undefined || stamp.value === ''\n ? t('timeUnknown')\n : formatDateTime(stamp.value, { fallback: t('timeUnknown') })\n const who = whoOf(row)\n const workspace = row.kind === 'task' ? row.task.workspace : row.record.workspace\n const category = row.kind === 'task' ? row.task.category : row.record.category\n const tags = (row.kind === 'task' ? row.task.tags : row.record.tags) ?? []\n const logPath = row.kind === 'task' ? row.task.worklog : row.record.log\n\n return h('div', { className: 'dtl-row', 'data-open': open },\n h('button', {\n type: 'button',\n className: 'dtl-row-head',\n 'aria-expanded': open,\n title: open ? t('collapseAria') : t('expandAria'),\n onClick: () => { setOpen(!open) },\n },\n h(StateDot, { state: dotOf(row) }),\n h('span', {\n style: { flex: '1 1 auto', minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontSize: 'var(--dtl-font-md)' },\n title: titleOf(row),\n }, titleOf(row)),\n category === null || category === undefined || category === ''\n ? null\n : h(Chip, { text: category }),\n tags.slice(0, 2).map(tag => h(Chip, { key: tag, text: `#${tag}` })),\n h('span', { style: { display: 'flex', alignItems: 'center', gap: 'var(--dtl-space-2)', color: 'var(--dtl-fg-3)', fontSize: 'var(--dtl-font-xs)', whiteSpace: 'nowrap' } },\n workspace === undefined ? null : h('span', null, workspace.title),\n h('span', { title: stamp.value ?? undefined }, `${stamp.label} ${when}`),\n h('span', null, who === null || who === '' ? t('ownerUnclaimed') : `@${who}`),\n ),\n h(open ? IconChevronDownOutlineRegular : IconChevronRightOutlineRegular, { size: 16 }),\n ),\n open\n ? h('div', { className: 'dtl-row-body' },\n // 过程文档路径(真实值;没有工作区文件服务可点开 ⇒ 只展示路径,不假装能跳转)\n h('div', { style: { margin: 'var(--dtl-space-2) 0', fontSize: 'var(--dtl-font-sm)', color: 'var(--dtl-fg-2)' } },\n `${t('worklogTitle')}·${t('worklogPath')}:${\n logPath === null || logPath === undefined || logPath === '' ? t('worklogMissing') : logPath\n }`,\n ),\n // 已结案:host 没有「按历史行取正文」的路由 ⇒ 只渲染已有的「完成了什么」,不编造正文。\n row.kind === 'closed'\n ? h('div', { style: { fontSize: 'var(--dtl-font-md)', color: 'var(--dtl-fg-2)' } }, row.record.summary ?? '')\n : h(WorklogBody, { t, workspaceId: row.task.workspace.id, taskId: row.task.id, labels: mdLabels }),\n )\n : null,\n )\n}\n\n/** 在办行的过程正文:**展开才取**(首屏不为正文付代价),且只在挂载时取一次(不随重渲染重取)。 */\nfunction WorklogBody(props: {\n t: Translate\n workspaceId: string\n taskId: string\n labels: { code: { copyLabel: string; copiedLabel: string }; footnotes: string }\n}): ReactNode {\n const { t, workspaceId, taskId, labels } = props\n const [detail, setDetail] = useState<TaskDetail | null>(null)\n const [failed, setFailed] = useState<string | null>(null)\n useEffect(() => {\n let alive = true\n fetchTaskDetail(workspaceId, taskId)\n .then((body) => { if (alive) setDetail(body) })\n .catch((error: unknown) => {\n if (alive) setFailed(error instanceof Error ? error.message : String(error))\n })\n return () => { alive = false }\n }, [workspaceId, taskId])\n\n if (failed !== null) {\n return h('div', { style: { color: 'var(--dtl-danger)', fontSize: 'var(--dtl-font-sm)' } }, `${t('loadFailed')}:${failed}`)\n }\n if (detail === null) return h('div', { style: { color: 'var(--dtl-fg-3)', fontSize: 'var(--dtl-font-sm)' } }, t('loading'))\n if (detail.worklog === null) {\n return h('div', { style: { color: 'var(--dtl-fg-3)', fontSize: 'var(--dtl-font-sm)' } }, t('worklogMissing'))\n }\n return h('div', { className: 'dtl-worklog' }, h(MarkdownText, { text: detail.worklog, labels, variant: 'compact' }))\n}\n","/**\n * 看板首页(`main` 槽整页内容):4 统计卡(可点联动筛选)+ 筛选行 + 统一任务列表。\n * 定稿见 docs/design/features/board-home.md §二;按钮矩阵归「任务按钮与会话交接」任务(写入路径 A/B 未拍板),\n * 本页**不画**任务动作按钮——不先斩后奏。\n *\n * 刷新纪律(2026-10-17 拍板):挂载首读 + 手动刷新按钮,**不轮询**;空态三分(未就绪不渲染 / 真空引导卡 / 筛选空小字)。\n */\nimport { createElement as h, useEffect, useMemo, useState, useSyncExternalStore, type ReactNode } from 'react'\nimport { Button, IconRefreshOutlineRegular, SegmentedControl } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { LocaleKey, Translate } from './locales'\nimport {\n boardSnapshot, boardSubscribe, HISTORY_PAGE, loadMoreHistory, refresh, setQuery,\n type Bucket,\n} from './store'\nimport type { RangeKey } from './format'\nimport { selectRows } from './rows'\nimport { EmptyState, ensureUiBase, SearchInput, Select, StatCard } from './ui'\nimport { TaskRowView } from './task-row'\n\ntype Tab = 'tasks' | 'stats'\n\n/** 整页容器:占满中栏、自己滚动。 */\nconst pageStyle: Record<string, string> = {\n height: '100%', width: '100%', boxSizing: 'border-box', overflow: 'auto',\n padding: '24px 22px', color: 'var(--dtl-fg)',\n}\nconst headerStyle: Record<string, string> = {\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 }\nconst statsGridStyle: Record<string, string> = {\n display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(160px,1fr))', gap: 'var(--dtl-space-3)',\n marginBottom: 'var(--dtl-space-4)',\n}\nconst filterRowStyle: Record<string, string> = {\n display: 'flex', alignItems: 'center', gap: 'var(--dtl-space-2)', flexWrap: 'wrap',\n marginBottom: 'var(--dtl-space-3)',\n}\nconst hintStyle: Record<string, string> = { fontSize: 'var(--dtl-font-sm)', color: 'var(--dtl-fg-2)' }\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 rows = useMemo(() => selectRows(state), [state])\n const tagOptions = useMemo(\n () => [{ value: '', label: t('tagAll') }, ...(state.filters?.tags ?? []).map(tag => ({ value: tag, label: `#${tag}` }))],\n [state.filters, t],\n )\n\n /** 统计卡 → 筛选联动(点第二下不反选:档位语义是「切到这一档」,不是开关)。 */\n const card = (label: LocaleKey, value: string, bucket: Bucket, range: RangeKey): ReactNode =>\n h(StatCard, {\n label: t(label),\n value,\n active: state.query.bucket === bucket && (bucket !== 'closed' || state.query.range === range),\n onClick: () => { setQuery({ bucket, range, limit: HISTORY_PAGE }) },\n })\n\n return h('div', { style: pageStyle },\n h('div', { style: { display: 'flex', justifyContent: 'center' } },\n h('div', { style: { width: '100%', maxWidth: '1120px', 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(SegmentedControl<Tab>, {\n id: 'dtl-tab',\n value: tab,\n label: t('panelTitle'),\n options: [\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 title: t('refresh'),\n disabled: state.refreshing,\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 // ── 统计卡区 ──\n board === null\n ? null\n : h('div', { style: statsGridStyle },\n card('statMyActive', String(board.stats.myActive), 'mine', 'all'),\n card('statOpen', String(board.stats.open), 'open', 'all'),\n card('statWeekDone', String(board.closed.weekDone), 'closed', 'week'),\n card('statYearDone', board.closed.yearCapped ? t('countCapped') : String(board.closed.yearDone), 'closed', 'year'),\n ),\n\n // ── 筛选行 ──\n h('div', { style: filterRowStyle },\n h(SegmentedControl<Bucket>, {\n id: 'dtl-bucket',\n value: state.query.bucket,\n label: t('bucketLabel'),\n options: [\n { value: 'all', label: t('bucketAll') },\n { value: 'mine', label: t('bucketMine') },\n { value: 'open', label: t('bucketOpen') },\n { value: 'closed', label: t('bucketClosed') },\n ],\n onChange: (next) => { setQuery({ bucket: next, limit: HISTORY_PAGE }) },\n }),\n h(Select<RangeKey>, {\n value: state.query.range,\n ariaLabel: t('rangeLabel'),\n options: [\n { value: 'all', label: t('rangeAll') },\n { value: 'week', label: t('rangeWeek') },\n { value: 'month', label: t('rangeMonth') },\n { value: 'year', label: t('rangeYear') },\n ],\n onChange: (next) => { setQuery({ range: next, limit: HISTORY_PAGE }) },\n }),\n h(Select<string>, {\n value: state.query.tag,\n ariaLabel: t('tagAll'),\n options: tagOptions,\n onChange: (next) => { setQuery({ tag: next, limit: HISTORY_PAGE }) },\n }),\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 // ── 列表(空态三分)──\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 : state.query.bucket === 'closed' && state.historyStatus === 'loading' && state.history.length === 0\n ? h('div', { style: hintStyle }, t('loading'))\n : state.query.bucket === 'closed' && state.historyStatus === 'error'\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.historyError ?? ''}`),\n h(Button, { variant: 'outline', size: 'sm', onClick: () => { setQuery({}) } }, t('retry')),\n )\n : rows.length === 0\n ? h(EmptyState, {\n tone: isPristine(state) ? 'guide' : 'filtered',\n title: isPristine(state) ? t('emptyNoTask') : t('emptyFiltered'),\n hint: isPristine(state) ? t('emptyNoTaskHint') : undefined,\n })\n : h('div', null,\n rows.map(row => h(TaskRowView, { key: row.key, t, row })),\n h('div', { style: { ...filterRowStyle, marginTop: 'var(--dtl-space-3)', color: 'var(--dtl-fg-3)', fontSize: 'var(--dtl-font-xs)' } },\n h('span', null, t('showingCount', { n: rows.length })),\n state.query.bucket === 'closed' && state.history.length >= state.query.limit\n ? h(Button, {\n variant: 'outline',\n size: 'sm',\n disabled: state.historyStatus === 'loading',\n onClick: () => { loadMoreHistory() },\n }, state.historyStatus === 'loading' ? t('loading') : t('loadMore'))\n : null,\n ),\n ),\n ),\n ),\n ),\n )\n}\n\n/** 是否「什么都没筛」:真空才给引导卡,一有筛选就只能是「筛选空」。 */\nfunction isPristine(state: ReturnType<typeof boardSnapshot>): boolean {\n const { query } = state\n return query.bucket === 'all' && query.range === 'all' && query.tag === '' && query.keyword.trim() === ''\n}\n","/**\n * 看板文案字典(zh / en)。命名空间 = 'dsh-task-ledger',经 ctx.locale.register 注册;\n * slot 注册项声明 `locale:` 后,宿主渲染器把 `t` 席位合成进组件 props ⇒ **切宿主语言即切界面语言**,\n * 插件不自备语言开关(与姊妹仓 dsh-task-dispatch-table 同款:界面语言跟随宿主)。\n *\n * ⚠️ 界面上**不许出现未登记的硬编码中文**:新增文案先在此加键,两档都要写。\n */\nexport type LocaleKey =\n // 头部\n | 'panelTitle' | 'backToConversation' | 'tabTasks' | 'tabStats' | 'statsPlaceholder'\n // 统计卡\n | 'statMyActive' | 'statOpen' | 'statWeekDone' | 'statYearDone' | 'countCapped'\n // 筛选行\n | 'bucketLabel' | 'bucketAll' | 'bucketMine' | 'bucketOpen' | 'bucketClosed'\n | 'rangeLabel' | 'rangeAll' | 'rangeWeek' | 'rangeMonth' | 'rangeYear'\n | 'tagAll' | 'searchPlaceholder'\n // 状态与按钮\n | 'refresh' | 'refreshing' | 'loading' | 'retry' | 'loadFailed'\n // 空态三分\n | 'emptyNoWorkspace' | 'emptyNoWorkspaceHint' | 'emptyNoTask' | 'emptyNoTaskHint' | 'emptyFiltered'\n // 列表\n | 'showingCount' | 'loadMore' | 'noMore' | 'expandAria' | 'collapseAria'\n | 'fieldOwner' | 'fieldCreator' | 'fieldCreated' | 'fieldStarted' | 'fieldFinished'\n | 'fieldWorkspace' | 'fieldCategory' | 'ownerUnclaimed' | 'timeUnknown'\n | 'statusOpen' | 'statusDoing' | 'statusBlocked' | 'statusClosed'\n // 过程文档\n | 'worklogTitle' | 'worklogMissing' | 'worklogPath'\n // 浮标入口\n | 'badgeTitle' | 'openBoard'\n // markdown 正文(官方 MarkdownText 的文案席位)\n | 'mdCopy' | 'mdCopied' | 'mdFootnotes'\n\n/** 翻译席位:`{name}` 占位符由 {@link interpolateTranslate} 自己替换(不依赖宿主是否支持 params)。 */\nexport type Translate = (key: LocaleKey, params?: Record<string, string | number>) => string\n\n/** 把宿主给的无参 t 包成带占位符替换的 t(官方模板一律 `{name}`)。 */\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\nexport const zh: Record<LocaleKey, string> = {\n panelTitle: '任务台账',\n backToConversation: '返回会话',\n tabTasks: '任务',\n tabStats: '统计',\n statsPlaceholder: '统计报表尚未开建(本版只有任务首页)',\n\n statMyActive: '我的待办',\n statOpen: '待认领',\n statWeekDone: '本周已完成',\n statYearDone: '近一年完成',\n countCapped: '1万+',\n\n bucketLabel: '状态档',\n bucketAll: '全部',\n bucketMine: '我的待办',\n bucketOpen: '待认领',\n bucketClosed: '已结案',\n rangeLabel: '时间',\n rangeAll: '全部时间',\n rangeWeek: '本周',\n rangeMonth: '本月',\n rangeYear: '近一年',\n tagAll: '全部标签',\n searchPlaceholder: '搜索标题 / 负责人 / 任务号',\n\n refresh: '刷新',\n refreshing: '读取中…',\n loading: '加载中…',\n retry: '重试',\n loadFailed: '读取失败',\n\n emptyNoWorkspace: '没有检测到台账工作区',\n emptyNoWorkspaceHint: '宿主已添加的工作区里,还没有含 ledger/progress.json 的目录。',\n emptyNoTask: '还没有任务',\n emptyNoTaskHint: '在任一工作区里用 task-ledger skill 建任务(/ledger),这里就会看到它。',\n emptyFiltered: '当前筛选下没有任务',\n\n showingCount: '显示 {n} 条',\n loadMore: '加载更多',\n noMore: '没有更多了',\n expandAria: '展开详情',\n collapseAria: '收起详情',\n\n fieldOwner: '负责人',\n fieldCreator: '创建人',\n fieldCreated: '创建',\n fieldStarted: '开始',\n fieldFinished: '结案',\n fieldWorkspace: '工作区',\n fieldCategory: '分类',\n ownerUnclaimed: '未认领',\n timeUnknown: '时间未知',\n statusOpen: '待认领',\n statusDoing: '进行中',\n statusBlocked: '受阻',\n statusClosed: '已结案',\n\n worklogTitle: '过程文档',\n worklogMissing: '过程文档缺失(文件已不在盘上)',\n worklogPath: '路径',\n\n badgeTitle: '我名下进行中 + 受阻的任务数',\n openBoard: '打开任务台账',\n\n mdCopy: '复制',\n mdCopied: '已复制',\n mdFootnotes: '脚注',\n}\n\nexport const en: Record<LocaleKey, string> = {\n panelTitle: 'Task ledger',\n backToConversation: 'Back to conversation',\n tabTasks: 'Tasks',\n tabStats: 'Stats',\n statsPlaceholder: 'The stats report is not built yet (this release ships the task home only)',\n\n statMyActive: 'My active',\n statOpen: 'Unclaimed',\n statWeekDone: 'Closed this week',\n statYearDone: 'Closed in the last year',\n countCapped: '10k+',\n\n bucketLabel: 'Status',\n bucketAll: 'All',\n bucketMine: 'My active',\n bucketOpen: 'Unclaimed',\n bucketClosed: 'Closed',\n rangeLabel: 'Time',\n rangeAll: 'Any time',\n rangeWeek: 'This week',\n rangeMonth: 'This month',\n rangeYear: 'Last year',\n tagAll: 'All tags',\n searchPlaceholder: 'Search title / owner / task id',\n\n refresh: 'Refresh',\n refreshing: 'Loading…',\n loading: 'Loading…',\n retry: 'Retry',\n loadFailed: 'Failed to load',\n\n emptyNoWorkspace: 'No ledger workspace detected',\n emptyNoWorkspaceHint: 'None of the workspaces added to the host contains ledger/progress.json.',\n emptyNoTask: 'No tasks yet',\n emptyNoTaskHint: 'Create one with the task-ledger skill (/ledger) in any workspace and it shows up here.',\n emptyFiltered: 'No task matches the current filters',\n\n showingCount: 'Showing {n}',\n loadMore: 'Load more',\n noMore: 'No more',\n expandAria: 'Expand details',\n collapseAria: 'Collapse details',\n\n fieldOwner: 'Owner',\n fieldCreator: 'Creator',\n fieldCreated: 'Created',\n fieldStarted: 'Started',\n fieldFinished: 'Closed',\n fieldWorkspace: 'Workspace',\n fieldCategory: 'Category',\n ownerUnclaimed: 'unclaimed',\n timeUnknown: 'unknown time',\n statusOpen: 'Unclaimed',\n statusDoing: 'In progress',\n statusBlocked: 'Blocked',\n statusClosed: 'Closed',\n\n worklogTitle: 'Process log',\n worklogMissing: 'Process log is missing (the file is no longer on disk)',\n worklogPath: 'Path',\n\n badgeTitle: 'Tasks I own that are in progress or blocked',\n openBoard: 'Open the task ledger',\n\n mdCopy: 'Copy',\n mdCopied: 'Copied',\n mdFootnotes: 'Footnotes',\n}\n\n/** 宿主未提供 t 时的兜底(保持中文:宿主语言服务缺席不该让界面变键名)。 */\nexport const fallbackTranslate: Translate = (key, params) => {\n const raw = zh[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 * 右上浮标入口(`shell.overlay` list 条目,全帧浮层,条目并排添加不替换)。\n * 定位用宿主变量 `--dsh-frame-overlay-top`(标题栏净空 + 20px),**不写死 top 值**;\n * 浮层默认 `pointer-events:none`、子元素自动恢复 ⇒ 自绘条目不挡下方应用。\n *\n * 角标 = 我名下 `doing` + `blocked` 计数(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 } from './ui'\n\n/** 台账图标(黑白线条,描边走 currentColor ⇒ 随主题反色)。 */\nfunction 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\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 { OverlayEntry } from './overlay-entry'\n\n/** 命名空间(settings / locale / 面板 id 同名,便于对应)。 */\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 }\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 })\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,自成一块:settings / locale 缺席也不该让入口消失)\n ctx.inject(['slots'], (sub) => {\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","useState","useMemo","h","StateDot","IconChevronDownOutlineRegular","IconChevronRightOutlineRegular","MarkdownText","useSyncExternalStore","useState","useMemo","h","Button","SegmentedControl","IconRefreshOutlineRegular","h","useSyncExternalStore","useRef","useState","h"],"sources":["../src/client/api.ts","../src/client/format.ts","../src/client/store.ts","../src/client/rows.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, FilterOptions, HistoryRecord, TaskDetail } 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\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/** 首页快照(在办全量 + 两个完成类统计 + 各工作区身份)。 */\nexport function fetchBoard(): Promise<BoardPayload> {\n return fetchJson<BoardPayload>('/board')\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 * 客户端时间口径**单源**:补零 / 时刻串 / 时间范围 → `--since` / `--until`。\n * 走**本机时区**;解析失败按调用方给的兜底返回(不编造时间)。\n *\n * ⚠️ 与 skill 脚本的边界一致:`--since` 取当天 00:00、`--until` 只给日期时含当天整天\n * (脚本 `normalize_time` 的 `date_only_ref=\"23:59:59\"`),故这里只拼 `YYYY-MM-DD`。\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/** ISO → `YYYY-MM-DD`。 */\nexport function formatYmd(iso: string): string {\n const ms = Date.parse(iso)\n if (Number.isNaN(ms)) return iso\n const d = new Date(ms)\n return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`\n}\n\n/** 时间范围档 → 脚本参数(只给日期;`all` = 不过滤)。 */\nexport type RangeKey = 'all' | 'week' | 'month' | 'year'\n\n/** 周一 00:00 所在日期(与 host `board.ts` 的 `mondayDateString` 同口径:周一为一周之始)。 */\nfunction mondayOf(now: Date): Date {\n const date = new Date(now)\n date.setDate(date.getDate() - ((date.getDay() + 6) % 7))\n return date\n}\n\n/**\n * 时间范围 → `{ since, until }`(`YYYY-MM-DD`,含边界)。`all` 返回空对象 = 不拼参数。\n * 近一年 = 今天往前推 365 天(与统计卡「近 13 个月文件」不同口径:那是文件窗口,这是创建时间过滤)。\n */\nexport function rangeToDates(range: RangeKey, now: Date = new Date()): { since?: string; until?: string } {\n const ymd = (date: Date): string => `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`\n if (range === 'week') return { since: ymd(mondayOf(now)), until: ymd(now) }\n if (range === 'month') return { since: ymd(new Date(now.getFullYear(), now.getMonth(), 1)), until: ymd(now) }\n if (range === 'year') {\n const start = new Date(now)\n start.setDate(start.getDate() - 365)\n return { since: ymd(start), until: ymd(now) }\n }\n return {}\n}\n\n/** 任务行是否落在时间范围内(在办侧在浏览器里筛;无创建时间的行按「不带时间过滤」处理)。 */\nexport function withinRange(created: string | null | undefined, since: string | undefined, until: string | undefined): boolean {\n if (since === undefined && until === undefined) return true\n if (created === null || created === undefined || created === '') return false\n const day = created.slice(0, 10)\n if (since !== undefined && day < since) return false\n if (until !== undefined && day > until) return false\n return true\n}\n","/**\n * 看板数据仓(client 侧**唯一**取数入口):浮标角标与整页共用同一份快照,\n * 谁先挂载谁触发首读;刷新按钮走同一个 `refresh()`,**不轮询**(2026-10-17 拍板:\n * 后端纯按需,刷新时机归前端 = 打开面板首读 + 手动刷新)。\n *\n * 消费方式:`useSyncExternalStore(boardSubscribe, boardSnapshot)`。\n * ⚠️ `getSnapshot` 必须返回**稳定引用**(每次新建对象会被判定为「一直在变」⇒ 无限重渲染),\n * 故只在真变了时才换 `state` 对象。\n */\nimport { fetchBoard, fetchFilters, fetchHistory } from './api'\nimport { rangeToDates, type RangeKey } from './format'\nimport type { BoardPayload, FilterOptions, HistoryRecord } from './types'\n\n/** 状态档(历史查询 = 「已结案」档,不单开 tab)。 */\nexport type Bucket = 'all' | 'mine' | 'open' | 'closed'\n/** 已结案列表分页大小(「加载更多」= 加大 limit 重取)。 */\nexport const HISTORY_PAGE = 500\n\nexport interface Query {\n bucket: Bucket\n range: RangeKey\n tag: string\n keyword: string\n limit: number\n}\n\nexport interface BoardState {\n status: 'loading' | 'ready' | 'error'\n board: BoardPayload | null\n error: string | null\n history: readonly HistoryRecord[]\n historyStatus: 'idle' | 'loading' | 'ready' | 'error'\n historyError: string | null\n filters: FilterOptions | null\n query: Query\n /** 手动刷新在途(按钮转圈 / 禁用)。 */\n refreshing: boolean\n}\n\nconst INITIAL_QUERY: Query = { bucket: 'all', range: 'all', tag: '', keyword: '', limit: HISTORY_PAGE }\n\nlet state: BoardState = {\n status: 'loading',\n board: null,\n error: null,\n history: [],\n historyStatus: 'idle',\n historyError: null,\n filters: null,\n query: INITIAL_QUERY,\n refreshing: false,\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/** 重读:在办快照 + 筛选项 +(已结案档时)历史列表。 */\nexport async function refresh(): Promise<void> {\n const token = ++runToken\n set({ refreshing: true })\n try {\n const board = await fetchBoard()\n if (token !== runToken) return\n set({ status: 'ready', board, error: null })\n // 筛选项非首屏必需:失败不影响首页(下拉显示「全部标签」即可),故不进 error 态。\n void loadFilters(token)\n if (state.query.bucket === 'closed') await loadHistory(token)\n } catch (error) {\n if (token !== runToken) return\n set({ status: 'error', error: error instanceof Error ? error.message : String(error) })\n } finally {\n if (token === runToken) set({ refreshing: false })\n }\n}\n\nasync function loadFilters(token: number): Promise<void> {\n try {\n const filters = await fetchFilters()\n if (token === runToken) set({ filters })\n } catch { /* 候选拿不到就保持 null:下拉只有「全部标签」,不谎报选项 */ }\n}\n\nasync function loadHistory(token: number): Promise<void> {\n const { range, tag, limit } = state.query\n const dates = rangeToDates(range)\n set({ historyStatus: 'loading', historyError: null })\n try {\n const records = await fetchHistory({ ...dates, tag, limit })\n if (token !== runToken) return\n set({ history: records, historyStatus: 'ready' })\n } catch (error) {\n if (token !== runToken) return\n set({ historyStatus: 'error', historyError: error instanceof Error ? error.message : String(error) })\n }\n}\n\n/** 改筛选:已结案档要重取历史(服务端过滤);在办档只在浏览器里重筛(数据已全)。 */\nexport function setQuery(patch: Partial<Query>): void {\n const query: Query = { ...state.query, ...patch }\n set({ query })\n if (query.bucket === 'closed') void loadHistory(++runToken)\n}\n\n/** 「加载更多」:加大 limit 重取(skill 脚本无 offset,笨办法;500 条一页无感)。 */\nexport function loadMoreHistory(): void {\n setQuery({ limit: state.query.limit + HISTORY_PAGE })\n}\n\n\n","/**\n * 列表行的筛选 / 排序(**唯一**实现,纯函数、不碰网络):页面只渲染,不各写一份过滤。\n *\n * 单独成文件是为了能被冒烟直测(store.ts 引 api.ts,Node 剥类型解析不了无扩展名相对导入)。\n */\n// ⚠️ 相对导入一律带 `.ts` 扩展名:client 侧 tsconfig 开 `allowImportingTsExtensions`,\n// tsdown 单文件打包认得,Node 原生剥类型也认得 —— 冒烟才能直测本文件的纯筛选逻辑\n// (不带扩展名的话 Node 的 ESM 解析器找不到模块)。\nimport { rangeToDates, withinRange } from './format.ts'\nimport type { BoardPayload, HistoryRecord, TaskRow } from './types.ts'\nimport type { Query } from './store.ts'\n\n/** 列表行:在办与已结案两套形状,行件按 kind 分支(不做「统一成一个假结构」的归一化)。 */\nexport type Row =\n | { kind: 'task'; key: string; task: TaskRow; mine: boolean }\n | { kind: 'closed'; key: string; record: HistoryRecord }\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/**\n * 当前筛选下的列表行:\n * - 已结案档:history 由服务端按窗口 / 标签 / 分页过滤,浏览器只补关键字;\n * - 在办三档:快照已含全部在办 ⇒ 全在浏览器里筛(档位 / 时间 / 标签 / 关键字)。\n */\nexport function selectRows(current: { board: BoardPayload | null; history: readonly HistoryRecord[]; query: Query }): Row[] {\n const { board, history, query } = current\n if (query.bucket === 'closed') {\n return history\n .filter(record => hitKeyword(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 const dates = rangeToDates(query.range)\n const tasks = board?.tasks ?? []\n return tasks\n .filter((task) => {\n if (query.bucket === 'open' && task.status !== 'open') return false\n if (query.bucket === 'mine' && !isMine(task, board)) return false\n if (!withinRange(task.created, dates.since, dates.until)) return false\n if (query.tag !== '' && !(task.tags ?? []).includes(query.tag)) return false\n return hitKeyword(query.keyword, [task.title, task.id, task.owner, task.creator, task.category, ...(task.tags ?? [])])\n })\n // 按创建 / 开始时间倒序(最新的在上)\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: isMine(task, board) }))\n}\n\n/** 「我的」判定:owner = 该工作区的当前 git 身份,且状态是 doing / blocked(与 host 统计同口径)。 */\nexport function isMine(task: TaskRow, 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 * 语义 token 层:`--dtl-*`(dtl = dsh-task-ledger)。\n *\n * 规矩(与 docs/design/ui-style.md 两条硬约束一致):\n * - **只有本文件定义 `--dtl-*`**;其它文件一律 `var(--dtl-*)` 消费,不许直接写 `var(--dsw-*)`;\n * - 每个 token 都带兜底值 ⇒ 宿主升级换名时不炸(最坏退到兜底色,不会白屏);\n * - 明暗差异只写在本文件末尾的 `body[data-ds-dark-theme]` 段:能靠宿主 alias 表达的\n * 一律不写覆盖(alias 自己随主题变),只有「固定中性面 / 半透明底」这类 alias 表达不了的才进该段。\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 /* 反色面上的字(角标 / 实心钮):暗色下是 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 /* 固定中性实面(自绘浮标 / 角标这类必须自己变的东西) */\n --dtl-solid:var(--dsw-static-neutral-00,#fff);\n --dtl-on-solid:var(--dsw-alias-label-primary,#1f2328);\n\n /* ── 描边 ────────────────────────────────────────────────────── */\n --dtl-border:var(--dsw-alias-border-l2,#0000001a);\n --dtl-border-strong:var(--dsw-alias-border-l3,#0000001f);\n\n /* ── 语义色 ──────────────────────────────────────────────────── */\n /* 蓝色强调 / 选中一律走这条(不是 --dsw-alias-brand-primary:它明色近黑、暗色近白) */\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 --dtl-on-signal:#fff;\n /* 语义色浅底:用 color-mix 现算 ⇒ 状态色跟随宿主 alias,主题一换自动成立 */\n --dtl-business-soft:color-mix(in srgb,var(--dtl-business) 8%,transparent);\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 /* 展开行底:带透明度的蓝,透出卡片底色(灰底跟斑马纹分不出来) */\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\n /* ── 圆角 / 间距 / 控件高度 / 字号 ────────────────────────────── */\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-space-1:4px;\n --dtl-space-2:8px;\n --dtl-space-3:12px;\n --dtl-space-4:16px;\n --dtl-control-h-sm:24px;\n --dtl-control-h-md:28px;\n --dtl-control-h-lg:32px;\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-font-mono:var(--ds-font-family-code,monospace);\n\n /* ── 投影 / 动效 / 层级 ──────────────────────────────────────── */\n --dtl-shadow-1: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-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 /* 暗色下蓝要更亮、透明度更高才压得住深底 */\n --dtl-open-bg:rgba(96,165,250,.18);\n --dtl-open-bg-soft:rgba(96,165,250,.08);\n /* 暗色 chip 底板反过来掺白 */\n --dtl-chip-bg:rgba(255,255,255,.08);\n --dtl-chip-bg-hover:rgba(255,255,255,.16);\n /* 语义色浅底在深色下压到 5%(8% 在深底上偏重) */\n --dtl-business-soft:color-mix(in srgb,var(--dtl-business) 5%,transparent);\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 * UI 基础层:样式单源 + 共用元件。\n *\n * 两条纪律(用户 2026-10-17 要求「该抽象的必须抽象」):\n * 1. **样式**:所有颜色 / 圆角 / 字号一律 `var(--dtl-*)`(定义只在 tokens.ts),\n * 任何文件不许再写第二份 `var(--dsw-*)`、也不许自造 `--dtl-*`;\n * 需要 hover / focus / 动画的(内联样式表达不了)才有资格进这里的 `UI_BASE_CSS`。\n * 2. **元件**:卡 / chip / 下拉 / 空态 / 搜索框各只有一份实现,页面与浮标共用。\n */\nimport { createElement as h, type ChangeEvent, type ReactNode } from 'react'\nimport { UI_TOKENS_CSS } from './tokens'\n\n/** 基础层样式(幂等注入;只装 hover / focus / 动画这些内联样式写不了的部分)。 */\nconst UI_BASE_CSS = `\n/* 统计卡:可点、hover 抬底、选中走蓝描边 + 浅蓝底 */\n.dtl-card{\n display:flex;flex-direction:column;gap:var(--dtl-space-1);\n padding:var(--dtl-space-3) var(--dtl-space-4);\n border:1px solid var(--dtl-border);border-radius:var(--dtl-radius-md);\n background:var(--dtl-surface-1);color:var(--dtl-fg);\n cursor:pointer;text-align:left;font:inherit;\n transition:background var(--dtl-dur) var(--dtl-ease),border-color var(--dtl-dur) var(--dtl-ease);\n}\n.dtl-card:hover{background:var(--dtl-hover)}\n.dtl-card[aria-pressed=\"true\"]{border-color:var(--dtl-business);background:var(--dtl-business-soft)}\n.dtl-card:focus-visible{outline:2px solid var(--dtl-business);outline-offset:2px}\n\n/* 列表行:头部可点,hover 抬底;展开行走 --dtl-open-bg */\n.dtl-row{border:1px solid var(--dtl-border);border-radius:var(--dtl-radius-md);overflow:hidden}\n.dtl-row + .dtl-row{margin-top:var(--dtl-space-2)}\n.dtl-row-head{\n display:flex;align-items:center;gap:var(--dtl-space-2);\n width:100%;padding:var(--dtl-space-3);border:0;background:transparent;\n color:inherit;font:inherit;text-align:left;cursor:pointer;\n transition:background var(--dtl-dur) var(--dtl-ease);\n}\n.dtl-row-head:hover{background:var(--dtl-hover)}\n.dtl-row-head: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:0 var(--dtl-space-3) var(--dtl-space-3);border-top:1px solid var(--dtl-border)}\n\n/* chip(分类 / 标签):小底板走 --dtl-chip-bg,hover 加深 */\n.dtl-chip{\n display:inline-flex;align-items:center;gap:var(--dtl-space-1);\n padding:0 var(--dtl-space-2);height:20px;border-radius:var(--dtl-radius-sm);\n background:var(--dtl-chip-bg);color:var(--dtl-fg-2);\n font-size:var(--dtl-font-xs);white-space:nowrap;max-width:20ch;overflow:hidden;text-overflow:ellipsis;\n}\n\n/* 原生下拉 / 输入框:宿主没有廉价的选择器,用原生控件 + token 配色(不用自绘菜单) */\n.dtl-select,.dtl-input{\n height:var(--dtl-control-h-md);box-sizing:border-box;\n padding:0 var(--dtl-space-2);border:1px solid var(--dtl-border);border-radius:var(--dtl-radius-sm);\n background:var(--dtl-surface-1);color:var(--dtl-fg);\n font-family:inherit;font-size:var(--dtl-font-md);\n}\n.dtl-input{padding-left:var(--dtl-space-3);min-width:220px}\n.dtl-select:focus-visible,.dtl-input:focus-visible{outline:2px solid var(--dtl-business);outline-offset:1px}\n\n/* 空态:真空 = 虚线引导卡;筛选空 = 小字 */\n.dtl-empty-guide{\n padding:var(--dtl-space-4);border:1px dashed var(--dtl-border-strong);border-radius:var(--dtl-radius-md);\n background:transparent;\n}\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{\n 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}\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/* 过程正文:宽度受限、长行折行 */\n.dtl-worklog{max-width:72ch;font-size:var(--dtl-font-md);line-height:1.6}\n`\nconst STYLE_ID = 'dtl-ui-base'\n\n/** 幂等注入 token 层 + 基础层(面板与浮标谁先挂载都只注入一次)。 */\nexport function ensureUiBase(): void {\n if (typeof document === 'undefined') return\n if (document.getElementById(STYLE_ID) !== null) return\n const style = document.createElement('style')\n style.id = STYLE_ID\n style.textContent = `${UI_TOKENS_CSS}\\n${UI_BASE_CSS}`\n document.head.appendChild(style)\n}\n\n/** 统计卡:点一下联动下方列表筛选(aria-pressed = 选中态)。 */\nexport function StatCard(props: {\n label: string\n value: string\n active: boolean\n title?: string\n onClick: () => void\n}): ReactNode {\n return h('button', {\n type: 'button',\n className: 'dtl-card',\n 'aria-pressed': props.active,\n title: props.title,\n onClick: props.onClick,\n },\n h('span', { style: { fontSize: 'var(--dtl-font-sm)', color: 'var(--dtl-fg-2)' } }, props.label),\n h('span', { style: { fontSize: '24px', fontWeight: 600, lineHeight: 1.2 } }, props.value),\n )\n}\n\n/** chip(分类 / 标签 / 工作区名)。 */\nexport function Chip(props: { text: string; tone?: 'plain' | 'signal'; color?: string }): ReactNode {\n const style: Record<string, string> = {}\n if (props.tone === 'signal' && props.color !== undefined) {\n style.background = props.color\n style.color = 'var(--dtl-on-signal)'\n }\n return h('span', { className: 'dtl-chip', style, title: props.text }, props.text)\n}\n\n/** 原生下拉(时间 / 标签筛选共用一份)。 */\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}): ReactNode {\n return h('select', {\n className: 'dtl-select',\n 'aria-label': props.ariaLabel,\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/** 搜索框(关键字过滤)。 */\nexport function SearchInput(props: {\n value: string\n placeholder: string\n onChange: (next: string) => void\n ariaLabel: string\n}): ReactNode {\n return h('input', {\n className: 'dtl-input',\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/** 空态:`guide` = 真空引导卡(虚线),`filtered` = 筛选空小字。 */\nexport function EmptyState(props: { tone: 'guide' | 'filtered'; title: string; hint?: string }): ReactNode {\n if (props.tone === 'filtered') {\n return h('div', { className: 'dtl-empty-filtered' }, props.title)\n }\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 * 任务列表行(宿主**无表格件** ⇒ 自绘,几何参照官方 `DisclosureRow`,状态走官方 `StateDot`)。\n * 定稿见 docs/design/features/board-home.md §二 与 docs/design/ui-style.md:\n * 状态语义 open=idle / doing=ongoing / blocked=warning / 已结案=done。\n *\n * 行展开 = 过程文档路径 + 过程正文(在办走 `GET /task` 取正文;已结案只有路径——\n * host 没有按历史行取正文的路由,不编造内容)。\n */\nimport { createElement as h, useEffect, useMemo, useState, type ReactNode } from 'react'\nimport {\n IconChevronDownOutlineRegular, IconChevronRightOutlineRegular, MarkdownText, StateDot,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport { fetchTaskDetail } from './api'\nimport { formatDateTime } from './format'\nimport type { Translate } from './locales'\nimport type { Row } from './rows'\nimport { Chip } from './ui'\nimport type { TaskDetail } from './types'\n\n/** 行的状态点语义(在办三态 + 已结案)。 */\nfunction dotOf(row: Row): 'idle' | 'ongoing' | 'warning' | 'done' {\n if (row.kind === 'closed') return 'done'\n if (row.task.status === 'blocked') return 'warning'\n if (row.task.status === 'doing') return 'ongoing'\n return 'idle'\n}\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/** 行的「谁」:负责人优先,没有就创建人,都没有 = 未认领。 */\nfunction whoOf(row: Row): string | null {\n if (row.kind === 'task') return row.task.owner ?? row.task.creator ?? null\n return row.record.owner ?? null\n}\n\nexport function TaskRowView(props: { t: Translate; row: Row }): ReactNode {\n const { t, row } = props\n const [open, setOpen] = useState(false)\n\n // 官方 MarkdownText 要求 labels 引用稳定(新身份会丢弃渲染缓存)⇒ 按 t 记忆一次。\n const mdLabels = useMemo(() => ({ code: { copyLabel: t('mdCopy'), copiedLabel: t('mdCopied') }, footnotes: t('mdFootnotes') }), [t])\n\n /** 行的时间:在办 doing/blocked 看开始、其余看创建;已结案看结案。 */\n const stamp = row.kind === 'closed'\n ? { value: row.record.time ?? row.record.created ?? null, label: t('fieldFinished') }\n : row.task.status === 'open'\n ? { value: row.task.created ?? null, label: t('fieldCreated') }\n : { value: row.task.started ?? row.task.created ?? null, label: t('fieldStarted') }\n const when = stamp.value === null || stamp.value === undefined || stamp.value === ''\n ? t('timeUnknown')\n : formatDateTime(stamp.value, { fallback: t('timeUnknown') })\n const who = whoOf(row)\n const workspace = row.kind === 'task' ? row.task.workspace : row.record.workspace\n const category = row.kind === 'task' ? row.task.category : row.record.category\n const tags = (row.kind === 'task' ? row.task.tags : row.record.tags) ?? []\n const logPath = row.kind === 'task' ? row.task.worklog : row.record.log\n\n return h('div', { className: 'dtl-row', 'data-open': open },\n h('button', {\n type: 'button',\n className: 'dtl-row-head',\n 'aria-expanded': open,\n title: open ? t('collapseAria') : t('expandAria'),\n onClick: () => { setOpen(!open) },\n },\n h(StateDot, { state: dotOf(row) }),\n h('span', {\n style: { flex: '1 1 auto', minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontSize: 'var(--dtl-font-md)' },\n title: titleOf(row),\n }, titleOf(row)),\n category === null || category === undefined || category === ''\n ? null\n : h(Chip, { text: category }),\n tags.slice(0, 2).map(tag => h(Chip, { key: tag, text: `#${tag}` })),\n h('span', { style: { display: 'flex', alignItems: 'center', gap: 'var(--dtl-space-2)', color: 'var(--dtl-fg-3)', fontSize: 'var(--dtl-font-xs)', whiteSpace: 'nowrap' } },\n workspace === undefined ? null : h('span', null, workspace.title),\n h('span', { title: stamp.value ?? undefined }, `${stamp.label} ${when}`),\n h('span', null, who === null || who === '' ? t('ownerUnclaimed') : `@${who}`),\n ),\n h(open ? IconChevronDownOutlineRegular : IconChevronRightOutlineRegular, { size: 16 }),\n ),\n open\n ? h('div', { className: 'dtl-row-body' },\n // 过程文档路径(真实值;没有工作区文件服务可点开 ⇒ 只展示路径,不假装能跳转)\n h('div', { style: { margin: 'var(--dtl-space-2) 0', fontSize: 'var(--dtl-font-sm)', color: 'var(--dtl-fg-2)' } },\n `${t('worklogTitle')}·${t('worklogPath')}:${\n logPath === null || logPath === undefined || logPath === '' ? t('worklogMissing') : logPath\n }`,\n ),\n // 已结案:host 没有「按历史行取正文」的路由 ⇒ 只渲染已有的「完成了什么」,不编造正文。\n row.kind === 'closed'\n ? h('div', { style: { fontSize: 'var(--dtl-font-md)', color: 'var(--dtl-fg-2)' } }, row.record.summary ?? '')\n : h(WorklogBody, { t, workspaceId: row.task.workspace.id, taskId: row.task.id, labels: mdLabels }),\n )\n : null,\n )\n}\n\n/** 在办行的过程正文:**展开才取**(首屏不为正文付代价),且只在挂载时取一次(不随重渲染重取)。 */\nfunction WorklogBody(props: {\n t: Translate\n workspaceId: string\n taskId: string\n labels: { code: { copyLabel: string; copiedLabel: string }; footnotes: string }\n}): ReactNode {\n const { t, workspaceId, taskId, labels } = props\n const [detail, setDetail] = useState<TaskDetail | null>(null)\n const [failed, setFailed] = useState<string | null>(null)\n useEffect(() => {\n let alive = true\n fetchTaskDetail(workspaceId, taskId)\n .then((body) => { if (alive) setDetail(body) })\n .catch((error: unknown) => {\n if (alive) setFailed(error instanceof Error ? error.message : String(error))\n })\n return () => { alive = false }\n }, [workspaceId, taskId])\n\n if (failed !== null) {\n return h('div', { style: { color: 'var(--dtl-danger)', fontSize: 'var(--dtl-font-sm)' } }, `${t('loadFailed')}:${failed}`)\n }\n if (detail === null) return h('div', { style: { color: 'var(--dtl-fg-3)', fontSize: 'var(--dtl-font-sm)' } }, t('loading'))\n if (detail.worklog === null) {\n return h('div', { style: { color: 'var(--dtl-fg-3)', fontSize: 'var(--dtl-font-sm)' } }, t('worklogMissing'))\n }\n return h('div', { className: 'dtl-worklog' }, h(MarkdownText, { text: detail.worklog, labels, variant: 'compact' }))\n}\n","/**\n * 看板首页(`main` 槽整页内容):4 统计卡(可点联动筛选)+ 筛选行 + 统一任务列表。\n * 定稿见 docs/design/features/board-home.md §二;按钮矩阵归「任务按钮与会话交接」任务(写入路径 A/B 未拍板),\n * 本页**不画**任务动作按钮——不先斩后奏。\n *\n * 刷新纪律(2026-10-17 拍板):挂载首读 + 手动刷新按钮,**不轮询**;空态三分(未就绪不渲染 / 真空引导卡 / 筛选空小字)。\n */\nimport { createElement as h, useEffect, useMemo, useState, useSyncExternalStore, type ReactNode } from 'react'\nimport { Button, IconRefreshOutlineRegular, SegmentedControl } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { LocaleKey, Translate } from './locales'\nimport {\n boardSnapshot, boardSubscribe, HISTORY_PAGE, loadMoreHistory, refresh, setQuery,\n type Bucket,\n} from './store'\nimport type { RangeKey } from './format'\nimport { selectRows } from './rows'\nimport { EmptyState, ensureUiBase, SearchInput, Select, StatCard } from './ui'\nimport { TaskRowView } from './task-row'\n\ntype Tab = 'tasks' | 'stats'\n\n/** 整页容器:占满中栏、自己滚动。 */\nconst pageStyle: Record<string, string> = {\n height: '100%', width: '100%', boxSizing: 'border-box', overflow: 'auto',\n padding: '24px 22px', color: 'var(--dtl-fg)',\n}\nconst headerStyle: Record<string, string> = {\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 }\nconst statsGridStyle: Record<string, string> = {\n display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(160px,1fr))', gap: 'var(--dtl-space-3)',\n marginBottom: 'var(--dtl-space-4)',\n}\nconst filterRowStyle: Record<string, string> = {\n display: 'flex', alignItems: 'center', gap: 'var(--dtl-space-2)', flexWrap: 'wrap',\n marginBottom: 'var(--dtl-space-3)',\n}\nconst hintStyle: Record<string, string> = { fontSize: 'var(--dtl-font-sm)', color: 'var(--dtl-fg-2)' }\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 rows = useMemo(() => selectRows(state), [state])\n const tagOptions = useMemo(\n () => [{ value: '', label: t('tagAll') }, ...(state.filters?.tags ?? []).map(tag => ({ value: tag, label: `#${tag}` }))],\n [state.filters, t],\n )\n\n /** 统计卡 → 筛选联动(点第二下不反选:档位语义是「切到这一档」,不是开关)。 */\n const card = (label: LocaleKey, value: string, bucket: Bucket, range: RangeKey): ReactNode =>\n h(StatCard, {\n label: t(label),\n value,\n active: state.query.bucket === bucket && (bucket !== 'closed' || state.query.range === range),\n onClick: () => { setQuery({ bucket, range, limit: HISTORY_PAGE }) },\n })\n\n return h('div', { style: pageStyle },\n h('div', { style: { display: 'flex', justifyContent: 'center' } },\n h('div', { style: { width: '100%', maxWidth: '1120px', 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(SegmentedControl<Tab>, {\n id: 'dtl-tab',\n value: tab,\n label: t('panelTitle'),\n options: [\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 title: t('refresh'),\n disabled: state.refreshing,\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 // ── 统计卡区 ──\n board === null\n ? null\n : h('div', { style: statsGridStyle },\n card('statMyActive', String(board.stats.myActive), 'mine', 'all'),\n card('statOpen', String(board.stats.open), 'open', 'all'),\n card('statWeekDone', String(board.closed.weekDone), 'closed', 'week'),\n card('statYearDone', board.closed.yearCapped ? t('countCapped') : String(board.closed.yearDone), 'closed', 'year'),\n ),\n\n // ── 筛选行 ──\n h('div', { style: filterRowStyle },\n h(SegmentedControl<Bucket>, {\n id: 'dtl-bucket',\n value: state.query.bucket,\n label: t('bucketLabel'),\n options: [\n { value: 'all', label: t('bucketAll') },\n { value: 'mine', label: t('bucketMine') },\n { value: 'open', label: t('bucketOpen') },\n { value: 'closed', label: t('bucketClosed') },\n ],\n onChange: (next) => { setQuery({ bucket: next, limit: HISTORY_PAGE }) },\n }),\n h(Select<RangeKey>, {\n value: state.query.range,\n ariaLabel: t('rangeLabel'),\n options: [\n { value: 'all', label: t('rangeAll') },\n { value: 'week', label: t('rangeWeek') },\n { value: 'month', label: t('rangeMonth') },\n { value: 'year', label: t('rangeYear') },\n ],\n onChange: (next) => { setQuery({ range: next, limit: HISTORY_PAGE }) },\n }),\n h(Select<string>, {\n value: state.query.tag,\n ariaLabel: t('tagAll'),\n options: tagOptions,\n onChange: (next) => { setQuery({ tag: next, limit: HISTORY_PAGE }) },\n }),\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 // ── 列表(空态三分)──\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 : state.query.bucket === 'closed' && state.historyStatus === 'loading' && state.history.length === 0\n ? h('div', { style: hintStyle }, t('loading'))\n : state.query.bucket === 'closed' && state.historyStatus === 'error'\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.historyError ?? ''}`),\n h(Button, { variant: 'outline', size: 'sm', onClick: () => { setQuery({}) } }, t('retry')),\n )\n : rows.length === 0\n ? h(EmptyState, {\n tone: isPristine(state) ? 'guide' : 'filtered',\n title: isPristine(state) ? t('emptyNoTask') : t('emptyFiltered'),\n hint: isPristine(state) ? t('emptyNoTaskHint') : undefined,\n })\n : h('div', null,\n rows.map(row => h(TaskRowView, { key: row.key, t, row })),\n h('div', { style: { ...filterRowStyle, marginTop: 'var(--dtl-space-3)', color: 'var(--dtl-fg-3)', fontSize: 'var(--dtl-font-xs)' } },\n h('span', null, t('showingCount', { n: rows.length })),\n state.query.bucket === 'closed' && state.history.length >= state.query.limit\n ? h(Button, {\n variant: 'outline',\n size: 'sm',\n disabled: state.historyStatus === 'loading',\n onClick: () => { loadMoreHistory() },\n }, state.historyStatus === 'loading' ? t('loading') : t('loadMore'))\n : null,\n ),\n ),\n ),\n ),\n ),\n )\n}\n\n/** 是否「什么都没筛」:真空才给引导卡,一有筛选就只能是「筛选空」。 */\nfunction isPristine(state: ReturnType<typeof boardSnapshot>): boolean {\n const { query } = state\n return query.bucket === 'all' && query.range === 'all' && query.tag === '' && query.keyword.trim() === ''\n}\n","/**\n * 看板文案字典(zh / en)。命名空间 = 'dsh-task-ledger',经 ctx.locale.register 注册;\n * slot 注册项声明 `locale:` 后,宿主渲染器把 `t` 席位合成进组件 props ⇒ **切宿主语言即切界面语言**,\n * 插件不自备语言开关(与姊妹仓 dsh-task-dispatch-table 同款:界面语言跟随宿主)。\n *\n * ⚠️ 界面上**不许出现未登记的硬编码中文**:新增文案先在此加键,两档都要写。\n */\nexport type LocaleKey =\n // 头部\n | 'panelTitle' | 'backToConversation' | 'tabTasks' | 'tabStats' | 'statsPlaceholder'\n // 统计卡\n | 'statMyActive' | 'statOpen' | 'statWeekDone' | 'statYearDone' | 'countCapped'\n // 筛选行\n | 'bucketLabel' | 'bucketAll' | 'bucketMine' | 'bucketOpen' | 'bucketClosed'\n | 'rangeLabel' | 'rangeAll' | 'rangeWeek' | 'rangeMonth' | 'rangeYear'\n | 'tagAll' | 'searchPlaceholder'\n // 状态与按钮\n | 'refresh' | 'refreshing' | 'loading' | 'retry' | 'loadFailed'\n // 空态三分\n | 'emptyNoWorkspace' | 'emptyNoWorkspaceHint' | 'emptyNoTask' | 'emptyNoTaskHint' | 'emptyFiltered'\n // 列表\n | 'showingCount' | 'loadMore' | 'noMore' | 'expandAria' | 'collapseAria'\n | 'fieldOwner' | 'fieldCreator' | 'fieldCreated' | 'fieldStarted' | 'fieldFinished'\n | 'fieldWorkspace' | 'fieldCategory' | 'ownerUnclaimed' | 'timeUnknown'\n | 'statusOpen' | 'statusDoing' | 'statusBlocked' | 'statusClosed'\n // 过程文档\n | 'worklogTitle' | 'worklogMissing' | 'worklogPath'\n // 浮标入口\n | 'badgeTitle' | 'openBoard'\n // markdown 正文(官方 MarkdownText 的文案席位)\n | 'mdCopy' | 'mdCopied' | 'mdFootnotes'\n\n/** 翻译席位:`{name}` 占位符由 {@link interpolateTranslate} 自己替换(不依赖宿主是否支持 params)。 */\nexport type Translate = (key: LocaleKey, params?: Record<string, string | number>) => string\n\n/** 把宿主给的无参 t 包成带占位符替换的 t(官方模板一律 `{name}`)。 */\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\nexport const zh: Record<LocaleKey, string> = {\n panelTitle: '任务台账',\n backToConversation: '返回会话',\n tabTasks: '任务',\n tabStats: '统计',\n statsPlaceholder: '统计报表尚未开建(本版只有任务首页)',\n\n statMyActive: '我的待办',\n statOpen: '待认领',\n statWeekDone: '本周已完成',\n statYearDone: '近一年完成',\n countCapped: '1万+',\n\n bucketLabel: '状态档',\n bucketAll: '全部',\n bucketMine: '我的待办',\n bucketOpen: '待认领',\n bucketClosed: '已结案',\n rangeLabel: '时间',\n rangeAll: '全部时间',\n rangeWeek: '本周',\n rangeMonth: '本月',\n rangeYear: '近一年',\n tagAll: '全部标签',\n searchPlaceholder: '搜索标题 / 负责人 / 任务号',\n\n refresh: '刷新',\n refreshing: '读取中…',\n loading: '加载中…',\n retry: '重试',\n loadFailed: '读取失败',\n\n emptyNoWorkspace: '没有检测到台账工作区',\n emptyNoWorkspaceHint: '宿主已添加的工作区里,还没有含 ledger/progress.json 的目录。',\n emptyNoTask: '还没有任务',\n emptyNoTaskHint: '在任一工作区里用 task-ledger skill 建任务(/ledger),这里就会看到它。',\n emptyFiltered: '当前筛选下没有任务',\n\n showingCount: '显示 {n} 条',\n loadMore: '加载更多',\n noMore: '没有更多了',\n expandAria: '展开详情',\n collapseAria: '收起详情',\n\n fieldOwner: '负责人',\n fieldCreator: '创建人',\n fieldCreated: '创建',\n fieldStarted: '开始',\n fieldFinished: '结案',\n fieldWorkspace: '工作区',\n fieldCategory: '分类',\n ownerUnclaimed: '未认领',\n timeUnknown: '时间未知',\n statusOpen: '待认领',\n statusDoing: '进行中',\n statusBlocked: '受阻',\n statusClosed: '已结案',\n\n worklogTitle: '过程文档',\n worklogMissing: '过程文档缺失(文件已不在盘上)',\n worklogPath: '路径',\n\n badgeTitle: '我名下进行中 + 受阻的任务数',\n openBoard: '打开任务台账',\n\n mdCopy: '复制',\n mdCopied: '已复制',\n mdFootnotes: '脚注',\n}\n\nexport const en: Record<LocaleKey, string> = {\n panelTitle: 'Task ledger',\n backToConversation: 'Back to conversation',\n tabTasks: 'Tasks',\n tabStats: 'Stats',\n statsPlaceholder: 'The stats report is not built yet (this release ships the task home only)',\n\n statMyActive: 'My active',\n statOpen: 'Unclaimed',\n statWeekDone: 'Closed this week',\n statYearDone: 'Closed in the last year',\n countCapped: '10k+',\n\n bucketLabel: 'Status',\n bucketAll: 'All',\n bucketMine: 'My active',\n bucketOpen: 'Unclaimed',\n bucketClosed: 'Closed',\n rangeLabel: 'Time',\n rangeAll: 'Any time',\n rangeWeek: 'This week',\n rangeMonth: 'This month',\n rangeYear: 'Last year',\n tagAll: 'All tags',\n searchPlaceholder: 'Search title / owner / task id',\n\n refresh: 'Refresh',\n refreshing: 'Loading…',\n loading: 'Loading…',\n retry: 'Retry',\n loadFailed: 'Failed to load',\n\n emptyNoWorkspace: 'No ledger workspace detected',\n emptyNoWorkspaceHint: 'None of the workspaces added to the host contains ledger/progress.json.',\n emptyNoTask: 'No tasks yet',\n emptyNoTaskHint: 'Create one with the task-ledger skill (/ledger) in any workspace and it shows up here.',\n emptyFiltered: 'No task matches the current filters',\n\n showingCount: 'Showing {n}',\n loadMore: 'Load more',\n noMore: 'No more',\n expandAria: 'Expand details',\n collapseAria: 'Collapse details',\n\n fieldOwner: 'Owner',\n fieldCreator: 'Creator',\n fieldCreated: 'Created',\n fieldStarted: 'Started',\n fieldFinished: 'Closed',\n fieldWorkspace: 'Workspace',\n fieldCategory: 'Category',\n ownerUnclaimed: 'unclaimed',\n timeUnknown: 'unknown time',\n statusOpen: 'Unclaimed',\n statusDoing: 'In progress',\n statusBlocked: 'Blocked',\n statusClosed: 'Closed',\n\n worklogTitle: 'Process log',\n worklogMissing: 'Process log is missing (the file is no longer on disk)',\n worklogPath: 'Path',\n\n badgeTitle: 'Tasks I own that are in progress or blocked',\n openBoard: 'Open the task ledger',\n\n mdCopy: 'Copy',\n mdCopied: 'Copied',\n mdFootnotes: 'Footnotes',\n}\n\n/** 宿主未提供 t 时的兜底(保持中文:宿主语言服务缺席不该让界面变键名)。 */\nexport const fallbackTranslate: Translate = (key, params) => {\n const raw = zh[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 * 右上浮标入口(`shell.overlay` list 条目,全帧浮层,条目并排添加不替换)。\n * 定位用宿主变量 `--dsh-frame-overlay-top`(标题栏净空 + 20px),**不写死 top 值**;\n * 浮层默认 `pointer-events:none`、子元素自动恢复 ⇒ 自绘条目不挡下方应用。\n *\n * 角标 = 我名下 `doing` + `blocked` 计数(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 } from './ui'\n\n/** 台账图标(黑白线条,描边走 currentColor ⇒ 随主题反色)。 */\nfunction 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\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 { OverlayEntry } from './overlay-entry'\n\n/** 命名空间(settings / locale / 面板 id 同名,便于对应)。 */\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 }\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 })\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,自成一块:settings / locale 缺席也不该让入口消失)\n ctx.inject(['slots'], (sub) => {\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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-task-ledger",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "task-ledger",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
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",
|
|
@@ -1732,7 +1732,35 @@ def cmd_history(args: argparse.Namespace) -> None:
|
|
|
1732
1732
|
|
|
1733
1733
|
repo = find_repo(args.repo)
|
|
1734
1734
|
sources = history_sources(repo)
|
|
1735
|
+
want_owner = (
|
|
1736
|
+
args.owner.strip().lstrip("@") if args.owner else None
|
|
1737
|
+
) # 容忍「@名字」写法
|
|
1738
|
+
me = git_user() if args.mine else None
|
|
1739
|
+
filter_echo = {
|
|
1740
|
+
"owner": want_owner,
|
|
1741
|
+
"mine": args.mine,
|
|
1742
|
+
"since": since,
|
|
1743
|
+
"until": until,
|
|
1744
|
+
"limit": args.limit,
|
|
1745
|
+
"category": args.category.strip() if args.category else None,
|
|
1746
|
+
"tag": args.tag.strip() if args.tag else None,
|
|
1747
|
+
}
|
|
1735
1748
|
if not sources:
|
|
1749
|
+
# JSON / 计数模式必须**始终**机器可读:空台账也给 JSON,人话只走 stderr。
|
|
1750
|
+
# (2026-10-17 真机教训:这里原先直接 print 人话后 return,--json 调用方
|
|
1751
|
+
# 拿到非 JSON 输出 ⇒ dsh-task-ledger 看板 /board 整条 500。)
|
|
1752
|
+
if args.count_only:
|
|
1753
|
+
print(json.dumps({"count": 0, "capped": False}, ensure_ascii=False))
|
|
1754
|
+
return
|
|
1755
|
+
if args.json:
|
|
1756
|
+
print(
|
|
1757
|
+
json.dumps(
|
|
1758
|
+
{"count": 0, "filters": filter_echo, "records": []},
|
|
1759
|
+
ensure_ascii=False,
|
|
1760
|
+
indent=2,
|
|
1761
|
+
)
|
|
1762
|
+
)
|
|
1763
|
+
return
|
|
1736
1764
|
print(
|
|
1737
1765
|
f"没有已结案记录:ledger/history/ 还没有文件(存量 {HISTORY_REL} 也没有)。"
|
|
1738
1766
|
)
|
|
@@ -1763,10 +1791,6 @@ def cmd_history(args: argparse.Namespace) -> None:
|
|
|
1763
1791
|
records.extend(recs)
|
|
1764
1792
|
warnings.extend(f"{rel} {w}" for w in warns)
|
|
1765
1793
|
|
|
1766
|
-
me = git_user() if args.mine else None
|
|
1767
|
-
want_owner = (
|
|
1768
|
-
args.owner.strip().lstrip("@") if args.owner else None
|
|
1769
|
-
) # 容忍「@名字」写法
|
|
1770
1794
|
rows = []
|
|
1771
1795
|
undated = 0
|
|
1772
1796
|
for r in records:
|
|
@@ -1807,15 +1831,7 @@ def cmd_history(args: argparse.Namespace) -> None:
|
|
|
1807
1831
|
json.dumps(
|
|
1808
1832
|
{
|
|
1809
1833
|
"count": len(rows),
|
|
1810
|
-
"filters":
|
|
1811
|
-
"owner": want_owner,
|
|
1812
|
-
"mine": args.mine,
|
|
1813
|
-
"since": since,
|
|
1814
|
-
"until": until,
|
|
1815
|
-
"limit": args.limit,
|
|
1816
|
-
"category": args.category.strip() if args.category else None,
|
|
1817
|
-
"tag": args.tag.strip() if args.tag else None,
|
|
1818
|
-
},
|
|
1834
|
+
"filters": filter_echo,
|
|
1819
1835
|
"records": rows,
|
|
1820
1836
|
},
|
|
1821
1837
|
ensure_ascii=False,
|