Filter invalid style intents to preserve valid custom styles (#3241)

* Filter invalid style intents to preserve valid custom styles

* fix: preserve valid custom style rules

---------

Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
This commit is contained in:
Diego Vega Centeno
2026-07-09 08:50:34 -05:00
committed by GitHub
parent 9085a199cf
commit 689e7e24d4
2 changed files with 105 additions and 1 deletions
+64
View File
@@ -318,11 +318,75 @@ describe("summarySchema", () => {
});
describe("styleRulesSchema", () => {
const educationHeadingRule = {
id: "education-heading",
label: "Education heading",
enabled: true,
target: { scope: "sectionType", sectionType: "education" },
slots: { heading: { fontSize: 20 } },
};
it("defaults to an empty rule list on default resume metadata", () => {
expect(defaultResumeData.metadata.styleRules).toEqual([]);
expect(styleRulesSchema.safeParse(defaultResumeData.metadata.styleRules).success).toBe(true);
});
it("preserves valid rules when another rule has an invalid style intent", () => {
expect(
styleRulesSchema.parse([
educationHeadingRule,
{
id: "experience-heading",
label: "Experience heading",
enabled: true,
target: { scope: "sectionType", sectionType: "experience" },
slots: { heading: { fontSize: -1 } },
},
]),
).toEqual([educationHeadingRule]);
});
it("drops invalid style fields without dropping the whole rule", () => {
expect(
styleRulesSchema.parse([
{
id: "partial-heading",
label: "Partial heading",
enabled: true,
target: { scope: "global" },
slots: { heading: { color: "rgba(0, 0, 0, 1)", fontSize: -1 } },
},
]),
).toEqual([
{
id: "partial-heading",
label: "Partial heading",
enabled: true,
target: { scope: "global" },
slots: { heading: { color: "rgba(0, 0, 0, 1)" } },
},
]);
});
it("falls back to an empty rule list for non-array persisted values", () => {
expect(styleRulesSchema.parse({})).toEqual([]);
});
it("drops rules with unknown slot keys", () => {
expect(
styleRulesSchema.parse([
{
id: "unknown-slot",
label: "Unknown slot",
enabled: true,
target: { scope: "global" },
slots: { richListItemMarker: { color: "rgba(0, 0, 0, 1)" } },
},
educationHeadingRule,
]),
).toEqual([educationHeadingRule]);
});
it("accepts global, section-type, and section-id targets", () => {
const rules = [
{
+41 -1
View File
@@ -569,7 +569,47 @@ export const styleRuleSchema = z.strictObject({
slots: styleRuleSlotsSchema.describe("The semantic style slots configured by this rule."),
});
export const styleRulesSchema = z.array(styleRuleSchema).catch([]);
const filterStyleIntent = (intent: unknown): StyleIntent | undefined => {
const styleIntentShape = styleIntentSchema.shape;
if (typeof intent !== "object" || intent === null) return undefined;
const filteredIntent = Object.entries(intent).filter(([key, value]) => {
const fieldSchema = styleIntentSchema.shape[key as keyof typeof styleIntentShape];
if (!fieldSchema) return false;
return fieldSchema.safeParse(value).success;
});
return filteredIntent.length > 0 ? (Object.fromEntries(filteredIntent) as StyleIntent) : undefined;
};
export const styleRulesSchema = z
.array(z.unknown())
.transform((arr) =>
arr
.map((item) => {
const base = z
.strictObject({
id: z.string().min(1),
label: z.string().catch(""),
enabled: z.boolean().catch(true),
target: styleRuleTargetSchema,
slots: z.partialRecord(styleSlotSchema, z.unknown()),
})
.safeParse(item);
if (!base.success) return undefined;
const cleanedSlots = Object.fromEntries(
Object.entries(base.data.slots)
.map(([slot, intent]) => [slot, filterStyleIntent(intent)])
.filter((entry): entry is [string, StyleIntent] => entry[1] !== undefined),
);
if (Object.keys(cleanedSlots).length === 0) return undefined;
return { ...base.data, slots: cleanedSlots };
})
.filter((rule): rule is StyleRule => rule !== undefined),
)
.catch([]);
export type StyleRule = z.infer<typeof styleRuleSchema>;
export type StyleRuleTarget = z.infer<typeof styleRuleTargetSchema>;