dsh-task-ledger 0.0.17 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js CHANGED
@@ -44,13 +44,6 @@ window.__ModuleLoader__.load({
44
44
  function fetchBoard() {
45
45
  return fetchJson("/board");
46
46
  }
47
- /** 单条在办 + 过程文档正文(展开行用)。 */
48
- function fetchTaskDetail(workspaceId, taskId) {
49
- return fetchJson(`/task?${new URLSearchParams({
50
- ws: workspaceId,
51
- id: taskId
52
- }).toString()}`);
53
- }
54
47
  //#endregion
55
48
  //#region src/client/store.ts
56
49
  /**
@@ -519,16 +512,40 @@ body[data-ds-dark-theme]{
519
512
  .dtl-stat__value{font-size:var(--dtl-font-stat);font-weight:600;line-height:1.15;font-variant-numeric:tabular-nums;}
520
513
  .dtl-stat__hint{font-size:var(--dtl-font-xs);line-height:var(--dtl-line-xs);color:var(--dtl-fg-3);}
521
514
 
522
- /* 列表行:头部可点,hover 淡蓝;展开行走 --dtl-open-bg */
515
+ /* 列表行:分组标题头仍走 .dtl-row-head(可点折叠);任务行走 .dtl-taskrow(两行式,不可点) */
523
516
  .dtl-row-head{display:flex;align-items:center;gap:var(--dtl-space-2);
524
517
  width:100%;padding:var(--dtl-space-2) var(--dtl-space-3);border:0;background:transparent;
525
518
  color:inherit;font:inherit;text-align:left;cursor:pointer;
526
519
  transition:background-color var(--dtl-dur-fast) var(--dtl-ease);}
527
520
  .dtl-row-head:hover{background:var(--dtl-card-hover);}
528
521
  .dtl-row-head:focus-visible{outline:2px solid var(--dtl-business);outline-offset:-2px;}
529
- .dtl-row[data-open="true"]{background:var(--dtl-open-bg);}
530
- .dtl-row-body{padding:var(--dtl-space-2) var(--dtl-space-3) var(--dtl-space-3);
531
- background:var(--dtl-open-bg-soft);font-size:var(--dtl-font-md);}
522
+
523
+ /* 任务行(两行式,用户 2026-10-17 定稿):hover 整行提亮;右侧 时间⇄按钮排 互换 */
524
+ .dtl-taskrow{padding:var(--dtl-space-2) var(--dtl-space-3);border-radius:var(--dtl-radius-md);
525
+ transition:background-color var(--dtl-dur-fast) var(--dtl-ease);}
526
+ .dtl-taskrow:hover{background:var(--dtl-card-hover);}
527
+ .dtl-taskrow__top{display:flex;align-items:center;gap:var(--dtl-space-2);min-width:0;}
528
+ .dtl-taskrow__id{font-size:var(--dtl-font-xs);color:var(--dtl-fg-3);
529
+ font-variant-numeric:tabular-nums;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:220px;}
530
+ .dtl-taskrow__title{margin-top:2px;font-size:var(--dtl-font-md);font-weight:600;line-height:var(--dtl-line-md);}
531
+ .dtl-taskrow__meta{display:flex;align-items:center;gap:var(--dtl-space-1);margin-top:var(--dtl-space-1);flex-wrap:wrap;}
532
+ .dtl-taskrow__age{flex:none;color:var(--dtl-fg-3);font-size:var(--dtl-font-xs);white-space:nowrap;
533
+ font-variant-numeric:tabular-nums;}
534
+ .dtl-taskrow__actions{display:none;flex:none;gap:4px;}
535
+ .dtl-taskrow:hover .dtl-taskrow__age{display:none;}
536
+ .dtl-taskrow:hover .dtl-taskrow__actions{display:inline-flex;}
537
+
538
+ /* 状态 pill:只有异常态出标记 —— 阻塞=黄 / 停滞=红 / 待认领=灰描边;进行中无标记 */
539
+ .dtl-pill{flex:none;padding:1px 8px;border-radius:999px;font-size:10px;line-height:16px;font-weight:600;white-space:nowrap;}
540
+ .dtl-pill--blocked{background:var(--dtl-warning);color:var(--dtl-on-signal);}
541
+ .dtl-pill--stale{background:var(--dtl-danger);color:var(--dtl-on-signal);}
542
+ .dtl-pill--open{border:1px solid var(--dtl-border-strong);color:var(--dtl-fg-3);}
543
+
544
+ /* 行内迷你按钮(样子先行,点击行为归「任务按钮与会话交接」任务) */
545
+ .dtl-minibtn{padding:1px 8px;border:1px solid var(--dtl-border);border-radius:var(--dtl-radius-sm);
546
+ background:var(--dtl-surface-1);color:var(--dtl-fg-2);font:inherit;font-size:var(--dtl-font-xs);
547
+ line-height:16px;white-space:nowrap;cursor:default;}
548
+ .dtl-minibtn:hover{border-color:var(--dtl-border-strong);color:var(--dtl-fg);}
532
549
 
533
550
  /* 空态:真空 = 虚线引导卡;筛选空 = 小字 */
534
551
  .dtl-empty-guide{padding:var(--dtl-space-4);border:1px dashed var(--dtl-border-strong);border-radius:var(--dtl-radius-md);background:transparent;}
@@ -828,33 +845,27 @@ body[data-ds-dark-theme]{
828
845
  //#endregion
829
846
  //#region src/client/task-row.ts
830
847
  /**
831
- * 任务列表行(宿主**无表格件** ⇒ 自绘,几何参照官方 `DisclosureRow`,状态走官方 `StateDot`)。
832
- * 定稿见 docs/design/features/board-home.md §二 与 docs/design/ui-style.md:
833
- * 状态语义 open=idle / doing=ongoing / blocked=warning / 已结案=done。
834
- *
835
- * 行展开 = 过程文档路径 + 过程正文(在办走 `GET /task` 取正文;已结案只有路径——
836
- * host 没有按历史行取正文的路由,不编造内容)。
837
- *
838
- * `showWorkspace`:第一区「我的待办」是跨工作区平铺 ⇒ 工作区升为一列(定宽,不再是
839
- * 和标签混在一起的小 chip);第二区分组行由分组标题说明归属,行内不再重复。
848
+ * 任务列表行(宿主**无表格件** ⇒ 自绘):两行式(用户 2026-10-17 定稿,参照 GitHub 通知列表):
849
+ * - 上行小字:工作区徽标(第一区专用列)+ 任务号 + 状态 pill(**只有** 阻塞=黄 / 停滞=红 / 待认领=灰;
850
+ * 进行中是默认态不出任何标记,**没有转圈**)+ 右侧「已做 N 天」;
851
+ * - 下行主字:标题加粗;第三小行:分类 + 标签 chip。
852
+ * 悬停时右侧 时间⇄按钮排 互换(继续 / 结束 / 转移)——按钮**先只做样子**,
853
+ * 点击行为归「任务按钮与会话交接」任务(写入路径 A/B 未拍板);详情侧栏也归该任务,本行不做展开。
840
854
  */
841
- /** 行的状态点语义(在办三态 + 已结案)。 */
842
- function dotOf(row) {
843
- if (row.kind === "closed") return "done";
844
- if (row.task.status === "blocked") return "warning";
845
- if (row.task.status === "doing") return "ongoing";
846
- return "idle";
847
- }
848
855
  /** 行标题:在办 = 任务名(缺则任务号);已结案 = 任务包名(缺则「完成了什么」首句)。 */
849
856
  function titleOf(row) {
850
857
  if (row.kind === "task") return row.task.title ?? row.task.id;
851
858
  const title = row.record.workpackage ?? row.record.summary;
852
859
  return title !== void 0 && title !== "" ? title : row.record.src ?? "—";
853
860
  }
854
- /** 行的「谁」:负责人优先,没有就创建人,都没有 = 未认领。 */
855
- function whoOf(row) {
856
- if (row.kind === "task") return row.task.owner ?? row.task.creator ?? null;
857
- return row.record.owner ?? null;
861
+ /** 状态 pill:blocked=黄 / stale=红 / open=灰;doing 是默认态不出标记。 */
862
+ function pillsOf(row) {
863
+ if (row.kind !== "task") return [];
864
+ const pills = [];
865
+ if (row.task.status === "blocked") pills.push("blocked");
866
+ if (row.task.stale === true) pills.push("stale");
867
+ if (row.task.status === "open") pills.push("open");
868
+ return pills;
858
869
  }
859
870
  /** 工作区徽标(第一区专用列):色块首字母 + 标题,定宽、超出省略(不猜官方图标名)。 */
860
871
  function WorkspaceTag(props) {
@@ -888,108 +899,40 @@ body[data-ds-dark-theme]{
888
899
  }
889
900
  function TaskRowView(props) {
890
901
  const { t, row } = props;
891
- const [open, setOpen] = (0, react.useState)(false);
892
- const mdLabels = (0, react.useMemo)(() => ({
893
- code: {
894
- copyLabel: t("mdCopy"),
895
- copiedLabel: t("mdCopied")
896
- },
897
- footnotes: t("mdFootnotes")
898
- }), [t]);
899
- /** 行的时间:在办 doing/blocked 看开始、其余看创建;已结案看结案。 */
900
- const stamp = row.kind === "closed" ? {
901
- value: row.record.time ?? row.record.created ?? null,
902
- label: t("fieldFinished")
903
- } : row.task.status === "open" ? {
904
- value: row.task.created ?? null,
905
- label: t("fieldCreated")
906
- } : {
907
- value: row.task.started ?? row.task.created ?? null,
908
- label: t("fieldStarted")
909
- };
910
- const when = stamp.value === null || stamp.value === void 0 || stamp.value === "" ? t("timeUnknown") : formatDateTime(stamp.value, { fallback: t("timeUnknown") });
911
- const who = whoOf(row);
912
- const workspace = row.kind === "task" ? row.task.workspace : row.record.workspace;
913
902
  const category = row.kind === "task" ? row.task.category : row.record.category;
914
903
  const tags = (row.kind === "task" ? row.task.tags : row.record.tags) ?? [];
915
- const logPath = row.kind === "task" ? row.task.worklog : row.record.log;
916
- return (0, react.createElement)("div", {
917
- className: "dtl-row",
918
- "data-open": open
919
- }, (0, react.createElement)("button", {
904
+ const id = row.kind === "task" ? row.task.id : row.record.src ?? "";
905
+ const pills = pillsOf(row);
906
+ const days = row.kind === "task" ? row.task.ageDays : null;
907
+ const when = row.kind === "task" ? days === null ? t("timeUnknown") : t("rowAge", { n: days }) : `${t("fieldFinished")} ${formatDateTime(row.record.time ?? row.record.created ?? "", { fallback: t("timeUnknown") })}`;
908
+ return (0, react.createElement)("div", { className: "dtl-taskrow" }, (0, react.createElement)("div", { className: "dtl-taskrow__top" }, props.showWorkspace === true ? (0, react.createElement)(WorkspaceTag, { workspace: row.kind === "task" ? row.task.workspace : row.record.workspace }) : null, (0, react.createElement)("span", {
909
+ className: "dtl-taskrow__id",
910
+ title: id
911
+ }, id), pills.map((pill) => (0, react.createElement)("span", {
912
+ key: pill,
913
+ className: `dtl-pill dtl-pill--${pill}`
914
+ }, t(pill === "blocked" ? "statusBlocked" : pill === "stale" ? "statusStale" : "statusOpen"))), (0, react.createElement)("span", { style: {
915
+ flex: "1 1 auto",
916
+ minWidth: 0
917
+ } }), (0, react.createElement)("span", { className: "dtl-taskrow__age" }, when), row.kind === "task" ? (0, react.createElement)("span", { className: "dtl-taskrow__actions" }, (0, react.createElement)("button", {
920
918
  type: "button",
921
- className: "dtl-row-head",
922
- "aria-expanded": open,
923
- title: open ? t("collapseAria") : t("expandAria"),
924
- onClick: () => {
925
- setOpen(!open);
926
- }
927
- }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: dotOf(row) }), props.showWorkspace === true ? (0, react.createElement)(WorkspaceTag, { workspace }) : null, (0, react.createElement)("span", {
928
- className: "dtl-ellipsis",
929
- style: {
930
- flex: "1 1 auto",
931
- minWidth: 0,
932
- fontSize: "var(--dtl-font-md)"
933
- },
919
+ className: "dtl-minibtn",
920
+ title: t("actionResume")
921
+ }, t("actionResume")), (0, react.createElement)("button", {
922
+ type: "button",
923
+ className: "dtl-minibtn",
924
+ title: t("actionFinish")
925
+ }, t("actionFinish")), (0, react.createElement)("button", {
926
+ type: "button",
927
+ className: "dtl-minibtn",
928
+ title: t("actionTransfer")
929
+ }, t("actionTransfer"))) : null), (0, react.createElement)("div", {
930
+ className: "dtl-taskrow__title dtl-ellipsis",
934
931
  title: titleOf(row)
935
- }, titleOf(row)), category === null || category === void 0 || category === "" ? null : (0, react.createElement)(Chip, { text: category }), tags.slice(0, 2).map((tag) => (0, react.createElement)(Chip, {
932
+ }, titleOf(row)), (category === null || category === void 0 || category === "") && tags.length === 0 ? null : (0, react.createElement)("div", { className: "dtl-taskrow__meta" }, category === null || category === void 0 || category === "" ? null : (0, react.createElement)(Chip, { text: category }), tags.map((tag) => (0, react.createElement)(Chip, {
936
933
  key: tag,
937
934
  text: `#${tag}`
938
- })), (0, react.createElement)("span", { style: {
939
- display: "flex",
940
- alignItems: "center",
941
- gap: "var(--dtl-space-2)",
942
- flex: "none",
943
- color: "var(--dtl-fg-3)",
944
- fontSize: "var(--dtl-font-xs)",
945
- whiteSpace: "nowrap"
946
- } }, (0, react.createElement)("span", { title: stamp.value ?? void 0 }, `${stamp.label} ${when}`), (0, react.createElement)("span", null, who === null || who === "" ? t("ownerUnclaimed") : `@${who}`)), (0, react.createElement)(open ? _deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular : _deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, { size: 16 })), open ? (0, react.createElement)("div", { className: "dtl-row-body" }, (0, react.createElement)("div", { style: {
947
- margin: "var(--dtl-space-2) 0",
948
- fontSize: "var(--dtl-font-sm)",
949
- color: "var(--dtl-fg-2)"
950
- } }, `${t("worklogTitle")}·${t("worklogPath")}:${logPath === null || logPath === void 0 || logPath === "" ? t("worklogMissing") : logPath}`), row.kind === "closed" ? (0, react.createElement)("div", { style: {
951
- fontSize: "var(--dtl-font-md)",
952
- color: "var(--dtl-fg-2)"
953
- } }, row.record.summary ?? "") : (0, react.createElement)(WorklogBody, {
954
- t,
955
- workspaceId: row.task.workspace.id,
956
- taskId: row.task.id,
957
- labels: mdLabels
958
- })) : null);
959
- }
960
- /** 在办行的过程正文:**展开才取**(首屏不为正文付代价),且只在挂载时取一次(不随重渲染重取)。 */
961
- function WorklogBody(props) {
962
- const { t, workspaceId, taskId, labels } = props;
963
- const [detail, setDetail] = (0, react.useState)(null);
964
- const [failed, setFailed] = (0, react.useState)(null);
965
- (0, react.useEffect)(() => {
966
- let alive = true;
967
- fetchTaskDetail(workspaceId, taskId).then((body) => {
968
- if (alive) setDetail(body);
969
- }).catch((error) => {
970
- if (alive) setFailed(error instanceof Error ? error.message : String(error));
971
- });
972
- return () => {
973
- alive = false;
974
- };
975
- }, [workspaceId, taskId]);
976
- if (failed !== null) return (0, react.createElement)("div", { style: {
977
- color: "var(--dtl-danger)",
978
- fontSize: "var(--dtl-font-sm)"
979
- } }, `${t("loadFailed")}:${failed}`);
980
- if (detail === null) return (0, react.createElement)("div", { style: {
981
- color: "var(--dtl-fg-3)",
982
- fontSize: "var(--dtl-font-sm)"
983
- } }, t("loading"));
984
- if (detail.worklog === null) return (0, react.createElement)("div", { style: {
985
- color: "var(--dtl-fg-3)",
986
- fontSize: "var(--dtl-font-sm)"
987
- } }, t("worklogMissing"));
988
- return (0, react.createElement)("div", { className: "dtl-worklog" }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
989
- text: detail.worklog,
990
- labels,
991
- variant: "compact"
992
- }));
935
+ }))));
993
936
  }
994
937
  //#endregion
995
938
  //#region src/client/board-home.ts
@@ -1088,15 +1031,12 @@ body[data-ds-dark-theme]{
1088
1031
  const view = (0, react.useMemo)(() => selectView(state), [state]);
1089
1032
  const stats = board?.stats;
1090
1033
  const days = (value) => value === null || value === void 0 ? t("daysNone") : t("daysValue", { n: value });
1091
- /** 统计卡 → 筛选联动(卡即入口:点一下跳到对应视角)。 */
1092
- const card = (label, value, hint, patch) => (0, react.createElement)(StatCard, {
1034
+ /** 统计卡:纯展示(用户 2026-10-17:先全去掉点击,界面定稿后再议滚动 / 筛选联动)。 */
1035
+ const card = (label, value, hint) => (0, react.createElement)(StatCard, {
1093
1036
  label: t(label),
1094
1037
  value,
1095
1038
  hint,
1096
- active: isActive(state.query, patch),
1097
- onClick: () => {
1098
- setQuery(patch);
1099
- }
1039
+ interactive: false
1100
1040
  });
1101
1041
  /** 统计台卡:不可点(首页没有已结案可筛),hover 给具体日期区间。 */
1102
1042
  const periodCard = (label, period) => (0, react.createElement)(StatCard, {
@@ -1170,38 +1110,15 @@ body[data-ds-dark-theme]{
1170
1110
  }, state.refreshing ? t("refreshing") : t("refresh")))), tab === "stats" ? (0, react.createElement)("div", { style: hintStyle }, t("statsPlaceholder")) : (0, react.createElement)("div", null, stats === void 0 ? null : (0, react.createElement)("div", {
1171
1111
  className: "dtl-stats",
1172
1112
  style: statsRowStyle
1173
- }, (0, react.createElement)("div", { className: "dtl-stats__cards" }, card("statMyActive", stats.myActive, t("statMyActiveHint", { n: stats.myBlocked }), {
1174
- scope: "mine",
1175
- status: "all",
1176
- workspace: ""
1177
- }), card("statOpen", stats.open, t("statOpenHint", { n: days(stats.oldestOpenDays) }), {
1178
- scope: "all",
1179
- status: "open",
1180
- workspace: ""
1181
- }), card("statDoing", stats.doing, t("statDoingHint", { n: stats.doing - stats.myActive + stats.myBlocked }), {
1182
- scope: "all",
1183
- status: "active",
1184
- workspace: ""
1185
- }), (0, react.createElement)(StatCard, {
1113
+ }, (0, react.createElement)("div", { className: "dtl-stats__cards" }, card("statMyActive", stats.myActive, t("statMyActiveHint", { n: stats.myBlocked })), card("statOpen", stats.open, t("statOpenHint", { n: days(stats.oldestOpenDays) })), card("statDoing", stats.doing, t("statDoingHint", { n: stats.doing - stats.myActive + stats.myBlocked })), (0, react.createElement)(StatCard, {
1186
1114
  label: t("statBlockedStale"),
1187
1115
  value: stats.blocked,
1188
1116
  hint: stats.stale === 0 ? void 0 : (0, react.createElement)("span", null, (0, react.createElement)("span", { style: {
1189
1117
  color: "var(--dtl-danger)",
1190
1118
  fontWeight: 600
1191
1119
  } }, t("statStaleWord")), ` ${t("statStaleHint", { n: stats.stale })}`),
1192
- active: isActive(state.query, {
1193
- scope: "all",
1194
- status: "blocked",
1195
- workspace: ""
1196
- }),
1197
1120
  title: t("statStaleTitle", { n: stats.staleDays }),
1198
- onClick: () => {
1199
- setQuery({
1200
- scope: "all",
1201
- status: "blocked",
1202
- workspace: ""
1203
- });
1204
- }
1121
+ interactive: false
1205
1122
  }), periodCard("statWeekDone", board?.trend.thisWeek), periodCard("statLastWeekDone", board?.trend.lastWeek), periodCard("statMonthDone", board?.trend.thisMonth), periodCard("statLastMonthDone", board?.trend.lastMonth)), (0, react.createElement)("div", {
1206
1123
  className: "dtl-card",
1207
1124
  style: chartCardStyle
@@ -1361,10 +1278,6 @@ body[data-ds-dark-theme]{
1361
1278
  row
1362
1279
  }))) : null);
1363
1280
  }
1364
- /** 卡片高亮:当前 query 是否正好落在该卡对应的视角上。 */
1365
- function isActive(query, patch) {
1366
- return Object.entries(patch).every(([key, value]) => query[key] === value);
1367
- }
1368
1281
  /** 是否「什么都没筛」:真空才给引导卡,一有筛选就只能是「筛选空」。 */
1369
1282
  function isPristine(query) {
1370
1283
  return query.scope === "all" && query.status === "all" && query.workspace === "" && query.keyword.trim() === "";
@@ -1436,6 +1349,10 @@ body[data-ds-dark-theme]{
1436
1349
  fieldStarted: "开始",
1437
1350
  fieldFinished: "结案",
1438
1351
  timeUnknown: "时间未知",
1352
+ rowAge: "已做 {n} 天",
1353
+ actionResume: "继续",
1354
+ actionFinish: "结束",
1355
+ actionTransfer: "转移",
1439
1356
  worklogTitle: "过程文档",
1440
1357
  worklogPath: "路径",
1441
1358
  worklogMissing: "(缺失)",
@@ -1515,6 +1432,10 @@ body[data-ds-dark-theme]{
1515
1432
  fieldStarted: "started",
1516
1433
  fieldFinished: "closed",
1517
1434
  timeUnknown: "unknown time",
1435
+ rowAge: "active for {n}d",
1436
+ actionResume: "Resume",
1437
+ actionFinish: "Close",
1438
+ actionTransfer: "Transfer",
1518
1439
  worklogTitle: "Worklog",
1519
1440
  worklogPath: "path",
1520
1441
  worklogMissing: "(missing)",