@zeniai/client-epic-state 5.2.32 → 5.2.34-beta0ND

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 (86) hide show
  1. package/lib/commonPayloadTypes/classesView/reportByClassPayloadV2.d.ts +13 -1
  2. package/lib/entity/notificationRegistry/notificationRegistryReducer.d.ts +2 -1
  3. package/lib/entity/notificationRegistry/notificationRegistryReducer.js +20 -9
  4. package/lib/entity/notificationRegistry/notificationRegistrySelector.d.ts +2 -1
  5. package/lib/entity/notificationRegistry/notificationRegistrySelector.js +7 -1
  6. package/lib/entity/notificationRegistry/notificationRegistryState.d.ts +5 -0
  7. package/lib/entity/notificationRegistry/notificationRegistryState.js +7 -1
  8. package/lib/entity/sectionClassesViewV2/sectionClassesViewEpic.js +10 -7
  9. package/lib/entity/sectionClassesViewV2/sectionClassesViewReducer.d.ts +3 -3
  10. package/lib/entity/sectionProjectView/sectionProjectViewEpic.js +6 -2
  11. package/lib/entity/sectionProjectView/sectionProjectViewReducer.d.ts +3 -3
  12. package/lib/entity/sectionProjectView/sectionProjectViewReducer.js +6 -3
  13. package/lib/epic.js +12 -0
  14. package/lib/esm/entity/class/classReducer.js +1 -1
  15. package/lib/esm/entity/notificationRegistry/notificationRegistryReducer.js +20 -9
  16. package/lib/esm/entity/notificationRegistry/notificationRegistrySelector.js +5 -0
  17. package/lib/esm/entity/notificationRegistry/notificationRegistryState.js +5 -0
  18. package/lib/esm/entity/sectionClassesViewV2/sectionClassesViewEpic.js +10 -7
  19. package/lib/esm/entity/sectionProjectView/sectionProjectViewEpic.js +6 -2
  20. package/lib/esm/entity/sectionProjectView/sectionProjectViewReducer.js +7 -4
  21. package/lib/esm/epic.js +12 -0
  22. package/lib/esm/index.js +6 -6
  23. package/lib/esm/view/aiCfoView/aiCfoViewReducer.js +268 -1
  24. package/lib/esm/view/aiCfoView/aiCfoViewSelector.js +50 -0
  25. package/lib/esm/view/aiCfoView/epics/deleteRoutineEpic.js +35 -0
  26. package/lib/esm/view/aiCfoView/epics/fetchRoutineRunsEpic.js +37 -0
  27. package/lib/esm/view/aiCfoView/epics/fetchRoutinesEpic.js +37 -0
  28. package/lib/esm/view/aiCfoView/epics/runRoutineEpic.js +34 -0
  29. package/lib/esm/view/aiCfoView/epics/saveRoutineEpic.js +65 -0
  30. package/lib/esm/view/aiCfoView/epics/setRoutineEnabledEpic.js +28 -0
  31. package/lib/esm/view/aiCfoView/epics/toRoutine.js +64 -0
  32. package/lib/esm/view/aiCfoView/epics/toRoutineRun.js +42 -0
  33. package/lib/esm/view/expenseAutomationView/selectors/fluxAnalysisViewSelector.js +12 -10
  34. package/lib/esm/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +3 -11
  35. package/lib/esm/view/expenseAutomationView/selectors/missingReceiptsSelector.js +10 -11
  36. package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewPayload.js +4 -0
  37. package/lib/esm/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +20 -7
  38. package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewReducer.js +43 -17
  39. package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewSelector.js +24 -4
  40. package/lib/esm/view/settingsView/epic/fetchNotificationSettingsEpic.js +12 -2
  41. package/lib/index.d.ts +8 -8
  42. package/lib/index.js +63 -41
  43. package/lib/view/aiCfoView/aiCfoViewPayload.d.ts +90 -0
  44. package/lib/view/aiCfoView/aiCfoViewReducer.d.ts +164 -123
  45. package/lib/view/aiCfoView/aiCfoViewReducer.js +270 -3
  46. package/lib/view/aiCfoView/aiCfoViewSelector.d.ts +13 -1
  47. package/lib/view/aiCfoView/aiCfoViewSelector.js +55 -0
  48. package/lib/view/aiCfoView/aiCfoViewState.d.ts +157 -0
  49. package/lib/view/aiCfoView/epics/deleteRoutineEpic.d.ts +18 -0
  50. package/lib/view/aiCfoView/epics/deleteRoutineEpic.js +39 -0
  51. package/lib/view/aiCfoView/epics/fetchRoutineRunsEpic.d.ts +13 -0
  52. package/lib/view/aiCfoView/epics/fetchRoutineRunsEpic.js +41 -0
  53. package/lib/view/aiCfoView/epics/fetchRoutinesEpic.d.ts +7 -0
  54. package/lib/view/aiCfoView/epics/fetchRoutinesEpic.js +41 -0
  55. package/lib/view/aiCfoView/epics/runRoutineEpic.d.ts +14 -0
  56. package/lib/view/aiCfoView/epics/runRoutineEpic.js +38 -0
  57. package/lib/view/aiCfoView/epics/saveRoutineEpic.d.ts +13 -0
  58. package/lib/view/aiCfoView/epics/saveRoutineEpic.js +69 -0
  59. package/lib/view/aiCfoView/epics/setRoutineEnabledEpic.d.ts +15 -0
  60. package/lib/view/aiCfoView/epics/setRoutineEnabledEpic.js +32 -0
  61. package/lib/view/aiCfoView/epics/toRoutine.d.ts +17 -0
  62. package/lib/view/aiCfoView/epics/toRoutine.js +69 -0
  63. package/lib/view/aiCfoView/epics/toRoutineRun.d.ts +12 -0
  64. package/lib/view/aiCfoView/epics/toRoutineRun.js +47 -0
  65. package/lib/view/cashFlowClassesView/cashFlowClassesViewPayload.d.ts +4 -4
  66. package/lib/view/cashFlowProjectView/cashFlowProjectViewPayload.d.ts +4 -4
  67. package/lib/view/expenseAutomationView/selectors/fluxAnalysisViewSelector.js +12 -10
  68. package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +3 -11
  69. package/lib/view/expenseAutomationView/selectors/missingReceiptsSelector.js +10 -11
  70. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewPayload.d.ts +1 -11
  71. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewPayload.js +4 -0
  72. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.d.ts +1 -2
  73. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.d.ts +1 -2
  74. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewState.d.ts +15 -1
  75. package/lib/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewReducer.d.ts +1 -1
  76. package/lib/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewState.d.ts +1 -1
  77. package/lib/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +19 -6
  78. package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.d.ts +8 -5
  79. package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.js +43 -17
  80. package/lib/view/notificationPreferencesView/notificationPreferencesViewSelector.d.ts +4 -0
  81. package/lib/view/notificationPreferencesView/notificationPreferencesViewSelector.js +30 -5
  82. package/lib/view/notificationPreferencesView/notificationPreferencesViewState.d.ts +4 -3
  83. package/lib/view/profitAndLossProjectView/profitAndLossProjectViewPayload.d.ts +12 -0
  84. package/lib/view/settingsView/epic/fetchNotificationSettingsEpic.js +12 -2
  85. package/lib/view/settingsView/settingsViewPayload.d.ts +2 -0
  86. package/package.json +1 -1
@@ -0,0 +1,28 @@
1
+ import { of } from 'rxjs';
2
+ import { catchError, filter, mergeMap } from 'rxjs/operators';
3
+ import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
4
+ import { fetchRoutines, saveRoutineFailure, saveRoutineSuccess, setRoutineEnabled, } from '../aiCfoViewReducer';
5
+ /**
6
+ * PUT /1.0/routines/{scheduleId} with nothing but `enabled`.
7
+ *
8
+ * Separate from `saveRoutineEpic` because pausing is not editing. A routine
9
+ * whose cadence this client version does not recognise has `recurrence: null`
10
+ * and cannot produce a `RoutineDraft` — and that is precisely the routine
11
+ * someone most needs to be able to stop. Sending only `enabled` also means a
12
+ * pause can never rewrite a schedule as a side effect.
13
+ */
14
+ export const setRoutineEnabledEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(setRoutineEnabled.match),
15
+ // mergeMap: pausing one routine must not cancel pausing another, and these
16
+ // target different rows so they cannot collide the way two saves of one
17
+ // form would.
18
+ mergeMap((action) => {
19
+ const { enabled, scheduleId } = action.payload;
20
+ return zeniAPI
21
+ .putAndGetJSON(`${zeniAPI.apiEndPoints.aiCfoMicroServiceBaseUrl}/1.0/routines/${encodeURIComponent(scheduleId)}`, { enabled })
22
+ .pipe(mergeMap((response) => isSuccessResponse(response)
23
+ ? of(saveRoutineSuccess(), fetchRoutines())
24
+ : // Refetch on failure too: the switch was flipped optimistically,
25
+ // and leaving it showing "paused" for a routine that is still
26
+ // running would be the one lie this list must not tell.
27
+ of(saveRoutineFailure(response.status), fetchRoutines())), catchError((error) => of(saveRoutineFailure(createZeniAPIStatus('Unexpected Error', 'Set routine enabled REST API call failed', error instanceof Error ? { message: error.message } : undefined)), fetchRoutines())));
28
+ }));
@@ -0,0 +1,64 @@
1
+ const RECURRENCES = ['daily', 'weekly', 'monthly'];
2
+ const STATUSES = ['succeeded', 'failed', 'timeout'];
3
+ /**
4
+ * A routine is usable if it has an identity. That is the whole bar.
5
+ *
6
+ * `schedule_id` and `name` are what every consumer dereferences, so those are
7
+ * what this establishes.
8
+ *
9
+ * Deliberately NOT also requiring `macro_id`. A routine whose skill was deleted
10
+ * can come back without one — and it is still an active schedule emailing the
11
+ * user's financials. Dropping it here would make it invisible in the UI while
12
+ * it kept running, with no way for anyone to turn it off. Showing a broken row
13
+ * that can be paused or deleted is strictly better than hiding a live one.
14
+ */
15
+ export const isRoutinePayload = (value) => {
16
+ const candidate = value;
17
+ return (typeof candidate?.schedule_id === 'string' &&
18
+ candidate.schedule_id !== '' &&
19
+ typeof candidate.name === 'string');
20
+ };
21
+ /**
22
+ * Narrow a server string to a known recurrence, or null.
23
+ *
24
+ * Null, NOT a fallback to 'daily'. A read-side default becomes a write-side
25
+ * value the moment the user opens that routine and saves it — so guessing here
26
+ * would quietly convert a cadence this client does not understand into a daily
27
+ * one, and the owner would find out from the volume of email. Null says "this
28
+ * client does not know", and the UI shows the row so it can still be paused or
29
+ * deleted while refusing to edit its schedule.
30
+ */
31
+ const toRecurrence = (value) => RECURRENCES.includes(value)
32
+ ? value
33
+ : null;
34
+ const toStatus = (value) => STATUSES.includes(value) ? value : null;
35
+ /** Map one routine off the wire. Every default here is a deliberate reading. */
36
+ export const toRoutine = (payload) => ({
37
+ unreadRuns: payload.unread_runs ?? 0,
38
+ consecutiveFailures: payload.consecutive_failures ?? 0,
39
+ dayOfMonth: payload.day_of_month ?? null,
40
+ // Absent reads as enabled: a routine the server returned without the flag is
41
+ // one it intends to run, and defaulting to false would show every row paused.
42
+ enabled: payload.enabled !== false,
43
+ hour: payload.hour ?? 0,
44
+ isAdHoc: payload.is_ad_hoc === true,
45
+ lastRunAt: payload.last_run_at ?? null,
46
+ lastStatus: toStatus(payload.last_status),
47
+ // '' is the sanctioned "unset ID" (CLAUDE.md), not a papered-over absence:
48
+ // the row still renders, and the UI reads this to show it as broken rather
49
+ // than offering to open a skill that is not there.
50
+ macroId: payload.macro_id ?? '',
51
+ minute: payload.minute ?? 0,
52
+ name: payload.name,
53
+ nextRunAt: payload.next_run_at ?? null,
54
+ pinnedVersion: payload.pinned_version ?? null,
55
+ recurrence: toRecurrence(payload.recurrence),
56
+ scheduleId: payload.schedule_id,
57
+ sessionId: payload.session_id ?? null,
58
+ // Null, not a guessed 'UTC'. Same trap as `recurrence`: a read-side default
59
+ // is written straight back on the next save, silently moving the routine's
60
+ // delivery by however many hours the real zone differed by. The server always
61
+ // sends this, so null means something is wrong and the UI should say so.
62
+ tzName: payload.tz_name ?? null,
63
+ weekday: payload.weekday ?? null,
64
+ });
@@ -0,0 +1,42 @@
1
+ const STATUSES = [
2
+ 'claimed',
3
+ 'running',
4
+ 'succeeded',
5
+ 'failed',
6
+ 'timeout',
7
+ ];
8
+ /**
9
+ * A run is usable if it has an identity — the same bar `isRoutinePayload` sets.
10
+ *
11
+ * Nothing else is required. A run that is still in flight has no `finished_at`,
12
+ * a claimed one has no `started_at`, and a failed one may have neither an error
13
+ * nor an answer to link to. Requiring any of those would drop exactly the rows
14
+ * the history exists to show.
15
+ */
16
+ export const isRoutineRunPayload = (value) => {
17
+ const candidate = value;
18
+ return typeof candidate?.run_id === 'string' && candidate.run_id !== '';
19
+ };
20
+ /**
21
+ * Narrow a server status to one this client knows, or null.
22
+ *
23
+ * Null rather than a fallback, for the same reason `toRecurrence` returns null:
24
+ * a guess here would badge an unknown state as something specific, and on a run
25
+ * history the difference between "failed" and "we don't know" is the whole
26
+ * message. The UI shows an unknown status as neither succeeded nor failed.
27
+ */
28
+ const toStatus = (value) => STATUSES.find((status) => status === value) ?? null;
29
+ /** Empty strings become null: the server sends '' for an absent deep link. */
30
+ const toId = (value) => value != null && value !== '' ? value : null;
31
+ export const toRoutineRun = (payload) => ({
32
+ runId: payload.run_id,
33
+ attempt: payload.attempt ?? 1,
34
+ isManual: payload.is_manual === true,
35
+ error: toId(payload.error),
36
+ finishedAt: toId(payload.finished_at),
37
+ questionAnswerId: toId(payload.question_answer_id),
38
+ sessionId: toId(payload.session_id),
39
+ scheduledFor: toId(payload.scheduled_for),
40
+ startedAt: toId(payload.started_at),
41
+ status: toStatus(payload.status),
42
+ });
@@ -1,4 +1,3 @@
1
- import { reduceAnyFetchState } from '../../../commonStateTypes/reduceFetchState';
2
1
  import { toMonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
3
2
  import { getAccountBase } from '../../../entity/account/accountSelector';
4
3
  import { getClassById } from '../../../entity/class/classSelector';
@@ -7,7 +6,7 @@ import { getUserByUserId } from '../../../entity/user/userSelector';
7
6
  import { getVendorByVendorId } from '../../../entity/vendor/vendorSelector';
8
7
  import { getAllSteps } from './getAllStepsHelper';
9
8
  export function getExpenseAutomationFluxAnalysisView(state) {
10
- const { expenseAutomationFluxAnalysisViewState, monthEndCloseChecksState, monthEndCloseChecksViewState, } = state;
9
+ const { expenseAutomationFluxAnalysisViewState, monthEndCloseChecksState } = state;
11
10
  const { fetchState, error, lastRefreshSuccessTime, refreshStatus, uiState, reviewOperatingExpenseIdsByPeriod, selectedSectionIdsByPeriod, groupsBySelectedPeriod, fluxAnalysisEntities, bulkReviewStatus, currency, totalBalancesAndVariance, } = expenseAutomationFluxAnalysisViewState;
12
11
  const fetchStateAndError = {
13
12
  fetchState,
@@ -92,17 +91,20 @@ export function getExpenseAutomationFluxAnalysisView(state) {
92
91
  },
93
92
  };
94
93
  const completionStatus = fluxCompletionStatus != null ? fluxCompletionStatus : 'incomplete';
95
- const monthEndFetchState = monthYearPeriodId != null
96
- ? (monthEndCloseChecksViewState.monthEndCloseChecksViewByTenantId[currentTenant.tenantId]?.[monthYearPeriodId] ?? { fetchState: 'Not-Started', error: undefined })
97
- : { fetchState: 'Not-Started', error: undefined };
98
- const reducedFetchStatus = reduceAnyFetchState([
99
- fetchStateAndError,
100
- monthEndFetchState,
101
- ]);
94
+ const groupsForSelectedPeriod = monthYearPeriodId != null
95
+ ? groupsBySelectedPeriod[monthYearPeriodId]
96
+ : undefined;
97
+ // `fetchState` is a flat slice field that survives a period switch. The per-period group is
98
+ // created by the fetch's own request reducer, so its absence means "not requested yet for this
99
+ // period" -- report 'Not-Started' then, so the table keeps its skeleton instead of rendering
100
+ // the previous period's rows during the transition.
101
+ const fetchStatus = fetchState === 'Completed' && groupsForSelectedPeriod == null
102
+ ? { fetchState: 'Not-Started', error: undefined }
103
+ : fetchStateAndError;
102
104
  return {
103
105
  sections,
104
106
  totalBalancesAndVariance,
105
- fetchStatus: reducedFetchStatus,
107
+ fetchStatus,
106
108
  bulkReviewStatus,
107
109
  reviewOperatingExpensesIds,
108
110
  selectedSectionIds,
@@ -1,6 +1,5 @@
1
1
  import omit from 'lodash/omit';
2
2
  import orderBy from 'lodash/orderBy';
3
- import { reduceAnyFetchState } from '../../../commonStateTypes/reduceFetchState';
4
3
  import { LAST_SCHEDULE_DAY_OF_MONTH, toMonthYearPeriodId, } from '../../../commonStateTypes/timePeriod';
5
4
  import { getAccountIdsForTypes } from '../../../entity/account/accountSelector';
6
5
  import { generateJEOneTimeAccrualKey } from '../../../entity/jeSchedules/jeScheduleHelper';
@@ -210,7 +209,7 @@ export function getExpenseAutomationJESchedulesView(state) {
210
209
  const failedJeScheduledTransaction = [];
211
210
  const accruedSchedules = [];
212
211
  const oneTimeAccruals = [];
213
- const { accountState, accountListState, classState, classListState, expenseAutomationJESchedulesViewState, expenseAutomationViewState, monthEndCloseChecksState, monthEndCloseChecksViewState, } = state;
212
+ const { accountState, accountListState, classState, classListState, expenseAutomationJESchedulesViewState, expenseAutomationViewState, monthEndCloseChecksState, } = state;
214
213
  const { jeScheduleTransactionKeysByPeriodAndTab, jeScheduleAccruedKeysByPeriodAndTab, jeScheduleOneTimeAccrualKeysByPeriodAndTab, failedJeScheduleTransactionKeysByPeriodAndTab, jeScheduleLocalDataById, postStatusById, ignoreStatusById, revertStatusById, oneTimeAccrualActionStatusById, uiState, error, fetchState, refreshStatus, newSchedule, } = expenseAutomationJESchedulesViewState;
215
214
  const accountSettingsView = getJEAccountSettingsView(state);
216
215
  const newScheduleView = newSchedule.selectedDetailScheduleType === 'fixed_assets' ||
@@ -401,13 +400,6 @@ export function getExpenseAutomationJESchedulesView(state) {
401
400
  : [];
402
401
  const jeCompletionStatus = allSteps.find((step) => step.step === 'je_schedules')?.completionStatus;
403
402
  const completionStatus = jeCompletionStatus != null ? jeCompletionStatus : 'incomplete';
404
- const monthEndFetchState = monthYearPeriodId != null
405
- ? (monthEndCloseChecksViewState.monthEndCloseChecksViewByTenantId[currentTenant.tenantId]?.[monthYearPeriodId] ?? { fetchState: 'Not-Started', error: undefined })
406
- : { fetchState: 'Not-Started', error: undefined };
407
- const reducedFetchStatus = reduceAnyFetchState([
408
- { fetchState, error },
409
- monthEndFetchState,
410
- ]);
411
403
  const countByType = (schedules) => {
412
404
  const counts = ALL_SCHEDULES_TYPES.reduce((acc, type) => ({ ...acc, [type]: 0 }), {});
413
405
  schedules.forEach((schedule) => {
@@ -445,10 +437,10 @@ export function getExpenseAutomationJESchedulesView(state) {
445
437
  ignoreStatusById: ignoreStatusById,
446
438
  revertStatusById: revertStatusById,
447
439
  oneTimeAccrualActionStatusById: oneTimeAccrualActionStatusById,
448
- fetchState: reducedFetchStatus.fetchState,
440
+ fetchState,
449
441
  jeScheduleLocalDataById: jeScheduleLocalDataById,
450
442
  uiState: uiState,
451
- error: reducedFetchStatus.error,
443
+ error,
452
444
  refreshStatus,
453
445
  completionStatus,
454
446
  };
@@ -1,5 +1,4 @@
1
1
  import orderBy from 'lodash/orderBy';
2
- import { reduceAnyFetchState } from '../../../commonStateTypes/reduceFetchState';
3
2
  import { toMonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
4
3
  import { getCurrentTenant } from '../../../entity/tenant/tenantSelector';
5
4
  import { getSupportedTransactionById, getSupportedTransactionsByIds, } from '../../../entity/transaction/transactionSelector';
@@ -8,7 +7,7 @@ import { getCompletedTransactionsCacheKey } from '../reducers/missingReceiptsVie
8
7
  import { isUnmatchedTabFileStatus, } from '../types/missingReceiptsViewState';
9
8
  import { getAllSteps } from './getAllStepsHelper';
10
9
  export function getExpenseAutomationMissingReceiptsView(state) {
11
- const { expenseAutomationMissingReceiptsViewState, expenseAutomationViewState, transactionState, monthEndCloseChecksState, monthEndCloseChecksViewState, } = state;
10
+ const { expenseAutomationMissingReceiptsViewState, expenseAutomationViewState, transactionState, monthEndCloseChecksState, } = state;
12
11
  const currentTenant = getCurrentTenant(state);
13
12
  const { selectedPeriodByTenantId } = expenseAutomationViewState;
14
13
  const selectedPeriod = selectedPeriodByTenantId[currentTenant.tenantId];
@@ -176,16 +175,16 @@ export function getExpenseAutomationMissingReceiptsView(state) {
176
175
  vendorLogo: result.vendorLogo ?? transaction?.logo?.href,
177
176
  };
178
177
  });
179
- const monthEndFetchState = monthYearPeriodId != null
180
- ? (monthEndCloseChecksViewState.monthEndCloseChecksViewByTenantId[currentTenant.tenantId]?.[monthYearPeriodId] ?? { fetchState: 'Not-Started', error: undefined })
181
- : { fetchState: 'Not-Started', error: undefined };
182
- const reducedFetchStatus = reduceAnyFetchState([
183
- { fetchState, error },
184
- monthEndFetchState,
185
- ]);
178
+ // `fetchState` is a flat slice field that survives a period switch, so it still reads
179
+ // 'Completed' before the newly selected period's ids land. Reporting 'Not-Started' until they
180
+ // do keeps the skeleton up instead of flashing the "all done" empty state over the previous
181
+ // period's rows.
182
+ const fetchStatus = fetchState === 'Completed' && transactionIdsForSelectedPeriod == null
183
+ ? { fetchState: 'Not-Started', error: undefined }
184
+ : { fetchState, error };
186
185
  return {
187
- fetchState: reducedFetchStatus.fetchState,
188
- error: reducedFetchStatus.error,
186
+ fetchState: fetchStatus.fetchState,
187
+ error: fetchStatus.error,
189
188
  uploadReceiptStatusById,
190
189
  uiState,
191
190
  refreshStatus,
@@ -30,6 +30,10 @@ export const localDataToSaveInvoicingCustomerPayload = (localData, billingAddres
30
30
  : {}),
31
31
  };
32
32
  };
33
+ /**
34
+ * The email rendered once with sentinel tokens standing in for the subject and
35
+ * body, so the composer can splice a draft into it without another request.
36
+ */
33
37
  export const toInvoicingPaymentLinkEmailPreview = (payload) => ({
34
38
  body: payload.body,
35
39
  bodyToken: payload.body_token,
@@ -1,11 +1,12 @@
1
1
  import { EMPTY, of } from 'rxjs';
2
2
  import { catchError, filter, finalize, mergeMap, switchMap, takeUntil, withLatestFrom, } from 'rxjs/operators';
3
+ import { NOTIFICATION_DOMAINS } from '../../../entity/notificationRegistry/notificationRegistryState';
3
4
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
4
5
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../responsePayload';
5
6
  import { notificationPreferencesUrl } from '../notificationPreferencesEndpoint';
6
7
  import { mapPreferencesToPayload, } from '../notificationPreferencesViewPayload';
7
8
  import { clearAllNotificationPreferencesView, clearNotificationPreferencesLocalOverrides, saveNotificationPreferences, saveNotificationPreferencesFailure, saveNotificationPreferencesSuccess, } from '../notificationPreferencesViewReducer';
8
- import { getNotificationLocalOverrides } from '../notificationPreferencesViewSelector';
9
+ import { getNotificationLocalOverridesForDomain } from '../notificationPreferencesViewSelector';
9
10
  export const saveNotificationPreferencesEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter((action) => saveNotificationPreferences.match(action)), withLatestFrom(state$),
10
11
  // `switchMap` unsubscribes from any in-flight PUT observable when a newer
11
12
  // Save fires. The prior HTTP request has already left the
@@ -13,9 +14,19 @@ export const saveNotificationPreferencesEpic = (actions$, state$, zeniAPI) => ac
13
14
  // ignored, so stale success/failure actions cannot clobber the newer
14
15
  // batch's state. Server-side ordering is arrival-order LWW.
15
16
  switchMap(([, state]) => {
16
- const overrides = getNotificationLocalOverrides(state);
17
- const preferencesPatch = mapPreferencesToPayload(overrides);
18
- if (Object.keys(preferencesPatch).length === 0) {
17
+ // A clean domain is omitted entirely: an empty patch is still a write against it.
18
+ const overridesByDomain = {};
19
+ const body = {};
20
+ NOTIFICATION_DOMAINS.forEach((domain) => {
21
+ const overrides = getNotificationLocalOverridesForDomain(state, domain);
22
+ const preferencesPatch = mapPreferencesToPayload(overrides);
23
+ if (Object.keys(preferencesPatch).length === 0) {
24
+ return;
25
+ }
26
+ overridesByDomain[domain] = overrides;
27
+ body[`${domain}_preferences`] = preferencesPatch;
28
+ });
29
+ if (Object.keys(body).length === 0) {
19
30
  // No diff → do not dispatch a synthetic success (that would wipe
20
31
  // stored preferences via the reducer). Simply no-op.
21
32
  return EMPTY;
@@ -28,7 +39,6 @@ switchMap(([, state]) => {
28
39
  // in-flight save's response (Success/Failure never dispatched, so
29
40
  // the counter never drained and future saves got their merge dropped).
30
41
  const cancelEpochAtDispatch = state.notificationPreferencesViewState.cancelEpoch;
31
- const body = { task_checklist_preferences: preferencesPatch };
32
42
  // Suppress the error snackbar when the response lands AFTER the user
33
43
  // pressed Cancel — the Failure reducer already swallows the state
34
44
  // update, so the toast would be a stale error for a save the user
@@ -57,8 +67,11 @@ switchMap(([, state]) => {
57
67
  if (isSuccessResponse(response)) {
58
68
  return of(saveNotificationPreferencesSuccess({
59
69
  cancelEpochAtDispatch,
60
- savedOverrides: overrides,
61
- serverEcho: response.data?.task_checklist_preferences,
70
+ savedOverridesByDomain: overridesByDomain,
71
+ serverEchoByDomain: {
72
+ task_checklist: response.data?.task_checklist_preferences,
73
+ commenting: response.data?.commenting_preferences,
74
+ },
62
75
  }));
63
76
  }
64
77
  const actions = [
@@ -1,14 +1,25 @@
1
1
  import { createSlice } from '@reduxjs/toolkit';
2
+ import { DEFAULT_NOTIFICATION_DOMAIN, } from '../../entity/notificationRegistry/notificationRegistryState';
2
3
  import { clearAll } from '../../rootActions';
3
4
  import { mapPayloadToPreferences, } from './notificationPreferencesViewPayload';
4
5
  import { emptyNotificationPreferences, } from './notificationPreferencesViewState';
5
6
  export const initialState = {
6
7
  cancelEpoch: 0,
7
- localOverrides: emptyNotificationPreferences(),
8
- preferences: emptyNotificationPreferences(),
8
+ localOverridesByDomain: {},
9
+ preferencesByDomain: {},
9
10
  saveEpoch: 0,
10
11
  savePreferencesState: { fetchState: 'Not-Started' },
11
12
  };
13
+ // Lazy: a gate-off tenant never receives that domain's key at all.
14
+ const overridesFor = (byDomain, domain) => {
15
+ const existing = byDomain[domain];
16
+ if (existing != null) {
17
+ return existing;
18
+ }
19
+ const created = emptyNotificationPreferences();
20
+ byDomain[domain] = created;
21
+ return created;
22
+ };
12
23
  const mergePreferences = (base, patch) => {
13
24
  const eventEnabledByChannel = {
14
25
  ...base.eventEnabledByChannel,
@@ -146,7 +157,8 @@ const notificationPreferencesView = createSlice({
146
157
  // Success/Failure never dispatches, subsequent legitimate saves
147
158
  // still work because their captured epoch matches the current one.
148
159
  draft.cancelEpoch += 1;
149
- draft.localOverrides = emptyNotificationPreferences();
160
+ // One shared Save/Cancel footer, so Cancel discards every domain.
161
+ draft.localOverridesByDomain = {};
150
162
  // Also drop `savePreferencesState` back to Not-Started so any
151
163
  // "Saving…" affordance bound to `getNotificationPreferencesSaveState`
152
164
  // clears on Cancel. Without this, a Cancel-during-save whose stale
@@ -163,9 +175,6 @@ const notificationPreferencesView = createSlice({
163
175
  // them; the save-success path is the only clearer.
164
176
  // A `null`/`undefined` payload isn't a snapshot at all (e.g. the
165
177
  // envelope key was omitted); no-op instead of wiping preferences.
166
- if (action.payload.preferences == null) {
167
- return;
168
- }
169
178
  // Stale-GET guard (Cursor Bugbot 3632510237): if the fetch epic
170
179
  // captured a `saveEpoch` lower than the current one, a save has
171
180
  // succeeded since this GET dispatched — the response is carrying
@@ -175,7 +184,13 @@ const notificationPreferencesView = createSlice({
175
184
  if (action.payload.saveEpochAtDispatch < draft.saveEpoch) {
176
185
  return;
177
186
  }
178
- draft.preferences = mapPayloadToPreferences(action.payload.preferences);
187
+ Object.entries(action.payload.preferencesByDomain).forEach(([domain, payload]) => {
188
+ if (payload == null) {
189
+ return;
190
+ }
191
+ draft.preferencesByDomain[domain] =
192
+ mapPayloadToPreferences(payload);
193
+ });
179
194
  },
180
195
  saveNotificationPreferences(draft) {
181
196
  draft.savePreferencesState = { fetchState: 'In-Progress' };
@@ -234,33 +249,44 @@ const notificationPreferencesView = createSlice({
234
249
  // skip the `preferences` replace. Cursor Bugbot 3632510237.
235
250
  draft.saveEpoch += 1;
236
251
  draft.savePreferencesState = { fetchState: 'Completed' };
237
- const patch = action.payload.serverEcho == null
238
- ? action.payload.savedOverrides
239
- : mapPayloadToPreferences(action.payload.serverEcho);
240
- draft.preferences = mergePreferences(draft.preferences, patch);
241
- draft.localOverrides = subtractSavedOverrides(draft.localOverrides, action.payload.savedOverrides);
252
+ Object.entries(action.payload.savedOverridesByDomain).forEach(([rawDomain, savedOverrides]) => {
253
+ if (savedOverrides == null) {
254
+ return;
255
+ }
256
+ const domain = rawDomain;
257
+ const serverEcho = action.payload.serverEchoByDomain[domain];
258
+ const patch = serverEcho == null
259
+ ? savedOverrides
260
+ : mapPayloadToPreferences(serverEcho);
261
+ draft.preferencesByDomain[domain] = mergePreferences(draft.preferencesByDomain[domain] ?? emptyNotificationPreferences(), patch);
262
+ draft.localOverridesByDomain[domain] = subtractSavedOverrides(draft.localOverridesByDomain[domain] ??
263
+ emptyNotificationPreferences(), savedOverrides);
264
+ });
242
265
  },
243
266
  setGroupChannelMaster(draft, action) {
244
267
  // Mirror of `toggleEventChannel` but scoped to the group-channel master
245
268
  // held under `groupChannelEnabledByGroupId[groupId][channel]`.
246
269
  const { channel, enabled, groupId } = action.payload;
270
+ const overrides = overridesFor(draft.localOverridesByDomain, action.payload.domain ?? DEFAULT_NOTIFICATION_DOMAIN);
247
271
  const groupMap = {
248
- ...(draft.localOverrides.groupChannelEnabledByGroupId[groupId] ?? {}),
272
+ ...(overrides.groupChannelEnabledByGroupId[groupId] ?? {}),
249
273
  };
250
274
  groupMap[channel] = enabled;
251
- draft.localOverrides.groupChannelEnabledByGroupId[groupId] = groupMap;
275
+ overrides.groupChannelEnabledByGroupId[groupId] = groupMap;
252
276
  },
253
277
  setGroupFrequency(draft, action) {
254
- draft.localOverrides.groupFrequencyByGroupId[action.payload.groupId] =
278
+ const overrides = overridesFor(draft.localOverridesByDomain, action.payload.domain ?? DEFAULT_NOTIFICATION_DOMAIN);
279
+ overrides.groupFrequencyByGroupId[action.payload.groupId] =
255
280
  action.payload.frequency;
256
281
  },
257
282
  toggleEventChannel(draft, action) {
258
283
  const { channel, enabled, eventId } = action.payload;
284
+ const overrides = overridesFor(draft.localOverridesByDomain, action.payload.domain ?? DEFAULT_NOTIFICATION_DOMAIN);
259
285
  const eventMap = {
260
- ...(draft.localOverrides.eventEnabledByChannel[eventId] ?? {}),
286
+ ...(overrides.eventEnabledByChannel[eventId] ?? {}),
261
287
  };
262
288
  eventMap[channel] = enabled;
263
- draft.localOverrides.eventEnabledByChannel[eventId] = eventMap;
289
+ overrides.eventEnabledByChannel[eventId] = eventMap;
264
290
  },
265
291
  },
266
292
  extraReducers: (builder) => {
@@ -1,11 +1,18 @@
1
1
  import { createSelector } from '@reduxjs/toolkit';
2
+ import { DEFAULT_NOTIFICATION_DOMAIN, } from '../../entity/notificationRegistry/notificationRegistryState';
3
+ import { emptyNotificationPreferences, } from './notificationPreferencesViewState';
2
4
  const getView = (state) => state.notificationPreferencesViewState;
3
5
  export const getNotificationPreferencesSaveState = (state) => getView(state).savePreferencesState;
4
6
  // Memoised on `preferences` + `localOverrides` so the merged shape keeps a
5
7
  // stable reference while neither input changes — a plain function would
6
8
  // allocate a fresh object on every call and re-render every `useSelector`
7
9
  // consumer on any unrelated dispatch.
8
- export const getEffectiveNotificationPreferences = createSelector((state) => getView(state).preferences, (state) => getView(state).localOverrides, (preferences, localOverrides) => {
10
+ const EMPTY_PREFERENCES = emptyNotificationPreferences();
11
+ // One per domain: a shared createSelector has a cache of one and would thrash across two.
12
+ const effectivePreferencesSelectorByDomain = new Map();
13
+ const createEffectivePreferencesSelector = (domain) => createSelector((state) => getView(state).preferencesByDomain[domain], (state) => getView(state).localOverridesByDomain[domain], (storedPreferences, storedOverrides) => {
14
+ const preferences = storedPreferences ?? EMPTY_PREFERENCES;
15
+ const localOverrides = storedOverrides ?? EMPTY_PREFERENCES;
9
16
  const mergedEventEnabledByChannel = { ...preferences.eventEnabledByChannel };
10
17
  Object.entries(localOverrides.eventEnabledByChannel).forEach(([eventId, channels]) => {
11
18
  mergedEventEnabledByChannel[eventId] = {
@@ -29,7 +36,20 @@ export const getEffectiveNotificationPreferences = createSelector((state) => get
29
36
  },
30
37
  };
31
38
  });
32
- export const getNotificationLocalOverrides = (state) => getView(state).localOverrides;
33
- export const hasUnsavedNotificationPreferences = createSelector((state) => getView(state).localOverrides, (localOverrides) => Object.keys(localOverrides.eventEnabledByChannel).length > 0 ||
39
+ export const getEffectiveNotificationPreferencesForDomain = (state, domain) => {
40
+ let selector = effectivePreferencesSelectorByDomain.get(domain);
41
+ if (selector == null) {
42
+ selector = createEffectivePreferencesSelector(domain);
43
+ effectivePreferencesSelectorByDomain.set(domain, selector);
44
+ }
45
+ return selector(state);
46
+ };
47
+ export const getEffectiveNotificationPreferences = (state) => getEffectiveNotificationPreferencesForDomain(state, DEFAULT_NOTIFICATION_DOMAIN);
48
+ export const getNotificationLocalOverridesForDomain = (state, domain) => getView(state).localOverridesByDomain[domain] ?? EMPTY_PREFERENCES;
49
+ export const getNotificationLocalOverrides = (state) => getNotificationLocalOverridesForDomain(state, DEFAULT_NOTIFICATION_DOMAIN);
50
+ const isDirty = (localOverrides) => Object.keys(localOverrides.eventEnabledByChannel).length > 0 ||
34
51
  Object.keys(localOverrides.groupChannelEnabledByGroupId).length > 0 ||
35
- Object.keys(localOverrides.groupFrequencyByGroupId).length > 0);
52
+ Object.keys(localOverrides.groupFrequencyByGroupId).length > 0;
53
+ export const hasUnsavedNotificationPreferencesForDomain = (state, domain) => isDirty(getNotificationLocalOverridesForDomain(state, domain));
54
+ // Drives the shared page-level Save/Cancel footer.
55
+ export const hasUnsavedNotificationPreferences = (state) => Object.values(getView(state).localOverridesByDomain).some((localOverrides) => localOverrides != null && isDirty(localOverrides));
@@ -38,11 +38,21 @@ export const fetchNotificationSettingsEpic = (actions$, state$, zeniAPI) => acti
38
38
  // 67d6918e.
39
39
  updateActions.push(updateNotificationRegistry({
40
40
  registry: response.data.task_checklist_registry ?? undefined,
41
+ // An absent `<domain>_registry` means that domain's gate is off.
42
+ registriesByDomain: {
43
+ task_checklist: response.data.task_checklist_registry ?? undefined,
44
+ commenting: response.data.commenting_registry ?? undefined,
45
+ },
41
46
  registryEpochAtDispatch,
42
47
  }));
43
- if (response.data.task_checklist_preferences != null) {
48
+ // One action so the domains cannot land on different store epochs.
49
+ const preferencesByDomain = {
50
+ task_checklist: response.data.task_checklist_preferences ?? undefined,
51
+ commenting: response.data.commenting_preferences ?? undefined,
52
+ };
53
+ if (Object.values(preferencesByDomain).some((preferences) => preferences != null)) {
44
54
  updateActions.push(fetchNotificationPreferencesSuccess({
45
- preferences: response.data.task_checklist_preferences,
55
+ preferencesByDomain,
46
56
  saveEpochAtDispatch,
47
57
  }));
48
58
  }
package/lib/index.d.ts CHANGED
@@ -116,8 +116,8 @@ import { updateCommentsNotifications, updateCommentsNotificationsStatuses } from
116
116
  import { NotificationWithAuthors } from './entity/notification/types/notificationSelectorTypes';
117
117
  import { Notification, NotificationActivity, NotificationEventData, NotificationMetaData, NotificationUpdates, NotificationValueFormat } from './entity/notification/types/notificationStateTypes';
118
118
  import { ExternalNotificationData, NotificationActivityType, NotificationGroup, NotificationIdentifierType, NotificationMode, NotificationStatus, NotificationUpdateValue, toNotificationModeStrict } from './entity/notification/types/notificationTypes';
119
- import { getNotificationRegistry } from './entity/notificationRegistry/notificationRegistrySelector';
120
- import { NotificationChannel, NotificationFrequency, NotificationRegistry, RegistryNotificationEvent, RegistryNotificationGroup, toNotificationChannel, toNotificationFrequency } from './entity/notificationRegistry/notificationRegistryState';
119
+ import { getNotificationRegistry, getNotificationRegistryForDomain } from './entity/notificationRegistry/notificationRegistrySelector';
120
+ import { DEFAULT_NOTIFICATION_DOMAIN, NOTIFICATION_DOMAINS, NotificationChannel, NotificationDomain, NotificationFrequency, NotificationRegistry, RegistryNotificationEvent, RegistryNotificationGroup, toNotificationChannel, toNotificationDomain, toNotificationFrequency } from './entity/notificationRegistry/notificationRegistryState';
121
121
  import { getPlaidPaymentAccounts } from './entity/paymentAccount/paymentAccountSelector';
122
122
  import { Logo, PaymentAccount, VERIFIED_PAYMENT_ACCOUNT_PROVIDER_VERIFICATION_STATUS } from './entity/paymentAccount/paymentAccountState';
123
123
  import { PaymentInstrument } from './entity/paymentInstrument/paymentInstrument';
@@ -379,7 +379,7 @@ import { getNetBurnOrIncomeStoryCardReport } from './view/netBurnOrIncomeStoryCa
379
379
  import { AccountingMethod, NetBurnOrIncomeStoryCardReport } from './view/netBurnOrIncomeStoryCard/netBurnOrIncomeStoryCardSelectorTypes';
380
380
  import { AverageMonthsCount, NetBurnOrIncomeRunway, NetBurnOrIncomeStoryCardState, TimeSpanIdForAverage } from './view/netBurnOrIncomeStoryCard/netBurnOrIncomeStoryCardState';
381
381
  import { clearAllNotificationPreferencesView, clearNotificationPreferencesLocalOverrides, saveNotificationPreferences, setGroupChannelMaster, setGroupFrequency, toggleEventChannel } from './view/notificationPreferencesView/notificationPreferencesViewReducer';
382
- import { getEffectiveNotificationPreferences, getNotificationLocalOverrides, getNotificationPreferencesSaveState, hasUnsavedNotificationPreferences } from './view/notificationPreferencesView/notificationPreferencesViewSelector';
382
+ import { getEffectiveNotificationPreferences, getEffectiveNotificationPreferencesForDomain, getNotificationLocalOverrides, getNotificationLocalOverridesForDomain, getNotificationPreferencesSaveState, hasUnsavedNotificationPreferences, hasUnsavedNotificationPreferencesForDomain } from './view/notificationPreferencesView/notificationPreferencesViewSelector';
383
383
  import { NotificationPreferences } from './view/notificationPreferencesView/notificationPreferencesViewState';
384
384
  import { fetchNotificationUnreadCount, fetchNotificationUnreadCountSuccess, fetchNotificationView, updateNotificationViewAllNotificationsStatus, updateNotificationViewCurrentTabAndSubTab, updateNotificationViewNotificationStatus, updateNotificationViewSubTab, updateNotificationViewTabState, updateNotificationViewUIState } from './view/notificationView/notificationViewReducer';
385
385
  import { NotificationView, getExternalNotificationsForSelectedSubTab, getNotificationView, getNotificationsForSelectedSubTab } from './view/notificationView/notificationViewSelector';
@@ -966,7 +966,7 @@ export { ExternalNotificationData, NotificationGroup, NotificationActivityType,
966
966
  export { NotificationView, NotificationViewUIState, NotificationTabState, NotificationTabType, NotificationSubTabType, toNotificationSubTabTypeStrict, toNotificationTabTypeStrict, fetchNotificationView, fetchNotificationUnreadCount, fetchNotificationUnreadCountSuccess, updateNotificationViewAllNotificationsStatus, updateNotificationViewNotificationStatus, updateNotificationViewTabState, updateNotificationViewCurrentTabAndSubTab, updateNotificationViewSubTab, updateNotificationViewUIState, getNotificationView, getExternalNotificationsForSelectedSubTab, getNotificationsForSelectedSubTab, };
967
967
  export { fetchRegisteredInterests, notifyMeForFeature, getFeatureNotificationView, getRegisteredInterests, getRegisteredInterestsByFeature, isFeatureInterestRegistered, FeatureInterest, FeatureNotificationViewState, };
968
968
  export { pushToastNotification, ToastNotification, ToastNotificationPayload, getLastNotificationTime, getNotifications, };
969
- export { getEffectiveNotificationPreferences, getNotificationLocalOverrides, getNotificationPreferencesSaveState, getNotificationRegistry, hasUnsavedNotificationPreferences, NotificationChannel, NotificationFrequency, NotificationPreferences, NotificationRegistry, RegistryNotificationEvent, RegistryNotificationGroup, clearAllNotificationPreferencesView, clearNotificationPreferencesLocalOverrides, saveNotificationPreferences, setGroupChannelMaster, setGroupFrequency, toggleEventChannel, toNotificationChannel, toNotificationFrequency, };
969
+ export { getEffectiveNotificationPreferences, getEffectiveNotificationPreferencesForDomain, getNotificationLocalOverrides, getNotificationLocalOverridesForDomain, getNotificationPreferencesSaveState, getNotificationRegistry, getNotificationRegistryForDomain, hasUnsavedNotificationPreferences, hasUnsavedNotificationPreferencesForDomain, NOTIFICATION_DOMAINS, NotificationChannel, DEFAULT_NOTIFICATION_DOMAIN, NotificationDomain, NotificationFrequency, NotificationPreferences, NotificationRegistry, RegistryNotificationEvent, RegistryNotificationGroup, clearAllNotificationPreferencesView, clearNotificationPreferencesLocalOverrides, saveNotificationPreferences, setGroupChannelMaster, setGroupFrequency, toggleEventChannel, toNotificationChannel, toNotificationDomain, toNotificationFrequency, };
970
970
  export { getReferralListView, getInviteFormView, ReferralListSelectorView, ReferralViewState, InviteCompanyLocalData, ReferralInvitation, ReferralListViewSortKey, ReferralViewUIState, toReferralListViewSortKeyType, ReferralStatus, ReferralAmountStatus, StatusTypes, AmountStatusTypes, DEFAULT_REFERRER_AMOUNT, fetchReferrals, sendReferralInvite, clearReferrals, saveReferralFormDataInLocalStore, updateReferralListSortUiState, resendReferralInvite, fetchRewardsPlan, RewardsPlanCardReport, getRewardsPlanCard, updateReferViewed, RewardsPlanData, };
971
971
  export { ALL_WEEK_DAYS, DayOfWeek, RecurringDatePickerOptions, RecurringFrequencyType, SEMI_WEEKLY_REQUIRED_DAYS_COUNT, getMinAllowedEndDate, getRecurringEndDateFromCount, toDayOfWeek, toRecurringFrequency, };
972
972
  export { fetchCockpitContext, fetchCompanyTaskManagerView, fetchTaskManagerMetrics, CompanyTaskManagerSelectorView, CompanyTaskManagerViewUIState, TaskManagerMetrics, getCompanyTaskManagerView, TaskWithCompanyDetail, createTaskFromTaskGroupTemplate, TaskGroupTemplate, };
@@ -983,12 +983,12 @@ export { AiAccountantCustomerViewState, AiAccountantSortKey, AiAccountantUIState
983
983
  export { AiAccountantFilterCategoryField } from './view/companyView/types/cockpitTypes';
984
984
  export { cancelAiAccountantOnboarding, fetchAiAccountantCustomers, fetchAiAccountantJobs, setSelectedTenantIdsForJobTrigger, triggerAiAccountantJob, updateAiAccountantUIState, } from './view/aiAccountantView/aiAccountantViewReducer';
985
985
  export { AiAccountantCockpitView, getAiAccountantCockpitView, } from './view/aiAccountantView/aiAccountantViewSelector';
986
- export { fetchChatSessionsForUser, createSession, submitQuestion, updateAiCfoViewScrollPosition, updateCurrentInput, updateCotCollapsedState, setSession, clearCurrentSessionId, clearDeleteChatSessionStatus, clearLastContextMessage, createSessionAndSubmit, stopSubmit, stopSubmitQuestion, fetchChatHistory, acceptMasterTOS, deleteChatSession, submitFeedback, updateResponseState, fetchSuggestedQuestions, fetchSuggestedQuestionsSuccess, fetchSuggestedQuestionsFailure, fetchSkills, fetchSkillsSuccess, fetchSkillsFailure, fetchSkillDetail, clearSkillDetail, clearSkillMutation, deleteSkill, fetchSkillVersionBody, fetchSkillVersions, saveSkill, setSkillVisibility, openSkillsBrowser, closeSkillsBrowser, applyAiCfoSidePanelHostPageTransition, clearAiCfoSidePanelHostPageContext, } from './view/aiCfoView/aiCfoViewReducer';
987
- export { AiCfoViewState, AiCfoViewUIState, Skill, SkillDetail, SkillDetailState, SkillDraft, SkillMutationState, SkillVersion, SkillVersionBodyState, SkillVersionsState, SkillVisibility, SkillsState, SuggestedQuestionsForPageContext, } from './view/aiCfoView/aiCfoViewState';
986
+ export { acceptMasterTOS, applyAiCfoSidePanelHostPageTransition, clearAiCfoSidePanelHostPageContext, clearCurrentSessionId, clearDeleteChatSessionStatus, clearLastContextMessage, clearSkillDetail, clearSkillMutation, closeRoutineRuns, closeRoutinesBrowser, closeSkillsBrowser, createSession, createSessionAndSubmit, deleteChatSession, deleteRoutine, deleteSkill, fetchChatHistory, fetchChatSessionsForUser, fetchRoutineRuns, fetchRoutines, fetchSkillDetail, fetchSkills, fetchSkillsFailure, fetchSkillsSuccess, fetchSkillVersionBody, fetchSkillVersions, fetchSuggestedQuestions, fetchSuggestedQuestionsFailure, fetchSuggestedQuestionsSuccess, openRoutinesBrowser, openSkillsBrowser, runRoutine, saveRoutine, saveSkill, setRoutineEnabled, setSession, setSkillVisibility, stopSubmit, stopSubmitQuestion, submitFeedback, submitQuestion, updateAiCfoViewScrollPosition, updateCotCollapsedState, updateCurrentInput, updateResponseState, } from './view/aiCfoView/aiCfoViewReducer';
987
+ export { AiCfoViewState, AiCfoViewUIState, Routine, RoutineDraft, RoutineMutationState, RoutineRecurrence, RoutineRun, RoutineRunsState, RoutineRunStatus, RoutinesState, RoutineStatus, Skill, SkillDetail, SkillDetailState, SkillDraft, SkillMutationState, SkillsState, SkillVersion, SkillVersionBodyState, SkillVersionsState, SkillVisibility, SuggestedQuestionsForPageContext, } from './view/aiCfoView/aiCfoViewState';
988
988
  export { FetchSuggestedQuestionsResponse, SuggestedQuestionsDataPayload, FetchSkillsResponse, SkillSummaryPayload, SkillListDataPayload, } from './view/aiCfoView/aiCfoViewPayload';
989
989
  export type { FeedbackType, SubmitFeedbackPayload, } from './view/aiCfoView/aiCfoViewPayload';
990
990
  export type { AiCfoSuggestedQuestionsPageContext } from './common/aiCfo/aiCfoSuggestedQuestionsPageContext';
991
- export { AiCfoViewSelector, getAiCfoView, getSuggestedQuestionsForPageContext, getSkills, getSkillDetail, getSkillMutation, getSkillVersionBody, getSkillVersions, getSkillsEnabled, } from './view/aiCfoView/aiCfoViewSelector';
991
+ export { AiCfoViewSelector, canCreateRoutine, getAiCfoView, getRoutineMutation, getRoutineRuns, getRoutines, getRoutinesEnabled, getSkillDetail, getSkillMutation, getSkills, getSkillsEnabled, getSkillVersionBody, getSkillVersions, getSuggestedQuestionsForPageContext, } from './view/aiCfoView/aiCfoViewSelector';
992
992
  export { MessagePayload, ChatSessionPayload, AiCfoAnswerPayload, } from './entity/aiCfo/aiCfoPayload';
993
993
  export { setNewSession, setSessions, updateAiCfoAnswerCardPolicyWizardPlan, upsertAnswerPayload, upsertOrAddQuestionAnswerPayload, addQuestionPayload, clearSession, toAiCfoExports, toAiCfoVisualization, appendSyntheticAiCfoAnswer, updateSyntheticAiCfoAnswer, removeSyntheticAiCfoAnswer, } from './entity/aiCfo/aiCfoReducer';
994
994
  export { AiCfoSelectorView, getAllQuestionAnswersForChatSession, getQuestionAnswerByIdForChatSession, getAllQuestionsForChatSession, getAiCfoSelectorView, getSyntheticAiCfoAnswersForChatSession, getSyntheticAiCfoAnswerByQuestionAnswerId, ChatSessionWithOrderedQuestionAnswers, } from './entity/aiCfo/aiCfoSelector';
@@ -1171,7 +1171,7 @@ export type { InvoicingCustomerColumnConfig, InvoicingCustomerFilters, Invoicing
1171
1171
  export { getInvoicingCustomerListView } from './view/invoicing/invoicingCustomerListView/invoicingCustomerListViewSelector';
1172
1172
  export type { InvoicingCustomerListView } from './view/invoicing/invoicingCustomerListView/invoicingCustomerListViewSelector';
1173
1173
  export { fetchInvoicingCustomerDetail, fetchInvoicingCustomerDetailPage, fetchInvoicingPaymentLinkEmailPreviewPage, } from './view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer';
1174
- export type { InvoicingPaymentLinkEmailPreviewData } from './view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewPayload';
1174
+ export type { InvoicingPaymentLinkEmailPreviewData } from './view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewState';
1175
1175
  export { createInvoicingSetupIntent, fetchInvoicingPlaidLinkToken, resetInvoicingCustomerPaymentMethod, saveInvoicingPaymentMethod, sendInvoicingPaymentLink, } from './view/invoicing/customerPaymentMethod/customerPaymentMethodReducer';
1176
1176
  export type { InvoicingSetupIntent } from './view/invoicing/customerPaymentMethod/customerPaymentMethodState';
1177
1177
  export { getInvoicingPaymentLinkSendState, getInvoicingPaymentMethodSaveState, getInvoicingPlaidLinkToken, getInvoicingSentPaymentLinkEmail, getInvoicingSentPaymentLinkMagicUrl, getInvoicingSetupIntent, getInvoicingSetupIntentFetchState, } from './view/invoicing/customerPaymentMethod/customerPaymentMethodSelector';