@harborppm/cli 0.11.0 → 0.11.2

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
@@ -3285,6 +3285,19 @@ function weeklyLoad(tasks, personId, now, weeks = 6) {
3285
3285
  };
3286
3286
  }
3287
3287
 
3288
+ //#endregion
3289
+ //#region src/lib/selectors.ts
3290
+ /** Projects (or programs) still running, and the ones set to Done, which lists tuck away until asked for. */
3291
+ function splitDone(items) {
3292
+ const open = [];
3293
+ const done = [];
3294
+ for (const item of items) (item.health === "done" ? done : open).push(item);
3295
+ return {
3296
+ open,
3297
+ done
3298
+ };
3299
+ }
3300
+
3288
3301
  //#endregion
3289
3302
  //#region src/tui/views/lists.tsx
3290
3303
  const count = (n, noun) => `${n} ${noun}${n === 1 ? "" : "s"}`;
@@ -3335,19 +3348,23 @@ function RowList({ title, right, rows, empty, active, width, height, onOpen, key
3335
3348
  ]
3336
3349
  });
3337
3350
  }
3338
- /** Every project, under its program and portfolio, with health and progress. */
3351
+ /** Every project, under its program and portfolio, with health and progress. Done
3352
+ * projects and programs stay out of the way until D shows them. */
3339
3353
  function ProjectsView({ active, width, height, go }) {
3340
3354
  const portfolios = useHarbor((s) => s.portfolios);
3341
3355
  const programs = useHarbor((s) => s.programs);
3342
3356
  const projects = useHarbor((s) => s.projects);
3343
3357
  const tasks = useHarbor((s) => s.tasks);
3344
3358
  const { today } = useDeps();
3345
- const rows = useMemo(() => {
3359
+ const [showDone, setShowDone] = useState(false);
3360
+ const { rows, shown, hidden } = useMemo(() => {
3346
3361
  const data = {
3347
3362
  tasks,
3348
3363
  projects
3349
3364
  };
3350
3365
  const out = [];
3366
+ let shown = 0;
3367
+ let hidden = 0;
3351
3368
  for (const portfolio of portfolios) {
3352
3369
  out.push({
3353
3370
  id: portfolio.id,
@@ -3358,6 +3375,11 @@ function ProjectsView({ active, width, height, go }) {
3358
3375
  health: portfolio.health
3359
3376
  });
3360
3377
  for (const program of programs.filter((g) => g.portfolioId === portfolio.id)) {
3378
+ const { open, done } = splitDone(projects.filter((p) => p.programId === program.id));
3379
+ if (program.health === "done" && open.length === 0) {
3380
+ hidden += 1 + done.length;
3381
+ if (!showDone) continue;
3382
+ } else hidden += done.length;
3361
3383
  out.push({
3362
3384
  id: program.id,
3363
3385
  kind: "program",
@@ -3365,8 +3387,9 @@ function ProjectsView({ active, width, height, go }) {
3365
3387
  title: program.name,
3366
3388
  health: program.health
3367
3389
  });
3368
- for (const project of projects.filter((p) => p.programId === program.id)) {
3369
- const { done, total } = projectSummary(data, project);
3390
+ for (const project of showDone ? [...open, ...done] : open) {
3391
+ shown++;
3392
+ const { done: finished, total } = projectSummary(data, project);
3370
3393
  const late = tasksOf(tasks, project.id).filter((t) => isLate(t, today())).length;
3371
3394
  out.push({
3372
3395
  id: project.id,
@@ -3374,22 +3397,27 @@ function ProjectsView({ active, width, height, go }) {
3374
3397
  depth: 2,
3375
3398
  title: `${project.name}${project.key ? ` ${project.key}` : ""}`,
3376
3399
  health: project.health,
3377
- detail: `${done}/${total} done${late ? ` · ${late} late` : ""} · ends ${shortDay(project.endDate, today())}`
3400
+ detail: `${finished}/${total} done${late ? ` · ${late} late` : ""} · ends ${shortDay(project.endDate, today())}`
3378
3401
  });
3379
3402
  }
3380
3403
  }
3381
3404
  }
3382
- return out;
3405
+ return {
3406
+ rows: out,
3407
+ shown,
3408
+ hidden
3409
+ };
3383
3410
  }, [
3384
3411
  portfolios,
3385
3412
  programs,
3386
3413
  projects,
3387
3414
  tasks,
3388
- today
3415
+ today,
3416
+ showDone
3389
3417
  ]);
3390
3418
  return /* @__PURE__ */ jsx(RowList, {
3391
3419
  title: "Portfolios, programs and projects",
3392
- right: `${count(projects.length, "project")} · n new project · e edit`,
3420
+ right: `${count(shown, "project")}${hidden ? ` · ${hidden} done ${showDone ? "shown" : "hidden"} (D)` : ""} · n new project · e edit`,
3393
3421
  rows,
3394
3422
  empty: "No projects yet. n adds one.",
3395
3423
  active,
@@ -3401,7 +3429,8 @@ function ProjectsView({ active, width, height, go }) {
3401
3429
  const programId = r?.kind === "program" ? r.id : r?.kind === "project" ? projects.find((p) => p.id === r.id)?.programId : void 0;
3402
3430
  go.create("project", programId ? { programId } : {});
3403
3431
  },
3404
- e: (r) => r && r.kind !== "raid" && r.kind !== "person" && go.openPanel(r.kind, r.id)
3432
+ e: (r) => r && r.kind !== "raid" && r.kind !== "person" && go.openPanel(r.kind, r.id),
3433
+ D: () => hidden > 0 && setShowDone((v) => !v)
3405
3434
  }
3406
3435
  });
3407
3436
  }
@@ -3411,7 +3440,10 @@ function ProgramsView({ active, width, height, go }) {
3411
3440
  const portfolios = useHarbor((s) => s.portfolios);
3412
3441
  const milestones = useHarbor((s) => s.milestones);
3413
3442
  const { today } = useDeps();
3414
- const rows = useMemo(() => programs.map((program) => {
3443
+ const [showDone, setShowDone] = useState(false);
3444
+ const split = useMemo(() => splitDone(programs), [programs]);
3445
+ const listed = useMemo(() => showDone ? [...split.open, ...split.done] : split.open, [split, showDone]);
3446
+ const rows = useMemo(() => listed.map((program) => {
3415
3447
  const portfolio = portfolios.find((p) => p.id === program.portfolioId);
3416
3448
  const late = milestones.filter((m) => m.programId === program.id && !m.done && m.dueDate < today()).length;
3417
3449
  return {
@@ -3427,16 +3459,17 @@ function ProgramsView({ active, width, height, go }) {
3427
3459
  ].filter(Boolean).join(" · ")
3428
3460
  };
3429
3461
  }), [
3430
- programs,
3462
+ listed,
3431
3463
  portfolios,
3432
3464
  milestones,
3433
3465
  today
3434
3466
  ]);
3467
+ const done = split.done.length;
3435
3468
  return /* @__PURE__ */ jsx(RowList, {
3436
3469
  title: "Programs",
3437
- right: `${count(programs.length, "program")} · n new · e edit`,
3470
+ right: `${count(listed.length, "program")}${done ? ` · ${done} done ${showDone ? "shown" : "hidden"} (D)` : ""} · n new · e edit`,
3438
3471
  rows,
3439
- empty: "No programs yet. n adds one.",
3472
+ empty: done ? "Every program is done. D shows them." : "No programs yet. n adds one.",
3440
3473
  active,
3441
3474
  width,
3442
3475
  height,
@@ -3446,7 +3479,8 @@ function ProgramsView({ active, width, height, go }) {
3446
3479
  const portfolioId = r ? programs.find((p) => p.id === r.id)?.portfolioId : void 0;
3447
3480
  go.create("program", portfolioId ? { portfolioId } : {});
3448
3481
  },
3449
- e: (r) => r && go.openPanel("program", r.id)
3482
+ e: (r) => r && go.openPanel("program", r.id),
3483
+ D: () => done > 0 && setShowDone((v) => !v)
3450
3484
  }
3451
3485
  });
3452
3486
  }
@@ -4689,19 +4723,6 @@ function PersonPanel({ id, width, height, go, onClose }) {
4689
4723
  });
4690
4724
  }
4691
4725
 
4692
- //#endregion
4693
- //#region src/lib/selectors.ts
4694
- /** Projects (or programs) still running, and the ones set to Done, which lists tuck away until asked for. */
4695
- function splitDone(items) {
4696
- const open = [];
4697
- const done = [];
4698
- for (const item of items) (item.health === "done" ? done : open).push(item);
4699
- return {
4700
- open,
4701
- done
4702
- };
4703
- }
4704
-
4705
4726
  //#endregion
4706
4727
  //#region src/tui/views/program.tsx
4707
4728
  /**
package/lib/cli.mjs CHANGED
@@ -40,8 +40,9 @@ export const HELP = `harbor: Harbor from the terminal
40
40
  harbor theme [auto|light|dark] the app's colours
41
41
  harbor --version which version this is
42
42
 
43
- harbor programs programs and their projects
44
- harbor projects [words] [--program P] projects with health and open work
43
+ harbor programs [--done] programs and their projects
44
+ harbor projects [words] [--program P] [--done]
45
+ projects with health and open work
45
46
  harbor mine what's on you, late first
46
47
  harbor tasks [words] [--project P] [--assignee me|none|NAME]
47
48
  [--late] [--status S,S]
@@ -560,12 +561,12 @@ export async function run(argv, io = {}) {
560
561
  return 0;
561
562
  }
562
563
  case "programs": {
563
- const { positionals, harbor } = parse(spec());
564
+ const { positionals, values, harbor } = parse(spec(["done"]));
564
565
  none(positionals);
565
- return call("harbor_overview", { harbor }, programTree);
566
+ return call("harbor_overview", { harbor }, (r) => programTree(r, { done: values.done }));
566
567
  }
567
568
  case "projects": {
568
- const { positionals, values, harbor } = parse(spec([], ["program"]));
569
+ const { positionals, values, harbor } = parse(spec(["done"], ["program"]));
569
570
  return call(
570
571
  "list_projects",
571
572
  {
@@ -574,7 +575,7 @@ export async function run(argv, io = {}) {
574
575
  program: values.program,
575
576
  limit: 100,
576
577
  },
577
- projectList,
578
+ (r) => projectList(r, { done: values.done }),
578
579
  );
579
580
  }
580
581
  case "program": {
package/lib/format.mjs CHANGED
@@ -72,20 +72,33 @@ export function taskList(r, opts) {
72
72
  const HEALTH = { "on-track": "on track", "at-risk": "at risk", "off-track": "off track" };
73
73
  const health = (h) => HEALTH[h] ?? h ?? null;
74
74
 
75
- /** Each portfolio's programs and their projects, indented, as harbor_overview gives them. */
76
- export function programTree(r) {
75
+ /** "2 done not shown (--done shows them).", or nothing. */
76
+ const hiddenNote = (n, what) =>
77
+ n ? `\n${n} done ${what}${n === 1 ? "" : "s"} not shown (--done shows them).` : "";
78
+
79
+ /**
80
+ * Each portfolio's programs and their projects, indented, as harbor_overview
81
+ * gives them. Programs and projects set to Done stay out unless `done`.
82
+ */
83
+ export function programTree(r, { done = false } = {}) {
77
84
  const portfolios = r.portfolios ?? [];
78
85
  if (!portfolios.some((pf) => pf.programs.length)) {
79
86
  return 'No programs yet. Open one with harbor program "NAME".';
80
87
  }
88
+ const shown = (x) => done || x.health !== "done";
89
+ let hidden = 0;
81
90
  const out = [];
82
91
  for (const pf of portfolios) {
83
- if (portfolios.length > 1) out.push(pf.name);
92
+ const programs = pf.programs.filter(shown);
93
+ hidden += pf.programs.length - programs.length;
94
+ if (portfolios.length > 1 && programs.length) out.push(pf.name);
84
95
  const pad = portfolios.length > 1 ? " " : "";
85
- for (const pg of pf.programs) {
96
+ for (const pg of programs) {
86
97
  const bits = [health(pg.health), pg.ends ? `ends ${pg.ends}` : null].filter(Boolean);
87
98
  out.push(`${pad}${pg.name} (${bits.join(" · ")})`);
88
- for (const pj of pg.projects) {
99
+ const projects = pg.projects.filter(shown);
100
+ hidden += pg.projects.length - projects.length;
101
+ for (const pj of projects) {
89
102
  const work = [
90
103
  health(pj.health),
91
104
  `${pj.openTasks} open`,
@@ -97,15 +110,20 @@ export function programTree(r) {
97
110
  if (!pg.projects.length) out.push(`${pad} no projects yet`);
98
111
  }
99
112
  }
100
- return out.join("\n");
113
+ if (!out.length) return `Every program is done.${hiddenNote(hidden, "item").replace("\n", " ")}`;
114
+ return out.join("\n") + hiddenNote(hidden, "item");
101
115
  }
102
116
 
103
- /** Projects one per line, name first so it can be passed to --project. */
104
- export function projectList(r) {
117
+ /** Projects one per line, name first so it can be passed to --project. Done ones only with `done`. */
118
+ export function projectList(r, { done = false } = {}) {
105
119
  if (!r.projects?.length) return "No projects match.";
120
+ const projects = done ? r.projects : r.projects.filter((p) => p.health !== "done");
121
+ const hidden = r.projects.length - projects.length;
122
+ if (!projects.length)
123
+ return `Every project that matches is done.${hiddenNote(hidden, "project").replace("\n", " ")}`;
106
124
  const more = r.total > r.projects.length ? `\n…and ${r.total - r.projects.length} more.` : "";
107
125
  return (
108
- r.projects
126
+ projects
109
127
  .map((p) => {
110
128
  const late = p.tasks.late ? `, ${p.tasks.late} late` : "";
111
129
  const bits = [
@@ -117,7 +135,9 @@ export function projectList(r) {
117
135
  ].filter(Boolean);
118
136
  return `${p.name} (${bits.join(" · ")})`;
119
137
  })
120
- .join("\n") + more
138
+ .join("\n") +
139
+ hiddenNote(hidden, "project") +
140
+ more
121
141
  );
122
142
  }
123
143
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harborppm/cli",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
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",