@harborppm/cli 0.68.0 → 0.69.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 +11 -5
- package/lib/cli.mjs +36 -3
- package/lib/format.mjs +1 -1
- package/package.json +1 -1
package/dist/app.mjs
CHANGED
|
@@ -6439,20 +6439,23 @@ function PriorityMark({ priority }) {
|
|
|
6439
6439
|
children: priority === "urgent" ? "!!" : priority === "high" ? "!" : "↓"
|
|
6440
6440
|
});
|
|
6441
6441
|
}
|
|
6442
|
+
/** A heading keeps its own width, up to this much of the row; the hints beside it shorten first. */
|
|
6443
|
+
const HEADING_MAX = "70%";
|
|
6442
6444
|
/** A section heading inside a screen. */
|
|
6443
6445
|
function Heading({ children, right }) {
|
|
6444
6446
|
return /* @__PURE__ */ jsxs(Box, {
|
|
6445
6447
|
justifyContent: "space-between",
|
|
6446
6448
|
columnGap: 2,
|
|
6447
6449
|
children: [/* @__PURE__ */ jsx(Box, {
|
|
6448
|
-
flexShrink:
|
|
6450
|
+
flexShrink: 0,
|
|
6451
|
+
maxWidth: "70%",
|
|
6449
6452
|
children: /* @__PURE__ */ jsx(Text, {
|
|
6450
6453
|
bold: true,
|
|
6451
6454
|
wrap: "truncate-end",
|
|
6452
6455
|
children
|
|
6453
6456
|
})
|
|
6454
6457
|
}), right ? /* @__PURE__ */ jsx(Box, {
|
|
6455
|
-
flexShrink:
|
|
6458
|
+
flexShrink: 1,
|
|
6456
6459
|
children: /* @__PURE__ */ jsx(Text, {
|
|
6457
6460
|
color: theme.muted,
|
|
6458
6461
|
wrap: "truncate-end",
|
|
@@ -27975,7 +27978,8 @@ function ProjectView({ projectId, active, width, height, go, onBack }) {
|
|
|
27975
27978
|
justifyContent: "space-between",
|
|
27976
27979
|
columnGap: 2,
|
|
27977
27980
|
children: [/* @__PURE__ */ jsx(Box, {
|
|
27978
|
-
flexShrink:
|
|
27981
|
+
flexShrink: 0,
|
|
27982
|
+
maxWidth: "70%",
|
|
27979
27983
|
children: /* @__PURE__ */ jsxs(Text, {
|
|
27980
27984
|
wrap: "truncate-end",
|
|
27981
27985
|
children: [
|
|
@@ -27996,7 +28000,7 @@ function ProjectView({ projectId, active, width, height, go, onBack }) {
|
|
|
27996
28000
|
]
|
|
27997
28001
|
})
|
|
27998
28002
|
}), /* @__PURE__ */ jsx(Box, {
|
|
27999
|
-
flexShrink:
|
|
28003
|
+
flexShrink: 1,
|
|
28000
28004
|
children: /* @__PURE__ */ jsxs(Text, {
|
|
28001
28005
|
color: theme.muted,
|
|
28002
28006
|
wrap: "truncate-end",
|
|
@@ -28090,7 +28094,9 @@ function ProjectView({ projectId, active, width, height, go, onBack }) {
|
|
|
28090
28094
|
const w = Math.floor((width - 2) / shownColumns.length);
|
|
28091
28095
|
const on = active && ci === col;
|
|
28092
28096
|
const r = Math.min(rowByCol[ci] ?? 0, Math.max(0, c.tasks.length - 1));
|
|
28093
|
-
const
|
|
28097
|
+
const room = body - 3;
|
|
28098
|
+
const fits = Math.floor(room / 3) >= c.tasks.length;
|
|
28099
|
+
const view = windowOf(c.tasks, r, Math.max(1, Math.floor((fits ? room : room - 2) / 3)));
|
|
28094
28100
|
const over = c.limit != null && c.all > c.limit;
|
|
28095
28101
|
return /* @__PURE__ */ jsxs(Box, {
|
|
28096
28102
|
flexDirection: "column",
|
package/lib/cli.mjs
CHANGED
|
@@ -440,11 +440,28 @@ const level = (v, name) => {
|
|
|
440
440
|
const nullable = (v) => (v === "none" ? null : v);
|
|
441
441
|
const REPEAT_WORD = { weekly: "every week", biweekly: "every two weeks", monthly: "every month" };
|
|
442
442
|
|
|
443
|
-
|
|
443
|
+
const WEEKDAYS = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
|
|
444
|
+
|
|
445
|
+
/** "fri", "Friday": how many days until that weekday comes round (today counts). */
|
|
446
|
+
function daysToWeekday(v, today) {
|
|
447
|
+
const w = v.toLowerCase();
|
|
448
|
+
if (w.length < 3) return null;
|
|
449
|
+
const i = WEEKDAYS.findIndex((d) => d.startsWith(w));
|
|
450
|
+
return i < 0 ? null : (i - today.getDay() + 7) % 7;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** --due as people say it: today, tomorrow, friday, +3, or a date. Anything else goes to Harbor to judge. */
|
|
444
454
|
export function dueDate(v, today = new Date()) {
|
|
445
455
|
if (v === undefined || v === null) return v;
|
|
446
456
|
const words = { today: 0, tomorrow: 1 };
|
|
447
|
-
const days =
|
|
457
|
+
const days =
|
|
458
|
+
v in words
|
|
459
|
+
? words[v]
|
|
460
|
+
: /^\+\d{1,3}$/.test(v)
|
|
461
|
+
? Number(v.slice(1))
|
|
462
|
+
: /^[a-z]+$/i.test(v)
|
|
463
|
+
? daysToWeekday(v, today)
|
|
464
|
+
: null;
|
|
448
465
|
if (days === null) return v;
|
|
449
466
|
const d = new Date(today.getFullYear(), today.getMonth(), today.getDate() + days);
|
|
450
467
|
return todayLocal(d);
|
|
@@ -538,7 +555,7 @@ export function hint(message, project) {
|
|
|
538
555
|
(_, list) => `pick one of ${list.replace(/"/g, "").split("|").join(", ")}`,
|
|
539
556
|
)
|
|
540
557
|
// The CLI reads today, tomorrow and +N before Harbor sees the date.
|
|
541
|
-
.replace(/^a date as YYYY-MM-DD$/, "a date like 2026-11-30, today, tomorrow or +N days");
|
|
558
|
+
.replace(/^a date as YYYY-MM-DD$/, "a date like 2026-11-30, today, tomorrow, friday or +N days");
|
|
542
559
|
if (!seen.has(flag)) seen.set(flag, said);
|
|
543
560
|
}
|
|
544
561
|
return [...seen].map(([flag, why]) => `${flag}: ${why}`).join("\n");
|
|
@@ -1226,12 +1243,28 @@ function distance(a, b) {
|
|
|
1226
1243
|
return d[a.length][b.length];
|
|
1227
1244
|
}
|
|
1228
1245
|
|
|
1246
|
+
/** What someone who knows the website, or a feature's name, might type. */
|
|
1247
|
+
const OTHER_NAMES = {
|
|
1248
|
+
today: "harbor mine lists what's on you, late first; harbor week plans your days.",
|
|
1249
|
+
inbox: "harbor catchup says what was handed to you and what moved.",
|
|
1250
|
+
people: "harbor lifeboats, harbor clock and harbor ashore cover the crew; the app's People screen (harbor, then 6) shows everyone's load.",
|
|
1251
|
+
crew: "harbor invite EMAIL brings someone aboard; the app's Crew screen (harbor, then C) lists who's on it.",
|
|
1252
|
+
longpole: "harbor why PROJECT shows what's holding it up.",
|
|
1253
|
+
"long-pole": "harbor why PROJECT shows what's holding it up.",
|
|
1254
|
+
pilot: "harbor show TASK says the date its owner's queue suggests; --due pilot on add or edit takes it.",
|
|
1255
|
+
dock: "harbor try PROJECT is the dry dock.",
|
|
1256
|
+
"dry-dock": "harbor try PROJECT is the dry dock.",
|
|
1257
|
+
};
|
|
1258
|
+
|
|
1229
1259
|
/** A mistyped command, with the one it was probably meant to be. */
|
|
1230
1260
|
export function noCommand(name) {
|
|
1231
1261
|
if (name.startsWith("-")) return `${name} goes after a command, like harbor mine ${name}.`;
|
|
1232
1262
|
// A plural kind reads the register, not the command that writes one.
|
|
1233
1263
|
if (["decisions", "issues"].includes(name))
|
|
1234
1264
|
return `No command "${name}". harbor risks --kind ${name.slice(0, -1)} lists them.`;
|
|
1265
|
+
// Words for things the website has a page for, or that go by another name here.
|
|
1266
|
+
const other = OTHER_NAMES[name.toLowerCase()];
|
|
1267
|
+
if (other) return `No command "${name}". ${other}`;
|
|
1235
1268
|
const near = COMMANDS.filter((c) => distance(name, c) <= (c.length > 4 ? 2 : 1));
|
|
1236
1269
|
const guess = near.length === 1 ? ` Did you mean harbor ${near[0]}?` : "";
|
|
1237
1270
|
return `No command "${name}".${guess} Run harbor help.`;
|
package/lib/format.mjs
CHANGED