diff --git a/apps/web/src/app/(onboarding)/setup/StepAuthEnvVars.client.test.tsx b/apps/web/src/app/(onboarding)/setup/StepAuthEnvVars.client.test.tsx
index db0901af4..3be16ff2b 100644
--- a/apps/web/src/app/(onboarding)/setup/StepAuthEnvVars.client.test.tsx
+++ b/apps/web/src/app/(onboarding)/setup/StepAuthEnvVars.client.test.tsx
@@ -457,6 +457,51 @@ describe('StepAuthEnvVars', () => {
).toHaveAttribute('href', '/api/setup/roomote-logo');
});
+ it('offers a continue action when a saved Slack config is revisited', () => {
+ const authSetup = buildAuthSetup('slack');
+ const savedSlackAuthSetup: SetupAuthStatus = {
+ ...authSetup,
+ providers: authSetup.providers.map((provider) =>
+ provider.id === 'slack'
+ ? {
+ ...provider,
+ savedSatisfied: true,
+ setupSatisfied: true,
+ fields: provider.fields.map((field) => ({
+ ...field,
+ savedSatisfied: true,
+ savedValue:
+ field.envVarName === 'R_SLACK_CLIENT_ID'
+ ? '11040692082085.11578538885334'
+ : field.savedValue,
+ })),
+ }
+ : provider,
+ ),
+ };
+
+ render(
+ ,
+ );
+
+ // Saved Slack skips the "Create Slack app" intro and shows the value form.
+ expect(screen.getByText('Enter the values below:')).toBeInTheDocument();
+ expect(
+ screen.queryByRole('link', { name: /create slack app/i }),
+ ).not.toBeInTheDocument();
+
+ // The intro is what "owns" the Slack action buttons, so once it is hidden
+ // the step itself must still render an action button — otherwise the user
+ // is stranded on a page with no way to continue.
+ expect(
+ screen.getByRole('button', { name: /continue/i }),
+ ).toBeInTheDocument();
+ });
+
it('keeps Microsoft setup focused on the single app values', () => {
render(
!field.runtimeSatisfied) ?? false;
+ // Slack "owns" the step actions only while its intro screen is shown, which
+ // is the same condition SlackSetupExperience uses to render that intro. If a
+ // saved (or runtime) config is being edited instead, the intro is hidden and
+ // this step must render its own action button — otherwise there is no way to
+ // continue. Keep this in sync with SlackSetupExperience's intro guard.
const providerOwnsActions =
selectedProvider?.id === 'slack' &&
!showManualSlackValues &&
- !selectedProvider.runtimeSatisfied;
+ !selectedProvider.runtimeSatisfied &&
+ !selectedProvider.savedSatisfied;
if (bootstrapMode && selectedProviderRuntimeConfigured) {
return (