@startsimpli/funnels 0.4.6 → 0.4.8

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 (46) hide show
  1. package/package.json +1 -1
  2. package/src/api/client.test.ts +76 -76
  3. package/src/api/client.ts +16 -16
  4. package/src/components/FilterRuleEditor/FilterRuleEditor.stories.tsx +24 -24
  5. package/src/components/FilterRuleEditor/FilterRuleEditor.test.tsx +39 -39
  6. package/src/components/FilterRuleEditor/FilterRuleEditor.tsx +1 -1
  7. package/src/components/FilterRuleEditor/RuleRow.tsx +8 -8
  8. package/src/components/FunnelCard/FunnelCard.stories.tsx +118 -118
  9. package/src/components/FunnelCard/FunnelCard.test.ts +48 -48
  10. package/src/components/FunnelCard/FunnelCard.test.tsx +40 -40
  11. package/src/components/FunnelCard/FunnelCard.tsx +3 -3
  12. package/src/components/FunnelPreview/FunnelPreview.test.tsx +63 -63
  13. package/src/components/FunnelPreview/FunnelPreview.tsx +11 -11
  14. package/src/components/FunnelPreview/StageBreakdown.tsx +5 -5
  15. package/src/components/FunnelPreview/example.tsx +43 -43
  16. package/src/components/FunnelRunHistory/FunnelRunHistory.stories.tsx +70 -70
  17. package/src/components/FunnelRunHistory/FunnelRunHistory.test.tsx +63 -63
  18. package/src/components/FunnelRunHistory/FunnelRunHistory.tsx +12 -12
  19. package/src/components/FunnelRunHistory/RunDetailsModal.tsx +5 -5
  20. package/src/components/FunnelRunHistory/RunFilters.tsx +8 -8
  21. package/src/components/FunnelRunHistory/RunRow.tsx +7 -7
  22. package/src/components/FunnelRunHistory/StageBreakdownList.tsx +9 -9
  23. package/src/components/FunnelRunHistory/types.ts +6 -6
  24. package/src/components/FunnelStageBuilder/FunnelStageBuilder.stories.tsx +41 -41
  25. package/src/components/FunnelStageBuilder/FunnelStageBuilder.test.tsx +6 -6
  26. package/src/components/FunnelStageBuilder/FunnelStageBuilder.tsx +5 -5
  27. package/src/components/FunnelStageBuilder/StageActions.test.tsx +7 -7
  28. package/src/components/FunnelStageBuilder/StageActions.tsx +4 -4
  29. package/src/components/FunnelStageBuilder/StageCard.tsx +3 -3
  30. package/src/components/FunnelStageBuilder/StageForm.tsx +14 -14
  31. package/src/components/FunnelVisualFlow/FunnelVisualFlow.stories.tsx +42 -42
  32. package/src/components/FunnelVisualFlow/FunnelVisualFlow.test.tsx +31 -31
  33. package/src/components/FunnelVisualFlow/FunnelVisualFlow.tsx +4 -4
  34. package/src/components/FunnelVisualFlow/StageNode.tsx +8 -8
  35. package/src/components/FunnelVisualFlow/example.tsx +7 -7
  36. package/src/core/engine.test.ts +271 -271
  37. package/src/core/engine.ts +60 -60
  38. package/src/core/evaluator.example.ts +31 -31
  39. package/src/core/evaluator.test.ts +92 -92
  40. package/src/core/evaluator.ts +23 -23
  41. package/src/core/field-resolver.example.ts +1 -1
  42. package/src/core/field-resolver.test.ts +4 -4
  43. package/src/store/create-funnel-store.test.ts +32 -32
  44. package/src/store/create-funnel-store.ts +3 -3
  45. package/src/store/types.ts +2 -2
  46. package/src/types/index.ts +64 -64
@@ -43,7 +43,7 @@ export function RunRow({
43
43
  }: RunRowProps) {
44
44
  const matchRate =
45
45
  run.status === 'completed'
46
- ? calculateMatchRate(run.total_matched, run.total_input)
46
+ ? calculateMatchRate(run.totalMatched, run.totalInput)
47
47
  : null;
48
48
 
49
49
  return (
@@ -61,9 +61,9 @@ export function RunRow({
61
61
  <td className="px-4 py-3">
62
62
  <span
63
63
  className="text-sm text-gray-900"
64
- title={formatFullTimestamp(run.started_at)}
64
+ title={formatFullTimestamp(run.startedAt)}
65
65
  >
66
- {formatRelativeTime(run.started_at)}
66
+ {formatRelativeTime(run.startedAt)}
67
67
  </span>
68
68
  </td>
69
69
 
@@ -75,28 +75,28 @@ export function RunRow({
75
75
  {/* Trigger */}
76
76
  <td className="px-4 py-3">
77
77
  <span className="text-sm text-gray-700 capitalize">
78
- {run.trigger_type}
78
+ {run.triggerType}
79
79
  </span>
80
80
  </td>
81
81
 
82
82
  {/* Duration */}
83
83
  <td className="px-4 py-3">
84
84
  <span className="text-sm text-gray-900">
85
- {formatDuration(run.duration_ms)}
85
+ {formatDuration(run.durationMs)}
86
86
  </span>
87
87
  </td>
88
88
 
89
89
  {/* Input */}
90
90
  <td className="px-4 py-3 text-right">
91
91
  <span className="text-sm font-medium text-gray-900">
92
- {formatNumber(run.total_input)}
92
+ {formatNumber(run.totalInput)}
93
93
  </span>
94
94
  </td>
95
95
 
96
96
  {/* Matched */}
97
97
  <td className="px-4 py-3 text-right">
98
98
  <span className="text-sm font-medium text-green-600">
99
- {run.status === 'completed' ? formatNumber(run.total_matched) : '-'}
99
+ {run.status === 'completed' ? formatNumber(run.totalMatched) : '-'}
100
100
  </span>
101
101
  </td>
102
102
 
@@ -30,15 +30,15 @@ export function StageBreakdownList({
30
30
  return (
31
31
  <div className={`space-y-3 ${className}`}>
32
32
  {stages.map((stage, index) => {
33
- const delta = stage.matched_count - stage.input_count;
33
+ const delta = stage.matchedCount - stage.inputCount;
34
34
  const matchRate =
35
- stage.input_count > 0
36
- ? Math.round((stage.matched_count / stage.input_count) * 100)
35
+ stage.inputCount > 0
36
+ ? Math.round((stage.matchedCount / stage.inputCount) * 100)
37
37
  : 0;
38
38
 
39
39
  return (
40
40
  <div
41
- key={stage.stage_id}
41
+ key={stage.stageId}
42
42
  className="p-3 bg-gray-50 rounded-lg border border-gray-200"
43
43
  >
44
44
  {/* Stage Header */}
@@ -47,7 +47,7 @@ export function StageBreakdownList({
47
47
  {index + 1}
48
48
  </span>
49
49
  <h4 className="text-sm font-semibold text-gray-900">
50
- {stage.stage_name}
50
+ {stage.stageName}
51
51
  </h4>
52
52
  </div>
53
53
 
@@ -57,7 +57,7 @@ export function StageBreakdownList({
57
57
  <div>
58
58
  <div className="text-xs text-gray-600 mb-1">Input</div>
59
59
  <div className="text-lg font-bold text-blue-600">
60
- {formatNumber(stage.input_count)}
60
+ {formatNumber(stage.inputCount)}
61
61
  </div>
62
62
  </div>
63
63
 
@@ -65,7 +65,7 @@ export function StageBreakdownList({
65
65
  <div>
66
66
  <div className="text-xs text-gray-600 mb-1">Matched</div>
67
67
  <div className="text-lg font-bold text-green-600">
68
- {formatNumber(stage.matched_count)}
68
+ {formatNumber(stage.matchedCount)}
69
69
  </div>
70
70
  </div>
71
71
 
@@ -95,10 +95,10 @@ export function StageBreakdownList({
95
95
  )}
96
96
 
97
97
  {/* Error Count */}
98
- {stage.error_count && stage.error_count > 0 && (
98
+ {stage.errorCount && stage.errorCount > 0 && (
99
99
  <div className="mt-2 pt-2 border-t border-gray-200">
100
100
  <div className="text-sm text-red-600 text-center">
101
- ⚠ {formatNumber(stage.error_count)} errors
101
+ ⚠ {formatNumber(stage.errorCount)} errors
102
102
  </div>
103
103
  </div>
104
104
  )}
@@ -11,17 +11,17 @@ import { FunnelRun, FunnelRunStatus, TriggerType } from '../../types';
11
11
  */
12
12
  export interface RunFilters {
13
13
  status?: FunnelRunStatus | 'all';
14
- trigger_type?: TriggerType | 'all';
15
- date_range?: 'all' | 'today' | 'week' | 'month' | 'custom';
16
- start_date?: Date;
17
- end_date?: Date;
14
+ triggerType?: TriggerType | 'all';
15
+ dateRange?: 'all' | 'today' | 'week' | 'month' | 'custom';
16
+ startDate?: Date;
17
+ endDate?: Date;
18
18
  }
19
19
 
20
20
  /**
21
21
  * Sort configuration for run history
22
22
  */
23
23
  export interface RunSort {
24
- field: 'started_at' | 'status' | 'match_rate' | 'duration_ms';
24
+ field: 'startedAt' | 'status' | 'matchRate' | 'durationMs';
25
25
  direction: 'asc' | 'desc';
26
26
  }
27
27
 
@@ -30,7 +30,7 @@ export interface RunSort {
30
30
  */
31
31
  export interface Pagination {
32
32
  page: number;
33
- page_size: number;
33
+ pageSize: number;
34
34
  total: number;
35
35
  }
36
36
 
@@ -95,10 +95,10 @@ function createEmptyFunnel(): Funnel {
95
95
  name: 'VC Outreach Funnel',
96
96
  description: 'Filter and prioritize venture capital firms for outreach',
97
97
  status: 'draft',
98
- input_type: 'both',
98
+ inputType: 'both',
99
99
  stages: [],
100
- created_at: new Date().toISOString(),
101
- updated_at: new Date().toISOString(),
100
+ createdAt: new Date().toISOString(),
101
+ updatedAt: new Date().toISOString(),
102
102
  };
103
103
  }
104
104
 
@@ -109,55 +109,55 @@ function createSampleFunnel(): Funnel {
109
109
  name: 'VC Outreach Funnel',
110
110
  description: 'Filter and prioritize venture capital firms for outreach',
111
111
  status: 'draft',
112
- input_type: 'both',
112
+ inputType: 'both',
113
113
  stages: [
114
114
  {
115
115
  id: 'stage-1',
116
116
  order: 0,
117
117
  name: 'High ICP Score',
118
118
  description: 'Firms with strong ideal customer profile match',
119
- filter_logic: 'AND',
119
+ filterLogic: 'AND',
120
120
  rules: [
121
121
  {
122
- field_path: 'firm.stage',
122
+ fieldPath: 'firm.stage',
123
123
  operator: 'in',
124
124
  value: ['Seed', 'Series A'],
125
125
  },
126
126
  {
127
- field_path: 'firm.check_size_min',
127
+ fieldPath: 'firm.check_size_min',
128
128
  operator: 'lte',
129
129
  value: 500000,
130
130
  },
131
131
  ],
132
- match_action: 'tag_continue',
133
- no_match_action: 'continue',
134
- match_tags: ['high-priority', 'icp-match'],
132
+ matchAction: 'tag_continue',
133
+ noMatchAction: 'continue',
134
+ matchTags: ['high-priority', 'icp-match'],
135
135
  },
136
136
  {
137
137
  id: 'stage-2',
138
138
  order: 1,
139
139
  name: 'Has Contact Info',
140
140
  description: 'Ensure we have investor email or LinkedIn',
141
- filter_logic: 'OR',
141
+ filterLogic: 'OR',
142
142
  rules: [
143
143
  {
144
- field_path: 'investor.email',
144
+ fieldPath: 'investor.email',
145
145
  operator: 'isnotnull',
146
146
  value: null,
147
147
  },
148
148
  {
149
- field_path: 'investor.linkedin_url',
149
+ fieldPath: 'investor.linkedin_url',
150
150
  operator: 'isnotnull',
151
151
  value: null,
152
152
  },
153
153
  ],
154
- match_action: 'output',
155
- no_match_action: 'exclude',
156
- no_match_tags: ['no-contact-info'],
154
+ matchAction: 'output',
155
+ noMatchAction: 'exclude',
156
+ noMatchTags: ['no-contact-info'],
157
157
  },
158
158
  ],
159
- created_at: new Date().toISOString(),
160
- updated_at: new Date().toISOString(),
159
+ createdAt: new Date().toISOString(),
160
+ updatedAt: new Date().toISOString(),
161
161
  };
162
162
  }
163
163
 
@@ -216,10 +216,10 @@ export const SingleStage: Story = {
216
216
  order: 0,
217
217
  name: 'Stage 1',
218
218
  description: '',
219
- filter_logic: 'AND',
219
+ filterLogic: 'AND',
220
220
  rules: [],
221
- match_action: 'continue',
222
- no_match_action: 'continue',
221
+ matchAction: 'continue',
222
+ noMatchAction: 'continue',
223
223
  },
224
224
  ];
225
225
  return <InteractiveFunnelStageBuilder initialFunnel={funnel} />;
@@ -235,10 +235,10 @@ export const MultipleStages: Story = {
235
235
  order: i,
236
236
  name: `Stage ${i + 1}`,
237
237
  description: `Description for stage ${i + 1}`,
238
- filter_logic: 'AND' as const,
238
+ filterLogic: 'AND' as const,
239
239
  rules: [],
240
- match_action: 'continue' as const,
241
- no_match_action: 'continue' as const,
240
+ matchAction: 'continue' as const,
241
+ noMatchAction: 'continue' as const,
242
242
  }));
243
243
  return <InteractiveFunnelStageBuilder initialFunnel={funnel} />;
244
244
  },
@@ -253,19 +253,19 @@ export const WithValidationErrors: Story = {
253
253
  id: 'stage-1',
254
254
  order: 0,
255
255
  name: 'Duplicate Name',
256
- filter_logic: 'AND',
256
+ filterLogic: 'AND',
257
257
  rules: [],
258
- match_action: 'continue',
259
- no_match_action: 'continue',
258
+ matchAction: 'continue',
259
+ noMatchAction: 'continue',
260
260
  },
261
261
  {
262
262
  id: 'stage-2',
263
263
  order: 1,
264
264
  name: 'Duplicate Name', // Duplicate!
265
- filter_logic: 'AND',
265
+ filterLogic: 'AND',
266
266
  rules: [],
267
- match_action: 'continue',
268
- no_match_action: 'continue',
267
+ matchAction: 'continue',
268
+ noMatchAction: 'continue',
269
269
  },
270
270
  ];
271
271
  return <InteractiveFunnelStageBuilder initialFunnel={funnel} />;
@@ -281,30 +281,30 @@ export const WithDifferentActions: Story = {
281
281
  id: 'stage-1',
282
282
  order: 0,
283
283
  name: 'Tag & Continue',
284
- filter_logic: 'AND',
284
+ filterLogic: 'AND',
285
285
  rules: [],
286
- match_action: 'tag_continue',
287
- no_match_action: 'continue',
288
- match_tags: ['qualified', 'high-priority'],
286
+ matchAction: 'tag_continue',
287
+ noMatchAction: 'continue',
288
+ matchTags: ['qualified', 'high-priority'],
289
289
  },
290
290
  {
291
291
  id: 'stage-2',
292
292
  order: 1,
293
293
  name: 'Output',
294
- filter_logic: 'OR',
294
+ filterLogic: 'OR',
295
295
  rules: [],
296
- match_action: 'output',
297
- no_match_action: 'exclude',
296
+ matchAction: 'output',
297
+ noMatchAction: 'exclude',
298
298
  },
299
299
  {
300
300
  id: 'stage-3',
301
301
  order: 2,
302
302
  name: 'Tag & Exclude',
303
- filter_logic: 'AND',
303
+ filterLogic: 'AND',
304
304
  rules: [],
305
- match_action: 'continue',
306
- no_match_action: 'tag_exclude',
307
- no_match_tags: ['rejected', 'low-priority'],
305
+ matchAction: 'continue',
306
+ noMatchAction: 'tag_exclude',
307
+ noMatchTags: ['rejected', 'low-priority'],
308
308
  },
309
309
  ];
310
310
  return <InteractiveFunnelStageBuilder initialFunnel={funnel} />;
@@ -32,19 +32,19 @@ function createTestFunnel(stageCount: number = 2): Funnel {
32
32
  name: 'Test Funnel',
33
33
  description: 'Test funnel',
34
34
  status: 'draft',
35
- input_type: 'contacts',
35
+ inputType: 'contacts',
36
36
  stages: Array.from({ length: stageCount }, (_, i) => ({
37
37
  id: `stage-${i}`,
38
38
  order: i,
39
39
  name: `Stage ${i + 1}`,
40
40
  description: '',
41
- filter_logic: 'AND' as const,
41
+ filterLogic: 'AND' as const,
42
42
  rules: [],
43
- match_action: 'continue' as const,
44
- no_match_action: 'continue' as const,
43
+ matchAction: 'continue' as const,
44
+ noMatchAction: 'continue' as const,
45
45
  })),
46
- created_at: new Date().toISOString(),
47
- updated_at: new Date().toISOString(),
46
+ createdAt: new Date().toISOString(),
47
+ updatedAt: new Date().toISOString(),
48
48
  };
49
49
  }
50
50
 
@@ -59,12 +59,12 @@ function createEmptyStage(order: number): FunnelStage {
59
59
  order,
60
60
  name: `Stage ${order + 1}`,
61
61
  description: '',
62
- filter_logic: 'AND',
62
+ filterLogic: 'AND',
63
63
  rules: [],
64
- match_action: 'continue',
65
- no_match_action: 'continue',
66
- match_tags: [],
67
- no_match_tags: [],
64
+ matchAction: 'continue',
65
+ noMatchAction: 'continue',
66
+ matchTags: [],
67
+ noMatchTags: [],
68
68
  };
69
69
  }
70
70
 
@@ -13,10 +13,10 @@ function createTestStage(): FunnelStage {
13
13
  id: 'stage-1',
14
14
  order: 0,
15
15
  name: 'Test Stage',
16
- filter_logic: 'AND',
16
+ filterLogic: 'AND',
17
17
  rules: [],
18
- match_action: 'continue',
19
- no_match_action: 'continue',
18
+ matchAction: 'continue',
19
+ noMatchAction: 'continue',
20
20
  };
21
21
  }
22
22
 
@@ -131,7 +131,7 @@ describe('StageActions', () => {
131
131
 
132
132
  it('displays current match action', () => {
133
133
  const stage = createTestStage();
134
- stage.match_action = 'tag_continue';
134
+ stage.matchAction = 'tag_continue';
135
135
 
136
136
  const onMatchActionChange = vi.fn();
137
137
  const onNoMatchActionChange = vi.fn();
@@ -150,7 +150,7 @@ describe('StageActions', () => {
150
150
 
151
151
  it('displays current no match action', () => {
152
152
  const stage = createTestStage();
153
- stage.no_match_action = 'tag_exclude';
153
+ stage.noMatchAction = 'tag_exclude';
154
154
 
155
155
  const onMatchActionChange = vi.fn();
156
156
  const onNoMatchActionChange = vi.fn();
@@ -169,7 +169,7 @@ describe('StageActions', () => {
169
169
 
170
170
  it('shows description for match action', () => {
171
171
  const stage = createTestStage();
172
- stage.match_action = 'output';
172
+ stage.matchAction = 'output';
173
173
 
174
174
  const onMatchActionChange = vi.fn();
175
175
  const onNoMatchActionChange = vi.fn();
@@ -187,7 +187,7 @@ describe('StageActions', () => {
187
187
 
188
188
  it('shows description for no match action', () => {
189
189
  const stage = createTestStage();
190
- stage.no_match_action = 'exclude';
190
+ stage.noMatchAction = 'exclude';
191
191
 
192
192
  const onMatchActionChange = vi.fn();
193
193
  const onNoMatchActionChange = vi.fn();
@@ -85,7 +85,7 @@ export function StageActions({
85
85
  </label>
86
86
  <select
87
87
  id={`match-action-${stage.id}`}
88
- value={stage.match_action}
88
+ value={stage.matchAction}
89
89
  onChange={(e) => onMatchActionChange(e.target.value as MatchAction)}
90
90
  className="form-select"
91
91
  >
@@ -96,7 +96,7 @@ export function StageActions({
96
96
  ))}
97
97
  </select>
98
98
  <p className="form-hint">
99
- {MATCH_ACTIONS.find((a) => a.value === stage.match_action)?.description}
99
+ {MATCH_ACTIONS.find((a) => a.value === stage.matchAction)?.description}
100
100
  </p>
101
101
  </div>
102
102
 
@@ -107,7 +107,7 @@ export function StageActions({
107
107
  </label>
108
108
  <select
109
109
  id={`no-match-action-${stage.id}`}
110
- value={stage.no_match_action}
110
+ value={stage.noMatchAction}
111
111
  onChange={(e) => onNoMatchActionChange(e.target.value as NoMatchAction)}
112
112
  className="form-select"
113
113
  >
@@ -118,7 +118,7 @@ export function StageActions({
118
118
  ))}
119
119
  </select>
120
120
  <p className="form-hint">
121
- {NO_MATCH_ACTIONS.find((a) => a.value === stage.no_match_action)?.description}
121
+ {NO_MATCH_ACTIONS.find((a) => a.value === stage.noMatchAction)?.description}
122
122
  </p>
123
123
  </div>
124
124
  </div>
@@ -174,15 +174,15 @@ export function StageCard({
174
174
  </div>
175
175
  <div className="summary-item">
176
176
  <span className="summary-label">Logic:</span>
177
- <span className="summary-value">{stage.filter_logic}</span>
177
+ <span className="summary-value">{stage.filterLogic}</span>
178
178
  </div>
179
179
  <div className="summary-item">
180
180
  <span className="summary-label">On Match:</span>
181
- <span className="summary-value">{stage.match_action}</span>
181
+ <span className="summary-value">{stage.matchAction}</span>
182
182
  </div>
183
183
  <div className="summary-item">
184
184
  <span className="summary-label">On No Match:</span>
185
- <span className="summary-value">{stage.no_match_action}</span>
185
+ <span className="summary-value">{stage.noMatchAction}</span>
186
186
  </div>
187
187
  </div>
188
188
  )}
@@ -95,35 +95,35 @@ export function StageForm({
95
95
  * Handle filter logic change
96
96
  */
97
97
  const handleFilterLogicChange = useCallback((logic: FilterLogic) => {
98
- onUpdate({ ...stage, filter_logic: logic });
98
+ onUpdate({ ...stage, filterLogic: logic });
99
99
  }, [stage, onUpdate]);
100
100
 
101
101
  /**
102
102
  * Handle match action change
103
103
  */
104
104
  const handleMatchActionChange = useCallback((action: MatchAction) => {
105
- onUpdate({ ...stage, match_action: action });
105
+ onUpdate({ ...stage, matchAction: action });
106
106
  }, [stage, onUpdate]);
107
107
 
108
108
  /**
109
109
  * Handle no match action change
110
110
  */
111
111
  const handleNoMatchActionChange = useCallback((action: NoMatchAction) => {
112
- onUpdate({ ...stage, no_match_action: action });
112
+ onUpdate({ ...stage, noMatchAction: action });
113
113
  }, [stage, onUpdate]);
114
114
 
115
115
  /**
116
116
  * Handle match tags change
117
117
  */
118
118
  const handleMatchTagsChange = useCallback((tags: string[]) => {
119
- onUpdate({ ...stage, match_tags: tags });
119
+ onUpdate({ ...stage, matchTags: tags });
120
120
  }, [stage, onUpdate]);
121
121
 
122
122
  /**
123
123
  * Handle no match tags change
124
124
  */
125
125
  const handleNoMatchTagsChange = useCallback((tags: string[]) => {
126
- onUpdate({ ...stage, no_match_tags: tags });
126
+ onUpdate({ ...stage, noMatchTags: tags });
127
127
  }, [stage, onUpdate]);
128
128
 
129
129
  /**
@@ -173,13 +173,13 @@ export function StageForm({
173
173
  <button
174
174
  type="button"
175
175
  onClick={() => handleFilterLogicChange('AND')}
176
- className={`toggle-button ${stage.filter_logic === 'AND' ? 'active' : ''}`}
176
+ className={`toggle-button ${stage.filterLogic === 'AND' ? 'active' : ''}`}
177
177
  >
178
178
  <input
179
179
  type="radio"
180
180
  name={`filter-logic-${stage.id}`}
181
181
  value="AND"
182
- checked={stage.filter_logic === 'AND'}
182
+ checked={stage.filterLogic === 'AND'}
183
183
  onChange={() => handleFilterLogicChange('AND')}
184
184
  className="sr-only"
185
185
  />
@@ -188,13 +188,13 @@ export function StageForm({
188
188
  <button
189
189
  type="button"
190
190
  onClick={() => handleFilterLogicChange('OR')}
191
- className={`toggle-button ${stage.filter_logic === 'OR' ? 'active' : ''}`}
191
+ className={`toggle-button ${stage.filterLogic === 'OR' ? 'active' : ''}`}
192
192
  >
193
193
  <input
194
194
  type="radio"
195
195
  name={`filter-logic-${stage.id}`}
196
196
  value="OR"
197
- checked={stage.filter_logic === 'OR'}
197
+ checked={stage.filterLogic === 'OR'}
198
198
  onChange={() => handleFilterLogicChange('OR')}
199
199
  className="sr-only"
200
200
  />
@@ -202,7 +202,7 @@ export function StageForm({
202
202
  </button>
203
203
  </div>
204
204
  <p className="form-hint">
205
- {stage.filter_logic === 'AND'
205
+ {stage.filterLogic === 'AND'
206
206
  ? 'All rules must match for this stage to pass'
207
207
  : 'At least one rule must match for this stage to pass'}
208
208
  </p>
@@ -216,11 +216,11 @@ export function StageForm({
216
216
  />
217
217
 
218
218
  {/* Match tags */}
219
- {(stage.match_action === 'tag' || stage.match_action === 'tag_continue') && (
219
+ {(stage.matchAction === 'tag' || stage.matchAction === 'tag_continue') && (
220
220
  <div className="form-group">
221
221
  <label className="form-label">Tags on Match</label>
222
222
  <TagInput
223
- tags={stage.match_tags || []}
223
+ tags={stage.matchTags || []}
224
224
  onChange={handleMatchTagsChange}
225
225
  placeholder="Add tag..."
226
226
  />
@@ -231,11 +231,11 @@ export function StageForm({
231
231
  )}
232
232
 
233
233
  {/* No match tags */}
234
- {stage.no_match_action === 'tag_exclude' && (
234
+ {stage.noMatchAction === 'tag_exclude' && (
235
235
  <div className="form-group">
236
236
  <label className="form-label">Tags on No Match</label>
237
237
  <TagInput
238
- tags={stage.no_match_tags || []}
238
+ tags={stage.noMatchTags || []}
239
239
  onChange={handleNoMatchTagsChange}
240
240
  placeholder="Add tag..."
241
241
  />