Close method-card Save like first add, keep key-resource chips on the approach they were chosen for, and leave expanded See-all stacks in catalog order.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
adilallo
2026-08-25 09:21:07 -06:00
co-authored by Cursor
parent b1ca1a748e
commit 963fa03c5c
12 changed files with 437 additions and 167 deletions
@@ -35,10 +35,6 @@ import {
decisionApproachPresetFor,
membershipPresetFor,
} from "../../../../lib/create/finalReviewChipPresets";
import {
applyDecisionApproachKeyResources,
selectedKeyResourceLabelsFromCheckedIds,
} from "../../../../lib/create/decisionApproachKeyResources";
import { isCustomMethodCardId } from "../../../../lib/create/isCustomMethodCardId";
import { usesWizardFieldBlocksModalBody } from "../../../../lib/create/usesWizardFieldBlocksModalBody";
import type { CustomMethodCardFieldBlock } from "../../../../lib/create/customMethodCardFieldBlocks";
@@ -1270,7 +1266,6 @@ function facetSeedSignature(
return JSON.stringify({
meta: state.customMethodCardMetaById?.[id] ?? null,
details: state.decisionApproachDetailsById?.[id] ?? null,
keyResources: state.selectedDecisionKeyResourceIds ?? null,
blocks: state.customMethodCardFieldBlocksById?.[id] ?? null,
});
case "conflictManagement":
@@ -1335,18 +1330,12 @@ function seedDraftForTarget(
const saved =
state.decisionApproachDetailsById?.[target.overrideKey] ??
decisionApproachPresetFor(target.overrideKey);
const withKeys = applyDecisionApproachKeyResources(
saved,
selectedKeyResourceLabelsFromCheckedIds(
state.selectedDecisionKeyResourceIds ?? [],
),
);
return {
groupKey: "decisionApproaches",
value: {
...withKeys,
applicableScope: [...withKeys.applicableScope],
selectedApplicableScope: [...withKeys.selectedApplicableScope],
...saved,
applicableScope: [...saved.applicableScope],
selectedApplicableScope: [...saved.selectedApplicableScope],
},
};
}
@@ -1,10 +1,7 @@
"use client";
import { useMemo } from "react";
import {
mergeCompactCardIdsWithPinnedSelected,
orderRankedMethodsWithPinnedSelection,
} from "../../../../lib/create/methodCardDisplayOrder";
import { mergeCompactCardIdsWithPinnedSelected } from "../../../../lib/create/methodCardDisplayOrder";
import {
deriveCompactCards,
rankMethodsByScore,
@@ -15,10 +12,10 @@ import {
type MethodEntry = { id: string; label: string; supportText: string };
/**
* Applies score ranking, compact-slot rules, then surfaces selected ids first in
* `selected*Ids` order (most-recent add at index 0 via
* {@link moveFacetSelectionIdToFront}). Selection-first applies whenever the facet
* has any selection — not only after footer Confirm (`methodSectionsPinCommitted`).
* Applies score ranking and compact-slot rules. Expanded CardStack order stays
* the ranked catalog (selected cards keep their place). Compact slots still
* pin selected ids first so a pick outside the unpinned top-N remains visible
* when the stack is collapsed.
*/
export function useMethodCardDeckOrdering(
section: RecommendationSection,
@@ -35,16 +32,7 @@ export function useMethodCardDeckOrdering(
);
const selectionShowcaseActive = selectedIds.length > 0;
const displayMethods = useMemo(
() =>
orderRankedMethodsWithPinnedSelection(
rankedMethods,
selectedIds,
selectionShowcaseActive,
),
[rankedMethods, selectedIds, selectionShowcaseActive],
);
const displayMethods = rankedMethods;
const { compactCardIds: baseCompactCardIds, recommendedIds } = useMemo(
() =>
@@ -60,13 +48,13 @@ export function useMethodCardDeckOrdering(
const compactCardIds = useMemo(
() =>
mergeCompactCardIdsWithPinnedSelected(
displayMethods.map((m) => m.id),
rankedMethods.map((m) => m.id),
baseCompactCardIds,
selectedIds,
selectionShowcaseActive,
5,
),
[displayMethods, baseCompactCardIds, selectedIds, selectionShowcaseActive],
[rankedMethods, baseCompactCardIds, selectedIds, selectionShowcaseActive],
);
const sampleCards = useMemo(
@@ -643,16 +643,9 @@ export function CommunicationMethodsScreen() {
},
});
}
if (pendingDraft) {
customizeSnapshotRef.current = captureMethodCardCustomizeSnapshot(
pendingDraft,
persistWizardBlocks ? draftFieldBlocks : null,
customizeSnapshotRef.current?.headerDraft ?? {
title: "",
description: "",
},
);
}
pendingEphemeralDuplicateIdRef.current = null;
customizeSnapshotRef.current = null;
void handleCreateModalClose();
return;
}
@@ -740,6 +733,7 @@ export function CommunicationMethodsScreen() {
title={modalConfig.title}
description={modalConfig.description}
nextButtonText={modalConfig.nextButtonText}
showBackButton={false}
showNextButton={showMethodModalPrimary}
backdropVariant="blurredYellow"
kebabTriggerAriaLabel={modalKebabMenu.triggerAriaLabel}
@@ -644,16 +644,9 @@ export function ConflictManagementScreen() {
},
});
}
if (pendingDraft) {
customizeSnapshotRef.current = captureMethodCardCustomizeSnapshot(
pendingDraft,
persistWizardBlocks ? draftFieldBlocks : null,
customizeSnapshotRef.current?.headerDraft ?? {
title: "",
description: "",
},
);
}
pendingEphemeralDuplicateIdRef.current = null;
customizeSnapshotRef.current = null;
void handleCreateModalClose();
return;
}
@@ -741,6 +734,7 @@ export function ConflictManagementScreen() {
title={modalConfig.title}
description={modalConfig.description}
nextButtonText={modalConfig.nextButtonText}
showBackButton={false}
showNextButton={showMethodModalPrimary}
backdropVariant="blurredYellow"
kebabTriggerAriaLabel={modalKebabMenu.triggerAriaLabel}
@@ -637,16 +637,9 @@ export function MembershipMethodsScreen() {
},
});
}
if (pendingDraft) {
customizeSnapshotRef.current = captureMethodCardCustomizeSnapshot(
pendingDraft,
persistWizardBlocks ? draftFieldBlocks : null,
customizeSnapshotRef.current?.headerDraft ?? {
title: "",
description: "",
},
);
}
pendingEphemeralDuplicateIdRef.current = null;
customizeSnapshotRef.current = null;
void handleCreateModalClose();
return;
}
@@ -734,6 +727,7 @@ export function MembershipMethodsScreen() {
title={modalConfig.title}
description={modalConfig.description}
nextButtonText={modalConfig.nextButtonText}
showBackButton={false}
showNextButton={showMethodModalPrimary}
backdropVariant="blurredYellow"
kebabTriggerAriaLabel={modalKebabMenu.triggerAriaLabel}
@@ -35,10 +35,8 @@ import { uploadCreateFlowFile } from "../../../../../lib/create/uploadToServer";
import { decisionApproachPresetFor } from "../../../../../lib/create/finalReviewChipPresets";
import {
applyDecisionApproachKeyResources,
decisionApproachKeyResourceIdsFromLabels,
decisionApproachKeyResourceCheckboxIds,
selectedKeyResourceLabelsFromCheckedIds,
stringArraysEqual,
syncDecisionApproachKeyResourceDetails,
} from "../../../../../lib/create/decisionApproachKeyResources";
import type { CustomMethodCardFieldBlock } from "../../../../../lib/create/customMethodCardFieldBlocks";
import { mergePresetMethodsWithCustom } from "../../../../../lib/create/mergePresetMethodsWithCustom";
@@ -94,11 +92,12 @@ export function DecisionApproachesScreen() {
>(null);
const selectedIds = state.selectedDecisionApproachIds ?? [];
const messageBoxCheckedIds = state.selectedDecisionKeyResourceIds ?? [];
const selectedKeyResourceLabels = useMemo(
() => selectedKeyResourceLabelsFromCheckedIds(messageBoxCheckedIds),
[messageBoxCheckedIds],
);
const messageBoxCheckedIds = decisionApproachKeyResourceCheckboxIds({
detailsById: state.decisionApproachDetailsById,
selectedApproachIds: selectedIds,
reminderIds: state.selectedDecisionKeyResourceIds,
openDraft: pendingDraft,
});
const messageBoxItems: InfoMessageBoxItem[] = useMemo(
() =>
@@ -155,23 +154,16 @@ export function DecisionApproachesScreen() {
setPendingDraft(
applyDecisionApproachKeyResources(pendingDraft, nextLabels),
);
return;
}
updateState({
selectedDecisionKeyResourceIds: nextCheckedIds,
decisionApproachDetailsById: syncDecisionApproachKeyResourceDetails(
state.decisionApproachDetailsById,
selectedIds,
nextLabels,
decisionApproachPresetFor,
),
});
},
[
markCreateFlowInteraction,
messageBoxCheckedIds,
pendingDraft,
selectedIds,
state.decisionApproachDetailsById,
updateState,
],
);
@@ -179,19 +171,12 @@ export function DecisionApproachesScreen() {
const seedDraft = useCallback(
(id: string): DecisionApproachDetailEntry => {
const saved = state.decisionApproachDetailsById?.[id];
const base = saved
? {
...saved,
applicableScope: [...saved.applicableScope],
selectedApplicableScope: [...saved.selectedApplicableScope],
}
: decisionApproachPresetFor(id);
return applyDecisionApproachKeyResources(
base,
selectedKeyResourceLabels,
);
if (saved) {
return structuredClone(saved);
}
return decisionApproachPresetFor(id);
},
[selectedKeyResourceLabels, state.decisionApproachDetailsById],
[state.decisionApproachDetailsById],
);
const handleCardSelect = useCallback(
@@ -237,31 +222,8 @@ export function DecisionApproachesScreen() {
(next: DecisionApproachDetailEntry) => {
markCreateFlowInteraction();
setPendingDraft(next);
const nextCheckedIds = decisionApproachKeyResourceIdsFromLabels(
next.selectedApplicableScope,
);
if (stringArraysEqual(nextCheckedIds, messageBoxCheckedIds)) {
return;
}
const nextLabels =
selectedKeyResourceLabelsFromCheckedIds(nextCheckedIds);
updateState({
selectedDecisionKeyResourceIds: nextCheckedIds,
decisionApproachDetailsById: syncDecisionApproachKeyResourceDetails(
state.decisionApproachDetailsById,
selectedIds,
nextLabels,
decisionApproachPresetFor,
),
});
},
[
markCreateFlowInteraction,
messageBoxCheckedIds,
selectedIds,
state.decisionApproachDetailsById,
updateState,
],
[markCreateFlowInteraction],
);
const isSelectedCardModal =
@@ -652,10 +614,7 @@ export function DecisionApproachesScreen() {
},
decisionApproachDetailsById: {
...(state.decisionApproachDetailsById ?? {}),
[id]: applyDecisionApproachKeyResources(
decisionApproachPresetFor(id),
selectedKeyResourceLabels,
),
[id]: decisionApproachPresetFor(id),
},
customMethodCardFieldBlocksById: {
...(state.customMethodCardFieldBlocksById ?? {}),
@@ -667,7 +626,6 @@ export function DecisionApproachesScreen() {
markCreateFlowInteraction,
pendingDraft,
selectedIds,
selectedKeyResourceLabels,
state.customMethodCardFieldBlocksById,
state.customMethodCardMetaById,
state.decisionApproachDetailsById,
@@ -687,31 +645,30 @@ export function DecisionApproachesScreen() {
modalUsesWizardFieldBlocksBody && draftFieldBlocks !== null;
if (selectedIds.includes(pendingCardId)) {
if (persistWizardBlocks) {
updateState({
customMethodCardFieldBlocksById: {
...(state.customMethodCardFieldBlocksById ?? {}),
[pendingCardId]: structuredClone(draftFieldBlocks ?? []),
},
});
} else if (pendingDraft) {
updateState({
replaceState((prev) => {
if (persistWizardBlocks) {
return {
...prev,
customMethodCardFieldBlocksById: {
...(prev.customMethodCardFieldBlocksById ?? {}),
[pendingCardId]: structuredClone(draftFieldBlocks ?? []),
},
};
}
if (!pendingDraft) {
return prev;
}
return {
...prev,
decisionApproachDetailsById: {
...(state.decisionApproachDetailsById ?? {}),
[pendingCardId]: pendingDraft,
...(prev.decisionApproachDetailsById ?? {}),
[pendingCardId]: structuredClone(pendingDraft),
},
});
}
if (pendingDraft) {
customizeSnapshotRef.current = captureMethodCardCustomizeSnapshot(
pendingDraft,
persistWizardBlocks ? draftFieldBlocks : null,
customizeSnapshotRef.current?.headerDraft ?? {
title: "",
description: "",
},
);
}
};
});
pendingEphemeralDuplicateIdRef.current = null;
customizeSnapshotRef.current = null;
void handleCreateModalClose();
return;
}
@@ -719,24 +676,25 @@ export function DecisionApproachesScreen() {
void handleCreateModalClose();
return;
}
updateState({
replaceState((prev) => ({
...prev,
selectedDecisionApproachIds: moveFacetSelectionIdToFront(
selectedIds,
prev.selectedDecisionApproachIds ?? [],
pendingCardId,
),
decisionApproachDetailsById: {
...(state.decisionApproachDetailsById ?? {}),
[pendingCardId]: pendingDraft,
...(prev.decisionApproachDetailsById ?? {}),
[pendingCardId]: structuredClone(pendingDraft),
},
...(persistWizardBlocks
? {
customMethodCardFieldBlocksById: {
...(state.customMethodCardFieldBlocksById ?? {}),
...(prev.customMethodCardFieldBlocksById ?? {}),
[pendingCardId]: structuredClone(draftFieldBlocks ?? []),
},
}
: {}),
});
}));
pendingEphemeralDuplicateIdRef.current = null;
customizeSnapshotRef.current = null;
void handleCreateModalClose();
@@ -747,9 +705,8 @@ export function DecisionApproachesScreen() {
modalUsesWizardFieldBlocksBody,
pendingCardId,
pendingDraft,
replaceState,
selectedIds,
state,
updateState,
]);
const modalConfig = pendingCardId
@@ -840,6 +797,7 @@ export function DecisionApproachesScreen() {
title={modalConfig.title}
description={modalConfig.description}
nextButtonText={modalConfig.nextButtonText}
showBackButton={false}
showNextButton={showMethodModalPrimary}
backdropVariant="blurredYellow"
kebabTriggerAriaLabel={modalKebabMenu.triggerAriaLabel}