@critical-path/svelte 0.11.0 → 0.13.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/CHANGELOG.md +27 -0
- package/README.md +96 -2
- package/dist/critical-path-state.svelte.d.ts +13 -0
- package/dist/critical-path-state.svelte.d.ts.map +1 -0
- package/dist/deliverable-summary-state.svelte.d.ts +14 -0
- package/dist/deliverable-summary-state.svelte.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +813 -435
- package/dist/kanban-state.svelte.d.ts +24 -0
- package/dist/kanban-state.svelte.d.ts.map +1 -0
- package/dist/task-transitions-state.svelte.d.ts +12 -0
- package/dist/task-transitions-state.svelte.d.ts.map +1 -0
- package/dist/timeline-ladder-state.svelte.d.ts +23 -0
- package/dist/timeline-ladder-state.svelte.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/critical-path-state.svelte.ts +33 -0
- package/src/deliverable-summary-state.svelte.ts +41 -0
- package/src/index.test.ts +208 -2
- package/src/index.ts +6 -0
- package/src/kanban-state.svelte.ts +112 -0
- package/src/task-transitions-state.svelte.ts +36 -0
- package/src/timeline-ladder-state.svelte.ts +58 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { CriticalPathClient } from '@critical-path/client';
|
|
2
|
+
import type { Task, TaskStatus, StatusDefinition } from '@critical-path/core';
|
|
3
|
+
export interface KanbanStateOptions {
|
|
4
|
+
groupBy?: 'workflow' | 'semantic';
|
|
5
|
+
customDefinitions?: StatusDefinition[];
|
|
6
|
+
}
|
|
7
|
+
export declare class KanbanState {
|
|
8
|
+
projectId?: string | undefined;
|
|
9
|
+
options: KanbanStateOptions;
|
|
10
|
+
private taskState;
|
|
11
|
+
get data(): Task[];
|
|
12
|
+
get tasks(): Task[];
|
|
13
|
+
get loading(): boolean;
|
|
14
|
+
get error(): Error | null;
|
|
15
|
+
columns: Record<string, Task[]>;
|
|
16
|
+
constructor(client: CriticalPathClient, projectId?: string | undefined, options?: KanbanStateOptions);
|
|
17
|
+
fetch(): Promise<void>;
|
|
18
|
+
createTask(input: Omit<Task, 'id' | 'createdAt' | 'updatedAt'>): Promise<Task>;
|
|
19
|
+
moveTask(taskId: string, targetStatus: TaskStatus): Promise<Task>;
|
|
20
|
+
updateTaskStatus(taskId: string, status: TaskStatus): Promise<Task>;
|
|
21
|
+
deleteTask(taskId: string): Promise<boolean | undefined>;
|
|
22
|
+
}
|
|
23
|
+
export declare function createKanbanState(client: CriticalPathClient, projectId?: string, options?: KanbanStateOptions): KanbanState;
|
|
24
|
+
//# sourceMappingURL=kanban-state.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kanban-state.svelte.d.ts","sourceRoot":"","sources":["../src/kanban-state.svelte.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,gBAAgB,EAAkB,MAAM,qBAAqB,CAAC;AAI9F,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;IAClC,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACxC;AAED,qBAAa,WAAW;IAmEb,SAAS,CAAC,EAAE,MAAM;IAClB,OAAO,EAAE,kBAAkB;IAnEpC,OAAO,CAAC,SAAS,CAAY;IAE7B,IAAI,IAAI,IAAI,IAAI,EAAE,CAEjB;IAED,IAAI,KAAK,IAAI,IAAI,EAAE,CAElB;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,KAAK,IAAI,KAAK,GAAG,IAAI,CAExB;IAED,OAAO,yBA4CJ;gBAGD,MAAM,EAAE,kBAAkB,EACnB,SAAS,CAAC,EAAE,MAAM,YAAA,EAClB,OAAO,GAAE,kBAAuB;IAKnC,KAAK;IAIL,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC;IAI9D,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU;IAIjD,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU;IAInD,UAAU,CAAC,MAAM,EAAE,MAAM;CAGhC;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,kBAAkB,EAC1B,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,kBAAkB,GAC3B,WAAW,CAEb"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CriticalPathClient } from '@critical-path/client';
|
|
2
|
+
export declare class TaskTransitionsState {
|
|
3
|
+
private client;
|
|
4
|
+
taskId?: string | undefined;
|
|
5
|
+
allowedTransitions: string[];
|
|
6
|
+
loading: boolean;
|
|
7
|
+
error: Error | null;
|
|
8
|
+
constructor(client: CriticalPathClient, taskId?: string | undefined);
|
|
9
|
+
fetch(taskId?: string): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export declare function createTaskTransitionsState(client: CriticalPathClient, taskId?: string): TaskTransitionsState;
|
|
12
|
+
//# sourceMappingURL=task-transitions-state.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-transitions-state.svelte.d.ts","sourceRoot":"","sources":["../src/task-transitions-state.svelte.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,qBAAa,oBAAoB;IAKnB,OAAO,CAAC,MAAM;IAA6B,MAAM,CAAC,EAAE,MAAM;IAJtE,kBAAkB,WAAwB;IAC1C,OAAO,UAA0B;IACjC,KAAK,eAA8B;gBAEf,MAAM,EAAE,kBAAkB,EAAS,MAAM,CAAC,EAAE,MAAM,YAAA;IAEhE,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM;CAkB5B;AAED,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,kBAAkB,EAC1B,MAAM,CAAC,EAAE,MAAM,GACd,oBAAoB,CAEtB"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CriticalPathClient } from '@critical-path/client';
|
|
2
|
+
import type { TimelineLadder, TimelineLadderOptions, AbstractionLevel, MacroTimelineSummary, ConcreteTaskEvidence } from '@critical-path/core';
|
|
3
|
+
export declare class TimelineLadderState {
|
|
4
|
+
private client;
|
|
5
|
+
projectId: string;
|
|
6
|
+
data: TimelineLadder | null;
|
|
7
|
+
level: AbstractionLevel;
|
|
8
|
+
loading: boolean;
|
|
9
|
+
error: Error | null;
|
|
10
|
+
macro: MacroTimelineSummary | null;
|
|
11
|
+
standard: {
|
|
12
|
+
tasks: import("@critical-path/client").StandardTaskTimelineItem[];
|
|
13
|
+
criticalPathTaskIds: string[];
|
|
14
|
+
dependencies: import("@critical-path/core").TaskDependency[];
|
|
15
|
+
totalDurationHours: number;
|
|
16
|
+
} | null | undefined;
|
|
17
|
+
concrete: Record<string, ConcreteTaskEvidence> | null;
|
|
18
|
+
constructor(client: CriticalPathClient, projectId: string, initialOptions?: TimelineLadderOptions);
|
|
19
|
+
setLevel(newLevel: AbstractionLevel): Promise<void>;
|
|
20
|
+
fetch(options?: TimelineLadderOptions): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
export declare function createTimelineLadderState(client: CriticalPathClient, projectId: string, initialOptions?: TimelineLadderOptions): TimelineLadderState;
|
|
23
|
+
//# sourceMappingURL=timeline-ladder-state.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeline-ladder-state.svelte.d.ts","sourceRoot":"","sources":["../src/timeline-ladder-state.svelte.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACrB,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,qBAAqB,CAAC;AAE7B,qBAAa,mBAAmB;IAW5B,OAAO,CAAC,MAAM;IACP,SAAS,EAAE,MAAM;IAX1B,IAAI,wBAAuC;IAC3C,KAAK,mBAAmC;IACxC,OAAO,UAA0B;IACjC,KAAK,eAA8B;IAEnC,KAAK,8BAAmE;IACxE,QAAQ;;;;;yBAA4E;IACpF,QAAQ,8CAAsF;gBAGpF,MAAM,EAAE,kBAAkB,EAC3B,SAAS,EAAE,MAAM,EACxB,cAAc,CAAC,EAAE,qBAAqB;IAOlC,QAAQ,CAAC,QAAQ,EAAE,gBAAgB;IAKnC,KAAK,CAAC,OAAO,CAAC,EAAE,qBAAqB;CAc5C;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,kBAAkB,EAC1B,SAAS,EAAE,MAAM,EACjB,cAAc,CAAC,EAAE,qBAAqB,GACrC,mBAAmB,CAErB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@critical-path/svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Svelte 5 Runes state and reactive integrations for Critical Path headless project management framework",
|
|
5
5
|
"author": "Jack James",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@critical-path/
|
|
22
|
-
"@critical-path/client": "0.
|
|
23
|
-
"@critical-path/
|
|
21
|
+
"@critical-path/mcp": "0.3.0",
|
|
22
|
+
"@critical-path/client": "0.10.0",
|
|
23
|
+
"@critical-path/core": "0.16.0"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"svelte": "^5.0.0"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/// <reference types="svelte" />
|
|
2
|
+
import type { CriticalPathClient } from '@critical-path/client';
|
|
3
|
+
import type { CriticalPathAnalysis } from '@critical-path/core';
|
|
4
|
+
|
|
5
|
+
export class CriticalPathState {
|
|
6
|
+
data = $state<CriticalPathAnalysis | null>(null);
|
|
7
|
+
loading = $state<boolean>(false);
|
|
8
|
+
error = $state<Error | null>(null);
|
|
9
|
+
|
|
10
|
+
constructor(
|
|
11
|
+
private client: CriticalPathClient,
|
|
12
|
+
public projectId: string
|
|
13
|
+
) {}
|
|
14
|
+
|
|
15
|
+
async fetch() {
|
|
16
|
+
this.loading = true;
|
|
17
|
+
this.error = null;
|
|
18
|
+
try {
|
|
19
|
+
this.data = await this.client.calculateCriticalPath(this.projectId);
|
|
20
|
+
} catch (err) {
|
|
21
|
+
this.error = err instanceof Error ? err : new Error(String(err));
|
|
22
|
+
} finally {
|
|
23
|
+
this.loading = false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createCriticalPathState(
|
|
29
|
+
client: CriticalPathClient,
|
|
30
|
+
projectId: string
|
|
31
|
+
): CriticalPathState {
|
|
32
|
+
return new CriticalPathState(client, projectId);
|
|
33
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/// <reference types="svelte" />
|
|
2
|
+
import type { CriticalPathClient } from '@critical-path/client';
|
|
3
|
+
import type { DeliverableSummary } from '@critical-path/core';
|
|
4
|
+
|
|
5
|
+
export class DeliverableSummaryState {
|
|
6
|
+
summary = $state<DeliverableSummary | null>(null);
|
|
7
|
+
loading = $state<boolean>(false);
|
|
8
|
+
error = $state<Error | null>(null);
|
|
9
|
+
|
|
10
|
+
get data(): DeliverableSummary | null {
|
|
11
|
+
return this.summary;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
constructor(private client: CriticalPathClient, public deliverableId?: string) {}
|
|
15
|
+
|
|
16
|
+
async fetch(deliverableId?: string) {
|
|
17
|
+
const targetId = deliverableId || this.deliverableId;
|
|
18
|
+
if (!targetId) {
|
|
19
|
+
this.summary = null;
|
|
20
|
+
this.loading = false;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
this.deliverableId = targetId;
|
|
24
|
+
this.loading = true;
|
|
25
|
+
this.error = null;
|
|
26
|
+
try {
|
|
27
|
+
this.summary = await this.client.getDeliverableSummary(targetId);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
this.error = err instanceof Error ? err : new Error(String(err));
|
|
30
|
+
} finally {
|
|
31
|
+
this.loading = false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function createDeliverableSummaryState(
|
|
37
|
+
client: CriticalPathClient,
|
|
38
|
+
deliverableId?: string
|
|
39
|
+
): DeliverableSummaryState {
|
|
40
|
+
return new DeliverableSummaryState(client, deliverableId);
|
|
41
|
+
}
|
package/src/index.test.ts
CHANGED
|
@@ -15,11 +15,31 @@ import {
|
|
|
15
15
|
createTaskActivityState,
|
|
16
16
|
DeliverableState,
|
|
17
17
|
createDeliverableState,
|
|
18
|
+
DeliverableSummaryState,
|
|
19
|
+
createDeliverableSummaryState,
|
|
20
|
+
KanbanState,
|
|
21
|
+
createKanbanState,
|
|
22
|
+
TaskTransitionsState,
|
|
23
|
+
createTaskTransitionsState,
|
|
18
24
|
WebMcpState,
|
|
19
|
-
createWebMcpState
|
|
25
|
+
createWebMcpState,
|
|
26
|
+
CriticalPathState,
|
|
27
|
+
createCriticalPathState,
|
|
28
|
+
TimelineLadderState,
|
|
29
|
+
createTimelineLadderState
|
|
20
30
|
} from './index.js';
|
|
21
31
|
import type { CriticalPathClient } from '@critical-path/client';
|
|
22
|
-
import type {
|
|
32
|
+
import type {
|
|
33
|
+
Project,
|
|
34
|
+
Task,
|
|
35
|
+
Workflow,
|
|
36
|
+
Comment,
|
|
37
|
+
Attachment,
|
|
38
|
+
Deliverable,
|
|
39
|
+
DeliverableSummary,
|
|
40
|
+
CriticalPathAnalysis,
|
|
41
|
+
TimelineLadder
|
|
42
|
+
} from '@critical-path/core';
|
|
23
43
|
|
|
24
44
|
describe('@critical-path/svelte Svelte 5 Runes Test Suite', () => {
|
|
25
45
|
it('exports client factory and Svelte 5 Runes state factories', () => {
|
|
@@ -31,6 +51,9 @@ describe('@critical-path/svelte Svelte 5 Runes Test Suite', () => {
|
|
|
31
51
|
expect(createAttachmentState).toBeDefined();
|
|
32
52
|
expect(createTaskActivityState).toBeDefined();
|
|
33
53
|
expect(createDeliverableState).toBeDefined();
|
|
54
|
+
expect(createDeliverableSummaryState).toBeDefined();
|
|
55
|
+
expect(createKanbanState).toBeDefined();
|
|
56
|
+
expect(createTaskTransitionsState).toBeDefined();
|
|
34
57
|
expect(ProjectState).toBeDefined();
|
|
35
58
|
expect(TaskState).toBeDefined();
|
|
36
59
|
expect(WorkflowState).toBeDefined();
|
|
@@ -38,6 +61,11 @@ describe('@critical-path/svelte Svelte 5 Runes Test Suite', () => {
|
|
|
38
61
|
expect(AttachmentState).toBeDefined();
|
|
39
62
|
expect(TaskActivityState).toBeDefined();
|
|
40
63
|
expect(DeliverableState).toBeDefined();
|
|
64
|
+
expect(DeliverableSummaryState).toBeDefined();
|
|
65
|
+
expect(KanbanState).toBeDefined();
|
|
66
|
+
expect(TaskTransitionsState).toBeDefined();
|
|
67
|
+
expect(WebMcpState).toBeDefined();
|
|
68
|
+
expect(createWebMcpState).toBeDefined();
|
|
41
69
|
});
|
|
42
70
|
|
|
43
71
|
describe('WorkflowState', () => {
|
|
@@ -455,6 +483,184 @@ describe('@critical-path/svelte Svelte 5 Runes Test Suite', () => {
|
|
|
455
483
|
expect(mcpState.tools).toEqual([]);
|
|
456
484
|
});
|
|
457
485
|
});
|
|
486
|
+
|
|
487
|
+
describe('KanbanState', () => {
|
|
488
|
+
it('initializes and buckets tasks into workflow and semantic columns', async () => {
|
|
489
|
+
const mockTasks: Task[] = [
|
|
490
|
+
{
|
|
491
|
+
id: 't_todo',
|
|
492
|
+
projectId: 'p1',
|
|
493
|
+
title: 'To Do Task',
|
|
494
|
+
status: 'todo',
|
|
495
|
+
priority: 'high',
|
|
496
|
+
createdAt: '2026-01-01',
|
|
497
|
+
updatedAt: '2026-01-01'
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
id: 't_done',
|
|
501
|
+
projectId: 'p1',
|
|
502
|
+
title: 'Done Task',
|
|
503
|
+
status: 'done',
|
|
504
|
+
priority: 'low',
|
|
505
|
+
createdAt: '2026-01-01',
|
|
506
|
+
updatedAt: '2026-01-01'
|
|
507
|
+
}
|
|
508
|
+
];
|
|
509
|
+
|
|
510
|
+
const mockClient = {
|
|
511
|
+
getTasks: vi.fn().mockResolvedValue(mockTasks),
|
|
512
|
+
updateTask: vi.fn().mockImplementation((id, updates) => Promise.resolve({ ...mockTasks.find((t) => t.id === id), ...updates }))
|
|
513
|
+
} as unknown as CriticalPathClient;
|
|
514
|
+
|
|
515
|
+
// Workflow grouping
|
|
516
|
+
const kanban = createKanbanState(mockClient, 'p1');
|
|
517
|
+
await kanban.fetch();
|
|
518
|
+
|
|
519
|
+
expect(kanban.columns.todo).toHaveLength(1);
|
|
520
|
+
expect(kanban.columns.done).toHaveLength(1);
|
|
521
|
+
expect(kanban.columns.in_progress).toHaveLength(0);
|
|
522
|
+
|
|
523
|
+
// Move task
|
|
524
|
+
const updated = await kanban.moveTask('t_todo', 'in_progress');
|
|
525
|
+
expect(updated.status).toBe('in_progress');
|
|
526
|
+
|
|
527
|
+
// Semantic grouping
|
|
528
|
+
const semanticKanban = createKanbanState(mockClient, 'p1', { groupBy: 'semantic' });
|
|
529
|
+
await semanticKanban.fetch();
|
|
530
|
+
expect(semanticKanban.columns.not_started).toHaveLength(1);
|
|
531
|
+
expect(semanticKanban.columns.completed).toHaveLength(1);
|
|
532
|
+
});
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
describe('TaskTransitionsState', () => {
|
|
536
|
+
it('fetches allowed transitions for a task', async () => {
|
|
537
|
+
const mockClient = {
|
|
538
|
+
getAllowedTaskTransitions: vi.fn().mockResolvedValue(['in_progress', 'canceled'])
|
|
539
|
+
} as unknown as CriticalPathClient;
|
|
540
|
+
|
|
541
|
+
const transitionsState = createTaskTransitionsState(mockClient, 'task_123');
|
|
542
|
+
expect(transitionsState.allowedTransitions).toEqual([]);
|
|
543
|
+
|
|
544
|
+
await transitionsState.fetch();
|
|
545
|
+
expect(mockClient.getAllowedTaskTransitions).toHaveBeenCalledWith('task_123');
|
|
546
|
+
expect(transitionsState.allowedTransitions).toEqual(['in_progress', 'canceled']);
|
|
547
|
+
});
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
describe('DeliverableSummaryState', () => {
|
|
551
|
+
it('fetches deliverable summary reactively', async () => {
|
|
552
|
+
const mockDeliverable: Deliverable = {
|
|
553
|
+
id: 'deliv_1',
|
|
554
|
+
projectId: 'proj_1',
|
|
555
|
+
title: 'MVP Launch',
|
|
556
|
+
status: 'planned',
|
|
557
|
+
createdAt: '2026-01-01',
|
|
558
|
+
updatedAt: '2026-01-01'
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
const mockSummary: DeliverableSummary = {
|
|
562
|
+
deliverable: mockDeliverable,
|
|
563
|
+
totalTasks: 4,
|
|
564
|
+
completedTasks: 3,
|
|
565
|
+
activeTasks: 1,
|
|
566
|
+
progressPercentage: 75,
|
|
567
|
+
estimatedHours: 40,
|
|
568
|
+
loggedHours: 30
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
const mockClient = {
|
|
572
|
+
getDeliverableSummary: vi.fn().mockResolvedValue(mockSummary)
|
|
573
|
+
} as unknown as CriticalPathClient;
|
|
574
|
+
|
|
575
|
+
const summaryState = createDeliverableSummaryState(mockClient, 'deliv_1');
|
|
576
|
+
expect(summaryState.summary).toBeNull();
|
|
577
|
+
expect(summaryState.data).toBeNull();
|
|
578
|
+
|
|
579
|
+
await summaryState.fetch();
|
|
580
|
+
expect(mockClient.getDeliverableSummary).toHaveBeenCalledWith('deliv_1');
|
|
581
|
+
expect(summaryState.summary).toEqual(mockSummary);
|
|
582
|
+
expect(summaryState.data).toEqual(mockSummary);
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
describe('CriticalPathState', () => {
|
|
587
|
+
it('manages loading, error, and critical path analysis data', async () => {
|
|
588
|
+
const mockAnalysis: CriticalPathAnalysis = {
|
|
589
|
+
projectId: 'proj_cpm',
|
|
590
|
+
calculatedAt: '2026-09-11T12:00:00Z',
|
|
591
|
+
totalDurationHours: 42,
|
|
592
|
+
criticalTaskIds: ['t1', 't2'],
|
|
593
|
+
tasks: []
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
const mockClient = {
|
|
597
|
+
calculateCriticalPath: vi.fn().mockResolvedValue(mockAnalysis)
|
|
598
|
+
} as unknown as CriticalPathClient;
|
|
599
|
+
|
|
600
|
+
const cpmState = createCriticalPathState(mockClient, 'proj_cpm');
|
|
601
|
+
expect(cpmState.data).toBeNull();
|
|
602
|
+
expect(cpmState.loading).toBe(false);
|
|
603
|
+
|
|
604
|
+
await cpmState.fetch();
|
|
605
|
+
expect(mockClient.calculateCriticalPath).toHaveBeenCalledWith('proj_cpm');
|
|
606
|
+
expect(cpmState.data).toEqual(mockAnalysis);
|
|
607
|
+
expect(cpmState.loading).toBe(false);
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
describe('TimelineLadderState', () => {
|
|
612
|
+
it('manages multi-scale ladder data, levels, and derived rungs', async () => {
|
|
613
|
+
const mockLadder: TimelineLadder = {
|
|
614
|
+
projectId: 'proj_ladder',
|
|
615
|
+
generatedAt: '2026-09-11T12:00:00Z',
|
|
616
|
+
level: 'all',
|
|
617
|
+
macro: {
|
|
618
|
+
projectId: 'proj_ladder',
|
|
619
|
+
projectName: 'Ladder Proj',
|
|
620
|
+
totalDurationHours: 50,
|
|
621
|
+
criticalPathDurationHours: 40,
|
|
622
|
+
overallProgressPercentage: 60,
|
|
623
|
+
health: 'on_track',
|
|
624
|
+
totalTasks: 5,
|
|
625
|
+
completedTasks: 3,
|
|
626
|
+
inProgressTasks: 2,
|
|
627
|
+
blockedTasks: 0,
|
|
628
|
+
totalEstimatedHours: 50,
|
|
629
|
+
totalLoggedHours: 25,
|
|
630
|
+
phases: []
|
|
631
|
+
},
|
|
632
|
+
standard: {
|
|
633
|
+
tasks: [],
|
|
634
|
+
criticalPathTaskIds: [],
|
|
635
|
+
dependencies: [],
|
|
636
|
+
totalDurationHours: 40
|
|
637
|
+
},
|
|
638
|
+
concrete: {}
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
const mockClient = {
|
|
642
|
+
getTimelineLadder: vi.fn().mockResolvedValue(mockLadder)
|
|
643
|
+
} as unknown as CriticalPathClient;
|
|
644
|
+
|
|
645
|
+
const ladderState = createTimelineLadderState(mockClient, 'proj_ladder', { level: 'all' });
|
|
646
|
+
expect(ladderState.data).toBeNull();
|
|
647
|
+
expect(ladderState.macro).toBeNull();
|
|
648
|
+
expect(ladderState.standard).toBeNull();
|
|
649
|
+
expect(ladderState.concrete).toBeNull();
|
|
650
|
+
|
|
651
|
+
await ladderState.fetch();
|
|
652
|
+
expect(mockClient.getTimelineLadder).toHaveBeenCalledWith('proj_ladder', { level: 'all' });
|
|
653
|
+
expect(ladderState.data).toEqual(mockLadder);
|
|
654
|
+
expect(ladderState.macro?.projectName).toBe('Ladder Proj');
|
|
655
|
+
expect(ladderState.standard?.tasks).toEqual([]);
|
|
656
|
+
expect(ladderState.concrete).toEqual({});
|
|
657
|
+
|
|
658
|
+
await ladderState.setLevel('macro');
|
|
659
|
+
expect(ladderState.level).toBe('macro');
|
|
660
|
+
expect(mockClient.getTimelineLadder).toHaveBeenCalledWith('proj_ladder', { level: 'macro' });
|
|
661
|
+
});
|
|
662
|
+
});
|
|
458
663
|
});
|
|
459
664
|
|
|
460
665
|
|
|
666
|
+
|
package/src/index.ts
CHANGED
|
@@ -11,5 +11,11 @@ export * from './comment-state.svelte.js';
|
|
|
11
11
|
export * from './attachment-state.svelte.js';
|
|
12
12
|
export * from './activity-state.svelte.js';
|
|
13
13
|
export * from './deliverable-state.svelte.js';
|
|
14
|
+
export * from './deliverable-summary-state.svelte.js';
|
|
15
|
+
export * from './kanban-state.svelte.js';
|
|
16
|
+
export * from './task-transitions-state.svelte.js';
|
|
14
17
|
export * from './webmcp-state.svelte.js';
|
|
18
|
+
export * from './critical-path-state.svelte.js';
|
|
19
|
+
export * from './timeline-ladder-state.svelte.js';
|
|
20
|
+
|
|
15
21
|
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/// <reference types="svelte" />
|
|
2
|
+
import type { CriticalPathClient } from '@critical-path/client';
|
|
3
|
+
import type { Task, TaskStatus, StatusDefinition, SemanticStatus } from '@critical-path/core';
|
|
4
|
+
import { resolveStatusDefinition } from '@critical-path/core';
|
|
5
|
+
import { TaskState } from './task-state.svelte.js';
|
|
6
|
+
|
|
7
|
+
export interface KanbanStateOptions {
|
|
8
|
+
groupBy?: 'workflow' | 'semantic';
|
|
9
|
+
customDefinitions?: StatusDefinition[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class KanbanState {
|
|
13
|
+
private taskState: TaskState;
|
|
14
|
+
|
|
15
|
+
get data(): Task[] {
|
|
16
|
+
return this.taskState.data;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get tasks(): Task[] {
|
|
20
|
+
return this.taskState.data;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get loading(): boolean {
|
|
24
|
+
return this.taskState.loading;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get error(): Error | null {
|
|
28
|
+
return this.taskState.error;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
columns = $derived.by<Record<string, Task[]>>(() => {
|
|
32
|
+
const tasks = this.taskState.data;
|
|
33
|
+
if (this.options?.groupBy === 'semantic') {
|
|
34
|
+
const semanticCols: Record<SemanticStatus, Task[]> = {
|
|
35
|
+
not_started: [],
|
|
36
|
+
in_progress: [],
|
|
37
|
+
completed: [],
|
|
38
|
+
canceled: []
|
|
39
|
+
};
|
|
40
|
+
for (const task of tasks) {
|
|
41
|
+
const category =
|
|
42
|
+
task.semanticStatus ||
|
|
43
|
+
resolveStatusDefinition(task.status, this.options?.customDefinitions).category;
|
|
44
|
+
if (semanticCols[category]) {
|
|
45
|
+
semanticCols[category].push(task);
|
|
46
|
+
} else {
|
|
47
|
+
semanticCols.not_started.push(task);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return semanticCols;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const workflowCols: Record<string, Task[]> = {};
|
|
54
|
+
if (this.options?.customDefinitions && this.options.customDefinitions.length > 0) {
|
|
55
|
+
for (const def of this.options.customDefinitions) {
|
|
56
|
+
workflowCols[def.key] = [];
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
workflowCols.backlog = [];
|
|
60
|
+
workflowCols.todo = [];
|
|
61
|
+
workflowCols.in_progress = [];
|
|
62
|
+
workflowCols.in_review = [];
|
|
63
|
+
workflowCols.done = [];
|
|
64
|
+
workflowCols.canceled = [];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (const task of tasks) {
|
|
68
|
+
if (!workflowCols[task.status]) {
|
|
69
|
+
workflowCols[task.status] = [];
|
|
70
|
+
}
|
|
71
|
+
workflowCols[task.status].push(task);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return workflowCols;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
constructor(
|
|
78
|
+
client: CriticalPathClient,
|
|
79
|
+
public projectId?: string,
|
|
80
|
+
public options: KanbanStateOptions = {}
|
|
81
|
+
) {
|
|
82
|
+
this.taskState = new TaskState(client, projectId);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async fetch() {
|
|
86
|
+
return this.taskState.fetch();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async createTask(input: Omit<Task, 'id' | 'createdAt' | 'updatedAt'>) {
|
|
90
|
+
return this.taskState.createTask(input);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async moveTask(taskId: string, targetStatus: TaskStatus) {
|
|
94
|
+
return this.taskState.updateTaskStatus(taskId, targetStatus);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async updateTaskStatus(taskId: string, status: TaskStatus) {
|
|
98
|
+
return this.taskState.updateTaskStatus(taskId, status);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async deleteTask(taskId: string) {
|
|
102
|
+
return this.taskState.deleteTask(taskId);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function createKanbanState(
|
|
107
|
+
client: CriticalPathClient,
|
|
108
|
+
projectId?: string,
|
|
109
|
+
options?: KanbanStateOptions
|
|
110
|
+
): KanbanState {
|
|
111
|
+
return new KanbanState(client, projectId, options);
|
|
112
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/// <reference types="svelte" />
|
|
2
|
+
import type { CriticalPathClient } from '@critical-path/client';
|
|
3
|
+
|
|
4
|
+
export class TaskTransitionsState {
|
|
5
|
+
allowedTransitions = $state<string[]>([]);
|
|
6
|
+
loading = $state<boolean>(false);
|
|
7
|
+
error = $state<Error | null>(null);
|
|
8
|
+
|
|
9
|
+
constructor(private client: CriticalPathClient, public taskId?: string) {}
|
|
10
|
+
|
|
11
|
+
async fetch(taskId?: string) {
|
|
12
|
+
const targetTaskId = taskId || this.taskId;
|
|
13
|
+
if (!targetTaskId) {
|
|
14
|
+
this.allowedTransitions = [];
|
|
15
|
+
this.loading = false;
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
this.taskId = targetTaskId;
|
|
19
|
+
this.loading = true;
|
|
20
|
+
this.error = null;
|
|
21
|
+
try {
|
|
22
|
+
this.allowedTransitions = await this.client.getAllowedTaskTransitions(targetTaskId);
|
|
23
|
+
} catch (err) {
|
|
24
|
+
this.error = err instanceof Error ? err : new Error(String(err));
|
|
25
|
+
} finally {
|
|
26
|
+
this.loading = false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createTaskTransitionsState(
|
|
32
|
+
client: CriticalPathClient,
|
|
33
|
+
taskId?: string
|
|
34
|
+
): TaskTransitionsState {
|
|
35
|
+
return new TaskTransitionsState(client, taskId);
|
|
36
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/// <reference types="svelte" />
|
|
2
|
+
import type { CriticalPathClient } from '@critical-path/client';
|
|
3
|
+
import type {
|
|
4
|
+
TimelineLadder,
|
|
5
|
+
TimelineLadderOptions,
|
|
6
|
+
AbstractionLevel,
|
|
7
|
+
MacroTimelineSummary,
|
|
8
|
+
ConcreteTaskEvidence
|
|
9
|
+
} from '@critical-path/core';
|
|
10
|
+
|
|
11
|
+
export class TimelineLadderState {
|
|
12
|
+
data = $state<TimelineLadder | null>(null);
|
|
13
|
+
level = $state<AbstractionLevel>('all');
|
|
14
|
+
loading = $state<boolean>(false);
|
|
15
|
+
error = $state<Error | null>(null);
|
|
16
|
+
|
|
17
|
+
macro = $derived<MacroTimelineSummary | null>(this.data?.macro ?? null);
|
|
18
|
+
standard = $derived<TimelineLadder['standard'] | null>(this.data?.standard ?? null);
|
|
19
|
+
concrete = $derived<Record<string, ConcreteTaskEvidence> | null>(this.data?.concrete ?? null);
|
|
20
|
+
|
|
21
|
+
constructor(
|
|
22
|
+
private client: CriticalPathClient,
|
|
23
|
+
public projectId: string,
|
|
24
|
+
initialOptions?: TimelineLadderOptions
|
|
25
|
+
) {
|
|
26
|
+
if (initialOptions?.level) {
|
|
27
|
+
this.level = initialOptions.level;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async setLevel(newLevel: AbstractionLevel) {
|
|
32
|
+
this.level = newLevel;
|
|
33
|
+
await this.fetch();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async fetch(options?: TimelineLadderOptions) {
|
|
37
|
+
this.loading = true;
|
|
38
|
+
this.error = null;
|
|
39
|
+
try {
|
|
40
|
+
this.data = await this.client.getTimelineLadder(this.projectId, {
|
|
41
|
+
level: this.level,
|
|
42
|
+
...options
|
|
43
|
+
});
|
|
44
|
+
} catch (err) {
|
|
45
|
+
this.error = err instanceof Error ? err : new Error(String(err));
|
|
46
|
+
} finally {
|
|
47
|
+
this.loading = false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function createTimelineLadderState(
|
|
53
|
+
client: CriticalPathClient,
|
|
54
|
+
projectId: string,
|
|
55
|
+
initialOptions?: TimelineLadderOptions
|
|
56
|
+
): TimelineLadderState {
|
|
57
|
+
return new TimelineLadderState(client, projectId, initialOptions);
|
|
58
|
+
}
|