diff --git a/.cursor/rules/create-flow.mdc b/.cursor/rules/create-flow.mdc
index d9ffda7..cf8d0a2 100644
--- a/.cursor/rules/create-flow.mdc
+++ b/.cursor/rules/create-flow.mdc
@@ -16,6 +16,9 @@ alwaysApply: false
`app/(app)/create/utils/createFlowScreenRegistry.ts`. Never branch on layout kind
inside a screen — pick the matching shell (`CreateFlowStepShell` /
`CreateFlowTwoColumnSelectShell`).
+- Keep create-flow step routing centralized in
+ `app/(app)/create/utils/createFlowPaths.ts` (`createFlowStepPath`,
+ `CREATE_ROUTES`) — do not introduce new hardcoded `/create/...` literals.
- Shared create-flow pieces go in `app/(app)/create/components/` (layout shells,
field composites). Generic primitives go in `app/components/`.
@@ -49,8 +52,9 @@ file are a smell once they're used more than once.
namespace (see `localization.mdc`).
- Modal `sections` defaults are DB-shaped seed placeholders, not UI
constants — expect replacement with live data.
-- Modal `sections` defaults are DB-shaped seed placeholders, not UI
- constants — expect replacement with live data.
+- Custom-rule facet mappings (step ids, template-category aliases, selection
+ keys, strip keys) must be sourced from `lib/create/customRuleFacets.ts`
+ (`CUSTOM_RULE_FACETS`) instead of adding new ad-hoc switches/tables.
## Interaction tracking
diff --git a/app/(app)/create/CreateFlowLayoutClient.tsx b/app/(app)/create/CreateFlowLayoutClient.tsx
index e59ff21..d5618d7 100644
--- a/app/(app)/create/CreateFlowLayoutClient.tsx
+++ b/app/(app)/create/CreateFlowLayoutClient.tsx
@@ -20,12 +20,20 @@ import {
getNextStep,
getStepIndex,
parseReviewReturnSearchParam,
- CREATE_FLOW_REVIEW_RETURN_QUERY_KEY,
+ createFlowStepUsesSelectSplitScroll,
TEMPLATES_FACET_RECOMMEND_QUERY,
TEMPLATES_FACET_RECOMMEND_VALUE,
TEMPLATE_REVIEW_FROM_CREATE_FLOW_QUERY,
TEMPLATE_REVIEW_FROM_CREATE_FLOW_VALUE,
} from "./utils/flowSteps";
+import {
+ CREATE_FLOW_SYNC_DRAFT_QUERY,
+ CREATE_FLOW_SYNC_DRAFT_VALUE,
+ CREATE_ROUTES,
+ createFlowStepPath,
+ createFlowStepPathAfterStrippingReviewReturn,
+ createFlowStepPathWithSyncDraft,
+} from "./utils/createFlowPaths";
import { getProportionBarProgressForCreateFlowStep } from "./utils/createFlowProportionProgress";
import {
createFlowStepUsesCenteredTextLayout,
@@ -42,12 +50,12 @@ import {
clearAnonymousCreateFlowStorage,
setTransferPendingFlag,
} from "./utils/anonymousDraftStorage";
-import type { CreateFlowMethodCardFacetSection } from "./types";
import {
createFlowStateFromPublishedRule,
isPublishedRuleSelectionMissing,
methodSectionsPinsFromPublishedHydratePatch,
} from "../../../lib/create/publishedDocumentToCreateFlowState";
+import { METHOD_FACET_API_SECTION_IDS } from "../../../lib/create/customRuleFacets";
import { readLastPublishedRule } from "../../../lib/create/lastPublishedRule";
import { deleteServerDraft } from "../../../lib/create/api";
import messages from "../../../messages/en/index";
@@ -193,8 +201,8 @@ function CreateFlowLayoutContent({
const loginReturnPath =
currentStep === "edit-rule"
- ? "/create/edit-rule?syncDraft=1"
- : "/create/final-review?syncDraft=1";
+ ? createFlowStepPathWithSyncDraft("edit-rule")
+ : createFlowStepPathWithSyncDraft("final-review");
const {
publishBannerMessage,
@@ -248,7 +256,7 @@ function CreateFlowLayoutContent({
if (sessionUser) {
void deleteServerDraft();
}
- router.push("/");
+ router.push(CREATE_ROUTES.root);
return;
}
@@ -262,7 +270,7 @@ function CreateFlowLayoutContent({
variant: "saveProgress",
nextPath:
returnToTemplateReview ??
- `${pathname ?? "/create"}?syncDraft=1`,
+ `${pathname != null && pathname.length > 0 ? pathname : CREATE_ROUTES.createRoot}?${CREATE_FLOW_SYNC_DRAFT_QUERY}=${CREATE_FLOW_SYNC_DRAFT_VALUE}`,
backdropVariant: "blurredYellow",
});
return;
@@ -278,7 +286,7 @@ function CreateFlowLayoutContent({
sessionUser &&
currentStep === "community-save"
) {
- router.replace("/create/review");
+ router.replace(CREATE_ROUTES.review);
}
}, [sessionResolved, sessionUser, currentStep, router]);
@@ -294,12 +302,12 @@ function CreateFlowLayoutContent({
if (currentStep !== "edit-rule") return;
const last = readLastPublishedRule();
if (!last) {
- router.replace("/create/completed");
+ router.replace(CREATE_ROUTES.completed);
return;
}
const editingId = state.editingPublishedRuleId?.trim() ?? "";
if (editingId.length > 0 && editingId !== last.id) {
- router.replace("/create/completed");
+ router.replace(CREATE_ROUTES.completed);
return;
}
const titleOk =
@@ -307,9 +315,7 @@ function CreateFlowLayoutContent({
const sectionsClear = (state.sections?.length ?? 0) === 0;
const patch = createFlowStateFromPublishedRule(last);
const pinPatch = methodSectionsPinsFromPublishedHydratePatch(patch);
- const METHOD_CARD_PIN_FACETS: readonly CreateFlowMethodCardFacetSection[] =
- ["communication", "membership", "decisionApproaches", "conflictManagement"];
- const needsPinMerge = METHOD_CARD_PIN_FACETS.some(
+ const needsPinMerge = METHOD_FACET_API_SECTION_IDS.some(
(key) =>
pinPatch[key] === true &&
state.methodSectionsPinCommitted?.[key] !== true,
@@ -406,11 +412,9 @@ function CreateFlowLayoutContent({
currentStep === "final-review" || currentStep === "edit-rule";
const isCardLayoutStep = createFlowStepUsesCardLayout(currentStep);
/** Two-column select / right-rail: below `lg` main scrolls; at `lg+` only the right column scrolls. */
- const isSelectSplitScrollStep =
- currentStep === "community-size" ||
- currentStep === "community-structure" ||
- currentStep === "core-values" ||
- currentStep === "decision-approaches";
+ const isSelectSplitScrollStep = createFlowStepUsesSelectSplitScroll(
+ currentStep,
+ );
const stepIdx = currentStep != null ? getStepIndex(currentStep) : -1;
/** At `md+`, main cross-axis: center by default; exceptions stay top-aligned (see product spec). */
@@ -586,7 +590,7 @@ function CreateFlowLayoutContent({
editingPublishedRuleId: last.id,
sections: [],
});
- router.push("/create/edit-rule");
+ router.push(createFlowStepPath("edit-rule"));
}
: undefined
}
@@ -738,15 +742,11 @@ function CreateFlowLayoutContent({
setMethodSectionsPinCommitted(facet, true);
}
if (reviewReturnTarget) {
- const params = new URLSearchParams(
- searchParams?.toString() ?? "",
- );
- params.delete(CREATE_FLOW_REVIEW_RETURN_QUERY_KEY);
- const qs = params.toString();
router.push(
- qs.length > 0
- ? `/create/${reviewReturnTarget}?${qs}`
- : `/create/${reviewReturnTarget}`,
+ createFlowStepPathAfterStrippingReviewReturn(
+ reviewReturnTarget,
+ searchParams,
+ ),
);
return;
}
@@ -784,20 +784,16 @@ function CreateFlowLayoutContent({
? () =>
router.push(
templateReviewFooterBackToCreateReview
- ? "/create/review"
- : "/",
+ ? CREATE_ROUTES.review
+ : CREATE_ROUTES.root,
)
: reviewReturnTarget
? () => {
- const params = new URLSearchParams(
- searchParams?.toString() ?? "",
- );
- params.delete(CREATE_FLOW_REVIEW_RETURN_QUERY_KEY);
- const qs = params.toString();
router.push(
- qs.length > 0
- ? `/create/${reviewReturnTarget}?${qs}`
- : `/create/${reviewReturnTarget}`,
+ createFlowStepPathAfterStrippingReviewReturn(
+ reviewReturnTarget,
+ searchParams,
+ ),
);
}
: previousStep
diff --git a/app/(app)/create/context/CreateFlowContext.tsx b/app/(app)/create/context/CreateFlowContext.tsx
index 5b9beb1..37169fe 100644
--- a/app/(app)/create/context/CreateFlowContext.tsx
+++ b/app/(app)/create/context/CreateFlowContext.tsx
@@ -185,8 +185,8 @@ export function CreateFlowProvider({
clearCoreValueDetailsLocalStorage();
}, []);
- // Keys produced by the Create Custom stage screens + `buildTemplateCustomizePrefill`.
- // Kept in sync with `CreateFlowState` comments marked "Create Custom —".
+ // Keys cleared here match `STRIP_CUSTOM_RULE_SELECTION_STATE_KEYS` from
+ // `lib/create/customRuleFacets.ts` (CUSTOM_RULE_FACETS / CR-92).
const resetCustomRuleSelections = useCallback(() => {
setState((prev) => stripCustomRuleSelectionFields(prev));
// Effect on `state.coreValueDetailsByChipId` clears its dedicated
diff --git a/app/(app)/create/hooks/useCreateFlowFinalize.ts b/app/(app)/create/hooks/useCreateFlowFinalize.ts
index 9415997..f187f7a 100644
--- a/app/(app)/create/hooks/useCreateFlowFinalize.ts
+++ b/app/(app)/create/hooks/useCreateFlowFinalize.ts
@@ -10,6 +10,7 @@ import {
CREATE_FLOW_COMPLETED_CELEBRATE_QUERY,
CREATE_FLOW_COMPLETED_CELEBRATE_VALUE,
} from "../utils/flowSteps";
+import { createFlowStepPath } from "../utils/createFlowPaths";
type AppRouterLike = { push: (_href: string) => void };
@@ -80,7 +81,7 @@ export function useCreateFlowFinalize({
document: ruleDocument,
});
updateState({ editingPublishedRuleId: undefined });
- router.push("/create/completed");
+ router.push(createFlowStepPath("completed"));
return;
}
if (updateResult.status === 401) {
@@ -113,7 +114,10 @@ export function useCreateFlowFinalize({
document: ruleDocument,
});
router.push(
- `/create/completed?${CREATE_FLOW_COMPLETED_CELEBRATE_QUERY}=${CREATE_FLOW_COMPLETED_CELEBRATE_VALUE}`,
+ createFlowStepPath("completed", {
+ [CREATE_FLOW_COMPLETED_CELEBRATE_QUERY]:
+ CREATE_FLOW_COMPLETED_CELEBRATE_VALUE,
+ }),
);
return;
}
diff --git a/app/(app)/create/hooks/useFacetRecommendations.ts b/app/(app)/create/hooks/useFacetRecommendations.ts
index 950f6c7..42b4efd 100644
--- a/app/(app)/create/hooks/useFacetRecommendations.ts
+++ b/app/(app)/create/hooks/useFacetRecommendations.ts
@@ -2,16 +2,14 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { buildFacetQueryString } from "../../../../lib/create/buildFacetQueryString";
+import type { MethodFacetApiSectionId } from "../../../../lib/create/customRuleFacets";
import { useCreateFlow } from "../context/CreateFlowContext";
/**
* Card-deck section ids served by `/api/create-flow/methods` (CR-88 §9.2).
+ * Same tuple as {@link METHOD_FACET_API_SECTION_IDS} (`CUSTOM_RULE_FACETS`, CR-92).
*/
-export type RecommendationSection =
- | "communication"
- | "membership"
- | "decisionApproaches"
- | "conflictManagement";
+export type RecommendationSection = MethodFacetApiSectionId;
export type FacetRecommendationsResult = {
/** `true` once the network call completes (or short-circuits with no facets). */
diff --git a/app/(app)/create/screens/CreateFlowScreenView.tsx b/app/(app)/create/screens/CreateFlowScreenView.tsx
index 4ffd1f9..635cd15 100644
--- a/app/(app)/create/screens/CreateFlowScreenView.tsx
+++ b/app/(app)/create/screens/CreateFlowScreenView.tsx
@@ -2,20 +2,7 @@
import type { ReactNode } from "react";
import type { CreateFlowStep } from "../types";
-import { InformationalScreen } from "./informational/InformationalScreen";
-import { CreateFlowTextFieldScreen } from "./text/CreateFlowTextFieldScreen";
-import { CommunitySizeSelectScreen } from "./select/CommunitySizeSelectScreen";
-import { CommunityStructureSelectScreen } from "./select/CommunityStructureSelectScreen";
-import { CoreValuesSelectScreen } from "./select/CoreValuesSelectScreen";
-import { ConfirmStakeholdersScreen } from "./select/ConfirmStakeholdersScreen";
-import { CommunityUploadScreen } from "./upload/CommunityUploadScreen";
-import { CommunityReviewScreen } from "./review/CommunityReviewScreen";
-import { FinalReviewScreen } from "./review/FinalReviewScreen";
-import { CommunicationMethodsScreen } from "./card/CommunicationMethodsScreen";
-import { MembershipMethodsScreen } from "./card/MembershipMethodsScreen";
-import { ConflictManagementScreen } from "./card/ConflictManagementScreen";
-import { DecisionApproachesScreen } from "./right-rail/DecisionApproachesScreen";
-import { CompletedScreen } from "./completed/CompletedScreen";
+import { renderCreateFlowScreen } from "./createFlowScreenComponents";
/**
* Maps each wizard `screenId` to its screen component.
@@ -23,73 +10,14 @@ import { CompletedScreen } from "./completed/CompletedScreen";
* **Folder rule (Figma):** subfolders match `CREATE_FLOW_SCREEN_REGISTRY[].layoutKind`
* — `select/` (two-column chip flows), `card/` (compact card-stack steps), `text/`, etc.
* The URL segment (`communication-methods`) is not the folder name; see `createFlowScreenRegistry.ts`.
+ *
+ * Implementation lives in {@link renderCreateFlowScreen} (`createFlowScreenComponents.tsx`)
+ * so the registry metadata and this router stay easier to keep in sync (CR-92 §3).
*/
export function CreateFlowScreenView({
screenId,
}: {
screenId: CreateFlowStep;
}): ReactNode {
- switch (screenId) {
- case "informational":
- return ;
- case "community-name":
- return (
-
- );
- case "community-structure":
- return ;
- case "community-context":
- return (
-
- );
- case "community-size":
- return ;
- case "community-upload":
- return ;
- case "community-save":
- return (
-
- );
- case "review":
- return ;
- case "core-values":
- return ;
- case "communication-methods":
- return ;
- case "membership-methods":
- return ;
- case "decision-approaches":
- return ;
- case "conflict-management":
- return ;
- case "confirm-stakeholders":
- return ;
- case "final-review":
- return ;
- case "edit-rule":
- return ;
- case "completed":
- return ;
- default: {
- const _exhaustive: never = screenId;
- return _exhaustive;
- }
- }
+ return renderCreateFlowScreen(screenId);
}
diff --git a/app/(app)/create/screens/createFlowScreenComponents.tsx b/app/(app)/create/screens/createFlowScreenComponents.tsx
new file mode 100644
index 0000000..581ca0f
--- /dev/null
+++ b/app/(app)/create/screens/createFlowScreenComponents.tsx
@@ -0,0 +1,88 @@
+/**
+ * Step → screen component map (Linear CR-92 §3). Keeps {@link CreateFlowScreenView}
+ * thin; pair with {@link CREATE_FLOW_SCREEN_REGISTRY} metadata in tests/docs so
+ * new steps do not drift.
+ */
+
+import type { ReactNode } from "react";
+import type { CreateFlowStep } from "../types";
+import { InformationalScreen } from "./informational/InformationalScreen";
+import { CreateFlowTextFieldScreen } from "./text/CreateFlowTextFieldScreen";
+import { CommunitySizeSelectScreen } from "./select/CommunitySizeSelectScreen";
+import { CommunityStructureSelectScreen } from "./select/CommunityStructureSelectScreen";
+import { CoreValuesSelectScreen } from "./select/CoreValuesSelectScreen";
+import { ConfirmStakeholdersScreen } from "./select/ConfirmStakeholdersScreen";
+import { CommunityUploadScreen } from "./upload/CommunityUploadScreen";
+import { CommunityReviewScreen } from "./review/CommunityReviewScreen";
+import { FinalReviewScreen } from "./review/FinalReviewScreen";
+import { CommunicationMethodsScreen } from "./card/CommunicationMethodsScreen";
+import { MembershipMethodsScreen } from "./card/MembershipMethodsScreen";
+import { ConflictManagementScreen } from "./card/ConflictManagementScreen";
+import { DecisionApproachesScreen } from "./right-rail/DecisionApproachesScreen";
+import { CompletedScreen } from "./completed/CompletedScreen";
+
+export function renderCreateFlowScreen(screenId: CreateFlowStep): ReactNode {
+ switch (screenId) {
+ case "informational":
+ return ;
+ case "community-name":
+ return (
+
+ );
+ case "community-structure":
+ return ;
+ case "community-context":
+ return (
+
+ );
+ case "community-size":
+ return ;
+ case "community-upload":
+ return ;
+ case "community-save":
+ return (
+
+ );
+ case "review":
+ return ;
+ case "core-values":
+ return ;
+ case "communication-methods":
+ return ;
+ case "membership-methods":
+ return ;
+ case "decision-approaches":
+ return ;
+ case "conflict-management":
+ return ;
+ case "confirm-stakeholders":
+ return ;
+ case "final-review":
+ return ;
+ case "edit-rule":
+ return ;
+ case "completed":
+ return ;
+ default: {
+ const _exhaustive: never = screenId;
+ return _exhaustive;
+ }
+ }
+}
diff --git a/app/(app)/create/screens/review/CommunityReviewScreen.tsx b/app/(app)/create/screens/review/CommunityReviewScreen.tsx
index 0536910..c5f164b 100644
--- a/app/(app)/create/screens/review/CommunityReviewScreen.tsx
+++ b/app/(app)/create/screens/review/CommunityReviewScreen.tsx
@@ -17,19 +17,7 @@ import {
vectorMarkPath,
} from "../../../../../lib/assetUtils";
import { methodSectionsPinsForHydratedSelections } from "../../../../../lib/create/publishedDocumentToCreateFlowState";
-
-/**
- * Targets for a `pendingTemplateAction` redirect. Customize resumes the
- * custom-rule stage with chips already prefilled; useWithoutChanges jumps to
- * the review-and-complete stage since the template body is already in state.
- */
-const PENDING_TEMPLATE_REDIRECT_TARGET: Record<
- "customize" | "useWithoutChanges",
- string
-> = {
- customize: "/create/core-values",
- useWithoutChanges: "/create/confirm-stakeholders",
-};
+import { createFlowStepPath } from "../../utils/createFlowPaths";
/** Create Community review — Figma `19706:12135` (`/create/review`; two columns from `lg:`; column caps in `createFlowLayoutTokens`). */
export function CommunityReviewScreen() {
@@ -56,8 +44,10 @@ export function CommunityReviewScreen() {
if (firedRedirectRef.current) return;
const pending = state.pendingTemplateAction;
if (!pending) return;
- const target = PENDING_TEMPLATE_REDIRECT_TARGET[pending.mode];
- if (!target) return;
+ const target =
+ pending.mode === "customize"
+ ? createFlowStepPath("core-values")
+ : createFlowStepPath("confirm-stakeholders");
firedRedirectRef.current = true;
const pinMerge =
pending.mode === "customize"
diff --git a/app/(app)/create/types.ts b/app/(app)/create/types.ts
index 57ca0b8..526f1cc 100644
--- a/app/(app)/create/types.ts
+++ b/app/(app)/create/types.ts
@@ -5,6 +5,8 @@
* including step types, state management, and context interfaces.
*/
+import type { MethodFacetApiSectionId } from "../../../lib/create/customRuleFacets";
+
/**
* Valid step IDs for the create rule flow (URL segment after `/create/`).
* Create Community order matches Figma; `review` closes that stage per design.
@@ -36,12 +38,11 @@ export type CreateFlowTextStateField =
| "communityContext"
| "communitySaveEmail";
-/** Facet-backed method card stacks (`GET /api/create-flow/methods?section=`). */
-export type CreateFlowMethodCardFacetSection =
- | "communication"
- | "membership"
- | "decisionApproaches"
- | "conflictManagement";
+/**
+ * Facet-backed method card stacks (`GET /api/create-flow/methods?section=`).
+ * Canonical ids: {@link METHOD_FACET_API_SECTION_IDS} in `lib/create/customRuleFacets.ts`.
+ */
+export type CreateFlowMethodCardFacetSection = MethodFacetApiSectionId;
/**
* Serialized chip row for `community-structure` (preset + custom labels).
diff --git a/app/(app)/create/utils/createFlowPaths.ts b/app/(app)/create/utils/createFlowPaths.ts
new file mode 100644
index 0000000..8d5ba0f
--- /dev/null
+++ b/app/(app)/create/utils/createFlowPaths.ts
@@ -0,0 +1,78 @@
+/**
+ * Central `/create/...` path builders (Linear CR-92 §2).
+ * Prefer these over string literals so layout, redirects, hooks, and tests stay aligned.
+ */
+
+import type { CreateFlowStep } from "../types";
+import { CREATE_FLOW_REVIEW_RETURN_QUERY_KEY } from "./flowSteps";
+
+export const CREATE_ROUTES = {
+ root: "/",
+ createRoot: "/create",
+ /** First step resolves via redirect from `/create`. */
+ createFirstStep: "/create",
+ review: "/create/review",
+ finalReview: "/create/final-review",
+ completed: "/create/completed",
+ editRule: "/create/edit-rule",
+} as const;
+
+/**
+ * Post-login return and session-gate paths on wizard steps.
+ * (Also used when `pathname` is unknown but `syncDraft` must be appended.)
+ */
+export const CREATE_FLOW_SYNC_DRAFT_QUERY = "syncDraft" as const;
+export const CREATE_FLOW_SYNC_DRAFT_VALUE = "1" as const;
+
+export function createFlowStepPathWithSyncDraft(step: CreateFlowStep): string {
+ return createFlowStepPath(step, {
+ [CREATE_FLOW_SYNC_DRAFT_QUERY]: CREATE_FLOW_SYNC_DRAFT_VALUE,
+ });
+}
+
+export type CreateFlowPathQuery = Record<
+ string,
+ string | number | boolean | undefined
+>;
+
+/**
+ * Path for a wizard step: `/create/{screenId}` with optional query string.
+ */
+export function createFlowStepPath(
+ step: CreateFlowStep,
+ query?: CreateFlowPathQuery,
+): string {
+ const base = `/create/${step}`;
+ if (query == null || Object.keys(query).length === 0) return base;
+ const sp = new URLSearchParams();
+ for (const [k, v] of Object.entries(query)) {
+ if (v === undefined) continue;
+ sp.set(k, String(v));
+ }
+ const q = sp.toString();
+ return q.length > 0 ? `${base}?${q}` : base;
+}
+
+export function createCompletedPath(query?: CreateFlowPathQuery): string {
+ return createFlowStepPath("completed", query);
+}
+
+/**
+ * Navigate back from a facet step to final-review / edit-rule, dropping
+ * `reviewReturn` from the current query while preserving other params.
+ */
+export function createFlowStepPathAfterStrippingReviewReturn(
+ step: CreateFlowStep,
+ searchParams: URLSearchParams | null | undefined,
+): string {
+ const params = new URLSearchParams(searchParams?.toString() ?? "");
+ params.delete(CREATE_FLOW_REVIEW_RETURN_QUERY_KEY);
+ const query: CreateFlowPathQuery = {};
+ params.forEach((value, key) => {
+ query[key] = value;
+ });
+ return createFlowStepPath(
+ step,
+ Object.keys(query).length > 0 ? query : undefined,
+ );
+}
diff --git a/app/(app)/create/utils/customRuleConfirmFooterSteps.ts b/app/(app)/create/utils/customRuleConfirmFooterSteps.ts
index 4b22413..26e6beb 100644
--- a/app/(app)/create/utils/customRuleConfirmFooterSteps.ts
+++ b/app/(app)/create/utils/customRuleConfirmFooterSteps.ts
@@ -1,3 +1,4 @@
+import { CUSTOM_RULE_FACETS } from "../../../../lib/create/customRuleFacets";
import type {
CreateFlowMethodCardFacetSection,
CreateFlowState,
@@ -11,9 +12,9 @@ type FooterMessageKey = keyof typeof footerMessages;
* Binding for each Custom Rule stage step whose footer primary button
* gates the user on "has at least one chip selected?". All five screens
* render the same `