chore: migrate simple server-side moment usages to date-fns#40077
chore: migrate simple server-side moment usages to date-fns#40077
Conversation
|
Looks like this PR is not ready to merge, because of the following issues:
Please fix the issues and try again If you have any trouble, please check the PR guidelines |
|
WalkthroughLarge systematic refactoring replacing Moment.js and moment-timezone dependencies with date-fns and native JavaScript Date APIs across ~40 files, maintaining equivalent control flow and business logic while updating timestamp calculations, formatting, arithmetic, and timezone handling patterns. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
11 issues found across 71 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/meteor/app/cloud/server/functions/buildRegistrationData.ts">
<violation number="1" location="apps/meteor/app/cloud/server/functions/buildRegistrationData.ts:72">
P2: `format(new Date(), 'yyyy-MM')` uses local time, but the previous `moment.utc()` call produced a UTC month. This can shift the billing period at month boundaries depending on server timezone. Use a UTC-based formatter to preserve behavior.</violation>
</file>
<file name="apps/meteor/server/services/omnichannel/service.ts">
<violation number="1" location="apps/meteor/server/services/omnichannel/service.ts:56">
P2: `format(new Date(), 'yyyy-MM')` formats in local time, but the previous logic used UTC. This can shift the month value around UTC boundaries and cause MAC activity to be checked against the wrong month. Use a UTC-based month string to preserve behavior.</violation>
</file>
<file name="apps/meteor/app/smarsh-connector/server/functions/generateEml.ts">
<violation number="1" location="apps/meteor/app/smarsh-connector/server/functions/generateEml.ts:66">
P2: Guard `Intl.DateTimeFormat` against invalid timezone values from settings; an invalid `Smarsh_Timezone` will throw and stop export generation.</violation>
</file>
<file name="apps/meteor/app/livechat/server/business-hour/filterBusinessHoursThatMustBeOpened.ts">
<violation number="1" location="apps/meteor/app/livechat/server/business-hour/filterBusinessHoursThatMustBeOpened.ts:52">
P2: This same‑day normalization marks hours as open before the start time (e.g., Monday 09:00 vs a 18:00–20:00 range). Remove this adjustment; the same‑day case should not subtract a week.</violation>
</file>
<file name="ee/packages/pdf-worker/src/strategies/ChatTranscript.spec.ts">
<violation number="1" location="ee/packages/pdf-worker/src/strategies/ChatTranscript.spec.ts:51">
P2: This test was weakened to only assert that `divider` is a string, so timezone/date-format regressions can pass undetected. It should still validate the expected formatted value.</violation>
</file>
<file name="packages/tools/src/timezone.ts">
<violation number="1" location="packages/tools/src/timezone.ts:19">
P1: `GMT` (UTC) is parsed incorrectly, producing `+NaN:00` and breaking `+00:00` timezone matching.</violation>
</file>
<file name="apps/meteor/app/livechat/server/lib/sendTranscript.ts">
<violation number="1" location="apps/meteor/app/livechat/server/lib/sendTranscript.ts:153">
P1: Guard `Intl.DateTimeFormat` against invalid timezone values; an invalid custom timezone will throw `RangeError` and break transcript sending.</violation>
</file>
<file name="apps/meteor/server/services/omnichannel-analytics/utils.ts">
<violation number="1" location="apps/meteor/server/services/omnichannel-analytics/utils.ts:9">
P1: `dayIterator` re-normalizes timezone-adjusted dates with `startOfDay`, shifting daily windows away from the requested timezone boundaries.</violation>
</file>
<file name="apps/meteor/app/statistics/server/lib/statistics.ts">
<violation number="1" location="apps/meteor/app/statistics/server/lib/statistics.ts:249">
P2: Billable period switched from UTC month to local-time month, which can select the wrong period near UTC month boundaries.</violation>
</file>
<file name="apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts">
<violation number="1" location="apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts:16">
P2: Using a fixed January 2025 reference date forces standard-time offsets, so DST timezones will generate UTC cron times that are one hour off during summer. Build the reference date from the current week in the target timezone so the offset matches the active DST rules.</violation>
</file>
<file name="apps/meteor/app/livechat/server/hooks/processRoomAbandonment.ts">
<violation number="1" location="apps/meteor/app/livechat/server/hooks/processRoomAbandonment.ts:55">
P1: UTC semantics were dropped during migration, so business-hour inactivity can be calculated against the wrong day/time on non-UTC servers.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| const datetime = new Intl.DateTimeFormat(userLanguage, { | ||
| timeZone: timezone, | ||
| dateStyle: 'long', | ||
| timeStyle: 'short', | ||
| }).format(new Date(message.ts)); |
There was a problem hiding this comment.
P1: Guard Intl.DateTimeFormat against invalid timezone values; an invalid custom timezone will throw RangeError and break transcript sending.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/meteor/app/livechat/server/lib/sendTranscript.ts, line 153:
<comment>Guard `Intl.DateTimeFormat` against invalid timezone values; an invalid custom timezone will throw `RangeError` and break transcript sending.</comment>
<file context>
@@ -151,7 +150,11 @@ export async function sendTranscript({
}
- const datetime = moment.tz(message.ts, timezone).locale(userLanguage).format('LLL');
+ const datetime = new Intl.DateTimeFormat(userLanguage, {
+ timeZone: timezone,
+ dateStyle: 'long',
</file context>
| const datetime = new Intl.DateTimeFormat(userLanguage, { | |
| timeZone: timezone, | |
| dateStyle: 'long', | |
| timeStyle: 'short', | |
| }).format(new Date(message.ts)); | |
| const date = new Date(message.ts); | |
| const datetime = (() => { | |
| try { | |
| return new Intl.DateTimeFormat(userLanguage, { | |
| timeZone: timezone, | |
| dateStyle: 'long', | |
| timeStyle: 'short', | |
| }).format(date); | |
| } catch { | |
| return new Intl.DateTimeFormat(userLanguage, { | |
| dateStyle: 'long', | |
| timeStyle: 'short', | |
| }).format(date); | |
| } | |
| })(); |
| yield m; | ||
| m.add(1, 'days'); | ||
| export async function* dayIterator(from: Date, to: Date): AsyncGenerator<Date> { | ||
| let current = startOfDay(from); |
There was a problem hiding this comment.
P1: dayIterator re-normalizes timezone-adjusted dates with startOfDay, shifting daily windows away from the requested timezone boundaries.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/meteor/server/services/omnichannel-analytics/utils.ts, line 9:
<comment>`dayIterator` re-normalizes timezone-adjusted dates with `startOfDay`, shifting daily windows away from the requested timezone boundaries.</comment>
<file context>
@@ -1,30 +1,64 @@
- yield m;
- m.add(1, 'days');
+export async function* dayIterator(from: Date, to: Date): AsyncGenerator<Date> {
+ let current = startOfDay(from);
+ const toStart = startOfDay(to);
+ while (differenceInDays(toStart, current) >= 0) {
</file context>
|
|
||
| const seats = await Users.getActiveLocalUserCount(); | ||
| const MAC = await LivechatContacts.countContactsOnPeriod(moment.utc().format('YYYY-MM')); | ||
| const MAC = await LivechatContacts.countContactsOnPeriod(format(new Date(), 'yyyy-MM')); |
There was a problem hiding this comment.
P2: format(new Date(), 'yyyy-MM') uses local time, but the previous moment.utc() call produced a UTC month. This can shift the billing period at month boundaries depending on server timezone. Use a UTC-based formatter to preserve behavior.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/meteor/app/cloud/server/functions/buildRegistrationData.ts, line 72:
<comment>`format(new Date(), 'yyyy-MM')` uses local time, but the previous `moment.utc()` call produced a UTC month. This can shift the billing period at month boundaries depending on server timezone. Use a UTC-based formatter to preserve behavior.</comment>
<file context>
@@ -69,7 +69,7 @@ export async function buildWorkspaceRegistrationData<T extends string | undefine
const seats = await Users.getActiveLocalUserCount();
- const MAC = await LivechatContacts.countContactsOnPeriod(moment.utc().format('YYYY-MM'));
+ const MAC = await LivechatContacts.countContactsOnPeriod(format(new Date(), 'yyyy-MM'));
const license = settings.get<string>('Enterprise_License');
</file context>
| new Intl.DateTimeFormat('en-US', { | ||
| timeZone, | ||
| year: 'numeric', | ||
| month: '2-digit', | ||
| day: '2-digit', | ||
| hour: '2-digit', | ||
| minute: '2-digit', | ||
| second: '2-digit', | ||
| timeZoneName: 'short', | ||
| hour12: false, | ||
| }) | ||
| .format(new Date(message.ts)) | ||
| .replace(/\//g, '-') | ||
| .replace(/, /g, ' '), | ||
| ); | ||
| rows.push(closetd); |
There was a problem hiding this comment.
P2: Guard Intl.DateTimeFormat against invalid timezone values from settings; an invalid Smarsh_Timezone will throw and stop export generation.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/meteor/app/smarsh-connector/server/functions/generateEml.ts, line 66:
<comment>Guard `Intl.DateTimeFormat` against invalid timezone values from settings; an invalid `Smarsh_Timezone` will throw and stop export generation.</comment>
<file context>
@@ -62,7 +62,22 @@ export const generateEml = async (): Promise<void> => {
rows.push(open20td);
- rows.push(moment(message.ts).tz(timeZone).format('YYYY-MM-DD HH-mm-ss z'));
+ rows.push(
+ new Intl.DateTimeFormat('en-US', {
+ timeZone,
+ year: 'numeric',
</file context>
| new Intl.DateTimeFormat('en-US', { | |
| timeZone, | |
| year: 'numeric', | |
| month: '2-digit', | |
| day: '2-digit', | |
| hour: '2-digit', | |
| minute: '2-digit', | |
| second: '2-digit', | |
| timeZoneName: 'short', | |
| hour12: false, | |
| }) | |
| .format(new Date(message.ts)) | |
| .replace(/\//g, '-') | |
| .replace(/, /g, ' '), | |
| ); | |
| rows.push(closetd); | |
| (() => { | |
| try { | |
| return new Intl.DateTimeFormat('en-US', { | |
| timeZone, | |
| year: 'numeric', | |
| month: '2-digit', | |
| day: '2-digit', | |
| hour: '2-digit', | |
| minute: '2-digit', | |
| second: '2-digit', | |
| timeZoneName: 'short', | |
| hour12: false, | |
| }) | |
| .format(new Date(message.ts)) | |
| .replace(/\//g, '-') | |
| .replace(/, /g, ' '); | |
| } catch { | |
| return new Intl.DateTimeFormat('en-US', { | |
| timeZone: 'UTC', | |
| year: 'numeric', | |
| month: '2-digit', | |
| day: '2-digit', | |
| hour: '2-digit', | |
| minute: '2-digit', | |
| second: '2-digit', | |
| timeZoneName: 'short', | |
| hour12: false, | |
| }) | |
| .format(new Date(message.ts)) | |
| .replace(/\//g, '-') | |
| .replace(/, /g, ' '); | |
| } | |
| })(), |
| if (currentDay === localTimeStartDay && localTimeFinish.diff(currentTime, 'days') < 0) { | ||
| localTimeFinish.add(1, 'week'); | ||
| // Same-day range: normalize for "current day is start day but start time is after current" (use previous week's start) | ||
| if (currentDay === hour.start.cron.dayOfWeek && startMinutes > currentMinutes) { |
There was a problem hiding this comment.
P2: This same‑day normalization marks hours as open before the start time (e.g., Monday 09:00 vs a 18:00–20:00 range). Remove this adjustment; the same‑day case should not subtract a week.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/meteor/app/livechat/server/business-hour/filterBusinessHoursThatMustBeOpened.ts, line 52:
<comment>This same‑day normalization marks hours as open before the start time (e.g., Monday 09:00 vs a 18:00–20:00 range). Remove this adjustment; the same‑day case should not subtract a week.</comment>
<file context>
@@ -13,36 +40,20 @@ export const filterBusinessHoursThatMustBeOpened = async (
- if (currentDay === localTimeStartDay && localTimeFinish.diff(currentTime, 'days') < 0) {
- localTimeFinish.add(1, 'week');
+ // Same-day range: normalize for "current day is start day but start time is after current" (use previous week's start)
+ if (currentDay === hour.start.cron.dayOfWeek && startMinutes > currentMinutes) {
+ startMinutes -= MINUTES_PER_WEEK;
}
</file context>
| moment(newDayData.messages[1].ts).tz(newDayData.timezone).format(newDayData.dateFormat), | ||
| ); | ||
| expect(result.messages[1]).toHaveProperty('divider'); | ||
| expect(typeof result.messages[1].divider).toBe('string'); |
There was a problem hiding this comment.
P2: This test was weakened to only assert that divider is a string, so timezone/date-format regressions can pass undetected. It should still validate the expected formatted value.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At ee/packages/pdf-worker/src/strategies/ChatTranscript.spec.ts, line 51:
<comment>This test was weakened to only assert that `divider` is a string, so timezone/date-format regressions can pass undetected. It should still validate the expected formatted value.</comment>
<file context>
@@ -48,10 +47,8 @@ describe('Strategies/ChatTranscript', () => {
- moment(newDayData.messages[1].ts).tz(newDayData.timezone).format(newDayData.dateFormat),
- );
+ expect(result.messages[1]).toHaveProperty('divider');
+ expect(typeof result.messages[1].divider).toBe('string');
});
</file context>
|
|
||
| const defaultValue = { contactsCount: 0, conversationsCount: 0, sources: [] }; | ||
| const billablePeriod = moment.utc().format('YYYY-MM'); | ||
| const billablePeriod = format(new Date(), 'yyyy-MM'); |
There was a problem hiding this comment.
P2: Billable period switched from UTC month to local-time month, which can select the wrong period near UTC month boundaries.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/meteor/app/statistics/server/lib/statistics.ts, line 249:
<comment>Billable period switched from UTC month to local-time month, which can select the wrong period near UTC month boundaries.</comment>
<file context>
@@ -246,29 +246,29 @@ export const statistics = {
const defaultValue = { contactsCount: 0, conversationsCount: 0, sources: [] };
- const billablePeriod = moment.utc().format('YYYY-MM');
+ const billablePeriod = format(new Date(), 'yyyy-MM');
statsPms.push(
LivechatRooms.getMACStatisticsForPeriod(billablePeriod).then(([result]) => {
</file context>
| const billablePeriod = format(new Date(), 'yyyy-MM'); | |
| const billablePeriod = new Date().toISOString().slice(0, 7); |
|
|
||
| export function parseDayTimeInZone(day: string, timeStr: string, timezone: string): Date { | ||
| const [h = 0, m = 0] = timeStr.split(':').map(Number); | ||
| const dayIndex = DAY_NAMES.indexOf(day); |
There was a problem hiding this comment.
P2: Using a fixed January 2025 reference date forces standard-time offsets, so DST timezones will generate UTC cron times that are one hour off during summer. Build the reference date from the current week in the target timezone so the offset matches the active DST rules.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts, line 16:
<comment>Using a fixed January 2025 reference date forces standard-time offsets, so DST timezones will generate UTC cron times that are one hour off during summer. Build the reference date from the current week in the target timezone so the offset matches the active DST rules.</comment>
<file context>
@@ -1,13 +1,30 @@
+
+export function parseDayTimeInZone(day: string, timeStr: string, timezone: string): Date {
+ const [h = 0, m = 0] = timeStr.split(':').map(Number);
+ const dayIndex = DAY_NAMES.indexOf(day);
+ const refDate = 5 + dayIndex; // 2025-01-05 is Sunday
+ const tzDate = new TZDate(2025, 0, refDate, h, m, 0, timezone);
</file context>
There was a problem hiding this comment.
Actionable comments posted: 19
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
apps/meteor/server/settings/smarsh.ts (1)
36-44:⚠️ Potential issue | 🟡 MinorConsider the Node 22.16.0 minimum version before including an unreachable empty fallback.
Line 36 uses an empty
[]fallback forIntl.supportedValuesOf('timeZone'), which would produce an unusable select dropdown. However, with Node 22.16.0 required (declared inpackage.json),Intl.supportedValuesOfis guaranteed to exist since Node 12+. This makes the fallback unreachable.If Node 22+ is a hard requirement, remove the fallback or update the ternary to assume availability. If older runtimes must be supported, import
guessTimezonefrom@rocket.chat/toolsas a safer fallback:- const zoneNames = typeof intl.supportedValuesOf === 'function' ? intl.supportedValuesOf('timeZone') : []; + const zoneNames = typeof intl.supportedValuesOf === 'function' ? intl.supportedValuesOf('timeZone') : [guessTimezone()];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/server/settings/smarsh.ts` around lines 36 - 44, The ternary fallback producing an empty array for zoneNames is unnecessary given Node >=22.16.0; replace the conditional with a direct call to intl.supportedValuesOf('timeZone') (use const zoneNames = intl.supportedValuesOf('timeZone')), build zoneValues from that, and keep the add('Smarsh_Timezone', 'America/Los_Angeles', ...) call as-is; if you still need backward compatibility for older runtimes instead, import guessTimezone from '@rocket.chat/tools' and use it to derive a sensible default/value list rather than returning an empty [].packages/tools/src/timezone.ts (1)
1-7:⚠️ Potential issue | 🟡 Minor
padOffsetdoesn't handle fractional timezone offsets.The function assumes integer hour offsets, but some timezones have fractional offsets (India +5:30, Nepal +5:45, Iran +3:30). When
utcOffsetcomes from the client (moment().utcOffset() / 60) or Slack importers (tz_offset / 3600), fractional values like 5.5 are possible. The function would produce+05.5:00instead of+05:30, breaking timezone matching for affected users.🐛 Proposed fix to handle fractional offsets
const padOffset = (offset: string | number): string => { const numberOffset = Number(offset); const absOffset = Math.abs(numberOffset); const isNegative = !(numberOffset === absOffset); + const hours = Math.floor(absOffset); + const minutes = Math.round((absOffset - hours) * 60); - return `${isNegative ? '-' : '+'}${absOffset < 10 ? `0${absOffset}` : absOffset}:00`; + return `${isNegative ? '-' : '+'}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`; };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/tools/src/timezone.ts` around lines 1 - 7, padOffset currently treats offset as integer hours and formats "+HH:00", which breaks for fractional offsets (e.g. +5.5 => +05.30). Update padOffset to parse the input (offset: string|number), determine sign, take absolute value, compute hours = Math.floor(abs) and minutes = Math.round((abs - hours) * 60), zero-pad both parts, and return `${sign}${HH}:${MM}`; ensure minutes are two digits and handle cases where rounding yields 60 by incrementing hours accordingly. Target the padOffset function to implement these numeric-to-HH:MM formatting changes.apps/meteor/app/livechat/server/business-hour/filterBusinessHoursThatMustBeOpened.ts (1)
34-56:⚠️ Potential issue | 🔴 CriticalThis normalization makes start-day hours open from midnight.
For a normal Monday
08:00-20:00slot, current Monday07:00makes Line 53 movestartMinutesinto the previous week, so Line 56 returnstrueand the business hour is considered open early. The same adjustment also misclassifies cross-day windows.🕒 Suggested fix
- const { day: currentDay, minutes: currentMinutes } = getCurrentDayAndTime(); + const { minutes: currentMinutes } = getCurrentDayAndTime(); @@ - let startMinutes = toMinutesSinceSunday(hour.start.cron.dayOfWeek, `${hour.start.cron.time}:00`); - const finishMinutes = toMinutesSinceSunday(hour.finish.cron.dayOfWeek, `${hour.finish.cron.time}:00`); - - // Overnight range (e.g. Saturday 22:00 to Sunday 02:00) - if (startMinutes > finishMinutes) { - return currentMinutes >= startMinutes || currentMinutes < finishMinutes; - } - - // Same-day range: normalize for "current day is start day but start time is after current" (use previous week's start) - if (currentDay === hour.start.cron.dayOfWeek && startMinutes > currentMinutes) { - startMinutes -= MINUTES_PER_WEEK; - } - - return currentMinutes >= startMinutes && currentMinutes < finishMinutes; + const startMinutes = toMinutesSinceSunday(hour.start.cron.dayOfWeek, `${hour.start.cron.time}:00`); + let finishMinutes = toMinutesSinceSunday(hour.finish.cron.dayOfWeek, `${hour.finish.cron.time}:00`); + let current = currentMinutes; + + if (finishMinutes <= startMinutes) { + finishMinutes += MINUTES_PER_WEEK; + if (current < startMinutes) { + current += MINUTES_PER_WEEK; + } + } + + return current >= startMinutes && current < finishMinutes;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/app/livechat/server/business-hour/filterBusinessHoursThatMustBeOpened.ts` around lines 34 - 56, The normalization that subtracts MINUTES_PER_WEEK when currentDay === hour.start.cron.dayOfWeek and startMinutes > currentMinutes is incorrect and causes same-day windows (e.g., Monday 08:00-20:00 at 07:00) to be treated as already open; remove that normalization block and rely only on the overnight check (startMinutes > finishMinutes) plus the final comparison currentMinutes >= startMinutes && currentMinutes < finishMinutes to determine openness. Locate the logic in filterBusinessHoursThatMustBeOpened (look for startMinutes, finishMinutes and MINUTES_PER_WEEK) and delete the conditional that mutates startMinutes so same-day ranges are not shifted into the previous week.apps/meteor/server/services/omnichannel-analytics/OverviewData.ts (1)
85-99:⚠️ Potential issue | 🟠 MajorUse actual calendar days in the timezone, not elapsed 24-hour periods.
fromandtoare UTC Date objects representing the selected timezone's day boundaries.differenceInDays(to, from) + 1counts elapsed 24-hour periods in UTC, which overshoots when the timezone crosses a DST transition. In America/New_York on November 1–2, 2025, the local range spans 2 calendar dates but 54+ hours UTC, yielding 3 instead of 2, causingConversations_per_dayto be ~33% too small. Count actual distinct calendar dates (e.g.,analyticsMap.sizeor recompute from timezone-aware boundaries).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/server/services/omnichannel-analytics/OverviewData.ts` around lines 85 - 99, The current Conversations function computes days with differenceInDays(to, from) + 1 which measures elapsed UTC 24-hour periods and breaks on DST; replace that with a timezone-aware count of distinct calendar dates between from and to in the provided timezone by either using the computed analyticsMap.size after you populate per-day keys or explicitly recomputing day boundaries in the given timezone (e.g., convert from and to to the timezone with utcToZonedTime and iterate calendar days or use startOfDay/eachDayOfInterval in that timezone) so that the days variable reflects actual local calendar days (refer to Conversations, the days variable, and analyticsMap when implementing the change).
🧹 Nitpick comments (11)
apps/meteor/ee/server/lib/engagementDashboard/date.ts (1)
26-26: Consider validating the parsed result for consistency withconvertDateToInt.
convertDateToIntnow validates its input, butconvertIntToDatereturns the parse result without checking validity. If a malformed integer (e.g.,0or negative) is passed, this returns an Invalid Date silently.🔧 Optional: Add validation to convertIntToDate
-export const convertIntToDate = (intValue: number): Date => parse(String(intValue), 'yyyyMMdd', new Date()); +export const convertIntToDate = (intValue: number): Date => { + const date = parse(String(intValue), 'yyyyMMdd', new Date()); + if (!isValid(date)) { + throw new Error(`convertIntToDate: invalid integer received: ${intValue}`); + } + return date; +};🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/ee/server/lib/engagementDashboard/date.ts` at line 26, convertIntToDate currently returns parse(...) without validating the result; update convertIntToDate to reject malformed integers by first handling non-positive inputs and then validating the parsed Date by ensuring it's valid and round-trips with convertDateToInt (e.g., convertDateToInt(parsedDate) === intValue); if validation fails, throw a clear error or return a controlled failure instead of returning an Invalid Date. Use the existing parse call and the convertDateToInt helper to perform the consistency check.apps/meteor/ee/app/livechat-enterprise/server/api/reports.ts (2)
22-28: Redundant validation incheckDates.The
isValidchecks at lines 23 and 26 are redundant becauseparseDatealready validates and throws if the date is invalid. By the timecheckDatesis called, bothstartandendare guaranteed to be validDateobjects.♻️ Suggested simplification
-const checkDates = (start: Date, end: Date) => { - if (!isValid(start)) { - throw new Error('The "start" query parameter must be a valid date.'); - } - if (!isValid(end)) { - throw new Error('The "end" query parameter must be a valid date.'); - } +const checkDates = (start: Date, end: Date) => { if (differenceInYears(startOfDay(end), startOfDay(start)) > 1.01) { throw new Error('The "start" and "end" query parameters must be less than 1 year apart.'); } if (isAfter(start, end)) { throw new Error('The "start" query parameter must be before the "end" query parameter.'); } };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/ee/app/livechat-enterprise/server/api/reports.ts` around lines 22 - 28, The checkDates function contains redundant isValid(start)/isValid(end) checks because parseDate already validates and throws for invalid inputs; remove the isValid checks and their Error throws inside checkDates so it simply compares start and end (e.g., ensuring start <= end) and relies on parseDate for validity. Update the function named checkDates to drop the isValid validations and keep only the necessary logical comparison or range checks, referencing parseDate as the source of input validation.
29-31: UsedifferenceInDayswith an explicit threshold for clearer semantics.
differenceInYearsreturns only integer values (full years), so comparing to1.01is equivalent to> 1. CheckingdifferenceInDays(startOfDay(end), startOfDay(start)) > 366would more explicitly communicate the 1-year boundary logic and eliminate the confusing decimal threshold.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/ee/app/livechat-enterprise/server/api/reports.ts` around lines 29 - 31, Replace the integer-year comparison in the reports validation with an explicit day-based check: in the block using differenceInYears(startOfDay(end), startOfDay(start)) > 1.01 (inside the reports API validation in this file), use differenceInDays(startOfDay(end), startOfDay(start)) > 366 so the intent of a "less than 1 year apart" check is clear and accounts for leap years; update the thrown Error message if necessary to reflect the day-based boundary.apps/meteor/app/livechat/server/statistics/LivechatAgentActivityMonitor.ts (1)
68-81: Minor redundancy:startDatecreated butsession.lastStartedAtre-wrapped.Line 71 creates
startDatefromsession.lastStartedAt, but line 74 creates anothernew Date(session.lastStartedAt). Consider reusingstartDatefor consistency.♻️ Suggested fix
const startDate = new Date(session.lastStartedAt); const stoppedAt = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate(), 23, 59, 59); const data = { ...formatDate(startDate), agentId: session.agentId }; - const availableTime = differenceInSeconds(stoppedAt, new Date(session.lastStartedAt)); + const availableTime = differenceInSeconds(stoppedAt, startDate);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/app/livechat/server/statistics/LivechatAgentActivityMonitor.ts` around lines 68 - 81, Replace the redundant new Date(session.lastStartedAt) with the already-created startDate to avoid double-wrapping; specifically, use startDate when computing stoppedAt and availableTime (so stoppedAt is built from startDate's year/month/date and availableTime = differenceInSeconds(stoppedAt, startDate)), and keep the existing calls to LivechatAgentActivity.updateLastStoppedAt, LivechatAgentActivity.updateServiceHistory and this._createOrUpdateSession unchanged except for passing startDate where the extra new Date was used.ee/packages/pdf-worker/package.json (1)
21-25: Standardize version constraints for date-fns dependencies.Version constraint patterns differ:
@date-fns/tzuses caret (^1.4.1), allowing minor/patch updates, whiledate-fnsuses tilde (~4.1.0), allowing patch-only updates. Align these for consistency.Since
@date-fns/tzintroduces timezone-aware date handling, ensure any timezone-sensitive tests include explicit UTC timezone enforcement to prevent snapshot or formatting failures across different environments.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ee/packages/pdf-worker/package.json` around lines 21 - 25, Update package dependency constraints to be consistent by aligning "@date-fns/tz" and "date-fns" to the same version constraint pattern (choose either caret or tilde) in package.json—update the entries for "@date-fns/tz" and "date-fns" accordingly—and run install to regenerate lockfile; additionally, in any timezone-sensitive tests that use `@date-fns/tz` or date-fns (look for test files referencing timezone handling or functions from `@date-fns/tz`), explicitly set or mock the timezone to UTC (e.g., set process.env.TZ or use a timezone-mocking helper) so snapshots and formatted outputs remain deterministic across environments.apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts (2)
26-30: Reject malformed duration strings instead of coercing them.
durationAsSeconds()now accepts bare numbers and silently turns bad segments into0, which makes these tests less useful for catching response-format regressions. Prefer failing fast on anything outside the expectedH:mm:ss/HH:mm:ssshape.♻️ Suggested change
-/** Parse HH:mm:ss or H:mm:ss to total seconds */ function durationAsSeconds(value: string): number { - const parts = String(value).trim().split(':').map(Number); - const [h = 0, m = 0, s = 0] = parts.length === 3 ? parts : [0, 0, parts[0]]; - return (h || 0) * 3600 + (m || 0) * 60 + (s || 0); + const match = /^(\d+):([0-5]\d):([0-5]\d(?:\.\d+)?)$/.exec(String(value).trim()); + if (!match) { + throw new Error(`Unexpected duration format: ${value}`); + } + + const [, hours, minutes, seconds] = match; + return Number(hours) * 3600 + Number(minutes) * 60 + Number(seconds); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts` around lines 26 - 30, The durationAsSeconds function currently coerces malformed inputs; instead validate the input shape and fail fast: in durationAsSeconds(value) first trim and assert the string matches the exact H:mm:ss or HH:mm:ss pattern (e.g. /^\d{1,2}:\d{2}:\d{2}$/), and if it does not match throw an Error (or return a clear failure) so bad segments aren’t silently treated as zero; once validated, split and convert to numbers and compute seconds as before.
734-735: Pin these date-only query windows to UTC.
format()andstartOfDay()are local-time based, so thisyyyy-MM-ddpattern stays host-timezone dependent wherever it appears in the file. Either derive these strings in UTC or ensure this suite always runs withTZ=UTC, otherwise contributors outside UTC can hit a different day window.Based on learnings, tests in this repo are expected to run in UTC, but UTC is not yet explicitly enforced in the shared test config/scripts.
Also applies to: 800-801, 1003-1003, 1386-1387
apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts (1)
43-43: Two different patterns for UTC weekday in the same file.Line 43 uses
toLocaleString('en-US', { timeZone: 'UTC', weekday: 'long' })while lines 247-248 use aDAY_NAMESarray withgetUTCDay(). Both are correct, but using a consistent approach would improve maintainability.♻️ Extract shared helper for consistency
const getUTCWeekdayName = (): string => { const DAY_NAMES = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; return DAY_NAMES[new Date().getUTCDay()]; };Then use
getUTCWeekdayName()in bothonStartBusinessHoursandapplyAnyOpenBusinessHourToAgent.Also applies to: 247-248
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts` at line 43, The file uses two different patterns to get the UTC weekday string; unify them by adding a shared helper (e.g., getUTCWeekdayName) and replace both usages: the toLocaleString call in onStartBusinessHours and the DAY_NAMES[new Date().getUTCDay()] usage in applyAnyOpenBusinessHourToAgent (and any other occurrences) so both functions call getUTCWeekdayName() for consistency and maintainability.apps/meteor/app/smarsh-connector/server/functions/generateEml.ts (1)
65-80: Intl.DateTimeFormat output format may vary across environments.The string replacements assume a specific output format from
Intl.DateTimeFormat. While 'en-US' locale with explicit options should be consistent, the exact output structure (e.g., separator positions) can vary slightly across JavaScript engines.Consider using
formatToParts()for more predictable output construction if format consistency is critical for Smarsh compliance.♻️ Alternative using formatToParts for explicit control
- rows.push( - new Intl.DateTimeFormat('en-US', { - timeZone, - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - timeZoneName: 'short', - hour12: false, - }) - .format(new Date(message.ts)) - .replace(/\//g, '-') - .replace(/, /g, ' '), - ); + const formatter = new Intl.DateTimeFormat('en-US', { + timeZone, + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + timeZoneName: 'short', + hour12: false, + }); + const parts = formatter.formatToParts(new Date(message.ts)); + const get = (type: string) => parts.find((p) => p.type === type)?.value ?? ''; + rows.push( + `${get('month')}-${get('day')}-${get('year')} ${get('hour')}:${get('minute')}:${get('second')} ${get('timeZoneName')}`, + );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/app/smarsh-connector/server/functions/generateEml.ts` around lines 65 - 80, The DateTime formatting in generateEml.ts (the Intl.DateTimeFormat call that pushes into rows using message.ts and timeZone) relies on string replacements which can vary by engine; replace that chain with a deterministic assembly using Intl.DateTimeFormat.prototype.formatToParts: call formatToParts(new Date(message.ts)) with the same options (timeZone, year, month, day, hour, minute, second, timeZoneName, hour12:false), extract the numeric parts and timeZoneName, pad as needed, and compose the final timestamp string with fixed separators (e.g., YYYY-MM-DD HH:MM:SS TZ) before pushing into rows to ensure consistent output across environments.apps/meteor/app/livechat/server/business-hour/Helper.ts (1)
14-14: Stale comment references removed moment API.The comment
// Format: moment.format('dddd')is now misleading since Moment has been removed. Consider updating or removing it.📝 Suggested update
businessHours: ILivechatBusinessHour[], - day: string, // Format: moment.format('dddd') + day: string, // Full weekday name in English (e.g., "Monday") ): Promise<Pick<ILivechatBusinessHour, '_id' | 'type'>[]> => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/app/livechat/server/business-hour/Helper.ts` at line 14, Update the stale inline comment on the day parameter in Helper.ts: remove or replace "// Format: moment.format('dddd')" with a current, accurate format description (for example "// Format: full weekday name, e.g., 'Monday'") next to the day parameter in the function or type that defines day so callers (and functions like the ones in Helper.ts) know the expected string format without referencing Moment.apps/meteor/tests/unit/app/livechat/server/business-hour/BusinessHourManager.spec.ts (1)
73-77: Reset the stub behavior here, not just the history.
saveBusinessHourStub.onSecondCall().rejects(error)persists across tests, so Line 76 leaves order-dependent behavior behind. AddresetBehavior()in this hook to keep the suite isolated.♻️ Suggested fix
afterEach(() => { sinon.restore(); loggerStub.resetHistory(); + saveBusinessHourStub.resetBehavior(); saveBusinessHourStub.resetHistory(); });Also applies to: 160-161
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/tests/unit/app/livechat/server/business-hour/BusinessHourManager.spec.ts` around lines 73 - 77, The afterEach hook currently restores sinon and resets histories but doesn't reset stub behavior, causing persistent configured responses (e.g., saveBusinessHourStub.onSecondCall().rejects(error)) to leak between tests; update the afterEach to call saveBusinessHourStub.resetBehavior() (and any other stubs like loggerStub if they have configured behaviors) in addition to resetHistory() so each test starts with fresh stub behavior, and apply the same change to the other afterEach at lines referenced (around the second occurrence).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/meteor/app/cloud/server/functions/buildRegistrationData.ts`:
- Line 72: The billing-period string is being generated with format(new Date(),
'yyyy-MM') which depends on the host timezone; update the call where MAC is
assigned (LivechatContacts.countContactsOnPeriod(...)) to use UTC-safe
formatting: replace format(new Date(), 'yyyy-MM') with new
Date().toISOString().slice(0, 7), and remove the now-unused format import from
the top of the module; also scan the other occurrences mentioned (e.g., in
license/startup, omnichannel/service, statistics, markRoomResponded) and apply
the same change so monthly reporting stays UTC-consistent.
In `@apps/meteor/app/livechat/server/hooks/processRoomAbandonment.ts`:
- Around line 81-97: The logic adding inactivity via calls to
differenceInSeconds can produce negative values when the inactivity window lies
entirely outside office hours; clamp startOfInactivity and endOfConversation to
the office-hours window (todayStartOfficeHours..todayEndOfficeHours) before
computing durations and only add to totalSeconds if the clamped interval has a
positive length. Update the branches that use differenceInSeconds (the checks
around isBefore/isAfter for startOfInactivity, endOfConversation,
todayStartOfficeHours, todayEndOfficeHours) to compute clampedStart =
max(startOfInactivity, todayStartOfficeHours) and clampedEnd =
min(endOfConversation, todayEndOfficeHours) and then add
differenceInSeconds(clampedEnd, clampedStart) only when clampedEnd >
clampedStart to avoid negative seconds.
In `@apps/meteor/app/statistics/server/lib/statistics.ts`:
- Around line 249-273: The billing period and rolling-window dates must be
computed in UTC: replace format(new Date(), 'yyyy-MM') with new
Date().toISOString().slice(0,7') for the billablePeriod used in
LivechatRooms.getMACStatisticsForPeriod (affecting
statistics.omnichannelContactsBySource), and replace subDays(today, N) usages
used before calling LivechatRooms.getMACStatisticsBetweenDates with UTC
millisecond arithmetic (e.g. new Date(Date.UTC(...)) or compute todayUtc = new
Date(Date.now()); then monthAgoUtc = new Date(todayUtc.getTime() - 30 *
24*60*60*1000) etc.) so window boundaries never shift by timezone/DST; also
remove now-unused imports format and subDays from the top of the file. Ensure
the promises pushed to statsPms use these UTC dates.
In `@apps/meteor/ee/app/license/server/startup.ts`:
- Line 115: The counter uses local server time because format(new Date(),
'yyyy-MM') was used; change it to produce a UTC-based YYYY-MM string instead so
month boundaries match tests — update the License.setLicenseLimitCounter
callback (the function that calls LivechatContacts.countContactsOnPeriod) to
compute the month in UTC (e.g. use a UTC formatting helper such as date-fns-tz's
formatInTimeZone(..., 'UTC', 'yyyy-MM') or derive the string from new
Date().toISOString().slice(0,7)) and pass that UTC month string into
LivechatContacts.countContactsOnPeriod.
In `@apps/meteor/ee/server/lib/deviceManagement/session.ts`:
- Line 58: Wrap the date-fns format(...) calls that use the dateFormat variable
in a try-catch and fall back to a date-fns-compatible format ('yyyy-MM-dd
HH:mm') on error; specifically, protect the format invocation that uses
dateFormat (the variable initialized from Message_TimeAndDateFormat) and the
other format call later in the file so that if an admin supplies Moment-style
tokens the code catches the thrown error, logs or records the formatting error,
and uses the fallback format instead to avoid crashing login email generation.
In `@apps/meteor/server/lib/dataExport/sendViaEmail.ts`:
- Around line 50-53: The Intl.DateTimeFormat construction using the variable
lang can throw RangeError for malformed locales; wrap the creation of
dateTimeFormatter in a try-catch inside sendViaEmail (and the other occurrence
around the line ~61) and if an exception is thrown, retry using the fallback
locale 'en' (e.g., try new Intl.DateTimeFormat(lang, ...) and on catch set
dateTimeFormatter = new Intl.DateTimeFormat('en', ...)); ensure you catch only
the construction error and preserve the original date/time options
(dateStyle/timeStyle) when creating the fallback formatter.
In `@apps/meteor/server/services/nps/notification.ts`:
- Line 35: The date formatting calls for expireAt and startAt are not validated
and can throw at runtime; update the code that builds the admin banner/message
to call isValid(expireAt) and isValid(startAt) (from date-fns) before using
format(expireAt, 'yyyy-MM-dd') / format(startAt, 'yyyy-MM-dd'), and provide a
safe fallback (e.g., null, empty string, or skip that field) when the date is
invalid; mirror the defensive pattern used by convertDateToInt and ensure these
checks occur in the same function that references expireAt and startAt so
invalid Date objects never reach format().
In `@apps/meteor/server/services/omnichannel-analytics/utils.ts`:
- Around line 8-13: dayIterator, hourIterator and weekIterator currently call
startOfDay/startOfHour/startOfWeek on plain Dates which uses the process
timezone; instead, convert inputs to the analytics timezone, perform truncation
and stepping there, and convert back to UTC for yielded bucket boundaries.
Specifically, in dayIterator/hourIterator/weekIterator: use date-fns-tz helpers
(utcToZonedTime to get a zoned Date from the input and timezone, call
startOfDay/startOfHour/startOfWeek on that zoned Date, then use zonedTimeToUtc
to convert the truncated zoned boundary back to an actual UTC Date to yield and
to compute the next boundary via addDays/addHours/addWeeks in the zoned
context). Ensure you use the passed _timezone (or analytics timezone variable)
consistently so buckets are computed end-to-end in that timezone.
- Around line 36-41: parseRangeInTimezone currently slices Y/M/D and hands them
to TZDate, which allows component overflow (e.g., 2026-02-31 -> 2026-03-03) and
later isValid() won't catch this; validate the Y, M, D components before
creating TZDate by constructing a plain Date (or using UTC) from (y, m-1, d) and
confirm its year, month, and date match the parsed components, and if they
don't, reject/throw or return an error so invalid calendar dates are not
silently normalized; update parseRangeInTimezone to perform this component-level
check and only build startTz/endTz when the components are valid.
In `@apps/meteor/server/services/omnichannel/service.ts`:
- Around line 55-57: The function isWithinMACLimit now uses local time via
format(new Date(), 'yyyy-MM') which breaks parity with earlier UTC-based
behavior; update the currentMonth computation inside isWithinMACLimit to
generate a UTC month string (e.g., using new Date().toISOString().slice(0,7) or
another UTC-based formatter) so the room.v.activity comparison remains
UTC-consistent, leaving the License.shouldPreventAction('monthlyActiveContacts')
call unchanged.
In `@apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts`:
- Around line 293-297: The test currently only checks existence of the totalizer
(avgWaitingTime) but the assertion that validates its numeric value was
commented out; restore the assertion by un-commenting and using
durationAsSeconds(avgWaitingTime.value) to compute avgWaitingTimeValue and
assert expect(avgWaitingTimeValue).to.be.closeTo(DELAY_BETWEEN_MESSAGES.max /
1000, 5); also ensure avgWaitingTime.value is present before conversion (or
assert it's defined) so the test fails if waiting-time calculation regresses;
locate the variables avgWaitingTime, durationAsSeconds, and
DELAY_BETWEEN_MESSAGES in the test to apply this change.
In `@apps/meteor/tests/end-to-end/api/livechat/23-mac.ts`:
- Line 71: Tests currently assert using UTC month string (new
Date().toISOString().slice(0, 7)) while production uses local time via
format(new Date(), 'yyyy-MM'); update the failing assertions in
apps/meteor/tests/end-to-end/api/livechat/23-mac.ts (and the other occurrences
noted: lines around 93, 101, 110-111, 129) to compute the month using the same
local-time helper as production — import format from 'date-fns' in the test file
and replace new Date().toISOString().slice(0, 7) with format(new Date(),
'yyyy-MM') so tests and production use the same timezone basis.
In
`@apps/meteor/tests/unit/server/services/omnichannel-analytics/OverviewData.tests.ts`:
- Around line 191-193: The mocha-based tests in OverviewData.tests.ts use
date-fns functions (startOfDay, endOfDay, startOfMonth, endOfMonth) which are
sensitive to the process timezone; update the test run environment to be
timezone-stable by adding TZ=UTC to the mocha scripts in package.json
(specifically the .testunit:server and .testunit:server:cov scripts) so all runs
use UTC, or alternatively refactor the date boundary construction in
OverviewData.tests.ts to build UTC-based dates (e.g., using Date.UTC or
equivalent) for the calls that create test boundaries referenced around
getAnalyticsBetweenDate / getOnHoldConversationsBetweenDate and the other
boundary blocks.
In `@ee/packages/pdf-worker/src/strategies/ChatTranscript.spec.ts`:
- Around line 50-51: Update the test in ChatTranscript.spec.ts to assert the
divider's formatted value/format rather than just its type: verify
result.messages[1].divider equals the expected timestamp string from the fixture
(or match a strict regex like /^\d{1,2}:\d{2} [AP]M · [A-Za-z]{3} \d{1,2}, \d{4}
UTC$/) to catch malformed outputs produced by formatInTimezone(), and also
ensure the package.json test script sets TZ=UTC so timezone-dependent formatting
is consistent across environments.
In `@ee/packages/pdf-worker/src/strategies/ChatTranscript.ts`:
- Around line 10-22: The momentTokenMap is missing mappings for tokens used
elsewhere (causing unconverted tokens and format failures); update the
momentTokenMap object to include entries for 'MMM' -> 'MMM', 'H' -> 'H', 'mm' ->
'mm', and 'ss' -> 'ss' so momentFormatToDateFns() can correctly translate
formats used by formatDate() and fixtures; locate and edit the momentTokenMap
constant in ChatTranscript.ts to add these key/value pairs.
- Around line 33-43: formatInTimezone currently builds a plain Date from TZDate
components (losing milliseconds and interpreting the result in the server's
local timezone), which can produce incorrect results around DST or when the
server timezone differs from the target; instead, stop extracting components and
use date-fns-tz's timezone-aware formatter (e.g., formatInTimeZone or format
from date-fns-tz) directly with the original timestamp/new Date(ts) and the
converted format (momentFormatToDateFns(fmt)) — update the formatInTimezone
function to call that timezone-aware formatter (keeping TZDate and manual
reconstruction out) so milliseconds are preserved and DST/timezone semantics are
correct.
In `@packages/agenda/src/Job.ts`:
- Around line 17-18: The code uses the private CronTime._getNextDateFrom and a
fragile CronNextDate shim; change calls to the public
CronTime.getNextDateFrom(...) which returns a Luxon DateTime, and remove or
replace the CronNextDate type shim with the proper Luxon DateTime type (or
accept its valueOf()), then use dateTime.valueOf() where the code previously
used valueOf() on the shim. Update any occurrences in Job.ts that reference
_getNextDateFrom to call getNextDateFrom and adjust typings accordingly.
In `@packages/i18n/src/locales/pt-BR.i18n.json`:
- Line 3232: Update the locale string for Message_DateFormat_Description (and
the other affected key at the same region) to replace Moment-style tokens with
date-fns equivalents and/or show both sets; reference the i18n key
"Message_DateFormat_Description" and the related key at the other occurrence and
change the text to mention date-fns tokens (e.g., yyyy, MM, dd, HH:mm:ss, etc.),
note that a PDF worker conversion utility will map legacy Moment tokens to
date-fns when needed, and make the description concise so admins are guided to
use native date-fns tokens or understand that automatic conversion occurs.
---
Outside diff comments:
In
`@apps/meteor/app/livechat/server/business-hour/filterBusinessHoursThatMustBeOpened.ts`:
- Around line 34-56: The normalization that subtracts MINUTES_PER_WEEK when
currentDay === hour.start.cron.dayOfWeek and startMinutes > currentMinutes is
incorrect and causes same-day windows (e.g., Monday 08:00-20:00 at 07:00) to be
treated as already open; remove that normalization block and rely only on the
overnight check (startMinutes > finishMinutes) plus the final comparison
currentMinutes >= startMinutes && currentMinutes < finishMinutes to determine
openness. Locate the logic in filterBusinessHoursThatMustBeOpened (look for
startMinutes, finishMinutes and MINUTES_PER_WEEK) and delete the conditional
that mutates startMinutes so same-day ranges are not shifted into the previous
week.
In `@apps/meteor/server/services/omnichannel-analytics/OverviewData.ts`:
- Around line 85-99: The current Conversations function computes days with
differenceInDays(to, from) + 1 which measures elapsed UTC 24-hour periods and
breaks on DST; replace that with a timezone-aware count of distinct calendar
dates between from and to in the provided timezone by either using the computed
analyticsMap.size after you populate per-day keys or explicitly recomputing day
boundaries in the given timezone (e.g., convert from and to to the timezone with
utcToZonedTime and iterate calendar days or use startOfDay/eachDayOfInterval in
that timezone) so that the days variable reflects actual local calendar days
(refer to Conversations, the days variable, and analyticsMap when implementing
the change).
In `@apps/meteor/server/settings/smarsh.ts`:
- Around line 36-44: The ternary fallback producing an empty array for zoneNames
is unnecessary given Node >=22.16.0; replace the conditional with a direct call
to intl.supportedValuesOf('timeZone') (use const zoneNames =
intl.supportedValuesOf('timeZone')), build zoneValues from that, and keep the
add('Smarsh_Timezone', 'America/Los_Angeles', ...) call as-is; if you still need
backward compatibility for older runtimes instead, import guessTimezone from
'@rocket.chat/tools' and use it to derive a sensible default/value list rather
than returning an empty [].
In `@packages/tools/src/timezone.ts`:
- Around line 1-7: padOffset currently treats offset as integer hours and
formats "+HH:00", which breaks for fractional offsets (e.g. +5.5 => +05.30).
Update padOffset to parse the input (offset: string|number), determine sign,
take absolute value, compute hours = Math.floor(abs) and minutes =
Math.round((abs - hours) * 60), zero-pad both parts, and return
`${sign}${HH}:${MM}`; ensure minutes are two digits and handle cases where
rounding yields 60 by incrementing hours accordingly. Target the padOffset
function to implement these numeric-to-HH:MM formatting changes.
---
Nitpick comments:
In `@apps/meteor/app/livechat/server/business-hour/Helper.ts`:
- Line 14: Update the stale inline comment on the day parameter in Helper.ts:
remove or replace "// Format: moment.format('dddd')" with a current, accurate
format description (for example "// Format: full weekday name, e.g., 'Monday'")
next to the day parameter in the function or type that defines day so callers
(and functions like the ones in Helper.ts) know the expected string format
without referencing Moment.
In `@apps/meteor/app/livechat/server/statistics/LivechatAgentActivityMonitor.ts`:
- Around line 68-81: Replace the redundant new Date(session.lastStartedAt) with
the already-created startDate to avoid double-wrapping; specifically, use
startDate when computing stoppedAt and availableTime (so stoppedAt is built from
startDate's year/month/date and availableTime = differenceInSeconds(stoppedAt,
startDate)), and keep the existing calls to
LivechatAgentActivity.updateLastStoppedAt,
LivechatAgentActivity.updateServiceHistory and this._createOrUpdateSession
unchanged except for passing startDate where the extra new Date was used.
In `@apps/meteor/app/smarsh-connector/server/functions/generateEml.ts`:
- Around line 65-80: The DateTime formatting in generateEml.ts (the
Intl.DateTimeFormat call that pushes into rows using message.ts and timeZone)
relies on string replacements which can vary by engine; replace that chain with
a deterministic assembly using Intl.DateTimeFormat.prototype.formatToParts: call
formatToParts(new Date(message.ts)) with the same options (timeZone, year,
month, day, hour, minute, second, timeZoneName, hour12:false), extract the
numeric parts and timeZoneName, pad as needed, and compose the final timestamp
string with fixed separators (e.g., YYYY-MM-DD HH:MM:SS TZ) before pushing into
rows to ensure consistent output across environments.
In `@apps/meteor/ee/app/livechat-enterprise/server/api/reports.ts`:
- Around line 22-28: The checkDates function contains redundant
isValid(start)/isValid(end) checks because parseDate already validates and
throws for invalid inputs; remove the isValid checks and their Error throws
inside checkDates so it simply compares start and end (e.g., ensuring start <=
end) and relies on parseDate for validity. Update the function named checkDates
to drop the isValid validations and keep only the necessary logical comparison
or range checks, referencing parseDate as the source of input validation.
- Around line 29-31: Replace the integer-year comparison in the reports
validation with an explicit day-based check: in the block using
differenceInYears(startOfDay(end), startOfDay(start)) > 1.01 (inside the reports
API validation in this file), use differenceInDays(startOfDay(end),
startOfDay(start)) > 366 so the intent of a "less than 1 year apart" check is
clear and accounts for leap years; update the thrown Error message if necessary
to reflect the day-based boundary.
In `@apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts`:
- Line 43: The file uses two different patterns to get the UTC weekday string;
unify them by adding a shared helper (e.g., getUTCWeekdayName) and replace both
usages: the toLocaleString call in onStartBusinessHours and the DAY_NAMES[new
Date().getUTCDay()] usage in applyAnyOpenBusinessHourToAgent (and any other
occurrences) so both functions call getUTCWeekdayName() for consistency and
maintainability.
In `@apps/meteor/ee/server/lib/engagementDashboard/date.ts`:
- Line 26: convertIntToDate currently returns parse(...) without validating the
result; update convertIntToDate to reject malformed integers by first handling
non-positive inputs and then validating the parsed Date by ensuring it's valid
and round-trips with convertDateToInt (e.g., convertDateToInt(parsedDate) ===
intValue); if validation fails, throw a clear error or return a controlled
failure instead of returning an Invalid Date. Use the existing parse call and
the convertDateToInt helper to perform the consistency check.
In `@apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts`:
- Around line 26-30: The durationAsSeconds function currently coerces malformed
inputs; instead validate the input shape and fail fast: in
durationAsSeconds(value) first trim and assert the string matches the exact
H:mm:ss or HH:mm:ss pattern (e.g. /^\d{1,2}:\d{2}:\d{2}$/), and if it does not
match throw an Error (or return a clear failure) so bad segments aren’t silently
treated as zero; once validated, split and convert to numbers and compute
seconds as before.
In
`@apps/meteor/tests/unit/app/livechat/server/business-hour/BusinessHourManager.spec.ts`:
- Around line 73-77: The afterEach hook currently restores sinon and resets
histories but doesn't reset stub behavior, causing persistent configured
responses (e.g., saveBusinessHourStub.onSecondCall().rejects(error)) to leak
between tests; update the afterEach to call saveBusinessHourStub.resetBehavior()
(and any other stubs like loggerStub if they have configured behaviors) in
addition to resetHistory() so each test starts with fresh stub behavior, and
apply the same change to the other afterEach at lines referenced (around the
second occurrence).
In `@ee/packages/pdf-worker/package.json`:
- Around line 21-25: Update package dependency constraints to be consistent by
aligning "@date-fns/tz" and "date-fns" to the same version constraint pattern
(choose either caret or tilde) in package.json—update the entries for
"@date-fns/tz" and "date-fns" accordingly—and run install to regenerate
lockfile; additionally, in any timezone-sensitive tests that use `@date-fns/tz` or
date-fns (look for test files referencing timezone handling or functions from
`@date-fns/tz`), explicitly set or mock the timezone to UTC (e.g., set
process.env.TZ or use a timezone-mocking helper) so snapshots and formatted
outputs remain deterministic across environments.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 023d04fa-cc1a-4e10-8b20-b6eb7e1aedc5
📒 Files selected for processing (71)
apps/meteor/app/cloud/server/functions/buildRegistrationData.tsapps/meteor/app/irc/server/irc-bridge/index.jsapps/meteor/app/lib/server/lib/notifyUsersOnMessage.tsapps/meteor/app/lib/server/lib/processDirectEmail.tsapps/meteor/app/lib/server/lib/sendNotificationsOnMessage.tsapps/meteor/app/lib/server/methods/sendMessage.tsapps/meteor/app/lib/server/methods/updateMessage.tsapps/meteor/app/lib/server/startup/mentionUserNotInChannel.tsapps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.tsapps/meteor/app/livechat/server/business-hour/BusinessHourManager.tsapps/meteor/app/livechat/server/business-hour/Default.tsapps/meteor/app/livechat/server/business-hour/Helper.tsapps/meteor/app/livechat/server/business-hour/LivechatBusinessHours.tsapps/meteor/app/livechat/server/business-hour/filterBusinessHoursThatMustBeOpened.tsapps/meteor/app/livechat/server/hooks/markRoomResponded.tsapps/meteor/app/livechat/server/hooks/processRoomAbandonment.tsapps/meteor/app/livechat/server/lib/analytics/dashboards.tsapps/meteor/app/livechat/server/lib/sendTranscript.tsapps/meteor/app/livechat/server/statistics/LivechatAgentActivityMonitor.tsapps/meteor/app/smarsh-connector/server/functions/generateEml.tsapps/meteor/app/statistics/server/lib/statistics.tsapps/meteor/app/utils/server/lib/getTimezone.tsapps/meteor/ee/app/license/server/startup.tsapps/meteor/ee/app/livechat-enterprise/server/api/reports.tsapps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.tsapps/meteor/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.tsapps/meteor/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.tsapps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.tsapps/meteor/ee/app/livechat-enterprise/server/lib/Helper.tsapps/meteor/ee/server/lib/deviceManagement/session.tsapps/meteor/ee/server/lib/engagementDashboard/channels.tsapps/meteor/ee/server/lib/engagementDashboard/date.tsapps/meteor/ee/server/lib/engagementDashboard/messages.tsapps/meteor/ee/server/lib/engagementDashboard/users.tsapps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.tsapps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoTransferChatsScheduler.tests.tsapps/meteor/ee/tests/unit/apps/livechat-enterprise/server/hooks/afterInquiryQueued.spec.tsapps/meteor/package.jsonapps/meteor/server/lib/dataExport/processDataDownloads.tsapps/meteor/server/lib/dataExport/sendViaEmail.tsapps/meteor/server/lib/getMomentLocale.tsapps/meteor/server/methods/loadLocale.tsapps/meteor/server/services/nps/notification.tsapps/meteor/server/services/omnichannel-analytics/AgentData.tsapps/meteor/server/services/omnichannel-analytics/OverviewData.tsapps/meteor/server/services/omnichannel-analytics/service.tsapps/meteor/server/services/omnichannel-analytics/utils.tsapps/meteor/server/services/omnichannel/service.tsapps/meteor/server/settings/smarsh.tsapps/meteor/tests/data/livechat/businessHours.tsapps/meteor/tests/end-to-end/api/livechat/04-dashboards.tsapps/meteor/tests/end-to-end/api/livechat/23-mac.tsapps/meteor/tests/unit/app/livechat/server/business-hour/BusinessHourManager.spec.tsapps/meteor/tests/unit/server/services/omnichannel-analytics/AgentData.tests.tsapps/meteor/tests/unit/server/services/omnichannel-analytics/OverviewData.tests.tsee/apps/omnichannel-transcript/package.jsonee/apps/queue-worker/package.jsonee/packages/omnichannel-services/package.jsonee/packages/pdf-worker/package.jsonee/packages/pdf-worker/src/strategies/ChatTranscript.spec.tsee/packages/pdf-worker/src/strategies/ChatTranscript.tspackages/agenda/package.jsonpackages/agenda/src/Job.tspackages/i18n/src/locales/en.i18n.jsonpackages/i18n/src/locales/es.i18n.jsonpackages/i18n/src/locales/hi-IN.i18n.jsonpackages/i18n/src/locales/pt-BR.i18n.jsonpackages/i18n/src/locales/zh.i18n.jsonpackages/message-types/package.jsonpackages/tools/package.jsonpackages/tools/src/timezone.ts
💤 Files with no reviewable changes (5)
- ee/packages/omnichannel-services/package.json
- apps/meteor/server/lib/getMomentLocale.ts
- ee/apps/omnichannel-transcript/package.json
- ee/apps/queue-worker/package.json
- packages/tools/package.json
|
|
||
| const seats = await Users.getActiveLocalUserCount(); | ||
| const MAC = await LivechatContacts.countContactsOnPeriod(moment.utc().format('YYYY-MM')); | ||
| const MAC = await LivechatContacts.countContactsOnPeriod(format(new Date(), 'yyyy-MM')); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, locate and examine the file mentioned in the review
find . -type f -name "buildRegistrationData.ts" | head -5Repository: RocketChat/Rocket.Chat
Length of output: 131
🏁 Script executed:
# Check the structure to find the file
fd buildRegistrationData.tsRepository: RocketChat/Rocket.Chat
Length of output: 129
🏁 Script executed:
# Read the file to see line 72 and context
cat -n apps/meteor/app/cloud/server/functions/buildRegistrationData.ts | head -80Repository: RocketChat/Rocket.Chat
Length of output: 3542
🏁 Script executed:
# Check git history to see what the previous implementation was
git log --oneline -p apps/meteor/app/cloud/server/functions/buildRegistrationData.ts | head -150Repository: RocketChat/Rocket.Chat
Length of output: 4300
🏁 Script executed:
# The git log shows this is a new file. Let me check if there's a previous version of this function
# Search for similar code patterns or related functions that might have used moment
git log --all --oneline --diff-filter=D -- "*buildRegistrationData*" | head -10Repository: RocketChat/Rocket.Chat
Length of output: 48
🏁 Script executed:
# Let me search for the old moment-based implementation that might have done the same thing
git log --all -p -S "moment.utc()" -- "*registration*" | head -200Repository: RocketChat/Rocket.Chat
Length of output: 152
🏁 Script executed:
# Let me search more broadly for moment/date-fns usage patterns around LivechatContacts and MAC/billing
rg "countContactsOnPeriod" -B 3 -A 3 --type ts --type tsxRepository: RocketChat/Rocket.Chat
Length of output: 93
🏁 Script executed:
# Check date-fns documentation about timezone handling for format()
# Also look for other date-fns usage in the codebase with timezone considerations
rg "format\(new Date\(" --type ts --type tsx | head -20Repository: RocketChat/Rocket.Chat
Length of output: 93
🏁 Script executed:
# Search for countContactsOnPeriod usage without file type filter
rg "countContactsOnPeriod" -B 3 -A 3Repository: RocketChat/Rocket.Chat
Length of output: 3152
🏁 Script executed:
# Look for other usages of format() with new Date() to understand patterns
rg "format\(new Date\(" | head -20Repository: RocketChat/Rocket.Chat
Length of output: 2650
🏁 Script executed:
# The commit fcb678b782 created this file. Let me check what file it replaced or what similar code existed before
git show fcb678b782 --statRepository: RocketChat/Rocket.Chat
Length of output: 50379
🏁 Script executed:
# Let me also check if there's a previous version of this in a different location
# Search for any history of functions that might have calculated monthly active contacts before
git log --all -p --follow -S "countContactsOnPeriod" | head -300Repository: RocketChat/Rocket.Chat
Length of output: 111
🏁 Script executed:
# Look at the other location where countContactsOnPeriod is used with format
grep -n "countContactsOnPeriod" apps/meteor/ee/app/license/server/startup.ts -B 2 -A 2Repository: RocketChat/Rocket.Chat
Length of output: 426
🏁 Script executed:
# Check how billing periods are handled elsewhere in the codebase
rg "billablePeriod|format\(new Date.*yyyy-MM" -B 1 -A 1Repository: RocketChat/Rocket.Chat
Length of output: 2931
🏁 Script executed:
# Let's check what the date-fns format() function does with plain Date objects
# by looking at usage patterns and any timezone-related handling elsewhere
rg "formatUTC|utc.*format|timezone" apps/meteor/app/cloud --type js -A 2 -B 2Repository: RocketChat/Rocket.Chat
Length of output: 48
🏁 Script executed:
# Check if there's any server timezone configuration or UTC enforcement
rg "TZ=UTC|timezone|utc" package.json -A 2 -B 2Repository: RocketChat/Rocket.Chat
Length of output: 48
🏁 Script executed:
# Check if date-fns has a UTC-aware formatting option
# Look for any formatUTC or similar usage in the codebase
rg "date-fns" apps/meteor --type js | head -5Repository: RocketChat/Rocket.Chat
Length of output: 48
🏁 Script executed:
# Let's understand the migration commit better - check if there's documentation about this change
git log fcb678b782 --format=fullerRepository: RocketChat/Rocket.Chat
Length of output: 680
🏁 Script executed:
# Verify date-fns behavior: check if there's documentation or other references to timezone handling
# Look for any related issues or comments
rg "date-fns.*timezone|date-fns.*UTC" -A 3 -B 3Repository: RocketChat/Rocket.Chat
Length of output: 48
🏁 Script executed:
# Verify the suggested fix works - check if toISOString() is used elsewhere for date formatting
rg "toISOString\(\).slice" --type js | head -10Repository: RocketChat/Rocket.Chat
Length of output: 48
🏁 Script executed:
# Check if there's a formatUTC or utcFormat function available in date-fns
rg "formatUTC|utc" node_modules/date-fns/package.json 2>/dev/null || echo "Checking date-fns for UTC support..."Repository: RocketChat/Rocket.Chat
Length of output: 102
🏁 Script executed:
# Check if date-fns provides a utcToZonedTime or similar function
# Look at available date-fns imports in the project
rg "import.*from.*date-fns" apps/meteor/app/cloud/server/functions/buildRegistrationData.tsRepository: RocketChat/Rocket.Chat
Length of output: 100
🏁 Script executed:
# Verify the actual behavior: does date-fns have UTC formatting?
# Check the date-fns version being used
grep "date-fns" package.jsonRepository: RocketChat/Rocket.Chat
Length of output: 48
🏁 Script executed:
# Look for any documentation or comments about the timezone issue in the migration commit
git show fcb678b782 -- apps/meteor/app/cloud/server/functions/buildRegistrationData.ts | head -100Repository: RocketChat/Rocket.Chat
Length of output: 3885
🏁 Script executed:
# Quick confirmation that the pattern appears in license counter which is definitely critical
grep -n "monthlyActiveContacts\|MAC" apps/meteor/ee/app/license/server/startup.ts | head -5Repository: RocketChat/Rocket.Chat
Length of output: 204
Use UTC-safe month formatting for billing period.
format(new Date(), 'yyyy-MM') uses the host timezone. On non-UTC servers, this causes incorrect billing month reporting near UTC month boundaries. Since this tracks monthly active contacts for licensing, the regression from the prior UTC-backed approach must be corrected.
Use new Date().toISOString().slice(0, 7) instead. Also remove the now-unused format import from line 2.
Note: The same pattern appears in ee/app/license/server/startup.ts:115, omnichannel/service.ts, statistics.ts, and markRoomResponded.ts—all should be reviewed and updated together.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/meteor/app/cloud/server/functions/buildRegistrationData.ts` at line 72,
The billing-period string is being generated with format(new Date(), 'yyyy-MM')
which depends on the host timezone; update the call where MAC is assigned
(LivechatContacts.countContactsOnPeriod(...)) to use UTC-safe formatting:
replace format(new Date(), 'yyyy-MM') with new Date().toISOString().slice(0, 7),
and remove the now-unused format import from the top of the module; also scan
the other occurrences mentioned (e.g., in license/startup, omnichannel/service,
statistics, markRoomResponded) and apply the same change so monthly reporting
stays UTC-consistent.
|
|
||
| const dateFormat = settings.get('Message_TimeAndDateFormat'); | ||
|
|
||
| const dateFormat = settings.get<string>('Message_TimeAndDateFormat') ?? 'yyyy-MM-dd HH:mm'; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Setting definitions/usages =="
rg -n -C3 "Message_TimeAndDateFormat" apps/meteor packages
echo
echo "== Date format tokens near relevant setting/format usage =="
rg -n -C2 "Message_TimeAndDateFormat|moment\\(\\)\\.format|format\\(new Date\\(" apps/meteor packagesRepository: RocketChat/Rocket.Chat
Length of output: 50379
🏁 Script executed:
cat -n apps/meteor/ee/server/lib/deviceManagement/session.ts | sed -n '50,85p'Repository: RocketChat/Rocket.Chat
Length of output: 1354
🏁 Script executed:
head -20 apps/meteor/ee/server/lib/deviceManagement/session.tsRepository: RocketChat/Rocket.Chat
Length of output: 957
🏁 Script executed:
rg -n "Message_TimeAndDateFormat" apps/meteor --type ts -A 3 -B 1 | head -40Repository: RocketChat/Rocket.Chat
Length of output: 1480
🏁 Script executed:
cat -n apps/meteor/client/hooks/useFormatDateAndTime.tsRepository: RocketChat/Rocket.Chat
Length of output: 1123
Add error handling to date-fns format call for Message_TimeAndDateFormat setting.
At lines 58 and 77, Message_TimeAndDateFormat may contain Moment.js-style tokens (YYYY, DD, Do, etc.) per the setting's i18n description and default value LLL, but date-fns/format does not support these patterns and will throw. Wrapping the format call in a try-catch with a date-fns-compatible fallback ('yyyy-MM-dd HH:mm') ensures login email generation does not crash if an admin customizes the setting with unsupported tokens.
Hardening suggestion
- const dateFormat = settings.get<string>('Message_TimeAndDateFormat') ?? 'yyyy-MM-dd HH:mm';
+ const dateFormat = settings.get<string>('Message_TimeAndDateFormat') ?? 'yyyy-MM-dd HH:mm';
+ let formattedDate: string;
+ try {
+ formattedDate = format(new Date(), dateFormat);
+ } catch {
+ formattedDate = format(new Date(), 'yyyy-MM-dd HH:mm');
+ }
@@
- date: format(new Date(), dateFormat),
+ date: formattedDate,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/meteor/ee/server/lib/deviceManagement/session.ts` at line 58, Wrap the
date-fns format(...) calls that use the dateFormat variable in a try-catch and
fall back to a date-fns-compatible format ('yyyy-MM-dd HH:mm') on error;
specifically, protect the format invocation that uses dateFormat (the variable
initialized from Message_TimeAndDateFormat) and the other format call later in
the file so that if an admin supplies Moment-style tokens the code catches the
thrown error, logs or records the formatting error, and uses the fallback format
instead to avoid crashing login email generation.
| const momentTokenMap: Record<string, string> = { | ||
| LT: 'p', | ||
| LTS: 'pp', | ||
| LLLL: 'EEEE, PPP p', | ||
| LLL: 'PPP p', | ||
| LL: 'PPP', | ||
| L: 'P', | ||
| YYYY: 'yyyy', | ||
| YY: 'yy', | ||
| DD: 'dd', | ||
| D: 'd', | ||
| Do: 'do', | ||
| }; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for dateFormat and timeAndDateFormat configurations
rg -n "dateFormat|timeAndDateFormat" --type ts -C2Repository: RocketChat/Rocket.Chat
Length of output: 18451
Token map is missing mappings for tokens used in actual configurations.
The momentTokenMap lacks mappings for tokens that are actively used in the codebase:
MMM(abbreviated month) → should map toMMM— used in'MMM D, YYYY'format (see worker.fixtures.ts, ChatTranscript.fixtures.ts)H(24-hour no padding) → should map toH— used in'H:mm:ss'format (line 149)mm(minutes) → should map tomm— used in'H:mm:ss'formatss(seconds) → should map toss— used in'H:mm:ss'format
When momentFormatToDateFns() processes these formats, unmapped tokens will pass through unconverted, causing formatDate() to fail or produce incorrect output.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ee/packages/pdf-worker/src/strategies/ChatTranscript.ts` around lines 10 -
22, The momentTokenMap is missing mappings for tokens used elsewhere (causing
unconverted tokens and format failures); update the momentTokenMap object to
include entries for 'MMM' -> 'MMM', 'H' -> 'H', 'mm' -> 'mm', and 'ss' -> 'ss'
so momentFormatToDateFns() can correctly translate formats used by formatDate()
and fixtures; locate and edit the momentTokenMap constant in ChatTranscript.ts
to add these key/value pairs.
| function formatInTimezone(ts: Date | number, timezone: string, fmt: string): string { | ||
| const d = new TZDate(new Date(ts).getTime(), timezone); | ||
| const y = d.getFullYear(); | ||
| const m = d.getMonth(); | ||
| const day = d.getDate(); | ||
| const h = d.getHours(); | ||
| const min = d.getMinutes(); | ||
| const s = d.getSeconds(); | ||
| const date = new Date(y, m, day, h, min, s); | ||
| return format(date, momentFormatToDateFns(fmt)); | ||
| } |
There was a problem hiding this comment.
Potential issue: milliseconds are lost in timezone conversion.
The formatInTimezone function extracts date components from TZDate and creates a new plain Date, which discards milliseconds. While this is likely acceptable for display formatting, it's worth noting.
More importantly, creating a plain Date from the extracted components can cause issues near DST transitions because new Date(y, m, day, h, min, s) interprets these values in the local timezone of the server, not in the target timezone. If the server runs in a different timezone than the target, the formatted output could be incorrect.
🔧 Suggested fix using date-fns-tz format directly
+import { format as formatTz } from '@date-fns/tz';
+
function formatInTimezone(ts: Date | number, timezone: string, fmt: string): string {
- const d = new TZDate(new Date(ts).getTime(), timezone);
- const y = d.getFullYear();
- const m = d.getMonth();
- const day = d.getDate();
- const h = d.getHours();
- const min = d.getMinutes();
- const s = d.getSeconds();
- const date = new Date(y, m, day, h, min, s);
- return format(date, momentFormatToDateFns(fmt));
+ const d = new TZDate(new Date(ts).getTime(), timezone);
+ return formatTz(d, momentFormatToDateFns(fmt));
}Does date-fns format() function work correctly with TZDate from `@date-fns/tz`?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ee/packages/pdf-worker/src/strategies/ChatTranscript.ts` around lines 33 -
43, formatInTimezone currently builds a plain Date from TZDate components
(losing milliseconds and interpreting the result in the server's local
timezone), which can produce incorrect results around DST or when the server
timezone differs from the target; instead, stop extracting components and use
date-fns-tz's timezone-aware formatter (e.g., formatInTimeZone or format from
date-fns-tz) directly with the original timestamp/new Date(ts) and the converted
format (momentFormatToDateFns(fmt)) — update the formatInTimezone function to
call that timezone-aware formatter (keeping TZDate and manual reconstruction
out) so milliseconds are preserved and DST/timezone semantics are correct.
| type CronNextDate = { valueOf(): number }; | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd "Job.ts" packages/agendaRepository: RocketChat/Rocket.Chat
Length of output: 164
🏁 Script executed:
cat -n packages/agenda/src/Job.ts | head -100Repository: RocketChat/Rocket.Chat
Length of output: 4110
🏁 Script executed:
# Check the cron package version in package.json
rg "cron" packages/agenda/package.jsonRepository: RocketChat/Rocket.Chat
Length of output: 84
🏁 Script executed:
# Check cron types if available
fd -i "package.json" packages/agenda | xargs cat | jq '.dependencies, .devDependencies' 2>/dev/nullRepository: RocketChat/Rocket.Chat
Length of output: 265
🏁 Script executed:
fd -type f "\.d\.ts" packages/agenda/node_modules/cron/ 2>/dev/null | head -5Repository: RocketChat/Rocket.Chat
Length of output: 48
🏁 Script executed:
# Check if cron package has types bundled
find packages/agenda/node_modules/cron -name "*.d.ts" 2>/dev/null | head -10Repository: RocketChat/Rocket.Chat
Length of output: 48
🏁 Script executed:
# Try to locate type definition file for cron
rg "getNextDateFrom|_getNextDateFrom" packages/agenda/node_modules/cron/ --type typescript 2>/dev/nullRepository: RocketChat/Rocket.Chat
Length of output: 48
🌐 Web query:
cron npm package 4.4.0 CronTime getNextDateFrom API
💡 Result:
In cron npm package v4.4.0 (kelektiv/node-cron), CronTime#getNextDateFrom is an instance method with this TypeScript signature:
getNextDateFrom(start: Date | CustomDateTime, timeZone?: string | CustomZone): DateTimestart: a JSDateor a LuxonDateTime-like object (CustomDateTime).timeZone(optional): either an IANA timezone string (e.g."America/New_York") or a LuxonZone-like object (CustomZone).- returns: a Luxon
DateTimefor the next scheduled occurrence afterstart. [1][2]
Example:
import { CronTime } from "cron";
const ct = new CronTime("*/5 * * * * *"); // every 5 seconds
const next = ct.getNextDateFrom(new Date());
console.log(next.toISO()); // Luxon DateTimeSources: cron@4.4.0 API docs and type definitions. [1][2]
References
🌐 Web query:
Luxon DateTime valueOf method
💡 Result:
In Luxon, DateTime#valueOf() returns the Unix epoch timestamp in milliseconds (a number). It’s an alias of DateTime#toMillis(), so these are equivalent:
dt.valueOf() // => epoch millis
dt.toMillis() // => same valueBecause valueOf() is used for numeric coercion in JavaScript, Luxon DateTimes can be compared/sorted numerically (e.g., a < b, a - b, or array.sort((a,b) => a - b)). The value represents the same instant in time regardless of the DateTime’s zone (changing the zone changes the representation, not the underlying epoch millis). [1] [2]
Sources:
[1] Luxon API docs (valueOf(): Returns the epoch milliseconds… Alias of DateTime#toMillis) (moment.github.io)
[2] Luxon API docs (same valueOf() / toMillis() description) (luxon.nodejs.cn)
Citations:
- 1: https://moment.github.io/luxon/api-docs/index.html?utm_source=openai
- 2: https://luxon.nodejs.cn/api-docs/index.html?utm_source=openai
Switch to CronTime.getNextDateFrom() instead of the private _getNextDateFrom() method.
The cron 4.4.0 public API exposes CronTime.getNextDateFrom(...) and returns a Luxon DateTime. The current code relies on the private _getNextDateFrom method with a type shim, which is fragile after the major version bump from 1.8.2 to 4.4.0. The public method is type-safe and DateTime.valueOf() returns epoch milliseconds, making it compatible with the existing comparison logic.
Suggested fix
-type CronNextDate = { valueOf(): number };
-
export class Job {
@@
- let nextDate = cronTime._getNextDateFrom(lastRun, timezone ?? undefined) as CronNextDate;
+ let nextDate = cronTime.getNextDateFrom(lastRun, timezone ?? undefined);
if (nextDate.valueOf() === lastRun.getTime() || nextDate.valueOf() <= previousNextRunAt.getTime()) {
// Handle cronTime giving back the same date for the next run time
- nextDate = cronTime._getNextDateFrom(new Date(lastRun.getTime() + 1000), timezone ?? undefined) as CronNextDate;
+ nextDate = cronTime.getNextDateFrom(new Date(lastRun.getTime() + 1000), timezone ?? undefined);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| type CronNextDate = { valueOf(): number }; | |
| export class Job { |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/agenda/src/Job.ts` around lines 17 - 18, The code uses the private
CronTime._getNextDateFrom and a fragile CronNextDate shim; change calls to the
public CronTime.getNextDateFrom(...) which returns a Luxon DateTime, and remove
or replace the CronNextDate type shim with the proper Luxon DateTime type (or
accept its valueOf()), then use dateTime.valueOf() where the code previously
used valueOf() on the shim. Update any occurrences in Job.ts that reference
_getNextDateFrom to call getNextDateFrom and adjust typings accordingly.
| "Message_CustomFields_Enabled": "Permitir campos personalizados em mensagens", | ||
| "Message_DateFormat": "Formato de data", | ||
| "Message_DateFormat_Description": "Veja também: [Moment.js](http://momentjs.com/docs/#/displaying/format/)", | ||
| "Message_DateFormat_Description": "Tokens de formato: L, LL, LLL, YYYY, MM, DD, etc.", |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In date-fns v2+, which date/time format tokens are valid for localized and explicit formatting, and which Moment.js tokens (like L, LT, YYYY, DD) are incompatible or deprecated?
💡 Result:
date-fns v2+ valid format tokens
1) Localized “preset” tokens (date-fns-specific)
Use these in format() / parse() for locale-aware formatting:
P…PPPP= localized date (short → full)p…pppp= localized time (short → full)- Combine freely (e.g.
Pp,PPpp,PPPp,PPPPpppp).
TheseP/ptokens are explicitly listed as date-fns additions for “long localized date/time”. [1]
2) Explicit tokens (Unicode TR-35, plus a few additions)
date-fns v2+ uses Unicode Technical Standard #35 field symbols (not Moment’s custom set) for explicit formatting—e.g. calendar year (y), month (M/L), day-of-month (d), weekday (E/e/c), hours/minutes/seconds (H/h/m/s), timezone (X/x/O/z), etc. The full accepted token table is in the format docs. [1]
Moment.js tokens that are incompatible (or commonly break in date-fns v2+)
A) Moment localized presets are NOT supported
Moment’s locale-aware presets:
L,LL,LLL,LLLLLT,LTS
are Moment-only. [2]
In date-fns, use P/p instead (see above). [1]
B) These Moment-style tokens are blocked by default in date-fns v2+ (and often mean something else)
date-fns treats these as different concepts than most Moment users expect, so it throws unless you opt in:
YYYY/YYin date-fns = local week-numbering year, not calendar year; preferyyyy/yy. (Opt-in:useAdditionalWeekYearTokens: true.) [1][3]DD/Din date-fns = day of year, not day of month; preferdd/d. (Opt-in:useAdditionalDayOfYearTokens: true.) [1][3]
Quick “Moment → date-fns” mapping for the common ones you mentioned
L/LT→P/p(and combos likePp) [1][2]YYYY→yyyy(unless you really want week-year) [1][3]DD→dd(day of month) [1][3]
Sources:
[1] date-fns format token reference + notes (Unicode TR-35; P/p additions; errors for YYYY/DD) — https://date-fns.org/docs/format (mirrored) [turn1view0]
[2] Moment locale-aware preset tokens (LT, LTS, L, LL, LLL, LLLL) — https://momentjs.com/docs/#/displaying/format/ (mirrored) [turn0search3]
[3] date-fns unicodeTokens.md explaining DD/YYYY confusion and opt-in flags — https://github.com/date-fns/date-fns/blob/main/docs/unicodeTokens.md [turn2view0]
🏁 Script executed:
# Search for usages of these i18n keys in the codebase
rg -i "message_dateformat_description|message_timeformat_description" --type json -A 2 -B 2Repository: RocketChat/Rocket.Chat
Length of output: 50378
🏁 Script executed:
# Look for other locale files and check if any have already been updated with date-fns tokens
find . -name "*.i18n.json" -type f | head -20Repository: RocketChat/Rocket.Chat
Length of output: 900
🏁 Script executed:
# Search for date-fns token usage patterns in the codebase
rg "format\(" --type ts --type tsx -A 2 | grep -E "(P{1,4}|p{1,4}|yyyy|dd|hh|HH)" | head -30Repository: RocketChat/Rocket.Chat
Length of output: 93
🏁 Script executed:
# Check the actual usage context - where these descriptions are displayed
rg "Message_DateFormat" --type ts --type tsx -B 3 -A 3Repository: RocketChat/Rocket.Chat
Length of output: 93
🏁 Script executed:
rg "Message_DateFormat_Description|Message_TimeFormat_Description" --type ts -B 3 -A 3Repository: RocketChat/Rocket.Chat
Length of output: 893
🏁 Script executed:
# Search for date-fns usage and format string handling
rg "format\(" --type ts | grep -i "date-fns\|datefns" -A 2 -B 2 | head -40Repository: RocketChat/Rocket.Chat
Length of output: 660
🏁 Script executed:
# Check for migration-related comments or TODOs
rg "moment|Moment" --type ts -i | grep -E "(migrate|TODO|FIXME|date-fns)" -A 2 -B 2 | head -30Repository: RocketChat/Rocket.Chat
Length of output: 48
🏁 Script executed:
# Look at the actual PR or changes related to date-fns migration
git log --oneline --all | grep -i "date-fns\|moment" | head -10Repository: RocketChat/Rocket.Chat
Length of output: 763
🏁 Script executed:
# Find the momentFormatToDateFns function
rg "momentFormatToDateFns" -A 10 -B 2Repository: RocketChat/Rocket.Chat
Length of output: 2606
🏁 Script executed:
# Check the message settings file more closely
cat -n apps/meteor/server/settings/message.ts | head -100Repository: RocketChat/Rocket.Chat
Length of output: 3638
🏁 Script executed:
# Search for recent changes to i18n files related to this migration
git log --oneline --all -20 -- packages/i18n/src/locales/Repository: RocketChat/Rocket.Chat
Length of output: 1554
🏁 Script executed:
# Find momentTokenMap definition
rg "momentTokenMap" -B 5 -A 30Repository: RocketChat/Rocket.Chat
Length of output: 4860
🏁 Script executed:
# Check if there are multiple implementations or if this is used server-wide
rg "momentFormatToDateFns\|momentTokenMap" -lRepository: RocketChat/Rocket.Chat
Length of output: 48
Update format descriptions to reflect date-fns standards
The descriptions still list Moment-style tokens (L, LL, YYYY, DD, LT, LTS) after the migration to date-fns. While a conversion utility exists in the PDF worker, these descriptions don't guide admins toward native date-fns tokens or clarify the conversion behavior. Update to show date-fns equivalents alongside or instead of Moment tokens.
💡 Suggested update
- "Message_DateFormat_Description": "Tokens de formato: L, LL, LLL, YYYY, MM, DD, etc.",
+ "Message_DateFormat_Description": "Tokens de formato (date-fns): P, PP, PPP, yyyy, MM, dd, etc.",
...
- "Message_TimeFormat_Description": "Tokens de formato: LT, LTS, H, h, mm, A, etc.",
+ "Message_TimeFormat_Description": "Tokens de formato (date-fns): p, pp, HH, hh, mm, a, etc.",Also applies to: 3295-3295
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/i18n/src/locales/pt-BR.i18n.json` at line 3232, Update the locale
string for Message_DateFormat_Description (and the other affected key at the
same region) to replace Moment-style tokens with date-fns equivalents and/or
show both sets; reference the i18n key "Message_DateFormat_Description" and the
related key at the other occurrence and change the text to mention date-fns
tokens (e.g., yyyy, MM, dd, HH:mm:ss, etc.), note that a PDF worker conversion
utility will map legacy Moment tokens to date-fns when needed, and make the
description concise so admins are guided to use native date-fns tokens or
understand that automatic conversion occurs.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #40077 +/- ##
===========================================
- Coverage 70.20% 70.18% -0.02%
===========================================
Files 3278 3277 -1
Lines 116605 116591 -14
Branches 20723 20618 -105
===========================================
- Hits 81859 81835 -24
- Misses 31444 31474 +30
+ Partials 3302 3282 -20
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
e982067 to
67581c9
Compare
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/meteor/app/lib/server/functions/addUserToRoom.ts (1)
101-109:⚠️ Potential issue | 🟠 MajorDon't let a stale delete turn the re-invite into a silent no-op.
removeByRoomIdAndUserId()can legitimately returnnullif the banned subscription disappears between the earlier lookup and the delete. Inpackages/models/src/models/Subscriptions.ts:1932-1946it still callsUsers.removeRoomByUserId(), so returning here skipsRoom.createUserSubscription()and can leave the user out of the room while reporting success. Only gate the removal notification/system message ondeletedSub; keep the add flow going.Suggested change
const deletedSub = await Subscriptions.removeByRoomIdAndUserId(rid, userToBeAdded._id); - if (!deletedSub) { - return true; - } - void notifyOnSubscriptionChanged(deletedSub, 'removed'); - - if (!skipSystemMessage && inviter) { - await Message.saveSystemMessage('user-unbanned', rid, userToBeAdded.username!, inviter, { ts: now }); + if (deletedSub) { + void notifyOnSubscriptionChanged(deletedSub, 'removed'); + + if (!skipSystemMessage && inviter) { + await Message.saveSystemMessage('user-unbanned', rid, userToBeAdded.username!, inviter, { ts: now }); + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/app/lib/server/functions/addUserToRoom.ts` around lines 101 - 109, The current early return when Subscriptions.removeByRoomIdAndUserId(rid, userToBeAdded._1d) returns null can silently abort the re-add flow; instead, only skip/guard the notifyOnSubscriptionChanged and Message.saveSystemMessage calls when deletedSub is falsy but always proceed to call Room.createUserSubscription(...) to add the user. Concretely, remove the early "return true" after the delete, change the logic so that if (deletedSub) { void notifyOnSubscriptionChanged(deletedSub, 'removed'); if (!skipSystemMessage && inviter) { await Message.saveSystemMessage(...); } } and then continue with the existing add flow (Room.createUserSubscription and subsequent steps) so a stale delete does not prevent re-inviting the user.
♻️ Duplicate comments (2)
apps/meteor/ee/server/lib/deviceManagement/session.ts (1)
58-77:⚠️ Potential issue | 🟠 MajorHarden formatting for admin-provided date patterns.
Line 58 reads
Message_TimeAndDateFormat, and Line 77 passes it directly todate-fns/format. If the setting still contains Moment-style tokens (LLL,YYYY, etc.),formatcan throw before reaching theMailer.sendtry/catch.Proposed fix
- const dateFormat = settings.get<string>('Message_TimeAndDateFormat') ?? 'yyyy-MM-dd HH:mm'; + const dateFormat = settings.get<string>('Message_TimeAndDateFormat') ?? 'yyyy-MM-dd HH:mm'; + let formattedDate: string; + try { + formattedDate = format(new Date(), dateFormat); + } catch { + formattedDate = format(new Date(), 'yyyy-MM-dd HH:mm'); + } @@ - date: format(new Date(), dateFormat), + date: formattedDate,#!/bin/bash set -euo pipefail echo "== Setting references for Message_TimeAndDateFormat ==" rg -n -C2 "Message_TimeAndDateFormat" apps/meteor packages echo echo "== Current direct date-fns usage in session login mail data ==" rg -n -C2 "format\\(new Date\\(\\), dateFormat\\)" apps/meteor/ee/server/lib/deviceManagement/session.ts echo echo "== Potential Moment-style defaults/usages (e.g., LLL/YYYY/DD/Do) ==" rg -n -C2 "\\bLLL\\b|\\bYYYY\\b|\\bDD\\b|\\bDo\\b|moment\\(\\)\\.format" apps/meteor packages🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/ee/server/lib/deviceManagement/session.ts` around lines 58 - 77, The code reads Message_TimeAndDateFormat into the dateFormat variable and passes it directly to format(new Date(), dateFormat) which can throw for Moment-style tokens; modify the session mail data construction to sanitize/validate/normalize dateFormat before calling date-fns format (e.g., detect common Moment tokens and either translate them to date-fns equivalents or reject them), and wrap the format call in a small try/catch that falls back to a safe default ('yyyy-MM-dd HH:mm') on error; look for the dateFormat variable and the format(new Date(), dateFormat) expression in this file (and update the mail payload building logic that sets mailData.date) to implement the validation+fallback.packages/agenda/src/Job.ts (1)
17-18:⚠️ Potential issue | 🔴 CriticalUse the public
CronTimeAPI instead of the private_getNextDateFromshim.Line 70 and Line 73 rely on a private method plus
CronNextDatecasting (Line 17), which is brittle after thecronmajor upgrade and can silently break scheduling behavior.Suggested fix
-type CronNextDate = { valueOf(): number }; @@ - let nextDate = cronTime._getNextDateFrom(lastRun, timezone ?? undefined) as CronNextDate; + let nextDate = cronTime.getNextDateFrom(lastRun, timezone ?? undefined); if (nextDate.valueOf() === lastRun.getTime() || nextDate.valueOf() <= previousNextRunAt.getTime()) { // Handle cronTime giving back the same date for the next run time - nextDate = cronTime._getNextDateFrom(new Date(lastRun.getTime() + 1000), timezone ?? undefined) as CronNextDate; + nextDate = cronTime.getNextDateFrom(new Date(lastRun.getTime() + 1000), timezone ?? undefined); }#!/bin/bash # Verify private vs public CronTime API usage and dependency version references. rg -n --type=ts '\b_getNextDateFrom\s*\(' rg -n --type=ts '\bgetNextDateFrom\s*\(' rg -n '"cron"\s*:' packages/agenda/package.json rg -n '_getNextDateFrom|getNextDateFrom' packages/agenda/src/definition/cron.d.tsFor npm package `cron` v4.4.x (kelektiv/cron), is `CronTime._getNextDateFrom` part of the public API, or should code use `CronTime.getNextDateFrom`?Also applies to: 70-73
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/agenda/src/Job.ts` around lines 17 - 18, The code is calling the private CronTime._getNextDateFrom and using a fragile CronNextDate cast; replace uses of _getNextDateFrom with the public CronTime.getNextDateFrom API and remove the custom CronNextDate type. Locate occurrences of _getNextDateFrom in Job.ts (references near the logic that computes next run time around the code that uses CronTime and the CronNextDate cast) and swap them to call CronTime.getNextDateFrom (adapting call signatures/return handling as needed), then update any downstream code that assumed valueOf() to use the public Date (or numeric) return from getNextDateFrom so scheduling remains stable across cron major versions.
🧹 Nitpick comments (5)
apps/meteor/ee/server/lib/engagementDashboard/users.ts (1)
92-98: Validate and narrowcreateDestructuredDateinput.
new Date(input)can produceInvalid Date; the current code would then returnNaNdate parts into downstream session queries. Since current call sites passDate, narrowing the type and validating improves reliability.♻️ Proposed refactor
-const createDestructuredDate = (input: Date | number): { year: number; month: number; day: number } => { - const date = new Date(input); +const createDestructuredDate = (input: Date): { year: number; month: number; day: number } => { + if (!isValid(input)) { + throw new Error(`createDestructuredDate: invalid date received: ${String(input)}`); + } return { - year: date.getFullYear(), - month: date.getMonth() + 1, - day: date.getDate(), + year: input.getFullYear(), + month: input.getMonth() + 1, + day: input.getDate(), }; };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/ee/server/lib/engagementDashboard/users.ts` around lines 92 - 98, The createDestructuredDate function currently constructs new Date(input) which can yield an Invalid Date and produce NaNs downstream; change the function signature to accept a Date (not number) and inside validate the date by checking isNaN(date.getTime()) (or similar) and throw a clear error if invalid, or alternatively keep the union type but perform the same validation and throw; update any call sites that pass numbers to provide Date objects so all callers use the narrowed/validated createDestructuredDate.apps/meteor/app/livechat/server/lib/sendTranscript.ts (1)
153-157: Move formatter creation outside the loop and add defensive date handling.The
Intl.DateTimeFormatinstantiation should happen once before the loop rather than per message (lines 153–157). WhilegetTimezone()guarantees valid IANA timezone strings, defensively handle the edge case of invalidmessage.tsby checking forNaNdates before formatting.Suggested approach
+ const dateFormatter = new Intl.DateTimeFormat(userLanguage, { + timeZone: timezone, + dateStyle: 'long', + timeStyle: 'short', + }); + for await (const message of messages) { + const messageDate = new Date(message.ts); + const datetime = Number.isNaN(messageDate.getTime()) ? '' : dateFormatter.format(messageDate); - const datetime = new Intl.DateTimeFormat(userLanguage, { - timeZone: timezone, - dateStyle: 'long', - timeStyle: 'short', - }).format(new Date(message.ts));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/app/livechat/server/lib/sendTranscript.ts` around lines 153 - 157, Move the Intl.DateTimeFormat creation out of the per-message loop by creating a single formatter using userLanguage and timezone (used to compute datetime) before iterating messages; inside the loop, first construct a Date from message.ts and defensively check its validity (e.g., ensure !isNaN(date.getTime())) before calling formatter.format, and handle invalid timestamps by either skipping formatting or using a fallback string (so references: message.ts, userLanguage, timezone, datetime, and the formatter creation should be moved outside the loop that produces datetime).apps/meteor/server/services/omnichannel-analytics/OverviewData.ts (1)
133-140: The DD-MM-YYYY ↔ YYYY-MM-DD round-trip works but creates format coupling.The code converts the
DD-MM-YYYYmap key back toYYYY-MM-DDforparseRangeInTimezone. This works correctly, but creates implicit coupling between the format used at line 110 and the parsing logic here.Consider extracting the format string as a constant or adding a brief comment to make the coupling explicit. This is optional since the current implementation is correct.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/server/services/omnichannel-analytics/OverviewData.ts` around lines 133 - 140, The conversion from busiestDayFromMap ('DD-MM-YYYY') back to 'YYYY-MM-DD' for parseRangeInTimezone creates implicit format coupling; update the code around busiestDay/busiestDayFromMap by extracting the date format(s) into a named constant (e.g., DATE_KEY_FORMAT = 'DD-MM-YYYY' and ISO_DATE_FORMAT = 'YYYY-MM-DD') or add a concise comment explaining the round-trip coupling, then use the constant(s) when splitting/constructing the string passed to parseRangeInTimezone and formatInTimezone so the intended formats are explicit and easy to maintain.apps/meteor/server/services/omnichannel-analytics/utils.ts (1)
59-64: Consider simplifyingformatHourInTimezone- the timezone parameter has no effect.The function creates a
TZDateat the specified hour in the given timezone, then extracts that same hour value. SincegetHours()on aTZDatereturns the hour in its timezone, this always returns the inputhourformatted as "12AM", "1AM", etc., regardless of which timezone is passed.The function works correctly for its current usage (labeling hour slots), but the
timezoneparameter creates a misleading API. Consider either:
- Removing the
timezoneparameter since it's unused- Documenting that the function simply formats an hour number without conversion
💡 Simplified version without unused timezone parameter
-export function formatHourInTimezone(hour: number, timezone: string): string { - const tzDate = new TZDate(2025, 0, 6, hour, 0, 0, 0, timezone); - const h = tzDate.getHours(); - // eslint-disable-next-line no-nested-ternary - return h === 0 ? '12AM' : h < 12 ? `${h}AM` : h === 12 ? '12PM' : `${h - 12}PM`; +export function formatHour12(hour: number): string { + const h = hour % 24; + if (h === 0) return '12AM'; + if (h < 12) return `${h}AM`; + if (h === 12) return '12PM'; + return `${h - 12}PM`; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/server/services/omnichannel-analytics/utils.ts` around lines 59 - 64, formatHourInTimezone currently ignores the timezone argument (TZDate/getHours just returns the input hour), so remove the unused timezone parameter and the TZDate construction: change the signature to formatHourInTimezone(hour: number): string, replace the TZDate logic with a simple hour-to-label mapping (use the existing ternary logic on the input hour), and update all call sites to drop the timezone argument; alternatively, if you prefer to keep the name, add documentation clearly stating it only formats the hour without timezone conversion.apps/meteor/app/livechat/server/business-hour/LivechatBusinessHours.ts (1)
11-17: Consider extracting the UTC offset formatting to avoid duplication.The offset string calculation duplicates the logic in
AbstractBusinessHour.getUTCFromTimezone(). WhilegetUTCFromTimezoneis a protected method on a class, consider extracting it as a standalone utility function (similar toparseDayTimeInZoneandformatUtcDayTime) to maintain DRY principles.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/app/livechat/server/business-hour/LivechatBusinessHours.ts` around lines 11 - 17, The UTC offset string construction in LivechatBusinessHours.ts duplicates logic from AbstractBusinessHour.getUTCFromTimezone; extract that logic into a new utility function (e.g., getUTCOffsetString(timezone?: string) alongside existing utilities like parseDayTimeInZone and formatUtcDayTime), update AbstractBusinessHour.getUTCFromTimezone to call the new utility, and replace the inline offset calculation in LivechatBusinessHours (the const tz/now/offsetMin/sign/h/m/utcStr block) to call the new utility instead to eliminate duplication and keep behavior consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts`:
- Around line 155-163: The method formatDayOfTheWeekFromServerTimezoneAndUtcHour
uses new Date().getTimezoneOffset() which reflects the current DST state; change
the method to accept the business timezone name and compute the offset for the
specific UTC date using tzOffset(timezone, utc) (the same approach used by
getUTCFromTimezone), replace serverOffsetMs calculation with tzOffset(timezone,
utc) * 60_000, and update all callers to pass the timezone so the day/hour
conversion respects DST for the provided utc date.
- Around line 14-20: parseDayTimeInZone currently anchors to January 2025 which
produces a fixed (non-DST-aware) UTC offset; update it to compute the Date using
a real date that reflects the DST state (e.g. use the provided/current date or
accept an explicit date parameter) so offsets vary with DST. Locate
parseDayTimeInZone and change its signature to accept an optional Date (or
compute the next occurrence of the given weekday relative to today) and use that
year/month/day when constructing TZDate; then update callers such as
convertWorkHours and
startDaylightSavingTimeVerifier/hasDaylightSavingTimeChanged to pass the
current/reference date when recalculating/saving business hours so UTC times
reflect the correct DST offset. Ensure behavior preserves existing API when no
date is supplied by defaulting to the current date.
In `@apps/meteor/app/livechat/server/business-hour/LivechatBusinessHours.ts`:
- Around line 29-38: The cron fields for the default start/finish entries are
set with hardcoded day/time and must be generated the same way convertWorkHours
does: take the guessed timezone default times, parse them to UTC (as you already
do for startUtc/finishUtc), then call
formatDayOfTheWeekFromServerTimezoneAndUtcHour(utcDay, utcTime) to produce the
server-local cron day and time; replace the current cron: { dayOfWeek: day,
time: '08:00' } / { dayOfWeek: day, time: '20:00' } assignments with the values
derived from formatDayOfTheWeekFromServerTimezoneAndUtcHour for startUtc and
finishUtc so cron matches AbstractBusinessHour.convertWorkHours behavior.
In `@apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts`:
- Around line 298-300: The fire-and-forget call to
OmnichannelQueueInactivityMonitor.scheduleInquiry(inq._id, aggregatedDate) is
returning a promise that can reject but the surrounding try/catch won't catch
async rejections; change the call to handle promise rejections (either await the
call inside the try/catch or append .catch(...) to the returned promise) so any
error from scheduleInquiry (e.g., job.save()) is logged/handled instead of
becoming an unhandled rejection.
In `@apps/meteor/package.json`:
- Line 76: package.json contains a duplicate dependency entry for `@date-fns/tz`;
remove the later duplicate occurrence so there is only a single "@date-fns/tz"
entry (keep the existing one in proper alphabetical order) to avoid confusion
for package managers and maintain a clean dependencies list.
In `@ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts`:
- Around line 325-356: The teardown can skip deleteUser if one of the retry
blocks throws; wrap the two retry calls in a try/catch/finally (or surround each
retry with its own try/catch and run deleteUser in a finally) so that
deleteUser(rcUser, {}, rc1AdminRequestConfig) always runs regardless of retry
failures; in the catch log or swallow the retry error (include context like
'waiting for Synapse user displayname to propagate to RC' or 'waiting for
subscription fname...' and variables rcRoom._id, federationConfig.hs1.adminUser)
but do not rethrow so cleanup proceeds. Ensure references are to the existing
afterAll block, retry calls, getSubscriptionByRoomId, rcRoom, rcUserConfig, and
deleteUser so the change is easy to locate.
- Line 327: The test mutates the global Synapse admin identity by calling
hs1AdminApp.matrixClient.setDisplayName(federationConfig.hs1.adminUser); instead
create and use a dedicated test user for this spec: provision a new user (e.g.
via the same helper that creates test accounts), obtain its Matrix client
(instead of hs1AdminApp.matrixClient), perform setDisplayName on that client's
setDisplayName method and clean up the user after the test; replace all uses of
federationConfig.hs1.adminUser and hs1AdminApp.matrixClient in this test with
the dedicated test user's identifier and client to avoid shared-state coupling
and flakiness.
---
Outside diff comments:
In `@apps/meteor/app/lib/server/functions/addUserToRoom.ts`:
- Around line 101-109: The current early return when
Subscriptions.removeByRoomIdAndUserId(rid, userToBeAdded._1d) returns null can
silently abort the re-add flow; instead, only skip/guard the
notifyOnSubscriptionChanged and Message.saveSystemMessage calls when deletedSub
is falsy but always proceed to call Room.createUserSubscription(...) to add the
user. Concretely, remove the early "return true" after the delete, change the
logic so that if (deletedSub) { void notifyOnSubscriptionChanged(deletedSub,
'removed'); if (!skipSystemMessage && inviter) { await
Message.saveSystemMessage(...); } } and then continue with the existing add flow
(Room.createUserSubscription and subsequent steps) so a stale delete does not
prevent re-inviting the user.
---
Duplicate comments:
In `@apps/meteor/ee/server/lib/deviceManagement/session.ts`:
- Around line 58-77: The code reads Message_TimeAndDateFormat into the
dateFormat variable and passes it directly to format(new Date(), dateFormat)
which can throw for Moment-style tokens; modify the session mail data
construction to sanitize/validate/normalize dateFormat before calling date-fns
format (e.g., detect common Moment tokens and either translate them to date-fns
equivalents or reject them), and wrap the format call in a small try/catch that
falls back to a safe default ('yyyy-MM-dd HH:mm') on error; look for the
dateFormat variable and the format(new Date(), dateFormat) expression in this
file (and update the mail payload building logic that sets mailData.date) to
implement the validation+fallback.
In `@packages/agenda/src/Job.ts`:
- Around line 17-18: The code is calling the private CronTime._getNextDateFrom
and using a fragile CronNextDate cast; replace uses of _getNextDateFrom with the
public CronTime.getNextDateFrom API and remove the custom CronNextDate type.
Locate occurrences of _getNextDateFrom in Job.ts (references near the logic that
computes next run time around the code that uses CronTime and the CronNextDate
cast) and swap them to call CronTime.getNextDateFrom (adapting call
signatures/return handling as needed), then update any downstream code that
assumed valueOf() to use the public Date (or numeric) return from
getNextDateFrom so scheduling remains stable across cron major versions.
---
Nitpick comments:
In `@apps/meteor/app/livechat/server/business-hour/LivechatBusinessHours.ts`:
- Around line 11-17: The UTC offset string construction in
LivechatBusinessHours.ts duplicates logic from
AbstractBusinessHour.getUTCFromTimezone; extract that logic into a new utility
function (e.g., getUTCOffsetString(timezone?: string) alongside existing
utilities like parseDayTimeInZone and formatUtcDayTime), update
AbstractBusinessHour.getUTCFromTimezone to call the new utility, and replace the
inline offset calculation in LivechatBusinessHours (the const
tz/now/offsetMin/sign/h/m/utcStr block) to call the new utility instead to
eliminate duplication and keep behavior consistent.
In `@apps/meteor/app/livechat/server/lib/sendTranscript.ts`:
- Around line 153-157: Move the Intl.DateTimeFormat creation out of the
per-message loop by creating a single formatter using userLanguage and timezone
(used to compute datetime) before iterating messages; inside the loop, first
construct a Date from message.ts and defensively check its validity (e.g.,
ensure !isNaN(date.getTime())) before calling formatter.format, and handle
invalid timestamps by either skipping formatting or using a fallback string (so
references: message.ts, userLanguage, timezone, datetime, and the formatter
creation should be moved outside the loop that produces datetime).
In `@apps/meteor/ee/server/lib/engagementDashboard/users.ts`:
- Around line 92-98: The createDestructuredDate function currently constructs
new Date(input) which can yield an Invalid Date and produce NaNs downstream;
change the function signature to accept a Date (not number) and inside validate
the date by checking isNaN(date.getTime()) (or similar) and throw a clear error
if invalid, or alternatively keep the union type but perform the same validation
and throw; update any call sites that pass numbers to provide Date objects so
all callers use the narrowed/validated createDestructuredDate.
In `@apps/meteor/server/services/omnichannel-analytics/OverviewData.ts`:
- Around line 133-140: The conversion from busiestDayFromMap ('DD-MM-YYYY') back
to 'YYYY-MM-DD' for parseRangeInTimezone creates implicit format coupling;
update the code around busiestDay/busiestDayFromMap by extracting the date
format(s) into a named constant (e.g., DATE_KEY_FORMAT = 'DD-MM-YYYY' and
ISO_DATE_FORMAT = 'YYYY-MM-DD') or add a concise comment explaining the
round-trip coupling, then use the constant(s) when splitting/constructing the
string passed to parseRangeInTimezone and formatInTimezone so the intended
formats are explicit and easy to maintain.
In `@apps/meteor/server/services/omnichannel-analytics/utils.ts`:
- Around line 59-64: formatHourInTimezone currently ignores the timezone
argument (TZDate/getHours just returns the input hour), so remove the unused
timezone parameter and the TZDate construction: change the signature to
formatHourInTimezone(hour: number): string, replace the TZDate logic with a
simple hour-to-label mapping (use the existing ternary logic on the input hour),
and update all call sites to drop the timezone argument; alternatively, if you
prefer to keep the name, add documentation clearly stating it only formats the
hour without timezone conversion.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 062e1974-20c2-425c-adec-7d80024afb8b
📒 Files selected for processing (82)
.changeset/many-avocados-battle.md.github/actions/meteor-build/action.yml.github/actions/setup-node/action.yml.github/actions/setup-playwright/action.yml.github/workflows/ci-code-check.yml.github/workflows/ci-test-e2e.yml.github/workflows/ci.yml.github/workflows/update-version-durability.ymlapps/meteor/app/cloud/server/functions/buildRegistrationData.tsapps/meteor/app/irc/server/irc-bridge/index.jsapps/meteor/app/lib/server/functions/addUserToRoom.tsapps/meteor/app/lib/server/lib/notifyUsersOnMessage.tsapps/meteor/app/lib/server/lib/processDirectEmail.tsapps/meteor/app/lib/server/lib/sendNotificationsOnMessage.tsapps/meteor/app/lib/server/methods/sendMessage.tsapps/meteor/app/lib/server/methods/updateMessage.tsapps/meteor/app/lib/server/startup/mentionUserNotInChannel.tsapps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.tsapps/meteor/app/livechat/server/business-hour/BusinessHourManager.tsapps/meteor/app/livechat/server/business-hour/Default.tsapps/meteor/app/livechat/server/business-hour/Helper.tsapps/meteor/app/livechat/server/business-hour/LivechatBusinessHours.tsapps/meteor/app/livechat/server/business-hour/filterBusinessHoursThatMustBeOpened.tsapps/meteor/app/livechat/server/hooks/markRoomResponded.tsapps/meteor/app/livechat/server/hooks/processRoomAbandonment.tsapps/meteor/app/livechat/server/lib/analytics/dashboards.tsapps/meteor/app/livechat/server/lib/sendTranscript.tsapps/meteor/app/livechat/server/statistics/LivechatAgentActivityMonitor.tsapps/meteor/app/smarsh-connector/server/functions/generateEml.tsapps/meteor/app/statistics/server/lib/statistics.tsapps/meteor/app/utils/server/lib/getTimezone.tsapps/meteor/ee/app/license/server/startup.tsapps/meteor/ee/app/livechat-enterprise/server/api/reports.tsapps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.tsapps/meteor/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.tsapps/meteor/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.tsapps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.tsapps/meteor/ee/app/livechat-enterprise/server/lib/Helper.tsapps/meteor/ee/server/lib/deviceManagement/session.tsapps/meteor/ee/server/lib/engagementDashboard/channels.tsapps/meteor/ee/server/lib/engagementDashboard/date.tsapps/meteor/ee/server/lib/engagementDashboard/messages.tsapps/meteor/ee/server/lib/engagementDashboard/users.tsapps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.tsapps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoTransferChatsScheduler.tests.tsapps/meteor/ee/tests/unit/apps/livechat-enterprise/server/hooks/afterInquiryQueued.spec.tsapps/meteor/package.jsonapps/meteor/server/lib/dataExport/processDataDownloads.tsapps/meteor/server/lib/dataExport/sendViaEmail.tsapps/meteor/server/lib/getMomentLocale.tsapps/meteor/server/methods/loadLocale.tsapps/meteor/server/services/nps/notification.tsapps/meteor/server/services/omnichannel-analytics/AgentData.tsapps/meteor/server/services/omnichannel-analytics/OverviewData.tsapps/meteor/server/services/omnichannel-analytics/service.tsapps/meteor/server/services/omnichannel-analytics/utils.tsapps/meteor/server/services/omnichannel/service.tsapps/meteor/server/settings/smarsh.tsapps/meteor/tests/data/livechat/businessHours.tsapps/meteor/tests/end-to-end/api/livechat/04-dashboards.tsapps/meteor/tests/end-to-end/api/livechat/23-mac.tsapps/meteor/tests/end-to-end/api/rooms.tsapps/meteor/tests/unit/app/livechat/server/business-hour/BusinessHourManager.spec.tsapps/meteor/tests/unit/server/services/omnichannel-analytics/AgentData.tests.tsapps/meteor/tests/unit/server/services/omnichannel-analytics/OverviewData.tests.tsee/apps/omnichannel-transcript/package.jsonee/apps/queue-worker/package.jsonee/packages/federation-matrix/tests/end-to-end/dms.spec.tsee/packages/omnichannel-services/package.jsonee/packages/pdf-worker/package.jsonee/packages/pdf-worker/src/strategies/ChatTranscript.spec.tsee/packages/pdf-worker/src/strategies/ChatTranscript.tspackages/agenda/package.jsonpackages/agenda/src/Job.tspackages/i18n/src/locales/en.i18n.jsonpackages/i18n/src/locales/es.i18n.jsonpackages/i18n/src/locales/hi-IN.i18n.jsonpackages/i18n/src/locales/pt-BR.i18n.jsonpackages/i18n/src/locales/zh.i18n.jsonpackages/message-types/package.jsonpackages/tools/package.jsonpackages/tools/src/timezone.ts
💤 Files with no reviewable changes (5)
- ee/packages/omnichannel-services/package.json
- ee/apps/queue-worker/package.json
- packages/tools/package.json
- apps/meteor/server/lib/getMomentLocale.ts
- ee/apps/omnichannel-transcript/package.json
✅ Files skipped from review due to trivial changes (29)
- .github/workflows/update-version-durability.yml
- .changeset/many-avocados-battle.md
- .github/actions/setup-playwright/action.yml
- apps/meteor/server/services/nps/notification.ts
- apps/meteor/app/lib/server/methods/sendMessage.ts
- packages/message-types/package.json
- apps/meteor/server/services/omnichannel/service.ts
- apps/meteor/app/livechat/server/business-hour/Default.ts
- apps/meteor/app/livechat/server/hooks/markRoomResponded.ts
- apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts
- apps/meteor/server/settings/smarsh.ts
- apps/meteor/ee/tests/unit/apps/livechat-enterprise/server/hooks/afterInquiryQueued.spec.ts
- ee/packages/pdf-worker/src/strategies/ChatTranscript.spec.ts
- packages/agenda/package.json
- .github/workflows/ci-test-e2e.yml
- apps/meteor/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.ts
- apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoTransferChatsScheduler.tests.ts
- apps/meteor/ee/server/lib/engagementDashboard/channels.ts
- apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts
- apps/meteor/server/lib/dataExport/processDataDownloads.ts
- apps/meteor/app/irc/server/irc-bridge/index.js
- apps/meteor/server/methods/loadLocale.ts
- apps/meteor/tests/unit/server/services/omnichannel-analytics/AgentData.tests.ts
- packages/i18n/src/locales/zh.i18n.json
- packages/i18n/src/locales/es.i18n.json
- apps/meteor/tests/unit/server/services/omnichannel-analytics/OverviewData.tests.ts
- packages/i18n/src/locales/hi-IN.i18n.json
- packages/i18n/src/locales/pt-BR.i18n.json
- packages/i18n/src/locales/en.i18n.json
🚧 Files skipped from review as they are similar to previous changes (25)
- apps/meteor/app/livechat/server/business-hour/Helper.ts
- apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
- apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts
- apps/meteor/app/utils/server/lib/getTimezone.ts
- apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
- ee/packages/pdf-worker/package.json
- apps/meteor/app/lib/server/methods/updateMessage.ts
- apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts
- apps/meteor/app/smarsh-connector/server/functions/generateEml.ts
- apps/meteor/server/lib/dataExport/sendViaEmail.ts
- apps/meteor/app/lib/server/startup/mentionUserNotInChannel.ts
- apps/meteor/ee/server/lib/engagementDashboard/date.ts
- apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts
- apps/meteor/app/livechat/server/statistics/LivechatAgentActivityMonitor.ts
- apps/meteor/app/livechat/server/hooks/processRoomAbandonment.ts
- apps/meteor/app/lib/server/lib/processDirectEmail.ts
- apps/meteor/ee/server/lib/engagementDashboard/messages.ts
- apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts
- apps/meteor/app/livechat/server/lib/analytics/dashboards.ts
- apps/meteor/tests/unit/app/livechat/server/business-hour/BusinessHourManager.spec.ts
- apps/meteor/ee/app/livechat-enterprise/server/api/reports.ts
- apps/meteor/app/livechat/server/business-hour/filterBusinessHoursThatMustBeOpened.ts
- packages/tools/src/timezone.ts
- apps/meteor/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.ts
- apps/meteor/server/services/omnichannel-analytics/AgentData.ts
| export function parseDayTimeInZone(day: string, timeStr: string, timezone: string): Date { | ||
| const [h = 0, m = 0] = timeStr.split(':').map(Number); | ||
| const dayIndex = DAY_NAMES.indexOf(day); | ||
| const refDate = 5 + dayIndex; // 2025-01-05 is Sunday | ||
| const tzDate = new TZDate(2025, 0, refDate, h, m, 0, timezone); | ||
| return new Date(tzDate.getTime()); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for DST-related handling or verifier logic
rg -n -i 'dst|daylight|summer|winter' apps/meteor/app/livechat/server/business-hour/Repository: RocketChat/Rocket.Chat
Length of output: 2277
🏁 Script executed:
# Get the hasDaylightSavingTimeChanged method implementation
sed -n '261,268p' apps/meteor/app/livechat/server/business-hour/BusinessHourManager.tsRepository: RocketChat/Rocket.Chat
Length of output: 491
🏁 Script executed:
# Get the startDaylightSavingTimeVerifier method implementation
sed -n '273,285p' apps/meteor/app/livechat/server/business-hour/BusinessHourManager.tsRepository: RocketChat/Rocket.Chat
Length of output: 633
🏁 Script executed:
# Check the broader context of DST verifier registration and invocation
sed -n '30,50p' apps/meteor/app/livechat/server/business-hour/BusinessHourManager.tsRepository: RocketChat/Rocket.Chat
Length of output: 673
🏁 Script executed:
# Get the complete startDaylightSavingTimeVerifier method
sed -n '273,310p' apps/meteor/app/livechat/server/business-hour/BusinessHourManager.tsRepository: RocketChat/Rocket.Chat
Length of output: 1282
🏁 Script executed:
# Check how business hours are used when creating cron jobs
rg -A 10 'createCronJobsForWorkHours' apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts | head -30Repository: RocketChat/Rocket.Chat
Length of output: 1075
🏁 Script executed:
# Check the LivechatBusinessHours model to see how times are stored
grep -n 'utc\|offset\|timezone' apps/meteor/app/livechat/server/business-hour/LivechatBusinessHours.ts | head -20Repository: RocketChat/Rocket.Chat
Length of output: 503
🏁 Script executed:
# Find saveBusinessHour implementation - likely in the business hour type classes
rg -n 'saveBusinessHour' apps/meteor/app/livechat/server/business-hour/ --type tsRepository: RocketChat/Rocket.Chat
Length of output: 815
🏁 Script executed:
# Look for where UTC times are calculated/formatted from local times
rg -B 5 -A 5 'formatUtcDayTime\|parseDayTimeInZone' apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.tsRepository: RocketChat/Rocket.Chat
Length of output: 48
🏁 Script executed:
# Read the Default.ts saveBusinessHour implementation
sed -n '19,80p' apps/meteor/app/livechat/server/business-hour/Default.tsRepository: RocketChat/Rocket.Chat
Length of output: 591
🏁 Script executed:
# Search for where UTC day/time conversions happen
rg -n 'start\.time\|finish\.time\|dayOfWeek' apps/meteor/app/livechat/server/business-hour/Default.ts | head -20Repository: RocketChat/Rocket.Chat
Length of output: 48
🏁 Script executed:
# Look at AbstractBusinessHour.ts to see the helper functions
cat -n apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts | head -50Repository: RocketChat/Rocket.Chat
Length of output: 3168
🏁 Script executed:
# Find baseSaveBusinessHour implementation
rg -n 'baseSaveBusinessHour' apps/meteor/app/livechat/server/business-hour/ -A 30 | head -60Repository: RocketChat/Rocket.Chat
Length of output: 4353
🏁 Script executed:
# Also check if workHours UTC times are recalculated during save
rg -n 'workHours\|utc.*dayOfWeek' apps/meteor/app/livechat/server/business-hour/Default.ts -B 2 -A 2Repository: RocketChat/Rocket.Chat
Length of output: 48
🏁 Script executed:
# Search for where UTC conversion happens when saving
rg -n 'parseDayTimeInZone\|formatUtcDayTime' apps/meteor/app/livechat/server/business-hour/ --type tsRepository: RocketChat/Rocket.Chat
Length of output: 48
🏁 Script executed:
# Verify the rest of convertWorkHours to see full context
sed -n '110,144p' apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.tsRepository: RocketChat/Rocket.Chat
Length of output: 1401
🏁 Script executed:
# Check if createCronJobsForWorkHours uses the UTC times or offset somehow differently
rg -n 'createCronJobsForWorkHours' apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts -A 20 | head -40Repository: RocketChat/Rocket.Chat
Length of output: 1444
🏁 Script executed:
# Get formatDayOfTheWeekFromServerTimezoneAndUtcHour implementation
sed -n '155,175p' apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.tsRepository: RocketChat/Rocket.Chat
Length of output: 554
🏁 Script executed:
# Check if there are any tests that verify DST handling
find apps/meteor/app/livechat/server/business-hour -name '*.spec.ts' -o -name '*.test.ts' | head -5Repository: RocketChat/Rocket.Chat
Length of output: 155
🏁 Script executed:
# Look for any related issues or comments about DST in the codebase
rg -n 'TODO|FIXME|BUG|XXX' apps/meteor/app/livechat/server/business-hour/ | grep -i 'dst\|dst\|daylight\|timezone\|offset'Repository: RocketChat/Rocket.Chat
Length of output: 48
The DST verifier exists but does not fix the underlying offset calculation issue.
While startDaylightSavingTimeVerifier() detects DST changes via hasDaylightSavingTimeChanged(), it does not solve the core problem. When DST is detected, the verifier re-saves business hours by calling convertWorkHours(), which recalculates UTC times using parseDayTimeInZone(). Since parseDayTimeInZone always anchors to January 2025 (a standard-time month), it produces identical UTC calculations regardless of the actual DST period.
Example: 08:00 in America/New_York will always convert to 13:00 UTC (EST offset), even when re-saved during EDT (when it should be 12:00 UTC). The verifier updates the offset string for display but leaves the UTC time calculations unchanged, causing business hours to be offset by ±1 hour for approximately 6 months per year in DST-observing timezones.
To fix this, parseDayTimeInZone() should use the actual current date or allow specifying an arbitrary date to capture the correct DST state, rather than a hardcoded January 2025 anchor.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts` around
lines 14 - 20, parseDayTimeInZone currently anchors to January 2025 which
produces a fixed (non-DST-aware) UTC offset; update it to compute the Date using
a real date that reflects the DST state (e.g. use the provided/current date or
accept an explicit date parameter) so offsets vary with DST. Locate
parseDayTimeInZone and change its signature to accept an optional Date (or
compute the next occurrence of the given weekday relative to today) and use that
year/month/day when constructing TZDate; then update callers such as
convertWorkHours and
startDaylightSavingTimeVerifier/hasDaylightSavingTimeChanged to pass the
current/reference date when recalculating/saving business hours so UTC times
reflect the correct DST offset. Ensure behavior preserves existing API when no
date is supplied by defaulting to the current date.
| private formatDayOfTheWeekFromServerTimezoneAndUtcHour(utc: Date, fmt: 'dddd' | 'HH:mm'): string { | ||
| // Convert UTC date to server local timezone by adding the server's UTC offset | ||
| const serverOffsetMs = -new Date().getTimezoneOffset() * 60_000; | ||
| const local = new Date(utc.getTime() + serverOffsetMs); | ||
| if (fmt === 'dddd') { | ||
| return DAY_NAMES[local.getUTCDay()]; | ||
| } | ||
| return `${String(local.getUTCHours()).padStart(2, '0')}:${String(local.getUTCMinutes()).padStart(2, '0')}`; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check how cron.dayOfWeek and cron.time are consumed downstream
rg -n 'cron\.dayOfWeek|cron\.time' apps/meteor/app/livechat/server/business-hour/Repository: RocketChat/Rocket.Chat
Length of output: 629
🏁 Script executed:
#!/bin/bash
# Find all usages of formatDayOfTheWeekFromServerTimezoneAndUtcHour
rg -n 'formatDayOfTheWeekFromServerTimezoneAndUtcHour' apps/meteor/app/livechat/server/business-hour/Repository: RocketChat/Rocket.Chat
Length of output: 877
🏁 Script executed:
#!/bin/bash
# Examine the AbstractBusinessHour.ts file to understand the method and its context
wc -l apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.tsRepository: RocketChat/Rocket.Chat
Length of output: 139
🏁 Script executed:
#!/bin/bash
# Look at the broader context around the method in question
sed -n '140,180p' apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.tsRepository: RocketChat/Rocket.Chat
Length of output: 996
🏁 Script executed:
#!/bin/bash
# Check the imports and structure of the file
head -50 apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.tsRepository: RocketChat/Rocket.Chat
Length of output: 2818
🏁 Script executed:
#!/bin/bash
# Get broader context around lines 130-139 to see where startUtc/finishUtc come from
sed -n '115,145p' apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.tsRepository: RocketChat/Rocket.Chat
Length of output: 1065
🏁 Script executed:
#!/bin/bash
# Search for where this method is being called (in which function)
sed -n '110,150p' apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.tsRepository: RocketChat/Rocket.Chat
Length of output: 1669
🏁 Script executed:
#!/bin/bash
# Look at how toMinutesSinceSunday uses cron fields and when scheduling happens
sed -n '30,70p' apps/meteor/app/livechat/server/business-hour/filterBusinessHoursThatMustBeOpened.tsRepository: RocketChat/Rocket.Chat
Length of output: 1328
🏁 Script executed:
#!/bin/bash
# Check the toMinutesSinceSunday function definition
rg -n 'function toMinutesSinceSunday|const toMinutesSinceSunday' apps/meteor/app/livechat/server/business-hour/Repository: RocketChat/Rocket.Chat
Length of output: 223
🏁 Script executed:
#!/bin/bash
# Get the toMinutesSinceSunday and getCurrentDayAndTime function definitions
sed -n '1,30p' apps/meteor/app/livechat/server/business-hour/filterBusinessHoursThatMustBeOpened.tsRepository: RocketChat/Rocket.Chat
Length of output: 913
🏁 Script executed:
#!/bin/bash
# Check the full method signature and class context - is timezone info available?
sed -n '100,165p' apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.tsRepository: RocketChat/Rocket.Chat
Length of output: 2669
🏁 Script executed:
#!/bin/bash
# Check if this is the right method - verify it's not using any timezone context
ast-grep --pattern 'private formatDayOfTheWeekFromServerTimezoneAndUtcHour'Repository: RocketChat/Rocket.Chat
Length of output: 48
🏁 Script executed:
#!/bin/bash
# Check if timezone name could be passed to the method
sed -n '109,135p' apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.tsRepository: RocketChat/Rocket.Chat
Length of output: 1108
The DST concern is valid. Fix the offset calculation to account for the UTC date being converted, not the current date.
The formatDayOfTheWeekFromServerTimezoneAndUtcHour method uses new Date().getTimezoneOffset() to calculate the offset, which reflects the current date's DST state. For UTC dates representing scheduled times, especially those across DST boundaries, this produces incorrect results. The method should accept the business hour's timezone name (available from the calling context) and use tzOffset(timezone, utc) to determine the correct offset for the specific date being converted, matching the pattern already used in getUTCFromTimezone nearby.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts` around
lines 155 - 163, The method formatDayOfTheWeekFromServerTimezoneAndUtcHour uses
new Date().getTimezoneOffset() which reflects the current DST state; change the
method to accept the business timezone name and compute the offset for the
specific UTC date using tzOffset(timezone, utc) (the same approach used by
getUTCFromTimezone), replace serverOffsetMs calculation with tzOffset(timezone,
utc) * 60_000, and update all callers to pass the timezone so the day/hour
conversion respects DST for the provided utc date.
| start: { | ||
| time: '08:00', | ||
| utc: { dayOfWeek: startUtc.ddd, time: startUtc.time }, | ||
| cron: { dayOfWeek: day, time: '08:00' }, | ||
| }, | ||
| cron: { | ||
| dayOfWeek: moment(`${day}:20:00`, 'dddd:HH:mm').format('dddd'), | ||
| time: moment(`${day}:20:00`, 'dddd:HH:mm').format('HH:mm'), | ||
| finish: { | ||
| time: '20:00', | ||
| utc: { dayOfWeek: finishUtc.ddd, time: finishUtc.time }, | ||
| cron: { dayOfWeek: day, time: '20:00' }, | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check how cron fields are consumed by the scheduler
rg -n -A5 'start\.cron|finish\.cron' apps/meteor/app/livechat/server/business-hour/Repository: RocketChat/Rocket.Chat
Length of output: 1844
🏁 Script executed:
#!/bin/bash
# Find the implementation of formatDayOfTheWeekFromServerTimezoneAndUtcHour
rg -n 'formatDayOfTheWeekFromServerTimezoneAndUtcHour' apps/meteor/app/livechat/server/business-hour/ -A 10 -B 2Repository: RocketChat/Rocket.Chat
Length of output: 3881
🏁 Script executed:
#!/bin/bash
# Find AbstractBusinessHour.convertWorkHours to see how it sets cron values
rg -n 'convertWorkHours' apps/meteor/app/livechat/server/business-hour/ -A 30 -B 2Repository: RocketChat/Rocket.Chat
Length of output: 5220
🏁 Script executed:
#!/bin/bash
# Check the guessTimezone function and understand what timezone it produces
rg -n 'guessTimezone' apps/meteor/app/livechat/server/business-hour/LivechatBusinessHours.ts -B 5 -A 5Repository: RocketChat/Rocket.Chat
Length of output: 856
The cron field values must be converted to server timezone to match AbstractBusinessHour.convertWorkHours behavior.
Currently, hardcoded day names and times (dayOfWeek: day, time: '08:00' / '20:00') are set without timezone conversion. However, in convertWorkHours, cron values are derived from UTC times converted to server-local timezone via formatDayOfTheWeekFromServerTimezoneAndUtcHour(). This inconsistency causes the default business hour's cron schedule to misalign with saved business hours when the server is not in UTC timezone.
Use the same conversion pattern: parse the default times in the guessed timezone to UTC, then convert those UTC values to server-local timezone for the cron fields—matching the pattern used in convertWorkHours lines 113–114 and 130–131.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/meteor/app/livechat/server/business-hour/LivechatBusinessHours.ts`
around lines 29 - 38, The cron fields for the default start/finish entries are
set with hardcoded day/time and must be generated the same way convertWorkHours
does: take the guessed timezone default times, parse them to UTC (as you already
do for startUtc/finishUtc), then call
formatDayOfTheWeekFromServerTimezoneAndUtcHour(utcDay, utcTime) to produce the
server-local cron day and time; replace the current cron: { dayOfWeek: day,
time: '08:00' } / { dayOfWeek: day, time: '20:00' } assignments with the values
derived from formatDayOfTheWeekFromServerTimezoneAndUtcHour for startUtc and
finishUtc so cron matches AbstractBusinessHour.convertWorkHours behavior.
| try { | ||
| void OmnichannelQueueInactivityMonitor.scheduleInquiry(inq._id, new Date(aggregatedDate.format())); | ||
| void OmnichannelQueueInactivityMonitor.scheduleInquiry(inq._id, aggregatedDate); | ||
| } catch (e) { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify async definition and the fire-and-forget call pattern.
rg -nP 'async\s+scheduleInquiry\s*\(' apps/meteor/ee/app/livechat-enterprise/server/lib/QueueInactivityMonitor.ts
rg -nP 'try\s*\{\s*void\s+OmnichannelQueueInactivityMonitor\.scheduleInquiry\s*\(' apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.tsRepository: RocketChat/Rocket.Chat
Length of output: 139
🏁 Script executed:
# Read the relevant section of Helper.ts to see the actual try/catch code
sed -n '290,310p' apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.tsRepository: RocketChat/Rocket.Chat
Length of output: 778
🏁 Script executed:
# Get more context about the function to understand intent
sed -n '280,315p' apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.tsRepository: RocketChat/Rocket.Chat
Length of output: 1115
🏁 Script executed:
# Check the implementation of scheduleInquiry to see what it might throw/reject
sed -n '77,120p' apps/meteor/ee/app/livechat-enterprise/server/lib/QueueInactivityMonitor.tsRepository: RocketChat/Rocket.Chat
Length of output: 1372
🏁 Script executed:
# Check if there's any global unhandled rejection handler in the codebase
rg -l 'unhandledRejection|process\.on.*reject' --type ts --type js | head -10Repository: RocketChat/Rocket.Chat
Length of output: 349
🏁 Script executed:
# Verify the context where this function is called to confirm it's intentional fire-and-forget
sed -n '288,305p' apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.tsRepository: RocketChat/Rocket.Chat
Length of output: 689
🏁 Script executed:
# Check if there are similar fire-and-forget patterns elsewhere in the codebase to understand conventions
rg -B2 -A2 'void.*\(.*\)\.catch' apps/meteor/ee/app/livechat-enterprise/server/lib/ | head -20Repository: RocketChat/Rocket.Chat
Length of output: 48
🏁 Script executed:
# Check if there are other instances of fire-and-forget patterns with proper error handling in the codebase
rg -B1 -A3 'void.*\(.*\)' apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts | head -30Repository: RocketChat/Rocket.Chat
Length of output: 373
🏁 Script executed:
# Check what types of errors scheduleInquiry or job.save() might throw
sed -n '77,90p' apps/meteor/ee/app/livechat-enterprise/server/lib/QueueInactivityMonitor.tsRepository: RocketChat/Rocket.Chat
Length of output: 573
Unhandled promise rejection: try/catch does not catch async errors from scheduleInquiry.
Line 299 returns a promise without awaiting it, so the surrounding try/catch only catches synchronous throws. Any rejection from scheduleInquiry (e.g., from job.save()) will become an unhandled promise rejection.
Use .catch() to handle errors in the fire-and-forget pattern:
Suggested fix
try {
- void OmnichannelQueueInactivityMonitor.scheduleInquiry(inq._id, aggregatedDate);
+ OmnichannelQueueInactivityMonitor.scheduleInquiry(inq._id, aggregatedDate).catch((e) => {
+ logger.error({ err: e });
+ });
} catch (e) {
// this will usually happen if other instance attempts to re-create a job
logger.error({ err: e });
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts` around lines 298
- 300, The fire-and-forget call to
OmnichannelQueueInactivityMonitor.scheduleInquiry(inq._id, aggregatedDate) is
returning a promise that can reject but the surrounding try/catch won't catch
async rejections; change the call to handle promise rejections (either await the
call inside the try/catch or append .catch(...) to the returned promise) so any
error from scheduleInquiry (e.g., job.save()) is logged/handled instead of
becoming an unhandled rejection.
| afterAll(async () => { | ||
| // Reset display name back to original before deleting the user | ||
| await hs1AdminApp.matrixClient.setDisplayName(federationConfig.hs1.adminUser); | ||
|
|
||
| // wait until the name change is reflected in RC before finishing the test | ||
| await retry( | ||
| 'waiting for Synapse user displayname to propagate to RC', | ||
| async () => { | ||
| const response = await rc1AdminRequestConfig.request | ||
| .get(api('users.info')) | ||
| .set(rc1AdminRequestConfig.credentials) | ||
| .query({ username: federationConfig.hs1.adminMatrixUserId }) | ||
| .expect(200); | ||
|
|
||
| expect(response.body.user).toHaveProperty('name', federationConfig.hs1.adminUser); | ||
| }, | ||
| { retries: 15, delayMs: 1000 }, | ||
| ); | ||
|
|
||
| // Also wait for the DM subscription fname to be updated, since this propagates | ||
| // asynchronously after the user name change via debounced Room.updateDirectMessageRoomName | ||
| await retry( | ||
| 'waiting for subscription fname to reflect reset display name', | ||
| async () => { | ||
| const sub = await getSubscriptionByRoomId(rcRoom._id, rcUserConfig.credentials, rcUserConfig.request); | ||
| expect(sub).toHaveProperty('fname', federationConfig.hs1.adminUser); | ||
| }, | ||
| { retries: 10, delayMs: 1000 }, | ||
| ); | ||
|
|
||
| await deleteUser(rcUser, {}, rc1AdminRequestConfig); | ||
| }); |
There was a problem hiding this comment.
Make teardown failure-safe to avoid leaked state.
On Line 330-Line 353, a retry failure prevents Line 355 (deleteUser) from running. This can leave residual users and pollute subsequent tests.
Suggested hardening
afterAll(async () => {
- // Reset display name back to original before deleting the user
- await hs1AdminApp.matrixClient.setDisplayName(federationConfig.hs1.adminUser);
-
- // wait until the name change is reflected in RC before finishing the test
- await retry(
- 'waiting for Synapse user displayname to propagate to RC',
- async () => {
- const response = await rc1AdminRequestConfig.request
- .get(api('users.info'))
- .set(rc1AdminRequestConfig.credentials)
- .query({ username: federationConfig.hs1.adminMatrixUserId })
- .expect(200);
-
- expect(response.body.user).toHaveProperty('name', federationConfig.hs1.adminUser);
- },
- { retries: 15, delayMs: 1000 },
- );
-
- // Also wait for the DM subscription fname to be updated, since this propagates
- // asynchronously after the user name change via debounced Room.updateDirectMessageRoomName
- await retry(
- 'waiting for subscription fname to reflect reset display name',
- async () => {
- const sub = await getSubscriptionByRoomId(rcRoom._id, rcUserConfig.credentials, rcUserConfig.request);
- expect(sub).toHaveProperty('fname', federationConfig.hs1.adminUser);
- },
- { retries: 10, delayMs: 1000 },
- );
-
- await deleteUser(rcUser, {}, rc1AdminRequestConfig);
+ try {
+ await hs1AdminApp.matrixClient.setDisplayName(federationConfig.hs1.adminUser);
+
+ await retry(
+ 'waiting for Synapse user displayname to propagate to RC',
+ async () => {
+ const response = await rc1AdminRequestConfig.request
+ .get(api('users.info'))
+ .set(rc1AdminRequestConfig.credentials)
+ .query({ username: federationConfig.hs1.adminMatrixUserId })
+ .expect(200);
+
+ expect(response.body.user).toHaveProperty('name', federationConfig.hs1.adminUser);
+ },
+ { retries: 15, delayMs: 1000 },
+ );
+
+ if (rcRoom?._id) {
+ await retry(
+ 'waiting for subscription fname to reflect reset display name',
+ async () => {
+ const sub = await getSubscriptionByRoomId(rcRoom._id, rcUserConfig.credentials, rcUserConfig.request);
+ expect(sub).toHaveProperty('fname', federationConfig.hs1.adminUser);
+ },
+ { retries: 10, delayMs: 1000 },
+ );
+ }
+ } finally {
+ await deleteUser(rcUser, {}, rc1AdminRequestConfig);
+ }
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| afterAll(async () => { | |
| // Reset display name back to original before deleting the user | |
| await hs1AdminApp.matrixClient.setDisplayName(federationConfig.hs1.adminUser); | |
| // wait until the name change is reflected in RC before finishing the test | |
| await retry( | |
| 'waiting for Synapse user displayname to propagate to RC', | |
| async () => { | |
| const response = await rc1AdminRequestConfig.request | |
| .get(api('users.info')) | |
| .set(rc1AdminRequestConfig.credentials) | |
| .query({ username: federationConfig.hs1.adminMatrixUserId }) | |
| .expect(200); | |
| expect(response.body.user).toHaveProperty('name', federationConfig.hs1.adminUser); | |
| }, | |
| { retries: 15, delayMs: 1000 }, | |
| ); | |
| // Also wait for the DM subscription fname to be updated, since this propagates | |
| // asynchronously after the user name change via debounced Room.updateDirectMessageRoomName | |
| await retry( | |
| 'waiting for subscription fname to reflect reset display name', | |
| async () => { | |
| const sub = await getSubscriptionByRoomId(rcRoom._id, rcUserConfig.credentials, rcUserConfig.request); | |
| expect(sub).toHaveProperty('fname', federationConfig.hs1.adminUser); | |
| }, | |
| { retries: 10, delayMs: 1000 }, | |
| ); | |
| await deleteUser(rcUser, {}, rc1AdminRequestConfig); | |
| }); | |
| afterAll(async () => { | |
| try { | |
| await hs1AdminApp.matrixClient.setDisplayName(federationConfig.hs1.adminUser); | |
| await retry( | |
| 'waiting for Synapse user displayname to propagate to RC', | |
| async () => { | |
| const response = await rc1AdminRequestConfig.request | |
| .get(api('users.info')) | |
| .set(rc1AdminRequestConfig.credentials) | |
| .query({ username: federationConfig.hs1.adminMatrixUserId }) | |
| .expect(200); | |
| expect(response.body.user).toHaveProperty('name', federationConfig.hs1.adminUser); | |
| }, | |
| { retries: 15, delayMs: 1000 }, | |
| ); | |
| if (rcRoom?._id) { | |
| await retry( | |
| 'waiting for subscription fname to reflect reset display name', | |
| async () => { | |
| const sub = await getSubscriptionByRoomId(rcRoom._id, rcUserConfig.credentials, rcUserConfig.request); | |
| expect(sub).toHaveProperty('fname', federationConfig.hs1.adminUser); | |
| }, | |
| { retries: 10, delayMs: 1000 }, | |
| ); | |
| } | |
| } finally { | |
| await deleteUser(rcUser, {}, rc1AdminRequestConfig); | |
| } | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts` around lines 325
- 356, The teardown can skip deleteUser if one of the retry blocks throws; wrap
the two retry calls in a try/catch/finally (or surround each retry with its own
try/catch and run deleteUser in a finally) so that deleteUser(rcUser, {},
rc1AdminRequestConfig) always runs regardless of retry failures; in the catch
log or swallow the retry error (include context like 'waiting for Synapse user
displayname to propagate to RC' or 'waiting for subscription fname...' and
variables rcRoom._id, federationConfig.hs1.adminUser) but do not rethrow so
cleanup proceeds. Ensure references are to the existing afterAll block, retry
calls, getSubscriptionByRoomId, rcRoom, rcUserConfig, and deleteUser so the
change is easy to locate.
|
|
||
| afterAll(async () => { | ||
| // Reset display name back to original before deleting the user | ||
| await hs1AdminApp.matrixClient.setDisplayName(federationConfig.hs1.adminUser); |
There was a problem hiding this comment.
Avoid mutating the shared Synapse admin identity in-test.
Line 369 changes the global HS1 admin display name, and Line 327 resets it later. This introduces shared-state coupling and can create flaky behavior when other federation tests use the same admin account concurrently or interleave unexpectedly.
Prefer creating a dedicated Synapse test user for this scenario and mutating only that user’s display name.
Also applies to: 369-369
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts` at line 327, The
test mutates the global Synapse admin identity by calling
hs1AdminApp.matrixClient.setDisplayName(federationConfig.hs1.adminUser); instead
create and use a dedicated test user for this spec: provision a new user (e.g.
via the same helper that creates test accounts), obtain its Matrix client
(instead of hs1AdminApp.matrixClient), perform setDisplayName on that client's
setDisplayName method and clean up the user after the test; replace all uses of
federationConfig.hs1.adminUser and hs1AdminApp.matrixClient in this test with
the dedicated test user's identifier and client to avoid shared-state coupling
and flakiness.
67581c9 to
62e5237
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/i18n/src/locales/en.i18n.json (1)
5929-5930:⚠️ Potential issue | 🟠 MajorRe-add
Yesterday_atto prevent user-facing fallback text regression.
useTimeAgostill callst('Yesterday_at')(apps/meteor/client/hooks/useTimeAgo.ts, Line 18). With this key missing, users can see literalYesterday_atin timestamp labels.💡 Proposed fix
"Yesterday": "Yesterday", + "Yesterday_at": "Yesterday at", "You": "You",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/i18n/src/locales/en.i18n.json` around lines 5929 - 5930, Re-add the missing "Yesterday_at" translation key used by useTimeAgo (apps/meteor/client/hooks/useTimeAgo.ts calls t('Yesterday_at')); add the key to en.i18n.json with a human-readable interpolated value that matches the hook's expected token (e.g., "Yesterday at {{time}}") and propagate the same key to other locale files to avoid fallback showing the literal key.
🧹 Nitpick comments (1)
packages/agenda/src/Job.ts (1)
59-61: Remove thedateForTimezonemethod or clarify its purpose.The method returns its input unchanged (no-op) and is not called anywhere in the codebase. Either remove it entirely if it's no longer needed, or if it must remain for external API compatibility, rename it to reflect that it's a passthrough or add documentation explaining the change.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/agenda/src/Job.ts` around lines 59 - 61, The dateForTimezone method on the Job class is a no-op and unused; remove it entirely if it's dead code, or if it must remain for API compatibility keep the method but rename it to indicate it's a passthrough (e.g., passthroughDateForTimezone) or add a docstring above dateForTimezone explaining it intentionally returns the input unchanged for backward compatibility; update any exported types/exports referencing dateForTimezone accordingly and ensure tests/build still pass.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/meteor/ee/server/lib/engagementDashboard/users.ts`:
- Around line 61-62: The previous-period start calculation subtracts the
inclusive daysBetweenDates directly, causing an off-by-one too-early start; in
the block using endOfLastWeek and startOfLastWeek update the computation so
startOfLastWeek is moved by (daysBetweenDates - 1) days from endOfLastWeek
(i.e., subtract one less day) so the previous period covers the exact same
inclusive span as the current period (reference variables: endOfLastWeek,
startOfLastWeek, daysBetweenDates).
---
Outside diff comments:
In `@packages/i18n/src/locales/en.i18n.json`:
- Around line 5929-5930: Re-add the missing "Yesterday_at" translation key used
by useTimeAgo (apps/meteor/client/hooks/useTimeAgo.ts calls t('Yesterday_at'));
add the key to en.i18n.json with a human-readable interpolated value that
matches the hook's expected token (e.g., "Yesterday at {{time}}") and propagate
the same key to other locale files to avoid fallback showing the literal key.
---
Nitpick comments:
In `@packages/agenda/src/Job.ts`:
- Around line 59-61: The dateForTimezone method on the Job class is a no-op and
unused; remove it entirely if it's dead code, or if it must remain for API
compatibility keep the method but rename it to indicate it's a passthrough
(e.g., passthroughDateForTimezone) or add a docstring above dateForTimezone
explaining it intentionally returns the input unchanged for backward
compatibility; update any exported types/exports referencing dateForTimezone
accordingly and ensure tests/build still pass.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e4e348a4-81b7-4106-84c7-2911b6b5adbf
📒 Files selected for processing (47)
apps/meteor/app/cloud/server/functions/buildRegistrationData.tsapps/meteor/app/irc/server/irc-bridge/index.jsapps/meteor/app/lib/server/lib/notifyUsersOnMessage.tsapps/meteor/app/lib/server/lib/processDirectEmail.tsapps/meteor/app/lib/server/lib/sendNotificationsOnMessage.tsapps/meteor/app/lib/server/methods/sendMessage.tsapps/meteor/app/lib/server/methods/updateMessage.tsapps/meteor/app/lib/server/startup/mentionUserNotInChannel.tsapps/meteor/app/livechat/server/hooks/markRoomResponded.tsapps/meteor/app/smarsh-connector/server/functions/generateEml.tsapps/meteor/app/statistics/server/lib/statistics.tsapps/meteor/app/utils/server/lib/getTimezone.tsapps/meteor/ee/app/license/server/startup.tsapps/meteor/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.tsapps/meteor/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.tsapps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.tsapps/meteor/ee/app/livechat-enterprise/server/lib/Helper.tsapps/meteor/ee/server/lib/deviceManagement/session.tsapps/meteor/ee/server/lib/engagementDashboard/channels.tsapps/meteor/ee/server/lib/engagementDashboard/date.tsapps/meteor/ee/server/lib/engagementDashboard/messages.tsapps/meteor/ee/server/lib/engagementDashboard/users.tsapps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.tsapps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoTransferChatsScheduler.tests.tsapps/meteor/ee/tests/unit/apps/livechat-enterprise/server/hooks/afterInquiryQueued.spec.tsapps/meteor/package.jsonapps/meteor/server/lib/dataExport/processDataDownloads.tsapps/meteor/server/lib/dataExport/sendViaEmail.tsapps/meteor/server/lib/getMomentLocale.tsapps/meteor/server/methods/loadLocale.tsapps/meteor/server/services/nps/notification.tsapps/meteor/server/services/omnichannel/service.tsapps/meteor/server/settings/smarsh.tsapps/meteor/tests/end-to-end/api/livechat/23-mac.tsee/apps/omnichannel-transcript/package.jsonee/apps/queue-worker/package.jsonee/packages/omnichannel-services/package.jsonee/packages/pdf-worker/package.jsonpackages/agenda/package.jsonpackages/agenda/src/Job.tspackages/i18n/src/locales/en.i18n.jsonpackages/i18n/src/locales/es.i18n.jsonpackages/i18n/src/locales/hi-IN.i18n.jsonpackages/i18n/src/locales/pt-BR.i18n.jsonpackages/i18n/src/locales/zh.i18n.jsonpackages/message-types/package.jsonpackages/tools/package.json
💤 Files with no reviewable changes (5)
- ee/apps/omnichannel-transcript/package.json
- ee/apps/queue-worker/package.json
- ee/packages/omnichannel-services/package.json
- packages/tools/package.json
- apps/meteor/server/lib/getMomentLocale.ts
✅ Files skipped from review due to trivial changes (22)
- apps/meteor/app/livechat/server/hooks/markRoomResponded.ts
- apps/meteor/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.ts
- apps/meteor/server/services/nps/notification.ts
- apps/meteor/app/lib/server/lib/processDirectEmail.ts
- apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts
- apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts
- apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
- apps/meteor/server/services/omnichannel/service.ts
- apps/meteor/app/lib/server/methods/sendMessage.ts
- packages/message-types/package.json
- apps/meteor/ee/server/lib/engagementDashboard/date.ts
- apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts
- apps/meteor/ee/server/lib/deviceManagement/session.ts
- apps/meteor/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.ts
- apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoTransferChatsScheduler.tests.ts
- packages/agenda/package.json
- packages/i18n/src/locales/zh.i18n.json
- apps/meteor/server/lib/dataExport/processDataDownloads.ts
- apps/meteor/tests/end-to-end/api/livechat/23-mac.ts
- packages/i18n/src/locales/hi-IN.i18n.json
- apps/meteor/ee/app/license/server/startup.ts
- packages/i18n/src/locales/pt-BR.i18n.json
🚧 Files skipped from review as they are similar to previous changes (16)
- apps/meteor/app/lib/server/methods/updateMessage.ts
- apps/meteor/app/lib/server/startup/mentionUserNotInChannel.ts
- apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts
- apps/meteor/app/smarsh-connector/server/functions/generateEml.ts
- ee/packages/pdf-worker/package.json
- apps/meteor/app/irc/server/irc-bridge/index.js
- apps/meteor/server/methods/loadLocale.ts
- apps/meteor/ee/tests/unit/apps/livechat-enterprise/server/hooks/afterInquiryQueued.spec.ts
- apps/meteor/server/lib/dataExport/sendViaEmail.ts
- apps/meteor/ee/server/lib/engagementDashboard/channels.ts
- apps/meteor/app/utils/server/lib/getTimezone.ts
- apps/meteor/server/settings/smarsh.ts
- apps/meteor/ee/server/lib/engagementDashboard/messages.ts
- packages/i18n/src/locales/es.i18n.json
- apps/meteor/app/statistics/server/lib/statistics.ts
- apps/meteor/package.json
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: ✅ Tests Done
- GitHub Check: CodeQL-Build
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)
**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation
Files:
apps/meteor/app/cloud/server/functions/buildRegistrationData.tsapps/meteor/ee/server/lib/engagementDashboard/users.tspackages/agenda/src/Job.ts
🧠 Learnings (11)
📓 Common learnings
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 39397
File: packages/gazzodown/src/elements/Timestamp/DateTimeFormats.spec.tsx:20-23
Timestamp: 2026-03-06T18:09:17.867Z
Learning: In the RocketChat/Rocket.Chat gazzodown package (`packages/gazzodown`), tests are intended to run under the UTC timezone, but as of PR `#39397` this is NOT yet explicitly enforced in `jest.config.ts` or the `package.json` test scripts (which just run `jest` without `TZ=UTC`). To make timezone-sensitive snapshot tests reliable across all environments, `TZ=UTC` should be added to the test scripts in `package.json` or to `jest.config.ts` via `testEnvironmentOptions.timezone`. Without explicit UTC enforcement, snapshot tests involving date-fns formatted output or `toLocaleString()` will fail for contributors in non-UTC timezones.
Learnt from: ahmed-n-abdeltwab
Repo: RocketChat/Rocket.Chat PR: 38974
File: apps/meteor/app/api/server/v1/im.ts:220-221
Timestamp: 2026-02-24T19:09:09.561Z
Learning: In RocketChat/Rocket.Chat OpenAPI migration PRs for apps/meteor/app/api/server/v1 endpoints, maintainers prefer to avoid any logic changes; style-only cleanups (like removing inline comments) may be deferred to follow-ups to keep scope tight.
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 39397
File: packages/gazzodown/src/elements/Timestamp/RelativeTime.spec.tsx:63-70
Timestamp: 2026-03-06T18:02:20.381Z
Learning: In RocketChat/Rocket.Chat, tests in the `packages/gazzodown` package (and likely the broader test suite) are always expected to run in the UTC timezone. This makes `toLocaleString()` output deterministic, so snapshot tests that include locale/timezone-sensitive content (such as `title` attributes from `toLocaleString()`) are stable and do not need to be replaced with targeted assertions.
Learnt from: smirk-dev
Repo: RocketChat/Rocket.Chat PR: 39625
File: apps/meteor/app/api/server/v1/push.ts:85-97
Timestamp: 2026-03-14T14:58:58.834Z
Learning: In RocketChat/Rocket.Chat, the `push.token` POST/DELETE endpoints in `apps/meteor/app/api/server/v1/push.ts` were already migrated to the chained router API pattern on `develop` prior to PR `#39625`. `cleanTokenResult` (which strips `authToken` and returns `PushTokenResult`) and `isPushTokenPOSTProps`/`isPushTokenDELETEProps` validators already exist on `develop`. PR `#39625` only migrates `push.get` and `push.info` to the chained pattern. Do not flag `cleanTokenResult` or `PushTokenResult` as newly introduced behavior-breaking changes when reviewing this PR.
📚 Learning: 2026-01-17T01:51:47.764Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 38219
File: packages/core-typings/src/cloud/Announcement.ts:5-6
Timestamp: 2026-01-17T01:51:47.764Z
Learning: In packages/core-typings/src/cloud/Announcement.ts, the AnnouncementSchema.createdBy field intentionally overrides IBannerSchema.createdBy (object with _id and optional username) with a string enum ['cloud', 'system'] to match existing runtime behavior. This is documented as technical debt with a FIXME comment at apps/meteor/app/cloud/server/functions/syncWorkspace/handleCommsSync.ts:53 and should not be flagged as an error until the runtime behavior is corrected.
Applied to files:
apps/meteor/app/cloud/server/functions/buildRegistrationData.tsapps/meteor/ee/server/lib/engagementDashboard/users.ts
📚 Learning: 2026-03-11T16:46:55.955Z
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 39535
File: apps/meteor/app/apps/server/bridges/livechat.ts:249-249
Timestamp: 2026-03-11T16:46:55.955Z
Learning: In `apps/meteor/app/apps/server/bridges/livechat.ts`, `createVisitor()` intentionally does not propagate `externalIds` to `registerData`. This is by design: the method is deprecated (JSDoc: `deprecated Use createAndReturnVisitor instead. Note: This method does not support externalIds.`) to push callers toward `createAndReturnVisitor()`, which does support `externalIds`. Do not flag the missing `externalIds` field in `createVisitor()` as a bug.
Applied to files:
apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
📚 Learning: 2026-03-06T18:09:17.867Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 39397
File: packages/gazzodown/src/elements/Timestamp/DateTimeFormats.spec.tsx:20-23
Timestamp: 2026-03-06T18:09:17.867Z
Learning: In the RocketChat/Rocket.Chat gazzodown package (`packages/gazzodown`), tests are intended to run under the UTC timezone, but as of PR `#39397` this is NOT yet explicitly enforced in `jest.config.ts` or the `package.json` test scripts (which just run `jest` without `TZ=UTC`). To make timezone-sensitive snapshot tests reliable across all environments, `TZ=UTC` should be added to the test scripts in `package.json` or to `jest.config.ts` via `testEnvironmentOptions.timezone`. Without explicit UTC enforcement, snapshot tests involving date-fns formatted output or `toLocaleString()` will fail for contributors in non-UTC timezones.
Applied to files:
apps/meteor/app/cloud/server/functions/buildRegistrationData.tspackages/agenda/src/Job.ts
📚 Learning: 2026-03-23T19:33:46.159Z
Learnt from: cardoso
Repo: RocketChat/Rocket.Chat PR: 39818
File: apps/meteor/client/hooks/usePruneWarningMessage.ts:45-56
Timestamp: 2026-03-23T19:33:46.159Z
Learning: In RocketChat/Rocket.Chat, cron expressions used by the retention policy (e.g., `RetentionPolicy_Advanced_Precision_Cron`) follow the convention of the `cron` npm package (outdated version), which uses **0-indexed months** (0 = January, 11 = December), not the standard 1-12 used in POSIX cron. The local `sendAt` function in `apps/meteor/client/hooks/usePruneWarningMessage.ts` intentionally replicates this 0-indexed behavior. Do NOT suggest adding `+1` to `date.getMonth()` in this file, as it would break tests and create inconsistency with the backend.
Applied to files:
apps/meteor/app/cloud/server/functions/buildRegistrationData.tsapps/meteor/ee/server/lib/engagementDashboard/users.tspackages/agenda/src/Job.ts
📚 Learning: 2026-03-06T18:02:20.381Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 39397
File: packages/gazzodown/src/elements/Timestamp/RelativeTime.spec.tsx:63-70
Timestamp: 2026-03-06T18:02:20.381Z
Learning: In RocketChat/Rocket.Chat, tests in the `packages/gazzodown` package (and likely the broader test suite) are always expected to run in the UTC timezone. This makes `toLocaleString()` output deterministic, so snapshot tests that include locale/timezone-sensitive content (such as `title` attributes from `toLocaleString()`) are stable and do not need to be replaced with targeted assertions.
Applied to files:
apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
📚 Learning: 2026-03-18T22:07:19.687Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 38357
File: apps/meteor/app/apps/server/converters/departments.ts:59-70
Timestamp: 2026-03-18T22:07:19.687Z
Learning: In `apps/meteor/app/apps/server/converters/departments.ts`, the `convertAppDepartment` method uses non-null assertions (`!`) on `department.name`, `department.email`, and `department.offlineMessageChannelName` when constructing `newDepartment: ILivechatDepartment`. These fields are optional on `IAppsDepartment`, but the App framework flow guarantees their presence at the call site. Do not flag these non-null assertions as unsafe during code review.
Applied to files:
apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
📚 Learning: 2026-02-10T16:32:49.806Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 38528
File: apps/meteor/client/startup/roles.ts:14-14
Timestamp: 2026-02-10T16:32:49.806Z
Learning: In Rocket.Chat's Meteor client code, DDP streams use EJSON which automatically deserializes Date objects. Stream handlers (e.g., sdk.stream()) receive Date fields as Date objects directly and do not require manual conversion using `new Date()`. Only REST API responses require manual date conversion because they return plain JSON where dates are serialized as strings.
Applied to files:
apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
📚 Learning: 2025-11-19T18:20:07.720Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 37419
File: packages/i18n/src/locales/en.i18n.json:918-921
Timestamp: 2025-11-19T18:20:07.720Z
Learning: Repo: RocketChat/Rocket.Chat — i18n/formatting
Learning: This repository uses a custom message formatting parser in UI blocks/messages; do not assume standard Markdown rules. For keys like Call_ended_bold, Call_not_answered_bold, Call_failed_bold, and Call_transferred_bold in packages/i18n/src/locales/en.i18n.json, retain the existing single-asterisk emphasis unless maintainers request otherwise.
Applied to files:
apps/meteor/app/cloud/server/functions/buildRegistrationData.tspackages/i18n/src/locales/en.i18n.json
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In the Rocket.Chat repository, do not reference Biome lint rules in code review feedback. Biome is not used even if biome.json exists; only reference Biome rules if there is explicit, project-wide usage documented. For TypeScript files, review lint implications without Biome guidance unless the project enables Biome rules.
Applied to files:
apps/meteor/app/cloud/server/functions/buildRegistrationData.tsapps/meteor/ee/server/lib/engagementDashboard/users.tspackages/agenda/src/Job.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In this repository (RocketChat/Rocket.Chat), Biome lint rules are not used even if a biome.json exists. When reviewing TypeScript files (e.g., packages/ui-voip/src/providers/useMediaSession.ts), ensure lint suggestions do not reference Biome-specific rules. Rely on general ESLint/TypeScript lint rules and project conventions instead.
Applied to files:
apps/meteor/app/cloud/server/functions/buildRegistrationData.tsapps/meteor/ee/server/lib/engagementDashboard/users.tspackages/agenda/src/Job.ts
🔇 Additional comments (13)
apps/meteor/app/cloud/server/functions/buildRegistrationData.ts (1)
72-72: Local timezone used for billing period - already flagged in prior review.The concern about
format(new Date(), 'yyyy-MM')using local server time instead of UTC for MAC billing has been comprehensively addressed in the existing review comment.I'll note that
markRoomResponded.ts(line 26-31 in relevant snippets) also usesformat(new Date(), 'yyyy-MM')when writing activity periods, so reads and writes are at least internally consistent within this migration. However, for billing/licensing purposes, UTC consistency across different server timezones is typically required, which the prior comment's fix (new Date().toISOString().slice(0, 7)) would address for both locations.packages/i18n/src/locales/en.i18n.json (1)
3433-3433: Date/time description updates look good.These strings are clearer and remove hard dependency wording on Moment docs while keeping token guidance concise.
Also applies to: 3501-3501
packages/agenda/src/Job.ts (4)
17-18: Previously flagged: use of private_getNextDateFromAPI.The concern about using the private
_getNextDateFrommethod and theCronNextDatetype shim has already been raised in a prior review. The publicgetNextDateFrom()method should be preferred for stability.
70-74: Previously flagged: prefer publicgetNextDateFrom()API.This concern was already raised in a prior review. The private
_getNextDateFrommethod usage and type cast should be replaced with the public API.
67-67: LGTM!Direct date assignment is correct since timezone handling is now delegated to
CronTime._getNextDateFrom()via the second parameter.
84-87: LGTM!Using
getTime()instead ofvalueOf()is idiomatic for Date objects and both return identical epoch milliseconds values. The arithmetic logic is preserved correctly.apps/meteor/ee/server/lib/engagementDashboard/users.ts (7)
3-3: Import migration is correct.
isValid,subDays, andsubHoursare appropriately scoped replacements for this file’s server-side date math.
12-14: Good guard for invalidcreatedAt.Line 12 prevents persisting malformed analytics rows and avoids downstream date-int conversion issues.
29-29: Backfill window logic looks good.Using
subDays(date, 90)keeps the period explicit and aligns with fixed-day backfill behavior.
64-64: Yesterday boundary update is correct.Line 64 keeps the intent clear and consistent with the date-fns migration.
92-97: Date destructuring refactor is clean.The native
Dateextraction is straightforward, andmonth + 1preserves 1-based month output.
133-133: 24-hour window calculation is clear and correct.Using
subHours(start, 24)reads directly as intended.
152-152: Weekly lookback migration is correct.Line 152 keeps the existing “last 7 days” behavior explicit with date-fns.
| const endOfLastWeek = subDays(start, 1); | ||
| const startOfLastWeek = subDays(endOfLastWeek, daysBetweenDates); |
There was a problem hiding this comment.
Fix off-by-one in previous-period start calculation.
Line 62 subtracts daysBetweenDates from endOfLastWeek, but daysBetweenDates is inclusive. That shifts the previous period one day too far back (e.g., 7-day range becomes an 8-day boundary window).
Proposed fix
const endOfLastWeek = subDays(start, 1);
-const startOfLastWeek = subDays(endOfLastWeek, daysBetweenDates);
+const startOfLastWeek = subDays(endOfLastWeek, daysBetweenDates - 1);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const endOfLastWeek = subDays(start, 1); | |
| const startOfLastWeek = subDays(endOfLastWeek, daysBetweenDates); | |
| const endOfLastWeek = subDays(start, 1); | |
| const startOfLastWeek = subDays(endOfLastWeek, daysBetweenDates - 1); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/meteor/ee/server/lib/engagementDashboard/users.ts` around lines 61 - 62,
The previous-period start calculation subtracts the inclusive daysBetweenDates
directly, causing an off-by-one too-early start; in the block using
endOfLastWeek and startOfLastWeek update the computation so startOfLastWeek is
moved by (daysBetweenDates - 1) days from endOfLastWeek (i.e., subtract one less
day) so the previous period covers the exact same inclusive span as the current
period (reference variables: endOfLastWeek, startOfLastWeek, daysBetweenDates).
Mechanical replacements across server code: moment() → new Date(), .diff() → date-fns utilities, .format() → Intl.DateTimeFormat/format(), dependency updates. No complex timezone logic changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ated The timezone.ts rewrite is in the complex PR. This branch must keep moment-timezone to avoid build failure. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cron upgrade and moment removal belong to the complex PR since business hours and analytics still depend on them. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
308fd6f to
7149865
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
apps/meteor/app/statistics/server/lib/statistics.ts (1)
249-273:⚠️ Potential issue | 🟠 MajorPreserve UTC semantics for MAC period and rolling windows.
Line 249 and Lines 257/264/271 now use local-time date-fns behavior, which can shift billing period and date-range boundaries by server timezone/DST. This regresses prior
moment.utc()behavior and can miscount records forgetMACStatisticsForPeriod/getMACStatisticsBetweenDates.Proposed fix
-import { format, subDays } from 'date-fns'; @@ - const billablePeriod = format(new Date(), 'yyyy-MM'); + const now = new Date(); + const billablePeriod = now.toISOString().slice(0, 7); @@ - const today = new Date(); - const monthAgo = subDays(today, 30); + const today = now; + const monthAgo = new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000); @@ - const weekAgo = subDays(today, 7); + const weekAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000); @@ - const yesterday = subDays(today, 1); + const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);#!/bin/bash # Verify UTC expectations in tests and affected MAC call sites rg -n "toISOString\\(\\)\\.slice\\(0, 7\\)|getMACStatisticsForPeriod|getMACStatisticsBetweenDates" \ apps/meteor/tests/end-to-end/api/livechat/23-mac.ts \ apps/meteor/app/statistics/server/lib/statistics.ts \ packages/models/src/models/LivechatRooms.ts🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/app/statistics/server/lib/statistics.ts` around lines 249 - 273, The code currently builds billablePeriod and rolling-date boundaries using local-time date-fns helpers which breaks prior UTC semantics; change billablePeriod to derive the UTC year-month (e.g. const billablePeriod = new Date().toISOString().slice(0,7)) and build the rolling-window dates passed to LivechatRooms.getMACStatisticsBetweenDates using UTC-normalized Date objects (create today/monthAgo/weekAgo/yesterday as UTC midnight via new Date(Date.UTC(year, monthIndex, day)) or by truncating ISO strings to UTC timestamps) so LivechatRooms.getMACStatisticsForPeriod and getMACStatisticsBetweenDates receive UTC-based boundaries instead of local-time values.apps/meteor/ee/server/lib/engagementDashboard/users.ts (1)
60-64:⚠️ Potential issue | 🟠 MajorPrevious-period start is still off by one.
Same issue as before:
daysBetweenDatesis inclusive, so subtracting it directly makes the previous window one day too wide and distorts the period comparison.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/ee/server/lib/engagementDashboard/users.ts` around lines 60 - 64, The previous-period range is one day too long because daysBetweenDates is computed inclusive by diffBetweenDaysInclusive; when computing startOfLastWeek (and endOfLastWeek) use an exclusive-length offset: set endOfLastWeek = subDays(start, 1) (keep) but compute startOfLastWeek by subtracting (daysBetweenDates - 1) rather than daysBetweenDates, i.e. use subDays(endOfLastWeek, daysBetweenDates - 1) so the previous window matches the original interval length; ensure any uses of daysBetweenDates for window spans account for the inclusive calculation from diffBetweenDaysInclusive.
🧹 Nitpick comments (1)
apps/meteor/ee/server/lib/engagementDashboard/date.ts (1)
27-30: Make the day-diff helper’s contract match how it is actually used.This helper is declared as
(start, end), but the dashboard callers pass(end, start)to get a positive span. Renaming the parameters to something likelater/earlierwould make the API much harder to misuse.♻️ Proposed refactor
-const diffBetweenDays = (start: string | number | Date, end: string | number | Date): number => - differenceInDays(new Date(start), new Date(end)); -export const diffBetweenDaysInclusive = (start: string | number | Date, end: string | number | Date): number => - diffBetweenDays(start, end) + 1; +const diffBetweenDays = (later: string | number | Date, earlier: string | number | Date): number => + differenceInDays(new Date(later), new Date(earlier)); +export const diffBetweenDaysInclusive = (later: string | number | Date, earlier: string | number | Date): number => + diffBetweenDays(later, earlier) + 1;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/ee/server/lib/engagementDashboard/date.ts` around lines 27 - 30, Rename the parameters on diffBetweenDays and diffBetweenDaysInclusive from (start, end) to (later, earlier) to match how callers pass arguments; update diffBetweenDays to call differenceInDays(new Date(later), new Date(earlier)) so the result is positive for a later date first, and update diffBetweenDaysInclusive to accept (later, earlier) and return diffBetweenDays(later, earlier) + 1; keep function names diffBetweenDays and diffBetweenDaysInclusive the same so callers work without further changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/meteor/ee/server/lib/engagementDashboard/messages.ts`:
- Around line 75-79: The previous-period range is off-by-one because
daysBetweenDates from diffBetweenDaysInclusive is already inclusive; adjust the
back-calculation so the previous window has the same length as the current
window by subtracting (daysBetweenDates - 1) instead of daysBetweenDates when
computing startOfLastWeek (i.e., use subDays(endOfLastWeek, daysBetweenDates -
1)); update any related comments and keep references to daysBetweenDates,
endOfLastWeek, startOfLastWeek, and diffBetweenDaysInclusive so reviewers can
verify the inclusive range now matches the current period exactly.
---
Duplicate comments:
In `@apps/meteor/app/statistics/server/lib/statistics.ts`:
- Around line 249-273: The code currently builds billablePeriod and rolling-date
boundaries using local-time date-fns helpers which breaks prior UTC semantics;
change billablePeriod to derive the UTC year-month (e.g. const billablePeriod =
new Date().toISOString().slice(0,7)) and build the rolling-window dates passed
to LivechatRooms.getMACStatisticsBetweenDates using UTC-normalized Date objects
(create today/monthAgo/weekAgo/yesterday as UTC midnight via new
Date(Date.UTC(year, monthIndex, day)) or by truncating ISO strings to UTC
timestamps) so LivechatRooms.getMACStatisticsForPeriod and
getMACStatisticsBetweenDates receive UTC-based boundaries instead of local-time
values.
In `@apps/meteor/ee/server/lib/engagementDashboard/users.ts`:
- Around line 60-64: The previous-period range is one day too long because
daysBetweenDates is computed inclusive by diffBetweenDaysInclusive; when
computing startOfLastWeek (and endOfLastWeek) use an exclusive-length offset:
set endOfLastWeek = subDays(start, 1) (keep) but compute startOfLastWeek by
subtracting (daysBetweenDates - 1) rather than daysBetweenDates, i.e. use
subDays(endOfLastWeek, daysBetweenDates - 1) so the previous window matches the
original interval length; ensure any uses of daysBetweenDates for window spans
account for the inclusive calculation from diffBetweenDaysInclusive.
---
Nitpick comments:
In `@apps/meteor/ee/server/lib/engagementDashboard/date.ts`:
- Around line 27-30: Rename the parameters on diffBetweenDays and
diffBetweenDaysInclusive from (start, end) to (later, earlier) to match how
callers pass arguments; update diffBetweenDays to call differenceInDays(new
Date(later), new Date(earlier)) so the result is positive for a later date
first, and update diffBetweenDaysInclusive to accept (later, earlier) and return
diffBetweenDays(later, earlier) + 1; keep function names diffBetweenDays and
diffBetweenDaysInclusive the same so callers work without further changes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7e9a21ca-007a-46dd-8aba-5e310114de5c
📒 Files selected for processing (34)
apps/meteor/app/cloud/server/functions/buildRegistrationData.tsapps/meteor/app/irc/server/irc-bridge/index.jsapps/meteor/app/lib/server/lib/notifyUsersOnMessage.tsapps/meteor/app/lib/server/lib/processDirectEmail.tsapps/meteor/app/lib/server/lib/sendNotificationsOnMessage.tsapps/meteor/app/lib/server/methods/sendMessage.tsapps/meteor/app/lib/server/methods/updateMessage.tsapps/meteor/app/lib/server/startup/mentionUserNotInChannel.tsapps/meteor/app/livechat/server/hooks/markRoomResponded.tsapps/meteor/app/smarsh-connector/server/functions/generateEml.tsapps/meteor/app/statistics/server/lib/statistics.tsapps/meteor/app/utils/server/lib/getTimezone.tsapps/meteor/ee/app/license/server/startup.tsapps/meteor/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.tsapps/meteor/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.tsapps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.tsapps/meteor/ee/app/livechat-enterprise/server/lib/Helper.tsapps/meteor/ee/server/lib/deviceManagement/session.tsapps/meteor/ee/server/lib/engagementDashboard/channels.tsapps/meteor/ee/server/lib/engagementDashboard/date.tsapps/meteor/ee/server/lib/engagementDashboard/messages.tsapps/meteor/ee/server/lib/engagementDashboard/users.tsapps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.tsapps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoTransferChatsScheduler.tests.tsapps/meteor/ee/tests/unit/apps/livechat-enterprise/server/hooks/afterInquiryQueued.spec.tsapps/meteor/server/lib/dataExport/processDataDownloads.tsapps/meteor/server/lib/dataExport/sendViaEmail.tsapps/meteor/server/lib/getMomentLocale.tsapps/meteor/server/methods/loadLocale.tsapps/meteor/server/services/nps/notification.tsapps/meteor/server/services/omnichannel/service.tsapps/meteor/server/settings/smarsh.tsapps/meteor/tests/end-to-end/api/livechat/23-mac.tsee/apps/omnichannel-transcript/package.json
💤 Files with no reviewable changes (2)
- ee/apps/omnichannel-transcript/package.json
- apps/meteor/server/lib/getMomentLocale.ts
✅ Files skipped from review due to trivial changes (9)
- apps/meteor/tests/end-to-end/api/livechat/23-mac.ts
- apps/meteor/server/services/nps/notification.ts
- apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoTransferChatsScheduler.tests.ts
- apps/meteor/app/livechat/server/hooks/markRoomResponded.ts
- apps/meteor/ee/tests/unit/apps/livechat-enterprise/server/hooks/afterInquiryQueued.spec.ts
- apps/meteor/ee/server/lib/engagementDashboard/channels.ts
- apps/meteor/app/utils/server/lib/getTimezone.ts
- apps/meteor/app/smarsh-connector/server/functions/generateEml.ts
- apps/meteor/server/lib/dataExport/processDataDownloads.ts
🚧 Files skipped from review as they are similar to previous changes (16)
- apps/meteor/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.ts
- apps/meteor/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.ts
- apps/meteor/ee/app/license/server/startup.ts
- apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
- apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts
- apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts
- apps/meteor/app/irc/server/irc-bridge/index.js
- apps/meteor/app/lib/server/lib/processDirectEmail.ts
- apps/meteor/server/settings/smarsh.ts
- apps/meteor/server/services/omnichannel/service.ts
- apps/meteor/app/lib/server/methods/updateMessage.ts
- apps/meteor/ee/server/lib/deviceManagement/session.ts
- apps/meteor/server/lib/dataExport/sendViaEmail.ts
- apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts
- apps/meteor/app/lib/server/startup/mentionUserNotInChannel.ts
- apps/meteor/server/methods/loadLocale.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: 📦 Build Packages
- GitHub Check: CodeQL-Build
- GitHub Check: CodeQL-Build
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)
**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation
Files:
apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.tsapps/meteor/app/statistics/server/lib/statistics.tsapps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.tsapps/meteor/app/lib/server/methods/sendMessage.tsapps/meteor/ee/server/lib/engagementDashboard/date.tsapps/meteor/ee/server/lib/engagementDashboard/users.tsapps/meteor/ee/server/lib/engagementDashboard/messages.ts
🧠 Learnings (26)
📓 Common learnings
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 39397
File: packages/gazzodown/src/elements/Timestamp/DateTimeFormats.spec.tsx:20-23
Timestamp: 2026-03-06T18:09:17.867Z
Learning: In the RocketChat/Rocket.Chat gazzodown package (`packages/gazzodown`), tests are intended to run under the UTC timezone, but as of PR `#39397` this is NOT yet explicitly enforced in `jest.config.ts` or the `package.json` test scripts (which just run `jest` without `TZ=UTC`). To make timezone-sensitive snapshot tests reliable across all environments, `TZ=UTC` should be added to the test scripts in `package.json` or to `jest.config.ts` via `testEnvironmentOptions.timezone`. Without explicit UTC enforcement, snapshot tests involving date-fns formatted output or `toLocaleString()` will fail for contributors in non-UTC timezones.
Learnt from: smirk-dev
Repo: RocketChat/Rocket.Chat PR: 39625
File: apps/meteor/app/api/server/v1/push.ts:85-97
Timestamp: 2026-03-14T14:58:58.834Z
Learning: In RocketChat/Rocket.Chat, the `push.token` POST/DELETE endpoints in `apps/meteor/app/api/server/v1/push.ts` were already migrated to the chained router API pattern on `develop` prior to PR `#39625`. `cleanTokenResult` (which strips `authToken` and returns `PushTokenResult`) and `isPushTokenPOSTProps`/`isPushTokenDELETEProps` validators already exist on `develop`. PR `#39625` only migrates `push.get` and `push.info` to the chained pattern. Do not flag `cleanTokenResult` or `PushTokenResult` as newly introduced behavior-breaking changes when reviewing this PR.
Learnt from: ahmed-n-abdeltwab
Repo: RocketChat/Rocket.Chat PR: 38974
File: apps/meteor/app/api/server/v1/im.ts:220-221
Timestamp: 2026-02-24T19:09:09.561Z
Learning: In RocketChat/Rocket.Chat OpenAPI migration PRs for apps/meteor/app/api/server/v1 endpoints, maintainers prefer to avoid any logic changes; style-only cleanups (like removing inline comments) may be deferred to follow-ups to keep scope tight.
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 39397
File: packages/gazzodown/src/elements/Timestamp/RelativeTime.spec.tsx:63-70
Timestamp: 2026-03-06T18:02:20.381Z
Learning: In RocketChat/Rocket.Chat, tests in the `packages/gazzodown` package (and likely the broader test suite) are always expected to run in the UTC timezone. This makes `toLocaleString()` output deterministic, so snapshot tests that include locale/timezone-sensitive content (such as `title` attributes from `toLocaleString()`) are stable and do not need to be replaced with targeted assertions.
📚 Learning: 2026-01-17T01:51:47.764Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 38219
File: packages/core-typings/src/cloud/Announcement.ts:5-6
Timestamp: 2026-01-17T01:51:47.764Z
Learning: In packages/core-typings/src/cloud/Announcement.ts, the AnnouncementSchema.createdBy field intentionally overrides IBannerSchema.createdBy (object with _id and optional username) with a string enum ['cloud', 'system'] to match existing runtime behavior. This is documented as technical debt with a FIXME comment at apps/meteor/app/cloud/server/functions/syncWorkspace/handleCommsSync.ts:53 and should not be flagged as an error until the runtime behavior is corrected.
Applied to files:
apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.tsapps/meteor/app/statistics/server/lib/statistics.tsapps/meteor/app/lib/server/methods/sendMessage.tsapps/meteor/ee/server/lib/engagementDashboard/users.ts
📚 Learning: 2026-03-06T18:09:17.867Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 39397
File: packages/gazzodown/src/elements/Timestamp/DateTimeFormats.spec.tsx:20-23
Timestamp: 2026-03-06T18:09:17.867Z
Learning: In the RocketChat/Rocket.Chat gazzodown package (`packages/gazzodown`), tests are intended to run under the UTC timezone, but as of PR `#39397` this is NOT yet explicitly enforced in `jest.config.ts` or the `package.json` test scripts (which just run `jest` without `TZ=UTC`). To make timezone-sensitive snapshot tests reliable across all environments, `TZ=UTC` should be added to the test scripts in `package.json` or to `jest.config.ts` via `testEnvironmentOptions.timezone`. Without explicit UTC enforcement, snapshot tests involving date-fns formatted output or `toLocaleString()` will fail for contributors in non-UTC timezones.
Applied to files:
apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.tsapps/meteor/app/statistics/server/lib/statistics.tsapps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
📚 Learning: 2026-03-16T22:56:54.500Z
Learnt from: KevLehman
Repo: RocketChat/Rocket.Chat PR: 39677
File: packages/models/src/helpers/omnichannel/agentStatus.ts:10-29
Timestamp: 2026-03-16T22:56:54.500Z
Learning: In `packages/models/src/helpers/omnichannel/agentStatus.ts` (PR `#39677`), the `queryStatusAgentOnline` function intentionally omits the `$or` offline-status guard for non-bot agents when `isLivechatEnabledWhenAgentIdle === true`. This is by design: the setting `Livechat_enabled_when_agent_idle` (`accept_chats_when_agent_idle`) means agents should receive chats even when idle/offline, so the offline filter must be removed in that path. Bots are always status-agnostic and are always included regardless of their online/offline status. Do not flag this as a bug.
Applied to files:
apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts
📚 Learning: 2026-03-23T19:33:46.159Z
Learnt from: cardoso
Repo: RocketChat/Rocket.Chat PR: 39818
File: apps/meteor/client/hooks/usePruneWarningMessage.ts:45-56
Timestamp: 2026-03-23T19:33:46.159Z
Learning: In RocketChat/Rocket.Chat, cron expressions used by the retention policy (e.g., `RetentionPolicy_Advanced_Precision_Cron`) follow the convention of the `cron` npm package (outdated version), which uses **0-indexed months** (0 = January, 11 = December), not the standard 1-12 used in POSIX cron. The local `sendAt` function in `apps/meteor/client/hooks/usePruneWarningMessage.ts` intentionally replicates this 0-indexed behavior. Do NOT suggest adding `+1` to `date.getMonth()` in this file, as it would break tests and create inconsistency with the backend.
Applied to files:
apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.tsapps/meteor/app/statistics/server/lib/statistics.tsapps/meteor/ee/server/lib/engagementDashboard/users.tsapps/meteor/ee/server/lib/engagementDashboard/messages.ts
📚 Learning: 2025-10-28T16:53:42.761Z
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 37205
File: ee/packages/federation-matrix/src/FederationMatrix.ts:296-301
Timestamp: 2025-10-28T16:53:42.761Z
Learning: In the Rocket.Chat federation-matrix integration (ee/packages/federation-matrix/), the createRoom method from rocket.chat/federation-sdk will support a 4-argument signature (userId, roomName, visibility, displayName) in newer versions. Code using this 4-argument call is forward-compatible with planned library updates and should not be flagged as an error.
Applied to files:
apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.tsapps/meteor/ee/server/lib/engagementDashboard/messages.ts
📚 Learning: 2026-02-10T16:32:49.806Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 38528
File: apps/meteor/client/startup/roles.ts:14-14
Timestamp: 2026-02-10T16:32:49.806Z
Learning: In Rocket.Chat's Meteor client code, DDP streams use EJSON which automatically deserializes Date objects. Stream handlers (e.g., sdk.stream()) receive Date fields as Date objects directly and do not require manual conversion using `new Date()`. Only REST API responses require manual date conversion because they return plain JSON where dates are serialized as strings.
Applied to files:
apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.tsapps/meteor/app/statistics/server/lib/statistics.tsapps/meteor/app/lib/server/methods/sendMessage.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In the Rocket.Chat repository, do not reference Biome lint rules in code review feedback. Biome is not used even if biome.json exists; only reference Biome rules if there is explicit, project-wide usage documented. For TypeScript files, review lint implications without Biome guidance unless the project enables Biome rules.
Applied to files:
apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.tsapps/meteor/app/statistics/server/lib/statistics.tsapps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.tsapps/meteor/app/lib/server/methods/sendMessage.tsapps/meteor/ee/server/lib/engagementDashboard/date.tsapps/meteor/ee/server/lib/engagementDashboard/users.tsapps/meteor/ee/server/lib/engagementDashboard/messages.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In this repository (RocketChat/Rocket.Chat), Biome lint rules are not used even if a biome.json exists. When reviewing TypeScript files (e.g., packages/ui-voip/src/providers/useMediaSession.ts), ensure lint suggestions do not reference Biome-specific rules. Rely on general ESLint/TypeScript lint rules and project conventions instead.
Applied to files:
apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.tsapps/meteor/app/statistics/server/lib/statistics.tsapps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.tsapps/meteor/app/lib/server/methods/sendMessage.tsapps/meteor/ee/server/lib/engagementDashboard/date.tsapps/meteor/ee/server/lib/engagementDashboard/users.tsapps/meteor/ee/server/lib/engagementDashboard/messages.ts
📚 Learning: 2026-03-11T16:46:55.955Z
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 39535
File: apps/meteor/app/apps/server/bridges/livechat.ts:249-249
Timestamp: 2026-03-11T16:46:55.955Z
Learning: In `apps/meteor/app/apps/server/bridges/livechat.ts`, `createVisitor()` intentionally does not propagate `externalIds` to `registerData`. This is by design: the method is deprecated (JSDoc: `deprecated Use createAndReturnVisitor instead. Note: This method does not support externalIds.`) to push callers toward `createAndReturnVisitor()`, which does support `externalIds`. Do not flag the missing `externalIds` field in `createVisitor()` as a bug.
Applied to files:
apps/meteor/app/statistics/server/lib/statistics.ts
📚 Learning: 2026-03-06T18:02:20.381Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 39397
File: packages/gazzodown/src/elements/Timestamp/RelativeTime.spec.tsx:63-70
Timestamp: 2026-03-06T18:02:20.381Z
Learning: In RocketChat/Rocket.Chat, tests in the `packages/gazzodown` package (and likely the broader test suite) are always expected to run in the UTC timezone. This makes `toLocaleString()` output deterministic, so snapshot tests that include locale/timezone-sensitive content (such as `title` attributes from `toLocaleString()`) are stable and do not need to be replaced with targeted assertions.
Applied to files:
apps/meteor/app/statistics/server/lib/statistics.tsapps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
📚 Learning: 2026-03-16T11:50:18.066Z
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 39657
File: apps/meteor/tests/end-to-end/apps/app-resolve-visitor.ts:18-21
Timestamp: 2026-03-16T11:50:18.066Z
Learning: In `apps/meteor/tests/end-to-end/apps/` (and Livechat E2E tests broadly), calling `createAgent()` and `makeAgentAvailable()` immediately after `updateSetting('Livechat_enabled', true)` — without any intermediate sleep — is an established, non-flaky pattern used across 10+ tests in the codebase. Do not flag this sequence as a potential race condition or suggest adding a delay between them.
Applied to files:
apps/meteor/app/statistics/server/lib/statistics.tsapps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Ensure tests run reliably in parallel without shared state conflicts
Applied to files:
apps/meteor/app/statistics/server/lib/statistics.tsapps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
📚 Learning: 2026-03-18T22:07:19.687Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 38357
File: apps/meteor/app/apps/server/converters/departments.ts:59-70
Timestamp: 2026-03-18T22:07:19.687Z
Learning: In `apps/meteor/app/apps/server/converters/departments.ts`, the `convertAppDepartment` method uses non-null assertions (`!`) on `department.name`, `department.email`, and `department.offlineMessageChannelName` when constructing `newDepartment: ILivechatDepartment`. These fields are optional on `IAppsDepartment`, but the App framework flow guarantees their presence at the call site. Do not flag these non-null assertions as unsafe during code review.
Applied to files:
apps/meteor/app/statistics/server/lib/statistics.ts
📚 Learning: 2026-02-25T20:10:16.987Z
Learnt from: ahmed-n-abdeltwab
Repo: RocketChat/Rocket.Chat PR: 38913
File: packages/ddp-client/src/legacy/types/SDKLegacy.ts:34-34
Timestamp: 2026-02-25T20:10:16.987Z
Learning: In the RocketChat/Rocket.Chat monorepo, packages/ddp-client and apps/meteor do not use TypeScript project references. Module augmentations in apps/meteor (e.g., declare module 'rocket.chat/rest-typings') are not visible when compiling packages/ddp-client in isolation, which is why legacy SDK methods that depend on OperationResult types for OpenAPI-migrated endpoints must remain commented out.
Applied to files:
apps/meteor/app/statistics/server/lib/statistics.tsapps/meteor/app/lib/server/methods/sendMessage.ts
📚 Learning: 2025-11-19T18:20:07.720Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 37419
File: packages/i18n/src/locales/en.i18n.json:918-921
Timestamp: 2025-11-19T18:20:07.720Z
Learning: Repo: RocketChat/Rocket.Chat — i18n/formatting
Learning: This repository uses a custom message formatting parser in UI blocks/messages; do not assume standard Markdown rules. For keys like Call_ended_bold, Call_not_answered_bold, Call_failed_bold, and Call_transferred_bold in packages/i18n/src/locales/en.i18n.json, retain the existing single-asterisk emphasis unless maintainers request otherwise.
Applied to files:
apps/meteor/app/statistics/server/lib/statistics.ts
📚 Learning: 2026-03-02T16:31:41.304Z
Learnt from: KevLehman
Repo: RocketChat/Rocket.Chat PR: 39250
File: apps/meteor/tests/end-to-end/api/livechat/07-queue.ts:1084-1094
Timestamp: 2026-03-02T16:31:41.304Z
Learning: In E2E API tests at apps/meteor/tests/end-to-end/api/livechat/, using sleep(1000) after updateSetting() or updateEESetting() calls in test setup hooks is acceptable and intentional to allow omnichannel settings to propagate their side effects.
Applied to files:
apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Utilize Playwright fixtures (`test`, `page`, `expect`) for consistency in test files
Applied to files:
apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Use `page.waitFor()` with specific conditions instead of hardcoded timeouts in Playwright tests
Applied to files:
apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Implement proper wait strategies for dynamic content in Playwright tests
Applied to files:
apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Use `expect` matchers for assertions (`toEqual`, `toContain`, `toBeTruthy`, `toHaveLength`, etc.) instead of `assert` statements in Playwright tests
Applied to files:
apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
📚 Learning: 2025-12-10T21:00:54.909Z
Learnt from: KevLehman
Repo: RocketChat/Rocket.Chat PR: 37091
File: ee/packages/abac/jest.config.ts:4-7
Timestamp: 2025-12-10T21:00:54.909Z
Learning: Rocket.Chat monorepo: Jest testMatch pattern '<rootDir>/src/**/*.spec.(ts|js|mjs)' is valid in this repo and used across multiple packages (e.g., packages/tools, ee/packages/omnichannel-services). Do not flag it as invalid in future reviews.
Applied to files:
apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
📚 Learning: 2025-12-16T17:29:45.163Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 37834
File: apps/meteor/tests/e2e/page-objects/fragments/admin-flextab-emoji.ts:12-22
Timestamp: 2025-12-16T17:29:45.163Z
Learning: In page object files under `apps/meteor/tests/e2e/page-objects/`, always import `expect` from `../../utils/test` (Playwright's async expect), not from Jest. Jest's `expect` has a synchronous signature and will cause TypeScript errors when used with web-first assertions like `toBeVisible()`.
Applied to files:
apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
📚 Learning: 2026-03-11T22:04:20.529Z
Learnt from: juliajforesti
Repo: RocketChat/Rocket.Chat PR: 39545
File: apps/meteor/client/views/room/body/hooks/useHasNewMessages.ts:59-61
Timestamp: 2026-03-11T22:04:20.529Z
Learning: In `apps/meteor/client/views/room/body/hooks/useHasNewMessages.ts`, the `msg.u._id === uid` early-return in the `streamNewMessage` handler is intentional: the "New messages" indicator is designed to notify about messages from other users only. Self-sent messages — including those sent from a different session/device — are always skipped, by design. Do not flag this as a multi-session regression.
Applied to files:
apps/meteor/app/lib/server/methods/sendMessage.ts
📚 Learning: 2026-02-24T19:16:35.307Z
Learnt from: sampaiodiego
Repo: RocketChat/Rocket.Chat PR: 39003
File: apps/meteor/client/lib/chats/flows/sendMessage.ts:39-45
Timestamp: 2026-02-24T19:16:35.307Z
Learning: In apps/meteor/client/lib/chats/flows/sendMessage.ts, when sdk.call('sendMessage', ...) throws an error, the message is intentionally left with temp: true (not deleted or cleaned up) to support a future retry UI feature. This allows users to retry sending failed messages rather than losing them.
Applied to files:
apps/meteor/app/lib/server/methods/sendMessage.ts
📚 Learning: 2026-03-03T11:11:48.541Z
Learnt from: ahmed-n-abdeltwab
Repo: RocketChat/Rocket.Chat PR: 39230
File: apps/meteor/app/api/server/v1/chat.ts:214-222
Timestamp: 2026-03-03T11:11:48.541Z
Learning: In apps/meteor/server/lib/moderation/reportMessage.ts, the reportMessage function validates that description is not empty or whitespace-only with `if (!description.trim())`. When migrating the chat.reportMessage endpoint to OpenAPI, adding minLength validation to the schema preserves this existing behavior.
Applied to files:
apps/meteor/app/lib/server/methods/sendMessage.ts
🔇 Additional comments (3)
apps/meteor/app/lib/server/methods/sendMessage.ts (1)
52-52: Timestamp diff replacement looks correct.Line 52 preserves the same millisecond delta behavior used by the existing 60s/10s guardrails.
apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts (1)
5-5: Mechanical migration looks correct.
addSeconds(new Date(), timeout)preserves the previous “schedule at now + timeout seconds” behavior while still passing a concreteDateinto Agenda.Also applies to: 53-53
apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts (1)
3-3: Test stays aligned with the production calculation.Using
addSeconds(new Date(), 5)here keeps the expectation consistent withscheduleRoom, and the existing tolerance still makes the assertion robust.Also applies to: 136-142
| const daysBetweenDates = diffBetweenDaysInclusive(end, start); | ||
| const endOfLastWeek = moment(start).clone().subtract(1, 'days').toDate(); | ||
| const startOfLastWeek = moment(endOfLastWeek).clone().subtract(daysBetweenDates, 'days').toDate(); | ||
| const endOfLastWeek = subDays(start, 1); | ||
| const startOfLastWeek = subDays(endOfLastWeek, daysBetweenDates); | ||
| const today = convertDateToInt(end); | ||
| const yesterday = convertDateToInt(moment(end).clone().subtract(1, 'days').toDate()); | ||
| const yesterday = convertDateToInt(subDays(end, 1)); |
There was a problem hiding this comment.
Fix the previous-period range off by one.
daysBetweenDates is already inclusive, so subtracting it directly makes the comparison window one day longer than the current window. A 7-day range turns into an 8-day previous period here, which skews period.variation.
🐛 Proposed fix
const daysBetweenDates = diffBetweenDaysInclusive(end, start);
const endOfLastWeek = subDays(start, 1);
- const startOfLastWeek = subDays(endOfLastWeek, daysBetweenDates);
+ const startOfLastWeek = subDays(endOfLastWeek, daysBetweenDates - 1);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const daysBetweenDates = diffBetweenDaysInclusive(end, start); | |
| const endOfLastWeek = moment(start).clone().subtract(1, 'days').toDate(); | |
| const startOfLastWeek = moment(endOfLastWeek).clone().subtract(daysBetweenDates, 'days').toDate(); | |
| const endOfLastWeek = subDays(start, 1); | |
| const startOfLastWeek = subDays(endOfLastWeek, daysBetweenDates); | |
| const today = convertDateToInt(end); | |
| const yesterday = convertDateToInt(moment(end).clone().subtract(1, 'days').toDate()); | |
| const yesterday = convertDateToInt(subDays(end, 1)); | |
| const daysBetweenDates = diffBetweenDaysInclusive(end, start); | |
| const endOfLastWeek = subDays(start, 1); | |
| const startOfLastWeek = subDays(endOfLastWeek, daysBetweenDates - 1); | |
| const today = convertDateToInt(end); | |
| const yesterday = convertDateToInt(subDays(end, 1)); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/meteor/ee/server/lib/engagementDashboard/messages.ts` around lines 75 -
79, The previous-period range is off-by-one because daysBetweenDates from
diffBetweenDaysInclusive is already inclusive; adjust the back-calculation so
the previous window has the same length as the current window by subtracting
(daysBetweenDates - 1) instead of daysBetweenDates when computing
startOfLastWeek (i.e., use subDays(endOfLastWeek, daysBetweenDates - 1)); update
any related comments and keep references to daysBetweenDates, endOfLastWeek,
startOfLastWeek, and diffBetweenDaysInclusive so reviewers can verify the
inclusive range now matches the current period exactly.
Summary
Mechanical moment→date-fns replacements across server code (47 files):
moment()→new Date().diff()→differenceInMinutes()/getTime()arithmetic.format()→format()/Intl.DateTimeFormataddSeconds,addMinutes,subDaysfor date mathNo complex timezone logic or business hour changes. Those are in #40082.
Split from #39180. Depends on #40076 (client changes).
Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
Refactor
Chores
Documentation