@flamingo-stack/openframe-frontend-core 0.0.679 → 0.0.680
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/{chunk-OV6YSKMP.cjs → chunk-2UTUZGMF.cjs} +17 -8
- package/dist/chunk-2UTUZGMF.cjs.map +1 -0
- package/dist/{chunk-25Y3UR3R.cjs → chunk-3YWZNB2S.cjs} +3 -3
- package/dist/{chunk-25Y3UR3R.cjs.map → chunk-3YWZNB2S.cjs.map} +1 -1
- package/dist/{chunk-UKZAW4FE.js → chunk-4DA64VEW.js} +3 -3
- package/dist/{chunk-HA3SBMR2.cjs → chunk-4NUKFUPW.cjs} +7 -7
- package/dist/{chunk-HA3SBMR2.cjs.map → chunk-4NUKFUPW.cjs.map} +1 -1
- package/dist/{chunk-TKL5JKZS.cjs → chunk-757VTCIH.cjs} +6 -6
- package/dist/{chunk-TKL5JKZS.cjs.map → chunk-757VTCIH.cjs.map} +1 -1
- package/dist/{chunk-HLMMG6Q7.js → chunk-75S3RKYX.js} +5 -5
- package/dist/{chunk-ICGGFQQU.js → chunk-7OZ5P7Z2.js} +4 -4
- package/dist/{chunk-CTF5IQQZ.js → chunk-BGHQW5D5.js} +17 -8
- package/dist/chunk-BGHQW5D5.js.map +1 -0
- package/dist/{chunk-CDMZOXS7.js → chunk-C5RRTWYW.js} +19 -13
- package/dist/{chunk-CDMZOXS7.js.map → chunk-C5RRTWYW.js.map} +1 -1
- package/dist/{chunk-EEL4RGHX.js → chunk-D5QSGV3I.js} +2 -2
- package/dist/{chunk-6LN7ZNHT.js → chunk-DIWJ6NBT.js} +2 -2
- package/dist/{chunk-IPZCEAAH.js → chunk-EWZPMPQP.js} +2 -2
- package/dist/{chunk-NQCID4VF.js → chunk-F5N2P2GD.js} +6 -6
- package/dist/{chunk-CY3XHQM3.cjs → chunk-F7EZBJTL.cjs} +3 -3
- package/dist/{chunk-CY3XHQM3.cjs.map → chunk-F7EZBJTL.cjs.map} +1 -1
- package/dist/{chunk-QU7SDFKO.cjs → chunk-FCGZIXI5.cjs} +55 -55
- package/dist/{chunk-QU7SDFKO.cjs.map → chunk-FCGZIXI5.cjs.map} +1 -1
- package/dist/{chunk-UAPSU6YW.cjs → chunk-FRXIDN6T.cjs} +3 -3
- package/dist/{chunk-UAPSU6YW.cjs.map → chunk-FRXIDN6T.cjs.map} +1 -1
- package/dist/{chunk-XSSYG7KU.js → chunk-JV7SNSYT.js} +5 -5
- package/dist/{chunk-HTNUTEXN.cjs → chunk-KQXKQ4PX.cjs} +17 -17
- package/dist/{chunk-HTNUTEXN.cjs.map → chunk-KQXKQ4PX.cjs.map} +1 -1
- package/dist/{chunk-3LW6AJMJ.cjs → chunk-KSDOCJ7B.cjs} +45 -45
- package/dist/{chunk-3LW6AJMJ.cjs.map → chunk-KSDOCJ7B.cjs.map} +1 -1
- package/dist/{chunk-KNPUHPIF.cjs → chunk-QFGPSBAX.cjs} +33 -33
- package/dist/{chunk-KNPUHPIF.cjs.map → chunk-QFGPSBAX.cjs.map} +1 -1
- package/dist/{chunk-ZB34GLOZ.cjs → chunk-RLUXVMSD.cjs} +40 -40
- package/dist/{chunk-ZB34GLOZ.cjs.map → chunk-RLUXVMSD.cjs.map} +1 -1
- package/dist/{chunk-GZIYE35W.cjs → chunk-RSDTLURW.cjs} +30 -24
- package/dist/chunk-RSDTLURW.cjs.map +1 -0
- package/dist/{chunk-RJYYGWJL.cjs → chunk-SJC2XUTE.cjs} +21 -21
- package/dist/{chunk-RJYYGWJL.cjs.map → chunk-SJC2XUTE.cjs.map} +1 -1
- package/dist/{chunk-L4NLXAET.js → chunk-SPI7DCD3.js} +2 -2
- package/dist/{chunk-4ZKZSWX4.js → chunk-ULOUTQBX.js} +3 -3
- package/dist/{chunk-2YMFBKYR.js → chunk-V4L2MYXY.js} +2 -2
- package/dist/{chunk-CGYFCEHM.cjs → chunk-VWYK6TSE.cjs} +10 -10
- package/dist/{chunk-CGYFCEHM.cjs.map → chunk-VWYK6TSE.cjs.map} +1 -1
- package/dist/{chunk-ZRN244JV.js → chunk-WTNFYZB6.js} +2 -2
- package/dist/{chunk-FUBOKAYO.js → chunk-YC7DIARJ.js} +4 -4
- package/dist/{chunk-7WGUMITJ.js → chunk-YDJBSKLB.js} +3 -3
- package/dist/{chunk-MGSWL3L3.cjs → chunk-YFU7BULM.cjs} +23 -23
- package/dist/{chunk-MGSWL3L3.cjs.map → chunk-YFU7BULM.cjs.map} +1 -1
- package/dist/{chunk-H27EHTLX.cjs → chunk-ZFQ3ZHMR.cjs} +3 -3
- package/dist/{chunk-H27EHTLX.cjs.map → chunk-ZFQ3ZHMR.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +13 -13
- package/dist/components/case-studies/index.js +3 -3
- package/dist/components/contact/index.cjs +4 -4
- package/dist/components/contact/index.js +3 -3
- package/dist/components/docs/index.cjs +7 -7
- package/dist/components/docs/index.js +6 -6
- package/dist/components/embeds/index.cjs +4 -4
- package/dist/components/embeds/index.js +3 -3
- package/dist/components/faq/index.cjs +4 -4
- package/dist/components/faq/index.js +3 -3
- package/dist/components/features/index.cjs +3 -3
- package/dist/components/features/index.js +2 -2
- package/dist/components/help-center-pages/index.cjs +52 -52
- package/dist/components/help-center-pages/index.js +14 -14
- package/dist/components/index.cjs +33 -33
- package/dist/components/index.js +13 -13
- package/dist/components/meeting-scheduler/index.cjs +28 -28
- package/dist/components/meeting-scheduler/index.js +2 -2
- package/dist/components/navigation/index.cjs +6 -6
- package/dist/components/navigation/index.js +5 -5
- package/dist/components/onboarding-guides/index.cjs +6 -6
- package/dist/components/onboarding-guides/index.js +5 -5
- package/dist/components/shared/onboarding/onboarding-walkthrough.d.ts +9 -1
- package/dist/components/shared/onboarding/onboarding-walkthrough.d.ts.map +1 -1
- package/dist/components/tickets/index.cjs +7 -7
- package/dist/components/tickets/index.js +6 -6
- package/dist/components/ui/index.cjs +3 -3
- package/dist/components/ui/index.js +2 -2
- package/dist/hooks/index.cjs +2 -2
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/ui/use-onboarding-state.d.ts +1 -0
- package/dist/hooks/ui/use-onboarding-state.d.ts.map +1 -1
- package/dist/index.cjs +7 -7
- package/dist/index.js +6 -6
- package/dist/utils/onboarding-storage.d.ts +5 -0
- package/dist/utils/onboarding-storage.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/shared/onboarding/onboarding-walkthrough.tsx +26 -10
- package/src/hooks/ui/use-onboarding-state.ts +8 -6
- package/src/utils/__tests__/onboarding-storage.test.ts +42 -0
- package/src/utils/onboarding-storage.ts +15 -1
- package/dist/chunk-CTF5IQQZ.js.map +0 -1
- package/dist/chunk-GZIYE35W.cjs.map +0 -1
- package/dist/chunk-OV6YSKMP.cjs.map +0 -1
- /package/dist/{chunk-UKZAW4FE.js.map → chunk-4DA64VEW.js.map} +0 -0
- /package/dist/{chunk-HLMMG6Q7.js.map → chunk-75S3RKYX.js.map} +0 -0
- /package/dist/{chunk-ICGGFQQU.js.map → chunk-7OZ5P7Z2.js.map} +0 -0
- /package/dist/{chunk-EEL4RGHX.js.map → chunk-D5QSGV3I.js.map} +0 -0
- /package/dist/{chunk-6LN7ZNHT.js.map → chunk-DIWJ6NBT.js.map} +0 -0
- /package/dist/{chunk-IPZCEAAH.js.map → chunk-EWZPMPQP.js.map} +0 -0
- /package/dist/{chunk-NQCID4VF.js.map → chunk-F5N2P2GD.js.map} +0 -0
- /package/dist/{chunk-XSSYG7KU.js.map → chunk-JV7SNSYT.js.map} +0 -0
- /package/dist/{chunk-L4NLXAET.js.map → chunk-SPI7DCD3.js.map} +0 -0
- /package/dist/{chunk-4ZKZSWX4.js.map → chunk-ULOUTQBX.js.map} +0 -0
- /package/dist/{chunk-2YMFBKYR.js.map → chunk-V4L2MYXY.js.map} +0 -0
- /package/dist/{chunk-ZRN244JV.js.map → chunk-WTNFYZB6.js.map} +0 -0
- /package/dist/{chunk-FUBOKAYO.js.map → chunk-YC7DIARJ.js.map} +0 -0
- /package/dist/{chunk-7WGUMITJ.js.map → chunk-YDJBSKLB.js.map} +0 -0
|
@@ -9,6 +9,14 @@ import { OnboardingStepCard } from './onboarding-step-card';
|
|
|
9
9
|
|
|
10
10
|
export interface OnboardingWalkthroughProps {
|
|
11
11
|
steps: OnboardingStepConfig[];
|
|
12
|
+
/** Heading above the steps. Defaults to "Get Started". */
|
|
13
|
+
title?: string;
|
|
14
|
+
/**
|
|
15
|
+
* What to render once the walkthrough is dismissed. Receives `restart`,
|
|
16
|
+
* which clears every completed/skipped step and shows the walkthrough
|
|
17
|
+
* again. Omitted: a dismissed walkthrough renders nothing.
|
|
18
|
+
*/
|
|
19
|
+
renderDismissed?: (restart: () => void) => React.ReactNode;
|
|
12
20
|
onDismiss?: () => void;
|
|
13
21
|
storageKey?: string;
|
|
14
22
|
className?: string;
|
|
@@ -26,6 +34,8 @@ export interface OnboardingWalkthroughProps {
|
|
|
26
34
|
|
|
27
35
|
export function OnboardingWalkthrough({
|
|
28
36
|
steps,
|
|
37
|
+
title = 'Get Started',
|
|
38
|
+
renderDismissed,
|
|
29
39
|
onDismiss,
|
|
30
40
|
storageKey = 'openframe-onboarding-state',
|
|
31
41
|
className,
|
|
@@ -38,6 +48,7 @@ export function OnboardingWalkthrough({
|
|
|
38
48
|
markComplete,
|
|
39
49
|
markSkipped,
|
|
40
50
|
dismissOnboarding,
|
|
51
|
+
resetOnboarding,
|
|
41
52
|
isStepComplete,
|
|
42
53
|
isStepSkipped,
|
|
43
54
|
allStepsComplete,
|
|
@@ -63,8 +74,11 @@ export function OnboardingWalkthrough({
|
|
|
63
74
|
// Skip if no completion status
|
|
64
75
|
if (!completionStatus) return;
|
|
65
76
|
|
|
66
|
-
// Create a stable key from completion status to detect actual changes
|
|
67
|
-
|
|
77
|
+
// Create a stable key from completion status to detect actual changes.
|
|
78
|
+
// An empty state (first load, or after a restart) is keyed by when it was
|
|
79
|
+
// written, so a restart applies the same status again.
|
|
80
|
+
const isFreshState = state.completedSteps.length === 0 && state.skippedSteps.length === 0;
|
|
81
|
+
const statusKey = `${JSON.stringify(completionStatus)}|${isFreshState ? state.lastUpdated : ''}`;
|
|
68
82
|
|
|
69
83
|
// Skip if we've already processed this exact status
|
|
70
84
|
if (lastCompletionStatusRef.current === statusKey) return;
|
|
@@ -86,7 +100,6 @@ export function OnboardingWalkthrough({
|
|
|
86
100
|
// Mark as in progress to prevent concurrent calls
|
|
87
101
|
autoMarkingInProgressRef.current = true;
|
|
88
102
|
|
|
89
|
-
console.log('📊 Auto-marking steps as complete from hook data:', completedStepIds);
|
|
90
103
|
markMultipleComplete(completedStepIds);
|
|
91
104
|
|
|
92
105
|
// Update the last processed status key
|
|
@@ -101,14 +114,20 @@ export function OnboardingWalkthrough({
|
|
|
101
114
|
// Even if no steps to mark, update the status key to prevent re-processing
|
|
102
115
|
lastCompletionStatusRef.current = statusKey;
|
|
103
116
|
}
|
|
104
|
-
}, [
|
|
117
|
+
}, [
|
|
118
|
+
completionStatus,
|
|
119
|
+
isLoadingCompletion,
|
|
120
|
+
state.completedSteps,
|
|
121
|
+
state.skippedSteps,
|
|
122
|
+
state.lastUpdated,
|
|
123
|
+
markMultipleComplete,
|
|
124
|
+
]);
|
|
105
125
|
|
|
106
126
|
// useCallback must be called unconditionally (before any early returns)
|
|
107
127
|
const handleStepAction = useCallback(
|
|
108
128
|
async (step: OnboardingStepConfig) => {
|
|
109
129
|
// Prevent duplicate action handling for the same step
|
|
110
130
|
if (actionInProgressRef.current.has(step.id)) {
|
|
111
|
-
console.log(`⏳ Action already in progress for "${step.id}", skipping`);
|
|
112
131
|
return;
|
|
113
132
|
}
|
|
114
133
|
|
|
@@ -120,13 +139,11 @@ export function OnboardingWalkthrough({
|
|
|
120
139
|
|
|
121
140
|
// Skip if already completed (could have been auto-marked while action was running)
|
|
122
141
|
if (state.completedSteps.includes(step.id)) {
|
|
123
|
-
console.log(`✓ Step "${step.id}" already completed, skipping mark`);
|
|
124
142
|
return;
|
|
125
143
|
}
|
|
126
144
|
|
|
127
145
|
// Auto-mark Knowledge Base as complete (no verification needed)
|
|
128
146
|
if (step.id === 'knowledge-base') {
|
|
129
|
-
console.log(`🎯 Auto-completing Knowledge Base`);
|
|
130
147
|
markComplete(step.id);
|
|
131
148
|
return;
|
|
132
149
|
}
|
|
@@ -135,7 +152,6 @@ export function OnboardingWalkthrough({
|
|
|
135
152
|
if (step.checkComplete) {
|
|
136
153
|
try {
|
|
137
154
|
const isComplete = await step.checkComplete();
|
|
138
|
-
console.log(`🔍 Post-action check for "${step.id}": isComplete=${isComplete}`);
|
|
139
155
|
|
|
140
156
|
// Double-check it wasn't completed while we were checking
|
|
141
157
|
if (isComplete && !state.completedSteps.includes(step.id)) {
|
|
@@ -166,7 +182,7 @@ export function OnboardingWalkthrough({
|
|
|
166
182
|
// ALL hooks have been called above - now safe to do conditional returns
|
|
167
183
|
// Don't render if dismissed
|
|
168
184
|
if (state.dismissed) {
|
|
169
|
-
return null;
|
|
185
|
+
return renderDismissed ? <>{renderDismissed(resetOnboarding)}</> : null;
|
|
170
186
|
}
|
|
171
187
|
|
|
172
188
|
const isAllComplete = allStepsComplete(steps);
|
|
@@ -175,7 +191,7 @@ export function OnboardingWalkthrough({
|
|
|
175
191
|
<div className={cn('w-full space-y-4', className)}>
|
|
176
192
|
{/* Header - responsive: stacks on mobile */}
|
|
177
193
|
<div className="flex flex-col items-start justify-between gap-3 md:flex-row md:items-center md:gap-0">
|
|
178
|
-
<h2 className={SECTION_HEADING_CLASS}>
|
|
194
|
+
<h2 className={SECTION_HEADING_CLASS}>{title}</h2>
|
|
179
195
|
|
|
180
196
|
{isLoadingCompletion ? (
|
|
181
197
|
<div className="h-[48px] w-full animate-pulse rounded-[6px] bg-ods-border md:w-[160px]" />
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
markStepSkipped as storageMarkSkipped,
|
|
9
9
|
dismissOnboarding as storageDismiss,
|
|
10
10
|
markMultipleComplete as storageMarkMultiple,
|
|
11
|
+
resetOnboardingState as storageReset,
|
|
11
12
|
type OnboardingState,
|
|
12
13
|
} from '../../utils/onboarding-storage';
|
|
13
14
|
import { readLocalStorageUpdateDetail } from '../../utils/storage-event';
|
|
@@ -42,7 +43,6 @@ export function useOnboardingState(storageKey: string = 'openframe-onboarding-st
|
|
|
42
43
|
const newState = loadOnboardingState(storageKey);
|
|
43
44
|
setState(newState);
|
|
44
45
|
forceUpdate(prev => prev + 1);
|
|
45
|
-
console.log('🔄 State updated from storage event:', newState);
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
window.addEventListener('localStorageUpdate', handleStorageUpdate);
|
|
@@ -53,7 +53,6 @@ export function useOnboardingState(storageKey: string = 'openframe-onboarding-st
|
|
|
53
53
|
|
|
54
54
|
const markComplete = useCallback(
|
|
55
55
|
(stepId: string) => {
|
|
56
|
-
console.log(`🎯 markComplete called for: "${stepId}"`);
|
|
57
56
|
const newState = storageMarkComplete(storageKey, stepId);
|
|
58
57
|
setState(newState);
|
|
59
58
|
forceUpdate(prev => prev + 1);
|
|
@@ -63,7 +62,6 @@ export function useOnboardingState(storageKey: string = 'openframe-onboarding-st
|
|
|
63
62
|
|
|
64
63
|
const markSkipped = useCallback(
|
|
65
64
|
(stepId: string) => {
|
|
66
|
-
console.log(`⏭️ markSkipped called for: "${stepId}"`);
|
|
67
65
|
const newState = storageMarkSkipped(storageKey, stepId);
|
|
68
66
|
setState(newState);
|
|
69
67
|
forceUpdate(prev => prev + 1);
|
|
@@ -72,19 +70,22 @@ export function useOnboardingState(storageKey: string = 'openframe-onboarding-st
|
|
|
72
70
|
);
|
|
73
71
|
|
|
74
72
|
const dismissOnboarding = useCallback(() => {
|
|
75
|
-
console.log(`🚫 dismissOnboarding called`);
|
|
76
73
|
const newState = storageDismiss(storageKey);
|
|
77
74
|
setState(newState);
|
|
78
75
|
forceUpdate(prev => prev + 1);
|
|
79
76
|
}, [storageKey]);
|
|
80
77
|
|
|
78
|
+
const resetOnboarding = useCallback(() => {
|
|
79
|
+
const newState = storageReset(storageKey);
|
|
80
|
+
setState(newState);
|
|
81
|
+
forceUpdate(prev => prev + 1);
|
|
82
|
+
}, [storageKey]);
|
|
83
|
+
|
|
81
84
|
const markMultipleComplete = useCallback(
|
|
82
85
|
(stepIds: string[]) => {
|
|
83
|
-
console.log(`🎯 markMultipleComplete called for:`, stepIds);
|
|
84
86
|
const newState = storageMarkMultiple(storageKey, stepIds);
|
|
85
87
|
setState(newState);
|
|
86
88
|
forceUpdate(prev => prev + 1);
|
|
87
|
-
console.log(`📝 State after batch:`, newState);
|
|
88
89
|
},
|
|
89
90
|
[storageKey],
|
|
90
91
|
);
|
|
@@ -115,6 +116,7 @@ export function useOnboardingState(storageKey: string = 'openframe-onboarding-st
|
|
|
115
116
|
markComplete,
|
|
116
117
|
markSkipped,
|
|
117
118
|
dismissOnboarding,
|
|
119
|
+
resetOnboarding,
|
|
118
120
|
isStepComplete,
|
|
119
121
|
isStepSkipped,
|
|
120
122
|
allStepsComplete,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The reset that brings a dismissed walkthrough back, round-tripped through
|
|
3
|
+
* real localStorage: reading the state back is the only proof the write stuck.
|
|
4
|
+
*/
|
|
5
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
6
|
+
import {
|
|
7
|
+
dismissOnboarding,
|
|
8
|
+
loadOnboardingState,
|
|
9
|
+
markStepComplete,
|
|
10
|
+
markStepSkipped,
|
|
11
|
+
resetOnboardingState,
|
|
12
|
+
} from '../onboarding-storage';
|
|
13
|
+
|
|
14
|
+
const KEY = 'onboarding-storage-test';
|
|
15
|
+
|
|
16
|
+
afterEach(() => localStorage.clear());
|
|
17
|
+
|
|
18
|
+
describe('resetOnboardingState', () => {
|
|
19
|
+
it('clears completed and skipped steps and undoes a dismissal', () => {
|
|
20
|
+
markStepComplete(KEY, 'install');
|
|
21
|
+
markStepSkipped(KEY, 'video');
|
|
22
|
+
dismissOnboarding(KEY);
|
|
23
|
+
expect(loadOnboardingState(KEY).dismissed).toBe(true);
|
|
24
|
+
|
|
25
|
+
const reset = resetOnboardingState(KEY);
|
|
26
|
+
|
|
27
|
+
expect(reset).toMatchObject({ completedSteps: [], skippedSteps: [], dismissed: false });
|
|
28
|
+
expect(loadOnboardingState(KEY)).toMatchObject({ completedSteps: [], skippedSteps: [], dismissed: false });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('tells other hook instances through the localStorageUpdate event', () => {
|
|
32
|
+
const seen: string[] = [];
|
|
33
|
+
const listener = (event: Event) => seen.push((event as CustomEvent<{ key: string }>).detail.key);
|
|
34
|
+
window.addEventListener('localStorageUpdate', listener);
|
|
35
|
+
try {
|
|
36
|
+
resetOnboardingState(KEY);
|
|
37
|
+
} finally {
|
|
38
|
+
window.removeEventListener('localStorageUpdate', listener);
|
|
39
|
+
}
|
|
40
|
+
expect(seen).toEqual([KEY]);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -26,7 +26,6 @@ export function saveOnboardingState(key: string, state: OnboardingState): void {
|
|
|
26
26
|
|
|
27
27
|
try {
|
|
28
28
|
localStorage.setItem(key, JSON.stringify(state));
|
|
29
|
-
console.log('💾 Saved onboarding state:', state);
|
|
30
29
|
|
|
31
30
|
// Dispatch custom event for cross-tab sync
|
|
32
31
|
if (typeof window !== 'undefined') {
|
|
@@ -110,6 +109,21 @@ export function dismissOnboarding(key: string): OnboardingState {
|
|
|
110
109
|
return newState;
|
|
111
110
|
}
|
|
112
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Reset the walkthrough to its initial state: no step completed or skipped,
|
|
114
|
+
* not dismissed. How a dismissed walkthrough is brought back.
|
|
115
|
+
*/
|
|
116
|
+
export function resetOnboardingState(key: string): OnboardingState {
|
|
117
|
+
const newState: OnboardingState = {
|
|
118
|
+
completedSteps: [],
|
|
119
|
+
skippedSteps: [],
|
|
120
|
+
dismissed: false,
|
|
121
|
+
lastUpdated: new Date().toISOString(),
|
|
122
|
+
};
|
|
123
|
+
saveOnboardingState(key, newState);
|
|
124
|
+
return newState;
|
|
125
|
+
}
|
|
126
|
+
|
|
113
127
|
/**
|
|
114
128
|
* Check if a step is complete
|
|
115
129
|
*/
|