diff --git a/packages/schema/src/resume/data.ts b/packages/schema/src/resume/data.ts index 91505dbea..e992c7da5 100644 --- a/packages/schema/src/resume/data.ts +++ b/packages/schema/src/resume/data.ts @@ -297,53 +297,28 @@ export const baseSectionSchema = z.object({ startOnNewPage: z.boolean().catch(false).describe("If true, the section always begins on a new page."), }); -const awardsSectionSchema = baseSectionSchema.extend({ - items: z.array(awardItemSchema).describe("The items to display in the awards section."), -}); +// ponytail: 12 identical baseSectionSchema.extend({ items }) blocks collapsed to a factory +const itemSection = (itemSchema: T, description: string) => + baseSectionSchema.extend({ items: z.array(itemSchema).describe(description) }); -const certificationsSectionSchema = baseSectionSchema.extend({ - items: z.array(certificationItemSchema).describe("The items to display in the certifications section."), -}); - -const educationSectionSchema = baseSectionSchema.extend({ - items: z.array(educationItemSchema).describe("The items to display in the education section."), -}); - -const experienceSectionSchema = baseSectionSchema.extend({ - items: z.array(experienceItemSchema).describe("The items to display in the experience section."), -}); - -const interestsSectionSchema = baseSectionSchema.extend({ - items: z.array(interestItemSchema).describe("The items to display in the interests section."), -}); - -const languagesSectionSchema = baseSectionSchema.extend({ - items: z.array(languageItemSchema).describe("The items to display in the languages section."), -}); - -const profilesSectionSchema = baseSectionSchema.extend({ - items: z.array(profileItemSchema).describe("The items to display in the profiles section."), -}); - -const projectsSectionSchema = baseSectionSchema.extend({ - items: z.array(projectItemSchema).describe("The items to display in the projects section."), -}); - -const publicationsSectionSchema = baseSectionSchema.extend({ - items: z.array(publicationItemSchema).describe("The items to display in the publications section."), -}); - -const referencesSectionSchema = baseSectionSchema.extend({ - items: z.array(referenceItemSchema).describe("The items to display in the references section."), -}); - -const skillsSectionSchema = baseSectionSchema.extend({ - items: z.array(skillItemSchema).describe("The items to display in the skills section."), -}); - -const volunteerSectionSchema = baseSectionSchema.extend({ - items: z.array(volunteerItemSchema).describe("The items to display in the volunteer section."), -}); +const awardsSectionSchema = itemSection(awardItemSchema, "The items to display in the awards section."); +const certificationsSectionSchema = itemSection( + certificationItemSchema, + "The items to display in the certifications section.", +); +const educationSectionSchema = itemSection(educationItemSchema, "The items to display in the education section."); +const experienceSectionSchema = itemSection(experienceItemSchema, "The items to display in the experience section."); +const interestsSectionSchema = itemSection(interestItemSchema, "The items to display in the interests section."); +const languagesSectionSchema = itemSection(languageItemSchema, "The items to display in the languages section."); +const profilesSectionSchema = itemSection(profileItemSchema, "The items to display in the profiles section."); +const projectsSectionSchema = itemSection(projectItemSchema, "The items to display in the projects section."); +const publicationsSectionSchema = itemSection( + publicationItemSchema, + "The items to display in the publications section.", +); +const referencesSectionSchema = itemSection(referenceItemSchema, "The items to display in the references section."); +const skillsSectionSchema = itemSection(skillItemSchema, "The items to display in the skills section."); +const volunteerSectionSchema = itemSection(volunteerItemSchema, "The items to display in the volunteer section."); const sectionsSchema = z.object({ profiles: profilesSectionSchema.describe("The section to display the profiles of the author."), @@ -573,24 +548,9 @@ const styleIntentSchema = z export type StyleIntent = z.infer; +// ponytail: 15 hand-listed optional slots collapsed to partialRecord; unknown keys still rejected const styleRuleSlotsSchema = z - .strictObject({ - section: styleIntentSchema.optional(), - heading: styleIntentSchema.optional(), - item: styleIntentSchema.optional(), - text: styleIntentSchema.optional(), - secondaryText: styleIntentSchema.optional(), - link: styleIntentSchema.optional(), - icon: styleIntentSchema.optional(), - level: styleIntentSchema.optional(), - richParagraph: styleIntentSchema.optional(), - richList: styleIntentSchema.optional(), - richListItemRow: styleIntentSchema.optional(), - richListItemContent: styleIntentSchema.optional(), - richLink: styleIntentSchema.optional(), - richBold: styleIntentSchema.optional(), - richMark: styleIntentSchema.optional(), - }) + .partialRecord(styleSlotSchema, styleIntentSchema) .refine((slots) => Object.values(slots).some(Boolean), { message: "At least one style slot must be configured.", }); diff --git a/packages/schema/src/resume/default.ts b/packages/schema/src/resume/default.ts index e20630c0a..88d29b2b6 100644 --- a/packages/schema/src/resume/default.ts +++ b/packages/schema/src/resume/default.ts @@ -1,5 +1,16 @@ import type { ResumeData } from "./data"; +// ponytail: 12 identical 7-field section blocks collapsed to a helper +const section = (icon: string) => ({ + title: "", + icon, + columns: 1, + hidden: false, + keepTogether: false, + startOnNewPage: false, + items: [] as never[], +}); + export const defaultResumeData: ResumeData = { picture: { hidden: false, @@ -32,114 +43,18 @@ export const defaultResumeData: ResumeData = { content: "", }, sections: { - profiles: { - title: "", - icon: "messenger-logo", - columns: 1, - hidden: false, - keepTogether: false, - startOnNewPage: false, - items: [], - }, - experience: { - title: "", - icon: "briefcase", - columns: 1, - hidden: false, - keepTogether: false, - startOnNewPage: false, - items: [], - }, - education: { - title: "", - icon: "graduation-cap", - columns: 1, - hidden: false, - keepTogether: false, - startOnNewPage: false, - items: [], - }, - projects: { - title: "", - icon: "code-simple", - columns: 1, - hidden: false, - keepTogether: false, - startOnNewPage: false, - items: [], - }, - skills: { - title: "", - icon: "compass-tool", - columns: 1, - hidden: false, - keepTogether: false, - startOnNewPage: false, - items: [], - }, - languages: { - title: "", - icon: "translate", - columns: 1, - hidden: false, - keepTogether: false, - startOnNewPage: false, - items: [], - }, - interests: { - title: "", - icon: "football", - columns: 1, - hidden: false, - keepTogether: false, - startOnNewPage: false, - items: [], - }, - awards: { - title: "", - icon: "trophy", - columns: 1, - hidden: false, - keepTogether: false, - startOnNewPage: false, - items: [], - }, - certifications: { - title: "", - icon: "certificate", - columns: 1, - hidden: false, - keepTogether: false, - startOnNewPage: false, - items: [], - }, - publications: { - title: "", - icon: "books", - columns: 1, - hidden: false, - keepTogether: false, - startOnNewPage: false, - items: [], - }, - volunteer: { - title: "", - icon: "hand-heart", - columns: 1, - hidden: false, - keepTogether: false, - startOnNewPage: false, - items: [], - }, - references: { - title: "", - icon: "phone", - columns: 1, - hidden: false, - keepTogether: false, - startOnNewPage: false, - items: [], - }, + profiles: section("messenger-logo"), + experience: section("briefcase"), + education: section("graduation-cap"), + projects: section("code-simple"), + skills: section("compass-tool"), + languages: section("translate"), + interests: section("football"), + awards: section("trophy"), + certifications: section("certificate"), + publications: section("books"), + volunteer: section("hand-heart"), + references: section("phone"), }, customSections: [], metadata: { diff --git a/packages/schema/src/templates.test.ts b/packages/schema/src/templates.test.ts deleted file mode 100644 index 3547dd6e8..000000000 --- a/packages/schema/src/templates.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { templateSchema } from "./templates"; - -describe("templateSchema", () => { - it("accepts known template names", () => { - const validTemplates = [ - "azurill", - "bronzor", - "chikorita", - "ditgar", - "ditto", - "gengar", - "glalie", - "kakuna", - "lapras", - "leafish", - "meowth", - "onyx", - "pikachu", - "rhyhorn", - "scizor", - ]; - for (const t of validTemplates) { - expect(templateSchema.safeParse(t).success).toBe(true); - } - }); - - it("rejects unknown template names", () => { - expect(templateSchema.safeParse("unknown").success).toBe(false); - expect(templateSchema.safeParse("").success).toBe(false); - expect(templateSchema.safeParse("ONYX").success).toBe(false); // case-sensitive - }); - - it("rejects non-string values", () => { - expect(templateSchema.safeParse(null).success).toBe(false); - expect(templateSchema.safeParse(undefined).success).toBe(false); - expect(templateSchema.safeParse(42).success).toBe(false); - }); - - it("returns the exact value for a valid template", () => { - const result = templateSchema.safeParse("onyx"); - expect(result.success).toBe(true); - if (result.success) { - expect(result.data).toBe("onyx"); - } - }); - - it("includes 14 templates", () => { - const validTemplates = templateSchema.options; - expect(validTemplates).toHaveLength(15); - }); -});