Import the remade decks on stable ids and recommend one card per job, so a saved draft still loads after a chip is renamed.
The real-group profiles stay as a test, so a change that moves a pick fails. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
96ca15d8e3
commit
bcbc6f30d9
89 files changed
+36555
-2828
No files matched your search
@@ -0,0 +1,175 @@
|
||||
import type { CreateFlowState } from "../../app/(app)/create/types";
|
||||
import idAliasesFile from "../../data/create/customRule/_idAliases.json";
|
||||
import type { DeckId } from "./deckTypes";
|
||||
import { DECK_IDS } from "./deckTypes";
|
||||
|
||||
type MovedTo = { deck: string; id: string };
|
||||
type MethodAlias =
|
||||
| string
|
||||
| {
|
||||
target: string | null;
|
||||
movedTo?: MovedTo[];
|
||||
};
|
||||
|
||||
type IdAliasesFile = {
|
||||
methods: Record<string, Record<string, MethodAlias>>;
|
||||
coreValues: {
|
||||
oldPositionToNewId: Record<string, string | null>;
|
||||
labelMerges: Record<string, string | null>;
|
||||
};
|
||||
};
|
||||
|
||||
const aliases = idAliasesFile as IdAliasesFile;
|
||||
|
||||
function isDeckId(value: string): value is DeckId {
|
||||
return (DECK_IDS as readonly string[]).includes(value);
|
||||
}
|
||||
|
||||
export type ResolvedMethodId = { deck: DeckId; id: string };
|
||||
|
||||
/**
|
||||
* Old card id → new card id. A cut card follows `movedTo` when that card
|
||||
* lives in a deck. `null` means the card was cut and has nowhere to go.
|
||||
*/
|
||||
export function resolveMethodId(
|
||||
deck: DeckId,
|
||||
id: string,
|
||||
): ResolvedMethodId | null {
|
||||
const raw = aliases.methods[deck]?.[id];
|
||||
if (raw === undefined) return { deck, id };
|
||||
if (typeof raw === "string") return { deck, id: raw };
|
||||
if (typeof raw.target === "string" && raw.target.length > 0) {
|
||||
return { deck, id: raw.target };
|
||||
}
|
||||
const moved = raw.movedTo?.find((row) => isDeckId(row.deck));
|
||||
if (moved && isDeckId(moved.deck)) return { deck: moved.deck, id: moved.id };
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Old 1-based core-value position → stable id. `null` means the value was cut. */
|
||||
export function resolveCoreValueId(id: string): string | null {
|
||||
if (Object.prototype.hasOwnProperty.call(aliases.coreValues.oldPositionToNewId, id)) {
|
||||
return aliases.coreValues.oldPositionToNewId[id] ?? null;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
export function resolveCoreValueLabel(label: string): string | null {
|
||||
if (Object.prototype.hasOwnProperty.call(aliases.coreValues.labelMerges, label)) {
|
||||
return aliases.coreValues.labelMerges[label];
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
function remapIdList(
|
||||
ids: string[] | undefined,
|
||||
deck: DeckId,
|
||||
into: Map<DeckId, string[]>,
|
||||
): void {
|
||||
if (!ids) return;
|
||||
for (const id of ids) {
|
||||
const next = resolveMethodId(deck, id);
|
||||
if (!next) continue;
|
||||
const list = into.get(next.deck) ?? [];
|
||||
if (!list.includes(next.id)) list.push(next.id);
|
||||
into.set(next.deck, list);
|
||||
}
|
||||
}
|
||||
|
||||
const METHOD_ID_KEYS = {
|
||||
decisionApproaches: "selectedDecisionApproachIds",
|
||||
membership: "selectedMembershipMethodIds",
|
||||
conflictManagement: "selectedConflictManagementIds",
|
||||
communication: "selectedCommunicationMethodIds",
|
||||
} as const satisfies Record<DeckId, keyof CreateFlowState>;
|
||||
|
||||
const DETAIL_KEYS = {
|
||||
decisionApproaches: "decisionApproachDetailsById",
|
||||
membership: "membershipMethodDetailsById",
|
||||
conflictManagement: "conflictManagementDetailsById",
|
||||
communication: "communicationMethodDetailsById",
|
||||
} as const satisfies Record<DeckId, keyof CreateFlowState>;
|
||||
|
||||
/**
|
||||
* Remap saved method and core-value ids onto the remade decks.
|
||||
* Safe to run more than once: stable ids pass through.
|
||||
*/
|
||||
export function migrateSavedDeckIds(
|
||||
state: CreateFlowState,
|
||||
): CreateFlowState {
|
||||
const byDeck = new Map<DeckId, string[]>();
|
||||
for (const deck of DECK_IDS) {
|
||||
const key = METHOD_ID_KEYS[deck];
|
||||
const ids = state[key];
|
||||
remapIdList(Array.isArray(ids) ? (ids as string[]) : undefined, deck, byDeck);
|
||||
}
|
||||
|
||||
const detailsByDeck = new Map<DeckId, Record<string, unknown>>();
|
||||
for (const deck of DECK_IDS) {
|
||||
const key = DETAIL_KEYS[deck];
|
||||
const details = state[key];
|
||||
if (!details || typeof details !== "object") continue;
|
||||
for (const [id, value] of Object.entries(details as Record<string, unknown>)) {
|
||||
const resolved = resolveMethodId(deck, id);
|
||||
if (!resolved) continue;
|
||||
const next = detailsByDeck.get(resolved.deck) ?? {};
|
||||
if (next[resolved.id] === undefined) next[resolved.id] = value;
|
||||
detailsByDeck.set(resolved.deck, next);
|
||||
}
|
||||
}
|
||||
const detailsOut: Partial<CreateFlowState> = {};
|
||||
for (const deck of DECK_IDS) {
|
||||
if (!detailsByDeck.has(deck) && state[DETAIL_KEYS[deck]] === undefined) continue;
|
||||
(detailsOut as Record<string, unknown>)[DETAIL_KEYS[deck]] =
|
||||
detailsByDeck.get(deck) ?? {};
|
||||
}
|
||||
|
||||
const coreIds: string[] = [];
|
||||
const seenValues = new Set<string>();
|
||||
for (const id of state.selectedCoreValueIds ?? []) {
|
||||
const next = resolveCoreValueId(id);
|
||||
if (!next || seenValues.has(next)) continue;
|
||||
seenValues.add(next);
|
||||
coreIds.push(next);
|
||||
}
|
||||
|
||||
const coreDetails = state.coreValueDetailsByChipId;
|
||||
let nextCoreDetails = coreDetails;
|
||||
if (coreDetails) {
|
||||
nextCoreDetails = {};
|
||||
for (const [id, value] of Object.entries(coreDetails)) {
|
||||
const next = resolveCoreValueId(id);
|
||||
if (!next || nextCoreDetails[next]) continue;
|
||||
nextCoreDetails[next] = value;
|
||||
}
|
||||
}
|
||||
|
||||
const seenSnapshot = new Set<string>();
|
||||
const snapshot = state.coreValuesChipsSnapshot?.flatMap((row) => {
|
||||
const nextId = resolveCoreValueId(row.id);
|
||||
if (!nextId || seenSnapshot.has(nextId)) return [];
|
||||
seenSnapshot.add(nextId);
|
||||
const mergedLabel = resolveCoreValueLabel(row.label);
|
||||
if (mergedLabel === null) return [];
|
||||
return [{ ...row, id: nextId, label: mergedLabel }];
|
||||
});
|
||||
|
||||
const methodPatch: Partial<CreateFlowState> = {};
|
||||
for (const deck of DECK_IDS) {
|
||||
const key = METHOD_ID_KEYS[deck];
|
||||
if (state[key] !== undefined || byDeck.has(deck)) {
|
||||
(methodPatch as Record<string, unknown>)[key] = byDeck.get(deck) ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
...methodPatch,
|
||||
...detailsOut,
|
||||
...(state.selectedCoreValueIds
|
||||
? { selectedCoreValueIds: coreIds }
|
||||
: {}),
|
||||
...(snapshot ? { coreValuesChipsSnapshot: snapshot } : {}),
|
||||
...(nextCoreDetails ? { coreValueDetailsByChipId: nextCoreDetails } : {}),
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user