@contractspec/example.learning-journey-registry 0.0.0-canary-20260113170453

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.
Files changed (63) hide show
  1. package/.turbo/turbo-build$colon$bundle.log +50 -0
  2. package/.turbo/turbo-build.log +51 -0
  3. package/CHANGELOG.md +612 -0
  4. package/LICENSE +21 -0
  5. package/README.md +25 -0
  6. package/dist/api-types.d.ts +41 -0
  7. package/dist/api-types.d.ts.map +1 -0
  8. package/dist/api-types.js +0 -0
  9. package/dist/api.d.ts +13 -0
  10. package/dist/api.d.ts.map +1 -0
  11. package/dist/api.js +171 -0
  12. package/dist/api.js.map +1 -0
  13. package/dist/docs/index.d.ts +1 -0
  14. package/dist/docs/index.js +1 -0
  15. package/dist/docs/learning-journey-registry.docblock.d.ts +1 -0
  16. package/dist/docs/learning-journey-registry.docblock.js +38 -0
  17. package/dist/docs/learning-journey-registry.docblock.js.map +1 -0
  18. package/dist/example.d.ts +7 -0
  19. package/dist/example.d.ts.map +1 -0
  20. package/dist/example.js +42 -0
  21. package/dist/example.js.map +1 -0
  22. package/dist/index.d.ts +8 -0
  23. package/dist/index.js +10 -0
  24. package/dist/learning-journey-registry.feature.d.ts +12 -0
  25. package/dist/learning-journey-registry.feature.d.ts.map +1 -0
  26. package/dist/learning-journey-registry.feature.js +75 -0
  27. package/dist/learning-journey-registry.feature.js.map +1 -0
  28. package/dist/presentations/index.d.ts +10 -0
  29. package/dist/presentations/index.d.ts.map +1 -0
  30. package/dist/presentations/index.js +71 -0
  31. package/dist/presentations/index.js.map +1 -0
  32. package/dist/progress-store.d.ts +11 -0
  33. package/dist/progress-store.d.ts.map +1 -0
  34. package/dist/progress-store.js +31 -0
  35. package/dist/progress-store.js.map +1 -0
  36. package/dist/tracks.d.ts +40 -0
  37. package/dist/tracks.d.ts.map +1 -0
  38. package/dist/tracks.js +48 -0
  39. package/dist/tracks.js.map +1 -0
  40. package/dist/ui/LearningMiniApp.d.ts +24 -0
  41. package/dist/ui/LearningMiniApp.d.ts.map +1 -0
  42. package/dist/ui/LearningMiniApp.js +80 -0
  43. package/dist/ui/LearningMiniApp.js.map +1 -0
  44. package/dist/ui/index.d.ts +2 -0
  45. package/dist/ui/index.js +3 -0
  46. package/example.ts +1 -0
  47. package/package.json +86 -0
  48. package/src/api-types.ts +43 -0
  49. package/src/api.test.ts +46 -0
  50. package/src/api.ts +301 -0
  51. package/src/docs/index.ts +1 -0
  52. package/src/docs/learning-journey-registry.docblock.ts +36 -0
  53. package/src/example.ts +31 -0
  54. package/src/index.ts +8 -0
  55. package/src/learning-journey-registry.feature.ts +64 -0
  56. package/src/presentations/index.ts +65 -0
  57. package/src/progress-store.ts +39 -0
  58. package/src/tracks.ts +91 -0
  59. package/src/ui/LearningMiniApp.tsx +121 -0
  60. package/src/ui/index.ts +5 -0
  61. package/tsconfig.json +9 -0
  62. package/tsconfig.tsbuildinfo +1 -0
  63. package/tsdown.config.js +17 -0
package/src/api.ts ADDED
@@ -0,0 +1,301 @@
1
+ import { learningJourneyTracks } from './tracks';
2
+ import type {
3
+ LearningJourneyTrackSpec,
4
+ StepAvailabilitySpec,
5
+ StepCompletionConditionSpec,
6
+ } from '@contractspec/module.learning-journey/track-spec';
7
+ import type { LearningEvent, StepProgress, TrackProgress } from './api-types';
8
+ import {
9
+ getLearnerTracks,
10
+ getTrackResolver,
11
+ initProgress,
12
+ } from './progress-store';
13
+
14
+ const getTrack = getTrackResolver(learningJourneyTracks);
15
+
16
+ const matchesFilter = (
17
+ filter: Record<string, unknown> | undefined,
18
+ payload: Record<string, unknown> | undefined
19
+ ): boolean => {
20
+ if (!filter) return true;
21
+ if (!payload) return false;
22
+ return Object.entries(filter).every(([key, value]) => payload[key] === value);
23
+ };
24
+
25
+ const matchesBaseEvent = (
26
+ condition: {
27
+ eventName: string;
28
+ eventVersion?: number;
29
+ sourceModule?: string;
30
+ payloadFilter?: Record<string, unknown>;
31
+ },
32
+ event: LearningEvent
33
+ ): boolean => {
34
+ if (condition.eventName !== event.name) return false;
35
+ if (condition.eventVersion !== undefined && event.version !== undefined) {
36
+ if (condition.eventVersion !== event.version) return false;
37
+ }
38
+ if (
39
+ condition.sourceModule &&
40
+ event.sourceModule &&
41
+ condition.sourceModule !== event.sourceModule
42
+ ) {
43
+ return false;
44
+ }
45
+ return matchesFilter(
46
+ condition.payloadFilter,
47
+ event.payload as Record<string, unknown> | undefined
48
+ );
49
+ };
50
+
51
+ const matchesCondition = (
52
+ condition: StepCompletionConditionSpec,
53
+ event: LearningEvent,
54
+ step: StepProgress,
55
+ trackStartedAt: Date | undefined
56
+ ): {
57
+ matched: boolean;
58
+ occurrences?: number;
59
+ masteryCount?: number;
60
+ } => {
61
+ if (condition.kind === 'count') {
62
+ if (!matchesBaseEvent(condition, event)) return { matched: false };
63
+ const occurrences = (step.occurrences ?? 0) + 1;
64
+ const within =
65
+ condition.withinHours === undefined ||
66
+ Boolean(
67
+ trackStartedAt &&
68
+ event.occurredAt &&
69
+ (event.occurredAt.getTime() - trackStartedAt.getTime()) /
70
+ (1000 * 60 * 60) <=
71
+ condition.withinHours
72
+ );
73
+ return { matched: within && occurrences >= condition.atLeast, occurrences };
74
+ }
75
+
76
+ if (condition.kind === 'time_window') {
77
+ if (!matchesBaseEvent(condition, event)) return { matched: false };
78
+ if (
79
+ condition.withinHoursOfStart !== undefined &&
80
+ trackStartedAt &&
81
+ event.occurredAt
82
+ ) {
83
+ const hoursSinceStart =
84
+ (event.occurredAt.getTime() - trackStartedAt.getTime()) /
85
+ (1000 * 60 * 60);
86
+ if (hoursSinceStart > condition.withinHoursOfStart) {
87
+ return { matched: false };
88
+ }
89
+ }
90
+ return { matched: true };
91
+ }
92
+
93
+ if (condition.kind === 'srs_mastery') {
94
+ if (event.name !== condition.eventName) return { matched: false };
95
+ const payload = event.payload as Record<string, unknown> | undefined;
96
+ if (!matchesFilter(condition.payloadFilter, payload)) {
97
+ return { matched: false };
98
+ }
99
+ const skillKey = condition.skillIdField ?? 'skillId';
100
+ const masteryKey = condition.masteryField ?? 'mastery';
101
+ const skillId = payload?.[skillKey];
102
+ const masteryValue = payload?.[masteryKey];
103
+ if (skillId === undefined || masteryValue === undefined) {
104
+ return { matched: false };
105
+ }
106
+ if (typeof masteryValue !== 'number') return { matched: false };
107
+ if (masteryValue < condition.minimumMastery) return { matched: false };
108
+ const masteryCount = (step.masteryCount ?? 0) + 1;
109
+ const required = condition.requiredCount ?? 1;
110
+ return { matched: masteryCount >= required, masteryCount };
111
+ }
112
+
113
+ return { matched: matchesBaseEvent(condition, event) };
114
+ };
115
+
116
+ const getAvailability = (
117
+ availability: StepAvailabilitySpec | undefined,
118
+ startedAt: Date | undefined
119
+ ): { availableAt?: Date; dueAt?: Date } => {
120
+ if (!availability || !startedAt) return {};
121
+
122
+ const baseTime = startedAt.getTime();
123
+ let unlockTime = baseTime;
124
+
125
+ if (availability.unlockOnDay !== undefined) {
126
+ unlockTime =
127
+ baseTime + (availability.unlockOnDay - 1) * 24 * 60 * 60 * 1000;
128
+ }
129
+
130
+ if (availability.unlockAfterHours !== undefined) {
131
+ unlockTime = baseTime + availability.unlockAfterHours * 60 * 60 * 1000;
132
+ }
133
+
134
+ const availableAt = new Date(unlockTime);
135
+ const dueAt =
136
+ availability.dueWithinHours !== undefined
137
+ ? new Date(
138
+ availableAt.getTime() + availability.dueWithinHours * 60 * 60 * 1000
139
+ )
140
+ : undefined;
141
+
142
+ return { availableAt, dueAt };
143
+ };
144
+
145
+ const computeProgressPercent = (steps: StepProgress[]): number => {
146
+ const total = steps.length || 1;
147
+ const done = steps.filter((s) => s.status === 'COMPLETED').length;
148
+ return Math.round((done / total) * 100);
149
+ };
150
+
151
+ const applyTrackCompletionBonuses = (
152
+ track: LearningJourneyTrackSpec,
153
+ progress: TrackProgress
154
+ ) => {
155
+ if (progress.isCompleted) return progress;
156
+
157
+ const completedAt = new Date();
158
+ const startedAt = progress.startedAt ?? completedAt;
159
+ const hoursElapsed =
160
+ (completedAt.getTime() - startedAt.getTime()) / (1000 * 60 * 60);
161
+
162
+ let xpEarned = progress.xpEarned;
163
+ const { completionRewards, streakRule } = track;
164
+
165
+ if (completionRewards?.xpBonus) {
166
+ xpEarned += completionRewards.xpBonus;
167
+ }
168
+
169
+ if (
170
+ streakRule?.hoursWindow !== undefined &&
171
+ hoursElapsed <= streakRule.hoursWindow &&
172
+ streakRule.bonusXp
173
+ ) {
174
+ xpEarned += streakRule.bonusXp;
175
+ }
176
+
177
+ return {
178
+ ...progress,
179
+ xpEarned,
180
+ isCompleted: true,
181
+ completedAt,
182
+ lastActivityAt: completedAt,
183
+ };
184
+ };
185
+
186
+ export const listTracks = (learnerId?: string) => {
187
+ const progressMap = learnerId ? getLearnerTracks(learnerId) : undefined;
188
+ const progress =
189
+ learnerId && progressMap
190
+ ? Array.from(progressMap.values())
191
+ : ([] as TrackProgress[]);
192
+
193
+ return {
194
+ tracks: learningJourneyTracks,
195
+ progress,
196
+ };
197
+ };
198
+
199
+ export const getProgress = (trackId: string, learnerId: string) => {
200
+ const track = getTrack(trackId);
201
+ if (!track) return undefined;
202
+
203
+ const map = getLearnerTracks(learnerId);
204
+ const existing = map.get(trackId) ?? initProgress(learnerId, track);
205
+ map.set(trackId, existing);
206
+ return existing;
207
+ };
208
+
209
+ export const recordEvent = (event: LearningEvent) => {
210
+ const targets =
211
+ event.trackId !== undefined
212
+ ? learningJourneyTracks.filter((t) => t.id === event.trackId)
213
+ : learningJourneyTracks;
214
+
215
+ const updated: TrackProgress[] = [];
216
+ const eventTime = event.occurredAt ?? new Date();
217
+
218
+ for (const track of targets) {
219
+ const map = getLearnerTracks(event.learnerId);
220
+ const current = map.get(track.id) ?? initProgress(event.learnerId, track);
221
+ const startedAt = current.startedAt ?? eventTime;
222
+
223
+ let changed = current.startedAt === undefined;
224
+ const steps: StepProgress[] = current.steps.map((step) => {
225
+ if (step.status === 'COMPLETED') return step;
226
+
227
+ const spec = track.steps.find((s) => s.id === step.id);
228
+ if (!spec) return step;
229
+
230
+ const { availableAt, dueAt } = getAvailability(
231
+ spec.availability,
232
+ startedAt
233
+ );
234
+ if (availableAt && eventTime < availableAt) {
235
+ return { ...step, availableAt, dueAt };
236
+ }
237
+ if (dueAt && eventTime > dueAt) {
238
+ // keep pending but note deadlines
239
+ return { ...step, availableAt, dueAt };
240
+ }
241
+
242
+ const result = matchesCondition(spec.completion, event, step, startedAt);
243
+
244
+ if (result.matched) {
245
+ changed = true;
246
+ return {
247
+ ...step,
248
+ status: 'COMPLETED',
249
+ xpEarned: spec.xpReward ?? 0,
250
+ completedAt: eventTime,
251
+ triggeringEvent: event.name,
252
+ eventPayload: event.payload,
253
+ occurrences: result.occurrences ?? step.occurrences,
254
+ masteryCount: result.masteryCount ?? step.masteryCount,
255
+ availableAt,
256
+ dueAt,
257
+ };
258
+ }
259
+
260
+ if (
261
+ result.occurrences !== undefined ||
262
+ result.masteryCount !== undefined
263
+ ) {
264
+ changed = true;
265
+ }
266
+
267
+ return {
268
+ ...step,
269
+ occurrences: result.occurrences ?? step.occurrences,
270
+ masteryCount: result.masteryCount ?? step.masteryCount,
271
+ availableAt,
272
+ dueAt,
273
+ };
274
+ });
275
+
276
+ if (!changed) {
277
+ continue;
278
+ }
279
+
280
+ const xpEarned =
281
+ steps.reduce((sum, s) => sum + s.xpEarned, 0) + (track.totalXp ?? 0);
282
+ let progress: TrackProgress = {
283
+ ...current,
284
+ steps,
285
+ xpEarned,
286
+ startedAt,
287
+ lastActivityAt: eventTime,
288
+ progress: computeProgressPercent(steps),
289
+ };
290
+
291
+ const allDone = steps.every((s) => s.status === 'COMPLETED');
292
+ if (allDone) {
293
+ progress = applyTrackCompletionBonuses(track, progress);
294
+ }
295
+
296
+ map.set(track.id, progress);
297
+ updated.push(progress);
298
+ }
299
+
300
+ return updated;
301
+ };
@@ -0,0 +1 @@
1
+ import './learning-journey-registry.docblock';
@@ -0,0 +1,36 @@
1
+ import type { DocBlock } from '@contractspec/lib.contracts/docs';
2
+ import { registerDocBlocks } from '@contractspec/lib.contracts/docs';
3
+
4
+ const registryDocBlocks: DocBlock[] = [
5
+ {
6
+ id: 'docs.learning-journey.registry',
7
+ title: 'Learning Journey — Example Track Registry',
8
+ summary:
9
+ 'Aggregates learning journey example tracks (Studio onboarding, Platform tour, CRM first win, Drills, Ambient Coach, Quest challenges).',
10
+ kind: 'usage',
11
+ visibility: 'public',
12
+ route: '/docs/learning-journey/registry',
13
+ tags: ['learning', 'registry', 'onboarding'],
14
+ body: `## Tracks
15
+ - \`studio_getting_started\` (Studio onboarding)
16
+ - \`platform_primitives_tour\` (Platform primitives)
17
+ - \`crm_first_win\` (CRM pipeline onboarding)
18
+ - \`drills_language_basics\` (Drills & SRS)
19
+ - \`money_ambient_coach\`, \`coliving_ambient_coach\` (Ambient tips)
20
+ - \`money_reset_7day\` (Quest/challenge)
21
+
22
+ ## Exports
23
+ - \`learningJourneyTracks\` — raw specs
24
+ - \`onboardingTrackCatalog\` — DTOs aligned with onboarding API
25
+ - \`mapTrackSpecToDto\` — helper to map individual tracks
26
+
27
+ ## Wiring
28
+ - Use with onboarding API contracts:
29
+ - \`learning.onboarding.listTracks\`
30
+ - \`learning.onboarding.getProgress\`
31
+ - \`learning.onboarding.recordEvent\`
32
+ - Intended for registry/adapters in Studio UI or services that surface tracks.`,
33
+ },
34
+ ];
35
+
36
+ registerDocBlocks(registryDocBlocks);
package/src/example.ts ADDED
@@ -0,0 +1,31 @@
1
+ import { defineExample } from '@contractspec/lib.contracts';
2
+
3
+ const example = defineExample({
4
+ meta: {
5
+ key: 'learning-journey-registry',
6
+ version: '1.0.0',
7
+ title: 'Learning Journey Registry',
8
+ description:
9
+ 'Registry of learning journey tracks + presentations + UI mini-app bindings.',
10
+ kind: 'library',
11
+ visibility: 'public',
12
+ stability: 'experimental',
13
+ owners: ['@platform.core'],
14
+ tags: ['learning', 'journey', 'registry'],
15
+ },
16
+ docs: {
17
+ rootDocId: 'docs.examples.learning-journey-registry',
18
+ },
19
+ entrypoints: {
20
+ packageName: '@contractspec/example.learning-journey-registry',
21
+ docs: './docs',
22
+ },
23
+ surfaces: {
24
+ templates: true,
25
+ sandbox: { enabled: true, modes: ['markdown', 'specs'] },
26
+ studio: { enabled: true, installable: true },
27
+ mcp: { enabled: true },
28
+ },
29
+ });
30
+
31
+ export default example;
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ import './docs';
2
+
3
+ export * from './tracks';
4
+ export * from './api';
5
+ export * from './presentations';
6
+ export * from './learning-journey-registry.feature';
7
+ export * from './ui';
8
+ export { default as example } from './example';
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Learning Journey Registry Feature Module Specification
3
+ *
4
+ * Defines the feature module for the learning journey registry.
5
+ */
6
+ import { defineFeature } from '@contractspec/lib.contracts';
7
+
8
+ /**
9
+ * Learning Journey Registry feature module that bundles
10
+ * the shared presentations for learning journey tracks.
11
+ */
12
+ export const LearningJourneyRegistryFeature = defineFeature({
13
+ meta: {
14
+ key: 'learning-journey-registry',
15
+ version: '1.0.0',
16
+ title: 'Learning Journey Registry',
17
+ description:
18
+ 'Shared registry and presentations for learning journey tracks',
19
+ domain: 'learning-journey',
20
+ owners: ['@learning-team'],
21
+ tags: ['learning', 'journey', 'onboarding', 'registry'],
22
+ stability: 'experimental',
23
+ },
24
+
25
+ // No operations in the registry - it's presentation-only
26
+ operations: [],
27
+
28
+ // Events emitted by this feature
29
+ events: [],
30
+
31
+ // Presentations associated with this feature
32
+ presentations: [
33
+ { key: 'learning.journey.track_list', version: '1.0.0' },
34
+ { key: 'learning.journey.track_detail', version: '1.0.0' },
35
+ { key: 'learning.journey.progress_widget', version: '1.0.0' },
36
+ ],
37
+
38
+ // No op to presentation links for registry
39
+ opToPresentation: [],
40
+
41
+ // Target requirements for multi-surface rendering
42
+ presentationsTargets: [
43
+ {
44
+ key: 'learning.journey.track_list',
45
+ version: '1.0.0',
46
+ targets: ['react', 'markdown'],
47
+ },
48
+ {
49
+ key: 'learning.journey.track_detail',
50
+ version: '1.0.0',
51
+ targets: ['react', 'markdown', 'application/json'],
52
+ },
53
+ {
54
+ key: 'learning.journey.progress_widget',
55
+ version: '1.0.0',
56
+ targets: ['react'],
57
+ },
58
+ ],
59
+
60
+ // Capability requirements
61
+ capabilities: {
62
+ requires: [{ key: 'identity', version: '1.0.0' }],
63
+ },
64
+ });
@@ -0,0 +1,65 @@
1
+ import { definePresentation, StabilityEnum, type PresentationSpecMeta } from '@contractspec/lib.contracts';
2
+
3
+ const baseMeta: Pick<
4
+ PresentationSpecMeta,
5
+ 'domain' | 'owners' | 'tags' | 'title' | 'stability' | 'goal' | 'context'
6
+ > = {
7
+ domain: 'learning-journey',
8
+ title: 'Learning Journey',
9
+ owners: ['@learning-team'] as string[],
10
+ tags: ['learning', 'journey', 'onboarding'] as string[],
11
+ stability: StabilityEnum.Experimental,
12
+ goal: 'Progress through learning tracks',
13
+ context: 'Learning journey section',
14
+ };
15
+
16
+ export const LearningTrackListPresentation = definePresentation({
17
+ meta: {
18
+ key: 'learning.journey.track_list',
19
+ version: '1.0.0',
20
+ description: 'List of learning journeys available to the learner.',
21
+ ...baseMeta,
22
+ },
23
+ source: {
24
+ type: 'component',
25
+ framework: 'react',
26
+ componentKey: 'LearningTrackList',
27
+ },
28
+ targets: ['react', 'markdown'],
29
+ });
30
+
31
+ export const LearningTrackDetailPresentation = definePresentation({
32
+ meta: {
33
+ key: 'learning.journey.track_detail',
34
+ version: '1.0.0',
35
+ description: 'Track detail with steps and progress state.',
36
+ ...baseMeta,
37
+ },
38
+ source: {
39
+ type: 'component',
40
+ framework: 'react',
41
+ componentKey: 'LearningTrackDetail',
42
+ },
43
+ targets: ['react', 'markdown', 'application/json'],
44
+ });
45
+
46
+ export const LearningTrackProgressWidgetPresentation = definePresentation({
47
+ meta: {
48
+ key: 'learning.journey.progress_widget',
49
+ version: '1.0.0',
50
+ description: 'Compact widget showing progress for active track.',
51
+ ...baseMeta,
52
+ },
53
+ source: {
54
+ type: 'component',
55
+ framework: 'react',
56
+ componentKey: 'LearningTrackProgressWidget',
57
+ },
58
+ targets: ['react'],
59
+ });
60
+
61
+ export const learningJourneyPresentations = [
62
+ LearningTrackListPresentation,
63
+ LearningTrackDetailPresentation,
64
+ LearningTrackProgressWidgetPresentation,
65
+ ];
@@ -0,0 +1,39 @@
1
+ import type { LearningJourneyTrackSpec } from '@contractspec/module.learning-journey/track-spec';
2
+
3
+ import type { TrackProgress } from './api-types';
4
+
5
+ export const progressStore = new Map<string, Map<string, TrackProgress>>();
6
+
7
+ export const getTrackResolver =
8
+ (tracks: LearningJourneyTrackSpec[]) =>
9
+ (trackId: string): LearningJourneyTrackSpec | undefined =>
10
+ tracks.find((t) => t.id === trackId);
11
+
12
+ export const getLearnerTracks = (learnerId: string) => {
13
+ const existing = progressStore.get(learnerId);
14
+ if (existing) return existing;
15
+ const map = new Map<string, TrackProgress>();
16
+ progressStore.set(learnerId, map);
17
+ return map;
18
+ };
19
+
20
+ export const initProgress = (
21
+ learnerId: string,
22
+ track: LearningJourneyTrackSpec
23
+ ): TrackProgress => ({
24
+ learnerId,
25
+ trackId: track.id,
26
+ progress: 0,
27
+ isCompleted: false,
28
+ xpEarned: 0,
29
+ steps: track.steps.map((step) => ({
30
+ id: step.id,
31
+ status: 'PENDING',
32
+ xpEarned: 0,
33
+ occurrences: 0,
34
+ masteryCount: 0,
35
+ })),
36
+ startedAt: undefined,
37
+ completedAt: undefined,
38
+ lastActivityAt: undefined,
39
+ });
package/src/tracks.ts ADDED
@@ -0,0 +1,91 @@
1
+ import type {
2
+ LearningJourneyStepSpec,
3
+ LearningJourneyTrackSpec,
4
+ StepAvailabilitySpec,
5
+ StepCompletionConditionSpec,
6
+ } from '@contractspec/module.learning-journey/track-spec';
7
+ import { crmLearningTracks } from '@contractspec/example.learning-journey-crm-onboarding/track';
8
+ import { drillTracks } from '@contractspec/example.learning-journey-duo-drills/track';
9
+ import { ambientCoachTracks } from '@contractspec/example.learning-journey-ambient-coach/track';
10
+ import { questTracks } from '@contractspec/example.learning-journey-quest-challenges/track';
11
+ import { platformLearningTracks } from '@contractspec/example.learning-journey-platform-tour/track';
12
+ import { studioLearningTracks } from '@contractspec/example.learning-journey-studio-onboarding/track';
13
+
14
+ export interface OnboardingStepDto {
15
+ id: string;
16
+ title: string;
17
+ description?: string;
18
+ completionEvent: string;
19
+ completionCondition?: StepCompletionConditionSpec;
20
+ xpReward?: number;
21
+ isRequired?: boolean;
22
+ canSkip?: boolean;
23
+ actionUrl?: string;
24
+ actionLabel?: string;
25
+ availability?: StepAvailabilitySpec;
26
+ metadata?: Record<string, unknown>;
27
+ }
28
+
29
+ export interface OnboardingTrackDto {
30
+ id: string;
31
+ name: string;
32
+ description?: string;
33
+ productId?: string;
34
+ targetUserSegment?: string;
35
+ targetRole?: string;
36
+ totalXp?: number;
37
+ streakRule?: LearningJourneyTrackSpec['streakRule'];
38
+ completionRewards?: LearningJourneyTrackSpec['completionRewards'];
39
+ steps: OnboardingStepDto[];
40
+ metadata?: Record<string, unknown>;
41
+ }
42
+
43
+ const mapStep = (step: LearningJourneyStepSpec): OnboardingStepDto => ({
44
+ id: step.id,
45
+ title: step.title,
46
+ description: step.description,
47
+ completionEvent: step.completion.eventName,
48
+ completionCondition: step.completion,
49
+ xpReward: step.xpReward,
50
+ isRequired: step.isRequired,
51
+ canSkip: step.canSkip,
52
+ actionUrl: step.actionUrl,
53
+ actionLabel: step.actionLabel,
54
+ availability: step.availability,
55
+ metadata: step.metadata,
56
+ });
57
+
58
+ export const mapTrackSpecToDto = (
59
+ track: LearningJourneyTrackSpec
60
+ ): OnboardingTrackDto => ({
61
+ id: track.id,
62
+ name: track.name,
63
+ description: track.description,
64
+ productId: track.productId,
65
+ targetUserSegment: track.targetUserSegment,
66
+ targetRole: track.targetRole,
67
+ totalXp: track.totalXp,
68
+ streakRule: track.streakRule,
69
+ completionRewards: track.completionRewards,
70
+ steps: track.steps.map(mapStep),
71
+ metadata: track.metadata,
72
+ });
73
+
74
+ export const learningJourneyTracks: LearningJourneyTrackSpec[] = [
75
+ ...studioLearningTracks,
76
+ ...platformLearningTracks,
77
+ ...crmLearningTracks,
78
+ ...drillTracks,
79
+ ...ambientCoachTracks,
80
+ ...questTracks,
81
+ ];
82
+
83
+ export const onboardingTrackCatalog: OnboardingTrackDto[] =
84
+ learningJourneyTracks.map(mapTrackSpecToDto);
85
+
86
+ export {
87
+ studioLearningTracks,
88
+ platformLearningTracks,
89
+ crmLearningTracks,
90
+ mapStep,
91
+ };