diff --git a/CHANGELOG.md b/CHANGELOG.md
index 49e9f9625..8d262284d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,12 @@
This file tracks product releases for Roomote (single monorepo version). Automated release entries are prepended by `pnpm run version`.
+## 0.4.2 (2026-07-13)
+
+### Patch changes
+
+- Onboarding Slack setup no longer strands revisits with saved credentials on a form with no Continue action: the step button is shown again when the intro screen is skipped.
+
## 0.4.1 (2026-07-13)
### Patch changes
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 a88988660..aa1d2290d 100644
--- a/apps/web/src/app/(onboarding)/setup/StepAuthEnvVars.client.test.tsx
+++ b/apps/web/src/app/(onboarding)/setup/StepAuthEnvVars.client.test.tsx
@@ -447,6 +447,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 (
diff --git a/package.json b/package.json
index 9926816b6..623eb1d43 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "roomote",
- "version": "0.4.1",
+ "version": "0.4.2",
"license": "FCL-1.0-ALv2",
"packageManager": "pnpm@10.29.3",
"engines": {