@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 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: 1,
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: 2,
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: 1,
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: 2,
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 view = windowOf(c.tasks, r, Math.max(1, Math.floor((body - 3) / 3)));
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
- /** --due as people say it: today, tomorrow, +3, or a date. Anything else goes to Harbor to judge. */
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 = v in words ? words[v] : /^\+\d{1,3}$/.test(v) ? Number(v.slice(1)) : null;
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
@@ -581,7 +581,7 @@ export function bridgeText(r) {
581
581
  );
582
582
  for (const p of r.projects ?? []) {
583
583
  const facts = [
584
- p.health,
584
+ p.health?.replace(/-/g, " "),
585
585
  p.lead,
586
586
  p.ends ? `ends ${p.ends}` : null,
587
587
  `${p.late} late of ${p.open} open`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harborppm/cli",
3
- "version": "0.68.0",
3
+ "version": "0.69.0",
4
4
  "description": "Harbor from the terminal: a full-screen app with everything, plus commands for scripts and agents.",
5
5
  "keywords": [
6
6
  "harbor",