setup: add ← Back option to Discord, WhatsApp, iMessage channel flows

Picking the wrong messaging channel during setup left users with no way
to bail out — they had to either complete the chosen flow or kill setup
and start over. This adds a Back option to the first prompt of three
channel sub-flows that share the same simple shape (one leading
brightSelect that's easy to extend).

Mechanics:
- New `setup/lib/back-nav.ts` exports a BACK_TO_CHANNEL_SELECTION
  sentinel and ChannelFlowResult type.
- `setup/auto.ts` wraps the channel dispatch in a while-loop; channels
  return BACK_TO_CHANNEL_SELECTION to bounce back to the chooser
  without restarting setup. Channels not yet wired return void and the
  loop exits after one pass, so the change is backwards compatible.
- Discord, WhatsApp, iMessage each add a `← Back to channel selection`
  option to their first prompt.

Telegram, Slack, Teams, and Signal will follow as separate PRs — they
each need a slightly different shape (extra prompt insertions, gating
inside multi-step flows, etc.) and are easier to review independently.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
exe.dev user
2026-05-05 09:20:17 +00:00
parent 948a0dcada
commit c795ecff6e
5 changed files with 104 additions and 60 deletions

View File

@@ -29,6 +29,7 @@ import path from 'path';
import * as p from '@clack/prompts';
import k from 'kleur';
import { BACK_TO_CHANNEL_SELECTION } from './lib/back-nav.js';
import { runDiscordChannel } from './channels/discord.js';
import { runIMessageChannel } from './channels/imessage.js';
import { runSignalChannel } from './channels/signal.js';
@@ -440,35 +441,45 @@ async function main(): Promise<void> {
let channelChoice: ChannelChoice = 'skip';
if (!skip.has('channel')) {
channelChoice = await askChannelChoice();
if (channelChoice !== 'skip' && channelChoice !== 'other') {
await resolveDisplayName();
}
if (channelChoice === 'telegram') {
await runTelegramChannel(displayName!);
} else if (channelChoice === 'discord') {
await runDiscordChannel(displayName!);
} else if (channelChoice === 'whatsapp') {
await runWhatsAppChannel(displayName!);
} else if (channelChoice === 'signal') {
await runSignalChannel(displayName!);
} else if (channelChoice === 'teams') {
await runTeamsChannel(displayName!);
} else if (channelChoice === 'slack') {
await runSlackChannel(displayName!);
} else if (channelChoice === 'imessage') {
await runIMessageChannel(displayName!);
} else if (channelChoice === 'other') {
await askOtherChannelName();
} else {
p.log.info(
brandBody(
wrapForGutter(
'No messaging app for now. You can add one later (like Telegram, Discord, WhatsApp, Teams, Slack, or iMessage).',
4,
// Loop so a channel sub-flow can return BACK_TO_CHANNEL_SELECTION on
// its first prompt and bounce the user back to the chooser without
// restarting setup. Channels not yet wired with the back option just
// return void and the loop exits after one pass.
let backed = true;
while (backed) {
backed = false;
channelChoice = await askChannelChoice();
if (channelChoice !== 'skip' && channelChoice !== 'other') {
await resolveDisplayName();
}
let result: void | typeof BACK_TO_CHANNEL_SELECTION;
if (channelChoice === 'telegram') {
await runTelegramChannel(displayName!);
} else if (channelChoice === 'discord') {
result = await runDiscordChannel(displayName!);
} else if (channelChoice === 'whatsapp') {
result = await runWhatsAppChannel(displayName!);
} else if (channelChoice === 'signal') {
await runSignalChannel(displayName!);
} else if (channelChoice === 'teams') {
await runTeamsChannel(displayName!);
} else if (channelChoice === 'slack') {
await runSlackChannel(displayName!);
} else if (channelChoice === 'imessage') {
result = await runIMessageChannel(displayName!);
} else if (channelChoice === 'other') {
await askOtherChannelName();
} else {
p.log.info(
brandBody(
wrapForGutter(
'No messaging app for now. You can add one later (like Telegram, Discord, WhatsApp, Teams, Slack, or iMessage).',
4,
),
),
),
);
);
}
if (result === BACK_TO_CHANNEL_SELECTION) backed = true;
}
}