feat(js-loader): Make latest unselectable
diff --git a/static/app/views/settings/project/projectKeys/details/loaderSettings.spec.tsx b/static/app/views/settings/project/projectKeys/details/loaderSettings.spec.tsx
index ec20a756636ac..5ca1cbfa51476 100644
--- a/static/app/views/settings/project/projectKeys/details/loaderSettings.spec.tsx
+++ b/static/app/views/settings/project/projectKeys/details/loaderSettings.spec.tsx
@@ -145,14 +145,14 @@ describe('Loader Script Settings', function () {
     });
 
     // Update SDK version
-    await selectEvent.select(screen.getByText('latest'), '7.x');
+    await selectEvent.select(screen.getByText('7.x'), '6.x');
 
     await waitFor(() => {
       expect(mockRequests.projectKeys).toHaveBeenCalledWith(
         `/projects/${organization.slug}/${params.projectSlug}/keys/${params.keyId}/`,
         expect.objectContaining({
           data: expect.objectContaining({
-            browserSdkVersion: '7.x',
+            browserSdkVersion: '6.x',
           }),
         })
       );
@@ -188,7 +188,7 @@ describe('Loader Script Settings', function () {
     );
 
     // Update SDK version - should reset performance & replay
-    await selectEvent.select(screen.getByText('latest'), '6.x');
+    await selectEvent.select(screen.getByText('7.x'), '6.x');
 
     await waitFor(() => {
       expect(mockRequests.projectKeys).toHaveBeenCalledWith(
diff --git a/static/app/views/settings/project/projectKeys/details/loaderSettings.tsx b/static/app/views/settings/project/projectKeys/details/loaderSettings.tsx
index b4bc335c4aa3a..9cd1b73ccd5ed 100644
--- a/static/app/views/settings/project/projectKeys/details/loaderSettings.tsx
+++ b/static/app/views/settings/project/projectKeys/details/loaderSettings.tsx
@@ -40,12 +40,21 @@ export function LoaderSettings({keyId, orgSlug, project, data, updateData}: Prop
   const values = requestPending
     ? optimisticState
     : {
-        browserSdkVersion: data.browserSdkVersion,
+        browserSdkVersion:
+          // "latest" was an option that we don't let users select anymore. It will be phased out when version v8 of
+          // the SDK is released, meaning we want to map the backend's response to v7 when it responds with "latest".
+          // "7.x" was the "latest" version when "latest" was phased out.
+          data.browserSdkVersion === 'latest' ? '7.x' : data.browserSdkVersion,
         hasDebug: data.dynamicSdkLoaderOptions.hasDebug,
         hasPerformance: data.dynamicSdkLoaderOptions.hasPerformance,
         hasReplay: data.dynamicSdkLoaderOptions.hasReplay,
       };
 
+  const sdkVersionChoices = data.browserSdk
+    ? // "latest" was an option that we do not want to allow users to select anymore. It was phased out with v7, before v8 was released.
+      data.browserSdk.choices.filter(([value]) => value !== 'latest')
+    : [];
+
   const apiEndpoint = `/projects/${orgSlug}/${project.slug}/keys/${keyId}/`;
   const loaderLink = getDynamicText({
     value: data.dsn.cdn,
@@ -148,21 +157,24 @@ export function LoaderSettings({keyId, orgSlug, project, data, updateData}: Prop
           <SelectField
             name={`${keyId}-browserSdkVersion`}
             label={t('SDK Version')}
-            options={
-              data.browserSdk
-                ? data.browserSdk.choices.map(([value, label]) => ({
-                    value,
-                    label,
-                  }))
-                : []
-            }
+            options={sdkVersionChoices.map(([value, label]) => ({
+              value,
+              label,
+            }))}
             value={values.browserSdkVersion}
             onChange={value => {
               updateLoaderOption({browserSdkVersion: value});
             }}
+            disabledReason={
+              sdkVersionChoices.length === 1
+                ? t(
+                    'At the moment, only the shown SDK version is available. New versions of the SDK will appear here as soon as they are released, and you will be able to upgrade by selecting them.'
+                  )
+                : undefined
+            }
             placeholder="7.x"
             allowClear={false}
-            disabled={!hasAccess || requestPending}
+            disabled={!hasAccess || requestPending || sdkVersionChoices.length === 1}
           />
 
           <BooleanField

We want to remove the option to select "latest" in the JS loader because it makes the maintenance of the JS SDK hell in terms of backwards compatibility.

For this we need to start mapping "latest", when selected, to the actual latest value, which at the current time is "7.x".

We are starting in the frontend:
- When the backend says "latest" is a valid option to select, we remove that option from the list of selectable items.
- Because of the above, we also need to map the "latest" value, if the backend says it is selected, to "7.x" so that the select knows what selection to show because "latest" is not an option anymore.
- Previously, there was never the case where only one item was selectable. After this change, there are situations where only "7.x" may be selected. In this case we disable the select (because there are no more valid options) and show a hint explaining that only one option can be selected and why.

In a follow-up, we will update the backend to limit what options are returned. (follow up: https://github.com/getsentry/sentry/pull/65691 - both could in theory be deployed independently but having this PR out first is a tiny bit better because of the disabled state of the select)