You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
services/react-query.ts does not set staleTime or refetchOnWindowFocus (react-query.ts). With the TanStack Query v5 defaults, a mounted query with stale data refetches when the tab becomes visible again (visibilitychange). staleTime is 0 by default, and useSuspenseQuery raises it to 1 second. retry is already false (#L12).
On the render page, useInstrumentBundle refetches the bundle each time the tab becomes visible again (useInstrumentBundle.ts#L7-L19). In a clinic, users often switch between the app and other programs during a visit. This has two costs:
Network. Each refetch downloads the full bundle again. A bundle that inlines media can be many megabytes.
Lost data. Queries set throwOnError: true (#L13). TanStack Query v5 sends a failed background refetch to the error boundary, even when the query already has data. If the refetch fails, for example with a 401 after the token expires (Session expires after one hour without warning, and in-progress instrument data is lost #1535), the error page replaces the open instrument and the entered data is lost. This is the stronger reason for the change.
The bundle for a given ID does not change:
A scalar instrument ID is hash(`${name}-${edition}`) (instruments.service.ts#L456-L458). It is not a hash of the content. But create returns 409 for an ID that already exists (#L112, #L151), repository sync treats that 409 as "already stored", and the only updates to an instrument row change sourceRepoId and sourceRepoName (instrument-repos.service.ts#L443-L470).
A series instrument ID is a hash of its content, title and owning group (#L460-L467). Only series can be deleted (#L218-L221). A series that is deleted and created again with the same title and items, but with other details changed, gets the same ID and a new bundle.
The first version of this issue said that all instrument IDs are content hashes. That is correct only for series.
Associated Application Components
Client
Proposed Solution
Set staleTime: Infinity on the bundle query only. useMailSettingsQuery and useSetupStateQuery already do this (useMailSettingsQuery.ts#L14, useSetupStateQuery.ts#L14). Also remove the cached bundles when a series is deleted, so that a series created again with the same ID does not show the old bundle:
Do not change the global defaults (refetchOnWindowFocus: false or a global staleTime). Many mutations do not invalidate the queries they affect, so a refetch when the tab becomes visible is how a user sees changes, including changes that other users make:
useCreateSessionMutation does not invalidate the subjects list or the summary.
The record submit on the render page (a raw axios.post) and useUploadInstrumentRecordsMutation invalidate nothing.
useUpdateGroupMutation invalidates nothing. Its callers update only their own view: group/manage.tsx calls changeGroup, and GroupEmailTemplates sets its own group query. The groups list is not invalidated.
The first version of this issue said that mutations already invalidate the queries they affect. The list above shows that this is not correct.
Separately, a failed background refetch should not replace a page that already has data. One option is throwOnError: (error, query) => query.state.data === undefined. apps/web/AGENTS.md makes throwOnError: true a rule, so this change needs a decision from the maintainers. See #1535.
Tests. A unit test that useInstrumentBundle does not refetch when the window gets focus. A unit test that useDeleteSeriesInstrumentMutation removes the cached bundles. An e2e test that opens an instrument, hides the tab and shows it again, and asserts that no new GET /v1/instruments/bundle/:id request occurs.
Current Implementation and Limitations
services/react-query.tsdoes not setstaleTimeorrefetchOnWindowFocus(react-query.ts). With the TanStack Query v5 defaults, a mounted query with stale data refetches when the tab becomes visible again (visibilitychange).staleTimeis 0 by default, anduseSuspenseQueryraises it to 1 second.retryis alreadyfalse(#L12).On the render page,
useInstrumentBundlerefetches the bundle each time the tab becomes visible again (useInstrumentBundle.ts#L7-L19). In a clinic, users often switch between the app and other programs during a visit. This has two costs:throwOnError: true(#L13). TanStack Query v5 sends a failed background refetch to the error boundary, even when the query already has data. If the refetch fails, for example with a 401 after the token expires (Session expires after one hour without warning, and in-progress instrument data is lost #1535), the error page replaces the open instrument and the entered data is lost. This is the stronger reason for the change.The bundle for a given ID does not change:
hash(`${name}-${edition}`)(instruments.service.ts#L456-L458). It is not a hash of the content. Butcreatereturns 409 for an ID that already exists (#L112, #L151), repository sync treats that 409 as "already stored", and the only updates to an instrument row changesourceRepoIdandsourceRepoName(instrument-repos.service.ts#L443-L470).The first version of this issue said that all instrument IDs are content hashes. That is correct only for series.
Associated Application Components
Client
Proposed Solution
Set
staleTime: Infinityon the bundle query only.useMailSettingsQueryanduseSetupStateQueryalready do this (useMailSettingsQuery.ts#L14, useSetupStateQuery.ts#L14). Also remove the cached bundles when a series is deleted, so that a series created again with the same ID does not show the old bundle:Do not change the global defaults (
refetchOnWindowFocus: falseor a globalstaleTime). Many mutations do not invalidate the queries they affect, so a refetch when the tab becomes visible is how a user sees changes, including changes that other users make:useCreateSessionMutationdoes not invalidate the subjects list or the summary.axios.post) anduseUploadInstrumentRecordsMutationinvalidate nothing.useUpdateGroupMutationinvalidates nothing. Its callers update only their own view:group/manage.tsxcallschangeGroup, andGroupEmailTemplatessets its own group query. The groups list is not invalidated.useDeleteGroupMutationinvalidates the groups, but not the users (useDeleteGroupMutation.ts#L14).The first version of this issue said that mutations already invalidate the queries they affect. The list above shows that this is not correct.
Separately, a failed background refetch should not replace a page that already has data. One option is
throwOnError: (error, query) => query.state.data === undefined.apps/web/AGENTS.mdmakesthrowOnError: truea rule, so this change needs a decision from the maintainers. See #1535.Tests. A unit test that
useInstrumentBundledoes not refetch when the window gets focus. A unit test thatuseDeleteSeriesInstrumentMutationremoves the cached bundles. An e2e test that opens an instrument, hides the tab and shows it again, and asserts that no newGET /v1/instruments/bundle/:idrequest occurs.Estimated Difficulty
Low
Priority
Low