feat: add semantic CSS stylesheets (#3274)

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
Amruth Pillai
2026-07-30 12:39:15 +02:00
committed by GitHub
co-authored by Cursor Agent
parent 4ac19f81b3
commit d2ffbf9618
320 changed files with 78393 additions and 2915 deletions
+44 -30
View File
@@ -1,5 +1,6 @@
import z from "zod";
import { templateSchema } from "../templates";
import { semanticStylesheetSchema } from "./stylesheet";
const iconSchema = z
.string()
@@ -358,38 +359,47 @@ export const sectionTypeSchema = z.enum([
export type CustomSectionType = z.infer<typeof sectionTypeSchema>;
const customSectionItemSchema = z.union([
// coverLetterItemSchema must come before summaryItemSchema because both have 'content',
// but coverLetterItemSchema also requires 'recipient'. If summaryItemSchema is first,
// cover letter items will match it and lose the 'recipient' field.
coverLetterItemSchema,
summaryItemSchema,
profileItemSchema,
experienceItemSchema,
educationItemSchema,
projectItemSchema,
skillItemSchema,
languageItemSchema,
interestItemSchema,
awardItemSchema,
certificationItemSchema,
publicationItemSchema,
volunteerItemSchema,
referenceItemSchema,
// Correlation protects renderer requirements; it does not make otherwise-overlapping item shapes exclusive.
// Keep cover-letter before summary so the overlapping content shapes retain their established precedence.
export const customSectionItemDefinitionByType = {
"cover-letter": { schemaName: "coverLetterItemSchema", schema: coverLetterItemSchema.catchall(z.any()) },
summary: { schemaName: "summaryItemSchema", schema: summaryItemSchema.catchall(z.any()) },
profiles: { schemaName: "profileItemSchema", schema: profileItemSchema.catchall(z.any()) },
experience: { schemaName: "experienceItemSchema", schema: experienceItemSchema.catchall(z.any()) },
education: { schemaName: "educationItemSchema", schema: educationItemSchema.catchall(z.any()) },
projects: { schemaName: "projectItemSchema", schema: projectItemSchema.catchall(z.any()) },
skills: { schemaName: "skillItemSchema", schema: skillItemSchema.catchall(z.any()) },
languages: { schemaName: "languageItemSchema", schema: languageItemSchema.catchall(z.any()) },
interests: { schemaName: "interestItemSchema", schema: interestItemSchema.catchall(z.any()) },
awards: { schemaName: "awardItemSchema", schema: awardItemSchema.catchall(z.any()) },
certifications: { schemaName: "certificationItemSchema", schema: certificationItemSchema.catchall(z.any()) },
publications: { schemaName: "publicationItemSchema", schema: publicationItemSchema.catchall(z.any()) },
volunteer: { schemaName: "volunteerItemSchema", schema: volunteerItemSchema.catchall(z.any()) },
references: { schemaName: "referenceItemSchema", schema: referenceItemSchema.catchall(z.any()) },
} as const satisfies Record<CustomSectionType, { schemaName: string; schema: z.ZodType }>;
export type CustomSectionItem = z.infer<(typeof customSectionItemDefinitionByType)[CustomSectionType]["schema"]>;
const customSectionSchemaOptions = Object.entries(customSectionItemDefinitionByType).map(([type, { schema }]) =>
baseSectionSchema.extend({
id: z.string().describe("The unique identifier for the custom section. Usually generated as a UUID."),
type: z
.literal(type as CustomSectionType)
.describe("The type of items this custom section contains. Determines which item schema and form fields to use."),
items: z
.array(schema)
.describe("The items to display in the custom section. Items follow the schema of the section type."),
}),
);
const [firstCustomSectionSchema, ...remainingCustomSectionSchemas] = customSectionSchemaOptions;
if (!firstCustomSectionSchema) throw new Error("At least one custom section schema is required.");
export const customSectionSchema = z.discriminatedUnion("type", [
firstCustomSectionSchema,
...remainingCustomSectionSchemas,
]);
export type CustomSectionItem = z.infer<typeof customSectionItemSchema>;
export const customSectionSchema = baseSectionSchema.extend({
id: z.string().describe("The unique identifier for the custom section. Usually generated as a UUID."),
type: sectionTypeSchema.describe(
"The type of items this custom section contains. Determines which item schema and form fields to use.",
),
items: z
.array(customSectionItemSchema)
.describe("The items to display in the custom section. Items follow the schema of the section type."),
});
export type CustomSection = z.infer<typeof customSectionSchema>;
const customSectionsSchema = z.array(customSectionSchema);
@@ -638,6 +648,7 @@ export const metadataSchema = z.object({
styleRules: styleRulesSchema.describe(
"Structured style rules that target semantic resume sections and slots for React PDF rendering.",
),
stylesheet: semanticStylesheetSchema.optional(),
});
export const resumeDataSchema = z.looseObject({
@@ -656,6 +667,9 @@ export const resumeDataSchema = z.looseObject({
});
export type ResumeData = z.infer<typeof resumeDataSchema>;
export const parseResumeData = (data: unknown): ResumeData => resumeDataSchema.parse(data);
export type LayoutPage = z.infer<typeof pageLayoutSchema>;
export type Typography = z.infer<typeof typographySchema>;
export type Design = z.infer<typeof designSchema>;