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

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 (34) hide show
  1. package/lib/entity/notificationRegistry/notificationRegistryReducer.d.ts +1 -2
  2. package/lib/entity/notificationRegistry/notificationRegistryReducer.js +9 -20
  3. package/lib/entity/notificationRegistry/notificationRegistrySelector.d.ts +1 -2
  4. package/lib/entity/notificationRegistry/notificationRegistrySelector.js +1 -7
  5. package/lib/entity/notificationRegistry/notificationRegistryState.d.ts +0 -5
  6. package/lib/entity/notificationRegistry/notificationRegistryState.js +1 -7
  7. package/lib/entity/sectionProjectView/sectionProjectViewReducer.js +1 -3
  8. package/lib/epic.js +4 -4
  9. package/lib/esm/entity/notificationRegistry/notificationRegistryReducer.js +9 -20
  10. package/lib/esm/entity/notificationRegistry/notificationRegistrySelector.js +0 -5
  11. package/lib/esm/entity/notificationRegistry/notificationRegistryState.js +0 -5
  12. package/lib/esm/entity/sectionProjectView/sectionProjectViewReducer.js +1 -3
  13. package/lib/esm/epic.js +4 -4
  14. package/lib/esm/index.js +4 -4
  15. package/lib/esm/view/aiCfoView/aiCfoViewReducer.js +2 -4
  16. package/lib/esm/view/aiCfoView/epics/fetchRoutineRunsEpic.js +1 -3
  17. package/lib/esm/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +7 -20
  18. package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewReducer.js +17 -43
  19. package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewSelector.js +4 -24
  20. package/lib/esm/view/settingsView/epic/fetchNotificationSettingsEpic.js +2 -12
  21. package/lib/index.d.ts +4 -4
  22. package/lib/index.js +14 -21
  23. package/lib/view/aiCfoView/aiCfoViewReducer.js +2 -4
  24. package/lib/view/aiCfoView/aiCfoViewSelector.d.ts +1 -1
  25. package/lib/view/aiCfoView/epics/fetchRoutineRunsEpic.js +1 -3
  26. package/lib/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +6 -19
  27. package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.d.ts +5 -8
  28. package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.js +17 -43
  29. package/lib/view/notificationPreferencesView/notificationPreferencesViewSelector.d.ts +0 -4
  30. package/lib/view/notificationPreferencesView/notificationPreferencesViewSelector.js +5 -30
  31. package/lib/view/notificationPreferencesView/notificationPreferencesViewState.d.ts +3 -4
  32. package/lib/view/settingsView/epic/fetchNotificationSettingsEpic.js +2 -12
  33. package/lib/view/settingsView/settingsViewPayload.d.ts +0 -2
  34. package/package.json +1 -1
@@ -1,10 +1,9 @@
1
1
  import { NotificationRegistryPayload } from './notificationRegistryPayload';
2
- import { NotificationDomain, NotificationRegistryState } from './notificationRegistryState';
2
+ import { 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>>;
8
7
  }
9
8
  export declare const updateNotificationRegistry: import("@reduxjs/toolkit").ActionCreatorWithPayload<UpdateNotificationRegistryPayload, "notificationRegistry/updateNotificationRegistry">;
10
9
  declare const _default: import("redux").Reducer<NotificationRegistryState>;
@@ -4,7 +4,6 @@ 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");
8
7
  exports.initialState = { registryEpoch: 0 };
9
8
  const notificationRegistry = (0, toolkit_1.createSlice)({
10
9
  name: 'notificationRegistry',
@@ -23,24 +22,16 @@ const notificationRegistry = (0, toolkit_1.createSlice)({
23
22
  // `task_checklist_registry` CLEARS so tenant B doesn't inherit A's
24
23
  // Task Checklist section. Guarded by the epoch check above so a stale
25
24
  // absent-response can't wipe a fresh registry.
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
- }
25
+ if (action.payload.registry == null) {
26
+ draft.registry = undefined;
27
+ return;
40
28
  }
41
- // Mirrored for consumers that predate the domain-keyed map.
42
- draft.registry = draft.registryByDomain.task_checklist;
43
- return;
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);
44
35
  },
45
36
  },
46
37
  extraReducers: (builder) => {
@@ -53,8 +44,6 @@ const notificationRegistry = (0, toolkit_1.createSlice)({
53
44
  // — Cursor Bugbot 67d6918e. Returning `initialState` verbatim would
54
45
  // rewind the counter and let a stale in-flight GET restore A's
55
46
  // registry over B's.
56
- // Drops `registry` and `registryByDomain` together: a tenant switch must
57
- // not leave one domain's section behind.
58
47
  return { registryEpoch: draft.registryEpoch + 1 };
59
48
  });
60
49
  },
@@ -1,3 +1,2 @@
1
- import { NotificationDomain, NotificationRegistry, NotificationRegistryState } from './notificationRegistryState';
1
+ import { 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.getNotificationRegistryForDomain = exports.getNotificationRegistry = void 0;
3
+ 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,9 +8,3 @@ exports.getNotificationRegistryForDomain = exports.getNotificationRegistry = voi
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,7 +1,3 @@
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;
5
1
  export declare const NOTIFICATION_CHANNELS: readonly ["email", "slack", "in_app"];
6
2
  export type NotificationChannel = (typeof NOTIFICATION_CHANNELS)[number];
7
3
  export declare const toNotificationChannel: (raw: string) => NotificationChannel;
@@ -34,5 +30,4 @@ export interface NotificationRegistry {
34
30
  export interface NotificationRegistryState {
35
31
  registryEpoch: number;
36
32
  registry?: NotificationRegistry;
37
- registryByDomain?: Partial<Record<NotificationDomain, NotificationRegistry>>;
38
33
  }
@@ -1,13 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toNotificationFrequency = exports.NOTIFICATION_FREQUENCIES = exports.toNotificationChannel = exports.NOTIFICATION_CHANNELS = exports.toNotificationDomain = exports.DEFAULT_NOTIFICATION_DOMAIN = exports.NOTIFICATION_DOMAINS = void 0;
3
+ exports.toNotificationFrequency = exports.NOTIFICATION_FREQUENCIES = exports.toNotificationChannel = exports.NOTIFICATION_CHANNELS = 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;
11
5
  exports.NOTIFICATION_CHANNELS = ['email', 'slack', 'in_app'];
12
6
  const toNotificationChannel = (raw) => (0, stringToUnion_1.stringToUnion)(raw, exports.NOTIFICATION_CHANNELS);
13
7
  exports.toNotificationChannel = toNotificationChannel;
@@ -35,9 +35,7 @@ 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
39
- ? projectPayload.accounts
40
- : undefined;
38
+ const projectAccounts = 'accounts' in projectPayload ? projectPayload.accounts : undefined;
41
39
  const nestedAccountIds = projectAccounts != null
42
40
  ? (0, getNestedAccountIDHierarchyForSection_1.getNestedAccountIDHierarchyForAccounts)(Object.keys(projectAccounts), accountsMetadata)
43
41
  : [];
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");
57
56
  const deleteSkillEpic_1 = require("./view/aiCfoView/epics/deleteSkillEpic");
58
57
  const fetchChatHistoryEpic_1 = require("./view/aiCfoView/epics/fetchChatHistoryEpic");
59
58
  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");
62
59
  const fetchSkillDetailEpic_1 = require("./view/aiCfoView/epics/fetchSkillDetailEpic");
63
60
  const fetchSkillVersionBodyEpic_1 = require("./view/aiCfoView/epics/fetchSkillVersionBodyEpic");
64
61
  const fetchSkillVersionsEpic_1 = require("./view/aiCfoView/epics/fetchSkillVersionsEpic");
65
62
  const fetchSkillsEpic_1 = require("./view/aiCfoView/epics/fetchSkillsEpic");
66
63
  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 saveSkillEpic_1 = require("./view/aiCfoView/epics/saveSkillEpic");
70
69
  const setRoutineEnabledEpic_1 = require("./view/aiCfoView/epics/setRoutineEnabledEpic");
70
+ const saveSkillEpic_1 = require("./view/aiCfoView/epics/saveSkillEpic");
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,7 +1,6 @@
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';
5
4
  export const initialState = { registryEpoch: 0 };
6
5
  const notificationRegistry = createSlice({
7
6
  name: 'notificationRegistry',
@@ -20,24 +19,16 @@ const notificationRegistry = createSlice({
20
19
  // `task_checklist_registry` CLEARS so tenant B doesn't inherit A's
21
20
  // Task Checklist section. Guarded by the epoch check above so a stale
22
21
  // absent-response can't wipe a fresh registry.
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
- }
22
+ if (action.payload.registry == null) {
23
+ draft.registry = undefined;
24
+ return;
37
25
  }
38
- // Mirrored for consumers that predate the domain-keyed map.
39
- draft.registry = draft.registryByDomain.task_checklist;
40
- return;
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);
41
32
  },
42
33
  },
43
34
  extraReducers: (builder) => {
@@ -50,8 +41,6 @@ const notificationRegistry = createSlice({
50
41
  // — Cursor Bugbot 67d6918e. Returning `initialState` verbatim would
51
42
  // rewind the counter and let a stale in-flight GET restore A's
52
43
  // registry over B's.
53
- // Drops `registry` and `registryByDomain` together: a tenant switch must
54
- // not leave one domain's section behind.
55
44
  return { registryEpoch: draft.registryEpoch + 1 };
56
45
  });
57
46
  },
@@ -4,8 +4,3 @@
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,9 +1,4 @@
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);
7
2
  export const NOTIFICATION_CHANNELS = ['email', 'slack', 'in_app'];
8
3
  export const toNotificationChannel = (raw) => stringToUnion(raw, NOTIFICATION_CHANNELS);
9
4
  export const NOTIFICATION_FREQUENCIES = ['instant', 'daily'];
@@ -32,9 +32,7 @@ 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
36
- ? projectPayload.accounts
37
- : undefined;
35
+ const projectAccounts = 'accounts' in projectPayload ? projectPayload.accounts : undefined;
38
36
  const nestedAccountIds = projectAccounts != null
39
37
  ? getNestedAccountIDHierarchyForAccounts(Object.keys(projectAccounts), accountsMetadata)
40
38
  : [];
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';
54
53
  import { deleteSkillEpic } from './view/aiCfoView/epics/deleteSkillEpic';
55
54
  import { fetchChatHistoryEpic, } from './view/aiCfoView/epics/fetchChatHistoryEpic';
56
55
  import { fetchChatSessionsForUserEpic, } from './view/aiCfoView/epics/fetchChatSessionsForUserEpic';
57
- import { fetchRoutineRunsEpic } from './view/aiCfoView/epics/fetchRoutineRunsEpic';
58
- import { fetchRoutinesEpic } from './view/aiCfoView/epics/fetchRoutinesEpic';
59
56
  import { fetchSkillDetailEpic } from './view/aiCfoView/epics/fetchSkillDetailEpic';
60
57
  import { fetchSkillVersionBodyEpic } from './view/aiCfoView/epics/fetchSkillVersionBodyEpic';
61
58
  import { fetchSkillVersionsEpic } from './view/aiCfoView/epics/fetchSkillVersionsEpic';
62
59
  import { fetchSkillsEpic, } from './view/aiCfoView/epics/fetchSkillsEpic';
63
60
  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 { saveSkillEpic } from './view/aiCfoView/epics/saveSkillEpic';
67
66
  import { setRoutineEnabledEpic } from './view/aiCfoView/epics/setRoutineEnabledEpic';
67
+ import { saveSkillEpic } from './view/aiCfoView/epics/saveSkillEpic';
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, getNotificationRegistryForDomain, } from './entity/notificationRegistry/notificationRegistrySelector';
68
- import { DEFAULT_NOTIFICATION_DOMAIN, NOTIFICATION_DOMAINS, toNotificationChannel, toNotificationDomain, toNotificationFrequency, } from './entity/notificationRegistry/notificationRegistryState';
67
+ import { getNotificationRegistry } from './entity/notificationRegistry/notificationRegistrySelector';
68
+ import { toNotificationChannel, 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';
@@ -234,7 +234,7 @@ import { toAverageMonthsCount } from './view/netBurnOrIncomeStoryCard/epic/updat
234
234
  import { fetchNetBurnOrIncomeStoryCard, updateNetBurnOrIncomeStoryCardSettings, } from './view/netBurnOrIncomeStoryCard/netBurnOrIncomeStoryCardReducer';
235
235
  import { getNetBurnOrIncomeStoryCardReport } from './view/netBurnOrIncomeStoryCard/netBurnOrIncomeStoryCardSelector';
236
236
  import { clearAllNotificationPreferencesView, clearNotificationPreferencesLocalOverrides, saveNotificationPreferences, setGroupChannelMaster, setGroupFrequency, toggleEventChannel, } from './view/notificationPreferencesView/notificationPreferencesViewReducer';
237
- import { getEffectiveNotificationPreferences, getEffectiveNotificationPreferencesForDomain, getNotificationLocalOverrides, getNotificationLocalOverridesForDomain, getNotificationPreferencesSaveState, hasUnsavedNotificationPreferences, hasUnsavedNotificationPreferencesForDomain, } from './view/notificationPreferencesView/notificationPreferencesViewSelector';
237
+ import { getEffectiveNotificationPreferences, getNotificationLocalOverrides, getNotificationPreferencesSaveState, hasUnsavedNotificationPreferences, } from './view/notificationPreferencesView/notificationPreferencesViewSelector';
238
238
  import { fetchNotificationUnreadCount, fetchNotificationUnreadCountSuccess, fetchNotificationView, updateNotificationViewAllNotificationsStatus, updateNotificationViewCurrentTabAndSubTab, updateNotificationViewNotificationStatus, updateNotificationViewSubTab, updateNotificationViewTabState, updateNotificationViewUIState, } from './view/notificationView/notificationViewReducer';
239
239
  import { getExternalNotificationsForSelectedSubTab, getNotificationView, getNotificationsForSelectedSubTab, } from './view/notificationView/notificationViewSelector';
240
240
  import { toNotificationSubTabTypeStrict, toNotificationTabTypeStrict, } from './view/notificationView/notificationViewState';
@@ -677,7 +677,7 @@ export { toNotificationModeStrict, updateCommentsNotifications, updateCommentsNo
677
677
  export { toNotificationSubTabTypeStrict, toNotificationTabTypeStrict, fetchNotificationView, fetchNotificationUnreadCount, fetchNotificationUnreadCountSuccess, updateNotificationViewAllNotificationsStatus, updateNotificationViewNotificationStatus, updateNotificationViewTabState, updateNotificationViewCurrentTabAndSubTab, updateNotificationViewSubTab, updateNotificationViewUIState, getNotificationView, getExternalNotificationsForSelectedSubTab, getNotificationsForSelectedSubTab, };
678
678
  export { fetchRegisteredInterests, notifyMeForFeature, getFeatureNotificationView, getRegisteredInterests, getRegisteredInterestsByFeature, isFeatureInterestRegistered, };
679
679
  export { pushToastNotification, getLastNotificationTime, getNotifications, };
680
- export { getEffectiveNotificationPreferences, getEffectiveNotificationPreferencesForDomain, getNotificationLocalOverrides, getNotificationLocalOverridesForDomain, getNotificationPreferencesSaveState, getNotificationRegistry, getNotificationRegistryForDomain, hasUnsavedNotificationPreferences, hasUnsavedNotificationPreferencesForDomain, NOTIFICATION_DOMAINS, DEFAULT_NOTIFICATION_DOMAIN, clearAllNotificationPreferencesView, clearNotificationPreferencesLocalOverrides, saveNotificationPreferences, setGroupChannelMaster, setGroupFrequency, toggleEventChannel, toNotificationChannel, toNotificationDomain, toNotificationFrequency, };
680
+ export { getEffectiveNotificationPreferences, getNotificationLocalOverrides, getNotificationPreferencesSaveState, getNotificationRegistry, hasUnsavedNotificationPreferences, clearAllNotificationPreferencesView, clearNotificationPreferencesLocalOverrides, saveNotificationPreferences, setGroupChannelMaster, setGroupFrequency, toggleEventChannel, toNotificationChannel, toNotificationFrequency, };
681
681
  export { getReferralListView, getInviteFormView, toReferralListViewSortKeyType, StatusTypes, AmountStatusTypes, DEFAULT_REFERRER_AMOUNT, fetchReferrals, sendReferralInvite, clearReferrals, saveReferralFormDataInLocalStore, updateReferralListSortUiState, resendReferralInvite, fetchRewardsPlan, getRewardsPlanCard, updateReferViewed, };
682
682
  export { ALL_WEEK_DAYS, SEMI_WEEKLY_REQUIRED_DAYS_COUNT, getMinAllowedEndDate, getRecurringEndDateFromCount, toDayOfWeek, toRecurringFrequency, };
683
683
  export { fetchCockpitContext, fetchCompanyTaskManagerView, fetchTaskManagerMetrics, getCompanyTaskManagerView, createTaskFromTaskGroupTemplate, };
@@ -835,8 +835,7 @@ 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 =
839
- draft.routines.deletedScheduleIds.filter((id) => id !== action.payload.scheduleId);
838
+ draft.routines.deletedScheduleIds = draft.routines.deletedScheduleIds.filter((id) => id !== action.payload.scheduleId);
840
839
  draft.routineMutation = {
841
840
  fetchState: 'Completed',
842
841
  error: undefined,
@@ -848,8 +847,7 @@ const aiCfoView = createSlice({
848
847
  // is what lets the refetch put the row back — leaving it hidden would
849
848
  // tell the user they had stopped something they had not, which is the
850
849
  // worst outcome this list can produce.
851
- draft.routines.deletedScheduleIds =
852
- draft.routines.deletedScheduleIds.filter((id) => id !== action.payload.scheduleId);
850
+ draft.routines.deletedScheduleIds = draft.routines.deletedScheduleIds.filter((id) => id !== action.payload.scheduleId);
853
851
  // Its own slice: the refetch that puts the row back would otherwise wipe
854
852
  // this error, leaving the row silently reappearing with no explanation.
855
853
  draft.routineMutation = {
@@ -27,9 +27,7 @@ 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 ?? [])
31
- .filter(isRoutineRunPayload)
32
- .map(toRoutineRun),
30
+ runs: (data.runs ?? []).filter(isRoutineRunPayload).map(toRoutineRun),
33
31
  scheduleId,
34
32
  sessionId: data.session_id ?? null,
35
33
  });
@@ -1,12 +1,11 @@
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';
4
3
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
5
4
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../responsePayload';
6
5
  import { notificationPreferencesUrl } from '../notificationPreferencesEndpoint';
7
6
  import { mapPreferencesToPayload, } from '../notificationPreferencesViewPayload';
8
7
  import { clearAllNotificationPreferencesView, clearNotificationPreferencesLocalOverrides, saveNotificationPreferences, saveNotificationPreferencesFailure, saveNotificationPreferencesSuccess, } from '../notificationPreferencesViewReducer';
9
- import { getNotificationLocalOverridesForDomain } from '../notificationPreferencesViewSelector';
8
+ import { getNotificationLocalOverrides } from '../notificationPreferencesViewSelector';
10
9
  export const saveNotificationPreferencesEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter((action) => saveNotificationPreferences.match(action)), withLatestFrom(state$),
11
10
  // `switchMap` unsubscribes from any in-flight PUT observable when a newer
12
11
  // Save fires. The prior HTTP request has already left the
@@ -14,19 +13,9 @@ export const saveNotificationPreferencesEpic = (actions$, state$, zeniAPI) => ac
14
13
  // ignored, so stale success/failure actions cannot clobber the newer
15
14
  // batch's state. Server-side ordering is arrival-order LWW.
16
15
  switchMap(([, state]) => {
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) {
16
+ const overrides = getNotificationLocalOverrides(state);
17
+ const preferencesPatch = mapPreferencesToPayload(overrides);
18
+ if (Object.keys(preferencesPatch).length === 0) {
30
19
  // No diff → do not dispatch a synthetic success (that would wipe
31
20
  // stored preferences via the reducer). Simply no-op.
32
21
  return EMPTY;
@@ -39,6 +28,7 @@ switchMap(([, state]) => {
39
28
  // in-flight save's response (Success/Failure never dispatched, so
40
29
  // the counter never drained and future saves got their merge dropped).
41
30
  const cancelEpochAtDispatch = state.notificationPreferencesViewState.cancelEpoch;
31
+ const body = { task_checklist_preferences: preferencesPatch };
42
32
  // Suppress the error snackbar when the response lands AFTER the user
43
33
  // pressed Cancel — the Failure reducer already swallows the state
44
34
  // update, so the toast would be a stale error for a save the user
@@ -67,11 +57,8 @@ switchMap(([, state]) => {
67
57
  if (isSuccessResponse(response)) {
68
58
  return of(saveNotificationPreferencesSuccess({
69
59
  cancelEpochAtDispatch,
70
- savedOverridesByDomain: overridesByDomain,
71
- serverEchoByDomain: {
72
- task_checklist: response.data?.task_checklist_preferences,
73
- commenting: response.data?.commenting_preferences,
74
- },
60
+ savedOverrides: overrides,
61
+ serverEcho: response.data?.task_checklist_preferences,
75
62
  }));
76
63
  }
77
64
  const actions = [
@@ -1,25 +1,14 @@
1
1
  import { createSlice } from '@reduxjs/toolkit';
2
- import { DEFAULT_NOTIFICATION_DOMAIN, } from '../../entity/notificationRegistry/notificationRegistryState';
3
2
  import { clearAll } from '../../rootActions';
4
3
  import { mapPayloadToPreferences, } from './notificationPreferencesViewPayload';
5
4
  import { emptyNotificationPreferences, } from './notificationPreferencesViewState';
6
5
  export const initialState = {
7
6
  cancelEpoch: 0,
8
- localOverridesByDomain: {},
9
- preferencesByDomain: {},
7
+ localOverrides: emptyNotificationPreferences(),
8
+ preferences: emptyNotificationPreferences(),
10
9
  saveEpoch: 0,
11
10
  savePreferencesState: { fetchState: 'Not-Started' },
12
11
  };
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
- };
23
12
  const mergePreferences = (base, patch) => {
24
13
  const eventEnabledByChannel = {
25
14
  ...base.eventEnabledByChannel,
@@ -157,8 +146,7 @@ const notificationPreferencesView = createSlice({
157
146
  // Success/Failure never dispatches, subsequent legitimate saves
158
147
  // still work because their captured epoch matches the current one.
159
148
  draft.cancelEpoch += 1;
160
- // One shared Save/Cancel footer, so Cancel discards every domain.
161
- draft.localOverridesByDomain = {};
149
+ draft.localOverrides = emptyNotificationPreferences();
162
150
  // Also drop `savePreferencesState` back to Not-Started so any
163
151
  // "Saving…" affordance bound to `getNotificationPreferencesSaveState`
164
152
  // clears on Cancel. Without this, a Cancel-during-save whose stale
@@ -175,6 +163,9 @@ const notificationPreferencesView = createSlice({
175
163
  // them; the save-success path is the only clearer.
176
164
  // A `null`/`undefined` payload isn't a snapshot at all (e.g. the
177
165
  // envelope key was omitted); no-op instead of wiping preferences.
166
+ if (action.payload.preferences == null) {
167
+ return;
168
+ }
178
169
  // Stale-GET guard (Cursor Bugbot 3632510237): if the fetch epic
179
170
  // captured a `saveEpoch` lower than the current one, a save has
180
171
  // succeeded since this GET dispatched — the response is carrying
@@ -184,13 +175,7 @@ const notificationPreferencesView = createSlice({
184
175
  if (action.payload.saveEpochAtDispatch < draft.saveEpoch) {
185
176
  return;
186
177
  }
187
- Object.entries(action.payload.preferencesByDomain).forEach(([domain, payload]) => {
188
- if (payload == null) {
189
- return;
190
- }
191
- draft.preferencesByDomain[domain] =
192
- mapPayloadToPreferences(payload);
193
- });
178
+ draft.preferences = mapPayloadToPreferences(action.payload.preferences);
194
179
  },
195
180
  saveNotificationPreferences(draft) {
196
181
  draft.savePreferencesState = { fetchState: 'In-Progress' };
@@ -249,44 +234,33 @@ const notificationPreferencesView = createSlice({
249
234
  // skip the `preferences` replace. Cursor Bugbot 3632510237.
250
235
  draft.saveEpoch += 1;
251
236
  draft.savePreferencesState = { fetchState: 'Completed' };
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
- });
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);
265
242
  },
266
243
  setGroupChannelMaster(draft, action) {
267
244
  // Mirror of `toggleEventChannel` but scoped to the group-channel master
268
245
  // held under `groupChannelEnabledByGroupId[groupId][channel]`.
269
246
  const { channel, enabled, groupId } = action.payload;
270
- const overrides = overridesFor(draft.localOverridesByDomain, action.payload.domain ?? DEFAULT_NOTIFICATION_DOMAIN);
271
247
  const groupMap = {
272
- ...(overrides.groupChannelEnabledByGroupId[groupId] ?? {}),
248
+ ...(draft.localOverrides.groupChannelEnabledByGroupId[groupId] ?? {}),
273
249
  };
274
250
  groupMap[channel] = enabled;
275
- overrides.groupChannelEnabledByGroupId[groupId] = groupMap;
251
+ draft.localOverrides.groupChannelEnabledByGroupId[groupId] = groupMap;
276
252
  },
277
253
  setGroupFrequency(draft, action) {
278
- const overrides = overridesFor(draft.localOverridesByDomain, action.payload.domain ?? DEFAULT_NOTIFICATION_DOMAIN);
279
- overrides.groupFrequencyByGroupId[action.payload.groupId] =
254
+ draft.localOverrides.groupFrequencyByGroupId[action.payload.groupId] =
280
255
  action.payload.frequency;
281
256
  },
282
257
  toggleEventChannel(draft, action) {
283
258
  const { channel, enabled, eventId } = action.payload;
284
- const overrides = overridesFor(draft.localOverridesByDomain, action.payload.domain ?? DEFAULT_NOTIFICATION_DOMAIN);
285
259
  const eventMap = {
286
- ...(overrides.eventEnabledByChannel[eventId] ?? {}),
260
+ ...(draft.localOverrides.eventEnabledByChannel[eventId] ?? {}),
287
261
  };
288
262
  eventMap[channel] = enabled;
289
- overrides.eventEnabledByChannel[eventId] = eventMap;
263
+ draft.localOverrides.eventEnabledByChannel[eventId] = eventMap;
290
264
  },
291
265
  },
292
266
  extraReducers: (builder) => {
@@ -1,18 +1,11 @@
1
1
  import { createSelector } from '@reduxjs/toolkit';
2
- import { DEFAULT_NOTIFICATION_DOMAIN, } from '../../entity/notificationRegistry/notificationRegistryState';
3
- import { emptyNotificationPreferences, } from './notificationPreferencesViewState';
4
2
  const getView = (state) => state.notificationPreferencesViewState;
5
3
  export const getNotificationPreferencesSaveState = (state) => getView(state).savePreferencesState;
6
4
  // Memoised on `preferences` + `localOverrides` so the merged shape keeps a
7
5
  // stable reference while neither input changes — a plain function would
8
6
  // allocate a fresh object on every call and re-render every `useSelector`
9
7
  // consumer on any unrelated dispatch.
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;
8
+ export const getEffectiveNotificationPreferences = createSelector((state) => getView(state).preferences, (state) => getView(state).localOverrides, (preferences, localOverrides) => {
16
9
  const mergedEventEnabledByChannel = { ...preferences.eventEnabledByChannel };
17
10
  Object.entries(localOverrides.eventEnabledByChannel).forEach(([eventId, channels]) => {
18
11
  mergedEventEnabledByChannel[eventId] = {
@@ -36,20 +29,7 @@ const createEffectivePreferencesSelector = (domain) => createSelector((state) =>
36
29
  },
37
30
  };
38
31
  });
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 ||
32
+ export const getNotificationLocalOverrides = (state) => getView(state).localOverrides;
33
+ export const hasUnsavedNotificationPreferences = createSelector((state) => getView(state).localOverrides, (localOverrides) => Object.keys(localOverrides.eventEnabledByChannel).length > 0 ||
51
34
  Object.keys(localOverrides.groupChannelEnabledByGroupId).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));
35
+ Object.keys(localOverrides.groupFrequencyByGroupId).length > 0);
@@ -38,21 +38,11 @@ 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
- },
46
41
  registryEpochAtDispatch,
47
42
  }));
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)) {
43
+ if (response.data.task_checklist_preferences != null) {
54
44
  updateActions.push(fetchNotificationPreferencesSuccess({
55
- preferencesByDomain,
45
+ preferences: response.data.task_checklist_preferences,
56
46
  saveEpochAtDispatch,
57
47
  }));
58
48
  }