@zeniai/client-epic-state 5.2.47 → 5.2.49-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 (36) hide show
  1. package/lib/entity/notificationRegistry/notificationRegistryReducer.d.ts +2 -1
  2. package/lib/entity/notificationRegistry/notificationRegistryReducer.js +20 -9
  3. package/lib/entity/notificationRegistry/notificationRegistrySelector.d.ts +2 -1
  4. package/lib/entity/notificationRegistry/notificationRegistrySelector.js +7 -1
  5. package/lib/entity/notificationRegistry/notificationRegistryState.d.ts +5 -0
  6. package/lib/entity/notificationRegistry/notificationRegistryState.js +7 -1
  7. package/lib/entity/sectionProjectView/sectionProjectViewReducer.js +3 -1
  8. package/lib/epic.js +4 -4
  9. package/lib/esm/entity/notificationRegistry/notificationRegistryReducer.js +20 -9
  10. package/lib/esm/entity/notificationRegistry/notificationRegistrySelector.js +5 -0
  11. package/lib/esm/entity/notificationRegistry/notificationRegistryState.js +5 -0
  12. package/lib/esm/entity/sectionProjectView/sectionProjectViewReducer.js +3 -1
  13. package/lib/esm/epic.js +4 -4
  14. package/lib/esm/index.js +4 -4
  15. package/lib/esm/view/aiCfoView/aiCfoViewReducer.js +4 -2
  16. package/lib/esm/view/aiCfoView/epics/fetchRoutineRunsEpic.js +3 -1
  17. package/lib/esm/view/expenseAutomationView/reducers/reconciliationViewReducer.js +80 -53
  18. package/lib/esm/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +20 -7
  19. package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewReducer.js +43 -17
  20. package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewSelector.js +24 -4
  21. package/lib/esm/view/settingsView/epic/fetchNotificationSettingsEpic.js +12 -2
  22. package/lib/index.d.ts +4 -4
  23. package/lib/index.js +21 -14
  24. package/lib/view/aiCfoView/aiCfoViewReducer.js +4 -2
  25. package/lib/view/aiCfoView/aiCfoViewSelector.d.ts +1 -1
  26. package/lib/view/aiCfoView/epics/fetchRoutineRunsEpic.js +3 -1
  27. package/lib/view/expenseAutomationView/reducers/reconciliationViewReducer.js +80 -53
  28. package/lib/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +19 -6
  29. package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.d.ts +8 -5
  30. package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.js +43 -17
  31. package/lib/view/notificationPreferencesView/notificationPreferencesViewSelector.d.ts +4 -0
  32. package/lib/view/notificationPreferencesView/notificationPreferencesViewSelector.js +30 -5
  33. package/lib/view/notificationPreferencesView/notificationPreferencesViewState.d.ts +4 -3
  34. package/lib/view/settingsView/epic/fetchNotificationSettingsEpic.js +12 -2
  35. package/lib/view/settingsView/settingsViewPayload.d.ts +2 -0
  36. package/package.json +1 -1
@@ -1,9 +1,10 @@
1
1
  import { NotificationRegistryPayload } from './notificationRegistryPayload';
2
- import { NotificationRegistryState } from './notificationRegistryState';
2
+ import { NotificationDomain, NotificationRegistryState } from './notificationRegistryState';
3
3
  export declare const initialState: NotificationRegistryState;
4
4
  export interface UpdateNotificationRegistryPayload {
5
5
  registry: NotificationRegistryPayload | undefined;
6
6
  registryEpochAtDispatch: number;
7
+ registriesByDomain?: Partial<Record<NotificationDomain, NotificationRegistryPayload | undefined>>;
7
8
  }
8
9
  export declare const updateNotificationRegistry: import("@reduxjs/toolkit").ActionCreatorWithPayload<UpdateNotificationRegistryPayload, "notificationRegistry/updateNotificationRegistry">;
9
10
  declare const _default: import("redux").Reducer<NotificationRegistryState>;
@@ -4,6 +4,7 @@ exports.updateNotificationRegistry = exports.initialState = void 0;
4
4
  const toolkit_1 = require("@reduxjs/toolkit");
5
5
  const rootActions_1 = require("../../rootActions");
6
6
  const notificationRegistryPayload_1 = require("./notificationRegistryPayload");
7
+ const notificationRegistryState_1 = require("./notificationRegistryState");
7
8
  exports.initialState = { registryEpoch: 0 };
8
9
  const notificationRegistry = (0, toolkit_1.createSlice)({
9
10
  name: 'notificationRegistry',
@@ -22,16 +23,24 @@ const notificationRegistry = (0, toolkit_1.createSlice)({
22
23
  // `task_checklist_registry` CLEARS so tenant B doesn't inherit A's
23
24
  // Task Checklist section. Guarded by the epoch check above so a stale
24
25
  // absent-response can't wipe a fresh registry.
25
- if (action.payload.registry == null) {
26
- draft.registry = undefined;
27
- return;
26
+ const { registriesByDomain } = action.payload;
27
+ // Absent `registriesByDomain` means a pre-domain caller: treat `registry`
28
+ // as the task_checklist snapshot so their behaviour is unchanged.
29
+ const snapshots = registriesByDomain ?? { task_checklist: action.payload.registry };
30
+ draft.registryByDomain = {};
31
+ for (const domain of notificationRegistryState_1.NOTIFICATION_DOMAINS) {
32
+ const payload = snapshots[domain];
33
+ // Full-replace per domain, and an absent snapshot CLEARS that domain so
34
+ // tenant B never inherits A's section. The gates are independent
35
+ // server-side, so one domain clearing must not touch the other.
36
+ if (payload != null) {
37
+ draft.registryByDomain[domain] =
38
+ (0, notificationRegistryPayload_1.mapNotificationRegistryPayloadToRegistry)(payload);
39
+ }
28
40
  }
29
- // Full-replace contract: the registry payload is atomic — every fetch
30
- // returns the complete group/event tree. If the backend ever ships a
31
- // partial registry response, this write MUST switch to a merge (mirror
32
- // the preferences slice's `mergePreferences`). Do not assume the
33
- // current payload is partial without also updating this reducer.
34
- draft.registry = (0, notificationRegistryPayload_1.mapNotificationRegistryPayloadToRegistry)(action.payload.registry);
41
+ // Mirrored for consumers that predate the domain-keyed map.
42
+ draft.registry = draft.registryByDomain.task_checklist;
43
+ return;
35
44
  },
36
45
  },
37
46
  extraReducers: (builder) => {
@@ -44,6 +53,8 @@ const notificationRegistry = (0, toolkit_1.createSlice)({
44
53
  // — Cursor Bugbot 67d6918e. Returning `initialState` verbatim would
45
54
  // rewind the counter and let a stale in-flight GET restore A's
46
55
  // registry over B's.
56
+ // Drops `registry` and `registryByDomain` together: a tenant switch must
57
+ // not leave one domain's section behind.
47
58
  return { registryEpoch: draft.registryEpoch + 1 };
48
59
  });
49
60
  },
@@ -1,2 +1,3 @@
1
- import { NotificationRegistry, NotificationRegistryState } from './notificationRegistryState';
1
+ import { NotificationDomain, NotificationRegistry, NotificationRegistryState } from './notificationRegistryState';
2
2
  export declare const getNotificationRegistry: (slice: NotificationRegistryState) => NotificationRegistry | undefined;
3
+ export declare const getNotificationRegistryForDomain: (slice: NotificationRegistryState, domain: NotificationDomain) => NotificationRegistry | undefined;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getNotificationRegistry = void 0;
3
+ exports.getNotificationRegistryForDomain = exports.getNotificationRegistry = void 0;
4
4
  // Current behavior: a null / undefined registry means the whole notification
5
5
  // section is hidden by consumers. This conflates two distinct states —
6
6
  // "user has no saved preferences yet" vs "the Task Checklist feature gate is
@@ -8,3 +8,9 @@ exports.getNotificationRegistry = void 0;
8
8
  // them (e.g. an explicit `gate_off` signal from the server) is a follow-up.
9
9
  const getNotificationRegistry = (slice) => slice.registry;
10
10
  exports.getNotificationRegistry = getNotificationRegistry;
11
+ // `undefined` conflates gate-off with no-preferences-yet; consumers hide the
12
+ // section either way. Falls back to `registry` for task_checklist so state built
13
+ // in the pre-domain shape (consumer mocks, older callers) still resolves.
14
+ const getNotificationRegistryForDomain = (slice, domain) => slice.registryByDomain?.[domain] ??
15
+ (domain === 'task_checklist' ? slice.registry : undefined);
16
+ exports.getNotificationRegistryForDomain = getNotificationRegistryForDomain;
@@ -1,3 +1,7 @@
1
+ export declare const NOTIFICATION_DOMAINS: readonly ["task_checklist", "commenting"];
2
+ export type NotificationDomain = (typeof NOTIFICATION_DOMAINS)[number];
3
+ export declare const DEFAULT_NOTIFICATION_DOMAIN: NotificationDomain;
4
+ export declare const toNotificationDomain: (raw: string) => NotificationDomain;
1
5
  export declare const NOTIFICATION_CHANNELS: readonly ["email", "slack", "in_app"];
2
6
  export type NotificationChannel = (typeof NOTIFICATION_CHANNELS)[number];
3
7
  export declare const toNotificationChannel: (raw: string) => NotificationChannel;
@@ -30,4 +34,5 @@ export interface NotificationRegistry {
30
34
  export interface NotificationRegistryState {
31
35
  registryEpoch: number;
32
36
  registry?: NotificationRegistry;
37
+ registryByDomain?: Partial<Record<NotificationDomain, NotificationRegistry>>;
33
38
  }
@@ -1,7 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toNotificationFrequency = exports.NOTIFICATION_FREQUENCIES = exports.toNotificationChannel = exports.NOTIFICATION_CHANNELS = void 0;
3
+ exports.toNotificationFrequency = exports.NOTIFICATION_FREQUENCIES = exports.toNotificationChannel = exports.NOTIFICATION_CHANNELS = exports.toNotificationDomain = exports.DEFAULT_NOTIFICATION_DOMAIN = exports.NOTIFICATION_DOMAINS = void 0;
4
4
  const stringToUnion_1 = require("../../commonStateTypes/stringToUnion");
5
+ // Wire vocabulary owned by the notifications service, not by this slice.
6
+ exports.NOTIFICATION_DOMAINS = ['task_checklist', 'commenting'];
7
+ // Callers that predate the domain split mean Task Checklist.
8
+ exports.DEFAULT_NOTIFICATION_DOMAIN = 'task_checklist';
9
+ const toNotificationDomain = (raw) => (0, stringToUnion_1.stringToUnion)(raw, exports.NOTIFICATION_DOMAINS);
10
+ exports.toNotificationDomain = toNotificationDomain;
5
11
  exports.NOTIFICATION_CHANNELS = ['email', 'slack', 'in_app'];
6
12
  const toNotificationChannel = (raw) => (0, stringToUnion_1.stringToUnion)(raw, exports.NOTIFICATION_CHANNELS);
7
13
  exports.toNotificationChannel = toNotificationChannel;
@@ -35,7 +35,9 @@ const sectionProjectView = (0, toolkit_1.createSlice)({
35
35
  Object.keys(sectionPayload.projects).forEach((projectId) => {
36
36
  const projectPayload = sectionPayload.projects[projectId];
37
37
  // A calculated section is grouped by project with no accounts under it.
38
- const projectAccounts = 'accounts' in projectPayload ? projectPayload.accounts : undefined;
38
+ const projectAccounts = 'accounts' in projectPayload
39
+ ? projectPayload.accounts
40
+ : undefined;
39
41
  const nestedAccountIds = projectAccounts != null
40
42
  ? (0, getNestedAccountIDHierarchyForSection_1.getNestedAccountIDHierarchyForAccounts)(Object.keys(projectAccounts), accountsMetadata)
41
43
  : [];
package/lib/epic.js CHANGED
@@ -53,21 +53,21 @@ const acceptMasterTOSEpic_1 = require("./view/aiCfoView/epics/acceptMasterTOSEpi
53
53
  const createSessionAndSubmitEpic_1 = require("./view/aiCfoView/epics/createSessionAndSubmitEpic");
54
54
  const createSessionEpic_1 = require("./view/aiCfoView/epics/createSessionEpic");
55
55
  const deleteChatSessionEpic_1 = require("./view/aiCfoView/epics/deleteChatSessionEpic");
56
+ const deleteRoutineEpic_1 = require("./view/aiCfoView/epics/deleteRoutineEpic");
56
57
  const deleteSkillEpic_1 = require("./view/aiCfoView/epics/deleteSkillEpic");
57
58
  const fetchChatHistoryEpic_1 = require("./view/aiCfoView/epics/fetchChatHistoryEpic");
58
59
  const fetchChatSessionsForUserEpic_1 = require("./view/aiCfoView/epics/fetchChatSessionsForUserEpic");
60
+ const fetchRoutineRunsEpic_1 = require("./view/aiCfoView/epics/fetchRoutineRunsEpic");
61
+ const fetchRoutinesEpic_1 = require("./view/aiCfoView/epics/fetchRoutinesEpic");
59
62
  const fetchSkillDetailEpic_1 = require("./view/aiCfoView/epics/fetchSkillDetailEpic");
60
63
  const fetchSkillVersionBodyEpic_1 = require("./view/aiCfoView/epics/fetchSkillVersionBodyEpic");
61
64
  const fetchSkillVersionsEpic_1 = require("./view/aiCfoView/epics/fetchSkillVersionsEpic");
62
65
  const fetchSkillsEpic_1 = require("./view/aiCfoView/epics/fetchSkillsEpic");
63
66
  const fetchSuggestedQuestionsEpic_1 = require("./view/aiCfoView/epics/fetchSuggestedQuestionsEpic");
64
- const deleteRoutineEpic_1 = require("./view/aiCfoView/epics/deleteRoutineEpic");
65
- const fetchRoutineRunsEpic_1 = require("./view/aiCfoView/epics/fetchRoutineRunsEpic");
66
- const fetchRoutinesEpic_1 = require("./view/aiCfoView/epics/fetchRoutinesEpic");
67
67
  const runRoutineEpic_1 = require("./view/aiCfoView/epics/runRoutineEpic");
68
68
  const saveRoutineEpic_1 = require("./view/aiCfoView/epics/saveRoutineEpic");
69
- const setRoutineEnabledEpic_1 = require("./view/aiCfoView/epics/setRoutineEnabledEpic");
70
69
  const saveSkillEpic_1 = require("./view/aiCfoView/epics/saveSkillEpic");
70
+ const setRoutineEnabledEpic_1 = require("./view/aiCfoView/epics/setRoutineEnabledEpic");
71
71
  const setSkillVisibilityEpic_1 = require("./view/aiCfoView/epics/setSkillVisibilityEpic");
72
72
  const stopSubmitEpic_1 = require("./view/aiCfoView/epics/stopSubmitEpic");
73
73
  const stopSubmitQuestionEpic_1 = require("./view/aiCfoView/epics/stopSubmitQuestionEpic");
@@ -1,6 +1,7 @@
1
1
  import { createSlice } from '@reduxjs/toolkit';
2
2
  import { clearAll } from '../../rootActions';
3
3
  import { mapNotificationRegistryPayloadToRegistry, } from './notificationRegistryPayload';
4
+ import { NOTIFICATION_DOMAINS, } from './notificationRegistryState';
4
5
  export const initialState = { registryEpoch: 0 };
5
6
  const notificationRegistry = createSlice({
6
7
  name: 'notificationRegistry',
@@ -19,16 +20,24 @@ const notificationRegistry = createSlice({
19
20
  // `task_checklist_registry` CLEARS so tenant B doesn't inherit A's
20
21
  // Task Checklist section. Guarded by the epoch check above so a stale
21
22
  // absent-response can't wipe a fresh registry.
22
- if (action.payload.registry == null) {
23
- draft.registry = undefined;
24
- return;
23
+ const { registriesByDomain } = action.payload;
24
+ // Absent `registriesByDomain` means a pre-domain caller: treat `registry`
25
+ // as the task_checklist snapshot so their behaviour is unchanged.
26
+ const snapshots = registriesByDomain ?? { task_checklist: action.payload.registry };
27
+ draft.registryByDomain = {};
28
+ for (const domain of NOTIFICATION_DOMAINS) {
29
+ const payload = snapshots[domain];
30
+ // Full-replace per domain, and an absent snapshot CLEARS that domain so
31
+ // tenant B never inherits A's section. The gates are independent
32
+ // server-side, so one domain clearing must not touch the other.
33
+ if (payload != null) {
34
+ draft.registryByDomain[domain] =
35
+ mapNotificationRegistryPayloadToRegistry(payload);
36
+ }
25
37
  }
26
- // Full-replace contract: the registry payload is atomic — every fetch
27
- // returns the complete group/event tree. If the backend ever ships a
28
- // partial registry response, this write MUST switch to a merge (mirror
29
- // the preferences slice's `mergePreferences`). Do not assume the
30
- // current payload is partial without also updating this reducer.
31
- draft.registry = mapNotificationRegistryPayloadToRegistry(action.payload.registry);
38
+ // Mirrored for consumers that predate the domain-keyed map.
39
+ draft.registry = draft.registryByDomain.task_checklist;
40
+ return;
32
41
  },
33
42
  },
34
43
  extraReducers: (builder) => {
@@ -41,6 +50,8 @@ const notificationRegistry = createSlice({
41
50
  // — Cursor Bugbot 67d6918e. Returning `initialState` verbatim would
42
51
  // rewind the counter and let a stale in-flight GET restore A's
43
52
  // registry over B's.
53
+ // Drops `registry` and `registryByDomain` together: a tenant switch must
54
+ // not leave one domain's section behind.
44
55
  return { registryEpoch: draft.registryEpoch + 1 };
45
56
  });
46
57
  },
@@ -4,3 +4,8 @@
4
4
  // off for this tenant". Both currently surface as `undefined`. Distinguishing
5
5
  // them (e.g. an explicit `gate_off` signal from the server) is a follow-up.
6
6
  export const getNotificationRegistry = (slice) => slice.registry;
7
+ // `undefined` conflates gate-off with no-preferences-yet; consumers hide the
8
+ // section either way. Falls back to `registry` for task_checklist so state built
9
+ // in the pre-domain shape (consumer mocks, older callers) still resolves.
10
+ export const getNotificationRegistryForDomain = (slice, domain) => slice.registryByDomain?.[domain] ??
11
+ (domain === 'task_checklist' ? slice.registry : undefined);
@@ -1,4 +1,9 @@
1
1
  import { stringToUnion } from '../../commonStateTypes/stringToUnion';
2
+ // Wire vocabulary owned by the notifications service, not by this slice.
3
+ export const NOTIFICATION_DOMAINS = ['task_checklist', 'commenting'];
4
+ // Callers that predate the domain split mean Task Checklist.
5
+ export const DEFAULT_NOTIFICATION_DOMAIN = 'task_checklist';
6
+ export const toNotificationDomain = (raw) => stringToUnion(raw, NOTIFICATION_DOMAINS);
2
7
  export const NOTIFICATION_CHANNELS = ['email', 'slack', 'in_app'];
3
8
  export const toNotificationChannel = (raw) => stringToUnion(raw, NOTIFICATION_CHANNELS);
4
9
  export const NOTIFICATION_FREQUENCIES = ['instant', 'daily'];
@@ -32,7 +32,9 @@ const sectionProjectView = createSlice({
32
32
  Object.keys(sectionPayload.projects).forEach((projectId) => {
33
33
  const projectPayload = sectionPayload.projects[projectId];
34
34
  // A calculated section is grouped by project with no accounts under it.
35
- const projectAccounts = 'accounts' in projectPayload ? projectPayload.accounts : undefined;
35
+ const projectAccounts = 'accounts' in projectPayload
36
+ ? projectPayload.accounts
37
+ : undefined;
36
38
  const nestedAccountIds = projectAccounts != null
37
39
  ? getNestedAccountIDHierarchyForAccounts(Object.keys(projectAccounts), accountsMetadata)
38
40
  : [];
package/lib/esm/epic.js CHANGED
@@ -50,21 +50,21 @@ import { acceptMasterTOSEpic, } from './view/aiCfoView/epics/acceptMasterTOSEpic
50
50
  import { createSessionAndSubmitEpic, } from './view/aiCfoView/epics/createSessionAndSubmitEpic';
51
51
  import { createSessionEpic, } from './view/aiCfoView/epics/createSessionEpic';
52
52
  import { deleteChatSessionEpic, } from './view/aiCfoView/epics/deleteChatSessionEpic';
53
+ import { deleteRoutineEpic } from './view/aiCfoView/epics/deleteRoutineEpic';
53
54
  import { deleteSkillEpic } from './view/aiCfoView/epics/deleteSkillEpic';
54
55
  import { fetchChatHistoryEpic, } from './view/aiCfoView/epics/fetchChatHistoryEpic';
55
56
  import { fetchChatSessionsForUserEpic, } from './view/aiCfoView/epics/fetchChatSessionsForUserEpic';
57
+ import { fetchRoutineRunsEpic } from './view/aiCfoView/epics/fetchRoutineRunsEpic';
58
+ import { fetchRoutinesEpic } from './view/aiCfoView/epics/fetchRoutinesEpic';
56
59
  import { fetchSkillDetailEpic } from './view/aiCfoView/epics/fetchSkillDetailEpic';
57
60
  import { fetchSkillVersionBodyEpic } from './view/aiCfoView/epics/fetchSkillVersionBodyEpic';
58
61
  import { fetchSkillVersionsEpic } from './view/aiCfoView/epics/fetchSkillVersionsEpic';
59
62
  import { fetchSkillsEpic, } from './view/aiCfoView/epics/fetchSkillsEpic';
60
63
  import { fetchSuggestedQuestionsEpic, } from './view/aiCfoView/epics/fetchSuggestedQuestionsEpic';
61
- import { deleteRoutineEpic } from './view/aiCfoView/epics/deleteRoutineEpic';
62
- import { fetchRoutineRunsEpic } from './view/aiCfoView/epics/fetchRoutineRunsEpic';
63
- import { fetchRoutinesEpic } from './view/aiCfoView/epics/fetchRoutinesEpic';
64
64
  import { runRoutineEpic } from './view/aiCfoView/epics/runRoutineEpic';
65
65
  import { saveRoutineEpic } from './view/aiCfoView/epics/saveRoutineEpic';
66
- import { setRoutineEnabledEpic } from './view/aiCfoView/epics/setRoutineEnabledEpic';
67
66
  import { saveSkillEpic } from './view/aiCfoView/epics/saveSkillEpic';
67
+ import { setRoutineEnabledEpic } from './view/aiCfoView/epics/setRoutineEnabledEpic';
68
68
  import { setSkillVisibilityEpic } from './view/aiCfoView/epics/setSkillVisibilityEpic';
69
69
  import { stopSubmitEpic, } from './view/aiCfoView/epics/stopSubmitEpic';
70
70
  import { stopSubmitQuestionEpic, } from './view/aiCfoView/epics/stopSubmitQuestionEpic';
package/lib/esm/index.js CHANGED
@@ -64,8 +64,8 @@ import { ALL_JE_SCHEDULE_IGNORE_REASONS, ALL_JE_SCHEDULE_REVERT_REASONS, ALL_ONE
64
64
  import { ALL_MONTH_END_CLOSE_CHECKS_FREQUENCY, } from './entity/monthEndCloseChecks/monthEndCloseChecksState';
65
65
  import { updateCommentsNotifications, updateCommentsNotificationsStatuses, } from './entity/notification/notificationReducer';
66
66
  import { toNotificationModeStrict, } from './entity/notification/types/notificationTypes';
67
- import { getNotificationRegistry } from './entity/notificationRegistry/notificationRegistrySelector';
68
- import { toNotificationChannel, toNotificationFrequency, } from './entity/notificationRegistry/notificationRegistryState';
67
+ import { getNotificationRegistry, getNotificationRegistryForDomain, } from './entity/notificationRegistry/notificationRegistrySelector';
68
+ import { DEFAULT_NOTIFICATION_DOMAIN, NOTIFICATION_DOMAINS, toNotificationChannel, toNotificationDomain, toNotificationFrequency, } from './entity/notificationRegistry/notificationRegistryState';
69
69
  import { getPlaidPaymentAccounts } from './entity/paymentAccount/paymentAccountSelector';
70
70
  import { VERIFIED_PAYMENT_ACCOUNT_PROVIDER_VERIFICATION_STATUS, } from './entity/paymentAccount/paymentAccountState';
71
71
  import { closeSnackbar, openSnackbar } from './entity/snackbar/snackbarReducer';
@@ -236,7 +236,7 @@ import { toAverageMonthsCount } from './view/netBurnOrIncomeStoryCard/epic/updat
236
236
  import { fetchNetBurnOrIncomeStoryCard, updateNetBurnOrIncomeStoryCardSettings, } from './view/netBurnOrIncomeStoryCard/netBurnOrIncomeStoryCardReducer';
237
237
  import { getNetBurnOrIncomeStoryCardReport } from './view/netBurnOrIncomeStoryCard/netBurnOrIncomeStoryCardSelector';
238
238
  import { clearAllNotificationPreferencesView, clearNotificationPreferencesLocalOverrides, saveNotificationPreferences, setGroupChannelMaster, setGroupFrequency, toggleEventChannel, } from './view/notificationPreferencesView/notificationPreferencesViewReducer';
239
- import { getEffectiveNotificationPreferences, getNotificationLocalOverrides, getNotificationPreferencesSaveState, hasUnsavedNotificationPreferences, } from './view/notificationPreferencesView/notificationPreferencesViewSelector';
239
+ import { getEffectiveNotificationPreferences, getEffectiveNotificationPreferencesForDomain, getNotificationLocalOverrides, getNotificationLocalOverridesForDomain, getNotificationPreferencesSaveState, hasUnsavedNotificationPreferences, hasUnsavedNotificationPreferencesForDomain, } from './view/notificationPreferencesView/notificationPreferencesViewSelector';
240
240
  import { fetchNotificationUnreadCount, fetchNotificationUnreadCountSuccess, fetchNotificationView, updateNotificationViewAllNotificationsStatus, updateNotificationViewCurrentTabAndSubTab, updateNotificationViewNotificationStatus, updateNotificationViewSubTab, updateNotificationViewTabState, updateNotificationViewUIState, } from './view/notificationView/notificationViewReducer';
241
241
  import { getExternalNotificationsForSelectedSubTab, getNotificationView, getNotificationsForSelectedSubTab, } from './view/notificationView/notificationViewSelector';
242
242
  import { toNotificationSubTabTypeStrict, toNotificationTabTypeStrict, } from './view/notificationView/notificationViewState';
@@ -680,7 +680,7 @@ export { toNotificationModeStrict, updateCommentsNotifications, updateCommentsNo
680
680
  export { toNotificationSubTabTypeStrict, toNotificationTabTypeStrict, fetchNotificationView, fetchNotificationUnreadCount, fetchNotificationUnreadCountSuccess, updateNotificationViewAllNotificationsStatus, updateNotificationViewNotificationStatus, updateNotificationViewTabState, updateNotificationViewCurrentTabAndSubTab, updateNotificationViewSubTab, updateNotificationViewUIState, getNotificationView, getExternalNotificationsForSelectedSubTab, getNotificationsForSelectedSubTab, };
681
681
  export { fetchRegisteredInterests, notifyMeForFeature, getFeatureNotificationView, getRegisteredInterests, getRegisteredInterestsByFeature, isFeatureInterestRegistered, };
682
682
  export { pushToastNotification, getLastNotificationTime, getNotifications, };
683
- export { getEffectiveNotificationPreferences, getNotificationLocalOverrides, getNotificationPreferencesSaveState, getNotificationRegistry, hasUnsavedNotificationPreferences, clearAllNotificationPreferencesView, clearNotificationPreferencesLocalOverrides, saveNotificationPreferences, setGroupChannelMaster, setGroupFrequency, toggleEventChannel, toNotificationChannel, toNotificationFrequency, };
683
+ export { getEffectiveNotificationPreferences, getEffectiveNotificationPreferencesForDomain, getNotificationLocalOverrides, getNotificationLocalOverridesForDomain, getNotificationPreferencesSaveState, getNotificationRegistry, getNotificationRegistryForDomain, hasUnsavedNotificationPreferences, hasUnsavedNotificationPreferencesForDomain, NOTIFICATION_DOMAINS, DEFAULT_NOTIFICATION_DOMAIN, clearAllNotificationPreferencesView, clearNotificationPreferencesLocalOverrides, saveNotificationPreferences, setGroupChannelMaster, setGroupFrequency, toggleEventChannel, toNotificationChannel, toNotificationDomain, toNotificationFrequency, };
684
684
  export { getReferralListView, getInviteFormView, toReferralListViewSortKeyType, StatusTypes, AmountStatusTypes, DEFAULT_REFERRER_AMOUNT, fetchReferrals, sendReferralInvite, clearReferrals, saveReferralFormDataInLocalStore, updateReferralListSortUiState, resendReferralInvite, fetchRewardsPlan, getRewardsPlanCard, updateReferViewed, };
685
685
  export { ALL_WEEK_DAYS, SEMI_WEEKLY_REQUIRED_DAYS_COUNT, getMinAllowedEndDate, getRecurringEndDateFromCount, toDayOfWeek, toRecurringFrequency, };
686
686
  export { fetchCockpitContext, fetchCompanyTaskManagerView, fetchTaskManagerMetrics, getCompanyTaskManagerView, createTaskFromTaskGroupTemplate, };
@@ -835,7 +835,8 @@ const aiCfoView = createSlice({
835
835
  // The server has it. Nothing it sends from here on will contain this row,
836
836
  // so the tombstone has done its job and holding it any longer is what
837
837
  // makes a routine recreated under the same id invisible forever.
838
- draft.routines.deletedScheduleIds = draft.routines.deletedScheduleIds.filter((id) => id !== action.payload.scheduleId);
838
+ draft.routines.deletedScheduleIds =
839
+ draft.routines.deletedScheduleIds.filter((id) => id !== action.payload.scheduleId);
839
840
  draft.routineMutation = {
840
841
  fetchState: 'Completed',
841
842
  error: undefined,
@@ -847,7 +848,8 @@ const aiCfoView = createSlice({
847
848
  // is what lets the refetch put the row back — leaving it hidden would
848
849
  // tell the user they had stopped something they had not, which is the
849
850
  // worst outcome this list can produce.
850
- draft.routines.deletedScheduleIds = draft.routines.deletedScheduleIds.filter((id) => id !== action.payload.scheduleId);
851
+ draft.routines.deletedScheduleIds =
852
+ draft.routines.deletedScheduleIds.filter((id) => id !== action.payload.scheduleId);
851
853
  // Its own slice: the refetch that puts the row back would otherwise wipe
852
854
  // this error, leaving the row silently reappearing with no explanation.
853
855
  draft.routineMutation = {
@@ -27,7 +27,9 @@ export const fetchRoutineRunsEpic = (actions$, _state$, zeniAPI) => actions$.pip
27
27
  // Filtered, not trusted: one malformed row must not take the
28
28
  // whole history down, and a run without an id is not addressable
29
29
  // by anything the UI does with it.
30
- runs: (data.runs ?? []).filter(isRoutineRunPayload).map(toRoutineRun),
30
+ runs: (data.runs ?? [])
31
+ .filter(isRoutineRunPayload)
32
+ .map(toRoutineRun),
31
33
  scheduleId,
32
34
  sessionId: data.session_id ?? null,
33
35
  });
@@ -102,32 +102,27 @@ const expenseAutomationReconciliationView = createSlice({
102
102
  draft.selectedTab = 'review';
103
103
  draft.selectedAccountId = action.payload.accountId;
104
104
  }
105
- const accountReconciliationRecords = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(action.payload.selectedPeriod)];
106
- const { transactionFetchState } = accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId];
107
- if (transactionFetchState.fetchState !== 'Completed' ||
105
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(action.payload.selectedPeriod)]?.reconciliationByAccountID[action.payload.accountId];
106
+ if (accountRecon == null) {
107
+ return;
108
+ }
109
+ if (accountRecon.transactionFetchState.fetchState !== 'Completed' ||
108
110
  action.payload.cacheOverride === true) {
109
- const data = accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId];
110
111
  if (action.payload.refreshViewInBackground === true) {
111
- accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId] = {
112
- ...data,
113
- refreshStatus: {
114
- fetchState: 'In-Progress',
115
- error: undefined,
116
- },
117
- accountId: action.payload.accountId,
118
- reconciliationId: undefined,
112
+ accountRecon.refreshStatus = {
113
+ fetchState: 'In-Progress',
114
+ error: undefined,
119
115
  };
116
+ accountRecon.accountId = action.payload.accountId;
117
+ accountRecon.reconciliationId = undefined;
120
118
  }
121
119
  else {
122
- accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId] = {
123
- ...data,
124
- transactionFetchState: {
125
- fetchState: 'In-Progress',
126
- error: undefined,
127
- },
128
- accountId: action.payload.accountId,
129
- reconciliationId: undefined,
120
+ accountRecon.transactionFetchState = {
121
+ fetchState: 'In-Progress',
122
+ error: undefined,
130
123
  };
124
+ accountRecon.accountId = action.payload.accountId;
125
+ accountRecon.reconciliationId = undefined;
131
126
  }
132
127
  }
133
128
  }
@@ -179,14 +174,17 @@ const expenseAutomationReconciliationView = createSlice({
179
174
  }
180
175
  }
181
176
  else {
182
- const accountReconciliationRecords = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(action.payload.selectedPeriod)];
177
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(action.payload.selectedPeriod)]?.reconciliationByAccountID[action.payload.accountId];
178
+ if (accountRecon == null) {
179
+ return;
180
+ }
183
181
  if (action.payload.refreshViewInBackground === true) {
184
- accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId].refreshStatus.error = action.payload.error;
185
- accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId].refreshStatus.fetchState = 'Error';
182
+ accountRecon.refreshStatus.error = action.payload.error;
183
+ accountRecon.refreshStatus.fetchState = 'Error';
186
184
  }
187
185
  else {
188
- accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId].transactionFetchState.error = action.payload.error;
189
- accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId].transactionFetchState.fetchState = 'Error';
186
+ accountRecon.transactionFetchState.error = action.payload.error;
187
+ accountRecon.transactionFetchState.fetchState = 'Error';
190
188
  }
191
189
  }
192
190
  },
@@ -391,8 +389,11 @@ const expenseAutomationReconciliationView = createSlice({
391
389
  },
392
390
  setStatementParseInProgress: (draft, action) => {
393
391
  const { accountId, selectedPeriod, statementParseInProgress } = action.payload;
394
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].statementParseInProgress =
395
- statementParseInProgress;
392
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
393
+ if (accountRecon == null) {
394
+ return;
395
+ }
396
+ accountRecon.statementParseInProgress = statementParseInProgress;
396
397
  },
397
398
  setStatementProcessingDismissedToBackground: (draft, action) => {
398
399
  const { accountId, selectedPeriod, dismissedToBackground } = action.payload;
@@ -476,15 +477,23 @@ const expenseAutomationReconciliationView = createSlice({
476
477
  },
477
478
  uploadAccountStatementSuccess: (draft, action) => {
478
479
  const { accountId, selectedPeriod } = action.payload;
479
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].statementUploadStatus = {
480
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
481
+ if (accountRecon == null) {
482
+ return;
483
+ }
484
+ accountRecon.statementUploadStatus = {
480
485
  fetchState: 'Completed',
481
486
  error: undefined,
482
487
  };
483
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].statementParseInProgress = true;
488
+ accountRecon.statementParseInProgress = true;
484
489
  },
485
490
  uploadAccountStatementFailure: (draft, action) => {
486
491
  const { accountId, selectedPeriod } = action.payload;
487
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].statementUploadStatus = {
492
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
493
+ if (accountRecon == null) {
494
+ return;
495
+ }
496
+ accountRecon.statementUploadStatus = {
488
497
  fetchState: 'Error',
489
498
  error: action.payload.error,
490
499
  };
@@ -677,23 +686,34 @@ const expenseAutomationReconciliationView = createSlice({
677
686
  },
678
687
  reparseStatementFailure: (draft, action) => {
679
688
  const { accountId, selectedPeriod } = action.payload;
680
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].reparseStatementStatus = {
689
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
690
+ if (accountRecon == null) {
691
+ return;
692
+ }
693
+ accountRecon.reparseStatementStatus = {
681
694
  fetchState: 'Error',
682
695
  error: action.payload.error,
683
696
  };
684
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].statementProcessingFailed = true;
697
+ accountRecon.statementProcessingFailed = true;
685
698
  },
686
699
  resetReparseStatementStatus: (draft, action) => {
687
700
  const { accountId, selectedPeriod } = action.payload;
688
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].reparseStatementStatus = {
701
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
702
+ if (accountRecon == null) {
703
+ return;
704
+ }
705
+ accountRecon.reparseStatementStatus = {
689
706
  fetchState: 'Not-Started',
690
707
  error: undefined,
691
708
  };
692
709
  },
693
710
  updateParsedStatementData: (draft, action) => {
694
711
  const { accountId, selectedPeriod, parsedStatementData } = action.payload;
695
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].parsedStatementData =
696
- parsedStatementData;
712
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
713
+ if (accountRecon == null) {
714
+ return;
715
+ }
716
+ accountRecon.parsedStatementData = parsedStatementData;
697
717
  },
698
718
  updateStatementUpdateLocalData: (draft, action) => {
699
719
  const { accountId, selectedPeriod, statementUpdateLocalData, statementUploadId, } = action.payload;
@@ -819,17 +839,21 @@ const expenseAutomationReconciliationView = createSlice({
819
839
  },
820
840
  updateAccountReconciliationLocalData: (draft, action) => {
821
841
  const { accountId, selectedPeriod, localData } = action.payload;
822
- const oldLocalData = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].localData;
842
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
843
+ if (accountRecon == null) {
844
+ return;
845
+ }
846
+ const oldLocalData = accountRecon.localData;
823
847
  if (oldLocalData == null) {
824
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].localData = localData;
848
+ accountRecon.localData = localData;
825
849
  }
826
850
  else {
827
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].localData = {
851
+ accountRecon.localData = {
828
852
  ...oldLocalData,
829
853
  ...localData,
830
854
  };
831
855
  }
832
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].localData = {
856
+ accountRecon.localData = {
833
857
  ...oldLocalData,
834
858
  ...localData,
835
859
  };
@@ -1119,7 +1143,10 @@ function updateAllReconciliation(draft, accounts, selectedPeriod, reconciliation
1119
1143
  }
1120
1144
  }
1121
1145
  function updateReconciliationByAccountID(draft, accounts, selectedPeriod, accountId, reconciliationData, refreshViewInBackground) {
1122
- const oldRecord = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId];
1146
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
1147
+ if (accountRecon == null) {
1148
+ return;
1149
+ }
1123
1150
  const oldConnectionStatus = draft.accountConnectionStatusByAccountID[accountId];
1124
1151
  draft.accountConnectionStatusByAccountID[accountId] = {
1125
1152
  status: toBankStatusCodeType(reconciliationData.bank_status.code),
@@ -1129,21 +1156,17 @@ function updateReconciliationByAccountID(draft, accounts, selectedPeriod, accoun
1129
1156
  : false,
1130
1157
  paymentAccountId: accounts.payment_account_id ?? undefined,
1131
1158
  };
1132
- const statementParseInProgress = resolveStatementParseInProgressAfterFetch(oldRecord, reconciliationData);
1159
+ const statementParseInProgress = resolveStatementParseInProgressAfterFetch(accountRecon, reconciliationData);
1160
+ accountRecon.reconciliationId =
1161
+ reconciliationData.reconciliation_id ?? undefined;
1162
+ accountRecon.statementParseInProgress = statementParseInProgress;
1133
1163
  if (refreshViewInBackground === true) {
1134
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId] = {
1135
- ...oldRecord,
1136
- reconciliationId: reconciliationData.reconciliation_id ?? undefined,
1137
- refreshStatus: { fetchState: 'Completed', error: undefined },
1138
- statementParseInProgress,
1139
- };
1164
+ accountRecon.refreshStatus = { fetchState: 'Completed', error: undefined };
1140
1165
  }
1141
1166
  else {
1142
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId] = {
1143
- ...oldRecord,
1144
- statementParseInProgress,
1145
- reconciliationId: reconciliationData.reconciliation_id ?? undefined,
1146
- transactionFetchState: { fetchState: 'Completed', error: undefined },
1167
+ accountRecon.transactionFetchState = {
1168
+ fetchState: 'Completed',
1169
+ error: undefined,
1147
1170
  };
1148
1171
  }
1149
1172
  }
@@ -1173,7 +1196,11 @@ const updateExcludedAccountExclusionInfo = (draft, account) => {
1173
1196
  };
1174
1197
  };
1175
1198
  const updateReconciliationByAccountIDOnIncludeAccount = (draft, accountId, reconciliation, monthYearPeriodId) => {
1176
- draft.accountReconciliationRecordsBySelectedPeriod[monthYearPeriodId].reconciliationByAccountID[accountId] = {
1199
+ const accountReconciliationRecords = draft.accountReconciliationRecordsBySelectedPeriod[monthYearPeriodId];
1200
+ if (accountReconciliationRecords == null) {
1201
+ return;
1202
+ }
1203
+ accountReconciliationRecords.reconciliationByAccountID[accountId] = {
1177
1204
  accountId,
1178
1205
  reconciliationId: reconciliation.reconciliation_id ?? undefined,
1179
1206
  transactionFetchState: { fetchState: 'Not-Started', error: undefined },