From 23c8327fd4ef1e8d3ab0fd32f7019d705f2b3c4c Mon Sep 17 00:00:00 2001 From: "posthog-eu[bot]" <226701856+posthog-eu[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 08:30:21 +0000 Subject: [PATCH 1/2] fix(frontend): stop reporting Directus network blips at error level The announcement React Query hooks captured each Directus failure inside the queryFn try/catch and rethrew. A query with `retry: 2` therefore reported the same failure up to three times, and a transient connectivity blip (fetch rejects with a TypeError) landed in error tracking at error level as pure noise. - Route capture through the existing global QueryCache / MutationCache error handlers, which fire once after retries are spent. - Add `requestErrorCapture`: a bare network blip becomes a low-severity event and stays out of error tracking; any other failure is captured as an exception. Both carry the request name and an offline flag. - Only requests that opt in with `meta.errorName` are captured, so the change does not widen capture to every query in the app. - Drop the now-redundant per-request try/catch (the mutations already toast in onError, so this also removes a double toast). Separately, emit build source maps and add a gated PostHog upload step to the dashboard deploy workflows, so future dashboard exceptions arrive symbolicated instead of as minified frames. Generated-By: PostHog Desktop Task-Id: e2bdde38-0dae-448d-80a9-63d27bfeff03 --- .../workflows/dev-deploy-vercel-dashboard.yml | 14 + .../prod-deploy-vercel-dashboard.yml | 14 + echo/frontend/src/App.tsx | 9 +- .../components/announcement/hooks/index.ts | 392 ++++++++---------- .../src/lib/requestErrorCapture.test.ts | 116 ++++++ echo/frontend/src/lib/requestErrorCapture.ts | 76 ++++ echo/frontend/vite.config.ts | 5 + 7 files changed, 408 insertions(+), 218 deletions(-) create mode 100644 echo/frontend/src/lib/requestErrorCapture.test.ts create mode 100644 echo/frontend/src/lib/requestErrorCapture.ts diff --git a/.github/workflows/dev-deploy-vercel-dashboard.yml b/.github/workflows/dev-deploy-vercel-dashboard.yml index af0985b39..f223c23c0 100644 --- a/.github/workflows/dev-deploy-vercel-dashboard.yml +++ b/.github/workflows/dev-deploy-vercel-dashboard.yml @@ -2,6 +2,7 @@ name: dev-deploy-vercel-dashboard env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_DASHBOARD }} + POSTHOG_CLI_API_KEY: ${{ secrets.POSTHOG_CLI_API_KEY }} on: push: branches: @@ -59,6 +60,19 @@ jobs: run: vercel build --target=staging --token=${{ secrets.VERCEL_TOKEN }} working-directory: echo/frontend + # Injects a chunk id into each built file and uploads the maps to PostHog, + # so dashboard exceptions arrive symbolicated. Skipped until the + # POSTHOG_CLI_API_KEY secret is set, so it never blocks a deploy. + - name: Inject and upload source maps to PostHog + if: ${{ env.POSTHOG_CLI_API_KEY != '' }} + uses: PostHog/upload-source-maps@v2 + with: + directory: echo/frontend/.vercel/output/static + host: https://eu.posthog.com + project-id: "160282" + api-key: ${{ secrets.POSTHOG_CLI_API_KEY }} + release-version: ${{ github.sha }} + - name: Deploy Project Artifacts to Vercel run: vercel deploy --prebuilt --target=staging --token=${{ secrets.VERCEL_TOKEN }} working-directory: echo/frontend \ No newline at end of file diff --git a/.github/workflows/prod-deploy-vercel-dashboard.yml b/.github/workflows/prod-deploy-vercel-dashboard.yml index c4947f5e4..6cb42b0f9 100644 --- a/.github/workflows/prod-deploy-vercel-dashboard.yml +++ b/.github/workflows/prod-deploy-vercel-dashboard.yml @@ -3,6 +3,7 @@ name: prod-deploy-vercel-dashboard env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_DASHBOARD }} + POSTHOG_CLI_API_KEY: ${{ secrets.POSTHOG_CLI_API_KEY }} on: workflow_dispatch: @@ -61,6 +62,19 @@ jobs: run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} working-directory: echo/frontend + # Injects a chunk id into each built file and uploads the maps to PostHog, + # so dashboard exceptions arrive symbolicated. Skipped until the + # POSTHOG_CLI_API_KEY secret is set, so it never blocks a deploy. + - name: Inject and upload source maps to PostHog + if: ${{ env.POSTHOG_CLI_API_KEY != '' }} + uses: PostHog/upload-source-maps@v2 + with: + directory: echo/frontend/.vercel/output/static + host: https://eu.posthog.com + project-id: "160282" + api-key: ${{ secrets.POSTHOG_CLI_API_KEY }} + release-version: ${{ github.sha }} + - name: Deploy Project Artifacts to Vercel run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} working-directory: echo/frontend \ No newline at end of file diff --git a/echo/frontend/src/App.tsx b/echo/frontend/src/App.tsx index 99f8f52d5..fb496d5b7 100644 --- a/echo/frontend/src/App.tsx +++ b/echo/frontend/src/App.tsx @@ -19,6 +19,7 @@ import { I18nProvider } from "./components/layout/I18nProvider"; import { ENABLE_AGENTATION, USE_PARTICIPANT_ROUTER } from "./config"; import { watchForNewVersion } from "./lib/appVersion"; import { detectAndEmitPilotBlock } from "./lib/pilotBlock"; +import { captureRequestErrorFromMeta } from "./lib/requestErrorCapture"; // Gated at runtime by ENABLE_AGENTATION (config.ts), not at build time, so no // per-deploy env var is needed. The chunk stays lazy: environments where the @@ -50,13 +51,17 @@ import { theme } from "./theme"; // match — see lib/pilotBlock.ts. const queryClient = new QueryClient({ mutationCache: new MutationCache({ - onError: (error) => { + // Fires once, after retries are spent — the right place to record a + // failure so a retrying request is not reported several times. + onError: (error, _variables, _context, mutation) => { detectAndEmitPilotBlock(error); + captureRequestErrorFromMeta(error, mutation.meta); }, }), queryCache: new QueryCache({ - onError: (error) => { + onError: (error, query) => { detectAndEmitPilotBlock(error); + captureRequestErrorFromMeta(error, query.meta); }, }), }); diff --git a/echo/frontend/src/components/announcement/hooks/index.ts b/echo/frontend/src/components/announcement/hooks/index.ts index 32af57ab9..72273d522 100644 --- a/echo/frontend/src/components/announcement/hooks/index.ts +++ b/echo/frontend/src/components/announcement/hooks/index.ts @@ -12,7 +12,6 @@ import { useQuery, useQueryClient, } from "@tanstack/react-query"; -import posthog from "posthog-js"; import { useEffect } from "react"; import useSessionStorageState from "use-session-storage-state"; import { useCurrentUser } from "@/components/auth/hooks"; @@ -26,43 +25,38 @@ export const useLatestAnnouncement = () => { return useQuery({ // Without a user this 403s on every cold load. enabled: !!currentUser?.id, + meta: { errorName: "announcement.latest" }, queryFn: async () => { - try { - const response = await directus.request( - readItems("announcement", { - deep: { - activity: { - _filter: { - user_id: { - _eq: currentUser?.id, - }, + const response = await directus.request( + readItems("announcement", { + deep: { + activity: { + _filter: { + user_id: { + _eq: currentUser?.id, }, }, }, - fields: [ - "id", - "created_at", - "expires_at", - "level", - { - translations: ["id", "languages_code", "title", "message"], - }, - { - activity: ["id", "user_id", "announcement_activity", "read"], - }, - ], - filter: notExpiredFilter(), - limit: 1, - sort: ["-created_at"], - }), - ); + }, + fields: [ + "id", + "created_at", + "expires_at", + "level", + { + translations: ["id", "languages_code", "title", "message"], + }, + { + activity: ["id", "user_id", "announcement_activity", "read"], + }, + ], + filter: notExpiredFilter(), + limit: 1, + sort: ["-created_at"], + }), + ); - return response.length > 0 ? response[0] : null; - } catch (error) { - posthog.captureException(error); - console.error("Error fetching latest announcement:", error); - throw error; - } + return response.length > 0 ? response[0] : null; }, queryKey: ["announcements", "latest"], retry: 2, @@ -94,49 +88,44 @@ export const useInfiniteAnnouncements = ({ nextOffset?: number; }) => lastPage.nextOffset, initialPageParam: 0, + meta: { errorName: "announcement.infinite" }, queryFn: async ({ pageParam = 0 }) => { - try { - const response: Announcement[] = await directus.request( - readItems("announcement", { - deep: { - activity: { - _filter: { - user_id: { - _eq: currentUser?.id, - }, + const response: Announcement[] = await directus.request( + readItems("announcement", { + deep: { + activity: { + _filter: { + user_id: { + _eq: currentUser?.id, }, }, }, - fields: [ - "id", - "created_at", - "expires_at", - "level", - { - translations: ["id", "languages_code", "title", "message"], - }, - { - activity: ["id", "user_id", "announcement_activity", "read"], - }, - ], - filter: notExpiredFilter(), - limit: initialLimit, - offset: pageParam * initialLimit, - sort: ["-created_at"], - ...query, - }), - ); + }, + fields: [ + "id", + "created_at", + "expires_at", + "level", + { + translations: ["id", "languages_code", "title", "message"], + }, + { + activity: ["id", "user_id", "announcement_activity", "read"], + }, + ], + filter: notExpiredFilter(), + limit: initialLimit, + offset: pageParam * initialLimit, + sort: ["-created_at"], + ...query, + }), + ); - return { - announcements: response, - nextOffset: - response.length === initialLimit ? pageParam + 1 : undefined, - }; - } catch (error) { - posthog.captureException(error); - console.error("Error fetching announcements:", error); - throw error; - } + return { + announcements: response, + nextOffset: + response.length === initialLimit ? pageParam + 1 : undefined, + }; }, queryKey: ["announcements", "infinite", currentUser?.id, query], }); @@ -145,6 +134,7 @@ export const useInfiniteAnnouncements = ({ export const useMarkAsReadMutation = () => { const queryClient = useQueryClient(); return useMutation({ + meta: { errorName: "announcement.markAsRead" }, mutationFn: async ({ announcementId, activityIds, @@ -155,29 +145,22 @@ export const useMarkAsReadMutation = () => { activityIds?: string[]; userId?: string; }) => { - try { - // Update in place; a second row would pile up on every toggle. - if (activityIds && activityIds.length > 0) { - return await directus.request( - updateItems("announcement_activity", activityIds, { - read: true, - } as any), - ); - } - + // Update in place; a second row would pile up on every toggle. + if (activityIds && activityIds.length > 0) { return await directus.request( - createItems("announcement_activity", { - announcement_activity: announcementId, + updateItems("announcement_activity", activityIds, { read: true, - ...(userId ? { user_id: userId } : {}), } as any), ); - } catch (error) { - toast.error(t`Failed to mark announcement as read`); - posthog.captureException(error); - console.error("Error in mutationFn:", error); - throw error; } + + return await directus.request( + createItems("announcement_activity", { + announcement_activity: announcementId, + read: true, + ...(userId ? { user_id: userId } : {}), + } as any), + ); }, onError: ( err, @@ -280,25 +263,19 @@ export const useMarkAsUnreadMutation = () => { const queryClient = useQueryClient(); return useMutation({ + meta: { errorName: "announcement.markAsUnread" }, mutationFn: async ({ activityIds, }: { announcementId: string; activityIds: string[]; }) => { - try { - const updates = activityIds.map((id) => - directus.request( - updateItem("announcement_activity", id, { read: false } as any), - ), - ); - return await Promise.all(updates); - } catch (error) { - toast.error(t`Failed to mark announcement as unread`); - posthog.captureException(error); - console.error("Error in markAsUnread mutationFn:", error); - throw error; - } + const updates = activityIds.map((id) => + directus.request( + updateItem("announcement_activity", id, { read: false } as any), + ), + ); + return await Promise.all(updates); }, onError: ( err, @@ -387,70 +364,63 @@ export const useMarkAllAsReadMutation = () => { const { data: currentUser } = useCurrentUser(); return useMutation({ + meta: { errorName: "announcement.markAllAsRead" }, mutationFn: async () => { - try { - // `deep._filter` scopes the rows to me and, unlike a permission, holds - // for admins too, who would otherwise overwrite other people's rows. - const liveAnnouncements = (await directus.request( - readItems("announcement", { - deep: { - activity: { _filter: { user_id: { _eq: currentUser?.id } } }, - }, - fields: ["id", { activity: ["id", "read"] }], - filter: notExpiredFilter(), - limit: -1, - }), - )) as { - id: string; - activity?: { id: string; read?: boolean | null }[]; - }[]; - - const unreadAnnouncements = liveAnnouncements.filter((announcement) => - isUnreadByMe(announcement.activity), - ); + // `deep._filter` scopes the rows to me and, unlike a permission, holds + // for admins too, who would otherwise overwrite other people's rows. + const liveAnnouncements = (await directus.request( + readItems("announcement", { + deep: { + activity: { _filter: { user_id: { _eq: currentUser?.id } } }, + }, + fields: ["id", { activity: ["id", "read"] }], + filter: notExpiredFilter(), + limit: -1, + }), + )) as { + id: string; + activity?: { id: string; read?: boolean | null }[]; + }[]; + + const unreadAnnouncements = liveAnnouncements.filter((announcement) => + isUnreadByMe(announcement.activity), + ); - const activityIdsToUpdate = unreadAnnouncements.flatMap( - (announcement) => - (announcement.activity ?? []).map((activity) => activity.id), - ); - const announcementsToCreate = unreadAnnouncements.filter( - (announcement) => (announcement.activity ?? []).length === 0, - ); + const activityIdsToUpdate = unreadAnnouncements.flatMap((announcement) => + (announcement.activity ?? []).map((activity) => activity.id), + ); + const announcementsToCreate = unreadAnnouncements.filter( + (announcement) => (announcement.activity ?? []).length === 0, + ); - const results = []; + const results = []; - if (activityIdsToUpdate.length > 0) { - results.push( - await directus.request( - updateItems("announcement_activity", activityIdsToUpdate, { + if (activityIdsToUpdate.length > 0) { + results.push( + await directus.request( + updateItems("announcement_activity", activityIdsToUpdate, { + read: true, + } as any), + ), + ); + } + + if (announcementsToCreate.length > 0) { + results.push( + await directus.request( + createItems( + "announcement_activity", + announcementsToCreate.map((announcement) => ({ + announcement_activity: announcement.id, read: true, - } as any), + ...(currentUser?.id ? { user_id: currentUser.id } : {}), + })) as any, ), - ); - } - - if (announcementsToCreate.length > 0) { - results.push( - await directus.request( - createItems( - "announcement_activity", - announcementsToCreate.map((announcement) => ({ - announcement_activity: announcement.id, - read: true, - ...(currentUser?.id ? { user_id: currentUser.id } : {}), - })) as any, - ), - ), - ); - } - - return results; - } catch (error) { - toast.error(t`Failed to mark all announcements as read`); - posthog.captureException(error); - console.error("Error in markAllAsRead mutationFn:", error); - throw error; + ), + ); } + + return results; }, onError: (err, _variables, context) => { // If the mutation fails, use the context returned from onMutate to roll back @@ -547,34 +517,29 @@ const useAnnouncementSummary = (select: (rows: SummaryRow[]) => T) => { return useQuery({ enabled: !!currentUser?.id, + meta: { errorName: "announcement.summary" }, queryFn: async () => { - try { - if (!currentUser?.id) { - return [] as SummaryRow[]; - } - - return (await directus.request( - readItems("announcement", { - deep: { - activity: { _filter: { user_id: { _eq: currentUser.id } } }, - }, - fields: [ - "id", - "created_at", - "level", - { translations: ["id", "languages_code", "title"] }, - { activity: ["read"] }, - ], - filter: notExpiredFilter(), - limit: -1, - sort: ["-created_at"], - }), - )) as SummaryRow[]; - } catch (error) { - posthog.captureException(error); - console.error("Error fetching announcement summary:", error); - throw error; + if (!currentUser?.id) { + return [] as SummaryRow[]; } + + return (await directus.request( + readItems("announcement", { + deep: { + activity: { _filter: { user_id: { _eq: currentUser.id } } }, + }, + fields: [ + "id", + "created_at", + "level", + { translations: ["id", "languages_code", "title"] }, + { activity: ["read"] }, + ], + filter: notExpiredFilter(), + limit: -1, + sort: ["-created_at"], + }), + )) as SummaryRow[]; }, queryKey: ["announcements", "summary", currentUser?.id], refetchInterval: 60_000, @@ -607,42 +572,37 @@ export const useWhatsNewAnnouncements = ({ return useQuery({ enabled, + meta: { errorName: "announcement.whatsNew" }, queryFn: async () => { - try { - const response: Announcement[] = await directus.request( - readItems("announcement", { - deep: { - activity: { - _filter: { - user_id: { - _eq: currentUser?.id, - }, + const response: Announcement[] = await directus.request( + readItems("announcement", { + deep: { + activity: { + _filter: { + user_id: { + _eq: currentUser?.id, }, }, }, - fields: [ - "id", - "created_at", - "expires_at", - "level", - { - translations: ["id", "languages_code", "title", "message"], - }, - { - activity: ["id", "user_id", "announcement_activity", "read"], - }, - ], - limit: 50, - sort: ["-created_at"], - }), - ); + }, + fields: [ + "id", + "created_at", + "expires_at", + "level", + { + translations: ["id", "languages_code", "title", "message"], + }, + { + activity: ["id", "user_id", "announcement_activity", "read"], + }, + ], + limit: 50, + sort: ["-created_at"], + }), + ); - return response; - } catch (error) { - posthog.captureException(error); - console.error("Error fetching what's new announcements:", error); - throw error; - } + return response; }, queryKey: ["announcements", "whats-new"], retry: 2, diff --git a/echo/frontend/src/lib/requestErrorCapture.test.ts b/echo/frontend/src/lib/requestErrorCapture.test.ts new file mode 100644 index 000000000..326da9f79 --- /dev/null +++ b/echo/frontend/src/lib/requestErrorCapture.test.ts @@ -0,0 +1,116 @@ +// @vitest-environment jsdom +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const capture = vi.fn(); +const captureException = vi.fn(); + +vi.mock("posthog-js", () => ({ + default: { + capture: (...args: unknown[]) => capture(...args), + captureException: (...args: unknown[]) => captureException(...args), + }, +})); + +import { + captureRequestError, + captureRequestErrorFromMeta, + isNetworkError, +} from "./requestErrorCapture"; + +const setOnline = (online: boolean) => { + Object.defineProperty(navigator, "onLine", { + configurable: true, + value: online, + }); +}; + +beforeEach(() => { + capture.mockClear(); + captureException.mockClear(); + setOnline(true); +}); + +afterEach(() => { + setOnline(true); +}); + +describe("isNetworkError", () => { + it("treats a fetch TypeError as a network error", () => { + expect(isNetworkError(new TypeError("Failed to fetch"))).toBe(true); + }); + + it("matches known network failure messages on a plain Error", () => { + expect(isNetworkError(new Error("NetworkError when attempting"))).toBe( + true, + ); + expect(isNetworkError(new Error("Load failed"))).toBe(true); + }); + + it("treats an offline browser as a network error", () => { + setOnline(false); + expect(isNetworkError(new Error("anything"))).toBe(true); + }); + + it("does not flag an application error", () => { + expect(isNetworkError(new Error("Request failed with status 500"))).toBe( + false, + ); + }); +}); + +describe("captureRequestError", () => { + it("records a network blip as a low-severity event, not an exception", () => { + captureRequestError( + new TypeError("Failed to fetch"), + "announcement.latest", + ); + + expect(captureException).not.toHaveBeenCalled(); + expect(capture).toHaveBeenCalledWith("request_network_error", { + offline: false, + request: "announcement.latest", + }); + }); + + it("captures an application error as an exception tagged with the request", () => { + const error = new Error("Request failed with status 500"); + captureRequestError(error, "announcement.summary"); + + expect(capture).not.toHaveBeenCalled(); + expect(captureException).toHaveBeenCalledWith(error, { + offline: false, + request: "announcement.summary", + }); + }); + + it("marks the offline flag when the browser is offline", () => { + setOnline(false); + captureRequestError( + new TypeError("Failed to fetch"), + "announcement.latest", + ); + + expect(capture).toHaveBeenCalledWith("request_network_error", { + offline: true, + request: "announcement.latest", + }); + }); +}); + +describe("captureRequestErrorFromMeta", () => { + it("captures only when meta carries a string errorName", () => { + captureRequestErrorFromMeta(new Error("boom"), { + errorName: "announcement.summary", + }); + + expect(captureException).toHaveBeenCalledTimes(1); + }); + + it("ignores errors from requests that did not opt in", () => { + captureRequestErrorFromMeta(new Error("boom"), undefined); + captureRequestErrorFromMeta(new Error("boom"), { other: "value" }); + + expect(capture).not.toHaveBeenCalled(); + expect(captureException).not.toHaveBeenCalled(); + }); +}); diff --git a/echo/frontend/src/lib/requestErrorCapture.ts b/echo/frontend/src/lib/requestErrorCapture.ts new file mode 100644 index 000000000..4872cd6f1 --- /dev/null +++ b/echo/frontend/src/lib/requestErrorCapture.ts @@ -0,0 +1,76 @@ +import posthog from "posthog-js"; + +/** + * Central capture point for failed React Query requests. + * + * A queryFn that captures inside its own try/catch fires once per attempt, so + * a query with `retry: 2` reports the same failure up to three times. Worse, + * a transient connectivity blip (fetch rejects with a `TypeError`) then lands + * in error tracking at error level, where it is pure noise. Route capture + * through the React Query cache error handlers instead: those fire once, after + * the retries are spent, so each failure is recorded a single time. + */ + +// fetch() rejects with a TypeError when the request never reaches an HTTP +// response — offline, DNS failure, dropped connection, blocked by CORS. Some +// environments word it differently, so match the common messages too. +const NETWORK_ERROR_HINTS = [ + "failed to fetch", + "networkerror", + "network request failed", + "load failed", +]; + +export const isNetworkError = (error: unknown): boolean => { + if (typeof navigator !== "undefined" && navigator.onLine === false) { + return true; + } + if (error instanceof TypeError) { + return true; + } + const message = ( + error instanceof Error ? error.message : String(error) + ).toLowerCase(); + return NETWORK_ERROR_HINTS.some((hint) => message.includes(hint)); +}; + +/** + * Record a failed request once its retries are exhausted. A bare connectivity + * blip is not an application error, so it lands as a low-severity event and + * stays out of error tracking; anything else is captured as an exception. Both + * carry the request name and whether the browser was offline. + */ +export const captureRequestError = ( + error: unknown, + requestName: string, +): void => { + const offline = typeof navigator !== "undefined" && !navigator.onLine; + + if (isNetworkError(error)) { + posthog.capture("request_network_error", { + offline, + request: requestName, + }); + return; + } + + posthog.captureException(error, { + offline, + request: requestName, + }); +}; + +/** + * React Query cache error handler helper. Only requests that opt in with a + * `meta.errorName` are captured, so this never widens capture to every query + * in the app. + */ +export const captureRequestErrorFromMeta = ( + error: unknown, + meta: Record | undefined, +): void => { + const requestName = meta?.errorName; + if (typeof requestName === "string") { + captureRequestError(error, requestName); + } +}; diff --git a/echo/frontend/vite.config.ts b/echo/frontend/vite.config.ts index be8c14d9f..2b244f94e 100644 --- a/echo/frontend/vite.config.ts +++ b/echo/frontend/vite.config.ts @@ -158,6 +158,11 @@ export default defineConfig(({ mode }) => { }, }, }, + // Emit source maps so PostHog error tracking can symbolicate the + // production bundle. Without them every exception surfaces as a + // minified frame (`async queryFn` in assets/index-.js) that no + // one can place. The deploy pipeline uploads these maps to PostHog. + sourcemap: true, }, define: { __APP_BUILD_ID__: JSON.stringify(buildId), From d7ed41edab3af85a21485cc4aa29b58910e1f141 Mon Sep 17 00:00:00 2001 From: "posthog-eu[bot]" <226701856+posthog-eu[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 08:46:39 +0000 Subject: [PATCH 2/2] chore(i18n): re-extract lingui catalogs after hook refactor Removing the redundant per-mutation try/catch moved the `t` macro call sites, so the catalog source-location references drifted. Re-run extract and compile; only the `#:` references change, no msgids or translations. Generated-By: PostHog Desktop Task-Id: e2bdde38-0dae-448d-80a9-63d27bfeff03 --- echo/frontend/src/locales/cs-CZ.po | 9 +++------ echo/frontend/src/locales/de-DE.po | 9 +++------ echo/frontend/src/locales/en-US.po | 9 +++------ echo/frontend/src/locales/es-ES.po | 9 +++------ echo/frontend/src/locales/fr-FR.po | 9 +++------ echo/frontend/src/locales/it-IT.po | 9 +++------ echo/frontend/src/locales/nl-NL.po | 9 +++------ echo/frontend/src/locales/uk-UA.po | 9 +++------ 8 files changed, 24 insertions(+), 48 deletions(-) diff --git a/echo/frontend/src/locales/cs-CZ.po b/echo/frontend/src/locales/cs-CZ.po index 51831643a..5b7b6258f 100644 --- a/echo/frontend/src/locales/cs-CZ.po +++ b/echo/frontend/src/locales/cs-CZ.po @@ -4165,18 +4165,15 @@ msgstr "Failed to generate the summary. Please try again later." msgid "Failed to load webhooks" msgstr "Failed to load webhooks" -#: src/components/announcement/hooks/index.ts:449 -#: src/components/announcement/hooks/index.ts:463 +#: src/components/announcement/hooks/index.ts:433 msgid "Failed to mark all announcements as read" msgstr "Failed to mark all announcements as read" -#: src/components/announcement/hooks/index.ts:176 -#: src/components/announcement/hooks/index.ts:196 +#: src/components/announcement/hooks/index.ts:179 msgid "Failed to mark announcement as read" msgstr "Failed to mark announcement as read" -#: src/components/announcement/hooks/index.ts:297 -#: src/components/announcement/hooks/index.ts:316 +#: src/components/announcement/hooks/index.ts:293 msgid "Failed to mark announcement as unread" msgstr "Failed to mark announcement as unread" diff --git a/echo/frontend/src/locales/de-DE.po b/echo/frontend/src/locales/de-DE.po index ad7c9e647..1c43b49e3 100644 --- a/echo/frontend/src/locales/de-DE.po +++ b/echo/frontend/src/locales/de-DE.po @@ -4166,18 +4166,15 @@ msgstr "Zusammenfassung konnte nicht erstellt werden. Versuch es später noch ma msgid "Failed to load webhooks" msgstr "Webhooks konnten nicht geladen werden" -#: src/components/announcement/hooks/index.ts:449 -#: src/components/announcement/hooks/index.ts:463 +#: src/components/announcement/hooks/index.ts:433 msgid "Failed to mark all announcements as read" msgstr "Fehler beim Markieren aller Ankündigungen als gelesen" -#: src/components/announcement/hooks/index.ts:176 -#: src/components/announcement/hooks/index.ts:196 +#: src/components/announcement/hooks/index.ts:179 msgid "Failed to mark announcement as read" msgstr "Fehler beim Markieren der Ankündigung als gelesen" -#: src/components/announcement/hooks/index.ts:297 -#: src/components/announcement/hooks/index.ts:316 +#: src/components/announcement/hooks/index.ts:293 msgid "Failed to mark announcement as unread" msgstr "" diff --git a/echo/frontend/src/locales/en-US.po b/echo/frontend/src/locales/en-US.po index 6f4795109..290fdc57f 100644 --- a/echo/frontend/src/locales/en-US.po +++ b/echo/frontend/src/locales/en-US.po @@ -4165,18 +4165,15 @@ msgstr "Failed to generate the summary. Please try again later." msgid "Failed to load webhooks" msgstr "Failed to load webhooks" -#: src/components/announcement/hooks/index.ts:449 -#: src/components/announcement/hooks/index.ts:463 +#: src/components/announcement/hooks/index.ts:433 msgid "Failed to mark all announcements as read" msgstr "Failed to mark all announcements as read" -#: src/components/announcement/hooks/index.ts:176 -#: src/components/announcement/hooks/index.ts:196 +#: src/components/announcement/hooks/index.ts:179 msgid "Failed to mark announcement as read" msgstr "Failed to mark announcement as read" -#: src/components/announcement/hooks/index.ts:297 -#: src/components/announcement/hooks/index.ts:316 +#: src/components/announcement/hooks/index.ts:293 msgid "Failed to mark announcement as unread" msgstr "Failed to mark announcement as unread" diff --git a/echo/frontend/src/locales/es-ES.po b/echo/frontend/src/locales/es-ES.po index d64ff4d14..130b205b6 100644 --- a/echo/frontend/src/locales/es-ES.po +++ b/echo/frontend/src/locales/es-ES.po @@ -4166,18 +4166,15 @@ msgstr "Error al generar el resumen. Inténtalo de nuevo más tarde." msgid "Failed to load webhooks" msgstr "Error al cargar los webhooks" -#: src/components/announcement/hooks/index.ts:449 -#: src/components/announcement/hooks/index.ts:463 +#: src/components/announcement/hooks/index.ts:433 msgid "Failed to mark all announcements as read" msgstr "Error al marcar todos los anuncios como leídos" -#: src/components/announcement/hooks/index.ts:176 -#: src/components/announcement/hooks/index.ts:196 +#: src/components/announcement/hooks/index.ts:179 msgid "Failed to mark announcement as read" msgstr "Error al marcar el anuncio como leído" -#: src/components/announcement/hooks/index.ts:297 -#: src/components/announcement/hooks/index.ts:316 +#: src/components/announcement/hooks/index.ts:293 msgid "Failed to mark announcement as unread" msgstr "" diff --git a/echo/frontend/src/locales/fr-FR.po b/echo/frontend/src/locales/fr-FR.po index 575f93cb5..edc8ebac1 100644 --- a/echo/frontend/src/locales/fr-FR.po +++ b/echo/frontend/src/locales/fr-FR.po @@ -4166,18 +4166,15 @@ msgstr "Impossible de générer le résumé. Réessaie plus tard." msgid "Failed to load webhooks" msgstr "Échec du chargement des webhooks" -#: src/components/announcement/hooks/index.ts:449 -#: src/components/announcement/hooks/index.ts:463 +#: src/components/announcement/hooks/index.ts:433 msgid "Failed to mark all announcements as read" msgstr "Échec du marquage de toutes les annonces comme lues" -#: src/components/announcement/hooks/index.ts:176 -#: src/components/announcement/hooks/index.ts:196 +#: src/components/announcement/hooks/index.ts:179 msgid "Failed to mark announcement as read" msgstr "Échec du marquage de l'annonce comme lue" -#: src/components/announcement/hooks/index.ts:297 -#: src/components/announcement/hooks/index.ts:316 +#: src/components/announcement/hooks/index.ts:293 msgid "Failed to mark announcement as unread" msgstr "" diff --git a/echo/frontend/src/locales/it-IT.po b/echo/frontend/src/locales/it-IT.po index 48c512bfc..a3b689981 100644 --- a/echo/frontend/src/locales/it-IT.po +++ b/echo/frontend/src/locales/it-IT.po @@ -4165,18 +4165,15 @@ msgstr "Failed to generate the summary. Please try again later." msgid "Failed to load webhooks" msgstr "Failed to load webhooks" -#: src/components/announcement/hooks/index.ts:449 -#: src/components/announcement/hooks/index.ts:463 +#: src/components/announcement/hooks/index.ts:433 msgid "Failed to mark all announcements as read" msgstr "Failed to mark all announcements as read" -#: src/components/announcement/hooks/index.ts:176 -#: src/components/announcement/hooks/index.ts:196 +#: src/components/announcement/hooks/index.ts:179 msgid "Failed to mark announcement as read" msgstr "Failed to mark announcement as read" -#: src/components/announcement/hooks/index.ts:297 -#: src/components/announcement/hooks/index.ts:316 +#: src/components/announcement/hooks/index.ts:293 msgid "Failed to mark announcement as unread" msgstr "" diff --git a/echo/frontend/src/locales/nl-NL.po b/echo/frontend/src/locales/nl-NL.po index 68955ee2a..d92876323 100644 --- a/echo/frontend/src/locales/nl-NL.po +++ b/echo/frontend/src/locales/nl-NL.po @@ -4163,18 +4163,15 @@ msgstr "Samenvatting maken lukt niet. Probeer het later nog een keer." msgid "Failed to load webhooks" msgstr "Fout bij het laden van webhooks" -#: src/components/announcement/hooks/index.ts:449 -#: src/components/announcement/hooks/index.ts:463 +#: src/components/announcement/hooks/index.ts:433 msgid "Failed to mark all announcements as read" msgstr "Fout bij het markeren van alle meldingen als gelezen" -#: src/components/announcement/hooks/index.ts:176 -#: src/components/announcement/hooks/index.ts:196 +#: src/components/announcement/hooks/index.ts:179 msgid "Failed to mark announcement as read" msgstr "Fout bij het markeren van de melding als gelezen" -#: src/components/announcement/hooks/index.ts:297 -#: src/components/announcement/hooks/index.ts:316 +#: src/components/announcement/hooks/index.ts:293 msgid "Failed to mark announcement as unread" msgstr "Aankondiging als ongelezen markeren mislukt" diff --git a/echo/frontend/src/locales/uk-UA.po b/echo/frontend/src/locales/uk-UA.po index 162058bd8..0ff7ad611 100644 --- a/echo/frontend/src/locales/uk-UA.po +++ b/echo/frontend/src/locales/uk-UA.po @@ -4165,18 +4165,15 @@ msgstr "Failed to generate the summary. Please try again later." msgid "Failed to load webhooks" msgstr "Failed to load webhooks" -#: src/components/announcement/hooks/index.ts:449 -#: src/components/announcement/hooks/index.ts:463 +#: src/components/announcement/hooks/index.ts:433 msgid "Failed to mark all announcements as read" msgstr "Failed to mark all announcements as read" -#: src/components/announcement/hooks/index.ts:176 -#: src/components/announcement/hooks/index.ts:196 +#: src/components/announcement/hooks/index.ts:179 msgid "Failed to mark announcement as read" msgstr "Failed to mark announcement as read" -#: src/components/announcement/hooks/index.ts:297 -#: src/components/announcement/hooks/index.ts:316 +#: src/components/announcement/hooks/index.ts:293 msgid "Failed to mark announcement as unread" msgstr ""