@harborppm/cli 0.90.0 → 0.91.0
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/README.md +3 -2
- package/dist/app.mjs +63 -19
- package/lib/cli.mjs +17 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -166,7 +166,8 @@ harbor project "NAME" --program P [--lead me|NAME] [--starts DATE]
|
|
|
166
166
|
they really finished in that project
|
|
167
167
|
harbor add "TITLE" --project P [--assignee me|NAME] [--also "NAME, NAME"] [--due DATE|pilot]
|
|
168
168
|
[--priority low|medium|high|urgent] [--status S] [--comment TEXT]
|
|
169
|
-
[--over-limit] [--literal]
|
|
169
|
+
[--starts DATE] [--over-limit] [--literal]
|
|
170
|
+
no TITLE: one title a line from stdin
|
|
170
171
|
--due pilot takes the earliest day the owner's queue
|
|
171
172
|
suggests (their open work due first, weekly pace, time
|
|
172
173
|
away, what it waits on, how long their similar tasks
|
|
@@ -174,7 +175,7 @@ harbor add "TITLE" --project P [--assignee me|NAME] [--also "NAME, NAME"] [--due
|
|
|
174
175
|
their next working day. A date that won't hold says so
|
|
175
176
|
harbor start TASK | submit TASK | done TASK [--confirm] [--over-limit]
|
|
176
177
|
harbor edit TASK [--title T] [--assignee me|none|NAME] [--also "NAME, NAME"|none]
|
|
177
|
-
[--due DATE|pilot|none]
|
|
178
|
+
[--due DATE|pilot|none] [--starts DATE|none]
|
|
178
179
|
[--priority P] [--status S] [--notes TEXT]
|
|
179
180
|
[--repeat weekly|biweekly|monthly|never]
|
|
180
181
|
[--waits-on TASK,TASK|none] [--confirm] [--over-limit]
|
package/dist/app.mjs
CHANGED
|
@@ -3670,8 +3670,8 @@ init_date_text();
|
|
|
3670
3670
|
init_format();
|
|
3671
3671
|
init_indexes();
|
|
3672
3672
|
init_premortem();
|
|
3673
|
-
const DAY = (iso) => parseIso(iso.slice(0, 10));
|
|
3674
|
-
const span$1 = (a, b) => differenceInCalendarDays(DAY(b), DAY(a));
|
|
3673
|
+
const DAY$1 = (iso) => parseIso(iso.slice(0, 10));
|
|
3674
|
+
const span$1 = (a, b) => differenceInCalendarDays(DAY$1(b), DAY$1(a));
|
|
3675
3675
|
/** Projects marked Done that have tasks to copy, most recently finished first. */
|
|
3676
3676
|
function finishedProjects(data) {
|
|
3677
3677
|
return data.projects.filter((p) => p.health === "done" && tasksOf(data.tasks, p.id).length > 0).map((p) => ({
|
|
@@ -3762,7 +3762,7 @@ function reckoningLine(r) {
|
|
|
3762
3762
|
}
|
|
3763
3763
|
/** The new project's end date, as long after its start as the old one really ran. */
|
|
3764
3764
|
function reckonedEnd(r, startDate) {
|
|
3765
|
-
return todayISO(addDays(DAY(startDate), r.took));
|
|
3765
|
+
return todayISO(addDays(DAY$1(startDate), r.took));
|
|
3766
3766
|
}
|
|
3767
3767
|
/** Puts the reckoning's tasks and risks into the new project, dated from its start. */
|
|
3768
3768
|
function applyReckoning(actions, projectId, r, startDate) {
|
|
@@ -3773,7 +3773,7 @@ function applyReckoning(actions, projectId, r, startDate) {
|
|
|
3773
3773
|
status: "todo",
|
|
3774
3774
|
priority: t.priority,
|
|
3775
3775
|
assigneeId: null,
|
|
3776
|
-
dueDate: todayISO(addDays(DAY(startDate), t.dueIn))
|
|
3776
|
+
dueDate: todayISO(addDays(DAY$1(startDate), t.dueIn))
|
|
3777
3777
|
}, {
|
|
3778
3778
|
allowOverCap: true,
|
|
3779
3779
|
allowBlocked: true
|
|
@@ -13990,6 +13990,33 @@ function linkedProjects(kr, projects) {
|
|
|
13990
13990
|
if (ids.size === 0) return [];
|
|
13991
13991
|
return projects.filter((p) => ids.has(p.id) || ids.has(p.programId));
|
|
13992
13992
|
}
|
|
13993
|
+
/**
|
|
13994
|
+
* The tasks a work key result counts: every task in the projects it names
|
|
13995
|
+
* (directly or through their program), and each task it names on its own,
|
|
13996
|
+
* each counted once however many ways it's linked.
|
|
13997
|
+
*/
|
|
13998
|
+
function workCount(kr, work) {
|
|
13999
|
+
let done = 0;
|
|
14000
|
+
let all = 0;
|
|
14001
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14002
|
+
const count = (t) => {
|
|
14003
|
+
if (seen.has(t.id)) return;
|
|
14004
|
+
seen.add(t.id);
|
|
14005
|
+
all++;
|
|
14006
|
+
if (t.status === "done") done++;
|
|
14007
|
+
};
|
|
14008
|
+
const projects = linkedProjects(kr, work.projects);
|
|
14009
|
+
for (const p of projects) for (const t of tasksOf(work.tasks, p.id)) count(t);
|
|
14010
|
+
const tasks = byId(work.tasks);
|
|
14011
|
+
for (const id of kr.linkIds ?? []) {
|
|
14012
|
+
const t = tasks.get(id);
|
|
14013
|
+
if (t) count(t);
|
|
14014
|
+
}
|
|
14015
|
+
return {
|
|
14016
|
+
done,
|
|
14017
|
+
all
|
|
14018
|
+
};
|
|
14019
|
+
}
|
|
13993
14020
|
/** How far along one key result is, 0 to 1. */
|
|
13994
14021
|
function keyResultProgress(kr, work) {
|
|
13995
14022
|
if (kr.kind === "number") {
|
|
@@ -13998,12 +14025,7 @@ function keyResultProgress(kr, work) {
|
|
|
13998
14025
|
if (span === 0) return (kr.current ?? start) >= (kr.target ?? 0) ? 1 : 0;
|
|
13999
14026
|
return clamp(((kr.current ?? start) - start) / span);
|
|
14000
14027
|
}
|
|
14001
|
-
|
|
14002
|
-
let all = 0;
|
|
14003
|
-
for (const p of linkedProjects(kr, work.projects)) for (const t of tasksOf(work.tasks, p.id)) {
|
|
14004
|
-
all++;
|
|
14005
|
-
if (t.status === "done") done++;
|
|
14006
|
-
}
|
|
14028
|
+
const { done, all } = workCount(kr, work);
|
|
14007
14029
|
return all === 0 ? 0 : done / all;
|
|
14008
14030
|
}
|
|
14009
14031
|
/** How far along a goal is: the average of its key results, 0 to 1. */
|
|
@@ -14012,10 +14034,12 @@ function goalProgress(goal, work) {
|
|
|
14012
14034
|
if (krs.length === 0) return 0;
|
|
14013
14035
|
return krs.reduce((sum, kr) => sum + keyResultProgress(kr, work), 0) / krs.length;
|
|
14014
14036
|
}
|
|
14015
|
-
/**
|
|
14037
|
+
/** The day a goal's clock starts: its start day, or the day it was set. */
|
|
14038
|
+
const goalStart = (goal) => goal.start || goal.createdAt.slice(0, 10);
|
|
14039
|
+
/** How much of the goal's time has gone by, 0 to 1, from its start day to its due day. */
|
|
14016
14040
|
function timeGone(goal, today) {
|
|
14017
14041
|
if (!goal.due) return null;
|
|
14018
|
-
const start = Date.parse(`${goal
|
|
14042
|
+
const start = Date.parse(`${goalStart(goal)}T00:00:00Z`);
|
|
14019
14043
|
const end = Date.parse(`${goal.due}T00:00:00Z`);
|
|
14020
14044
|
const now = Date.parse(`${today}T00:00:00Z`);
|
|
14021
14045
|
if (!Number.isFinite(start) || !Number.isFinite(end)) return null;
|
|
@@ -14033,6 +14057,7 @@ function goalStatus(progress, goal, today) {
|
|
|
14033
14057
|
const gone = timeGone(goal, today);
|
|
14034
14058
|
if (gone == null) return progress > 0 ? "on-track" : "not-started";
|
|
14035
14059
|
if (goal.due && today > goal.due) return "off-track";
|
|
14060
|
+
if (today < goalStart(goal)) return "not-started";
|
|
14036
14061
|
if (progress === 0 && gone < .1) return "not-started";
|
|
14037
14062
|
const behind = gone - progress;
|
|
14038
14063
|
if (behind <= .1) return "on-track";
|
|
@@ -14045,12 +14070,8 @@ function keyResultText(kr, work) {
|
|
|
14045
14070
|
const unit = kr.unit ? ` ${kr.unit}` : "";
|
|
14046
14071
|
return `${fmt(kr.current ?? kr.start ?? 0)} of ${fmt(kr.target ?? 0)}${unit}`;
|
|
14047
14072
|
}
|
|
14048
|
-
|
|
14049
|
-
|
|
14050
|
-
for (const p of linkedProjects(kr, work.projects)) for (const t of tasksOf(work.tasks, p.id)) {
|
|
14051
|
-
all++;
|
|
14052
|
-
if (t.status === "done") done++;
|
|
14053
|
-
}
|
|
14073
|
+
if (!kr.linkIds?.length) return "Nothing linked yet";
|
|
14074
|
+
const { done, all } = workCount(kr, work);
|
|
14054
14075
|
return all === 0 ? "No tasks yet" : `${done} of ${all} tasks done`;
|
|
14055
14076
|
}
|
|
14056
14077
|
const fmt = (n) => Number.isInteger(n) ? n.toLocaleString("en-US") : String(Math.round(n * 100) / 100);
|
|
@@ -20013,6 +20034,7 @@ function tasksCsv(data, projectId) {
|
|
|
20013
20034
|
"Blocked by",
|
|
20014
20035
|
"Description",
|
|
20015
20036
|
"Subtask of",
|
|
20037
|
+
"Starts",
|
|
20016
20038
|
...fields.map((f) => f.name)
|
|
20017
20039
|
], tasks.map((t) => [
|
|
20018
20040
|
taskKey(t, project) ?? "",
|
|
@@ -20025,6 +20047,7 @@ function tasksCsv(data, projectId) {
|
|
|
20025
20047
|
blockerIds(t).map((id) => here.get(id)).filter((b) => Boolean(b)).map(name).join("; "),
|
|
20026
20048
|
t.description,
|
|
20027
20049
|
((p) => p ? name(p) : "")(parentOf(t, data.tasks)),
|
|
20050
|
+
t.startDate ?? "",
|
|
20028
20051
|
...fields.map((f) => f.type === "url" ? safeLink(t.customFieldValues?.[f.id]) ?? "" : fieldValueText(f, t.customFieldValues?.[f.id], nameOf))
|
|
20029
20052
|
]));
|
|
20030
20053
|
}
|
|
@@ -21449,6 +21472,7 @@ const HARBOR_TOOLS = [
|
|
|
21449
21472
|
position: boardPositions(data.tasks).get(t.id) ?? null,
|
|
21450
21473
|
priority: t.priority,
|
|
21451
21474
|
due: dayOf(t.dueDate),
|
|
21475
|
+
...t.startDate ? { starts: dayOf(t.startDate) } : {},
|
|
21452
21476
|
assignee: nameOf$2(people, t.assigneeId),
|
|
21453
21477
|
...alsoOnOf(t, people),
|
|
21454
21478
|
project: project?.name ?? null,
|
|
@@ -22105,6 +22129,7 @@ const HARBOR_TOOLS = [
|
|
|
22105
22129
|
const today = todayISO(ctx.now);
|
|
22106
22130
|
const people = byId(data.people);
|
|
22107
22131
|
const named = byId([...data.programs, ...data.projects]);
|
|
22132
|
+
const tasksById = byId(data.tasks);
|
|
22108
22133
|
return { portfolios: (input.portfolio ? [findOne(data.portfolios, input.portfolio, "portfolio")] : data.portfolios).map((pf) => ({
|
|
22109
22134
|
id: pf.id,
|
|
22110
22135
|
name: pf.name,
|
|
@@ -22115,6 +22140,7 @@ const HARBOR_TOOLS = [
|
|
|
22115
22140
|
id: g.id,
|
|
22116
22141
|
title: g.title,
|
|
22117
22142
|
owner: g.ownerId ? people.get(g.ownerId)?.name ?? null : null,
|
|
22143
|
+
...g.start ? { start: g.start } : {},
|
|
22118
22144
|
due: g.due,
|
|
22119
22145
|
...g.why ? { why: g.why } : {},
|
|
22120
22146
|
progress: Math.round(progress * 100),
|
|
@@ -22131,7 +22157,7 @@ const HARBOR_TOOLS = [
|
|
|
22131
22157
|
target: kr.target,
|
|
22132
22158
|
current: kr.current ?? kr.start ?? 0
|
|
22133
22159
|
} : {
|
|
22134
|
-
counts: (kr.linkIds ?? []).map((id) => named.get(id)?.name ?? id),
|
|
22160
|
+
counts: (kr.linkIds ?? []).map((id) => named.get(id)?.name ?? tasksById.get(id)?.title ?? id),
|
|
22135
22161
|
projects: linkedProjects(kr, data.projects).length
|
|
22136
22162
|
}
|
|
22137
22163
|
}))
|
|
@@ -23935,11 +23961,13 @@ function nextOccurrence(task, id, today, createdAt, siblings = []) {
|
|
|
23935
23961
|
if (!task.repeat) return null;
|
|
23936
23962
|
const dueDate = task.dueDate ? nextSeriesDate(task.dueDate, task.repeat, today) : nextDueDate(today, task.repeat);
|
|
23937
23963
|
if (siblings.some((t) => t.status !== "done" && t.repeat === task.repeat && t.title === task.title && (t.dueDate ?? "") >= dueDate)) return null;
|
|
23964
|
+
const lead = task.startDate && task.dueDate ? differenceInCalendarDays(/* @__PURE__ */ new Date(`${task.dueDate.slice(0, 10)}T12:00:00`), /* @__PURE__ */ new Date(`${task.startDate.slice(0, 10)}T12:00:00`)) : null;
|
|
23938
23965
|
return {
|
|
23939
23966
|
...task,
|
|
23940
23967
|
id,
|
|
23941
23968
|
status: "todo",
|
|
23942
23969
|
dueDate,
|
|
23970
|
+
...task.startDate ? { startDate: lead != null && lead >= 0 ? format(addDays(/* @__PURE__ */ new Date(`${dueDate}T12:00:00`), -lead), "yyyy-MM-dd") : null } : {},
|
|
23943
23971
|
createdAt,
|
|
23944
23972
|
completedAt: null,
|
|
23945
23973
|
blockedById: null,
|
|
@@ -23953,6 +23981,7 @@ function nextOccurrence(task, id, today, createdAt, siblings = []) {
|
|
|
23953
23981
|
var REPEAT_LABEL, iso, step;
|
|
23954
23982
|
var init_recurrence = __esmMin((() => {
|
|
23955
23983
|
init_date_fns();
|
|
23984
|
+
init_date_text();
|
|
23956
23985
|
REPEAT_LABEL = {
|
|
23957
23986
|
weekly: "Every week",
|
|
23958
23987
|
biweekly: "Every two weeks",
|
|
@@ -24018,6 +24047,18 @@ var init_bulk = __esmMin((() => {
|
|
|
24018
24047
|
plural$2 = (n) => `${n} ${n === 1 ? "task" : "tasks"}`;
|
|
24019
24048
|
}));
|
|
24020
24049
|
|
|
24050
|
+
// src/lib/task-start.ts
|
|
24051
|
+
function fitStart(start, due) {
|
|
24052
|
+
const s = start ? start.slice(0, 10) : "";
|
|
24053
|
+
if (!DAY.test(s)) return null;
|
|
24054
|
+
const d = due ? due.slice(0, 10) : "";
|
|
24055
|
+
return DAY.test(d) && s > d ? d : s;
|
|
24056
|
+
}
|
|
24057
|
+
var DAY;
|
|
24058
|
+
var init_task_start = __esmMin((() => {
|
|
24059
|
+
DAY = /^\d{4}-\d{2}-\d{2}$/;
|
|
24060
|
+
}));
|
|
24061
|
+
|
|
24021
24062
|
// src/lib/slices/tasks.ts
|
|
24022
24063
|
function tasksSlice({ set, get, write }) {
|
|
24023
24064
|
return {
|
|
@@ -24054,6 +24095,7 @@ function tasksSlice({ set, get, write }) {
|
|
|
24054
24095
|
completedAt: t.status === "done" ? t.completedAt ?? stamp() : null,
|
|
24055
24096
|
checklist: t.checklist ?? [],
|
|
24056
24097
|
..."parentTaskId" in t ? { parentTaskId } : {},
|
|
24098
|
+
...t.startDate ? { startDate: fitStart(t.startDate, t.dueDate) } : {},
|
|
24057
24099
|
...blockersPatch(blockerIds({
|
|
24058
24100
|
id,
|
|
24059
24101
|
blockedById: t.blockedById,
|
|
@@ -24119,6 +24161,7 @@ function tasksSlice({ set, get, write }) {
|
|
|
24119
24161
|
...patch,
|
|
24120
24162
|
touchedAt: stamp()
|
|
24121
24163
|
};
|
|
24164
|
+
if (next.startDate) next.startDate = fitStart(next.startDate, next.dueDate);
|
|
24122
24165
|
if (next.assigneeId && next.followerIds?.includes(next.assigneeId)) next.followerIds = alsoOnIds(next);
|
|
24123
24166
|
if (patch.status && patch.status !== row.status) {
|
|
24124
24167
|
if (patch.status === "done" && !patch.completedAt) next.completedAt = stamp();
|
|
@@ -24583,6 +24626,7 @@ var init_tasks = __esmMin((() => {
|
|
|
24583
24626
|
init_bulk();
|
|
24584
24627
|
init_task_people();
|
|
24585
24628
|
init_subtasks();
|
|
24629
|
+
init_task_start();
|
|
24586
24630
|
}));
|
|
24587
24631
|
|
|
24588
24632
|
// src/lib/raid-history.ts
|
package/lib/cli.mjs
CHANGED
|
@@ -165,7 +165,7 @@ export const HELP = `harbor: Harbor from the terminal
|
|
|
165
165
|
--from: plan it from a finished
|
|
166
166
|
project as that one really ran
|
|
167
167
|
harbor add "TITLE" [--project P] [--assignee me|none|NAME]
|
|
168
|
-
[--also "NAME, NAME"] [--due DATE|pilot]
|
|
168
|
+
[--also "NAME, NAME"] [--due DATE|pilot] [--starts DATE]
|
|
169
169
|
[--priority low|medium|high|urgent]
|
|
170
170
|
[--status S] [--comment TEXT] [--over-limit] [--dry-run]
|
|
171
171
|
[--literal] [--template NAME] [--parent TASK]
|
|
@@ -181,6 +181,7 @@ export const HELP = `harbor: Harbor from the terminal
|
|
|
181
181
|
or Done
|
|
182
182
|
harbor edit TASK [--title T] [--assignee me|none|NAME]
|
|
183
183
|
[--also "NAME, NAME"|none] [--due DATE|pilot|none]
|
|
184
|
+
[--starts DATE|none]
|
|
184
185
|
[--priority P] [--status S] [--notes TEXT]
|
|
185
186
|
[--repeat weekly|biweekly|monthly|never]
|
|
186
187
|
[--waits-on TASK,TASK|none] [--parent TASK|none]
|
|
@@ -705,7 +706,7 @@ export function hint(message, project) {
|
|
|
705
706
|
)
|
|
706
707
|
.replace(
|
|
707
708
|
/^Nothing to change: pass a field to update\.$/,
|
|
708
|
-
"Nothing to change. Say what with --title, --assignee, --also, --due, --priority, --status, --notes, --repeat, --waits-on or --parent.",
|
|
709
|
+
"Nothing to change. Say what with --title, --assignee, --also, --due, --starts, --priority, --status, --notes, --repeat, --waits-on or --parent.",
|
|
709
710
|
)
|
|
710
711
|
.replace(
|
|
711
712
|
/^This connection was allowed to read only\..*$/s,
|
|
@@ -790,6 +791,10 @@ const DETAIL = {
|
|
|
790
791
|
" own owner and date, shown in the parent's details rather than on the",
|
|
791
792
|
" board. It goes in the parent's project; subtasks go one level deep.",
|
|
792
793
|
"",
|
|
794
|
+
" --starts DATE gives it a start day as well as a due day, so the project's",
|
|
795
|
+
" timeline draws it as a bar from one to the other; a start after the due",
|
|
796
|
+
" day comes to the due day. harbor edit TASK --starts DATE|none moves it.",
|
|
797
|
+
"",
|
|
793
798
|
" --due pilot takes the earliest day the owner's queue suggests: their open",
|
|
794
799
|
" work due first, their weekly pace, time away, what it waits on and how",
|
|
795
800
|
" long their similar tasks took. It needs an owner (--assignee none is",
|
|
@@ -2293,6 +2298,7 @@ export async function run(argv, io = {}) {
|
|
|
2293
2298
|
),
|
|
2294
2299
|
];
|
|
2295
2300
|
if (t.alsoOn?.length) lines.push(`Also on it: ${t.alsoOn.join(", ")}.`);
|
|
2301
|
+
if (t.starts) lines.push(`Starts ${t.starts}${t.due ? `, due ${t.due}` : ""}.`);
|
|
2296
2302
|
if (t.subtaskOf) lines.push(`Subtask of ${t.subtaskOf.key ?? t.subtaskOf.id} ${t.subtaskOf.title}.`);
|
|
2297
2303
|
if (t.repeats) lines.push(`Repeats ${REPEAT_WORD[t.repeats] ?? t.repeats}.`);
|
|
2298
2304
|
for (const [name, v] of Object.entries(t.fields ?? {}))
|
|
@@ -2546,7 +2552,7 @@ export async function run(argv, io = {}) {
|
|
|
2546
2552
|
const { positionals, values, harbor } = parse(
|
|
2547
2553
|
spec(
|
|
2548
2554
|
["over-limit", "dry-run", "literal"],
|
|
2549
|
-
["project", "assignee", "also", "due", "priority", "status", "comment", "template", "parent"],
|
|
2555
|
+
["project", "assignee", "also", "due", "starts", "priority", "status", "comment", "template", "parent"],
|
|
2550
2556
|
),
|
|
2551
2557
|
);
|
|
2552
2558
|
// A subtask goes in its parent's project.
|
|
@@ -2652,6 +2658,7 @@ export async function run(argv, io = {}) {
|
|
|
2652
2658
|
also: values.also === undefined ? undefined : alsoList(values.also),
|
|
2653
2659
|
due: duePilot(values.due) ? undefined : (dueDate(values.due) ?? words.due?.value),
|
|
2654
2660
|
suggestedDue: duePilot(values.due),
|
|
2661
|
+
starts: dueDate(values.starts),
|
|
2655
2662
|
priority: values.priority ?? words.priority?.value,
|
|
2656
2663
|
status: values.status === undefined ? undefined : statusWord(values.status),
|
|
2657
2664
|
parent: values.parent,
|
|
@@ -2775,6 +2782,7 @@ export async function run(argv, io = {}) {
|
|
|
2775
2782
|
"assignee",
|
|
2776
2783
|
"also",
|
|
2777
2784
|
"due",
|
|
2785
|
+
"starts",
|
|
2778
2786
|
"priority",
|
|
2779
2787
|
"status",
|
|
2780
2788
|
"notes",
|
|
@@ -2798,6 +2806,7 @@ export async function run(argv, io = {}) {
|
|
|
2798
2806
|
? undefined
|
|
2799
2807
|
: nullable(dueDate(values.due)),
|
|
2800
2808
|
suggestedDue: duePilot(values.due),
|
|
2809
|
+
starts: values.starts === undefined ? undefined : nullable(dueDate(values.starts)),
|
|
2801
2810
|
priority: values.priority,
|
|
2802
2811
|
status: values.status === undefined ? undefined : statusWord(values.status),
|
|
2803
2812
|
description: values.notes,
|
|
@@ -2818,6 +2827,11 @@ export async function run(argv, io = {}) {
|
|
|
2818
2827
|
(r) =>
|
|
2819
2828
|
[
|
|
2820
2829
|
`Updated: ${taskLine(r.updated, opts)}`,
|
|
2830
|
+
values.starts !== undefined
|
|
2831
|
+
? r.updated.starts
|
|
2832
|
+
? `Starts ${r.updated.starts}${r.updated.due ? `, due ${r.updated.due}` : ""}.`
|
|
2833
|
+
: "No start day now."
|
|
2834
|
+
: null,
|
|
2821
2835
|
values.parent !== undefined
|
|
2822
2836
|
? r.updated.subtaskOf
|
|
2823
2837
|
? `A subtask of ${r.updated.subtaskOf}.`
|