@task-mcp/shared 1.0.14 → 1.0.16
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/algorithms/critical-path.test.js +1 -1
- package/dist/algorithms/critical-path.test.js.map +1 -1
- package/dist/algorithms/dependency-integrity.test.js +1 -1
- package/dist/algorithms/dependency-integrity.test.js.map +1 -1
- package/dist/algorithms/tech-analysis.test.js +1 -1
- package/dist/algorithms/tech-analysis.test.js.map +1 -1
- package/dist/algorithms/topological-sort.test.js +1 -1
- package/dist/algorithms/topological-sort.test.js.map +1 -1
- package/dist/schemas/index.d.ts +1 -2
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +0 -2
- package/dist/schemas/index.js.map +1 -1
- package/dist/schemas/response-format.d.ts +8 -13
- package/dist/schemas/response-format.d.ts.map +1 -1
- package/dist/schemas/response-format.js.map +1 -1
- package/dist/schemas/task.d.ts +3 -9
- package/dist/schemas/task.d.ts.map +1 -1
- package/dist/schemas/task.js +3 -3
- package/dist/schemas/task.js.map +1 -1
- package/dist/schemas/view.d.ts +16 -8
- package/dist/schemas/view.d.ts.map +1 -1
- package/dist/schemas/view.js +2 -1
- package/dist/schemas/view.js.map +1 -1
- package/dist/utils/dashboard-renderer.d.ts +20 -13
- package/dist/utils/dashboard-renderer.d.ts.map +1 -1
- package/dist/utils/dashboard-renderer.js +46 -37
- package/dist/utils/dashboard-renderer.js.map +1 -1
- package/dist/utils/dashboard-renderer.test.js +84 -87
- package/dist/utils/dashboard-renderer.test.js.map +1 -1
- package/dist/utils/hierarchy.test.js +1 -1
- package/dist/utils/hierarchy.test.js.map +1 -1
- package/dist/utils/id.d.ts +2 -19
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +0 -43
- package/dist/utils/id.js.map +1 -1
- package/dist/utils/id.test.js +3 -38
- package/dist/utils/id.test.js.map +1 -1
- package/dist/utils/index.d.ts +6 -3
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +8 -4
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/projection.d.ts +1 -10
- package/dist/utils/projection.d.ts.map +1 -1
- package/dist/utils/projection.js +0 -48
- package/dist/utils/projection.js.map +1 -1
- package/dist/utils/projection.test.js +2 -66
- package/dist/utils/projection.test.js.map +1 -1
- package/dist/utils/workspace.d.ts +100 -0
- package/dist/utils/workspace.d.ts.map +1 -0
- package/dist/utils/workspace.js +173 -0
- package/dist/utils/workspace.js.map +1 -0
- package/package.json +1 -1
- package/src/algorithms/critical-path.test.ts +1 -1
- package/src/algorithms/dependency-integrity.test.ts +1 -1
- package/src/algorithms/tech-analysis.test.ts +1 -1
- package/src/algorithms/topological-sort.test.ts +1 -1
- package/src/schemas/index.ts +2 -11
- package/src/schemas/response-format.ts +10 -15
- package/src/schemas/task.ts +3 -3
- package/src/schemas/view.ts +2 -1
- package/src/utils/dashboard-renderer.test.ts +92 -90
- package/src/utils/dashboard-renderer.ts +63 -48
- package/src/utils/hierarchy.test.ts +1 -1
- package/src/utils/id.test.ts +2 -48
- package/src/utils/id.ts +1 -48
- package/src/utils/index.ts +17 -8
- package/src/utils/projection.test.ts +1 -81
- package/src/utils/projection.ts +0 -62
- package/src/utils/workspace.ts +182 -0
- package/src/schemas/project.ts +0 -68
|
@@ -15,16 +15,21 @@ import {
|
|
|
15
15
|
} from "./dashboard-renderer.js";
|
|
16
16
|
import { stripAnsi } from "./terminal-ui.js";
|
|
17
17
|
import type { Task } from "../schemas/task.js";
|
|
18
|
-
import type { Project } from "../schemas/project.js";
|
|
19
18
|
import type { InboxItem } from "../schemas/inbox.js";
|
|
20
19
|
|
|
20
|
+
interface WorkspaceInfo {
|
|
21
|
+
name: string;
|
|
22
|
+
taskCount: number;
|
|
23
|
+
completedCount: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
21
26
|
// =============================================================================
|
|
22
27
|
// Test Fixtures
|
|
23
28
|
// =============================================================================
|
|
24
29
|
|
|
25
30
|
const createTask = (overrides: Partial<Task> = {}): Task => ({
|
|
26
31
|
id: "task-1",
|
|
27
|
-
|
|
32
|
+
workspace: "test-workspace",
|
|
28
33
|
title: "Test Task",
|
|
29
34
|
status: "pending",
|
|
30
35
|
priority: "medium",
|
|
@@ -33,12 +38,10 @@ const createTask = (overrides: Partial<Task> = {}): Task => ({
|
|
|
33
38
|
...overrides,
|
|
34
39
|
});
|
|
35
40
|
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
createdAt: "2025-01-01T00:00:00.000Z",
|
|
41
|
-
updatedAt: "2025-01-01T00:00:00.000Z",
|
|
41
|
+
const createWorkspaceInfo = (overrides: Partial<WorkspaceInfo> = {}): WorkspaceInfo => ({
|
|
42
|
+
name: "test-workspace",
|
|
43
|
+
taskCount: 10,
|
|
44
|
+
completedCount: 5,
|
|
42
45
|
...overrides,
|
|
43
46
|
});
|
|
44
47
|
|
|
@@ -251,9 +254,8 @@ describe("renderStatusWidget", () => {
|
|
|
251
254
|
createTask({ id: "t2", status: "pending" }),
|
|
252
255
|
createTask({ id: "t3", status: "in_progress" }),
|
|
253
256
|
];
|
|
254
|
-
const projects = [createProject()];
|
|
255
257
|
|
|
256
|
-
const widget = renderStatusWidget(tasks
|
|
258
|
+
const widget = renderStatusWidget(tasks);
|
|
257
259
|
const plain = stripAnsi(widget);
|
|
258
260
|
|
|
259
261
|
expect(plain).toContain("Status");
|
|
@@ -270,7 +272,7 @@ describe("renderStatusWidget", () => {
|
|
|
270
272
|
createTask({ id: "t4", status: "pending" }),
|
|
271
273
|
];
|
|
272
274
|
|
|
273
|
-
const widget = renderStatusWidget(tasks
|
|
275
|
+
const widget = renderStatusWidget(tasks);
|
|
274
276
|
const plain = stripAnsi(widget);
|
|
275
277
|
|
|
276
278
|
expect(plain).toContain("50%");
|
|
@@ -283,7 +285,7 @@ describe("renderStatusWidget", () => {
|
|
|
283
285
|
createTask({ id: "t2", priority: "high" }),
|
|
284
286
|
];
|
|
285
287
|
|
|
286
|
-
const widget = renderStatusWidget(tasks
|
|
288
|
+
const widget = renderStatusWidget(tasks);
|
|
287
289
|
const plain = stripAnsi(widget);
|
|
288
290
|
|
|
289
291
|
expect(plain).toContain("Critical");
|
|
@@ -302,7 +304,7 @@ describe("renderStatusWidget", () => {
|
|
|
302
304
|
}),
|
|
303
305
|
];
|
|
304
306
|
|
|
305
|
-
const widget = renderStatusWidget(tasks
|
|
307
|
+
const widget = renderStatusWidget(tasks);
|
|
306
308
|
const plain = stripAnsi(widget);
|
|
307
309
|
|
|
308
310
|
expect(plain).toContain("Ready");
|
|
@@ -316,7 +318,7 @@ describe("renderStatusWidget", () => {
|
|
|
316
318
|
createTask({ id: "t3", status: "pending" }),
|
|
317
319
|
];
|
|
318
320
|
|
|
319
|
-
const widget = renderStatusWidget(tasks
|
|
321
|
+
const widget = renderStatusWidget(tasks);
|
|
320
322
|
const plain = stripAnsi(widget);
|
|
321
323
|
|
|
322
324
|
// 1 completed out of 2 active = 50%
|
|
@@ -510,73 +512,73 @@ describe("renderInboxWidget", () => {
|
|
|
510
512
|
// =============================================================================
|
|
511
513
|
|
|
512
514
|
describe("renderProjectsTable", () => {
|
|
513
|
-
test("returns message for empty
|
|
515
|
+
test("returns message for empty workspaces", () => {
|
|
514
516
|
const result = renderProjectsTable([], () => []);
|
|
515
517
|
const plain = stripAnsi(result);
|
|
516
518
|
|
|
517
|
-
expect(plain).toContain("No
|
|
519
|
+
expect(plain).toContain("No workspaces found");
|
|
518
520
|
});
|
|
519
521
|
|
|
520
|
-
test("renders
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
|
|
522
|
+
test("renders workspace names", () => {
|
|
523
|
+
const workspaces = [
|
|
524
|
+
createWorkspaceInfo({ name: "workspace-alpha" }),
|
|
525
|
+
createWorkspaceInfo({ name: "workspace-beta" }),
|
|
524
526
|
];
|
|
525
527
|
|
|
526
|
-
const result = renderProjectsTable(
|
|
528
|
+
const result = renderProjectsTable(workspaces, () => []);
|
|
527
529
|
const plain = stripAnsi(result);
|
|
528
530
|
|
|
529
|
-
expect(plain).toContain("
|
|
530
|
-
expect(plain).toContain("
|
|
531
|
+
expect(plain).toContain("workspace-alpha");
|
|
532
|
+
expect(plain).toContain("workspace-beta");
|
|
531
533
|
});
|
|
532
534
|
|
|
533
535
|
test("renders progress percentage", () => {
|
|
534
|
-
const
|
|
536
|
+
const workspaces = [createWorkspaceInfo({ name: "half-done", taskCount: 2, completedCount: 1 })];
|
|
535
537
|
const tasks = [
|
|
536
|
-
createTask({ id: "t1",
|
|
537
|
-
createTask({ id: "t2",
|
|
538
|
+
createTask({ id: "t1", workspace: "half-done", status: "completed" }),
|
|
539
|
+
createTask({ id: "t2", workspace: "half-done", status: "pending" }),
|
|
538
540
|
];
|
|
539
541
|
|
|
540
|
-
const result = renderProjectsTable(
|
|
542
|
+
const result = renderProjectsTable(workspaces, () => tasks);
|
|
541
543
|
const plain = stripAnsi(result);
|
|
542
544
|
|
|
543
545
|
expect(plain).toContain("50%");
|
|
544
546
|
});
|
|
545
547
|
|
|
546
548
|
test("renders ready and blocked counts", () => {
|
|
547
|
-
const
|
|
549
|
+
const workspaces = [createWorkspaceInfo({ name: "test-workspace" })];
|
|
548
550
|
const tasks = [
|
|
549
|
-
createTask({ id: "t1",
|
|
551
|
+
createTask({ id: "t1", workspace: "test-workspace", status: "pending" }),
|
|
550
552
|
createTask({
|
|
551
553
|
id: "t2",
|
|
552
|
-
|
|
554
|
+
workspace: "test-workspace",
|
|
553
555
|
status: "pending",
|
|
554
556
|
dependencies: [{ taskId: "t1", type: "blocked_by" }],
|
|
555
557
|
}),
|
|
556
558
|
];
|
|
557
559
|
|
|
558
|
-
const result = renderProjectsTable(
|
|
560
|
+
const result = renderProjectsTable(workspaces, () => tasks);
|
|
559
561
|
const plain = stripAnsi(result);
|
|
560
562
|
|
|
561
563
|
// Table headers should be present
|
|
562
|
-
expect(plain).toContain("
|
|
564
|
+
expect(plain).toContain("Workspace");
|
|
563
565
|
expect(plain).toContain("Progress");
|
|
564
566
|
expect(plain).toContain("Tasks");
|
|
565
567
|
expect(plain).toContain("Ready");
|
|
566
568
|
expect(plain).toContain("Blocked");
|
|
567
569
|
});
|
|
568
570
|
|
|
569
|
-
test("limits to 10
|
|
570
|
-
const
|
|
571
|
-
|
|
571
|
+
test("limits to 10 workspaces", () => {
|
|
572
|
+
const workspaces = Array.from({ length: 15 }, (_, i) =>
|
|
573
|
+
createWorkspaceInfo({ name: `workspace-${i}` })
|
|
572
574
|
);
|
|
573
575
|
|
|
574
|
-
const result = renderProjectsTable(
|
|
576
|
+
const result = renderProjectsTable(workspaces, () => []);
|
|
575
577
|
const plain = stripAnsi(result);
|
|
576
578
|
|
|
577
|
-
expect(plain).toContain("
|
|
578
|
-
expect(plain).toContain("
|
|
579
|
-
expect(plain).not.toContain("
|
|
579
|
+
expect(plain).toContain("workspace-0");
|
|
580
|
+
expect(plain).toContain("workspace-9");
|
|
581
|
+
expect(plain).not.toContain("workspace-10");
|
|
580
582
|
});
|
|
581
583
|
});
|
|
582
584
|
|
|
@@ -674,7 +676,7 @@ describe("renderTasksTable", () => {
|
|
|
674
676
|
describe("renderDashboard", () => {
|
|
675
677
|
const defaultData = {
|
|
676
678
|
tasks: [createTask({ status: "pending" })],
|
|
677
|
-
|
|
679
|
+
workspaces: [createWorkspaceInfo()],
|
|
678
680
|
};
|
|
679
681
|
|
|
680
682
|
test("renders banner by default", () => {
|
|
@@ -701,28 +703,28 @@ describe("renderDashboard", () => {
|
|
|
701
703
|
expect(plain).toContain("v1.2.3");
|
|
702
704
|
});
|
|
703
705
|
|
|
704
|
-
test("shows current
|
|
706
|
+
test("shows current workspace name when provided", () => {
|
|
705
707
|
const data = {
|
|
706
708
|
...defaultData,
|
|
707
|
-
|
|
709
|
+
currentWorkspace: "my-workspace",
|
|
708
710
|
};
|
|
709
711
|
const result = renderDashboard(data, () => []);
|
|
710
712
|
const plain = stripAnsi(result);
|
|
711
713
|
|
|
712
|
-
expect(plain).toContain("
|
|
713
|
-
expect(plain).toContain("
|
|
714
|
+
expect(plain).toContain("Workspace:");
|
|
715
|
+
expect(plain).toContain("my-workspace");
|
|
714
716
|
});
|
|
715
717
|
|
|
716
|
-
test("shows all
|
|
718
|
+
test("shows all workspaces count when no current workspace", () => {
|
|
717
719
|
const data = {
|
|
718
720
|
tasks: [],
|
|
719
|
-
|
|
721
|
+
workspaces: [createWorkspaceInfo({ name: "ws1" }), createWorkspaceInfo({ name: "ws2" })],
|
|
720
722
|
};
|
|
721
723
|
const result = renderDashboard(data, () => []);
|
|
722
724
|
const plain = stripAnsi(result);
|
|
723
725
|
|
|
724
|
-
expect(plain).toContain("All
|
|
725
|
-
expect(plain).toContain("2
|
|
726
|
+
expect(plain).toContain("All Workspaces");
|
|
727
|
+
expect(plain).toContain("2 workspaces");
|
|
726
728
|
});
|
|
727
729
|
|
|
728
730
|
test("renders inbox widget when items present", () => {
|
|
@@ -748,20 +750,20 @@ describe("renderDashboard", () => {
|
|
|
748
750
|
expect(plain).not.toContain("Test inbox item");
|
|
749
751
|
});
|
|
750
752
|
|
|
751
|
-
test("renders
|
|
753
|
+
test("renders workspaces table for multi-workspace view", () => {
|
|
752
754
|
const data = {
|
|
753
755
|
tasks: [],
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
756
|
+
workspaces: [
|
|
757
|
+
createWorkspaceInfo({ name: "workspace-one" }),
|
|
758
|
+
createWorkspaceInfo({ name: "workspace-two" }),
|
|
757
759
|
],
|
|
758
760
|
};
|
|
759
|
-
const result = renderDashboard(data, () => [], {
|
|
761
|
+
const result = renderDashboard(data, () => [], { showWorkspaces: true });
|
|
760
762
|
const plain = stripAnsi(result);
|
|
761
763
|
|
|
762
|
-
expect(plain).toContain("
|
|
763
|
-
expect(plain).toContain("
|
|
764
|
-
expect(plain).toContain("
|
|
764
|
+
expect(plain).toContain("Workspaces");
|
|
765
|
+
expect(plain).toContain("workspace-one");
|
|
766
|
+
expect(plain).toContain("workspace-two");
|
|
765
767
|
});
|
|
766
768
|
|
|
767
769
|
test("strips ANSI codes when stripAnsiCodes is true", () => {
|
|
@@ -777,42 +779,42 @@ describe("renderDashboard", () => {
|
|
|
777
779
|
// =============================================================================
|
|
778
780
|
|
|
779
781
|
describe("renderProjectDashboard", () => {
|
|
780
|
-
test("renders single
|
|
781
|
-
const
|
|
782
|
+
test("renders single workspace dashboard", () => {
|
|
783
|
+
const workspace = "my-workspace";
|
|
782
784
|
const tasks = [
|
|
783
785
|
createTask({ id: "t1", title: "Task 1", status: "pending" }),
|
|
784
786
|
createTask({ id: "t2", title: "Task 2", status: "completed" }),
|
|
785
787
|
];
|
|
786
788
|
|
|
787
|
-
const result = renderProjectDashboard(
|
|
789
|
+
const result = renderProjectDashboard(workspace, tasks);
|
|
788
790
|
const plain = stripAnsi(result);
|
|
789
791
|
|
|
790
|
-
expect(plain).toContain("
|
|
792
|
+
expect(plain).toContain("my-workspace");
|
|
791
793
|
expect(plain).toContain("Task 1");
|
|
792
794
|
});
|
|
793
795
|
|
|
794
796
|
test("includes version when provided", () => {
|
|
795
|
-
const
|
|
796
|
-
const result = renderProjectDashboard(
|
|
797
|
+
const workspace = "test-workspace";
|
|
798
|
+
const result = renderProjectDashboard(workspace, [], { version: "2.0.0" });
|
|
797
799
|
const plain = stripAnsi(result);
|
|
798
800
|
|
|
799
801
|
expect(plain).toContain("v2.0.0");
|
|
800
802
|
});
|
|
801
803
|
|
|
802
804
|
test("strips ANSI when requested", () => {
|
|
803
|
-
const
|
|
804
|
-
const result = renderProjectDashboard(
|
|
805
|
+
const workspace = "test-workspace";
|
|
806
|
+
const result = renderProjectDashboard(workspace, [], { stripAnsiCodes: true });
|
|
805
807
|
|
|
806
808
|
expect(result).not.toMatch(/\x1b\[/);
|
|
807
809
|
});
|
|
808
810
|
|
|
809
811
|
test("shows tasks table", () => {
|
|
810
|
-
const
|
|
812
|
+
const workspace = "test-workspace";
|
|
811
813
|
const tasks = [
|
|
812
814
|
createTask({ id: "t1", title: "Active Task", status: "pending" }),
|
|
813
815
|
];
|
|
814
816
|
|
|
815
|
-
const result = renderProjectDashboard(
|
|
817
|
+
const result = renderProjectDashboard(workspace, tasks);
|
|
816
818
|
const plain = stripAnsi(result);
|
|
817
819
|
|
|
818
820
|
expect(plain).toContain("Tasks");
|
|
@@ -826,30 +828,30 @@ describe("renderProjectDashboard", () => {
|
|
|
826
828
|
|
|
827
829
|
describe("renderGlobalDashboard", () => {
|
|
828
830
|
test("renders global dashboard with all components", () => {
|
|
829
|
-
const
|
|
830
|
-
|
|
831
|
-
|
|
831
|
+
const workspaces = [
|
|
832
|
+
createWorkspaceInfo({ name: "workspace-a" }),
|
|
833
|
+
createWorkspaceInfo({ name: "workspace-b" }),
|
|
832
834
|
];
|
|
833
835
|
const tasks = [
|
|
834
|
-
createTask({ id: "t1",
|
|
835
|
-
createTask({ id: "t2",
|
|
836
|
+
createTask({ id: "t1", workspace: "workspace-a", status: "pending" }),
|
|
837
|
+
createTask({ id: "t2", workspace: "workspace-b", status: "completed" }),
|
|
836
838
|
];
|
|
837
839
|
const inboxItems = [
|
|
838
840
|
createInboxItem({ content: "Inbox note", status: "pending" }),
|
|
839
841
|
];
|
|
840
842
|
|
|
841
843
|
const result = renderGlobalDashboard(
|
|
842
|
-
|
|
844
|
+
workspaces,
|
|
843
845
|
tasks,
|
|
844
846
|
inboxItems,
|
|
845
|
-
(
|
|
847
|
+
(workspace) => tasks.filter((t) => t.workspace === workspace)
|
|
846
848
|
);
|
|
847
849
|
const plain = stripAnsi(result);
|
|
848
850
|
|
|
849
851
|
// Banner is rendered as ASCII art blocks
|
|
850
852
|
expect(plain).toMatch(/████/);
|
|
851
|
-
expect(plain).toContain("
|
|
852
|
-
expect(plain).toContain("
|
|
853
|
+
expect(plain).toContain("workspace-a");
|
|
854
|
+
expect(plain).toContain("workspace-b");
|
|
853
855
|
expect(plain).toContain("Inbox note");
|
|
854
856
|
});
|
|
855
857
|
|
|
@@ -868,39 +870,39 @@ describe("renderGlobalDashboard", () => {
|
|
|
868
870
|
expect(result).not.toMatch(/\x1b\[/);
|
|
869
871
|
});
|
|
870
872
|
|
|
871
|
-
test("shows tasks table for single
|
|
872
|
-
const
|
|
873
|
+
test("shows tasks table for single workspace", () => {
|
|
874
|
+
const workspaces = [createWorkspaceInfo({ name: "solo-workspace" })];
|
|
873
875
|
const tasks = [
|
|
874
|
-
createTask({ id: "t1",
|
|
876
|
+
createTask({ id: "t1", workspace: "solo-workspace", title: "Solo Task", status: "pending" }),
|
|
875
877
|
];
|
|
876
878
|
|
|
877
|
-
const result = renderGlobalDashboard(
|
|
879
|
+
const result = renderGlobalDashboard(workspaces, tasks, [], () => tasks);
|
|
878
880
|
const plain = stripAnsi(result);
|
|
879
881
|
|
|
880
882
|
expect(plain).toContain("Tasks");
|
|
881
883
|
expect(plain).toContain("Solo Task");
|
|
882
884
|
});
|
|
883
885
|
|
|
884
|
-
test("uses
|
|
885
|
-
const
|
|
886
|
-
|
|
887
|
-
|
|
886
|
+
test("uses getWorkspaceTasks callback correctly", () => {
|
|
887
|
+
const workspaces = [
|
|
888
|
+
createWorkspaceInfo({ name: "workspace-one" }),
|
|
889
|
+
createWorkspaceInfo({ name: "workspace-two" }),
|
|
888
890
|
];
|
|
889
891
|
const allTasks = [
|
|
890
|
-
createTask({ id: "t1",
|
|
891
|
-
createTask({ id: "t2",
|
|
892
|
-
createTask({ id: "t3",
|
|
892
|
+
createTask({ id: "t1", workspace: "workspace-one", status: "completed" }),
|
|
893
|
+
createTask({ id: "t2", workspace: "workspace-one", status: "pending" }),
|
|
894
|
+
createTask({ id: "t3", workspace: "workspace-two", status: "pending" }),
|
|
893
895
|
];
|
|
894
896
|
|
|
895
897
|
let callCount = 0;
|
|
896
|
-
const
|
|
898
|
+
const getWorkspaceTasks = (workspace: string) => {
|
|
897
899
|
callCount++;
|
|
898
|
-
return allTasks.filter((t) => t.
|
|
900
|
+
return allTasks.filter((t) => t.workspace === workspace);
|
|
899
901
|
};
|
|
900
902
|
|
|
901
|
-
renderGlobalDashboard(
|
|
903
|
+
renderGlobalDashboard(workspaces, allTasks, [], getWorkspaceTasks);
|
|
902
904
|
|
|
903
|
-
// Should be called for each
|
|
905
|
+
// Should be called for each workspace in the table
|
|
904
906
|
expect(callCount).toBeGreaterThan(0);
|
|
905
907
|
});
|
|
906
908
|
});
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Provides consistent dashboard layout across all interfaces
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { Task,
|
|
6
|
+
import type { Task, InboxItem } from "../schemas/index.js";
|
|
7
7
|
import {
|
|
8
8
|
c,
|
|
9
9
|
box,
|
|
@@ -48,11 +48,17 @@ export interface DependencyMetrics {
|
|
|
48
48
|
} | undefined;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
export interface WorkspaceInfo {
|
|
52
|
+
name: string;
|
|
53
|
+
taskCount: number;
|
|
54
|
+
completedCount: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
51
57
|
export interface DashboardData {
|
|
52
58
|
tasks: Task[];
|
|
53
|
-
|
|
59
|
+
workspaces: WorkspaceInfo[];
|
|
54
60
|
inboxItems?: InboxItem[] | undefined;
|
|
55
|
-
|
|
61
|
+
currentWorkspace?: string | undefined;
|
|
56
62
|
version?: string | undefined;
|
|
57
63
|
activeTag?: string | undefined;
|
|
58
64
|
}
|
|
@@ -180,10 +186,7 @@ function getTimeAgo(date: Date): string {
|
|
|
180
186
|
/**
|
|
181
187
|
* Render Status widget (progress, counts, priorities, dependencies)
|
|
182
188
|
*/
|
|
183
|
-
export function renderStatusWidget(
|
|
184
|
-
tasks: Task[],
|
|
185
|
-
_projects: Project[]
|
|
186
|
-
): string {
|
|
189
|
+
export function renderStatusWidget(tasks: Task[]): string {
|
|
187
190
|
const stats = calculateStats(tasks);
|
|
188
191
|
const depMetrics = calculateDependencyMetrics(tasks);
|
|
189
192
|
const today = getTodayTasks(tasks);
|
|
@@ -315,14 +318,14 @@ export function renderInboxWidget(inboxItems: InboxItem[]): string | null {
|
|
|
315
318
|
}
|
|
316
319
|
|
|
317
320
|
/**
|
|
318
|
-
* Render
|
|
321
|
+
* Render Workspaces table
|
|
319
322
|
*/
|
|
320
|
-
export function
|
|
321
|
-
|
|
322
|
-
|
|
323
|
+
export function renderWorkspacesTable(
|
|
324
|
+
workspaces: WorkspaceInfo[],
|
|
325
|
+
getWorkspaceTasks: (workspace: string) => Task[]
|
|
323
326
|
): string {
|
|
324
|
-
if (
|
|
325
|
-
return c.gray("No
|
|
327
|
+
if (workspaces.length === 0) {
|
|
328
|
+
return c.gray("No workspaces found.");
|
|
326
329
|
}
|
|
327
330
|
|
|
328
331
|
const rows: {
|
|
@@ -333,8 +336,8 @@ export function renderProjectsTable(
|
|
|
333
336
|
total: number;
|
|
334
337
|
}[] = [];
|
|
335
338
|
|
|
336
|
-
for (const
|
|
337
|
-
const tasks =
|
|
339
|
+
for (const ws of workspaces.slice(0, 10)) {
|
|
340
|
+
const tasks = getWorkspaceTasks(ws.name);
|
|
338
341
|
const stats = calculateStats(tasks);
|
|
339
342
|
const depMetrics = calculateDependencyMetrics(tasks);
|
|
340
343
|
const activeTasks = stats.total - stats.cancelled;
|
|
@@ -351,7 +354,7 @@ export function renderProjectsTable(
|
|
|
351
354
|
c.green("█".repeat(filled)) + c.gray("░".repeat(empty));
|
|
352
355
|
|
|
353
356
|
rows.push({
|
|
354
|
-
name: truncateStr(
|
|
357
|
+
name: truncateStr(ws.name, 20),
|
|
355
358
|
progress: `${miniBar} ${pad(String(percent) + "%", 4, "right")}`,
|
|
356
359
|
ready: depMetrics.readyToWork,
|
|
357
360
|
blocked: depMetrics.blockedByDependencies,
|
|
@@ -360,7 +363,7 @@ export function renderProjectsTable(
|
|
|
360
363
|
}
|
|
361
364
|
|
|
362
365
|
const columns: TableColumn[] = [
|
|
363
|
-
{ key: "name", header: "
|
|
366
|
+
{ key: "name", header: "Workspace", width: 22 },
|
|
364
367
|
{ key: "progress", header: "Progress", width: 16 },
|
|
365
368
|
{ key: "total", header: "Tasks", width: 6, align: "right" },
|
|
366
369
|
{
|
|
@@ -382,8 +385,11 @@ export function renderProjectsTable(
|
|
|
382
385
|
return table(rows as unknown as Record<string, unknown>[], columns);
|
|
383
386
|
}
|
|
384
387
|
|
|
388
|
+
// Legacy export for backwards compatibility
|
|
389
|
+
export const renderProjectsTable = renderWorkspacesTable;
|
|
390
|
+
|
|
385
391
|
/**
|
|
386
|
-
* Render Tasks table for single
|
|
392
|
+
* Render Tasks table for single workspace view
|
|
387
393
|
*/
|
|
388
394
|
export function renderTasksTable(tasks: Task[], limit: number = 10): string {
|
|
389
395
|
const activeTasks = tasks.filter(
|
|
@@ -459,7 +465,7 @@ function getOverdueTasks(tasks: Task[]): Task[] {
|
|
|
459
465
|
export interface RenderDashboardOptions {
|
|
460
466
|
showBanner?: boolean | undefined;
|
|
461
467
|
showInbox?: boolean | undefined;
|
|
462
|
-
|
|
468
|
+
showWorkspaces?: boolean | undefined;
|
|
463
469
|
showTasks?: boolean | undefined;
|
|
464
470
|
stripAnsiCodes?: boolean | undefined;
|
|
465
471
|
}
|
|
@@ -469,18 +475,18 @@ export interface RenderDashboardOptions {
|
|
|
469
475
|
*/
|
|
470
476
|
export function renderDashboard(
|
|
471
477
|
data: DashboardData,
|
|
472
|
-
|
|
478
|
+
getWorkspaceTasks: (workspace: string) => Task[],
|
|
473
479
|
options: RenderDashboardOptions = {}
|
|
474
480
|
): string {
|
|
475
481
|
const {
|
|
476
482
|
showBanner = true,
|
|
477
483
|
showInbox = true,
|
|
478
|
-
|
|
484
|
+
showWorkspaces = true,
|
|
479
485
|
showTasks = true,
|
|
480
486
|
stripAnsiCodes = false,
|
|
481
487
|
} = options;
|
|
482
488
|
|
|
483
|
-
const { tasks,
|
|
489
|
+
const { tasks, workspaces, inboxItems = [], currentWorkspace, version, activeTag } = data;
|
|
484
490
|
const lines: string[] = [];
|
|
485
491
|
|
|
486
492
|
// Banner
|
|
@@ -490,10 +496,10 @@ export function renderDashboard(
|
|
|
490
496
|
lines.push("");
|
|
491
497
|
}
|
|
492
498
|
|
|
493
|
-
//
|
|
494
|
-
const
|
|
495
|
-
? `${c.bold("
|
|
496
|
-
: `${c.bold("All
|
|
499
|
+
// Workspace info header
|
|
500
|
+
const workspaceInfo = currentWorkspace
|
|
501
|
+
? `${c.bold("Workspace:")} ${currentWorkspace}`
|
|
502
|
+
: `${c.bold("All Workspaces")} (${workspaces.length} workspaces)`;
|
|
497
503
|
|
|
498
504
|
// Build header line with version and tag
|
|
499
505
|
const headerParts: string[] = [];
|
|
@@ -501,14 +507,14 @@ export function renderDashboard(
|
|
|
501
507
|
if (activeTag) headerParts.push(`#${activeTag}`);
|
|
502
508
|
|
|
503
509
|
if (headerParts.length > 0) {
|
|
504
|
-
lines.push(c.dim(`${headerParts.join(" ")} ${
|
|
510
|
+
lines.push(c.dim(`${headerParts.join(" ")} ${workspaceInfo}`));
|
|
505
511
|
} else {
|
|
506
|
-
lines.push(
|
|
512
|
+
lines.push(workspaceInfo);
|
|
507
513
|
}
|
|
508
514
|
lines.push("");
|
|
509
515
|
|
|
510
516
|
// Status + Actions widgets side by side
|
|
511
|
-
const statusWidget = renderStatusWidget(tasks
|
|
517
|
+
const statusWidget = renderStatusWidget(tasks);
|
|
512
518
|
const actionsWidget = renderActionsWidget(tasks);
|
|
513
519
|
lines.push(sideBySide([statusWidget, actionsWidget], 2));
|
|
514
520
|
lines.push("");
|
|
@@ -522,16 +528,16 @@ export function renderDashboard(
|
|
|
522
528
|
}
|
|
523
529
|
}
|
|
524
530
|
|
|
525
|
-
//
|
|
526
|
-
if (
|
|
527
|
-
lines.push(c.bold("
|
|
531
|
+
// Workspaces table (only for all-workspaces view)
|
|
532
|
+
if (showWorkspaces && !currentWorkspace && workspaces.length > 1) {
|
|
533
|
+
lines.push(c.bold("Workspaces"));
|
|
528
534
|
lines.push("");
|
|
529
|
-
lines.push(
|
|
535
|
+
lines.push(renderWorkspacesTable(workspaces, getWorkspaceTasks));
|
|
530
536
|
lines.push("");
|
|
531
537
|
}
|
|
532
538
|
|
|
533
|
-
// Tasks table (for single
|
|
534
|
-
if (showTasks && (
|
|
539
|
+
// Tasks table (for single workspace view)
|
|
540
|
+
if (showTasks && (currentWorkspace || workspaces.length === 1)) {
|
|
535
541
|
const activeTasks = tasks.filter(
|
|
536
542
|
(t) => t.status !== "completed" && t.status !== "cancelled"
|
|
537
543
|
);
|
|
@@ -549,17 +555,23 @@ export function renderDashboard(
|
|
|
549
555
|
}
|
|
550
556
|
|
|
551
557
|
/**
|
|
552
|
-
* Render single
|
|
558
|
+
* Render single workspace dashboard
|
|
553
559
|
*/
|
|
554
|
-
export function
|
|
555
|
-
|
|
560
|
+
export function renderWorkspaceDashboard(
|
|
561
|
+
workspace: string,
|
|
556
562
|
tasks: Task[],
|
|
557
563
|
options: { stripAnsiCodes?: boolean; version?: string; activeTag?: string } = {}
|
|
558
564
|
): string {
|
|
565
|
+
const wsInfo: WorkspaceInfo = {
|
|
566
|
+
name: workspace,
|
|
567
|
+
taskCount: tasks.length,
|
|
568
|
+
completedCount: tasks.filter(t => t.status === "completed").length,
|
|
569
|
+
};
|
|
570
|
+
|
|
559
571
|
const data: DashboardData = {
|
|
560
572
|
tasks,
|
|
561
|
-
|
|
562
|
-
|
|
573
|
+
workspaces: [wsInfo],
|
|
574
|
+
currentWorkspace: workspace,
|
|
563
575
|
version: options.version,
|
|
564
576
|
activeTag: options.activeTag,
|
|
565
577
|
};
|
|
@@ -570,36 +582,39 @@ export function renderProjectDashboard(
|
|
|
570
582
|
{
|
|
571
583
|
showBanner: true,
|
|
572
584
|
showInbox: false,
|
|
573
|
-
|
|
585
|
+
showWorkspaces: false,
|
|
574
586
|
showTasks: true,
|
|
575
587
|
stripAnsiCodes: options.stripAnsiCodes,
|
|
576
588
|
}
|
|
577
589
|
);
|
|
578
590
|
}
|
|
579
591
|
|
|
592
|
+
// Legacy export for backwards compatibility
|
|
593
|
+
export const renderProjectDashboard = renderWorkspaceDashboard;
|
|
594
|
+
|
|
580
595
|
/**
|
|
581
|
-
* Render global dashboard (all
|
|
596
|
+
* Render global dashboard (all workspaces)
|
|
582
597
|
*/
|
|
583
598
|
export function renderGlobalDashboard(
|
|
584
|
-
|
|
599
|
+
workspaces: WorkspaceInfo[],
|
|
585
600
|
allTasks: Task[],
|
|
586
601
|
inboxItems: InboxItem[],
|
|
587
|
-
|
|
602
|
+
getWorkspaceTasks: (workspace: string) => Task[],
|
|
588
603
|
options: { stripAnsiCodes?: boolean; version?: string; activeTag?: string } = {}
|
|
589
604
|
): string {
|
|
590
605
|
const data: DashboardData = {
|
|
591
606
|
tasks: allTasks,
|
|
592
|
-
|
|
607
|
+
workspaces,
|
|
593
608
|
inboxItems,
|
|
594
609
|
version: options.version,
|
|
595
610
|
activeTag: options.activeTag,
|
|
596
611
|
};
|
|
597
612
|
|
|
598
|
-
return renderDashboard(data,
|
|
613
|
+
return renderDashboard(data, getWorkspaceTasks, {
|
|
599
614
|
showBanner: true,
|
|
600
615
|
showInbox: true,
|
|
601
|
-
|
|
602
|
-
showTasks:
|
|
616
|
+
showWorkspaces: true,
|
|
617
|
+
showTasks: workspaces.length === 1,
|
|
603
618
|
stripAnsiCodes: options.stripAnsiCodes,
|
|
604
619
|
});
|
|
605
620
|
}
|