diff --git a/apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/page.tsx b/apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/page.tsx index a7bd21d69..06f6bdcbb 100644 --- a/apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/page.tsx +++ b/apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/page.tsx @@ -55,7 +55,12 @@ function PageSectionForm() { useSyncFormValues(form, page); const handleAutoSave = (name: K, value: FormValues[K]) => { - persist({ ...form.state.values, [name]: value }); + const next = { ...form.state.values, [name]: value }; + // Keep last-saved numeric page fields when the form holds a transient empty/NaN value + for (const key of ["marginX", "marginY", "gapX", "gapY"] as const) { + if (!Number.isFinite(next[key])) next[key] = page?.[key] ?? 0; + } + persist(next); }; const pageNumberFields = [ @@ -148,7 +153,7 @@ function PageSectionForm() { render={ { const v = e.target.value; - // Ignore transient empty/invalid input so resume metadata stays numeric - if (v === "") return; + if (v === "") { + // Allow clearing the controlled input without persisting invalid metadata + field.handleChange(Number.NaN); + return; + } const raw = Number(v); if (!Number.isFinite(raw)) return; diff --git a/packages/schema/src/resume/data.test.ts b/packages/schema/src/resume/data.test.ts index 6c1bfa392..70de04218 100644 --- a/packages/schema/src/resume/data.test.ts +++ b/packages/schema/src/resume/data.test.ts @@ -260,6 +260,19 @@ describe("pageSchema", () => { expect(pageSchema.safeParse(invalid).success).toBe(false); }); + it("falls back to default margins when out of range via .catch", () => { + const result = pageSchema.safeParse({ + ...defaultResumeData.metadata.page, + marginX: 1224, + marginY: 999, + }); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.marginX).toBe(14); + expect(result.data.marginY).toBe(12); + } + }); + it("falls back to 'a4' for unknown format via .catch", () => { const invalid = { ...defaultResumeData.metadata.page, format: "huge" }; const result = pageSchema.safeParse(invalid); diff --git a/packages/schema/src/resume/data.ts b/packages/schema/src/resume/data.ts index d1beb52a3..a06317d11 100644 --- a/packages/schema/src/resume/data.ts +++ b/packages/schema/src/resume/data.ts @@ -444,8 +444,8 @@ export const layoutSchema = z.object({ export const pageSchema = z.object({ gapX: z.number().min(0).describe("The horizontal gap between the sections of the page, defined in points (pt)."), gapY: z.number().min(0).describe("The vertical gap between the sections of the page, defined in points (pt)."), - marginX: z.number().min(0).max(100).describe("The horizontal margin of the page, defined in points (pt)."), - marginY: z.number().min(0).max(100).describe("The vertical margin of the page, defined in points (pt)."), + marginX: z.number().min(0).max(100).catch(14).describe("The horizontal margin of the page, defined in points (pt)."), + marginY: z.number().min(0).max(100).catch(12).describe("The vertical margin of the page, defined in points (pt)."), format: z .enum(["a4", "letter", "free-form"]) .describe("The format of the page. Can be 'a4', 'letter', or 'free-form'.")