mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Make public starter channels best effort (#5192)
## Summary - treat public starter-channel provisioning as best-effort after preserving the required private Welcome path - let community onboarding complete and focus Welcome when the reported metadata lookup error occurs - remove the now-obsolete retry-toast expectations for optional starter provisioning ## Scope This intentionally does not change relay tombstone semantics or auto-join existing public channels. ## Test plan - `pnpm exec playwright test tests/e2e/deep-link-invite.spec.ts` (8 passed) - `pnpm exec playwright test tests/e2e/onboarding.spec.ts --grep "failed public starter channel setup"` (1 passed) - `pnpm typecheck` - `pnpm check` - `pnpm test` (4483 passed) - pre-push hook: branch-skew, desktop-check, desktop-typecheck, desktop-test passed on `4658a07beb1e1d54443da5cd2e4a28fae0232f24` --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -82,14 +82,15 @@ export async function initializeStarterChannels(
|
||||
let starterChannels: Awaited<
|
||||
ReturnType<typeof ensureStarterChannels>
|
||||
> | null = null;
|
||||
let starterChannelsError: unknown = null;
|
||||
try {
|
||||
starterChannels = await ensureStarterChannels({
|
||||
ensureStarterChannels: ensureStarterChannelsCommand,
|
||||
getChannels,
|
||||
});
|
||||
} catch (error) {
|
||||
starterChannelsError = error;
|
||||
// Public starter channels are optional. Owners may have deliberately
|
||||
// deleted their deterministic starter channels; that must not strand a
|
||||
// new member after the required private Welcome channel succeeds.
|
||||
console.warn("Failed to initialize public starter channels.", error);
|
||||
}
|
||||
|
||||
@@ -145,16 +146,6 @@ export async function initializeStarterChannels(
|
||||
notifyWelcomeChannelReady(welcomeChannel.id);
|
||||
}
|
||||
const focusChannelId = focus ? welcomeChannel.id : undefined;
|
||||
if (starterChannelsError) {
|
||||
return {
|
||||
ok: false,
|
||||
focusChannelId,
|
||||
reason:
|
||||
starterChannelsError instanceof Error
|
||||
? starterChannelsError.message
|
||||
: "Failed to set up starter channels",
|
||||
};
|
||||
}
|
||||
return { ok: true, focusChannelId };
|
||||
} catch (error) {
|
||||
console.warn("Failed to initialize starter channels.", error);
|
||||
|
||||
@@ -8,7 +8,7 @@ import { seedActiveIdentity } from "../helpers/onboarding";
|
||||
// Invite claiming waits until setup finishes and the final identity is known.
|
||||
|
||||
const DEFAULT_MOCK_PUBKEY = "deadbeef".repeat(8);
|
||||
const WELCOME_FAILURE_PUBKEY = TEST_IDENTITIES.tyler.pubkey;
|
||||
const COMMUNITY_ONBOARDING_PUBKEY = TEST_IDENTITIES.tyler.pubkey;
|
||||
const TRANSACTION_STORAGE_KEY = "buzz-community-onboarding-transaction.v1";
|
||||
const COMMUNITY_RELAY_URL = "wss://hive.example.com";
|
||||
|
||||
@@ -262,7 +262,63 @@ test("queued add-community links open and acknowledge one at a time", async ({
|
||||
]);
|
||||
});
|
||||
|
||||
test("Welcome failure retries once before allowing starter channel setup to be skipped", async ({
|
||||
test("deleted public starter channels do not strand community onboarding", async ({
|
||||
page,
|
||||
}) => {
|
||||
const starterError =
|
||||
"starter channels created but metadata not yet available";
|
||||
await seedActiveIdentity(page, TEST_IDENTITIES.tyler);
|
||||
await page.addInitScript(
|
||||
({ pubkey, relayUrl, storageKey }) => {
|
||||
window.localStorage.setItem(
|
||||
`buzz-machine-onboarding-complete.v2:${pubkey}`,
|
||||
"true",
|
||||
);
|
||||
const timestamp = new Date().toISOString();
|
||||
window.localStorage.setItem(
|
||||
storageKey,
|
||||
JSON.stringify({
|
||||
id: "txn-deleted-starters-1",
|
||||
source: "deep-link-join",
|
||||
stage: "team-intro",
|
||||
relayUrl,
|
||||
communityName: "hive",
|
||||
communityId: "e2e-default-community",
|
||||
createdAt: timestamp,
|
||||
updatedAt: timestamp,
|
||||
}),
|
||||
);
|
||||
},
|
||||
{
|
||||
pubkey: COMMUNITY_ONBOARDING_PUBKEY,
|
||||
relayUrl: COMMUNITY_RELAY_URL,
|
||||
storageKey: TRANSACTION_STORAGE_KEY,
|
||||
},
|
||||
);
|
||||
await installMockBridge(
|
||||
page,
|
||||
{ ensureStarterChannelsErrors: [starterError] },
|
||||
{ relayWsUrl: COMMUNITY_RELAY_URL, skipOnboardingSeed: true },
|
||||
);
|
||||
await page.goto("/");
|
||||
|
||||
await page.getByRole("button", { name: "Take me to Buzz" }).click();
|
||||
|
||||
await expect(page.getByTestId("community-onboarding-flow")).toHaveCount(0);
|
||||
await expect(page).toHaveURL(/#\/channels\/[^/]+$/);
|
||||
await expect(page.getByTestId("chat-title")).toContainText("Welcome");
|
||||
await expect(page.getByText(starterError)).toHaveCount(0);
|
||||
expect(
|
||||
await page.evaluate(
|
||||
() =>
|
||||
window.__BUZZ_E2E_COMMANDS__?.filter(
|
||||
(command) => command === "ensure_starter_channels",
|
||||
).length ?? 0,
|
||||
),
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
test("required Welcome creation failure keeps community onboarding open", async ({
|
||||
page,
|
||||
}) => {
|
||||
const welcomeError = "Channel creation is not permitted.";
|
||||
@@ -289,80 +345,26 @@ test("Welcome failure retries once before allowing starter channel setup to be s
|
||||
);
|
||||
},
|
||||
{
|
||||
pubkey: WELCOME_FAILURE_PUBKEY,
|
||||
pubkey: COMMUNITY_ONBOARDING_PUBKEY,
|
||||
relayUrl: COMMUNITY_RELAY_URL,
|
||||
storageKey: TRANSACTION_STORAGE_KEY,
|
||||
},
|
||||
);
|
||||
await installMockBridge(
|
||||
page,
|
||||
{ ensureStarterChannelsErrors: [welcomeError, welcomeError, welcomeError] },
|
||||
{ createChannelErrors: [welcomeError] },
|
||||
{ relayWsUrl: COMMUNITY_RELAY_URL, skipOnboardingSeed: true },
|
||||
);
|
||||
await page.goto("/");
|
||||
|
||||
for (const name of ["fizz", "honey", "bumble"]) {
|
||||
const character = page.getByTestId(`starter-persona-${name}`);
|
||||
await expect(character).toBeVisible();
|
||||
await expect(character).toHaveAttribute(
|
||||
"src",
|
||||
`/onboarding/starter-team/${name}.png`,
|
||||
);
|
||||
}
|
||||
|
||||
const enterButton = page.getByRole("button", { name: "Take me to Buzz" });
|
||||
await enterButton.click();
|
||||
await page.getByRole("button", { name: "Take me to Buzz" }).click();
|
||||
|
||||
await expect(page.getByTestId("community-onboarding-flow")).toBeVisible();
|
||||
await expect(page.getByText(`${welcomeError} Try again.`)).toBeVisible();
|
||||
await expect(enterButton).toBeEnabled();
|
||||
const backButton = page.getByRole("button", { name: "Back" });
|
||||
await expect(backButton).toBeVisible();
|
||||
await backButton.click();
|
||||
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "Build your profile" }),
|
||||
).toBeVisible();
|
||||
await page.getByLabel("Community username").fill("Tyler");
|
||||
await page.getByTestId("community-profile-next").click();
|
||||
|
||||
await enterButton.click();
|
||||
await expect(page.getByText(`${welcomeError} Try again.`)).toBeVisible();
|
||||
await expect(enterButton).toBeEnabled();
|
||||
|
||||
await enterButton.click();
|
||||
|
||||
const skipButton = page.getByRole("button", { name: "Skip for now" });
|
||||
await expect(page.getByText(welcomeError, { exact: true })).toBeVisible();
|
||||
await expect(skipButton).toBeEnabled();
|
||||
await expect(page.getByRole("button", { name: "Back" })).toBeVisible();
|
||||
|
||||
const starterChannelAttempts = await page.evaluate(
|
||||
() =>
|
||||
window.__BUZZ_E2E_COMMANDS__?.filter(
|
||||
(command) => command === "ensure_starter_channels",
|
||||
).length ?? 0,
|
||||
);
|
||||
expect(starterChannelAttempts).toBe(3);
|
||||
|
||||
await skipButton.click();
|
||||
|
||||
await expect(page.getByTestId("community-onboarding-flow")).toHaveCount(0);
|
||||
expect(
|
||||
await page.evaluate(
|
||||
() =>
|
||||
window.__BUZZ_E2E_COMMANDS__?.filter(
|
||||
(command) => command === "ensure_starter_channels",
|
||||
).length ?? 0,
|
||||
),
|
||||
).toBe(3);
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.evaluate(
|
||||
(transaction) => window.localStorage.getItem(transaction),
|
||||
TRANSACTION_STORAGE_KEY,
|
||||
),
|
||||
)
|
||||
.toBeNull();
|
||||
page.getByRole("button", { name: "Take me to Buzz" }),
|
||||
).toBeEnabled();
|
||||
await expect(page.getByTestId("chat-title")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("persisted deep-link invite hands off to Joining after machine onboarding", async ({
|
||||
|
||||
@@ -2985,27 +2985,6 @@ test("first-run onboarding keeps the shell hidden and lands on private Welcome a
|
||||
await expectWelcomeGuideIntro(page);
|
||||
});
|
||||
|
||||
function retryToast(page: Page, title: string) {
|
||||
return page
|
||||
.locator("[data-sonner-toast][data-removed='false']")
|
||||
.filter({ hasText: title });
|
||||
}
|
||||
|
||||
async function retryToastAction(
|
||||
page: Page,
|
||||
{ command, title }: { command: string; title: string },
|
||||
) {
|
||||
const activeToast = retryToast(page, title);
|
||||
await expect(
|
||||
activeToast.getByRole("button", { name: "Retry" }),
|
||||
).toBeVisible();
|
||||
const before = await commandCount(page, command);
|
||||
await activeToast
|
||||
.getByRole("button", { name: "Retry" })
|
||||
.dispatchEvent("click");
|
||||
await expect.poll(() => commandCount(page, command)).toBeGreaterThan(before);
|
||||
}
|
||||
|
||||
async function commandCount(page: Page, command: string) {
|
||||
return page.evaluate(
|
||||
(target) =>
|
||||
@@ -3015,16 +2994,14 @@ async function commandCount(page: Page, command: string) {
|
||||
);
|
||||
}
|
||||
|
||||
test("failed starter channel retries recreate actionable toasts", async ({
|
||||
test("failed public starter channel setup does not show a retry toast", async ({
|
||||
page,
|
||||
}) => {
|
||||
const starterError = "Mock starter channel setup failed.";
|
||||
await seedActiveIdentity(page, BLANK_TYLER_IDENTITY);
|
||||
await installMockBridge(
|
||||
page,
|
||||
{
|
||||
ensureStarterChannelsErrors: [starterError, starterError, starterError],
|
||||
},
|
||||
{ ensureStarterChannelsErrors: [starterError, starterError] },
|
||||
{ skipOnboardingSeed: true },
|
||||
);
|
||||
await page.goto("/");
|
||||
@@ -3033,45 +3010,12 @@ test("failed starter channel retries recreate actionable toasts", async ({
|
||||
await completeProfileOnboarding(page);
|
||||
|
||||
await expectPrivateWelcomeLanding(page);
|
||||
const title = "Couldn't set up starter channels";
|
||||
const activeToast = retryToast(page, title);
|
||||
await expect(activeToast).toContainText(starterError);
|
||||
await retryToastAction(page, {
|
||||
command: "ensure_starter_channels",
|
||||
title,
|
||||
});
|
||||
await expect(activeToast).toContainText(starterError);
|
||||
await expect(
|
||||
activeToast.getByRole("button", { name: "Retry" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("successful starter channel retry clears its actionable toast", async ({
|
||||
page,
|
||||
}) => {
|
||||
const starterError = "Mock starter channel setup failed.";
|
||||
await seedActiveIdentity(page, BLANK_TYLER_IDENTITY);
|
||||
await installMockBridge(
|
||||
page,
|
||||
{
|
||||
ensureStarterChannelsErrors: [starterError, starterError],
|
||||
},
|
||||
{ skipOnboardingSeed: true },
|
||||
);
|
||||
await page.goto("/");
|
||||
|
||||
await page.getByTestId("onboarding-display-name").fill("Morty QA");
|
||||
await completeProfileOnboarding(page);
|
||||
|
||||
const title = "Couldn't set up starter channels";
|
||||
await expect(retryToast(page, title)).toContainText(starterError);
|
||||
await retryToastAction(page, {
|
||||
command: "ensure_starter_channels",
|
||||
title,
|
||||
});
|
||||
await expect(retryToast(page, title)).toHaveCount(0);
|
||||
await expectWelcomeView(page);
|
||||
await expectStarterChannels(page);
|
||||
page
|
||||
.locator("[data-sonner-toast][data-removed='false']")
|
||||
.filter({ hasText: "Couldn't set up starter channels" }),
|
||||
).toHaveCount(0);
|
||||
expect(await commandCount(page, "ensure_starter_channels")).toBe(2);
|
||||
});
|
||||
|
||||
test("first-run onboarding posts the live Fizz kickoff", async ({ page }) => {
|
||||
|
||||
Reference in New Issue
Block a user