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:
adilalloandCursor committed 2026-11-04 14:22:54 -07:00
1 parent 96ca15d8e3
commit bcbc6f30d9
89 files changed
+36555 -2828

No files matched your search

@@ -9,6 +9,12 @@ import {
loadFacetScores,
} from "../../../../lib/create/facetRecommendationsLoad";
import { useCreateFlow } from "../context/CreateFlowContext";
import { groupAnswersFromCreateFlowState } from "../../../../lib/create/groupAnswers";
import {
deckData,
recommendDeck,
} from "../../../../lib/create/deckRecommend";
import type { GroupAnswers } from "../../../../lib/create/deckTypes";
/**
* Card-deck section ids served by `/api/create-flow/methods` (CR-88 §9.2).
@@ -125,63 +131,46 @@ export function useFacetRecommendations(
}
/**
* Stable comparator for re-ranking a messages-file `methods[]` array. Higher
* `scoresBySlug[id]` first; ties fall back to authoring index, so a
* zero-facet user sees the original ordering verbatim.
* One card per job, then the display rule for the top `limit` cards.
* Cards marked every-rule carry no Recommended tag. With no facets, the
* first `limit` cards in deck order show and nothing is tagged.
*/
export function rankMethodsByScore<T extends { id: string }>(
methods: readonly T[],
scoresBySlug: Record<string, number>,
): T[] {
const indexById = new Map<string, number>();
methods.forEach((m, i) => indexById.set(m.id, i));
return [...methods].sort((a, b) => {
const sa = scoresBySlug[a.id] ?? 0;
const sb = scoresBySlug[b.id] ?? 0;
if (sa !== sb) return sb - sa;
return (indexById.get(a.id) ?? 0) - (indexById.get(b.id) ?? 0);
});
}
/**
* Picks (a) which method ids fill the compact card stack and (b) which of
* those should render with the "Recommended" tag. The messages JSON no
* longer carries a static `recommended` flag — both selections come
* entirely from facet scores (CR-88 §10).
*
* Behavior:
* - Facets selected & at least one method scored > 0 →
* `compactCardIds` = up to `limit` top-scored methods (1..limit cards;
* never padded with unrecommended fillers). All shown cards get the
* "Recommended" badge.
* - No facets selected, or every method scored 0 → `compactCardIds` =
* first `limit` in ranked/authoring order, `recommendedIds` empty (no
* badges shown — honest "no signal yet" fallback).
*
* `CardStack.view` is responsible for laying out variable-length compact
* arrays gracefully (uses `.map`/`.slice` and length-guarded indexing).
*/
export function deriveCompactCards<T extends { id: string }>(
rankedMethods: readonly T[],
scoresBySlug: Record<string, number>,
hasAnyFacets: boolean,
export function deriveCompactCards(
section: RecommendationSection,
answers: GroupAnswers,
limit: number,
): { compactCardIds: string[]; recommendedIds: Set<string> } {
const fallback = () => ({
compactCardIds: rankedMethods.slice(0, limit).map((m) => m.id),
recommendedIds: new Set<string>(),
});
if (!hasAnyFacets) return fallback();
const matched = rankedMethods.filter(
(m) => (scoresBySlug[m.id] ?? 0) > 0,
);
if (matched.length === 0) return fallback();
const top = matched.slice(0, limit);
): {
compactCardIds: string[];
recommendedIds: Set<string>;
orderedIds: string[];
} {
const recommendation = recommendDeck(deckData(section), answers, { limit });
const orderedIds = recommendation.fallback
? recommendation.seeAll
: [
...recommendation.shown.map((card) => card.id),
...recommendation.seeAll,
];
if (recommendation.fallback) {
return {
compactCardIds: orderedIds.slice(0, limit),
recommendedIds: new Set<string>(),
orderedIds,
};
}
return {
compactCardIds: top.map((m) => m.id),
recommendedIds: new Set(top.map((m) => m.id)),
compactCardIds: recommendation.shown.map((card) => card.id),
recommendedIds: new Set(
recommendation.recommended
.filter((card) => card.tag === "recommended")
.map((card) => card.id),
),
orderedIds,
};
}
export function groupAnswersForRecommendations(
state: Parameters<typeof groupAnswersFromCreateFlowState>[0],
): GroupAnswers {
return groupAnswersFromCreateFlowState(state);
}