Route kebab Customize through the prefilled policy wizard so existing methods and values can be renamed, reordered, and kept in that field order on the card after Finalize.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
adilallo
2026-08-21 13:51:37 -06:00
co-authored by Cursor
parent e9951f260b
commit 4006fe5c6c
31 changed files with 3062 additions and 1530 deletions
@@ -16,13 +16,23 @@ import type {
import { CreateFlowHeaderLockup } from "../../components/CreateFlowHeaderLockup";
import { CreateFlowTwoColumnSelectShell } from "../../components/CreateFlowTwoColumnSelectShell";
import { CoreValueEditFields } from "../../components/methodEditFields";
import CustomMethodCardModalBody from "../../components/CustomMethodCardModalBody";
import CustomMethodCardWizard from "../../components/CustomMethodCardWizard";
import { usesWizardFieldBlocksModalBody } from "../../../../../lib/create/usesWizardFieldBlocksModalBody";
import { buildCustomRuleModalKebabMenu } from "../../components/customRuleModalKebabMenu";
import {
duplicateCoreValueChipInDraft,
MAX_SELECTED_CORE_VALUES,
removeCoreValueChipFromDraft,
} from "../../../../../lib/create/coreValueChipFacet";
import { omitIdFromStringRecord } from "../../../../../lib/create/duplicateMethodCardModalDraft";
import {
buildMethodCardWizardInitialValues,
coreValueDetailsFromWizardFieldBlocks,
overlayFacetPrefillValues,
type MethodCardWizardInitialValues,
} from "../../../../../lib/create/methodCardWizardPrefill";
import type { CustomMethodCardFieldBlock } from "../../../../../lib/create/customMethodCardFieldBlocks";
import { uploadCreateFlowFile } from "../../../../../lib/create/uploadToServer";
const MAX_CORE_VALUES = MAX_SELECTED_CORE_VALUES;
@@ -113,6 +123,15 @@ export function CoreValuesSelectScreen() {
);
const [modalSession, setModalSession] = useState<ModalSession | null>(null);
const [draft, setDraft] = useState<CoreValueDetailEntry>(EMPTY_DETAIL);
const [addCustomWizardOpen, setAddCustomWizardOpen] = useState(false);
const [wizardCustomizeChipId, setWizardCustomizeChipId] = useState<
string | null
>(null);
const [wizardInitialValues, setWizardInitialValues] =
useState<MethodCardWizardInitialValues | null>(null);
const [draftFieldBlocks, setDraftFieldBlocks] = useState<
CustomMethodCardFieldBlock[] | null
>(null);
useEffect(() => {
setCoreValueOptions(
@@ -165,6 +184,7 @@ export function CoreValuesSelectScreen() {
return {
meaning: saved?.meaning ?? preset.meaning,
signals: saved?.signals ?? preset.signals,
...(saved?.supportText ? { supportText: saved.supportText } : {}),
};
},
[state.coreValueDetailsByChipId, getPresetTexts],
@@ -179,12 +199,22 @@ export function CoreValuesSelectScreen() {
) => {
const initial = seedDetail ?? getInitialTexts(chipId, valueLabel);
initialDraftRef.current = { ...initial };
const persisted = state.customMethodCardFieldBlocksById?.[chipId];
setDraftFieldBlocks(
Array.isArray(persisted) && persisted.length > 0
? structuredClone(persisted)
: null,
);
setDraft(initial);
setActiveModalChipId(chipId);
setModalSession(session);
markCreateFlowInteraction();
},
[getInitialTexts, markCreateFlowInteraction],
[
getInitialTexts,
markCreateFlowInteraction,
state.customMethodCardFieldBlocksById,
],
);
const handleDraftChange = useCallback(
@@ -200,19 +230,19 @@ export function CoreValuesSelectScreen() {
initialDraftRef.current = null;
setActiveModalChipId(null);
setModalSession(null);
setDraftFieldBlocks(null);
}, []);
const confirmLeaveWithoutSaving = useCallback(async () => {
const isPendingAdd =
modalSession === "pending" || modalSession === "customPending";
const initial = initialDraftRef.current;
const editingDirty =
modalSession === "editing" &&
const fieldsDirty =
initial != null &&
(draft.meaning !== initial.meaning || draft.signals !== initial.signals);
if (!isPendingAdd && !editingDirty) {
if (!fieldsDirty) {
return true;
}
const isPendingAdd =
modalSession === "pending" || modalSession === "customPending";
return requestConfirm({
title: cv.detailModal.discardTitle,
description: isPendingAdd
@@ -294,11 +324,7 @@ export function CoreValuesSelectScreen() {
markCreateFlowInteraction();
replaceState((prev) => ({
...prev,
selectedCoreValueIds: selectedIdsFromOptions(nextFiltered),
coreValuesChipsSnapshot:
chipOptionsToSnapshotRows(nextFiltered),
coreValueDetailsByChipId:
omitIdFromStringRecord(prev.coreValueDetailsByChipId, activeModalChipId),
...removeCoreValueChipFromDraft(prev, activeModalChipId),
}));
setCoreValueOptions(nextFiltered);
}
@@ -355,48 +381,185 @@ export function CoreValuesSelectScreen() {
if (!activeModalChipId || !modalSession) return;
markCreateFlowInteraction();
pendingEphemeralCoreDuplicateRef.current = null;
const existingBlocks =
draftFieldBlocks && draftFieldBlocks.length > 0
? draftFieldBlocks
: state.customMethodCardFieldBlocksById?.[activeModalChipId];
updateState({
coreValueDetailsByChipId: {
...(state.coreValueDetailsByChipId ?? {}),
[activeModalChipId]: draft,
},
...(existingBlocks && existingBlocks.length > 0
? {
customMethodCardFieldBlocksById: {
...(state.customMethodCardFieldBlocksById ?? {}),
[activeModalChipId]: overlayFacetPrefillValues(existingBlocks, {
group: "coreValues",
draft,
headings: {
meaning: cv.detailModal.meaningLabel,
signals: cv.detailModal.signalsLabel,
},
}),
},
}
: {}),
});
finalizeModalDismiss();
}, [
activeModalChipId,
cv.detailModal.meaningLabel,
cv.detailModal.signalsLabel,
draft,
draftFieldBlocks,
finalizeModalDismiss,
markCreateFlowInteraction,
modalSession,
state.coreValueDetailsByChipId,
state.customMethodCardFieldBlocksById,
updateState,
]);
const modalChipLabel =
coreValueOptions.find((o) => o.id === activeModalChipId)?.label ?? "";
const modalUsesWizardFieldBlocksBody = Boolean(
activeModalChipId &&
usesWizardFieldBlocksModalBody({
methodId: activeModalChipId,
meta: {},
fieldBlocksById: state.customMethodCardFieldBlocksById,
modalEditUnlocked: true,
draftFieldBlocks,
}),
);
const showFooterPrimary = Boolean(modalSession);
const handleCustomize = useCallback(() => {
if (!activeModalChipId) return;
markCreateFlowInteraction();
setWizardInitialValues(
buildMethodCardWizardInitialValues({
cardId: activeModalChipId,
fallbackTitle: modalChipLabel,
fallbackDescription:
draft.supportText?.trim() || cv.detailModal.subtitle,
meta: {},
persistedBlocks: state.customMethodCardFieldBlocksById,
draftFieldBlocks,
facetPrefill: {
group: "coreValues",
draft,
headings: {
meaning: cv.detailModal.meaningLabel,
signals: cv.detailModal.signalsLabel,
},
},
}),
);
setWizardCustomizeChipId(activeModalChipId);
setAddCustomWizardOpen(true);
}, [
activeModalChipId,
cv.detailModal.meaningLabel,
cv.detailModal.signalsLabel,
cv.detailModal.subtitle,
draft,
draftFieldBlocks,
markCreateFlowInteraction,
modalChipLabel,
state.customMethodCardFieldBlocksById,
]);
const handleCloseAddWizard = useCallback(() => {
setAddCustomWizardOpen(false);
setWizardCustomizeChipId(null);
setWizardInitialValues(null);
}, []);
const handleFinalizeCustomCard = useCallback(
({
title,
description,
fieldBlocks,
}: {
title: string;
description: string;
fieldBlocks: CustomMethodCardFieldBlock[];
}) => {
const chipId = wizardCustomizeChipId ?? activeModalChipId;
if (!chipId) return;
markCreateFlowInteraction();
pendingEphemeralCoreDuplicateRef.current = null;
const trimmedTitle = title.trim();
const trimmedDescription = description.trim();
const nextDetails = {
...coreValueDetailsFromWizardFieldBlocks(fieldBlocks, draft),
...(trimmedDescription.length > 0
? { supportText: trimmedDescription }
: {}),
};
replaceState((prev) => {
const snap = [...(prev.coreValuesChipsSnapshot ?? [])];
const i = snap.findIndex((r) => r.id === chipId);
if (i >= 0 && trimmedTitle.length > 0) {
snap[i] = { ...snap[i], label: trimmedTitle };
}
return {
...prev,
coreValuesChipsSnapshot: snap,
coreValueDetailsByChipId: {
...(prev.coreValueDetailsByChipId ?? {}),
[chipId]: nextDetails,
},
customMethodCardFieldBlocksById: {
...(prev.customMethodCardFieldBlocksById ?? {}),
[chipId]: structuredClone(fieldBlocks),
},
};
});
setDraft(nextDetails);
setDraftFieldBlocks(structuredClone(fieldBlocks));
setAddCustomWizardOpen(false);
setWizardCustomizeChipId(null);
setWizardInitialValues(null);
},
[
activeModalChipId,
draft,
markCreateFlowInteraction,
replaceState,
wizardCustomizeChipId,
],
);
const kebabMenuItems = useMemo(() => {
if (!modalSession || !activeModalChipId) return [];
const selectedCount = coreValueOptions.filter(
(o) => o.state === "selected",
).length;
return buildCustomRuleModalKebabMenu(modalKebabMenu, {
showCustomize: true,
onCustomize: handleCustomize,
onDuplicate:
modalSession !== "editing" || selectedCount >= MAX_CORE_VALUES
(state.editingPublishedRuleId?.trim() ?? "") !== "" ||
selectedCount >= MAX_CORE_VALUES
? undefined
: handleDuplicateCoreChip,
showRemove: true,
showRemove: modalSession === "editing",
onRemove: handleRemoveFromKebab,
});
}, [
activeModalChipId,
coreValueOptions,
handleCustomize,
handleDuplicateCoreChip,
handleRemoveFromKebab,
modalKebabMenu,
modalSession,
state.editingPublishedRuleId,
]);
const handleChipClick = (chipId: string) => {
const target = coreValueOptions.find((o) => o.id === chipId);
@@ -520,14 +683,16 @@ export function CoreValuesSelectScreen() {
{detailModal && (
<Create
isOpen={activeModalChipId !== null}
isOpen={activeModalChipId !== null && !addCustomWizardOpen}
onClose={handleModalDismiss}
backdropVariant="blurredYellow"
headerContent={
<div className="bg-[var(--color-surface-default-primary)] px-[24px] py-[12px] shrink-0">
<ContentLockup
title={modalChipLabel}
description={detailModal.subtitle}
description={
draft.supportText?.trim() || detailModal.subtitle
}
variant="modal"
alignment="left"
/>
@@ -548,13 +713,43 @@ export function CoreValuesSelectScreen() {
}
ariaLabel={modalChipLabel || "Core value details"}
>
<CoreValueEditFields
value={draft}
onChange={handleDraftChange}
/>
{modalUsesWizardFieldBlocksBody && activeModalChipId ? (
<CustomMethodCardModalBody
cardId={activeModalChipId}
blocksById={state.customMethodCardFieldBlocksById}
blocksOverride={
draftFieldBlocks !== null ? draftFieldBlocks : undefined
}
showPolicyContentLockupWhenNoBlocks={false}
onFieldBlocksChange={
draftFieldBlocks === null
? undefined
: (next) => {
setDraftFieldBlocks(next);
setDraft(
coreValueDetailsFromWizardFieldBlocks(next, draft),
);
}
}
/>
) : (
<CoreValueEditFields
value={draft}
onChange={handleDraftChange}
/>
)}
</Create>
)}
</CreateFlowTwoColumnSelectShell>
<CustomMethodCardWizard
isOpen={addCustomWizardOpen}
onClose={handleCloseAddWizard}
initialValues={wizardInitialValues}
onFinalize={handleFinalizeCustomCard}
onPersistCustomUploadFile={(file) =>
uploadCreateFlowFile(file, "customMethodAttachment")
}
/>
{confirmDialog}
</>
);