dsh-todo-time-log 0.5.2 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +177 -530
  2. package/lib/client.js +248 -103
  3. package/lib/index.js +42 -0
  4. package/package.json +14 -3
package/lib/client.js CHANGED
@@ -592,6 +592,7 @@
592
592
  "calendar.weekdaySun"
593
593
  ];
594
594
  var TABLE_COLUMNS = [
595
+ { key: "check", labelKey: "row.complete", sortBy: null, srOnly: true },
595
596
  { key: "title", labelKey: "table.title", sortBy: "title" },
596
597
  { key: "project", labelKey: "table.project", sortBy: "project" },
597
598
  { key: "status", labelKey: "table.status", sortBy: null },
@@ -625,8 +626,13 @@
625
626
  overflow: hidden;
626
627
  color: var(--dsw-alias-label-primary);
627
628
  background: var(--dsw-alias-bg-base);
629
+ /* 与宿主 UI 同一套字体栈(真机继承宿主 body 字体):显式声明后,静态预览与真机一致,
630
+ 也不会被祖先样式改掉。 */
631
+ font-family: var(--dsw-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
632
+ "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif);
628
633
  font-size: 14px;
629
634
  line-height: 1.6;
635
+ -webkit-font-smoothing: antialiased;
630
636
  }
631
637
  .ttl-scroll {
632
638
  flex: 1;
@@ -814,8 +820,9 @@
814
820
  /* 数据区:视图/筛选控制与列表同属一张卡片,成为唯一的数据容器。 */
815
821
  .ttl-data {
816
822
  border: 0.5px solid var(--dsw-alias-border-l2);
817
- border-radius: var(--dsw-radius-md);
823
+ border-radius: var(--dsw-radius-lg);
818
824
  background: var(--dsw-alias-bg-base);
825
+ box-shadow: var(--dsw-shadow-lv1);
819
826
  overflow: hidden;
820
827
  }
821
828
  .ttl-data > .ttl-toolbar {
@@ -1072,8 +1079,8 @@
1072
1079
  display: inline-flex;
1073
1080
  align-items: center;
1074
1081
  flex: none;
1075
- height: 20px;
1076
- padding: 0 8px;
1082
+ height: 18px;
1083
+ padding: 0 7px;
1077
1084
  border: 0;
1078
1085
  border-radius: 9999px;
1079
1086
  background: color-mix(in srgb, var(--dsw-alias-label-secondary) 9%, transparent);
@@ -1087,19 +1094,36 @@
1087
1094
  background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent);
1088
1095
  color: var(--dsw-alias-state-success-primary);
1089
1096
  }
1090
- .ttl-badge-row { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px; }
1091
- .ttl-badge.ttl-priority-high {
1092
- background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent);
1093
- color: var(--dsw-alias-state-error-primary);
1097
+ /* 状态:胶囊 + 状态点(颜色沿用胶囊文字色)。 */
1098
+ .ttl-status-badge::before {
1099
+ content: '';
1100
+ flex: none;
1101
+ width: 5px;
1102
+ height: 5px;
1103
+ margin-right: 4px;
1104
+ border-radius: 50%;
1105
+ background: currentColor;
1106
+ opacity: 0.9;
1094
1107
  }
1095
- .ttl-badge.ttl-priority-medium {
1096
- background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 16%, transparent);
1097
- color: var(--dsw-alias-state-warn-primary);
1108
+ .ttl-badge-row { display: inline-flex; flex-wrap: nowrap; align-items: center; gap: 4px; }
1109
+ /* 类型:小号圆角矩形标签(与状态胶囊区分层级)。 */
1110
+ .ttl-badge.ttl-type-badge {
1111
+ border-radius: 5px;
1112
+ padding: 0 6px;
1113
+ font-size: 10.5px;
1114
+ letter-spacing: 0.02em;
1098
1115
  }
1099
- .ttl-badge.ttl-priority-low {
1100
- background: color-mix(in srgb, var(--dsw-alias-label-secondary) 9%, transparent);
1101
- color: var(--dsw-alias-label-secondary);
1116
+ /* 优先级:箭头 + 文字,不再用第二个彩色胶囊,避免「标签汤」。 */
1117
+ .ttl-badge.ttl-priority-badge {
1118
+ gap: 3px;
1119
+ padding: 0;
1120
+ background: none;
1121
+ font-weight: 500;
1102
1122
  }
1123
+ .ttl-priority-arrow { font-size: 11px; line-height: 1; }
1124
+ .ttl-badge.ttl-priority-badge.ttl-priority-high { background: none; color: var(--dsw-alias-state-error-primary); }
1125
+ .ttl-badge.ttl-priority-badge.ttl-priority-medium { background: none; color: var(--dsw-alias-state-warn-label); }
1126
+ .ttl-badge.ttl-priority-badge.ttl-priority-low { background: none; color: var(--dsw-alias-label-tertiary); }
1103
1127
  .ttl-priority-select select { min-width: 64px; }
1104
1128
  .ttl-type-select select { min-width: 76px; }
1105
1129
  .ttl-badge.ttl-type-bug {
@@ -1176,7 +1200,7 @@
1176
1200
  gap: 2px;
1177
1201
  }
1178
1202
  .ttl-link {
1179
- padding: 2px 6px;
1203
+ padding: 2px 3px;
1180
1204
  border: 0;
1181
1205
  border-radius: 6px;
1182
1206
  background: none;
@@ -1246,10 +1270,11 @@
1246
1270
  }
1247
1271
  .ttl-skeleton-row {
1248
1272
  display: grid;
1249
- grid-template-columns: 32fr 14fr 8fr 12fr 12fr 10fr;
1273
+ /* 与 .ttl-table 的列宽同口径(标题吃剩余,状态列固定),骨架屏不跳列。 */
1274
+ grid-template-columns: 20px minmax(0, 1fr) 72px 132px 100px 100px 92px;
1250
1275
  align-items: center;
1251
- gap: 24px;
1252
- height: 44px;
1276
+ gap: 12px;
1277
+ height: 42px;
1253
1278
  padding: 0 12px;
1254
1279
  border-bottom: 0.5px solid var(--dsw-alias-border-l1);
1255
1280
  }
@@ -1300,14 +1325,34 @@
1300
1325
  .ttl-board-edit .ttl-project-picker { flex: 1 1 100%; }
1301
1326
  .ttl-calendar-entry-edit .ttl-project-picker { flex: 1 1 100%; }
1302
1327
  .ttl-gantt-row-edit .ttl-project-picker { flex: 0 1 220px; }.ttl-badge-project {
1303
- max-width: 140px;
1304
- overflow: hidden;
1305
- text-overflow: ellipsis;
1328
+ display: inline-flex;
1329
+ align-items: center;
1330
+ gap: 5px;
1331
+ max-width: 100%;
1306
1332
  padding: 0;
1307
1333
  background: none;
1308
- color: var(--dsw-alias-label-tertiary);
1334
+ color: var(--dsw-alias-label-secondary);
1309
1335
  font-weight: 400;
1310
1336
  }
1337
+ .ttl-project-dot {
1338
+ flex: none;
1339
+ width: 6px;
1340
+ height: 6px;
1341
+ border-radius: 50%;
1342
+ background: var(--dsw-alias-label-tertiary);
1343
+ }
1344
+ .ttl-badge-project[data-tone='business'] .ttl-project-dot { background: var(--dsw-alias-state-business-primary); }
1345
+ .ttl-badge-project[data-tone='done'] .ttl-project-dot { background: var(--dsw-alias-state-done-primary); }
1346
+ .ttl-badge-project[data-tone='success'] .ttl-project-dot { background: var(--dsw-alias-state-success-primary); }
1347
+ .ttl-badge-project[data-tone='warn'] .ttl-project-dot { background: var(--dsw-alias-state-warn-primary); }
1348
+ .ttl-badge-project[data-tone='error'] .ttl-project-dot { background: var(--dsw-alias-state-error-primary); }
1349
+ .ttl-badge-project[data-tone='idle'] .ttl-project-dot { background: var(--dsw-alias-label-caption); }
1350
+ .ttl-project-name {
1351
+ min-width: 0;
1352
+ overflow: hidden;
1353
+ text-overflow: ellipsis;
1354
+ white-space: nowrap;
1355
+ }
1311
1356
  .ttl-badge-project-none { color: var(--dsw-alias-label-caption); }
1312
1357
 
1313
1358
  .ttl-list-group {
@@ -1616,22 +1661,33 @@
1616
1661
  }
1617
1662
  .ttl-table {
1618
1663
  width: 100%;
1619
- min-width: 700px;
1664
+ /* 固定列宽:列宽只由 col 决定,不随标题长短跳动(否则长标题会挤扁状态列,徽标被迫换行)。 */
1665
+ table-layout: fixed;
1666
+ min-width: 860px;
1620
1667
  border-collapse: collapse;
1621
1668
  font-size: 13px;
1622
1669
  }
1670
+ /* 列宽口径:标题吃掉剩余宽度,其余列固定,保证「状态」列永远放得下一行徽标。 */
1671
+ .ttl-col-check { width: 40px; }
1672
+ .ttl-col-project { width: 104px; }
1673
+ .ttl-col-status { width: 160px; }
1674
+ .ttl-col-createdAt { width: 126px; }
1675
+ .ttl-col-completedAt { width: 126px; }
1676
+ .ttl-col-actions { width: 118px; }
1623
1677
  .ttl-th {
1624
- height: 36px;
1678
+ height: 34px;
1625
1679
  padding: 0 12px;
1626
- border-bottom: 0.5px solid var(--dsw-alias-border-l1);
1680
+ border-bottom: 0.5px solid var(--dsw-alias-border-l2);
1627
1681
  background: var(--dsw-alias-bg-module-platform);
1628
- color: var(--dsw-alias-label-secondary);
1629
- font-size: 12px;
1682
+ color: var(--dsw-alias-label-tertiary);
1683
+ font-size: 11px;
1630
1684
  font-weight: 500;
1631
- line-height: 18px;
1685
+ line-height: 16px;
1686
+ letter-spacing: 0.02em;
1632
1687
  text-align: left;
1633
1688
  white-space: nowrap;
1634
1689
  }
1690
+ .ttl-th-check { width: 40px; padding: 0 4px 0 12px; }
1635
1691
  .ttl-th-right { text-align: right; }
1636
1692
  .ttl-th-sortable { padding: 0; }
1637
1693
  .ttl-th-button {
@@ -1639,14 +1695,15 @@
1639
1695
  align-items: center;
1640
1696
  gap: 4px;
1641
1697
  width: 100%;
1642
- height: 36px;
1698
+ height: 34px;
1643
1699
  padding: 0 12px;
1644
1700
  border: 0;
1645
1701
  background: none;
1646
1702
  color: inherit;
1647
1703
  font: inherit;
1648
- font-size: 12px;
1649
- line-height: 18px;
1704
+ font-size: 11px;
1705
+ letter-spacing: 0.02em;
1706
+ line-height: 16px;
1650
1707
  text-align: left;
1651
1708
  white-space: nowrap;
1652
1709
  cursor: pointer;
@@ -1655,7 +1712,10 @@
1655
1712
  .ttl-th-button:hover { color: var(--dsw-alias-label-primary); }
1656
1713
  .ttl-th-button-active { color: var(--dsw-alias-label-primary); }
1657
1714
  .ttl-th-arrow { color: var(--dsw-alias-label-tertiary); font-size: 9px; line-height: 1; }
1658
- .ttl-table-row { border-bottom: 0.5px solid var(--dsw-alias-border-l1); }
1715
+ .ttl-table-row {
1716
+ border-bottom: 0.5px solid var(--dsw-alias-border-l1);
1717
+ transition: background 0.12s ease-out;
1718
+ }
1659
1719
  .ttl-table-row:hover { background: var(--dsw-alias-interactive-bg-hover); }
1660
1720
  .ttl-table tbody tr:last-child { border-bottom: 0; }
1661
1721
  .ttl-table-group-row .ttl-td {
@@ -1671,7 +1731,8 @@
1671
1731
  vertical-align: middle;
1672
1732
  line-height: 20px;
1673
1733
  }
1674
- .ttl-td-title { max-width: 360px; }
1734
+ .ttl-td-check { width: 40px; padding: 11px 4px 11px 12px; }
1735
+ .ttl-td-title { max-width: none; }
1675
1736
  .ttl-td-time {
1676
1737
  color: var(--dsw-alias-label-tertiary);
1677
1738
  font-size: 12px;
@@ -1681,7 +1742,17 @@
1681
1742
  font-variant-numeric: tabular-nums;
1682
1743
  }
1683
1744
  .ttl-td-actions { text-align: right; white-space: nowrap; }
1745
+ /* 操作区默认压低,行 hover / 键盘聚焦时再提亮,避免整张表被按钮占满注意力。 */
1746
+ .ttl-td-actions .ttl-link { color: var(--dsw-alias-label-tertiary); }
1747
+ .ttl-td-actions .ttl-link:hover { color: var(--dsw-alias-label-primary); }
1748
+ .ttl-table-row:hover .ttl-td-actions .ttl-link,
1749
+ .ttl-table-row:focus-within .ttl-td-actions .ttl-link { color: var(--dsw-alias-label-secondary); }
1750
+ .ttl-td-actions .ttl-link.ttl-link-danger { color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 72%, transparent); }
1751
+ .ttl-table-row:hover .ttl-td-actions .ttl-link.ttl-link-danger,
1752
+ .ttl-td-actions .ttl-link.ttl-link-danger:hover { color: var(--dsw-alias-state-error-primary); }
1684
1753
  .ttl-table-title-button {
1754
+ display: -webkit-box;
1755
+ width: 100%;
1685
1756
  max-width: 100%;
1686
1757
  padding: 0;
1687
1758
  border: 0;
@@ -1692,10 +1763,15 @@
1692
1763
  font-weight: 500;
1693
1764
  line-height: 20px;
1694
1765
  text-align: left;
1766
+ overflow: hidden;
1695
1767
  overflow-wrap: anywhere;
1768
+ /* 最多两行:长标题截断为省略号,完整文案在 title 与「详情」里可见,行高保持稳定。 */
1769
+ -webkit-line-clamp: 2;
1770
+ -webkit-box-orient: vertical;
1696
1771
  cursor: pointer;
1697
1772
  }
1698
1773
  .ttl-table-title-button:hover { color: var(--dsw-alias-state-business-primary); }
1774
+ .ttl-table-row[data-status='completed'] .ttl-table-title-button { color: var(--dsw-alias-label-secondary); }
1699
1775
  .ttl-table-detail-row .ttl-td { padding: 0 12px 10px; }
1700
1776
 
1701
1777
  .ttl-board {
@@ -1786,7 +1862,7 @@
1786
1862
  .ttl-board-move-select { height: 24px; max-width: 150px; padding: 0 4px; border: 0.5px solid var(--dsw-alias-border-l3); border-radius: 6px; background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-secondary); font: inherit; font-size: 12px; cursor: pointer; }
1787
1863
  .ttl-board-move-select:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary); outline-offset: 1px; }
1788
1864
  .ttl-board-compact .ttl-board-card { gap: 2px; padding: 6px 28px 6px 32px; }
1789
- .ttl-board-compact .ttl-board-check { top: 6px; left: 6px; width: 22px; height: 22px; }
1865
+ .ttl-board-compact .ttl-board-check { top: 6px; left: 6px; width: 18px; height: 18px; }
1790
1866
  .ttl-board-compact .ttl-board-more { top: 4px; right: 2px; }
1791
1867
  .ttl-board-compact .ttl-board-card-title { -webkit-line-clamp: 1; }
1792
1868
  .ttl-board-compact .ttl-board-card-files { display: none; }
@@ -1805,8 +1881,13 @@
1805
1881
  gap: 4px;
1806
1882
  padding: 9px 32px 9px 36px;
1807
1883
  border: 0.5px solid var(--dsw-alias-border-l2);
1808
- border-radius: 10px;
1884
+ border-radius: 12px;
1809
1885
  background: var(--dsw-alias-bg-base);
1886
+ transition: border-color 0.15s, box-shadow 0.15s;
1887
+ }
1888
+ .ttl-board-card:hover {
1889
+ border-color: var(--dsw-alias-border-l3);
1890
+ box-shadow: var(--dsw-shadow-lv1);
1810
1891
  }
1811
1892
  .ttl-board-card-editing { padding: 9px 10px; }
1812
1893
  .ttl-board-card-main { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
@@ -1835,30 +1916,30 @@
1835
1916
  }
1836
1917
  .ttl-board-project { display: inline-flex; align-items: center; gap: 4px; min-width: 0; max-width: 100%; }
1837
1918
  .ttl-board-project-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1838
- .ttl-board-check {
1839
- position: absolute;
1840
- top: 8px;
1841
- left: 8px;
1919
+ /* 勾选圈:表格行首与看板卡片共用(看板只额外负责定位)。 */
1920
+ .ttl-check {
1842
1921
  display: inline-flex;
1843
1922
  align-items: center;
1844
1923
  justify-content: center;
1845
- width: 24px;
1846
- height: 24px;
1924
+ width: 20px;
1925
+ height: 20px;
1847
1926
  padding: 0;
1848
- border: 1.5px solid var(--dsw-alias-border-l3);
1927
+ border: 1.5px solid var(--dsw-alias-border-l4);
1849
1928
  border-radius: 50%;
1850
1929
  background: none;
1851
1930
  color: transparent;
1852
1931
  cursor: pointer;
1932
+ transition: border-color 0.15s, background 0.15s, color 0.15s;
1853
1933
  }
1854
- .ttl-board-check:hover { border-color: var(--dsw-alias-state-success-primary); }
1855
- .ttl-board-check[data-checked='true'] {
1934
+ .ttl-check:hover { border-color: var(--dsw-alias-state-success-primary); }
1935
+ .ttl-check[data-checked='true'] {
1856
1936
  border-color: var(--dsw-alias-state-success-primary);
1857
1937
  background: var(--dsw-alias-state-success-primary);
1858
1938
  color: var(--dsw-alias-label-primary-foreground);
1859
1939
  }
1860
- .ttl-board-check:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary); outline-offset: 2px; }
1861
- .ttl-board-check:disabled { opacity: 0.5; cursor: not-allowed; }
1940
+ .ttl-check:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary); outline-offset: 2px; }
1941
+ .ttl-check:disabled { opacity: 0.5; cursor: not-allowed; }
1942
+ .ttl-board-check { position: absolute; top: 8px; left: 8px; }
1862
1943
  .ttl-board-more {
1863
1944
  position: absolute;
1864
1945
  top: 6px;
@@ -2119,7 +2200,7 @@
2119
2200
  .ttl-file-chip-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2120
2201
  .ttl-file-chip-icon { font-size: 10px; line-height: 1; }
2121
2202
  .ttl-file-more { color: var(--dsw-alias-label-tertiary); font-size: 11px; line-height: 20px; }
2122
- .ttl-title-cell { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; min-width: 0; }
2203
+ .ttl-title-cell { display: flex; flex-direction: column; align-items: stretch; gap: 3px; min-width: 0; }
2123
2204
  .ttl-board-card-files { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 2px; }
2124
2205
  .ttl-calendar-entry-files { display: flex; flex: 1 1 100%; flex-wrap: wrap; gap: 4px; }
2125
2206
  .ttl-detail-files { display: flex; flex-wrap: wrap; gap: 4px; }
@@ -2495,6 +2576,16 @@
2495
2576
  const name = normalizeProject(project);
2496
2577
  return name === "" ? t("project.none") : name;
2497
2578
  }
2579
+ var PROJECT_TONES = ["business", "done", "success", "warn", "error"];
2580
+ function projectTone(project) {
2581
+ const name = normalizeProject(project);
2582
+ if (name === "") return "idle";
2583
+ let hash = 0;
2584
+ for (let index = 0; index < name.length; index += 1) {
2585
+ hash = hash * 31 + name.charCodeAt(index) | 0;
2586
+ }
2587
+ return PROJECT_TONES[Math.abs(hash) % PROJECT_TONES.length];
2588
+ }
2498
2589
  function compareProjectNames(left, right) {
2499
2590
  if (left === right) return 0;
2500
2591
  if (left === "") return 1;
@@ -2824,6 +2915,7 @@
2824
2915
  (index) => h(
2825
2916
  "div",
2826
2917
  { key: index, className: "ttl-skeleton-row" },
2918
+ bar(true),
2827
2919
  bar(),
2828
2920
  bar(),
2829
2921
  bar(true),
@@ -2886,7 +2978,54 @@
2886
2978
  function ProjectBadge(props) {
2887
2979
  const project = normalizeProject(props && props.project);
2888
2980
  const className = project === "" ? "ttl-badge ttl-badge-project ttl-badge-project-none" : "ttl-badge ttl-badge-project";
2889
- return h("span", { className, title: projectLabel(project) }, projectLabel(project));
2981
+ return h(
2982
+ "span",
2983
+ { className, title: projectLabel(project), "data-tone": projectTone(project) },
2984
+ h("span", { className: "ttl-project-dot", "aria-hidden": "true" }),
2985
+ h("span", { className: "ttl-project-name" }, projectLabel(project))
2986
+ );
2987
+ }
2988
+ function CheckIcon(props) {
2989
+ const checked = Boolean(props && props.checked);
2990
+ return h(
2991
+ "svg",
2992
+ {
2993
+ width: 12,
2994
+ height: 12,
2995
+ viewBox: "0 0 16 16",
2996
+ fill: "none",
2997
+ stroke: "currentColor",
2998
+ strokeWidth: 2,
2999
+ strokeLinecap: "round",
3000
+ strokeLinejoin: "round",
3001
+ "aria-hidden": "true",
3002
+ focusable: "false"
3003
+ },
3004
+ checked ? h("path", { d: "M3.5 8.5l3 3 6-6.5" }) : null
3005
+ );
3006
+ }
3007
+ function CheckButton(props) {
3008
+ const checked = Boolean(props && props.checked);
3009
+ const disabled = Boolean(props && props.disabled);
3010
+ const label = props && typeof props.label === "string" ? props.label : "";
3011
+ const hover = props && typeof props.title === "string" && props.title !== "" ? props.title : void 0;
3012
+ const className = props && typeof props.className === "string" && props.className !== "" ? props.className : "";
3013
+ const onClick = props && typeof props.onClick === "function" ? props.onClick : null;
3014
+ return h(
3015
+ "button",
3016
+ {
3017
+ type: "button",
3018
+ className: className === "" ? "ttl-check" : `ttl-check ${className}`,
3019
+ role: "checkbox",
3020
+ "aria-checked": checked ? "true" : "false",
3021
+ "aria-label": label,
3022
+ title: hover,
3023
+ "data-checked": checked ? "true" : "false",
3024
+ disabled,
3025
+ onClick
3026
+ },
3027
+ h(CheckIcon, { checked })
3028
+ );
2890
3029
  }
2891
3030
  function GanttView(props) {
2892
3031
  const todos = props && Array.isArray(props.todos) ? props.todos : [];
@@ -3555,22 +3694,6 @@
3555
3694
  )
3556
3695
  );
3557
3696
  };
3558
- const boardCheckIcon = (completed) => h(
3559
- "svg",
3560
- {
3561
- width: 12,
3562
- height: 12,
3563
- viewBox: "0 0 16 16",
3564
- fill: "none",
3565
- stroke: "currentColor",
3566
- strokeWidth: 2,
3567
- strokeLinecap: "round",
3568
- strokeLinejoin: "round",
3569
- "aria-hidden": "true",
3570
- focusable: "false"
3571
- },
3572
- completed ? h("path", { d: "M3.5 8.5l3 3 6-6.5" }) : null
3573
- );
3574
3697
  const renderBoardCard = (todo) => {
3575
3698
  const isEditing = editingId === todo.id;
3576
3699
  const isConfirming = confirmId === todo.id;
@@ -3609,23 +3732,16 @@
3609
3732
  prioritySelectControl(editingPriority, setEditingPriority, busyId === todo.id),
3610
3733
  typeSelectControl(editingType, setEditingType, busyId === todo.id)
3611
3734
  ) : [
3612
- h(
3613
- "button",
3614
- {
3615
- key: "check",
3616
- type: "button",
3617
- className: "ttl-board-check",
3618
- role: "checkbox",
3619
- "aria-checked": completed ? "true" : "false",
3620
- "aria-label": t("board.checkAria", { title }),
3621
- "data-checked": completed ? "true" : "false",
3622
- disabled: isBusy,
3623
- onClick: () => {
3624
- void handleToggle(todo);
3625
- }
3626
- },
3627
- boardCheckIcon(completed)
3628
- ),
3735
+ h(CheckButton, {
3736
+ key: "check",
3737
+ checked: completed,
3738
+ disabled: isBusy,
3739
+ label: t("board.checkAria", { title }),
3740
+ className: "ttl-board-check",
3741
+ onClick: () => {
3742
+ void handleToggle(todo);
3743
+ }
3744
+ }),
3629
3745
  h(
3630
3746
  "div",
3631
3747
  { key: "main", className: "ttl-board-card-main" },
@@ -3639,8 +3755,8 @@
3639
3755
  "span",
3640
3756
  { className: "ttl-board-project", title: projectLabel(project) },
3641
3757
  h("span", {
3642
- className: "ttl-board-dot",
3643
- "data-status": "project",
3758
+ className: "ttl-project-dot",
3759
+ "data-tone": projectTone(project),
3644
3760
  "aria-hidden": "true"
3645
3761
  }),
3646
3762
  h("span", { className: "ttl-board-project-name" }, projectLabel(project))
@@ -4089,7 +4205,7 @@
4089
4205
 
4090
4206
  // src/client/views/table.js
4091
4207
  function createTableView(deps) {
4092
- const { confirmId, detailIdOf, direction, editingId, emptyActionKey, expandedIds, filter, handleEmptyAction, handleHeaderSort, listStatus, priorityBadge, projectInput, renderActionButtons, renderDetailContent, renderFileChips, setConfirmId, sortBy, statusBadge, titleInput, toggleExpanded, typeBadge, visibleTodos } = deps;
4208
+ const { confirmId, detailIdOf, direction, editingId, emptyActionKey, expandedIds, filter, handleEmptyAction, handleHeaderSort, listStatus, priorityBadge, projectInput, renderActionButtons, renderDetailContent, renderFileChips, renderRowCheck, setConfirmId, sortBy, statusBadge, titleInput, toggleExpanded, typeBadge, visibleTodos } = deps;
4093
4209
  const pushTableRow = (rows, todo) => {
4094
4210
  const completed = todo.status === "completed";
4095
4211
  const isEditing = editingId === todo.id;
@@ -4131,6 +4247,7 @@
4131
4247
  }
4132
4248
  }
4133
4249
  },
4250
+ h("td", { className: "ttl-td ttl-td-check" }, renderRowCheck(todo)),
4134
4251
  titleCell,
4135
4252
  h(
4136
4253
  "td",
@@ -4144,7 +4261,7 @@
4144
4261
  ),
4145
4262
  h("td", { className: "ttl-td ttl-td-time" }, formatTime(todo.createdAt)),
4146
4263
  h("td", { className: "ttl-td ttl-td-time" }, completed ? formatTime(todo.completedAt) : EMPTY_TIME),
4147
- h("td", { className: "ttl-td ttl-td-actions" }, renderActionButtons(todo, { showDetail: true }))
4264
+ h("td", { className: "ttl-td ttl-td-actions" }, renderActionButtons(todo, { showDetail: true, showToggle: false }))
4148
4265
  )
4149
4266
  );
4150
4267
  if (expanded) {
@@ -4207,6 +4324,11 @@
4207
4324
  "aria-label": t("table.aria"),
4208
4325
  "aria-busy": listStatus === "loading" ? "true" : "false"
4209
4326
  },
4327
+ h(
4328
+ "colgroup",
4329
+ null,
4330
+ TABLE_COLUMNS.map((column) => h("col", { key: column.key, className: `ttl-col ttl-col-${column.key}` }))
4331
+ ),
4210
4332
  h(
4211
4333
  "thead",
4212
4334
  null,
@@ -4221,10 +4343,10 @@
4221
4343
  {
4222
4344
  key: column.key,
4223
4345
  scope: "col",
4224
- className: (column.sortBy === null ? "ttl-th" : "ttl-th ttl-th-sortable") + (column.align === "right" ? " ttl-th-right" : ""),
4346
+ className: (column.sortBy === null ? "ttl-th" : "ttl-th ttl-th-sortable") + (column.align === "right" ? " ttl-th-right" : "") + ` ttl-th-${column.key}`,
4225
4347
  "aria-sort": column.sortBy === null ? void 0 : ariaSort
4226
4348
  },
4227
- column.sortBy === null ? h("span", null, t(column.labelKey)) : h(
4349
+ column.srOnly === true ? h("span", { className: "ttl-sr-only" }, t(column.labelKey)) : column.sortBy === null ? h("span", null, t(column.labelKey)) : h(
4228
4350
  "button",
4229
4351
  {
4230
4352
  type: "button",
@@ -5037,7 +5159,9 @@
5037
5159
  const detailIdOf = (todo) => `ttl-detail-${String(todo.id).replace(/[^A-Za-z0-9_-]/g, "-")}`;
5038
5160
  const statusBadge = (todo) => h(
5039
5161
  "span",
5040
- { className: todo.status === "completed" ? "ttl-badge ttl-badge-completed" : "ttl-badge" },
5162
+ {
5163
+ className: todo.status === "completed" ? "ttl-badge ttl-status-badge ttl-badge-completed" : "ttl-badge ttl-status-badge"
5164
+ },
5041
5165
  t(todo.status === "completed" ? "row.completed" : "row.pending")
5042
5166
  );
5043
5167
  const priorityBadge = (todo) => {
@@ -5049,6 +5173,11 @@
5049
5173
  className: `ttl-badge ttl-priority-badge ttl-priority-${priority}`,
5050
5174
  "data-priority": priority
5051
5175
  },
5176
+ h(
5177
+ "span",
5178
+ { className: "ttl-priority-arrow", "aria-hidden": "true" },
5179
+ priority === "high" ? "↑" : priority === "medium" ? "↗" : "↓"
5180
+ ),
5052
5181
  t(`priority.${priority}`)
5053
5182
  );
5054
5183
  };
@@ -5171,22 +5300,24 @@
5171
5300
  return buttons;
5172
5301
  }
5173
5302
  if (opts.showDetail === true) buttons.push(detailToggleButton(todo));
5174
- buttons.push(
5175
- h(
5176
- "button",
5177
- {
5178
- key: "toggle",
5179
- type: "button",
5180
- className: "ttl-link",
5181
- disabled: isBusy,
5182
- "aria-label": t("row.toggleAria", { action: toggleLabel, title: todo.title }),
5183
- onClick: () => {
5184
- void handleToggle(todo);
5185
- }
5186
- },
5187
- toggleLabel
5188
- )
5189
- );
5303
+ if (opts.showToggle !== false) {
5304
+ buttons.push(
5305
+ h(
5306
+ "button",
5307
+ {
5308
+ key: "toggle",
5309
+ type: "button",
5310
+ className: "ttl-link",
5311
+ disabled: isBusy,
5312
+ "aria-label": t("row.toggleAria", { action: toggleLabel, title: todo.title }),
5313
+ onClick: () => {
5314
+ void handleToggle(todo);
5315
+ }
5316
+ },
5317
+ toggleLabel
5318
+ )
5319
+ );
5320
+ }
5190
5321
  buttons.push(
5191
5322
  h(
5192
5323
  "button",
@@ -5245,6 +5376,19 @@
5245
5376
  }
5246
5377
  return buttons;
5247
5378
  };
5379
+ const renderRowCheck = (todo) => {
5380
+ const completed = todo.status === "completed";
5381
+ return h(CheckButton, {
5382
+ checked: completed,
5383
+ disabled: isBusy,
5384
+ label: t("board.checkAria", { title: todo.title === "" ? t("list.empty") : todo.title }),
5385
+ title: t(completed ? "row.uncomplete" : "row.complete"),
5386
+ className: "ttl-row-check",
5387
+ onClick: () => {
5388
+ void handleToggle(todo);
5389
+ }
5390
+ });
5391
+ };
5248
5392
  const copyFilePath = (path) => {
5249
5393
  try {
5250
5394
  if (typeof navigator === "undefined" || navigator === null) return;
@@ -5464,6 +5608,7 @@
5464
5608
  renderActionButtons,
5465
5609
  renderDetailContent,
5466
5610
  renderFileChips,
5611
+ renderRowCheck,
5467
5612
  setBoardActionsId,
5468
5613
  setBoardLaneCollapsed,
5469
5614
  setBoardShowAllCompleted,