@harborppm/cli 0.87.0 → 0.89.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/dist/app.mjs +98 -6
- package/lib/format.mjs +6 -0
- package/package.json +1 -1
package/dist/app.mjs
CHANGED
|
@@ -4944,7 +4944,7 @@ function normalizeBoardConfig(raw) {
|
|
|
4944
4944
|
const todo = columns.find((c) => c.status === "todo") ?? columns[0];
|
|
4945
4945
|
todo.visible = true;
|
|
4946
4946
|
}
|
|
4947
|
-
const swimlane = raw.swimlane === "assignee" || raw.swimlane === "priority" ? raw.swimlane : "none";
|
|
4947
|
+
const swimlane = raw.swimlane === "assignee" || raw.swimlane === "priority" ? raw.swimlane : typeof raw.swimlane === "string" && /^field:[^\s]{1,100}$/.test(raw.swimlane) ? raw.swimlane : "none";
|
|
4948
4948
|
const cardFields = {
|
|
4949
4949
|
dueDate: raw.cardFields?.dueDate !== false,
|
|
4950
4950
|
assignee: raw.cardFields?.assignee !== false,
|
|
@@ -5066,6 +5066,22 @@ function isVoyageProject(projectId) {
|
|
|
5066
5066
|
function ownProjects(projects) {
|
|
5067
5067
|
return projects.filter((p) => !isVoyageProject(p.id));
|
|
5068
5068
|
}
|
|
5069
|
+
/**
|
|
5070
|
+
* The harbor as anyone outside it may see it: First voyage is practice, so
|
|
5071
|
+
* share links, their embeds and the Monday letter to stakeholders leave it
|
|
5072
|
+
* out, with its tasks, risks and spend, from what they show and count.
|
|
5073
|
+
*/
|
|
5074
|
+
function withoutPractice(data) {
|
|
5075
|
+
if (!data.projects.some((p) => isVoyageProject(p.id))) return data;
|
|
5076
|
+
const out = {
|
|
5077
|
+
...data,
|
|
5078
|
+
projects: ownProjects(data.projects),
|
|
5079
|
+
tasks: data.tasks.filter((t) => !isVoyageProject(t.projectId)),
|
|
5080
|
+
raidItems: data.raidItems.filter((r) => !r.projectId || !isVoyageProject(r.projectId))
|
|
5081
|
+
};
|
|
5082
|
+
if (data.spendEntries) out.spendEntries = data.spendEntries.filter((e) => !isVoyageProject(e.projectId));
|
|
5083
|
+
return out;
|
|
5084
|
+
}
|
|
5069
5085
|
var VOYAGE_PROJECT_ID, STEPS, voyageTaskId, VOYAGE_NO_FORECAST, SEEDED;
|
|
5070
5086
|
var init_first_voyage = __esmMin((() => {
|
|
5071
5087
|
VOYAGE_PROJECT_ID = "voyage";
|
|
@@ -8205,6 +8221,7 @@ function list$2(names) {
|
|
|
8205
8221
|
* document only; nothing here changes it.
|
|
8206
8222
|
*/
|
|
8207
8223
|
function shippingForecast(data, opts) {
|
|
8224
|
+
data = withoutPractice(data);
|
|
8208
8225
|
const { now } = opts;
|
|
8209
8226
|
const today = todayISO(now);
|
|
8210
8227
|
const programs = data.programs.filter((p) => !opts.portfolioId || p.portfolioId === opts.portfolioId);
|
|
@@ -8291,6 +8308,7 @@ var init_shipping_forecast = __esmMin((() => {
|
|
|
8291
8308
|
init_date_text();
|
|
8292
8309
|
init_format();
|
|
8293
8310
|
init_indexes();
|
|
8311
|
+
init_first_voyage();
|
|
8294
8312
|
FORECAST_AREAS = 10;
|
|
8295
8313
|
WORDS = [
|
|
8296
8314
|
"no",
|
|
@@ -10022,7 +10040,9 @@ const KIND_RANK = {
|
|
|
10022
10040
|
sounding: 3,
|
|
10023
10041
|
assigned: 4,
|
|
10024
10042
|
idle: 5,
|
|
10025
|
-
mention: 6
|
|
10043
|
+
mention: 6,
|
|
10044
|
+
reply: 7,
|
|
10045
|
+
finished: 8
|
|
10026
10046
|
};
|
|
10027
10047
|
const PING_KIND_LABEL = {
|
|
10028
10048
|
harbor: "Your harbor",
|
|
@@ -10032,7 +10052,9 @@ const PING_KIND_LABEL = {
|
|
|
10032
10052
|
sounding: "Soundings",
|
|
10033
10053
|
assigned: "Assigned to you",
|
|
10034
10054
|
idle: "Quiet blockers",
|
|
10035
|
-
mention: "Mentions"
|
|
10055
|
+
mention: "Mentions",
|
|
10056
|
+
reply: "Replies",
|
|
10057
|
+
finished: "Finished"
|
|
10036
10058
|
};
|
|
10037
10059
|
function firstName$3(name) {
|
|
10038
10060
|
return name.split(/\s+/)[0] ?? name;
|
|
@@ -10240,6 +10262,63 @@ function inboxOf(data, personId, now = clockNow()) {
|
|
|
10240
10262
|
href: `/projects/${task.projectId}`
|
|
10241
10263
|
});
|
|
10242
10264
|
}
|
|
10265
|
+
const recentByTask = /* @__PURE__ */ new Map();
|
|
10266
|
+
for (const c of data.comments) {
|
|
10267
|
+
const list = recentByTask.get(c.taskId);
|
|
10268
|
+
if (list) list.push(c);
|
|
10269
|
+
else recentByTask.set(c.taskId, [c]);
|
|
10270
|
+
}
|
|
10271
|
+
for (const [taskId, thread] of recentByTask) {
|
|
10272
|
+
const task = byId(data.tasks).get(taskId);
|
|
10273
|
+
if (!task) continue;
|
|
10274
|
+
const ordered = [...thread].sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
10275
|
+
let joined = task.assignedBy === personId && task.assigneeId !== personId;
|
|
10276
|
+
const replies = [];
|
|
10277
|
+
for (const c of ordered) {
|
|
10278
|
+
if (c.authorId === personId) {
|
|
10279
|
+
joined = true;
|
|
10280
|
+
replies.length = 0;
|
|
10281
|
+
continue;
|
|
10282
|
+
}
|
|
10283
|
+
if (!joined || c.createdAt < since) continue;
|
|
10284
|
+
if ((c.mentionedIds ?? []).includes(personId)) continue;
|
|
10285
|
+
replies.push(c);
|
|
10286
|
+
}
|
|
10287
|
+
const latest = replies.at(-1);
|
|
10288
|
+
if (!latest) continue;
|
|
10289
|
+
const authors = [...new Set(replies.map((c) => {
|
|
10290
|
+
const who = data.people.find((p) => p.id === c.authorId);
|
|
10291
|
+
return who ? firstName$3(who.name) : "Someone";
|
|
10292
|
+
}))];
|
|
10293
|
+
const project = projectOf(data, task);
|
|
10294
|
+
const snippet = excerpt(latest.body);
|
|
10295
|
+
pings.push({
|
|
10296
|
+
id: `reply:${latest.id}`,
|
|
10297
|
+
kind: "reply",
|
|
10298
|
+
title: `${listNames(authors)} replied`,
|
|
10299
|
+
detail: snippet ? `on “${task.title}”${project ? ` · ${project.name}` : ""} — ${snippet}` : `on “${task.title}”${project ? ` · ${project.name}` : ""}`,
|
|
10300
|
+
at: latest.createdAt,
|
|
10301
|
+
taskId: task.id,
|
|
10302
|
+
personId: latest.authorId,
|
|
10303
|
+
href: `/projects/${task.projectId}`
|
|
10304
|
+
});
|
|
10305
|
+
}
|
|
10306
|
+
for (const task of data.tasks) {
|
|
10307
|
+
if (task.assignedBy !== personId || !task.assigneeId || task.assigneeId === personId) continue;
|
|
10308
|
+
if (task.status !== "done" || !task.completedAt || task.completedAt < since) continue;
|
|
10309
|
+
const who = data.people.find((p) => p.id === task.assigneeId);
|
|
10310
|
+
const project = projectOf(data, task);
|
|
10311
|
+
pings.push({
|
|
10312
|
+
id: `finished:${task.id}:${task.completedAt}`,
|
|
10313
|
+
kind: "finished",
|
|
10314
|
+
title: `${who ? firstName$3(who.name) : "Someone"} finished “${task.title}”`,
|
|
10315
|
+
detail: project ? `A task you gave them · ${project.name}` : "A task you gave them",
|
|
10316
|
+
at: task.completedAt,
|
|
10317
|
+
taskId: task.id,
|
|
10318
|
+
personId: task.assigneeId,
|
|
10319
|
+
href: `/projects/${task.projectId}`
|
|
10320
|
+
});
|
|
10321
|
+
}
|
|
10243
10322
|
return pings.sort((a, b) => {
|
|
10244
10323
|
const rank = KIND_RANK[a.kind] - KIND_RANK[b.kind];
|
|
10245
10324
|
if (rank !== 0) return rank;
|
|
@@ -29932,6 +30011,15 @@ function defaultAction(type) {
|
|
|
29932
30011
|
};
|
|
29933
30012
|
}
|
|
29934
30013
|
|
|
30014
|
+
// src/lib/board-lanes.ts
|
|
30015
|
+
/** Fields a board can be split into lanes by. */
|
|
30016
|
+
function isLaneField(def) {
|
|
30017
|
+
return def.type === "select" || def.type === "multi" || def.type === "person" || def.type === "checkbox" || def.type === "date";
|
|
30018
|
+
}
|
|
30019
|
+
function fieldLane(fieldId) {
|
|
30020
|
+
return `field:${fieldId}`;
|
|
30021
|
+
}
|
|
30022
|
+
|
|
29935
30023
|
// src/tui/views/setup.tsx
|
|
29936
30024
|
/**
|
|
29937
30025
|
* A project's board setup, as the website's board settings have it: where it
|
|
@@ -29998,6 +30086,10 @@ function BoardPanel({ id, width, height, go, active, onClose }) {
|
|
|
29998
30086
|
const people = useHarbor((s) => s.people);
|
|
29999
30087
|
if (!project) return null;
|
|
30000
30088
|
const config = boardConfigOf({ boardConfigs }, id);
|
|
30089
|
+
const lanes = [...LANES, ...(project.customFields ?? []).filter(isLaneField).map((f) => ({
|
|
30090
|
+
value: fieldLane(f.id),
|
|
30091
|
+
label: `By ${f.name}`
|
|
30092
|
+
}))];
|
|
30001
30093
|
const set = (patch) => store.getState().setBoardConfig(id, {
|
|
30002
30094
|
...config,
|
|
30003
30095
|
preset: "custom",
|
|
@@ -30080,15 +30172,15 @@ function BoardPanel({ id, width, height, go, active, onClose }) {
|
|
|
30080
30172
|
...config.columns.map(columnField),
|
|
30081
30173
|
{
|
|
30082
30174
|
label: "Lanes",
|
|
30083
|
-
value:
|
|
30175
|
+
value: lanes.find((l) => l.value === config.swimlane)?.label ?? "None",
|
|
30084
30176
|
edit: {
|
|
30085
30177
|
kind: "pick",
|
|
30086
30178
|
title: "Lanes across the website's board",
|
|
30087
|
-
choices:
|
|
30179
|
+
choices: lanes.map((l) => ({
|
|
30088
30180
|
label: l.label,
|
|
30089
30181
|
value: l.value
|
|
30090
30182
|
})),
|
|
30091
|
-
initial:
|
|
30183
|
+
initial: Math.max(0, lanes.findIndex((l) => l.value === config.swimlane)),
|
|
30092
30184
|
onPick: (v) => set({ swimlane: v })
|
|
30093
30185
|
}
|
|
30094
30186
|
},
|
package/lib/format.mjs
CHANGED
|
@@ -769,6 +769,12 @@ export function importText(r) {
|
|
|
769
769
|
if (r.alreadyThere) lines.push(`${n(r.alreadyThere, "task")} already there, left alone.`);
|
|
770
770
|
if (r.leftOut) lines.push(`${n(r.leftOut, "row")} left out (canceled, won't do or duplicate).`);
|
|
771
771
|
if (r.waitsOnLinks) lines.push(`${n(r.waitsOnLinks, "link")} between tasks became "waits on".`);
|
|
772
|
+
if (r.subtasks)
|
|
773
|
+
lines.push(
|
|
774
|
+
r.subtasks === 1
|
|
775
|
+
? "1 row came in as a subtask of its task."
|
|
776
|
+
: `${r.subtasks} rows came in as subtasks of their tasks.`,
|
|
777
|
+
);
|
|
772
778
|
for (const p of r.problems ?? [])
|
|
773
779
|
lines.push(`${p.card ? `Card “${p.card}”` : `Row ${p.row}`}: ${p.message}`);
|
|
774
780
|
return lines.join("\n");
|