@critical-path/svelte 0.13.1 → 0.15.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.
@@ -0,0 +1,24 @@
1
+ import type { CriticalPathClient } from '@critical-path/client';
2
+ import type { TaskMetrics, TaskProgressHistory, TaskInferredActuals, RealityDelta, TaskProgressInference, TaskEVM, ProgressCurveProfile } from '@critical-path/core';
3
+ export declare class TaskMetricsState {
4
+ private client;
5
+ taskId: string;
6
+ metrics: TaskMetrics | null;
7
+ history: TaskProgressHistory | null;
8
+ loading: boolean;
9
+ error: Error | null;
10
+ inferredActuals: TaskInferredActuals | null;
11
+ realityDelta: RealityDelta | null;
12
+ progress: TaskProgressInference | null;
13
+ evm: TaskEVM | null;
14
+ curveProfile: ProgressCurveProfile | null;
15
+ constructor(client: CriticalPathClient, taskId: string);
16
+ fetchMetrics(): Promise<TaskMetrics | null>;
17
+ fetchHistory(): Promise<TaskProgressHistory | null>;
18
+ fetchAll(): Promise<{
19
+ metrics: TaskMetrics | null;
20
+ history: TaskProgressHistory | null;
21
+ }>;
22
+ }
23
+ export declare function createTaskMetricsState(client: CriticalPathClient, taskId: string): TaskMetricsState;
24
+ //# sourceMappingURL=task-metrics-state.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-metrics-state.svelte.d.ts","sourceRoot":"","sources":["../src/task-metrics-state.svelte.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,KAAK,EACV,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,YAAY,EACZ,qBAAqB,EACrB,OAAO,EACP,oBAAoB,EACrB,MAAM,qBAAqB,CAAC;AAE7B,qBAAa,gBAAgB;IAazB,OAAO,CAAC,MAAM;IACP,MAAM,EAAE,MAAM;IAbvB,OAAO,qBAAoC;IAC3C,OAAO,6BAA4C;IACnD,OAAO,UAA0B;IACjC,KAAK,eAA8B;IAEnC,eAAe,6BAA+E;IAC9F,YAAY,sBAAqE;IACjF,QAAQ,+BAA0E;IAClF,GAAG,iBAAuD;IAC1D,YAAY,8BAA6E;gBAG/E,MAAM,EAAE,kBAAkB,EAC3B,MAAM,EAAE,MAAM;IAGjB,YAAY,IAAI,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAc3C,YAAY,IAAI,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAcnD,QAAQ,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;QAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAAA;KAAE,CAAC;CAkBhG;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,kBAAkB,EAC1B,MAAM,EAAE,MAAM,GACb,gBAAgB,CAElB"}
@@ -0,0 +1,26 @@
1
+ import type { CriticalPathClient } from '@critical-path/client';
2
+ import type { WorkloadDistribution, WorkloadDistributionOptions, WorkloadInterval, WorkloadGroupBy, WorkloadMetric, WorkloadBucket } from '@critical-path/core';
3
+ export declare class WorkloadState {
4
+ private client;
5
+ projectId?: string | undefined;
6
+ initialOptions?: WorkloadDistributionOptions | undefined;
7
+ data: WorkloadDistribution | null;
8
+ interval: WorkloadInterval;
9
+ groupBy: WorkloadGroupBy;
10
+ metric: WorkloadMetric;
11
+ loading: boolean;
12
+ error: Error | null;
13
+ buckets: WorkloadBucket[];
14
+ seriesKeys: string[];
15
+ seriesLabels: Record<string, string>;
16
+ totalHours: number;
17
+ totalCapacity: number;
18
+ averageUtilization: number | undefined;
19
+ constructor(client: CriticalPathClient, projectId?: string | undefined, initialOptions?: WorkloadDistributionOptions | undefined);
20
+ setInterval(newInterval: WorkloadInterval): Promise<void>;
21
+ setGroupBy(newGroupBy: WorkloadGroupBy): Promise<void>;
22
+ setMetric(newMetric: WorkloadMetric): Promise<void>;
23
+ fetch(options?: WorkloadDistributionOptions): Promise<void>;
24
+ }
25
+ export declare function createWorkloadState(client: CriticalPathClient, projectId?: string, initialOptions?: WorkloadDistributionOptions): WorkloadState;
26
+ //# sourceMappingURL=workload-state.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workload-state.svelte.d.ts","sourceRoot":"","sources":["../src/workload-state.svelte.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,KAAK,EACV,oBAAoB,EACpB,2BAA2B,EAC3B,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,cAAc,EACf,MAAM,qBAAqB,CAAC;AAE7B,qBAAa,aAAa;IAgBtB,OAAO,CAAC,MAAM;IACP,SAAS,CAAC,EAAE,MAAM;IAClB,cAAc,CAAC,EAAE,2BAA2B;IAjBrD,IAAI,8BAA6C;IACjD,QAAQ,mBAAoC;IAC5C,OAAO,kBAAuC;IAC9C,MAAM,iBAAqC;IAC3C,OAAO,UAA0B;IACjC,KAAK,eAA8B;IAEnC,OAAO,mBAAwD;IAC/D,UAAU,WAAmD;IAC7D,YAAY,yBAAmE;IAC/E,UAAU,SAAgD;IAC1D,aAAa,SAAmD;IAChE,kBAAkB,qBAA+D;gBAGvE,MAAM,EAAE,kBAAkB,EAC3B,SAAS,CAAC,EAAE,MAAM,YAAA,EAClB,cAAc,CAAC,EAAE,2BAA2B,YAAA;IAa/C,WAAW,CAAC,WAAW,EAAE,gBAAgB;IAKzC,UAAU,CAAC,UAAU,EAAE,eAAe;IAKtC,SAAS,CAAC,SAAS,EAAE,cAAc;IAKnC,KAAK,CAAC,OAAO,CAAC,EAAE,2BAA2B;CAiBlD;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,kBAAkB,EAC1B,SAAS,CAAC,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,2BAA2B,GAC3C,aAAa,CAEf"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@critical-path/svelte",
3
- "version": "0.13.1",
3
+ "version": "0.15.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/core": "0.16.0",
22
- "@critical-path/mcp": "0.3.1",
23
- "@critical-path/client": "0.10.0"
21
+ "@critical-path/client": "0.12.0",
22
+ "@critical-path/core": "0.18.0",
23
+ "@critical-path/mcp": "0.5.0"
24
24
  },
25
25
  "peerDependencies": {
26
26
  "svelte": "^5.0.0"
package/src/index.test.ts CHANGED
@@ -26,7 +26,11 @@ import {
26
26
  CriticalPathState,
27
27
  createCriticalPathState,
28
28
  TimelineLadderState,
29
- createTimelineLadderState
29
+ createTimelineLadderState,
30
+ TaskMetricsState,
31
+ createTaskMetricsState,
32
+ WorkloadState,
33
+ createWorkloadState
30
34
  } from './index.js';
31
35
  import type { CriticalPathClient } from '@critical-path/client';
32
36
  import type {
@@ -38,7 +42,8 @@ import type {
38
42
  Deliverable,
39
43
  DeliverableSummary,
40
44
  CriticalPathAnalysis,
41
- TimelineLadder
45
+ TimelineLadder,
46
+ WorkloadDistribution
42
47
  } from '@critical-path/core';
43
48
 
44
49
  describe('@critical-path/svelte Svelte 5 Runes Test Suite', () => {
@@ -660,6 +665,159 @@ describe('@critical-path/svelte Svelte 5 Runes Test Suite', () => {
660
665
  expect(mockClient.getTimelineLadder).toHaveBeenCalledWith('proj_ladder', { level: 'macro' });
661
666
  });
662
667
  });
668
+
669
+ describe('TaskMetricsState', () => {
670
+ it('fetches metrics and progress history and derives subfields', async () => {
671
+ const mockMetrics = {
672
+ taskId: 'task_1',
673
+ inferredActuals: {
674
+ actualStartDate: '2026-09-01T00:00:00Z',
675
+ actualEndDate: '2026-09-05T00:00:00Z',
676
+ isStartDateInferred: false,
677
+ isEndDateInferred: true,
678
+ activeWorkingHours: 96
679
+ },
680
+ realityDelta: {
681
+ estimatedHours: 20,
682
+ loggedHours: 15,
683
+ varianceHours: -5,
684
+ effortVarianceHours: -5,
685
+ accuracyRatio: 0.75,
686
+ isOverdue: false,
687
+ isOverEstimate: false
688
+ },
689
+ progress: {
690
+ progressPercentage: 75,
691
+ source: 'explicit' as const,
692
+ isExplicit: true,
693
+ breakdown: { explicitProgress: 75 }
694
+ },
695
+ evm: {
696
+ plannedValue: 20,
697
+ earnedValue: 15,
698
+ actualCost: 15,
699
+ costVariance: 0,
700
+ scheduleVariance: -5,
701
+ costPerformanceIndex: 1.0,
702
+ schedulePerformanceIndex: 0.75
703
+ }
704
+ };
705
+
706
+ const mockHistory = {
707
+ taskId: 'task_1',
708
+ points: [
709
+ { timestamp: '2026-09-01T00:00:00Z', progress: 0, status: 'todo', semanticStatus: 'not_started' as const, action: 'task.created' },
710
+ { timestamp: '2026-09-05T00:00:00Z', progress: 75, status: 'in_progress', semanticStatus: 'in_progress' as const, action: 'task.updated' }
711
+ ],
712
+ curveProfile: 'linear' as const
713
+ };
714
+
715
+ const mockClient = {
716
+ getTaskMetrics: vi.fn().mockResolvedValue(mockMetrics),
717
+ getTaskProgressHistory: vi.fn().mockResolvedValue(mockHistory)
718
+ } as unknown as CriticalPathClient;
719
+
720
+ const state = createTaskMetricsState(mockClient, 'task_1');
721
+ expect(state.metrics).toBeNull();
722
+ expect(state.history).toBeNull();
723
+ expect(state.inferredActuals).toBeNull();
724
+ expect(state.realityDelta).toBeNull();
725
+ expect(state.progress).toBeNull();
726
+ expect(state.evm).toBeNull();
727
+ expect(state.curveProfile).toBeNull();
728
+
729
+ await state.fetchAll();
730
+ expect(mockClient.getTaskMetrics).toHaveBeenCalledWith('task_1');
731
+ expect(mockClient.getTaskProgressHistory).toHaveBeenCalledWith('task_1');
732
+ expect(state.metrics).toEqual(mockMetrics);
733
+ expect(state.history).toEqual(mockHistory);
734
+ expect(state.inferredActuals?.activeWorkingHours).toBe(96);
735
+ expect(state.realityDelta?.effortVarianceHours).toBe(-5);
736
+ expect(state.progress?.progressPercentage).toBe(75);
737
+ expect(state.evm?.costPerformanceIndex).toBe(1.0);
738
+ expect(state.curveProfile).toBe('linear');
739
+ });
740
+ });
741
+
742
+ describe('WorkloadState & createWorkloadState', () => {
743
+ it('initializes with default options and fetches workload distribution', async () => {
744
+ const mockDistribution: WorkloadDistribution = {
745
+ projectId: 'proj_wl',
746
+ startDate: '2026-09-01',
747
+ endDate: '2026-09-14',
748
+ interval: 'week',
749
+ groupBy: 'assignee',
750
+ metric: 'blended',
751
+ seriesKeys: ['u1', 'u2'],
752
+ seriesLabels: { u1: 'Alice', u2: 'Bob' },
753
+ buckets: [
754
+ {
755
+ date: '2026-09-01',
756
+ timestamp: 1788220800000,
757
+ totalHours: 40,
758
+ values: { u1: 25, u2: 15 },
759
+ capacity: { u1: 35, u2: 40 },
760
+ totalCapacity: 75,
761
+ utilizationRatio: 0.533
762
+ }
763
+ ],
764
+ totalHours: 40,
765
+ totalCapacity: 75,
766
+ averageUtilization: 0.533
767
+ };
768
+
769
+ const mockClient = {
770
+ getWorkloadDistribution: vi.fn().mockResolvedValue(mockDistribution)
771
+ } as unknown as CriticalPathClient;
772
+
773
+ const state = createWorkloadState(mockClient, 'proj_wl', {
774
+ interval: 'week',
775
+ groupBy: 'assignee',
776
+ metric: 'blended'
777
+ });
778
+
779
+ expect(state.data).toBeNull();
780
+ expect(state.buckets).toEqual([]);
781
+ expect(state.seriesKeys).toEqual([]);
782
+ expect(state.totalHours).toBe(0);
783
+
784
+ await state.fetch();
785
+
786
+ expect(mockClient.getWorkloadDistribution).toHaveBeenCalledWith('proj_wl', {
787
+ interval: 'week',
788
+ groupBy: 'assignee',
789
+ metric: 'blended'
790
+ });
791
+ expect(state.data).toEqual(mockDistribution);
792
+ expect(state.buckets).toHaveLength(1);
793
+ expect(state.seriesKeys).toEqual(['u1', 'u2']);
794
+ expect(state.totalHours).toBe(40);
795
+ expect(state.totalCapacity).toBe(75);
796
+ expect(state.averageUtilization).toBe(0.533);
797
+ });
798
+
799
+ it('updates interval and re-fetches', async () => {
800
+ const mockClient = {
801
+ getWorkloadDistribution: vi.fn().mockResolvedValue({
802
+ buckets: [],
803
+ seriesKeys: [],
804
+ seriesLabels: {},
805
+ totalHours: 0,
806
+ totalCapacity: 0
807
+ })
808
+ } as unknown as CriticalPathClient;
809
+
810
+ const state = new WorkloadState(mockClient, 'proj_wl');
811
+ expect(state.interval).toBe('week');
812
+
813
+ await state.setInterval('day');
814
+ expect(state.interval).toBe('day');
815
+ expect(mockClient.getWorkloadDistribution).toHaveBeenCalledWith(
816
+ 'proj_wl',
817
+ expect.objectContaining({ interval: 'day' })
818
+ );
819
+ });
820
+ });
663
821
  });
664
822
 
665
823
 
package/src/index.ts CHANGED
@@ -17,5 +17,7 @@ export * from './task-transitions-state.svelte.js';
17
17
  export * from './webmcp-state.svelte.js';
18
18
  export * from './critical-path-state.svelte.js';
19
19
  export * from './timeline-ladder-state.svelte.js';
20
+ export * from './task-metrics-state.svelte.js';
21
+ export * from './workload-state.svelte.js';
20
22
 
21
23
 
@@ -0,0 +1,83 @@
1
+ /// <reference types="svelte" />
2
+ import type { CriticalPathClient } from '@critical-path/client';
3
+ import type {
4
+ TaskMetrics,
5
+ TaskProgressHistory,
6
+ TaskInferredActuals,
7
+ RealityDelta,
8
+ TaskProgressInference,
9
+ TaskEVM,
10
+ ProgressCurveProfile
11
+ } from '@critical-path/core';
12
+
13
+ export class TaskMetricsState {
14
+ metrics = $state<TaskMetrics | null>(null);
15
+ history = $state<TaskProgressHistory | null>(null);
16
+ loading = $state<boolean>(false);
17
+ error = $state<Error | null>(null);
18
+
19
+ inferredActuals = $derived<TaskInferredActuals | null>(this.metrics?.inferredActuals ?? null);
20
+ realityDelta = $derived<RealityDelta | null>(this.metrics?.realityDelta ?? null);
21
+ progress = $derived<TaskProgressInference | null>(this.metrics?.progress ?? null);
22
+ evm = $derived<TaskEVM | null>(this.metrics?.evm ?? null);
23
+ curveProfile = $derived<ProgressCurveProfile | null>(this.history?.curveProfile ?? null);
24
+
25
+ constructor(
26
+ private client: CriticalPathClient,
27
+ public taskId: string
28
+ ) {}
29
+
30
+ async fetchMetrics(): Promise<TaskMetrics | null> {
31
+ this.loading = true;
32
+ this.error = null;
33
+ try {
34
+ this.metrics = await this.client.getTaskMetrics(this.taskId);
35
+ return this.metrics;
36
+ } catch (err) {
37
+ this.error = err instanceof Error ? err : new Error(String(err));
38
+ return null;
39
+ } finally {
40
+ this.loading = false;
41
+ }
42
+ }
43
+
44
+ async fetchHistory(): Promise<TaskProgressHistory | null> {
45
+ this.loading = true;
46
+ this.error = null;
47
+ try {
48
+ this.history = await this.client.getTaskProgressHistory(this.taskId);
49
+ return this.history;
50
+ } catch (err) {
51
+ this.error = err instanceof Error ? err : new Error(String(err));
52
+ return null;
53
+ } finally {
54
+ this.loading = false;
55
+ }
56
+ }
57
+
58
+ async fetchAll(): Promise<{ metrics: TaskMetrics | null; history: TaskProgressHistory | null }> {
59
+ this.loading = true;
60
+ this.error = null;
61
+ try {
62
+ const [m, h] = await Promise.all([
63
+ this.client.getTaskMetrics(this.taskId),
64
+ this.client.getTaskProgressHistory(this.taskId)
65
+ ]);
66
+ this.metrics = m;
67
+ this.history = h;
68
+ return { metrics: m, history: h };
69
+ } catch (err) {
70
+ this.error = err instanceof Error ? err : new Error(String(err));
71
+ return { metrics: null, history: null };
72
+ } finally {
73
+ this.loading = false;
74
+ }
75
+ }
76
+ }
77
+
78
+ export function createTaskMetricsState(
79
+ client: CriticalPathClient,
80
+ taskId: string
81
+ ): TaskMetricsState {
82
+ return new TaskMetricsState(client, taskId);
83
+ }
@@ -0,0 +1,83 @@
1
+ /// <reference types="svelte" />
2
+ import type { CriticalPathClient } from '@critical-path/client';
3
+ import type {
4
+ WorkloadDistribution,
5
+ WorkloadDistributionOptions,
6
+ WorkloadInterval,
7
+ WorkloadGroupBy,
8
+ WorkloadMetric,
9
+ WorkloadBucket
10
+ } from '@critical-path/core';
11
+
12
+ export class WorkloadState {
13
+ data = $state<WorkloadDistribution | null>(null);
14
+ interval = $state<WorkloadInterval>('week');
15
+ groupBy = $state<WorkloadGroupBy>('assignee');
16
+ metric = $state<WorkloadMetric>('blended');
17
+ loading = $state<boolean>(false);
18
+ error = $state<Error | null>(null);
19
+
20
+ buckets = $derived<WorkloadBucket[]>(this.data?.buckets ?? []);
21
+ seriesKeys = $derived<string[]>(this.data?.seriesKeys ?? []);
22
+ seriesLabels = $derived<Record<string, string>>(this.data?.seriesLabels ?? {});
23
+ totalHours = $derived<number>(this.data?.totalHours ?? 0);
24
+ totalCapacity = $derived<number>(this.data?.totalCapacity ?? 0);
25
+ averageUtilization = $derived<number | undefined>(this.data?.averageUtilization);
26
+
27
+ constructor(
28
+ private client: CriticalPathClient,
29
+ public projectId?: string,
30
+ public initialOptions?: WorkloadDistributionOptions
31
+ ) {
32
+ if (initialOptions?.interval) {
33
+ this.interval = initialOptions.interval;
34
+ }
35
+ if (initialOptions?.groupBy) {
36
+ this.groupBy = initialOptions.groupBy;
37
+ }
38
+ if (initialOptions?.metric) {
39
+ this.metric = initialOptions.metric;
40
+ }
41
+ }
42
+
43
+ async setInterval(newInterval: WorkloadInterval) {
44
+ this.interval = newInterval;
45
+ await this.fetch();
46
+ }
47
+
48
+ async setGroupBy(newGroupBy: WorkloadGroupBy) {
49
+ this.groupBy = newGroupBy;
50
+ await this.fetch();
51
+ }
52
+
53
+ async setMetric(newMetric: WorkloadMetric) {
54
+ this.metric = newMetric;
55
+ await this.fetch();
56
+ }
57
+
58
+ async fetch(options?: WorkloadDistributionOptions) {
59
+ this.loading = true;
60
+ this.error = null;
61
+ try {
62
+ this.data = await this.client.getWorkloadDistribution(this.projectId, {
63
+ ...this.initialOptions,
64
+ interval: this.interval,
65
+ groupBy: this.groupBy,
66
+ metric: this.metric,
67
+ ...options
68
+ });
69
+ } catch (err) {
70
+ this.error = err instanceof Error ? err : new Error(String(err));
71
+ } finally {
72
+ this.loading = false;
73
+ }
74
+ }
75
+ }
76
+
77
+ export function createWorkloadState(
78
+ client: CriticalPathClient,
79
+ projectId?: string,
80
+ initialOptions?: WorkloadDistributionOptions
81
+ ): WorkloadState {
82
+ return new WorkloadState(client, projectId, initialOptions);
83
+ }