dsh-task-ledger 0.0.8 → 0.0.10

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
@@ -536,6 +536,39 @@ body[data-ds-dark-theme]{
536
536
  /* 过程正文:宽度受限、长行折行 */
537
537
  .dtl-worklog{max-width:72ch;font-size:var(--dtl-font-md);line-height:1.6}
538
538
 
539
+ /* 统计区整体:左 8 块(2 行 × 4 列)+ 右趋势图(参考图同款分栏)。
540
+ ⚠️ 列数写死 4 → 2,绝不给 3:3 列会排出 3+1 的非对称(用户 2026-10-17)。 */
541
+ .dtl-stats{display:grid;grid-template-columns:minmax(0,2fr) minmax(0,1fr);gap:var(--dtl-space-3);align-items:stretch;}
542
+ .dtl-stats__cards{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));grid-template-rows:repeat(2,auto);
543
+ gap:var(--dtl-space-3);align-content:start;}
544
+ @media (max-width:960px){.dtl-stats{grid-template-columns:minmax(0,1fr);}}
545
+ @media (max-width:640px){.dtl-stats__cards{grid-template-columns:repeat(2,minmax(0,1fr));}}
546
+
547
+ /* 趋势分组柱状图:细柱 + y 轴刻度 + 网格线 + 柱顶数值 + 均值虚线(参考图信息密度) */
548
+ .dtl-gchart{position:relative;height:104px;border-bottom:1px solid var(--dtl-border);}
549
+ .dtl-gchart__grid{position:absolute;left:0;right:0;border-top:1px solid var(--dtl-border-faint);pointer-events:none;}
550
+ .dtl-gchart__ytick{position:absolute;left:0;width:26px;text-align:right;font-size:10px;line-height:12px;
551
+ color:var(--dtl-fg-3);transform:translateY(50%);font-variant-numeric:tabular-nums;}
552
+ .dtl-gchart__avg{position:absolute;left:0;right:0;border-top:1px dashed var(--dtl-border-strong);pointer-events:none;}
553
+ .dtl-gchart__avgtag{position:absolute;right:0;font-size:10px;line-height:12px;color:var(--dtl-fg-3);
554
+ background:var(--dtl-surface-1);padding:0 2px;transform:translateY(-100%);}
555
+ .dtl-gchart__col{position:absolute;top:0;bottom:0;min-width:0;}
556
+ .dtl-gchart__pair{position:absolute;bottom:0;left:50%;transform:translateX(-50%);
557
+ display:flex;align-items:flex-end;gap:2px;}
558
+ .dtl-gchart__bar{width:8px;min-height:0;border-radius:var(--dtl-radius-xs) var(--dtl-radius-xs) 0 0;}
559
+ .dtl-gchart__bar--done{background:var(--dtl-business);}
560
+ .dtl-gchart__bar--created{background:var(--dtl-chip-bg);}
561
+ .dtl-gchart__col:hover .dtl-gchart__bar--done{background:var(--dtl-accent);}
562
+ .dtl-gchart__val{position:absolute;left:50%;transform:translateX(-50%);font-size:10px;line-height:12px;
563
+ color:var(--dtl-fg-2);font-variant-numeric:tabular-nums;white-space:nowrap;}
564
+ .dtl-gchart__label{position:absolute;left:0;right:0;bottom:-16px;text-align:center;
565
+ font-size:10px;line-height:14px;color:var(--dtl-fg-3);white-space:nowrap;}
566
+ .dtl-gchart-legend{display:flex;align-items:center;gap:var(--dtl-space-3);font-size:var(--dtl-font-xs);color:var(--dtl-fg-2);}
567
+ .dtl-gchart-legend i{display:inline-block;width:8px;height:8px;border-radius:2px;margin-right:4px;}
568
+
569
+ /* 过程正文:宽度受限、长行折行 */
570
+ .dtl-worklog{max-width:72ch;font-size:var(--dtl-font-md);line-height:1.6}
571
+
539
572
  /* 趋势柱状图:div 柱 + token 色,无新依赖 */
540
573
  .dtl-chart{display:flex;align-items:flex-end;gap:var(--dtl-space-2);height:120px;}
541
574
  .dtl-chart__col{display:flex;flex-direction:column;justify-content:flex-end;align-items:center;gap:var(--dtl-space-1);flex:1 1 0;height:100%;min-width:0;}
@@ -619,19 +652,24 @@ body[data-ds-dark-theme]{
619
652
  }
620
653
  });
621
654
  }
622
- /** 统计卡:主数字 + 灰色口径小字;点一下联动下方列表筛选。 */
655
+ /** 统计卡:主数字 + 灰色口径小字;点一下联动下方列表筛选。
656
+ * `value` 为 0 时显示「—」(用户 2026-10-17 定稿:0 会被读成「没数据 / 出错了」);
657
+ * `hint` 可以是富文本(如把「停滞」两字标红);
658
+ * `interactive: false` = 纯展示卡(统计台:首页没有已结案可筛,不装可点)。 */
623
659
  function StatCard(props) {
624
660
  ensureUiBase();
625
- return (0, react.createElement)("button", {
626
- type: "button",
627
- className: "dtl-card dtl-card--clickable dtl-stat",
628
- "data-active": props.active,
661
+ const interactive = props.interactive !== false && props.onClick !== void 0;
662
+ const shown = props.value === 0 || props.value === "0" ? "—" : String(props.value);
663
+ return (0, react.createElement)(interactive ? "button" : "div", {
664
+ type: interactive ? "button" : void 0,
665
+ className: `dtl-card dtl-stat${interactive ? " dtl-card--clickable" : ""}`,
666
+ "data-active": props.active === true,
629
667
  title: props.title,
630
- onClick: props.onClick
668
+ onClick: interactive ? props.onClick : void 0
631
669
  }, (0, react.createElement)("span", { style: {
632
670
  fontSize: "var(--dtl-font-sm)",
633
671
  color: "var(--dtl-fg-2)"
634
- } }, props.label), (0, react.createElement)("span", { className: "dtl-stat__value" }, props.value), props.hint === void 0 || props.hint === "" ? null : (0, react.createElement)("span", { className: "dtl-stat__hint" }, props.hint));
672
+ } }, props.label), (0, react.createElement)("span", { className: "dtl-stat__value" }, shown), props.hint === void 0 || props.hint === null || props.hint === "" ? null : (0, react.createElement)("span", { className: "dtl-stat__hint" }, props.hint));
635
673
  }
636
674
  /** chip(分类 / 标签 / 工作区名)。 */
637
675
  function Chip(props) {
@@ -658,25 +696,69 @@ body[data-ds-dark-theme]{
658
696
  color: "var(--dtl-fg-2)"
659
697
  } }, props.hint));
660
698
  }
661
- /**
662
- * 柱状图(div 柱,无新依赖):每桶一柱,柱高按最大值归一;
663
- * 数值常显(10px 灰),具体时刻走 title。零值的桶画 2px 矮桩,避免「这一周不存在」的误读。
664
- */
665
- function BarChart(props) {
699
+ /** 千位压缩(1.2k / 12k):y 轴刻度与柱顶数值用,避免 4 位数把刻度挤宽。 */
700
+ function compactCount(value) {
701
+ if (value >= 1e4) return `${Math.round(value / 1e3)}k`;
702
+ if (value >= 1e3) return `${(value / 1e3).toFixed(1)}k`;
703
+ return String(value);
704
+ }
705
+ /** 分组柱状图(div 柱,无新依赖):完成 / 新增各一细柱,y 轴三档刻度 + 网格线 + 柱顶数值 + 均值虚线。
706
+ * 列 / 柱 / 标签全部相对 104px 绘图区绝对定位 ⇒ 与刻度同一基准,不会各算各的高度。 */
707
+ function GroupedBarChart(props) {
666
708
  ensureUiBase();
667
- const max = props.buckets.reduce((peak, bucket) => Math.max(peak, bucket.value), 0);
668
- return (0, react.createElement)("div", {
669
- className: "dtl-chart",
709
+ const plotHeight = 104;
710
+ const top = Math.max(1, props.weeks.reduce((peak, week) => Math.max(peak, week.done, week.created), 0));
711
+ const doneTotal = props.weeks.reduce((sum, week) => sum + week.done, 0);
712
+ const avg = props.weeks.length === 0 ? 0 : doneTotal / props.weeks.length;
713
+ const ticks = [
714
+ 0,
715
+ top / 2,
716
+ top
717
+ ];
718
+ const heightOf = (value) => Math.round(value / top * plotHeight);
719
+ const columnStyle = (index) => ({
720
+ left: `${index / props.weeks.length * 100}%`,
721
+ right: `${(props.weeks.length - index - 1) / props.weeks.length * 100}%`
722
+ });
723
+ return (0, react.createElement)("div", { style: {
724
+ position: "relative",
725
+ paddingLeft: "30px",
726
+ paddingBottom: "18px"
727
+ } }, (0, react.createElement)("div", {
728
+ className: "dtl-gchart",
670
729
  role: "img",
671
730
  "aria-label": props.ariaLabel
672
- }, props.buckets.map((bucket) => (0, react.createElement)("div", {
673
- key: bucket.label,
674
- className: "dtl-chart__col",
675
- title: bucket.title
676
- }, (0, react.createElement)("span", { className: "dtl-chart__val" }, bucket.value === 0 ? "" : String(bucket.value)), (0, react.createElement)("span", {
677
- className: "dtl-chart__bar",
678
- style: { height: `${max === 0 ? 2 : Math.max(2, Math.round(bucket.value / max * 100))}%` }
679
- }), (0, react.createElement)("span", { className: "dtl-chart__label" }, bucket.label))));
731
+ }, ticks.map((tick) => (0, react.createElement)("div", {
732
+ key: `grid-${String(tick)}`,
733
+ className: "dtl-gchart__grid",
734
+ style: { bottom: `${heightOf(tick)}px` }
735
+ })), ticks.map((tick) => (0, react.createElement)("span", {
736
+ key: `tick-${String(tick)}`,
737
+ className: "dtl-gchart__ytick",
738
+ style: { bottom: `${heightOf(tick)}px` }
739
+ }, compactCount(Math.round(tick)))), avg > 0 ? (0, react.createElement)("div", {
740
+ className: "dtl-gchart__avg",
741
+ style: { bottom: `${heightOf(avg)}px` }
742
+ }, (0, react.createElement)("span", { className: "dtl-gchart__avgtag" }, props.avgLabel)) : null, props.weeks.map((week, index) => (0, react.createElement)("div", {
743
+ key: week.label,
744
+ className: "dtl-gchart__col",
745
+ style: columnStyle(index),
746
+ title: week.title
747
+ }, (0, react.createElement)("div", { className: "dtl-gchart__pair" }, (0, react.createElement)("span", {
748
+ className: "dtl-gchart__bar dtl-gchart__bar--done",
749
+ style: { height: `${heightOf(week.done)}px` }
750
+ }), (0, react.createElement)("span", {
751
+ className: "dtl-gchart__bar dtl-gchart__bar--created",
752
+ style: { height: `${heightOf(week.created)}px` }
753
+ })), week.done === 0 ? null : (0, react.createElement)("span", {
754
+ className: "dtl-gchart__val",
755
+ style: { bottom: `${heightOf(week.done) + 3}px` }
756
+ }, String(week.done)), (0, react.createElement)("span", { className: "dtl-gchart__label" }, week.label)))));
757
+ }
758
+ /** 图例(完成 / 新增 两个色块)。 */
759
+ function ChartLegend(props) {
760
+ ensureUiBase();
761
+ return (0, react.createElement)("div", { className: "dtl-gchart-legend" }, (0, react.createElement)("span", null, (0, react.createElement)("i", { style: { background: "var(--dtl-business)" } }), props.doneLabel), (0, react.createElement)("span", null, (0, react.createElement)("i", { style: { background: "var(--dtl-chip-bg)" } }), props.createdLabel));
680
762
  }
681
763
  //#endregion
682
764
  //#region src/client/task-row.ts
@@ -875,18 +957,10 @@ body[data-ds-dark-theme]{
875
957
  fontSize: "var(--dtl-font-xl)",
876
958
  fontWeight: 600
877
959
  };
878
- /** 统计区:左 2/3 卡网格 + 右 1/3 趋势图(窄屏堆叠)。 */
879
- const statsAreaStyle = {
880
- display: "grid",
881
- gridTemplateColumns: "minmax(0,2fr) minmax(0,1fr)",
882
- gap: "var(--dtl-space-3)",
883
- marginBottom: "var(--dtl-space-4)"
884
- };
885
- const cardsGridStyle = {
886
- display: "grid",
887
- gridTemplateColumns: "repeat(auto-fit,minmax(148px,1fr))",
888
- gap: "var(--dtl-space-3)"
889
- };
960
+ /** 统计区:左 8 块(2 行 × 4 列)+ 右趋势图,参考图同款分栏。
961
+ * 列数写死 4 → 2(媒体查询在 ui.ts 的 .dtl-stats / .dtl-stats__cards 里),
962
+ * 绝不给 3 列——3 列会排出 3+1 的非对称(用户 2026-10-17:必须上下对称)。 */
963
+ const statsRowStyle = { marginBottom: "var(--dtl-space-4)" };
890
964
  const chartCardStyle = {
891
965
  display: "flex",
892
966
  flexDirection: "column",
@@ -959,14 +1033,34 @@ body[data-ds-dark-theme]{
959
1033
  setQuery(patch);
960
1034
  }
961
1035
  });
962
- const chartBuckets = (0, react.useMemo)(() => (board?.closed.weekly ?? []).map((bucket) => ({
1036
+ /** 统计台卡:不可点(首页没有已结案可筛),hover 给具体日期区间。 */
1037
+ const periodCard = (label, period) => (0, react.createElement)(StatCard, {
1038
+ label: t(label),
1039
+ value: period?.done ?? 0,
1040
+ hint: t("statCreatedHint", { n: period?.created ?? 0 }),
1041
+ interactive: false,
1042
+ title: period === void 0 ? void 0 : t("periodRange", {
1043
+ from: period.from,
1044
+ to: period.to
1045
+ })
1046
+ });
1047
+ const [chartScope, setChartScope] = (0, react.useState)("all");
1048
+ const chartWeeks = (0, react.useMemo)(() => (board === null ? [] : board.closed.weekly[chartScope]).map((bucket) => ({
963
1049
  label: bucket.week.slice(5),
964
- value: bucket.count,
1050
+ done: bucket.done,
1051
+ created: bucket.created,
965
1052
  title: t("chartBucket", {
966
1053
  week: bucket.week,
967
- n: bucket.count
1054
+ done: bucket.done,
1055
+ created: bucket.created
968
1056
  })
969
- })), [board, t]);
1057
+ })), [
1058
+ board,
1059
+ chartScope,
1060
+ t
1061
+ ]);
1062
+ const chartDoneTotal = chartWeeks.reduce((sum, week) => sum + week.done, 0);
1063
+ const chartEmpty = chartWeeks.every((week) => week.done === 0 && week.created === 0);
970
1064
  return (0, react.createElement)("div", { style: pageStyle }, (0, react.createElement)("div", { style: {
971
1065
  display: "flex",
972
1066
  justifyContent: "center"
@@ -1009,42 +1103,76 @@ body[data-ds-dark-theme]{
1009
1103
  onClick: () => {
1010
1104
  refresh();
1011
1105
  }
1012
- }, 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", { style: statsAreaStyle }, (0, react.createElement)("div", { style: cardsGridStyle }, card("statMyActive", String(stats.myActive), t("statMyActiveHint", { n: stats.myBlocked }), {
1106
+ }, 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", {
1107
+ className: "dtl-stats",
1108
+ style: statsRowStyle
1109
+ }, (0, react.createElement)("div", { className: "dtl-stats__cards" }, card("statMyActive", stats.myActive, t("statMyActiveHint", { n: stats.myBlocked }), {
1013
1110
  scope: "mine",
1014
1111
  status: "all",
1015
1112
  workspace: ""
1016
- }), card("statOpen", String(stats.open), t("statOpenHint", { n: days(stats.oldestOpenDays) }), {
1113
+ }), card("statOpen", stats.open, t("statOpenHint", { n: days(stats.oldestOpenDays) }), {
1017
1114
  scope: "all",
1018
1115
  status: "open",
1019
1116
  workspace: ""
1020
- }), card("statDoing", String(stats.doing), t("statDoingHint", { n: stats.doing - stats.myActive + stats.myBlocked }), {
1117
+ }), card("statDoing", stats.doing, t("statDoingHint", { n: stats.doing - stats.myActive + stats.myBlocked }), {
1021
1118
  scope: "all",
1022
1119
  status: "active",
1023
1120
  workspace: ""
1024
- }), card("statBlocked", String(stats.blocked), t("statBlockedHint", { n: days(stats.longestBlockedDays) }), {
1025
- scope: "all",
1026
- status: "blocked",
1027
- workspace: ""
1028
- }), card("statWeekDone", String(board?.closed.weekDone ?? 0), t("statWeekDoneHint"), {
1029
- scope: "all",
1030
- status: "all",
1031
- workspace: ""
1032
- }), card("statStale", String(stats.stale), t("statStaleHint", { n: stats.staleDays }), {
1033
- scope: "all",
1034
- status: "stale",
1035
- workspace: ""
1036
- })), (0, react.createElement)("div", {
1121
+ }), (0, react.createElement)(StatCard, {
1122
+ label: t("statBlockedStale"),
1123
+ value: stats.blocked,
1124
+ hint: stats.stale === 0 ? void 0 : (0, react.createElement)("span", null, (0, react.createElement)("span", { style: {
1125
+ color: "var(--dtl-danger)",
1126
+ fontWeight: 600
1127
+ } }, t("statStaleWord")), ` ${t("statStaleHint", { n: stats.stale })}`),
1128
+ active: isActive(state.query, {
1129
+ scope: "all",
1130
+ status: "blocked",
1131
+ workspace: ""
1132
+ }),
1133
+ title: t("statStaleHint", { n: stats.staleDays }),
1134
+ onClick: () => {
1135
+ setQuery({
1136
+ scope: "all",
1137
+ status: "blocked",
1138
+ workspace: ""
1139
+ });
1140
+ }
1141
+ }), periodCard("statWeekDone", board?.trend.thisWeek), periodCard("statLastWeekDone", board?.trend.lastWeek), periodCard("statMonthDone", board?.trend.thisMonth), periodCard("statLastMonthDone", board?.trend.lastMonth)), (0, react.createElement)("div", {
1037
1142
  className: "dtl-card",
1038
1143
  style: chartCardStyle
1039
1144
  }, (0, react.createElement)("div", { style: {
1145
+ display: "flex",
1146
+ alignItems: "center",
1147
+ justifyContent: "space-between",
1148
+ gap: "var(--dtl-space-2)"
1149
+ } }, (0, react.createElement)("span", { style: {
1040
1150
  fontSize: "var(--dtl-font-md)",
1041
1151
  fontWeight: 600
1042
- } }, t("chartTitle")), chartBuckets.every((bucket) => bucket.value === 0) ? (0, react.createElement)("div", { style: {
1152
+ } }, t("chartTitle")), (0, react.createElement)(Segmented, {
1153
+ id: "dtl-chart-scope",
1154
+ value: chartScope,
1155
+ label: t("scopeLabel"),
1156
+ size: "sm",
1157
+ variant: "inset",
1158
+ items: [{
1159
+ value: "all",
1160
+ label: t("chartScopeAll")
1161
+ }, {
1162
+ value: "mine",
1163
+ label: t("chartScopeMine")
1164
+ }],
1165
+ onChange: setChartScope
1166
+ })), chartEmpty ? (0, react.createElement)("div", { style: {
1043
1167
  ...hintStyle,
1044
1168
  padding: "var(--dtl-space-4) 0"
1045
- } }, t("chartEmpty")) : (0, react.createElement)(BarChart, {
1046
- buckets: chartBuckets,
1047
- ariaLabel: t("chartTitle")
1169
+ } }, t("chartEmpty")) : (0, react.createElement)(GroupedBarChart, {
1170
+ weeks: chartWeeks,
1171
+ ariaLabel: t("chartTitle"),
1172
+ avgLabel: t("chartAvg", { n: Math.round(chartDoneTotal / Math.max(1, chartWeeks.length) * 10) / 10 })
1173
+ }), (0, react.createElement)(ChartLegend, {
1174
+ doneLabel: t("chartLegendDone"),
1175
+ createdLabel: t("chartLegendCreated")
1048
1176
  }), (0, react.createElement)("div", { style: {
1049
1177
  fontSize: "var(--dtl-font-xs)",
1050
1178
  color: "var(--dtl-fg-3)"
@@ -1209,18 +1337,26 @@ body[data-ds-dark-theme]{
1209
1337
  statOpenHint: "最老一条躺了 {n}",
1210
1338
  statDoing: "进行中",
1211
1339
  statDoingHint: "别人在做 {n}",
1212
- statBlocked: "阻塞中",
1213
- statBlockedHint: "卡住最久 {n}",
1340
+ statBlockedStale: "阻塞 · 停滞",
1341
+ statStaleWord: "停滞",
1342
+ statStaleHint: "{n} 条",
1214
1343
  statWeekDone: "本周完成",
1215
- statWeekDoneHint: "按结案时间",
1216
- statStale: "停滞任务",
1217
- statStaleHint: "超过 {n} 天没动",
1344
+ statLastWeekDone: "上周完成",
1345
+ statMonthDone: "本月完成",
1346
+ statLastMonthDone: "上月完成",
1347
+ statCreatedHint: "新增 {n} 条",
1218
1348
  daysNone: "—",
1219
1349
  daysValue: "{n} 天",
1220
- chartTitle: "近 8 周完成",
1221
- chartCaption: "按结案时间、周一起桶;悬停看该周条数",
1222
- chartBucket: "{week} 那一周:完成 {n} 条",
1223
- chartEmpty: "近 8 周没有已结案记录",
1350
+ chartTitle: "近 8 周",
1351
+ chartCaption: "完成(按结案时间)/ 新增(按创建时间),周一起桶;我的 = owner 是我",
1352
+ chartBucket: "{week} 那一周:完成 {done} 条,新增 {created} 条",
1353
+ chartEmpty: "近 8 周没有记录",
1354
+ chartScopeAll: "所有",
1355
+ chartScopeMine: "我的",
1356
+ chartLegendDone: "完成",
1357
+ chartLegendCreated: "新增",
1358
+ chartAvg: "周均 {n}",
1359
+ periodRange: "{from} ~ {to}",
1224
1360
  scopeLabel: "范围",
1225
1361
  scopeMine: "我的",
1226
1362
  scopeAll: "全部工作区",
@@ -1277,18 +1413,26 @@ body[data-ds-dark-theme]{
1277
1413
  statOpenHint: "oldest waiting {n}",
1278
1414
  statDoing: "In progress",
1279
1415
  statDoingHint: "{n} by others",
1280
- statBlocked: "Blocked",
1281
- statBlockedHint: "stuck for {n}",
1416
+ statBlockedStale: "Blocked · Stale",
1417
+ statStaleWord: "Stale",
1418
+ statStaleHint: "{n}",
1282
1419
  statWeekDone: "Done this week",
1283
- statWeekDoneHint: "by close time",
1284
- statStale: "Stale tasks",
1285
- statStaleHint: "no movement for {n}+ days",
1420
+ statLastWeekDone: "Done last week",
1421
+ statMonthDone: "Done this month",
1422
+ statLastMonthDone: "Done last month",
1423
+ statCreatedHint: "{n} created",
1286
1424
  daysNone: "—",
1287
1425
  daysValue: "{n}d",
1288
1426
  chartTitle: "Last 8 weeks",
1289
- chartCaption: "by close time, weeks start Monday; hover a bar for its count",
1290
- chartBucket: "week of {week}: {n} done",
1291
- chartEmpty: "Nothing closed in the last 8 weeks",
1427
+ chartCaption: "done (by close time) / created (by create time), weeks start Monday; mine = owner is me",
1428
+ chartBucket: "week of {week}: {done} done, {created} created",
1429
+ chartEmpty: "Nothing in the last 8 weeks",
1430
+ chartScopeAll: "All",
1431
+ chartScopeMine: "Mine",
1432
+ chartLegendDone: "Done",
1433
+ chartLegendCreated: "Created",
1434
+ chartAvg: "avg {n}/wk",
1435
+ periodRange: "{from} ~ {to}",
1292
1436
  scopeLabel: "Scope",
1293
1437
  scopeMine: "Mine",
1294
1438
  scopeAll: "All workspaces",