diff --git a/apps/server/package.json b/apps/server/package.json index 27c98c2e8..33c910f3a 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -22,10 +22,10 @@ "@ai-sdk/google": "^3.0.79", "@ai-sdk/openai": "^3.0.65", "@ai-sdk/openai-compatible": "^2.0.48", - "@aws-sdk/client-s3": "^3.1053.0", + "@aws-sdk/client-s3": "^3.1054.0", "@better-auth/api-key": "^1.6.11", "@better-auth/drizzle-adapter": "^1.6.11", - "@better-auth/infra": "^0.2.8", + "@better-auth/infra": "^0.2.10", "@better-auth/oauth-provider": "^1.6.11", "@better-auth/passkey": "^1.6.11", "@hono/node-server": "^2.0.4", @@ -52,7 +52,7 @@ "better-auth": "1.6.11", "cjk-regex": "^3.4.0", "deepmerge-ts": "^7.1.5", - "dompurify": "^3.4.5", + "dompurify": "^3.4.6", "dotenv": "^17.4.2", "drizzle-orm": "1.0.0-rc.3", "drizzle-zod": "1.0.0-beta.14-a36c63d", @@ -61,12 +61,12 @@ "hono": "^4.12.23", "jsonrepair": "^3.14.0", "node-html-parser": "^7.1.0", - "nodemailer": "^8.0.8", + "nodemailer": "^8.0.9", "ollama-ai-provider-v2": "^3.5.1", "pg": "^8.21.0", "phosphor-icons-react-pdf": "^0.1.3", "react": "^19.2.6", - "react-email": "^6.3.3", + "react-email": "^6.4.0", "react-pdf-html": "^2.1.5", "resumable-stream": "^2.2.12", "sharp": "^0.34.5", @@ -80,8 +80,8 @@ "@types/node": "^25.9.1", "@types/pg": "^8.20.0", "@types/react": "^19.2.15", - "@typescript/native-preview": "7.0.0-dev.20260526.1", - "react-doctor": "^0.2.6", + "@typescript/native-preview": "7.0.0-dev.20260527.1", + "react-doctor": "^0.2.9", "tsdown": "^0.22.0", "tsx": "^4.22.3", "typescript": "^6.0.3", diff --git a/apps/web/package.json b/apps/web/package.json index 0518e98ef..cc87035e1 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -20,7 +20,7 @@ "@ai-sdk/react": "^3.0.193", "@base-ui/react": "^1.5.0", "@better-auth/api-key": "^1.6.11", - "@better-auth/infra": "^0.2.8", + "@better-auth/infra": "^0.2.10", "@better-auth/oauth-provider": "^1.6.11", "@better-auth/passkey": "^1.6.11", "@dnd-kit/core": "^6.3.1", @@ -44,7 +44,7 @@ "@reactive-resume/ui": "workspace:*", "@reactive-resume/utils": "workspace:*", "@tailwindcss/vite": "^4.3.0", - "@tanstack/react-form": "^1.32.0", + "@tanstack/react-form": "^1.32.1", "@tanstack/react-hotkeys": "^0.10.0", "@tanstack/react-query": "^5.100.14", "@tanstack/react-router": "^1.170.8", @@ -96,11 +96,11 @@ "@types/pg": "^8.20.0", "@types/react": "^19.2.15", "@types/react-dom": "^19.2.3", - "@typescript/native-preview": "7.0.0-dev.20260526.1", + "@typescript/native-preview": "7.0.0-dev.20260527.1", "@vitejs/plugin-react": "^6.0.2", "babel-plugin-macros": "^3.1.0", "babel-plugin-react-compiler": "^1.0.0", - "react-doctor": "^0.2.6", + "react-doctor": "^0.2.9", "typescript": "^6.0.3", "vite": "^8.0.14" } diff --git a/apps/web/src/libs/resume/section.tsx b/apps/web/src/libs/resume/section.tsx index 9de60c0b3..92e3753e5 100644 --- a/apps/web/src/libs/resume/section.tsx +++ b/apps/web/src/libs/resume/section.tsx @@ -21,6 +21,7 @@ import { LayoutIcon, MessengerLogoIcon, NotepadIcon, + PaintBrushBroadIcon, PaletteIcon, PhoneIcon, ReadCvLogoIcon, @@ -44,6 +45,7 @@ export type RightSidebarSection = | "layout" | "typography" | "design" + | "styles" | "page" | "notes" | "sharing" @@ -78,6 +80,7 @@ export const rightSidebarSections: RightSidebarSection[] = [ "layout", "typography", "design", + "styles", "page", "notes", "sharing", @@ -116,6 +119,7 @@ export const getSectionTitle = (type: SidebarSection | CustomOnlyType): string = .with("layout", () => t`Layout`) .with("typography", () => t`Typography`) .with("design", () => t`Design`) + .with("styles", () => t`Custom Styles`) .with("page", () => t`Page`) .with("notes", () => t`Notes`) .with("sharing", () => t`Sharing`) @@ -159,6 +163,7 @@ export const getSectionIcon = (type: SidebarSection | CustomOnlyType, props?: Ic .with("layout", () => ) .with("typography", () => ) .with("design", () => ) + .with("styles", () => ) .with("page", () => ) .with("notes", () => ) .with("sharing", () => ) diff --git a/apps/web/src/routes/builder/$resumeId/-sidebar/right/index.tsx b/apps/web/src/routes/builder/$resumeId/-sidebar/right/index.tsx index e98722c6c..0957a200f 100644 --- a/apps/web/src/routes/builder/$resumeId/-sidebar/right/index.tsx +++ b/apps/web/src/routes/builder/$resumeId/-sidebar/right/index.tsx @@ -17,6 +17,7 @@ import { PageSectionBuilder } from "./sections/page"; import { ResumeAnalysisSectionBuilder } from "./sections/resume-analysis"; import { SharingSectionBuilder } from "./sections/sharing"; import { StatisticsSectionBuilder } from "./sections/statistics"; +import { StylesSectionBuilder } from "./sections/styles"; import { TemplateSectionBuilder } from "./sections/template"; import { TypographySectionBuilder } from "./sections/typography"; @@ -26,6 +27,7 @@ function getSectionComponent(type: RightSidebarSection) { .with("layout", () => ) .with("typography", () => ) .with("design", () => ) + .with("styles", () => ) .with("page", () => ) .with("notes", () => ) .with("sharing", () => ) diff --git a/apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/styles.test.tsx b/apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/styles.test.tsx new file mode 100644 index 000000000..af5a01318 --- /dev/null +++ b/apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/styles.test.tsx @@ -0,0 +1,322 @@ +// @vitest-environment happy-dom + +import { fireEvent, render, screen, within } from "@testing-library/react"; +import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { i18n } from "@lingui/core"; +import { I18nProvider } from "@lingui/react"; +import { isValidElement } from "react"; + +const updateResumeData = vi.hoisted(() => vi.fn()); +const styleRules = vi.hoisted(() => [ + { + id: "style-global-heading", + label: "All sections: Section heading", + enabled: true, + target: { scope: "global" }, + slots: { heading: { color: "rgba(220, 38, 38, 1)" } }, + }, +]); + +vi.mock("../shared/section-base", () => ({ + SectionBase: ({ children }: { children: React.ReactNode }) =>
{children}
, +})); + +vi.mock("@/features/resume/builder/draft", () => ({ + useCurrentResume: () => ({ + data: { + metadata: { styleRules }, + sections: { + experience: { title: "Experience" }, + skills: { title: "Skills" }, + }, + customSections: [{ id: "custom-1", title: "Open Source", type: "projects" }], + }, + }), + useUpdateResumeData: () => updateResumeData, +})); + +const { StylesSectionBuilder } = await import("./styles"); +const { getSectionIcon, getSectionTitle } = await import("@/libs/resume/section"); + +beforeAll(() => { + i18n.loadAndActivate({ locale: "en", messages: {} }); +}); + +beforeEach(() => { + updateResumeData.mockClear(); +}); + +const renderStyles = () => + render( + + + , + ); + +describe("StylesSectionBuilder", () => { + beforeEach(() => { + styleRules.splice(0, styleRules.length); + styleRules.push({ + id: "style-global-heading", + label: "All sections: Section heading", + enabled: true, + target: { scope: "global" }, + slots: { heading: { color: "rgba(220, 38, 38, 1)" } }, + }); + }); + + it("renders structured style rule controls", () => { + renderStyles(); + + expect(screen.getByLabelText("Target Scope")).toBeInTheDocument(); + expect(screen.getByLabelText("Style Slot")).toBeInTheDocument(); + expect(screen.getByLabelText("Text Color")).toBeInTheDocument(); + expect(screen.getByLabelText("Text Decoration Color")).toBeInTheDocument(); + expect(screen.getByRole("heading", { name: "Color" })).toBeInTheDocument(); + expect(screen.getByRole("heading", { name: "Text" })).toBeInTheDocument(); + expect(screen.getByRole("heading", { name: "Spacing" })).toBeInTheDocument(); + expect(screen.getByRole("heading", { name: "Border" })).toBeInTheDocument(); + expect(screen.getByLabelText("Font Style")).toBeInTheDocument(); + expect(screen.getByLabelText("Line Height")).toBeInTheDocument(); + expect(screen.getByLabelText("Letter Spacing")).toBeInTheDocument(); + expect(screen.getByLabelText("Text Decoration")).toBeInTheDocument(); + expect(screen.getByLabelText("Decoration Style")).toBeInTheDocument(); + expect(screen.getByLabelText("Text Align")).toBeInTheDocument(); + expect(screen.getByLabelText("Text Transform")).toBeInTheDocument(); + expect(screen.getByLabelText("Opacity")).toBeInTheDocument(); + expect(screen.getByLabelText("Margin Top")).toBeInTheDocument(); + expect(screen.getByLabelText("Margin Right")).toBeInTheDocument(); + expect(screen.getByLabelText("Margin Bottom")).toBeInTheDocument(); + expect(screen.getByLabelText("Margin Left")).toBeInTheDocument(); + expect(screen.getByLabelText("Row Gap")).toBeInTheDocument(); + expect(screen.getByLabelText("Column Gap")).toBeInTheDocument(); + expect(screen.getByLabelText("Border Style")).toBeInTheDocument(); + expect(screen.getByRole("option", { name: "Section heading" })).toBeInTheDocument(); + expect(screen.getByRole("option", { name: "List" })).toBeInTheDocument(); + expect(screen.getByRole("option", { name: "List item content" })).toBeInTheDocument(); + expect(screen.queryByRole("option", { name: "Bullet or number" })).not.toBeInTheDocument(); + }); + + it("labels the empty font weight option as default", () => { + renderStyles(); + + const fontWeightSelect = screen.getByLabelText("Font Weight"); + expect(within(fontWeightSelect).getByRole("option", { name: "Default" })).toBeInTheDocument(); + expect(within(fontWeightSelect).queryByRole("option", { name: "Template default" })).not.toBeInTheDocument(); + }); + + it("renames the sidebar entry and uses a distinct icon from design", () => { + const designIcon = getSectionIcon("design"); + const stylesIcon = getSectionIcon("styles"); + + expect(getSectionTitle("styles")).toBe("Custom Styles"); + expect(isValidElement(designIcon)).toBe(true); + expect(isValidElement(stylesIcon)).toBe(true); + expect(isValidElement(designIcon) && isValidElement(stylesIcon) && designIcon.type !== stylesIcon.type).toBe(true); + }); + + it("upserts one style rule for the selected target and slot", () => { + styleRules.splice(0, styleRules.length); + renderStyles(); + + fireEvent.change(screen.getByLabelText("Text Color"), { target: { value: "rgba(220, 38, 38, 1)" } }); + + expect(updateResumeData).toHaveBeenCalledTimes(1); + const recipe = updateResumeData.mock.calls[0]?.[0] as (draft: { metadata: { styleRules: unknown[] } }) => void; + const draft = { metadata: { styleRules: [] } }; + recipe(draft); + + expect(draft.metadata.styleRules).toEqual([ + { + id: "style-global-heading", + label: "All sections: Section heading", + enabled: true, + target: { scope: "global" }, + slots: { heading: { color: "rgba(220, 38, 38, 1)" } }, + }, + ]); + }); + + it("stores padding as per-side values", () => { + styleRules.splice(0, styleRules.length); + renderStyles(); + + expect(screen.queryByLabelText("Padding")).not.toBeInTheDocument(); + expect(screen.getByLabelText("Padding Top")).toBeInTheDocument(); + expect(screen.getByLabelText("Padding Right")).toBeInTheDocument(); + expect(screen.getByLabelText("Padding Bottom")).toBeInTheDocument(); + expect(screen.getByLabelText("Padding Left")).toBeInTheDocument(); + + fireEvent.change(screen.getByLabelText("Padding Top"), { target: { value: "12" } }); + + expect(updateResumeData).toHaveBeenCalledTimes(1); + const recipe = updateResumeData.mock.calls[0]?.[0] as (draft: { metadata: { styleRules: unknown[] } }) => void; + const draft = { metadata: { styleRules: [] } }; + recipe(draft); + + expect(draft.metadata.styleRules).toEqual([ + { + id: "style-global-heading", + label: "All sections: Section heading", + enabled: true, + target: { scope: "global" }, + slots: { heading: { paddingTop: 12 } }, + }, + ]); + }); + + it("stores text decoration intent", () => { + styleRules.splice(0, styleRules.length); + renderStyles(); + + fireEvent.change(screen.getByLabelText("Text Decoration"), { target: { value: "underline" } }); + + expect(updateResumeData).toHaveBeenCalledTimes(1); + const recipe = updateResumeData.mock.calls[0]?.[0] as (draft: { metadata: { styleRules: unknown[] } }) => void; + const draft = { metadata: { styleRules: [] } }; + recipe(draft); + + expect(draft.metadata.styleRules).toEqual([ + { + id: "style-global-heading", + label: "All sections: Section heading", + enabled: true, + target: { scope: "global" }, + slots: { heading: { textDecoration: "underline" } }, + }, + ]); + }); + + it("stores margin and gap intent", () => { + styleRules.splice(0, styleRules.length); + renderStyles(); + + expect(screen.getByLabelText("Margin Bottom")).toHaveAttribute("min", "-72"); + expect(screen.getByLabelText("Row Gap")).toHaveAttribute("min", "-72"); + + fireEvent.change(screen.getByLabelText("Margin Bottom"), { target: { value: "-10" } }); + fireEvent.change(screen.getByLabelText("Row Gap"), { target: { value: "-6" } }); + + expect(updateResumeData).toHaveBeenCalledTimes(2); + + const marginRecipe = updateResumeData.mock.calls[0]?.[0] as (draft: { + metadata: { styleRules: unknown[] }; + }) => void; + const marginDraft = { metadata: { styleRules: [] } }; + marginRecipe(marginDraft); + + expect(marginDraft.metadata.styleRules).toEqual([ + { + id: "style-global-heading", + label: "All sections: Section heading", + enabled: true, + target: { scope: "global" }, + slots: { heading: { marginBottom: -10 } }, + }, + ]); + + const gapRecipe = updateResumeData.mock.calls[1]?.[0] as (draft: { metadata: { styleRules: unknown[] } }) => void; + const gapDraft = { metadata: { styleRules: [] } }; + gapRecipe(gapDraft); + + expect(gapDraft.metadata.styleRules).toEqual([ + { + id: "style-global-heading", + label: "All sections: Section heading", + enabled: true, + target: { scope: "global" }, + slots: { heading: { rowGap: -6 } }, + }, + ]); + }); + + it("stores list slot rules for rich text lists", () => { + styleRules.splice(0, styleRules.length); + renderStyles(); + + fireEvent.change(screen.getByLabelText("Style Slot"), { target: { value: "richList" } }); + fireEvent.change(screen.getByLabelText("Row Gap"), { target: { value: "8" } }); + + expect(updateResumeData).toHaveBeenCalledTimes(1); + const recipe = updateResumeData.mock.calls[0]?.[0] as (draft: { metadata: { styleRules: unknown[] } }) => void; + const draft = { metadata: { styleRules: [] } }; + recipe(draft); + + expect(draft.metadata.styleRules).toEqual([ + { + id: "style-global-richList", + label: "All sections: List", + enabled: true, + target: { scope: "global" }, + slots: { richList: { rowGap: 8 } }, + }, + ]); + }); + + it("can reset the selected style rule", () => { + renderStyles(); + + fireEvent.click(screen.getByRole("button", { name: "Reset Style" })); + + expect(updateResumeData).toHaveBeenCalledTimes(1); + const recipe = updateResumeData.mock.calls[0]?.[0] as (draft: { + metadata: { styleRules: { id: string }[] }; + }) => void; + const draft = { + metadata: { + styleRules: [ + { + id: "style-global-heading", + label: "All sections: Section heading", + enabled: true, + target: { scope: "global" }, + slots: { heading: { color: "rgba(0, 0, 0, 1)" } }, + }, + { + id: "style-global-section", + label: "All sections: Section container", + enabled: true, + target: { scope: "global" }, + slots: { section: { padding: 4 } }, + }, + ], + }, + }; + recipe(draft); + + expect(draft.metadata.styleRules.map((rule) => rule.id)).toEqual(["style-global-section"]); + }); + + it("lists applied style rules and toggles individual rules", () => { + renderStyles(); + + expect(screen.getByText("Applied Rules")).toBeInTheDocument(); + expect(screen.getByText("All sections: Section heading")).toBeInTheDocument(); + + fireEvent.click(screen.getByLabelText("Toggle All sections: Section heading")); + + expect(updateResumeData).toHaveBeenCalledTimes(1); + const recipe = updateResumeData.mock.calls[0]?.[0] as (draft: { + metadata: { styleRules: { id: string; enabled: boolean }[] }; + }) => void; + const draft = { metadata: { styleRules: [{ id: "style-global-heading", enabled: true }] } }; + recipe(draft); + + expect(draft.metadata.styleRules[0]?.enabled).toBe(false); + }); + + it("opens a manage rules dialog with editable rule fields", () => { + renderStyles(); + + fireEvent.click(screen.getByRole("button", { name: "Manage Rules" })); + + expect(screen.getByRole("dialog")).toBeInTheDocument(); + expect(screen.getByText("Manage Style Rules")).toBeInTheDocument(); + expect(screen.getByLabelText("Rule Label")).toHaveValue("All sections: Section heading"); + expect(screen.getByLabelText("Dialog Text Color")).toHaveValue("rgba(220, 38, 38, 1)"); + expect(screen.getByLabelText("Dialog Padding Top")).toBeInTheDocument(); + expect(screen.getByLabelText("Dialog Padding Right")).toBeInTheDocument(); + expect(screen.getByLabelText("Dialog Padding Bottom")).toBeInTheDocument(); + expect(screen.getByLabelText("Dialog Padding Left")).toBeInTheDocument(); + }); +}); diff --git a/apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/styles.tsx b/apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/styles.tsx new file mode 100644 index 000000000..6c5d7af27 --- /dev/null +++ b/apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/styles.tsx @@ -0,0 +1,1159 @@ +import type { + ResumeData, + StyleIntent, + StyleRule, + StyleRuleTarget, + StyleSlot, +} from "@reactive-resume/schema/resume/data"; +import type { ComponentProps, ReactNode } from "react"; +import { Trans } from "@lingui/react/macro"; +import { SlidersHorizontalIcon, TrashSimpleIcon } from "@phosphor-icons/react"; +import { useMemo, useState } from "react"; +import { sectionTypeSchema } from "@reactive-resume/schema/resume/data"; +import { Badge } from "@reactive-resume/ui/components/badge"; +import { Button } from "@reactive-resume/ui/components/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@reactive-resume/ui/components/dialog"; +import { Input } from "@reactive-resume/ui/components/input"; +import { Label } from "@reactive-resume/ui/components/label"; +import { Separator } from "@reactive-resume/ui/components/separator"; +import { Switch } from "@reactive-resume/ui/components/switch"; +import { cn } from "@reactive-resume/utils/style"; +import { ColorPicker } from "@/components/input/color-picker"; +import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft"; +import { getSectionTitle } from "@/libs/resume/section"; +import { SectionBase } from "../shared/section-base"; + +type TargetScope = StyleRuleTarget["scope"]; + +type StyleSlotOption = { + value: StyleSlot; + label: string; + group: "Section" | "Rich text"; +}; + +const styleSlotOptions: StyleSlotOption[] = [ + { value: "section", label: "Section container", group: "Section" }, + { value: "heading", label: "Section heading", group: "Section" }, + { value: "item", label: "Item container", group: "Section" }, + { value: "text", label: "Primary text", group: "Section" }, + { value: "secondaryText", label: "Secondary text", group: "Section" }, + { value: "link", label: "Link", group: "Section" }, + { value: "icon", label: "Icon", group: "Section" }, + { value: "level", label: "Level indicator", group: "Section" }, + { value: "richParagraph", label: "Paragraph", group: "Rich text" }, + { value: "richList", label: "List", group: "Rich text" }, + { value: "richListItemRow", label: "List item row", group: "Rich text" }, + { value: "richListItemContent", label: "List item content", group: "Rich text" }, + { value: "richLink", label: "Inline link", group: "Rich text" }, + { value: "richBold", label: "Bold text", group: "Rich text" }, + { value: "richMark", label: "Highlight", group: "Rich text" }, +]; + +const groupedStyleSlotOptions: [StyleSlotOption["group"], StyleSlotOption[]][] = [ + ["Section", styleSlotOptions.filter((option) => option.group === "Section")], + ["Rich text", styleSlotOptions.filter((option) => option.group === "Rich text")], +]; + +const fontWeightOptions = ["100", "200", "300", "400", "500", "600", "700", "800", "900"] as const; +const fontStyleOptions = [ + { value: "normal", label: "Normal" }, + { value: "italic", label: "Italic" }, +] as const satisfies readonly { value: NonNullable; label: string }[]; +const textDecorationOptions = [ + { value: "none", label: "None" }, + { value: "underline", label: "Underline" }, + { value: "line-through", label: "Line through" }, +] as const satisfies readonly { value: NonNullable; label: string }[]; +const textDecorationStyleOptions = [ + { value: "solid", label: "Solid" }, + { value: "dashed", label: "Dashed" }, + { value: "dotted", label: "Dotted" }, +] as const satisfies readonly { value: NonNullable; label: string }[]; +const textAlignOptions = [ + { value: "left", label: "Left" }, + { value: "center", label: "Center" }, + { value: "right", label: "Right" }, + { value: "justify", label: "Justify" }, +] as const satisfies readonly { value: NonNullable; label: string }[]; +const textTransformOptions = [ + { value: "none", label: "None" }, + { value: "uppercase", label: "Uppercase" }, + { value: "lowercase", label: "Lowercase" }, + { value: "capitalize", label: "Capitalize" }, +] as const satisfies readonly { value: NonNullable; label: string }[]; +const borderStyleOptions = [ + { value: "solid", label: "Solid" }, + { value: "dashed", label: "Dashed" }, + { value: "dotted", label: "Dotted" }, +] as const satisfies readonly { value: NonNullable; label: string }[]; + +export function StylesSectionBuilder() { + return ( + + + + ); +} + +function StylesSectionForm() { + const resume = useCurrentResume(); + const data = resume.data; + const updateResumeData = useUpdateResumeData(); + const sectionOptions = useMemo(() => getSectionIdOptions(data), [data]); + const styleRules = data.metadata.styleRules ?? []; + + const [targetScope, setTargetScope] = useState("global"); + const [sectionType, setSectionType] = useState("summary"); + const [sectionId, setSectionId] = useState("summary"); + const [slot, setSlot] = useState("heading"); + const [isManageDialogOpen, setManageDialogOpen] = useState(false); + + const target = createTarget({ targetScope, sectionType, sectionId }); + const ruleId = getStyleRuleId(target, slot); + const currentRule = styleRules.find((rule) => rule.id === ruleId); + const currentIntent = currentRule?.slots[slot] ?? {}; + const targetLabel = getTargetLabel(data, target); + const slotLabel = getSlotLabel(slot); + + const upsertIntent = (patch: Partial) => { + const nextIntent = compactIntent({ ...currentIntent, ...patch }); + + updateResumeData((draft) => { + draft.metadata.styleRules ??= []; + const rules = draft.metadata.styleRules; + const existingIndex = rules.findIndex((rule) => rule.id === ruleId); + const existingRule = rules[existingIndex]; + + if (Object.keys(nextIntent).length === 0) { + if (existingIndex >= 0) rules.splice(existingIndex, 1); + return; + } + + const nextRule: StyleRule = { + id: ruleId, + label: existingRule?.label || `${targetLabel}: ${slotLabel}`, + enabled: existingRule?.enabled ?? true, + target, + slots: { [slot]: nextIntent }, + }; + + if (existingIndex >= 0) rules[existingIndex] = nextRule; + else rules.push(nextRule); + }); + }; + + const resetRule = () => { + updateResumeData((draft) => { + draft.metadata.styleRules = (draft.metadata.styleRules ?? []).filter((rule) => rule.id !== ruleId); + }); + }; + + const updateRuleEnabled = (ruleId: string, enabled: boolean) => { + updateResumeData((draft) => { + const rule = (draft.metadata.styleRules ?? []).find((rule) => rule.id === ruleId); + if (rule) rule.enabled = enabled; + }); + }; + + const updateRuleLabel = (ruleId: string, label: string) => { + updateResumeData((draft) => { + const rule = (draft.metadata.styleRules ?? []).find((rule) => rule.id === ruleId); + if (rule) rule.label = label; + }); + }; + + const updateRuleIntent = (ruleId: string, slot: StyleSlot, patch: Partial) => { + updateResumeData((draft) => { + const rules = draft.metadata.styleRules ?? []; + const ruleIndex = rules.findIndex((rule) => rule.id === ruleId); + const rule = rules[ruleIndex]; + if (!rule) return; + + const nextIntent = compactIntent({ ...(rule.slots[slot] ?? {}), ...patch }); + if (Object.keys(nextIntent).length === 0) delete rule.slots[slot]; + else rule.slots[slot] = nextIntent; + + if (getConfiguredSlots(rule).length === 0) rules.splice(ruleIndex, 1); + }); + }; + + const deleteRule = (ruleId: string) => { + updateResumeData((draft) => { + draft.metadata.styleRules = (draft.metadata.styleRules ?? []).filter((rule) => rule.id !== ruleId); + }); + }; + + return ( +
+
+ + + + + {targetScope === "sectionType" && ( + + + + )} + + {targetScope === "sectionId" && ( + + + + )} + + + + +
+ + + + + +
+ +
+ + + + setManageDialogOpen(true)} + /> + + +
+ ); +} + +function Field({ label, id, children }: { label: string; id: string; children: ReactNode }) { + return ( +
+ + {children} +
+ ); +} + +function Select({ className, ...props }: ComponentProps<"select">) { + return ( + onChange(event.target.value.trim() || undefined)} + /> + + + ); +} + +function NumberInput({ + label, + id, + value, + min, + max, + step = 1, + onChange, +}: { + label: string; + id?: string; + value: number | undefined; + min: number; + max: number; + step?: number; + onChange: (value: number | undefined) => void; +}) { + const inputId = id ?? `style-${label.toLowerCase().replaceAll(" ", "-")}`; + + return ( + + { + const value = event.target.value; + onChange(value === "" ? undefined : Number(value)); + }} + /> + + ); +} + +function AppliedRulesList({ + data, + rules, + onToggleRule, + onDeleteRule, + onManageRules, +}: { + data: ResumeData; + rules: StyleRule[]; + onToggleRule: (ruleId: string, enabled: boolean) => void; + onDeleteRule: (ruleId: string) => void; + onManageRules: () => void; +}) { + return ( +
+
+
+

+ Applied Rules +

+

+ {rules.length} {rules.length === 1 ? rule : rules} +

+
+ + +
+ + {rules.length === 0 ? ( +
+ No style rules yet. +
+ ) : ( +
+ {rules.map((rule) => ( + + ))} +
+ )} +
+ ); +} + +function AppliedRuleCard({ + data, + rule, + onToggleRule, + onDeleteRule, +}: { + data: ResumeData; + rule: StyleRule; + onToggleRule: (ruleId: string, enabled: boolean) => void; + onDeleteRule: (ruleId: string) => void; +}) { + const slots = getConfiguredSlots(rule); + const primaryIntent = slots[0] ? rule.slots[slots[0]] : undefined; + const fallbackLabel = getRuleFallbackLabel(data, rule); + + return ( +
+
+
+
+ {rule.label || fallbackLabel} + {!rule.enabled && ( + + Off + + )} +
+ +
+ {getTargetLabel(data, rule.target)} + {slots.map((slot) => ( + + {getSlotLabel(slot)} + + ))} +
+ + {primaryIntent && } +
+ +
+ onToggleRule(rule.id, checked)} + /> + +
+
+
+ ); +} + +function ManageStyleRulesDialog({ + data, + rules, + open, + onOpenChange, + onToggleRule, + onUpdateRuleLabel, + onUpdateRuleIntent, + onDeleteRule, +}: { + data: ResumeData; + rules: StyleRule[]; + open: boolean; + onOpenChange: (open: boolean) => void; + onToggleRule: (ruleId: string, enabled: boolean) => void; + onUpdateRuleLabel: (ruleId: string, label: string) => void; + onUpdateRuleIntent: (ruleId: string, slot: StyleSlot, patch: Partial) => void; + onDeleteRule: (ruleId: string) => void; +}) { + return ( + + + + + Manage Style Rules + + + Review and edit the style rules saved on this resume. + + + + {rules.length === 0 ? ( +
+ No style rules yet. +
+ ) : ( +
+ {rules.map((rule) => ( + + ))} +
+ )} +
+
+ ); +} + +function ManagedRuleCard({ + data, + rule, + onToggleRule, + onUpdateRuleLabel, + onUpdateRuleIntent, + onDeleteRule, +}: { + data: ResumeData; + rule: StyleRule; + onToggleRule: (ruleId: string, enabled: boolean) => void; + onUpdateRuleLabel: (ruleId: string, label: string) => void; + onUpdateRuleIntent: (ruleId: string, slot: StyleSlot, patch: Partial) => void; + onDeleteRule: (ruleId: string) => void; +}) { + const slots = getConfiguredSlots(rule); + const fallbackLabel = getRuleFallbackLabel(data, rule); + const labelId = `style-dialog-${slugify(rule.id)}-label`; + + return ( +
+
+
+ + onUpdateRuleLabel(rule.id, event.target.value)} + /> + + +
+ {getTargetLabel(data, rule.target)} + {slots.map((slot) => ( + + {getSlotLabel(slot)} + + ))} +
+
+ +
+ onToggleRule(rule.id, checked)} + /> + +
+
+ + {slots.map((slot) => ( + onUpdateRuleIntent(rule.id, slot, patch)} + /> + ))} +
+ ); +} + +function RuleIntentEditor({ + idPrefix, + intent, + labelPrefix, + onChange, +}: { + idPrefix: string; + intent: StyleIntent; + labelPrefix?: string; + onChange: (patch: Partial) => void; +}) { + const labelStart = labelPrefix ? `${labelPrefix} ` : ""; + + return ( +
+ +
+ onChange({ color })} + /> + onChange({ backgroundColor })} + /> + onChange({ textDecorationColor })} + /> + onChange({ opacity })} + /> +
+
+ + +
+ onChange({ fontSize })} + /> + onChange({ fontWeight })} + /> + onChange({ fontStyle })} + /> + onChange({ lineHeight })} + /> + onChange({ letterSpacing })} + /> + onChange({ textDecoration })} + /> + onChange({ textDecorationStyle })} + /> + onChange({ textAlign })} + /> + onChange({ textTransform })} + /> +
+
+ + +
+ + + + + + + +
+ onChange({ rowGap })} + /> + onChange({ columnGap })} + /> +
+
+
+
+ + +
+ onChange({ borderStyle })} + /> + onChange({ borderWidth })} + /> + onChange({ borderRadius })} + /> + onChange({ borderColor })} + /> +
+
+
+ ); +} + +function ControlPanel({ title, children }: { title: string; children: ReactNode }) { + return ( +
+

{title}

+ {children} +
+ ); +} + +function ControlSubsection({ title, children }: { title: string; children: ReactNode }) { + return ( +
+
{title}
+ {children} +
+ ); +} + +function IntentSelectField({ + label, + id, + value, + options, + onChange, +}: { + label: string; + id: string; + value: TValue | undefined; + options: readonly { value: TValue; label: string }[]; + onChange: (value: TValue | undefined) => void; +}) { + return ( + + + + ); +} + +function FontWeightField({ + label, + id, + value, + onChange, +}: { + label: string; + id: string; + value: StyleIntent["fontWeight"] | undefined; + onChange: (value: StyleIntent["fontWeight"] | undefined) => void; +}) { + return ( + + + + ); +} + +const paddingSideOptions = [ + { property: "paddingTop", label: "Top" }, + { property: "paddingRight", label: "Right" }, + { property: "paddingBottom", label: "Bottom" }, + { property: "paddingLeft", label: "Left" }, +] as const; + +type PaddingSideProperty = (typeof paddingSideOptions)[number]["property"]; + +const marginSideOptions = [ + { property: "marginTop", label: "Top" }, + { property: "marginRight", label: "Right" }, + { property: "marginBottom", label: "Bottom" }, + { property: "marginLeft", label: "Left" }, +] as const; + +type MarginSideProperty = (typeof marginSideOptions)[number]["property"]; + +function PaddingSideInputs({ + idPrefix, + intent, + labelPrefix, + onChange, +}: { + idPrefix: string; + intent: StyleIntent; + labelPrefix?: string; + onChange: (patch: Partial) => void; +}) { + const labelStart = labelPrefix ? `${labelPrefix} ` : ""; + + return ( +
+ {paddingSideOptions.map((side) => ( + onChange(createPaddingSidePatch(intent, side.property, value))} + /> + ))} +
+ ); +} + +function MarginSideInputs({ + idPrefix, + intent, + labelPrefix, + onChange, +}: { + idPrefix: string; + intent: StyleIntent; + labelPrefix?: string; + onChange: (patch: Partial) => void; +}) { + const labelStart = labelPrefix ? `${labelPrefix} ` : ""; + + return ( +
+ {marginSideOptions.map((side) => ( + onChange(createMarginSidePatch(side.property, value))} + /> + ))} +
+ ); +} + +function getPaddingSideValue(intent: StyleIntent, property: PaddingSideProperty) { + return intent[property] ?? intent.padding; +} + +function createPaddingSidePatch( + intent: StyleIntent, + property: PaddingSideProperty, + value: number | undefined, +): Partial { + if (intent.padding === undefined) return { [property]: value }; + + const patch: Partial = { padding: undefined }; + + for (const side of paddingSideOptions) { + patch[side.property] = intent[side.property] ?? intent.padding; + } + + patch[property] = value; + + return patch; +} + +function createMarginSidePatch(property: MarginSideProperty, value: number | undefined): Partial { + return { [property]: value }; +} + +function RulePropertySummary({ intent }: { intent: StyleIntent }) { + const properties = [ + intent.color && { label: "Text", value: intent.color, color: intent.color }, + intent.backgroundColor && { label: "Background", value: intent.backgroundColor, color: intent.backgroundColor }, + intent.textDecorationColor && { + label: "Decoration", + value: intent.textDecorationColor, + color: intent.textDecorationColor, + }, + intent.borderColor && { label: "Border", value: intent.borderColor, color: intent.borderColor }, + intent.opacity !== undefined && { label: "Opacity", value: `${intent.opacity}` }, + intent.fontSize && { label: "Size", value: `${intent.fontSize}` }, + intent.fontWeight && { label: "Weight", value: intent.fontWeight }, + intent.fontStyle && { label: "Style", value: intent.fontStyle }, + intent.lineHeight !== undefined && { label: "Line", value: `${intent.lineHeight}` }, + intent.letterSpacing !== undefined && { label: "Tracking", value: `${intent.letterSpacing}` }, + intent.textDecoration && { label: "Decoration", value: intent.textDecoration }, + intent.textDecorationStyle && { label: "Decoration Style", value: intent.textDecorationStyle }, + intent.textAlign && { label: "Align", value: intent.textAlign }, + intent.textTransform && { label: "Transform", value: intent.textTransform }, + getPaddingSummary(intent) && { label: "Padding", value: getPaddingSummary(intent) }, + getMarginSummary(intent) && { label: "Margin", value: getMarginSummary(intent) }, + getGapSummary(intent) && { label: "Gap", value: getGapSummary(intent) }, + intent.borderStyle && { label: "Border Style", value: intent.borderStyle }, + intent.borderWidth !== undefined && { label: "Border Width", value: `${intent.borderWidth}` }, + intent.borderRadius !== undefined && { label: "Radius", value: `${intent.borderRadius}` }, + ].filter(Boolean) as { label: string; value: string; color?: string }[]; + + if (properties.length === 0) return null; + + return ( +
+ {properties.map((property) => ( + + {property.color && ( + + )} + {property.label} + {property.value} + + ))} +
+ ); +} + +function getPaddingSummary(intent: StyleIntent) { + if (intent.padding !== undefined) return `All ${intent.padding}`; + + const sideValues = paddingSideOptions.flatMap((side) => { + const value = intent[side.property]; + if (value === undefined) return []; + + return [`${side.label.at(0)} ${value}`]; + }); + + return sideValues.length > 0 ? sideValues.join(" / ") : undefined; +} + +function getMarginSummary(intent: StyleIntent) { + const sideValues = marginSideOptions.flatMap((side) => { + const value = intent[side.property]; + if (value === undefined) return []; + + return [`${side.label.at(0)} ${value}`]; + }); + + return sideValues.length > 0 ? sideValues.join(" / ") : undefined; +} + +function getGapSummary(intent: StyleIntent) { + const values = [ + intent.rowGap !== undefined && `Row ${intent.rowGap}`, + intent.columnGap !== undefined && `Column ${intent.columnGap}`, + ].filter(Boolean); + + return values.length > 0 ? values.join(" / ") : undefined; +} + +function createTarget({ + targetScope, + sectionType, + sectionId, +}: { + targetScope: TargetScope; + sectionType: string; + sectionId: string; +}): StyleRuleTarget { + if (targetScope === "sectionType") { + return { + scope: "sectionType", + sectionType: sectionType as Extract["sectionType"], + }; + } + if (targetScope === "sectionId") return { scope: "sectionId", sectionId }; + + return { scope: "global" }; +} + +function getStyleRuleId(target: StyleRuleTarget, slot: StyleSlot) { + if (target.scope === "global") return `style-global-${slot}`; + if (target.scope === "sectionType") return `style-section-type-${target.sectionType}-${slot}`; + + return `style-section-id-${slugify(target.sectionId)}-${slot}`; +} + +function getSlotLabel(slot: StyleSlot) { + return styleSlotOptions.find((option) => option.value === slot)?.label ?? slot; +} + +function getTargetLabel(data: ResumeData, target: StyleRuleTarget) { + if (target.scope === "global") return "All sections"; + if (target.scope === "sectionType") return getSectionTitle(target.sectionType); + + return getSectionIdOptions(data).find((option) => option.value === target.sectionId)?.label ?? target.sectionId; +} + +function getRuleFallbackLabel(data: ResumeData, rule: StyleRule) { + const slots = getConfiguredSlots(rule); + const slot = slots[0]; + return `${getTargetLabel(data, rule.target)}${slot ? `: ${getSlotLabel(slot)}` : ""}`; +} + +function getConfiguredSlots(rule: StyleRule): StyleSlot[] { + const slots: StyleSlot[] = []; + + for (const option of styleSlotOptions) { + if (hasIntent(rule.slots[option.value])) slots.push(option.value); + } + + return slots; +} + +function getSectionIdOptions(data: ResumeData) { + return [ + { value: "summary", label: data.summary?.title || getSectionTitle("summary") }, + ...Object.entries(data.sections).map(([section, value]) => ({ + value: section, + label: value.title || getSectionTitle(section as keyof ResumeData["sections"]), + })), + ...data.customSections.map((section) => ({ + value: section.id, + label: section.title || getSectionTitle(section.type), + })), + ]; +} + +function compactIntent(intent: Partial): StyleIntent { + return Object.fromEntries( + Object.entries(intent).filter(([, value]) => value !== undefined && value !== ""), + ) as StyleIntent; +} + +function hasIntent(intent: StyleIntent | undefined) { + return Boolean(intent && Object.keys(intent).length > 0); +} + +function slugify(value: string) { + return value + .replace(/[^a-zA-Z0-9]+/g, "-") + .replace(/(^-|-$)/g, "") + .toLowerCase(); +} diff --git a/docs/adr/0002-structured-style-rules-for-react-pdf.md b/docs/adr/0002-structured-style-rules-for-react-pdf.md new file mode 100644 index 000000000..d23322736 --- /dev/null +++ b/docs/adr/0002-structured-style-rules-for-react-pdf.md @@ -0,0 +1,5 @@ +# Structured Style Rules for React PDF + +Reactive Resume no longer renders final PDFs through browser HTML/CSS, so end-user custom CSS would create a misleading contract: React PDF accepts style objects on renderer components, not arbitrary browser selectors. Resume appearance customization is therefore modeled as structured Style Rules in resume metadata, targeting semantic section and rich-text slots that the PDF package translates into safe React PDF styles. + +This preserves user-controlled section styling while keeping PDF rendering portable across preview, export, public resume views, templates, and server generation. Raw CSS, raw React PDF style objects, section-item targeting, and direct PDF-preview selection are intentionally out of v1; they can be revisited only if the product needs that power enough to accept the additional validation and layout-breakage risk. diff --git a/package.json b/package.json index f3183846e..f2bd40968 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "knip": "^6.14.2", "lefthook": "^2.1.8", "npm-check-updates": "^22.2.1", - "turbo": "^2.9.14", + "turbo": "^2.9.15", "typescript": "^6.0.3", "vitest": "^4.1.7" } diff --git a/packages/ai/package.json b/packages/ai/package.json index 8cc0b52f8..a38942b50 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -29,7 +29,7 @@ }, "devDependencies": { "@reactive-resume/config": "workspace:*", - "@typescript/native-preview": "7.0.0-dev.20260526.1", + "@typescript/native-preview": "7.0.0-dev.20260527.1", "typescript": "^6.0.3" } } diff --git a/packages/api/package.json b/packages/api/package.json index 4b656d0ad..045c72b93 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -23,7 +23,7 @@ "@ai-sdk/google": "^3.0.79", "@ai-sdk/openai": "^3.0.65", "@ai-sdk/openai-compatible": "^2.0.48", - "@aws-sdk/client-s3": "^3.1053.0", + "@aws-sdk/client-s3": "^3.1054.0", "@orpc/client": "^1.14.3", "@orpc/experimental-ratelimit": "^1.14.3", "@orpc/server": "^1.14.3", @@ -41,7 +41,7 @@ "drizzle-orm": "1.0.0-rc.3", "drizzle-zod": "1.0.0-beta.14-a36c63d", "es-toolkit": "^1.47.0", - "ioredis": "^5.10.1", + "ioredis": "^5.11.0", "ollama-ai-provider-v2": "^3.5.1", "react": "^19.2.6", "resumable-stream": "^2.2.12", @@ -52,7 +52,7 @@ "devDependencies": { "@reactive-resume/config": "workspace:*", "@types/bcrypt": "^6.0.0", - "@typescript/native-preview": "7.0.0-dev.20260526.1", + "@typescript/native-preview": "7.0.0-dev.20260527.1", "typescript": "^6.0.3" } } diff --git a/packages/auth/package.json b/packages/auth/package.json index 4a2434253..cfcd0f657 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -18,7 +18,7 @@ "dependencies": { "@better-auth/api-key": "^1.6.11", "@better-auth/drizzle-adapter": "^1.6.11", - "@better-auth/infra": "^0.2.8", + "@better-auth/infra": "^0.2.10", "@better-auth/oauth-provider": "^1.6.11", "@better-auth/passkey": "^1.6.11", "@reactive-resume/db": "workspace:*", @@ -36,7 +36,7 @@ "@reactive-resume/config": "workspace:*", "@types/bcrypt": "^6.0.0", "@types/react": "^19.2.15", - "@typescript/native-preview": "7.0.0-dev.20260526.1", + "@typescript/native-preview": "7.0.0-dev.20260527.1", "typescript": "^6.0.3" } } diff --git a/packages/db/package.json b/packages/db/package.json index 47bb5eaa1..f8fdf61bf 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -31,7 +31,7 @@ "devDependencies": { "@reactive-resume/config": "workspace:*", "@types/pg": "^8.20.0", - "@typescript/native-preview": "7.0.0-dev.20260526.1", + "@typescript/native-preview": "7.0.0-dev.20260527.1", "drizzle-kit": "1.0.0-rc.3", "typescript": "^6.0.3" } diff --git a/packages/docx/package.json b/packages/docx/package.json index 339fb4a91..2c1ffc766 100644 --- a/packages/docx/package.json +++ b/packages/docx/package.json @@ -20,7 +20,7 @@ }, "devDependencies": { "@reactive-resume/config": "workspace:*", - "@typescript/native-preview": "7.0.0-dev.20260526.1", + "@typescript/native-preview": "7.0.0-dev.20260527.1", "typescript": "^6.0.3" } } diff --git a/packages/email/package.json b/packages/email/package.json index 86754b0ca..165e482b5 100644 --- a/packages/email/package.json +++ b/packages/email/package.json @@ -18,17 +18,17 @@ }, "dependencies": { "@reactive-resume/env": "workspace:*", - "nodemailer": "^8.0.8", + "nodemailer": "^8.0.9", "react": "^19.2.6", - "react-email": "^6.3.3" + "react-email": "^6.4.0" }, "devDependencies": { - "@react-email/ui": "^6.3.3", + "@react-email/ui": "^6.4.0", "@reactive-resume/config": "workspace:*", "@types/nodemailer": "^8.0.0", "@types/react": "^19.2.15", - "@typescript/native-preview": "7.0.0-dev.20260526.1", - "react-doctor": "^0.2.6", + "@typescript/native-preview": "7.0.0-dev.20260527.1", + "react-doctor": "^0.2.9", "typescript": "^6.0.3" } } diff --git a/packages/env/package.json b/packages/env/package.json index 8f86b1f38..559d594c7 100644 --- a/packages/env/package.json +++ b/packages/env/package.json @@ -22,7 +22,7 @@ "devDependencies": { "@reactive-resume/config": "workspace:*", "@types/node": "^25.9.1", - "@typescript/native-preview": "7.0.0-dev.20260526.1", + "@typescript/native-preview": "7.0.0-dev.20260527.1", "typescript": "^6.0.3" } } diff --git a/packages/fonts/package.json b/packages/fonts/package.json index 139df7610..9363e1ab8 100644 --- a/packages/fonts/package.json +++ b/packages/fonts/package.json @@ -19,7 +19,7 @@ }, "devDependencies": { "@reactive-resume/config": "workspace:*", - "@typescript/native-preview": "7.0.0-dev.20260526.1", + "@typescript/native-preview": "7.0.0-dev.20260527.1", "typescript": "^6.0.3" } } diff --git a/packages/import/package.json b/packages/import/package.json index 15b3de94f..0aa7aa67e 100644 --- a/packages/import/package.json +++ b/packages/import/package.json @@ -23,7 +23,7 @@ }, "devDependencies": { "@reactive-resume/config": "workspace:*", - "@typescript/native-preview": "7.0.0-dev.20260526.1", + "@typescript/native-preview": "7.0.0-dev.20260527.1", "typescript": "^6.0.3" } } diff --git a/packages/import/src/reactive-resume-v4-json.tsx b/packages/import/src/reactive-resume-v4-json.tsx index 7be36ba04..afd152a54 100644 --- a/packages/import/src/reactive-resume-v4-json.tsx +++ b/packages/import/src/reactive-resume-v4-json.tsx @@ -762,6 +762,7 @@ export class ReactiveResumeV4JSONImporter { }, }, notes: v4Data.metadata.notes ?? "", + styleRules: [], }, }; diff --git a/packages/mcp/package.json b/packages/mcp/package.json index 27048946d..9a7df632f 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -29,7 +29,7 @@ }, "devDependencies": { "@reactive-resume/config": "workspace:*", - "@typescript/native-preview": "7.0.0-dev.20260526.1", + "@typescript/native-preview": "7.0.0-dev.20260527.1", "typescript": "^6.0.3", "vitest": "^4.1.7" } diff --git a/packages/pdf/package.json b/packages/pdf/package.json index 554291b55..52c8d64b8 100644 --- a/packages/pdf/package.json +++ b/packages/pdf/package.json @@ -38,8 +38,8 @@ "@react-pdf/types": "^2.11.1", "@reactive-resume/config": "workspace:*", "@types/react": "^19.2.15", - "@typescript/native-preview": "7.0.0-dev.20260526.1", - "react-doctor": "^0.2.6", + "@typescript/native-preview": "7.0.0-dev.20260527.1", + "react-doctor": "^0.2.9", "typescript": "^6.0.3" } } diff --git a/packages/pdf/src/templates/shared/context.tsx b/packages/pdf/src/templates/shared/context.tsx index d88eb8f2d..2d8a337cf 100644 --- a/packages/pdf/src/templates/shared/context.tsx +++ b/packages/pdf/src/templates/shared/context.tsx @@ -1,4 +1,6 @@ +import type { StyleSlot } from "@reactive-resume/schema/resume/data"; import type { ReactNode } from "react"; +import type { SectionStyleRuleContext } from "./style-rules"; import type { StyleInput, TemplatePlacement } from "./styles"; import type { SectionTimelineStyleSlots, @@ -11,6 +13,8 @@ import type { TemplateStyleSlots, } from "./types"; import { createContext, use, useMemo } from "react"; +import { useRender } from "../../context"; +import { resolveStyleRuleSlot } from "./style-rules"; type TemplateContextValue = { styles: TemplateStyleSlots; @@ -27,6 +31,7 @@ type TemplateProviderProps = Omit(null); const TemplatePlacementContext = createContext("main"); +const SectionStyleContext = createContext(null); const resolveStyleSlot = (slot: TemplateStyleSlot | undefined, context: TemplateStyleContextValue): StyleInput => { if (!slot) return undefined; @@ -78,6 +83,16 @@ export const TemplatePlacementProvider = ({ return {children}; }; +export const SectionStyleProvider = ({ + context, + children, +}: { + context: SectionStyleRuleContext; + children: ReactNode; +}) => { + return {children}; +}; + const useTemplateContext = () => { const context = use(TemplateContext); @@ -108,6 +123,15 @@ export const useTemplateStyle = (slot: keyof TemplateStyleSlots): StyleInput => return resolveStyleSlot(styles[slot] as TemplateStyleSlot | undefined, context); }; +export const useSectionStyleRule = (slot: StyleSlot): StyleInput => { + const data = useRender(); + const context = use(SectionStyleContext); + + if (!context) return undefined; + + return resolveStyleRuleSlot(data, { ...context, slot }); +}; + export const useTemplateFeatureStyle = ( feature: keyof TemplateFeatureStyleSlots, slot: keyof SectionTimelineStyleSlots, diff --git a/packages/pdf/src/templates/shared/level-display.tsx b/packages/pdf/src/templates/shared/level-display.tsx index 7b084a161..ce6963c7e 100644 --- a/packages/pdf/src/templates/shared/level-display.tsx +++ b/packages/pdf/src/templates/shared/level-display.tsx @@ -2,7 +2,7 @@ import type { Style } from "@react-pdf/types"; import type { IconName } from "phosphor-icons-react-pdf/dynamic"; import { useRender } from "../../context"; import { View } from "../../renderer"; -import { useTemplateIconSlot, useTemplateStyle } from "./context"; +import { useSectionStyleRule, useTemplateIconSlot, useTemplateStyle } from "./context"; import { getTemplateMetrics } from "./metrics"; import { Icon } from "./primitives"; import { composeStyles } from "./styles"; @@ -19,6 +19,7 @@ export const LevelDisplay = ({ level }: { level: number }) => { const levelItemStyle = useTemplateStyle("levelItem"); const levelItemActiveStyle = useTemplateStyle("levelItemActive"); const levelItemInactiveStyle = useTemplateStyle("levelItemInactive"); + const levelRuleStyle = useSectionStyleRule("level"); const color = typeof iconProps.color === "string" ? iconProps.color : "#000000"; if (level === 0) return null; @@ -40,6 +41,7 @@ export const LevelDisplay = ({ level }: { level: number }) => { style={composeStyles( { flexDirection: "row", alignItems: "center", marginTop: 2, columnGap: gap }, levelContainerStyle, + levelRuleStyle, )} > {LEVEL_ITEM_KEYS.map((itemKey, index) => { diff --git a/packages/pdf/src/templates/shared/primitives.tsx b/packages/pdf/src/templates/shared/primitives.tsx index 1fb50194d..b3ded8590 100644 --- a/packages/pdf/src/templates/shared/primitives.tsx +++ b/packages/pdf/src/templates/shared/primitives.tsx @@ -3,7 +3,7 @@ import type { ComponentProps } from "react"; import type { StyleInput } from "./styles"; import { Icon as PhosphorIcon } from "phosphor-icons-react-pdf/dynamic"; import { Link as PdfLink, Text as PdfText, View } from "../../renderer"; -import { useTemplateIconSlot, useTemplateStyle } from "./context"; +import { useSectionStyleRule, useTemplateIconSlot, useTemplateStyle } from "./context"; import { safeTextStyle } from "./safe-text-style"; import { composeLinkStyles, composeStyles } from "./styles"; @@ -17,40 +17,64 @@ export const Div = ({ style, ...props }: ComponentProps) => { export const Text = ({ style, ...props }: ComponentProps) => { const textStyle = useTemplateStyle("text"); + const textRuleStyle = useSectionStyleRule("text"); - return ; + return ; }; export const Heading = ({ style, ...props }: ComponentProps) => { const headingStyle = useTemplateStyle("heading"); + const headingRuleStyle = useSectionStyleRule("heading"); - return ; + return ( + + ); }; export const Link = ({ style, ...props }: ComponentProps) => { const linkStyle = useTemplateStyle("link"); + const linkRuleStyle = useSectionStyleRule("link"); - return ; + return ; }; export const Small = ({ style, ...props }: ComponentProps) => { const textStyle = useTemplateStyle("text"); const smallStyle = useTemplateStyle("small"); + const secondaryTextRuleStyle = useSectionStyleRule("secondaryText"); - return ; + return ( + + ); }; export const Bold = ({ style, ...props }: ComponentProps) => { const textStyle = useTemplateStyle("text"); const boldStyle = useTemplateStyle("bold"); + const textRuleStyle = useSectionStyleRule("text"); - return ; + return ( + + ); }; export const Icon = ({ style, ...props }: ComponentProps) => { const { style: iconStyle, ...iconProps } = useTemplateIconSlot("icon"); + const iconRuleStyle = useSectionStyleRule("icon"); if (iconProps.display === "none") return null; - return ; + return ( + + ); }; diff --git a/packages/pdf/src/templates/shared/rich-text-html.test.ts b/packages/pdf/src/templates/shared/rich-text-html.test.ts index 69eaf6fb0..cb2df64ef 100644 --- a/packages/pdf/src/templates/shared/rich-text-html.test.ts +++ b/packages/pdf/src/templates/shared/rich-text-html.test.ts @@ -26,6 +26,12 @@ describe("normalizeRichTextHtml", () => { expect(normalizeRichTextHtml(html)).toBe(html); }); + it("unwraps single paragraph wrappers inside list items", () => { + expect(normalizeRichTextHtml("
  • a

  • b

")).toBe( + "
  • a
  • b
", + ); + }); + it("flushes accumulated inlines before block-level tags", () => { expect(normalizeRichTextHtml("loose
  • a
")).toBe("

loose

  • a
"); }); diff --git a/packages/pdf/src/templates/shared/rich-text-html.ts b/packages/pdf/src/templates/shared/rich-text-html.ts index d9649941c..8ce822c0d 100644 --- a/packages/pdf/src/templates/shared/rich-text-html.ts +++ b/packages/pdf/src/templates/shared/rich-text-html.ts @@ -1,4 +1,4 @@ -import type { Node } from "node-html-parser"; +import type { HTMLElement, Node } from "node-html-parser"; import { NodeType, parse } from "node-html-parser"; export const richTextMarkClassName = "rr-pdf-mark"; @@ -50,6 +50,23 @@ const normalizeMarkElements = (root: ReturnType) => { } }; +const isMeaningfulNode = (node: Node): boolean => + node.nodeType !== NodeType.TEXT_NODE || node.toString().trim().length > 0; + +const isElement = (node: Node): node is HTMLElement => node.nodeType === NodeType.ELEMENT_NODE; + +const unwrapSingleParagraphListItems = (root: ReturnType) => { + for (const listItem of root.querySelectorAll("li")) { + const meaningfulChildren = listItem.childNodes.filter(isMeaningfulNode); + if (meaningfulChildren.length !== 1) continue; + + const child = meaningfulChildren[0]; + if (!child || !isElement(child) || getTagName(child) !== "p") continue; + + listItem.innerHTML = child.innerHTML; + } +}; + const isInlineNode = (node: Node): boolean => { if (node.nodeType === NodeType.TEXT_NODE || node.nodeType === NodeType.COMMENT_NODE) return true; if (node.nodeType !== NodeType.ELEMENT_NODE) return false; @@ -98,6 +115,7 @@ export const normalizeRichTextHtml = (html: string): string => { let inlineNodes: string[] = []; normalizeMarkElements(root); + unwrapSingleParagraphListItems(root); const flushInlineNodes = () => { const inlineHtml = inlineNodes.join("").trim(); diff --git a/packages/pdf/src/templates/shared/rich-text-stylesheet.ts b/packages/pdf/src/templates/shared/rich-text-stylesheet.ts new file mode 100644 index 000000000..6b02da64f --- /dev/null +++ b/packages/pdf/src/templates/shared/rich-text-stylesheet.ts @@ -0,0 +1,52 @@ +import type { Style } from "@react-pdf/types"; +import type { StyleInput } from "./styles"; +import { richTextMarkClassName } from "./rich-text-html"; +import { safeTextStyle } from "./safe-text-style"; +import { mergeLinkStyles, mergeStyles } from "./styles"; + +type RichTextProseSpacing = { + paragraph: Style; + listItem: Style; +}; + +type CreateRichTextStylesheetOptions = { + boldStyle?: StyleInput; + linkStyle?: StyleInput; + richParagraphStyle?: StyleInput; + richParagraphRuleStyle?: StyleInput; + richListRuleStyle?: StyleInput; + richBoldRuleStyle?: StyleInput; + richLinkRuleStyle?: StyleInput; + richMarkRuleStyle?: StyleInput; + proseSpacing?: RichTextProseSpacing; +}; + +const richMarkStyle = { + backgroundColor: "#ffff00", +} satisfies Style; + +const emptyProseSpacing: RichTextProseSpacing = { + paragraph: {}, + listItem: {}, +}; + +export const createRichTextStylesheet = ({ + boldStyle, + linkStyle, + richParagraphStyle, + richParagraphRuleStyle, + richListRuleStyle, + richBoldRuleStyle, + richLinkRuleStyle, + richMarkRuleStyle, + proseSpacing = emptyProseSpacing, +}: CreateRichTextStylesheetOptions = {}) => ({ + b: mergeStyles(boldStyle, richBoldRuleStyle, safeTextStyle), + strong: mergeStyles(boldStyle, richBoldRuleStyle, safeTextStyle), + ul: mergeStyles(richListRuleStyle), + ol: mergeStyles(richListRuleStyle), + li: mergeStyles(proseSpacing.listItem), + [`.${richTextMarkClassName}`]: mergeStyles(richMarkStyle, richMarkRuleStyle, safeTextStyle), + p: mergeStyles(richParagraphStyle, richParagraphRuleStyle, safeTextStyle, proseSpacing.paragraph), + a: mergeLinkStyles(linkStyle, richLinkRuleStyle, safeTextStyle), +}); diff --git a/packages/pdf/src/templates/shared/rich-text.test.ts b/packages/pdf/src/templates/shared/rich-text.test.ts index 849861487..692fc9e62 100644 --- a/packages/pdf/src/templates/shared/rich-text.test.ts +++ b/packages/pdf/src/templates/shared/rich-text.test.ts @@ -5,6 +5,7 @@ import { parse } from "node-html-parser"; import { createElement } from "react"; import { normalizeRichTextHtml } from "./rich-text-html"; import { renderRichTextParagraph } from "./rich-text-renderers"; +import { createRichTextStylesheet } from "./rich-text-stylesheet"; type PdfElement = ReactElement<{ children?: unknown; style?: unknown }>; @@ -22,13 +23,13 @@ describe("normalizeRichTextHtml", () => { it("preserves existing block rich text", () => { expect(normalizeRichTextHtml("

Existing paragraph.

  • Existing item.

")).toBe( - "

Existing paragraph.

  • Existing item.

", + "

Existing paragraph.

  • Existing item.
", ); }); it("wraps inline runs around top-level blocks", () => { expect(normalizeRichTextHtml("Intro text
  • Item

Outro")).toBe( - "

Intro text

  • Item

Outro

", + "

Intro text

  • Item

Outro

", ); }); }); @@ -50,3 +51,19 @@ describe("renderRichTextParagraph", () => { expect(props.children).toEqual(["Plain ", expect.any(Object), " text"]); }); }); + +describe("createRichTextStylesheet", () => { + it("applies list style rules to unordered and ordered list containers", () => { + const stylesheet = createRichTextStylesheet({ + richListRuleStyle: { rowGap: 8 }, + proseSpacing: { + paragraph: { marginTop: 12, marginBottom: 12 }, + listItem: { marginTop: 2, marginBottom: 2 }, + }, + }); + + expect(stylesheet.ul).toEqual({ rowGap: 8 }); + expect(stylesheet.ol).toEqual({ rowGap: 8 }); + expect(stylesheet.li).toEqual({ marginTop: 2, marginBottom: 2 }); + }); +}); diff --git a/packages/pdf/src/templates/shared/rich-text.tsx b/packages/pdf/src/templates/shared/rich-text.tsx index 6a504668b..2947ca3fa 100644 --- a/packages/pdf/src/templates/shared/rich-text.tsx +++ b/packages/pdf/src/templates/shared/rich-text.tsx @@ -4,8 +4,8 @@ import { cloneElement, isValidElement } from "react"; import { Html } from "react-pdf-html"; import { useRender } from "../../context"; import { Text as PdfText, View } from "../../renderer"; -import { useTemplateStyle } from "./context"; -import { convertPseudoBulletParagraphs, normalizeRichTextHtml, richTextMarkClassName } from "./rich-text-html"; +import { useSectionStyleRule, useTemplateStyle } from "./context"; +import { convertPseudoBulletParagraphs, normalizeRichTextHtml } from "./rich-text-html"; import { renderRichTextParagraph, toRichTextStyleArray } from "./rich-text-renderers"; import { createRichTextProseSpacing, @@ -14,17 +14,14 @@ import { resolveRichTextBodyLineHeight, stripRichTextVerticalMargins, } from "./rich-text-spacing"; +import { createRichTextStylesheet } from "./rich-text-stylesheet"; import { safeTextStyle } from "./safe-text-style"; -import { composeStyles, mergeLinkStyles, mergeStyles } from "./styles"; +import { composeStyles } from "./styles"; const richListItemContentStackStyle = { flexDirection: "column", } satisfies Style; -const richMarkStyle = { - backgroundColor: "#ffff00", -} satisfies Style; - // react-pdf textkit reads BiDi base direction from each run's own `direction` attribute // (default "ltr"), and react-pdf-html buckets inline content into styleless inner // frames — so the rtl style has to be injected onto every descendant, not just a wrapper. @@ -65,7 +62,19 @@ export const RichText = ({ children }: { children: string }) => { const richListItemRowStyle = useTemplateStyle("richListItemRow"); const richListItemMarkerStyle = useTemplateStyle("richListItemMarker"); const richListItemContentStyle = useTemplateStyle("richListItemContent"); - const bodyLineHeight = resolveRichTextBodyLineHeight(richParagraphStyle, richListItemContentStyle); + const richParagraphRuleStyle = useSectionStyleRule("richParagraph"); + const richListRuleStyle = useSectionStyleRule("richList"); + const richListItemRowRuleStyle = useSectionStyleRule("richListItemRow"); + const richListItemContentRuleStyle = useSectionStyleRule("richListItemContent"); + const richLinkRuleStyle = useSectionStyleRule("richLink"); + const richBoldRuleStyle = useSectionStyleRule("richBold"); + const richMarkRuleStyle = useSectionStyleRule("richMark"); + const bodyLineHeight = resolveRichTextBodyLineHeight( + richParagraphStyle, + richParagraphRuleStyle, + richListItemContentStyle, + richListItemContentRuleStyle, + ); const proseSpacing = createRichTextProseSpacing(bodyLineHeight); const normalized = normalizeRichTextHtml(children); @@ -85,7 +94,9 @@ export const RichText = ({ children }: { children: string }) => { {children}, + b: ({ children }) => ( + {children} + ), p: (props) => { const paragraphProps = { ...props, @@ -111,7 +122,13 @@ export const RichText = ({ children }: { children: string }) => { const contentNode = rtl ? ( {applyRtlDirectionRecursively(children)} @@ -120,6 +137,7 @@ export const RichText = ({ children }: { children: string }) => { key="content" style={composeStyles( richListItemContentStyle, + richListItemContentRuleStyle, contentItemStyles, richListItemContentStackStyle, safeTextStyle, @@ -132,20 +150,30 @@ export const RichText = ({ children }: { children: string }) => { // Yoga ignores `flexDirection`/`direction` on rows inside react-pdf-html's
    // (works fine for split-row/contact-list). Swap DOM order to position the marker. return ( - + {rtl ? [contentNode, markerNode] : [markerNode, contentNode]} ); }, }} - stylesheet={{ - b: mergeStyles(boldStyle, safeTextStyle), - strong: mergeStyles(boldStyle, safeTextStyle), - li: mergeStyles(proseSpacing.listItem), - [`.${richTextMarkClassName}`]: mergeStyles(richMarkStyle, safeTextStyle), - p: mergeStyles(richParagraphStyle, safeTextStyle, proseSpacing.paragraph), - a: mergeLinkStyles(linkStyle, safeTextStyle), - }} + stylesheet={createRichTextStylesheet({ + boldStyle, + linkStyle, + richParagraphStyle, + richParagraphRuleStyle, + richListRuleStyle, + richBoldRuleStyle, + richLinkRuleStyle, + richMarkRuleStyle, + proseSpacing, + })} > {html} diff --git a/packages/pdf/src/templates/shared/sections.tsx b/packages/pdf/src/templates/shared/sections.tsx index 245f0ade3..cd2a767dc 100644 --- a/packages/pdf/src/templates/shared/sections.tsx +++ b/packages/pdf/src/templates/shared/sections.tsx @@ -27,7 +27,9 @@ import { getResumeSectionTitle } from "../../section-title"; import { getSectionItemRows, getSectionItemsLayout, shouldUseSectionTimeline } from "./columns"; import { getWebsiteDisplayText } from "./contact"; import { + SectionStyleProvider, TemplatePlacementProvider, + useSectionStyleRule, useTemplateFeature, useTemplateFeatureStyle, useTemplatePlacement, @@ -41,6 +43,7 @@ import { RichText } from "./rich-text"; import { createRtlStyleHelpers } from "./rtl"; import { getInlineItemWebsiteUrl, shouldRenderSeparateItemWebsite } from "./section-links"; import { hasSplitRowText, promoteSplitRowRight } from "./split-row"; +import { getSectionStyleRuleContext } from "./style-rules"; import { composeStyles } from "./styles"; type SectionItemsContextValue = { @@ -88,12 +91,16 @@ const SectionShell = ({ }) => { const data = useRender(); const sectionStyle = useTemplateStyle("section"); + const sectionRuleStyle = useSectionStyleRule("section"); const sectionHeadingStyle = useTemplateStyle("sectionHeading"); + const sectionHeadingRuleStyle = useSectionStyleRule("heading"); const sectionTitle = getResumeSectionTitle(data, sectionId, title); return ( - - {showHeading && {sectionTitle}} + + {showHeading && ( + {sectionTitle} + )} {children} ); @@ -160,13 +167,14 @@ const SectionItems = ({ children, columns = 1 }: { children: ReactNode; columns? const SectionItem = ({ children, style }: { children: ReactNode; style?: StyleInput }) => { const { itemStyle: sectionItemStyle, useTimeline } = useSectionItemsContext(); const itemStyle = useTemplateStyle("item"); + const itemRuleStyle = useSectionStyleRule("item"); const timelineItemStyle = useTemplateFeatureStyle("sectionTimeline", "item"); const timelineMarkerStyle = useTemplateFeatureStyle("sectionTimeline", "marker"); const timelineDotStyle = useTemplateFeatureStyle("sectionTimeline", "dot"); const timelineContentStyle = useTemplateFeatureStyle("sectionTimeline", "content"); if (!useTimeline) { - return
    {children}
    ; + return
    {children}
    ; } return ( @@ -174,7 +182,7 @@ const SectionItem = ({ children, style }: { children: ReactNode; style?: StyleIn -
    {children}
    +
    {children}
    ); }; @@ -924,23 +932,25 @@ export const Section = ({ return ( - {match(section) - .with("summary", () => ) - .with("profiles", () => ) - .with("experience", () => ) - .with("education", () => ) - .with("projects", () => ) - .with("skills", () => ) - .with("languages", () => ) - .with("interests", () => ) - .with("awards", () => ) - .with("certifications", () => ) - .with("publications", () => ) - .with("volunteer", () => ) - .with("references", () => ) - .otherwise(() => ( - - ))} + + {match(section) + .with("summary", () => ) + .with("profiles", () => ) + .with("experience", () => ) + .with("education", () => ) + .with("projects", () => ) + .with("skills", () => ) + .with("languages", () => ) + .with("interests", () => ) + .with("awards", () => ) + .with("certifications", () => ) + .with("publications", () => ) + .with("volunteer", () => ) + .with("references", () => ) + .otherwise(() => ( + + ))} + ); }; diff --git a/packages/pdf/src/templates/shared/style-rules.test.ts b/packages/pdf/src/templates/shared/style-rules.test.ts new file mode 100644 index 000000000..386aef12b --- /dev/null +++ b/packages/pdf/src/templates/shared/style-rules.test.ts @@ -0,0 +1,172 @@ +import type { ResumeData } from "@reactive-resume/schema/resume/data"; +import { describe, expect, it } from "vitest"; +import { defaultResumeData } from "@reactive-resume/schema/resume/default"; +import { getSectionStyleRuleContext, resolveStyleRuleSlot } from "./style-rules"; + +const createResumeData = (styleRules: ResumeData["metadata"]["styleRules"]): ResumeData => ({ + ...defaultResumeData, + metadata: { + ...defaultResumeData.metadata, + styleRules, + }, +}); + +describe("resolveStyleRuleSlot", () => { + it("applies global, section-type, and section-id rules in specificity order", () => { + const data = createResumeData([ + { + id: "global-heading", + label: "Global Heading", + enabled: true, + target: { scope: "global" }, + slots: { heading: { color: "rgba(0, 0, 0, 1)", fontSize: 12 } }, + }, + { + id: "experience-heading", + label: "Experience Heading", + enabled: true, + target: { scope: "sectionType", sectionType: "experience" }, + slots: { heading: { color: "rgba(220, 38, 38, 1)" } }, + }, + { + id: "specific-heading", + label: "Specific Heading", + enabled: true, + target: { scope: "sectionId", sectionId: "experience" }, + slots: { heading: { fontSize: 15 } }, + }, + ]); + + expect( + resolveStyleRuleSlot(data, { + sectionId: "experience", + sectionType: "experience", + slot: "heading", + }), + ).toEqual({ color: "#dc2626", fontSize: 15 }); + }); + + it("ignores disabled rules and non-matching deleted section ids", () => { + const data = createResumeData([ + { + id: "disabled", + label: "Disabled", + enabled: false, + target: { scope: "global" }, + slots: { section: { backgroundColor: "rgba(0, 0, 0, 1)" } }, + }, + { + id: "missing", + label: "Missing", + enabled: true, + target: { scope: "sectionId", sectionId: "missing-section" }, + slots: { section: { backgroundColor: "rgba(220, 38, 38, 1)" } }, + }, + ]); + + expect( + resolveStyleRuleSlot(data, { + sectionId: "experience", + sectionType: "experience", + slot: "section", + }), + ).toEqual({}); + }); + + it("resolves custom section types for section-type rules", () => { + const data = createResumeData([ + { + id: "custom-summary-rich-text", + label: "Custom Summary Rich Text", + enabled: true, + target: { scope: "sectionType", sectionType: "summary" }, + slots: { richParagraph: { color: "rgba(21, 93, 252, 1)" } }, + }, + ]); + const customSection = { + ...defaultResumeData.summary, + id: "custom-summary", + type: "summary" as const, + items: [], + }; + const customData = { ...data, customSections: [customSection] }; + + expect(getSectionStyleRuleContext(customData, "custom-summary")).toEqual({ + sectionId: "custom-summary", + sectionType: "summary", + }); + expect( + resolveStyleRuleSlot(customData, { + sectionId: "custom-summary", + sectionType: "summary", + slot: "richParagraph", + }), + ).toEqual({ color: "#155dfc" }); + }); + + it("clamps unsafe spacing values before returning React PDF styles", () => { + const data = createResumeData([ + { + id: "spacing", + label: "Spacing", + enabled: true, + target: { scope: "global" }, + slots: { item: { padding: 1000, marginTop: -1000, rowGap: -12, borderWidth: -5 } }, + }, + ]); + + expect( + resolveStyleRuleSlot(data, { + sectionId: "skills", + sectionType: "skills", + slot: "item", + }), + ).toEqual({ padding: 72, marginTop: -72, rowGap: -12, borderWidth: 0 }); + }); + + it("resolves visible text, opacity, and border style properties", () => { + const data = createResumeData([ + { + id: "rich-link-style", + label: "Rich Link Style", + enabled: true, + target: { scope: "global" }, + slots: { + richLink: { + color: "rgba(21, 93, 252, 1)", + textDecoration: "underline", + textDecorationColor: "rgba(220, 38, 38, 1)", + textDecorationStyle: "dashed", + fontStyle: "italic", + lineHeight: 9, + letterSpacing: -100, + textAlign: "right", + textTransform: "uppercase", + opacity: 2, + borderStyle: "dotted", + }, + }, + }, + ]); + + expect( + resolveStyleRuleSlot(data, { + sectionId: "experience", + sectionType: "experience", + slot: "richLink", + }), + ).toEqual({ + color: "#155dfc", + textDecoration: "underline", + textDecorationColor: "#dc2626", + textDecorationStyle: "dashed", + fontStyle: "italic", + lineHeight: 4, + letterSpacing: -16, + textAlign: "right", + textTransform: "uppercase", + opacity: 1, + borderStyle: "dotted", + }); + }); +}); diff --git a/packages/pdf/src/templates/shared/style-rules.ts b/packages/pdf/src/templates/shared/style-rules.ts new file mode 100644 index 000000000..1d946b65f --- /dev/null +++ b/packages/pdf/src/templates/shared/style-rules.ts @@ -0,0 +1,134 @@ +import type { Style } from "@react-pdf/types"; +import type { + CustomSectionType, + ResumeData, + SectionType, + StyleIntent, + StyleSlot, +} from "@reactive-resume/schema/resume/data"; +import { rgbaStringToHex } from "@reactive-resume/utils/color"; + +export type SectionStyleRuleContext = { + sectionId: string; + sectionType?: CustomSectionType | undefined; +}; + +export type ResolveStyleRuleSlotOptions = SectionStyleRuleContext & { + slot: StyleSlot; +}; + +const spacingProperties = [ + "padding", + "paddingTop", + "paddingRight", + "paddingBottom", + "paddingLeft", + "marginTop", + "marginRight", + "marginBottom", + "marginLeft", + "rowGap", + "columnGap", + "borderWidth", + "borderRadius", +] as const; + +const colorProperties = ["color", "backgroundColor", "borderColor", "textDecorationColor"] as const; + +const spacingPropertyRange = (property: (typeof spacingProperties)[number]) => { + if (property === "borderWidth" || property === "borderRadius") + return { min: 0, max: property === "borderWidth" ? 24 : 72 }; + + return { min: -72, max: 72 }; +}; + +const builtInSectionTypes = new Set([ + "profiles", + "experience", + "education", + "projects", + "skills", + "languages", + "interests", + "awards", + "certifications", + "publications", + "volunteer", + "references", +]); + +const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value)); + +const toPdfColor = (value: string) => { + try { + return rgbaStringToHex(value); + } catch { + return value; + } +}; + +const toStyle = (intent: StyleIntent | undefined): Style => { + if (!intent) return {}; + + const style: Style = {}; + + for (const property of colorProperties) { + const value = intent[property]; + if (value) style[property] = toPdfColor(value); + } + + if (intent.opacity !== undefined) style.opacity = clamp(intent.opacity, 0, 1); + if (intent.fontSize !== undefined) style.fontSize = clamp(intent.fontSize, 6, 48); + if (intent.fontWeight !== undefined) style.fontWeight = intent.fontWeight; + if (intent.fontStyle !== undefined) style.fontStyle = intent.fontStyle; + if (intent.lineHeight !== undefined) style.lineHeight = clamp(intent.lineHeight, 0.5, 4); + if (intent.letterSpacing !== undefined) style.letterSpacing = clamp(intent.letterSpacing, -16, 16); + if (intent.textDecoration !== undefined) style.textDecoration = intent.textDecoration; + if (intent.textDecorationStyle !== undefined) style.textDecorationStyle = intent.textDecorationStyle; + if (intent.textAlign !== undefined) style.textAlign = intent.textAlign; + if (intent.textTransform !== undefined) style.textTransform = intent.textTransform; + if (intent.borderStyle !== undefined) style.borderStyle = intent.borderStyle; + + for (const property of spacingProperties) { + const value = intent[property]; + const range = spacingPropertyRange(property); + if (value !== undefined) style[property] = clamp(value, range.min, range.max); + } + + return style; +}; + +export const getSectionStyleRuleContext = (data: ResumeData, sectionId: string): SectionStyleRuleContext => { + if (sectionId === "summary") return { sectionId, sectionType: "summary" }; + if (builtInSectionTypes.has(sectionId as SectionType)) { + return { sectionId, sectionType: sectionId as SectionType }; + } + + const customSection = data.customSections.find((section) => section.id === sectionId); + + return { sectionId, sectionType: customSection?.type }; +}; + +export const resolveStyleRuleSlot = (data: ResumeData, options: ResolveStyleRuleSlotOptions): Style => { + const matchingRules = (data.metadata.styleRules ?? []).filter((rule) => { + if (!rule.enabled) return false; + if (!rule.slots[options.slot]) return false; + + if (rule.target.scope === "global") return true; + if (rule.target.scope === "sectionType") return rule.target.sectionType === options.sectionType; + if (rule.target.scope === "sectionId") return rule.target.sectionId === options.sectionId; + + return false; + }); + + const specificity = { global: 0, sectionType: 1, sectionId: 2 } satisfies Record< + "global" | "sectionType" | "sectionId", + number + >; + + const bySpecificity = [...matchingRules].sort((a, b) => { + return specificity[a.target.scope] - specificity[b.target.scope]; + }); + + return Object.assign({}, ...bySpecificity.map((rule) => toStyle(rule.slots[options.slot]))); +}; diff --git a/packages/resume/package.json b/packages/resume/package.json index 8f69c69c8..d7df3d5c1 100644 --- a/packages/resume/package.json +++ b/packages/resume/package.json @@ -21,7 +21,7 @@ }, "devDependencies": { "@reactive-resume/config": "workspace:*", - "@typescript/native-preview": "7.0.0-dev.20260526.1", + "@typescript/native-preview": "7.0.0-dev.20260527.1", "typescript": "^6.0.3" } } diff --git a/packages/schema/package.json b/packages/schema/package.json index 29884600c..ee6705666 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -22,7 +22,7 @@ }, "devDependencies": { "@reactive-resume/config": "workspace:*", - "@typescript/native-preview": "7.0.0-dev.20260526.1", + "@typescript/native-preview": "7.0.0-dev.20260527.1", "typescript": "^6.0.3" } } diff --git a/packages/schema/src/resume/data.test.ts b/packages/schema/src/resume/data.test.ts index 04eb59579..ba05ff083 100644 --- a/packages/schema/src/resume/data.test.ts +++ b/packages/schema/src/resume/data.test.ts @@ -8,6 +8,8 @@ import { pictureSchema, resumeDataSchema, skillItemSchema, + styleRuleSchema, + styleRulesSchema, websiteSchema, } from "./data"; import { defaultResumeData } from "./default"; @@ -270,3 +272,108 @@ describe("pageSchema", () => { } }); }); + +describe("styleRulesSchema", () => { + 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("accepts global, section-type, and section-id targets", () => { + const rules = [ + { + id: "global-headings", + label: "Global headings", + enabled: true, + target: { scope: "global" }, + slots: { + heading: { + color: "rgba(220, 38, 38, 1)", + fontWeight: "700", + fontStyle: "italic", + lineHeight: 1.35, + letterSpacing: -0.5, + textDecoration: "underline", + textDecorationColor: "rgba(220, 38, 38, 1)", + textDecorationStyle: "dotted", + textAlign: "center", + textTransform: "uppercase", + opacity: 0.85, + }, + }, + }, + { + id: "experience-items", + label: "Experience items", + enabled: true, + target: { scope: "sectionType", sectionType: "experience" }, + slots: { + item: { + backgroundColor: "rgba(245, 245, 245, 1)", + padding: -6, + marginBottom: -4, + rowGap: -2, + borderStyle: "dashed", + }, + }, + }, + { + id: "custom-section", + label: "Custom section", + enabled: true, + target: { scope: "sectionId", sectionId: "94ddf90f-46ef-4b0a-9a99-2ed118af52dd" }, + slots: { richList: { rowGap: 6 } }, + }, + ]; + + expect(styleRulesSchema.safeParse(rules).success).toBe(true); + }); + + it("rejects section-item targets for v1", () => { + expect( + styleRuleSchema.safeParse({ + id: "item-style", + label: "Item Style", + enabled: true, + target: { scope: "sectionItem", sectionId: "experience", itemId: "item-1" }, + slots: { item: { color: "rgba(0, 0, 0, 1)" } }, + }).success, + ).toBe(false); + }); + + it("rejects unsupported raw layout properties", () => { + expect( + styleRuleSchema.safeParse({ + id: "unsafe", + label: "Unsafe", + enabled: true, + target: { scope: "global" }, + slots: { section: { position: "absolute" } }, + }).success, + ).toBe(false); + }); + + it("rejects the removed rich-text marker slot", () => { + expect( + styleRuleSchema.safeParse({ + id: "marker", + label: "Marker", + enabled: true, + target: { scope: "global" }, + slots: { richListItemMarker: { color: "rgba(21, 93, 252, 1)" } }, + }).success, + ).toBe(false); + }); + + it("rejects unsafe visible style values outside the v1 ranges", () => { + expect( + styleRuleSchema.safeParse({ + id: "unsafe-visible", + label: "Unsafe Visible", + enabled: true, + target: { scope: "global" }, + slots: { heading: { opacity: 2, lineHeight: 10, letterSpacing: -17 } }, + }).success, + ).toBe(false); + }); +}); diff --git a/packages/schema/src/resume/data.ts b/packages/schema/src/resume/data.ts index 57f18d265..d0eba2e48 100644 --- a/packages/schema/src/resume/data.ts +++ b/packages/schema/src/resume/data.ts @@ -491,6 +491,102 @@ export const typographySchema = z.object({ heading: typographyItemSchema.describe("The typography for the headings of the resume."), }); +export const styleSlotSchema = z.enum([ + "section", + "heading", + "item", + "text", + "secondaryText", + "link", + "icon", + "level", + "richParagraph", + "richList", + "richListItemRow", + "richListItemContent", + "richLink", + "richBold", + "richMark", +]); + +export type StyleSlot = z.infer; + +export const styleIntentSchema = z + .strictObject({ + color: z.string().optional(), + backgroundColor: z.string().optional(), + borderColor: z.string().optional(), + textDecorationColor: z.string().optional(), + opacity: z.number().min(0).max(1).optional(), + fontSize: z.number().min(6).max(48).optional(), + fontWeight: fontWeightSchema.optional(), + fontStyle: z.enum(["normal", "italic"]).optional(), + lineHeight: z.number().min(0.5).max(4).optional(), + letterSpacing: z.number().min(-16).max(16).optional(), + textDecoration: z.enum(["none", "underline", "line-through"]).optional(), + textDecorationStyle: z.enum(["solid", "dashed", "dotted"]).optional(), + textAlign: z.enum(["left", "center", "right", "justify"]).optional(), + textTransform: z.enum(["none", "uppercase", "lowercase", "capitalize"]).optional(), + padding: z.number().min(-72).max(72).optional(), + paddingTop: z.number().min(-72).max(72).optional(), + paddingRight: z.number().min(-72).max(72).optional(), + paddingBottom: z.number().min(-72).max(72).optional(), + paddingLeft: z.number().min(-72).max(72).optional(), + marginTop: z.number().min(-72).max(72).optional(), + marginRight: z.number().min(-72).max(72).optional(), + marginBottom: z.number().min(-72).max(72).optional(), + marginLeft: z.number().min(-72).max(72).optional(), + rowGap: z.number().min(-72).max(72).optional(), + columnGap: z.number().min(-72).max(72).optional(), + borderStyle: z.enum(["solid", "dashed", "dotted"]).optional(), + borderWidth: z.number().min(0).optional(), + borderRadius: z.number().min(0).optional(), + }) + .describe("Constrained visual style intent that can be safely translated to React PDF styles."); + +export type StyleIntent = z.infer; + +export 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(), + }) + .refine((slots) => Object.values(slots).some(Boolean), { + message: "At least one style slot must be configured.", + }); + +export const styleRuleTargetSchema = z.discriminatedUnion("scope", [ + z.strictObject({ scope: z.literal("global") }), + z.strictObject({ scope: z.literal("sectionType"), sectionType: sectionTypeSchema }), + z.strictObject({ scope: z.literal("sectionId"), sectionId: z.string().min(1) }), +]); + +export const styleRuleSchema = z.strictObject({ + id: z.string().min(1).describe("Unique identifier for this style rule."), + label: z.string().catch("").describe("Human-readable label for this style rule."), + enabled: z.boolean().catch(true).describe("Whether this style rule should affect PDF rendering."), + target: styleRuleTargetSchema.describe("The resume content this style rule applies to."), + slots: styleRuleSlotsSchema.describe("The semantic style slots configured by this rule."), +}); + +export const styleRulesSchema = z.array(styleRuleSchema).catch([]); + +export type StyleRule = z.infer; +export type StyleRuleTarget = z.infer; + export const metadataSchema = z.object({ template: templateSchema .catch("onyx") @@ -512,6 +608,9 @@ export const metadataSchema = z.object({ .describe( "Personal notes for the resume. Can be used to add any additional information or instructions for the resume. These notes are not displayed on the resume, they are only visible to the author of the resume when editing the resume. This should be a HTML-formatted string.", ), + styleRules: styleRulesSchema.describe( + "Structured style rules that target semantic resume sections and slots for React PDF rendering.", + ), }); export const resumeDataSchema = z.looseObject({ diff --git a/packages/schema/src/resume/default.ts b/packages/schema/src/resume/default.ts index e0e00c14e..84de0d7b4 100644 --- a/packages/schema/src/resume/default.ts +++ b/packages/schema/src/resume/default.ts @@ -150,5 +150,6 @@ export const defaultResumeData: ResumeData = { }, }, notes: "", + styleRules: [], }, }; diff --git a/packages/schema/src/resume/sample.ts b/packages/schema/src/resume/sample.ts index 1280d9f28..4f252cbf5 100644 --- a/packages/schema/src/resume/sample.ts +++ b/packages/schema/src/resume/sample.ts @@ -580,5 +580,6 @@ export const sampleResumeData: ResumeData = { }, }, notes: "", + styleRules: [], }, }; diff --git a/packages/ui/package.json b/packages/ui/package.json index 2998874c3..78b60598c 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -31,7 +31,7 @@ "react": "^19.2.6", "react-dom": "^19.2.6", "react-resizable-panels": "^4.11.2", - "shadcn": "^4.8.0", + "shadcn": "^4.8.1", "sonner": "^2.0.7", "tw-animate-css": "^1.4.0" }, @@ -42,9 +42,9 @@ "@types/js-cookie": "^3.0.6", "@types/react": "^19.2.15", "@types/react-dom": "^19.2.3", - "@typescript/native-preview": "7.0.0-dev.20260526.1", + "@typescript/native-preview": "7.0.0-dev.20260527.1", "postcss": "^8.5.15", - "react-doctor": "^0.2.6", + "react-doctor": "^0.2.9", "tailwindcss": "^4.3.0", "typescript": "^6.0.3" } diff --git a/packages/utils/package.json b/packages/utils/package.json index 906aa59de..ceaac73ae 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -30,7 +30,7 @@ "@sindresorhus/slugify": "^3.0.0", "@uiw/color-convert": "^2.10.3", "clsx": "^2.1.1", - "dompurify": "^3.4.5", + "dompurify": "^3.4.6", "tailwind-merge": "^3.6.0", "unique-names-generator": "^4.7.1", "uuid": "^14.0.0", @@ -39,7 +39,7 @@ "devDependencies": { "@reactive-resume/config": "workspace:*", "@types/node": "^25.9.1", - "@typescript/native-preview": "7.0.0-dev.20260526.1", + "@typescript/native-preview": "7.0.0-dev.20260527.1", "typescript": "^6.0.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a59ad6d51..05ac9e218 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -57,8 +57,8 @@ importers: specifier: ^22.2.1 version: 22.2.1 turbo: - specifier: ^2.9.14 - version: 2.9.14 + specifier: ^2.9.15 + version: 2.9.15 typescript: specifier: ^6.0.3 version: 6.0.3 @@ -81,8 +81,8 @@ importers: specifier: ^2.0.48 version: 2.0.48(zod@4.4.3) '@aws-sdk/client-s3': - specifier: ^3.1053.0 - version: 3.1053.0 + specifier: ^3.1054.0 + version: 3.1054.0 '@better-auth/api-key': specifier: ^1.6.11 version: 1.6.11(@better-auth/core@1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(better-auth@1.6.11(@opentelemetry/api@1.9.1)(drizzle-orm@1.0.0-rc.3(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(effect@4.0.0-beta.70)(pg@8.21.0)(zod@4.4.3))(next@16.2.6(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(pg@8.21.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(vitest@4.1.7)) @@ -90,8 +90,8 @@ importers: specifier: ^1.6.11 version: 1.6.11(@better-auth/core@1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(drizzle-orm@1.0.0-rc.3(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(effect@4.0.0-beta.70)(pg@8.21.0)(zod@4.4.3)) '@better-auth/infra': - specifier: ^0.2.8 - version: 0.2.8(97af957868093b766f74c847443b5514) + specifier: ^0.2.10 + version: 0.2.10(97af957868093b766f74c847443b5514) '@better-auth/oauth-provider': specifier: ^1.6.11 version: 1.6.11(@better-auth/core@1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-auth@1.6.11(@opentelemetry/api@1.9.1)(drizzle-orm@1.0.0-rc.3(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(effect@4.0.0-beta.70)(pg@8.21.0)(zod@4.4.3))(next@16.2.6(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(pg@8.21.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(vitest@4.1.7))(better-call@1.3.5(zod@4.4.3)) @@ -171,8 +171,8 @@ importers: specifier: ^7.1.5 version: 7.1.5 dompurify: - specifier: ^3.4.5 - version: 3.4.5 + specifier: ^3.4.6 + version: 3.4.6 dotenv: specifier: ^17.4.2 version: 17.4.2 @@ -198,8 +198,8 @@ importers: specifier: ^7.1.0 version: 7.1.0 nodemailer: - specifier: ^8.0.8 - version: 8.0.8 + specifier: ^8.0.9 + version: 8.0.9 ollama-ai-provider-v2: specifier: ^3.5.1 version: 3.5.1(ai@6.0.191(zod@4.4.3))(zod@4.4.3) @@ -213,8 +213,8 @@ importers: specifier: ^19.2.6 version: 19.2.6 react-email: - specifier: ^6.3.3 - version: 6.3.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + specifier: ^6.4.0 + version: 6.4.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react-pdf-html: specifier: ^2.1.5 version: 2.1.5(@react-pdf/renderer@4.5.1(react@19.2.6))(react@19.2.6) @@ -250,14 +250,14 @@ importers: specifier: ^19.2.15 version: 19.2.15 '@typescript/native-preview': - specifier: 7.0.0-dev.20260526.1 - version: 7.0.0-dev.20260526.1 + specifier: 7.0.0-dev.20260527.1 + version: 7.0.0-dev.20260527.1 react-doctor: - specifier: ^0.2.6 - version: 0.2.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + specifier: ^0.2.9 + version: 0.2.9(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(eslint@10.4.0(jiti@2.7.0)) tsdown: specifier: ^0.22.0 - version: 0.22.0(@typescript/native-preview@7.0.0-dev.20260526.1)(oxc-resolver@11.19.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(tsx@4.22.3)(typescript@6.0.3) + version: 0.22.0(@typescript/native-preview@7.0.0-dev.20260527.1)(oxc-resolver@11.19.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(tsx@4.22.3)(typescript@6.0.3) tsx: specifier: ^4.22.3 version: 4.22.3 @@ -280,8 +280,8 @@ importers: specifier: ^1.6.11 version: 1.6.11(@better-auth/core@1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(better-auth@1.6.11(@opentelemetry/api@1.9.1)(drizzle-orm@1.0.0-rc.3(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(effect@4.0.0-beta.70)(pg@8.21.0)(zod@4.4.3))(next@16.2.6(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(pg@8.21.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(vitest@4.1.7)) '@better-auth/infra': - specifier: ^0.2.8 - version: 0.2.8(97af957868093b766f74c847443b5514) + specifier: ^0.2.10 + version: 0.2.10(97af957868093b766f74c847443b5514) '@better-auth/oauth-provider': specifier: ^1.6.11 version: 1.6.11(@better-auth/core@1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-auth@1.6.11(@opentelemetry/api@1.9.1)(drizzle-orm@1.0.0-rc.3(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(effect@4.0.0-beta.70)(pg@8.21.0)(zod@4.4.3))(next@16.2.6(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(pg@8.21.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(vitest@4.1.7))(better-call@1.3.5(zod@4.4.3)) @@ -352,8 +352,8 @@ importers: specifier: ^4.3.0 version: 4.3.0(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(tsx@4.22.3)(yaml@2.9.0)) '@tanstack/react-form': - specifier: ^1.32.0 - version: 1.32.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + specifier: ^1.32.1 + version: 1.32.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@tanstack/react-hotkeys': specifier: ^0.10.0 version: 0.10.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -503,8 +503,8 @@ importers: specifier: ^19.2.3 version: 19.2.3(@types/react@19.2.15) '@typescript/native-preview': - specifier: 7.0.0-dev.20260526.1 - version: 7.0.0-dev.20260526.1 + specifier: 7.0.0-dev.20260527.1 + version: 7.0.0-dev.20260527.1 '@vitejs/plugin-react': specifier: ^6.0.2 version: 6.0.2(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.0.2)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(tsx@4.22.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(tsx@4.22.3)(yaml@2.9.0)) @@ -515,8 +515,8 @@ importers: specifier: ^1.0.0 version: 1.0.0 react-doctor: - specifier: ^0.2.6 - version: 0.2.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + specifier: ^0.2.9 + version: 0.2.9(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(eslint@10.4.0(jiti@2.7.0)) typescript: specifier: ^6.0.3 version: 6.0.3 @@ -549,8 +549,8 @@ importers: specifier: workspace:* version: link:../config '@typescript/native-preview': - specifier: 7.0.0-dev.20260526.1 - version: 7.0.0-dev.20260526.1 + specifier: 7.0.0-dev.20260527.1 + version: 7.0.0-dev.20260527.1 typescript: specifier: ^6.0.3 version: 6.0.3 @@ -570,8 +570,8 @@ importers: specifier: ^2.0.48 version: 2.0.48(zod@4.4.3) '@aws-sdk/client-s3': - specifier: ^3.1053.0 - version: 3.1053.0 + specifier: ^3.1054.0 + version: 3.1054.0 '@orpc/client': specifier: ^1.14.3 version: 1.14.3(@opentelemetry/api@1.9.1) @@ -624,8 +624,8 @@ importers: specifier: ^1.47.0 version: 1.47.0 ioredis: - specifier: ^5.10.1 - version: 5.10.1 + specifier: ^5.11.0 + version: 5.11.0 ollama-ai-provider-v2: specifier: ^3.5.1 version: 3.5.1(ai@6.0.191(zod@4.4.3))(zod@4.4.3) @@ -652,8 +652,8 @@ importers: specifier: ^6.0.0 version: 6.0.0 '@typescript/native-preview': - specifier: 7.0.0-dev.20260526.1 - version: 7.0.0-dev.20260526.1 + specifier: 7.0.0-dev.20260527.1 + version: 7.0.0-dev.20260527.1 typescript: specifier: ^6.0.3 version: 6.0.3 @@ -667,8 +667,8 @@ importers: specifier: ^1.6.11 version: 1.6.11(@better-auth/core@1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(drizzle-orm@1.0.0-rc.3(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(effect@4.0.0-beta.70)(pg@8.21.0)(zod@4.4.3)) '@better-auth/infra': - specifier: ^0.2.8 - version: 0.2.8(97af957868093b766f74c847443b5514) + specifier: ^0.2.10 + version: 0.2.10(97af957868093b766f74c847443b5514) '@better-auth/oauth-provider': specifier: ^1.6.11 version: 1.6.11(@better-auth/core@1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-auth@1.6.11(@opentelemetry/api@1.9.1)(drizzle-orm@1.0.0-rc.3(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(effect@4.0.0-beta.70)(pg@8.21.0)(zod@4.4.3))(next@16.2.6(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(pg@8.21.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(vitest@4.1.7))(better-call@1.3.5(zod@4.4.3)) @@ -716,8 +716,8 @@ importers: specifier: ^19.2.15 version: 19.2.15 '@typescript/native-preview': - specifier: 7.0.0-dev.20260526.1 - version: 7.0.0-dev.20260526.1 + specifier: 7.0.0-dev.20260527.1 + version: 7.0.0-dev.20260527.1 typescript: specifier: ^6.0.3 version: 6.0.3 @@ -749,8 +749,8 @@ importers: specifier: ^8.20.0 version: 8.20.0 '@typescript/native-preview': - specifier: 7.0.0-dev.20260526.1 - version: 7.0.0-dev.20260526.1 + specifier: 7.0.0-dev.20260527.1 + version: 7.0.0-dev.20260527.1 drizzle-kit: specifier: 1.0.0-rc.3 version: 1.0.0-rc.3 @@ -774,8 +774,8 @@ importers: specifier: workspace:* version: link:../config '@typescript/native-preview': - specifier: 7.0.0-dev.20260526.1 - version: 7.0.0-dev.20260526.1 + specifier: 7.0.0-dev.20260527.1 + version: 7.0.0-dev.20260527.1 typescript: specifier: ^6.0.3 version: 6.0.3 @@ -786,18 +786,18 @@ importers: specifier: workspace:* version: link:../env nodemailer: - specifier: ^8.0.8 - version: 8.0.8 + specifier: ^8.0.9 + version: 8.0.9 react: specifier: ^19.2.6 version: 19.2.6 react-email: - specifier: ^6.3.3 - version: 6.3.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + specifier: ^6.4.0 + version: 6.4.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) devDependencies: '@react-email/ui': - specifier: ^6.3.3 - version: 6.3.3(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + specifier: ^6.4.0 + version: 6.4.0(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@reactive-resume/config': specifier: workspace:* version: link:../config @@ -808,11 +808,11 @@ importers: specifier: ^19.2.15 version: 19.2.15 '@typescript/native-preview': - specifier: 7.0.0-dev.20260526.1 - version: 7.0.0-dev.20260526.1 + specifier: 7.0.0-dev.20260527.1 + version: 7.0.0-dev.20260527.1 react-doctor: - specifier: ^0.2.6 - version: 0.2.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + specifier: ^0.2.9 + version: 0.2.9(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(eslint@10.4.0(jiti@2.7.0)) typescript: specifier: ^6.0.3 version: 6.0.3 @@ -839,8 +839,8 @@ importers: specifier: ^25.9.1 version: 25.9.1 '@typescript/native-preview': - specifier: 7.0.0-dev.20260526.1 - version: 7.0.0-dev.20260526.1 + specifier: 7.0.0-dev.20260527.1 + version: 7.0.0-dev.20260527.1 typescript: specifier: ^6.0.3 version: 6.0.3 @@ -855,8 +855,8 @@ importers: specifier: workspace:* version: link:../config '@typescript/native-preview': - specifier: 7.0.0-dev.20260526.1 - version: 7.0.0-dev.20260526.1 + specifier: 7.0.0-dev.20260527.1 + version: 7.0.0-dev.20260527.1 typescript: specifier: ^6.0.3 version: 6.0.3 @@ -880,8 +880,8 @@ importers: specifier: workspace:* version: link:../config '@typescript/native-preview': - specifier: 7.0.0-dev.20260526.1 - version: 7.0.0-dev.20260526.1 + specifier: 7.0.0-dev.20260527.1 + version: 7.0.0-dev.20260527.1 typescript: specifier: ^6.0.3 version: 6.0.3 @@ -914,8 +914,8 @@ importers: specifier: workspace:* version: link:../config '@typescript/native-preview': - specifier: 7.0.0-dev.20260526.1 - version: 7.0.0-dev.20260526.1 + specifier: 7.0.0-dev.20260527.1 + version: 7.0.0-dev.20260527.1 typescript: specifier: ^6.0.3 version: 6.0.3 @@ -966,11 +966,11 @@ importers: specifier: ^19.2.15 version: 19.2.15 '@typescript/native-preview': - specifier: 7.0.0-dev.20260526.1 - version: 7.0.0-dev.20260526.1 + specifier: 7.0.0-dev.20260527.1 + version: 7.0.0-dev.20260527.1 react-doctor: - specifier: ^0.2.6 - version: 0.2.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + specifier: ^0.2.9 + version: 0.2.9(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(eslint@10.4.0(jiti@2.7.0)) typescript: specifier: ^6.0.3 version: 6.0.3 @@ -991,8 +991,8 @@ importers: specifier: workspace:* version: link:../config '@typescript/native-preview': - specifier: 7.0.0-dev.20260526.1 - version: 7.0.0-dev.20260526.1 + specifier: 7.0.0-dev.20260527.1 + version: 7.0.0-dev.20260527.1 typescript: specifier: ^6.0.3 version: 6.0.3 @@ -1007,8 +1007,8 @@ importers: specifier: workspace:* version: link:../config '@typescript/native-preview': - specifier: 7.0.0-dev.20260526.1 - version: 7.0.0-dev.20260526.1 + specifier: 7.0.0-dev.20260527.1 + version: 7.0.0-dev.20260527.1 typescript: specifier: ^6.0.3 version: 6.0.3 @@ -1052,8 +1052,8 @@ importers: specifier: ^4.11.2 version: 4.11.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6) shadcn: - specifier: ^4.8.0 - version: 4.8.0(@types/node@25.9.1)(babel-plugin-macros@3.1.0)(typescript@6.0.3) + specifier: ^4.8.1 + version: 4.8.1(@types/node@25.9.1)(babel-plugin-macros@3.1.0)(typescript@6.0.3) sonner: specifier: ^2.0.7 version: 2.0.7(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -1080,14 +1080,14 @@ importers: specifier: ^19.2.3 version: 19.2.3(@types/react@19.2.15) '@typescript/native-preview': - specifier: 7.0.0-dev.20260526.1 - version: 7.0.0-dev.20260526.1 + specifier: 7.0.0-dev.20260527.1 + version: 7.0.0-dev.20260527.1 postcss: specifier: ^8.5.15 version: 8.5.15 react-doctor: - specifier: ^0.2.6 - version: 0.2.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + specifier: ^0.2.9 + version: 0.2.9(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(eslint@10.4.0(jiti@2.7.0)) tailwindcss: specifier: ^4.3.0 version: 4.3.0 @@ -1107,8 +1107,8 @@ importers: specifier: ^2.1.1 version: 2.1.1 dompurify: - specifier: ^3.4.5 - version: 3.4.5 + specifier: ^3.4.6 + version: 3.4.6 tailwind-merge: specifier: ^3.6.0 version: 3.6.0 @@ -1129,8 +1129,8 @@ importers: specifier: ^25.9.1 version: 25.9.1 '@typescript/native-preview': - specifier: 7.0.0-dev.20260526.1 - version: 7.0.0-dev.20260526.1 + specifier: 7.0.0-dev.20260527.1 + version: 7.0.0-dev.20260527.1 typescript: specifier: ^6.0.3 version: 6.0.3 @@ -1147,8 +1147,8 @@ importers: specifier: ^8.20.0 version: 8.20.0 '@typescript/native-preview': - specifier: 7.0.0-dev.20260526.1 - version: 7.0.0-dev.20260526.1 + specifier: 7.0.0-dev.20260527.1 + version: 7.0.0-dev.20260527.1 drizzle-orm: specifier: 1.0.0-rc.3 version: 1.0.0-rc.3(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(effect@4.0.0-beta.70)(pg@8.21.0)(zod@4.4.3) @@ -1241,84 +1241,84 @@ packages: '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - '@aws-sdk/client-s3@3.1053.0': - resolution: {integrity: sha512-/oGxoB6p1Nqs935Blt+v1o+anSCEf2n3RjIrcLz84i4cn2Gr+Z7JpDdUkG5+74r5ctqEPG7k/phTGbJ9fNKnHg==} + '@aws-sdk/client-s3@3.1054.0': + resolution: {integrity: sha512-2ue7uVqaHYX4rytkcrLySYU/m/ZlRbL8KojWefbR24B0/TcFkqN2IovpBFrnmla/dtZAn9eVSlhHeEddOghZ5w==} engines: {node: '>=20.0.0'} - '@aws-sdk/core@3.974.13': - resolution: {integrity: sha512-+Y5/4tHki0uYgyx8eun146DegRVQBpdKGK5RbV0FTKJPpaKTchvqVxrrRFK6Wk0JksO4iAZKw3eqxGEIwtO98w==} + '@aws-sdk/core@3.974.14': + resolution: {integrity: sha512-ppamm04uoj3hhNO5IlQSs5D6rWX1fWkzcn6a4pZrojk8Y6ObY9wzLDdT/Eq3gv6O9hOebi9tYTNB8b8fQj9XJw==} engines: {node: '>=20.0.0'} '@aws-sdk/crc64-nvme@3.972.9': resolution: {integrity: sha512-P+QGozmXn2mZZI7sDgk+aUm+RTI61MPSFB+Ir2vjEjEbEsE4e7hYtzrDvAUxZy9ko81h53e11+F/GYlvwDkaOQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-env@3.972.39': - resolution: {integrity: sha512-29wX9zpAvEt1vcj0psha+y6ygBHy2V/S72mp6e7q0KARLWXq+pwE/lR6qGkwknQvruh52lXvlqZIga8Hdxkucw==} + '@aws-sdk/credential-provider-env@3.972.40': + resolution: {integrity: sha512-jjT0p0Y7KZtcvExYiPCLJnqM9lkXDV1KBEg/13OE2DXv/9batzlyJHVKUEnRNJccY0O2Sul17E1su38CgdBhGQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-http@3.972.41': - resolution: {integrity: sha512-IA3CQTjtJkb6u1H4mE4936c8OPBMa9Jggtwe8U2Mqw/vvb/tZ5Ebd0mcZcX0uKWQhOyYo/+qNIwkV5Xh+FeJJA==} + '@aws-sdk/credential-provider-http@3.972.42': + resolution: {integrity: sha512-+3fsKtWybe5BjKEUA3/07oh7Ayfd82IED2+gyyaVfS/4PU78E3TaOQxSGOJ1t7Imefoidw/ne9QA7apX8wEnJg==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-ini@3.972.43': - resolution: {integrity: sha512-4mzII+3mZEVXXE1xzrLQrCJL7/r62A63bA6SVzZoNL5rqCJghpf+xgGltVrIBBs0n+mOZBKrQl2tRREtvZ5l6A==} + '@aws-sdk/credential-provider-ini@3.972.44': + resolution: {integrity: sha512-gZFw5wBefCIPg9vpT+gV5FdhfNKhYTVDZa1IsZCcn3SRoYUOJ/E05vwIogkJoonqBL0ttBGi5vhthX7xceekRg==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-login@3.972.43': - resolution: {integrity: sha512-HG7kQCwXtbv3oBV61Ins0oNX8KKyvrMqqRkb6ZiAfQHbMuHaiNaEb2KnpKLPkNpqImSBK82UkVE/kaY6IfWikA==} + '@aws-sdk/credential-provider-login@3.972.44': + resolution: {integrity: sha512-QqEGHfQeZgUDqh7zpqHufrZ8T644ELEWvB+4gUdewLyRw4IRF+6CJqeQuRWqucZdQzoQeMh7fNAD9BWxFAdNig==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-node@3.972.44': - resolution: {integrity: sha512-sDaBIT0yrNNIPfvlsiTCmANm07zKju+ipWODjEXgZlsjMeIJR3LVp7RDyAOzUoAsTbDfYKDWp+i5WrFiQP6rmQ==} + '@aws-sdk/credential-provider-node@3.972.45': + resolution: {integrity: sha512-3YCv52ExXIRz3LAVNysevd+s7akSpg9dl39v9LJ7dOQH+s5rHi3jMZYQyxwMmglxQGMuzYRfQ0o1VSP2UOlIRw==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-process@3.972.39': - resolution: {integrity: sha512-2k/amBifLd75eXNwgvPw/2lKYSQ3NhvHQgkVKVjfUq13/eJ3JRtHmznuFenn74OK3sSfp4SMy1YB2w+UVXoKqA==} + '@aws-sdk/credential-provider-process@3.972.40': + resolution: {integrity: sha512-cXaozlgJCOwmE6D7x4npcPdyk7kiFZdrGjN3D6tXXtItJJMNGPafDfAJn4YQmciMooG/X+b0Y6RTqdVVMx26jg==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-sso@3.972.43': - resolution: {integrity: sha512-LPc3+Y4vhH1T4x6CMqwCM6hk5+SRf/Lwmgm8INm95wxTtIRHcMwQUVkDzWu4Iw/RSncxYM2BC01OrYbxOPZvyg==} + '@aws-sdk/credential-provider-sso@3.972.44': + resolution: {integrity: sha512-YePoj5kQuPmE0MHnyftXCfsO8ZSBd2kDr50XEIUrdejSbGFlayYvUuCohdb8drhGhPm6b65o7H1eC26EZhwUvA==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-web-identity@3.972.43': - resolution: {integrity: sha512-wQtL34lUD/09VXjwAUo2T+I3aEXRDxMB3DKmTJL/Zj0Gi6sLDTrVhae1XVt01yzkquOWajI/sZW72JGDZ1ciTw==} + '@aws-sdk/credential-provider-web-identity@3.972.44': + resolution: {integrity: sha512-Ys/JJe++8Z2Y5meR1taMBaVcrGBA0/XsVTQR+qOKZbdNyg+8Jlv5rYZSwh8SqEHY00goSOZy7PHzZ2rLNQxDLg==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-bucket-endpoint@3.972.15': - resolution: {integrity: sha512-O2HDANa+MrvbxpaRVQDiH3T13uAa9AkMjKyZmDygwauAmmvqZ5B0iRmKW+fuVGW6NPXuyXurFgIx69lSvmAWGA==} + '@aws-sdk/middleware-bucket-endpoint@3.972.16': + resolution: {integrity: sha512-FhasMTBDBmMN7EEa1hUeHwo5p5Mv3Dm8w0VEbdXX/6ola/uyhRuJt8zGkH09mLTmab20USTzEpPqyqEoe1MqNg==} engines: {node: '>=20.0.0'} '@aws-sdk/middleware-expect-continue@3.972.13': resolution: {integrity: sha512-sHiqIFg8o2ipT7t40B89Vj0ubSUtY6OSt/+Ee/OXhHch5K4+81zP2+QX8Lkc/nJ2QSmCySxOke7TEbmX69fe2g==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-flexible-checksums@3.974.21': - resolution: {integrity: sha512-alAu9heyiBK/OmRNXVxq8mmPTgeW2AQ6EYjRsI38kPZa1MZvt2Jh+BlGq7/GG9OVXOaEgD7DlGj/Lzfy5OmuEg==} + '@aws-sdk/middleware-flexible-checksums@3.974.22': + resolution: {integrity: sha512-ot1kZ1JGHUxcXPOARhej/n/+Odfx9VPt60pNrUq8Lf/U2blIF3+uj5v56gw76VD70dZvrfeLNo9jKz6pQJfOlA==} engines: {node: '>=20.0.0'} '@aws-sdk/middleware-location-constraint@3.972.11': resolution: {integrity: sha512-hkfspNUP4criAH6ton6BGKgnm5dZx+7bUOy1YqlTfejDeUPAM23D81q/IX+hdlS3KUsfwGz5ADTqZWKBEUpf4A==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-sdk-s3@3.972.42': - resolution: {integrity: sha512-/xNqNGXv9LaxZd25L9VV4pnSOw9OdDNO4rAHamM+h3KQBSITljIH9vk3dveGga1I2j36lQd0rdG3gjNEXvtNew==} + '@aws-sdk/middleware-sdk-s3@3.972.43': + resolution: {integrity: sha512-CBmixMY36JdAdt9ALgm7yVlvOXGUCHt9Z2kn5p9XVO5StO6HCH+cayV7YYV1CDLsXvVyebaXgBmif9wHoxCeNA==} engines: {node: '>=20.0.0'} '@aws-sdk/middleware-ssec@3.972.11': resolution: {integrity: sha512-7PQvGNhtveKlvVqNahqWx5yrwxP7ecwAoB1dYBf8eKwfo2tzzCbNnW+q2nO3N066ktQaB4iBQbDRWtizm+amoQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/nested-clients@3.997.11': - resolution: {integrity: sha512-nWXXJ1r/r8N2Gw1pWolRgED38/A9A8DHR2ETWIv220zh4PZHcybbR4hUVWWktmNXTRHzDJwRluapHn0rZxuoqA==} + '@aws-sdk/nested-clients@3.997.12': + resolution: {integrity: sha512-Js2VYaCM269feB0cs0cGmlIhdOgT9aMqzdBx68lCy6kVCYfzr0T36ovUFDvfUmatkuBeyBJhCwaLBh7P8meH5Q==} engines: {node: '>=20.0.0'} - '@aws-sdk/signature-v4-multi-region@3.996.28': - resolution: {integrity: sha512-qs9z5LqXO/CZC2Lg9SGKpoLU8Rhi+m2pFKZqfO9pytX1clc0katqtsDNupJxFy0xT9wsZSPzM2v1y+/H/zfp5Q==} + '@aws-sdk/signature-v4-multi-region@3.996.29': + resolution: {integrity: sha512-Few9FoQqOt/0KSvZYP+qdW0dfOhfQ9N+gl2UUDvCPW6mkPKHli9LMbKxWj+wZ5zKPaOoqxuR3Hhy3OTpndkfSw==} engines: {node: '>=20.0.0'} - '@aws-sdk/token-providers@3.1052.0': - resolution: {integrity: sha512-QqZNB3so7UIDxZtroc85TQaLVxdZRFm0eWM1CSR2N+b06as9TOrilvrlTZuj3guYlxMs6yLOgGxnklJ5qMYtTw==} + '@aws-sdk/token-providers@3.1054.0': + resolution: {integrity: sha512-hG9YKApmZOw+drJ9Nuoaf/OvC8e5W1+3eoLeN5p2uVCZRWsv27teIS0b4kiH6Sfv3WMmamqYJxmE2WMwyp/L/A==} engines: {node: '>=20.0.0'} '@aws-sdk/types@3.973.9': @@ -1329,8 +1329,8 @@ packages: resolution: {integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/xml-builder@3.972.25': - resolution: {integrity: sha512-GH+Kjz4nPKWKHnsiQpnhP1MJdTGIcK4rAka6tzakgjjUkVgNsmPeEbbRAf09SzS1hjGu6duGHCBsxYke0BhHjQ==} + '@aws-sdk/xml-builder@3.972.26': + resolution: {integrity: sha512-cDbrqvDS73whl6YAPSPq0U6whzG6UWI9PuWh0wrUuGoZexhWEqhdunbukV7iBoaWnFV1AODutM5hOD6rtn439g==} engines: {node: '>=20.0.0'} '@aws/lambda-invoke-store@0.2.4': @@ -1574,8 +1574,8 @@ packages: drizzle-orm: optional: true - '@better-auth/infra@0.2.8': - resolution: {integrity: sha512-FSessQK8BiKHGMABHN2ezvIcZaldYXxC4aNuVmdOClb9460h3xSXsQj2/pkGqtBbcA/YZFvrh3uJdhy2BgxJrA==} + '@better-auth/infra@0.2.10': + resolution: {integrity: sha512-u7ZZ7q4otp8TfRssm0DFfs1AsHPCj/nlVcus3xPhPvFVN+iUC0DMUp7gR4MsNZ2Lg6OH+3aRBSamyYrywz54zQ==} peerDependencies: '@better-auth/core': '>=1.4.0' '@better-auth/sso': '>=1.4.0' @@ -2182,6 +2182,36 @@ packages: cpu: [x64] os: [win32] + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.6.0': + resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.7.1': + resolution: {integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@floating-ui/core@1.7.5': resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} @@ -2215,6 +2245,26 @@ packages: peerDependencies: hono: ^4 + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + '@iarna/toml@2.2.5': resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} @@ -2406,8 +2456,8 @@ packages: '@types/node': optional: true - '@ioredis/commands@1.5.1': - resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==} + '@ioredis/commands@1.10.0': + resolution: {integrity: sha512-UmeW7z4LfctwoQ5wkhVzgq8tXkreED2xZGpX+Bg+zA+WJFZCT6c062AfCK/Dfk81xZnnwdhJCUMkitihRaoC2Q==} '@jest/schemas@29.6.3': resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} @@ -3568,8 +3618,8 @@ packages: react: ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/ui@6.3.3': - resolution: {integrity: sha512-ur1zabkODwYZ1KxQ4ho//xDnCXycLXFar6Nxa0XhjKOo3HFY5csSYt1kjADyY+uHy948JobtARjeDUefuw04Ww==} + '@react-email/ui@6.4.0': + resolution: {integrity: sha512-M9AD/Jvx0+SFjRS6kP69UgvPw2dvfKU4nqdEf7E4EeLY6Bh7i3gMVXTNVWKgRMid9tGmiqnynPib1NnZ+uvzNg==} '@react-pdf/fns@3.1.3': resolution: {integrity: sha512-0I7pApDr1/RLAKbizuLy/IHTEa93LSPy/bEwYniboC3Xqnp6Od8xFJKbKEzGw2wh/5zKFFwl00g4t9RwgIMc3w==} @@ -3810,8 +3860,8 @@ packages: '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} - '@swc/helpers@0.5.22': - resolution: {integrity: sha512-/e2Ly3Docn9kYByap6TV4oquJ3wQuz3c+kC74riqtkwU9CwTMeuj6t2rW+bRr4pyOx/CYQM4wr0RgaKQwGEz0A==} + '@swc/helpers@0.5.23': + resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} '@t3-oss/env-core@0.13.11': resolution: {integrity: sha512-sM7GYY+KL7H/Hl0BE0inWfk3nRHZOLhmVn7sHGxaZt9FAR6KqREXAE+6TqKfiavfXmpRxO/OZ2QgKRd+oiBYRQ==} @@ -3937,8 +3987,8 @@ packages: engines: {node: '>=18'} hasBin: true - '@tanstack/form-core@1.32.0': - resolution: {integrity: sha512-Tn5VRDSjyqjmaet2tJMuEWDRFyrCaon03vxXPlSSaiSs6C/N7lCIwGCXJbZXEUq1kTj8jYN9qyXHbsz4LQHcow==} + '@tanstack/form-core@1.32.1': + resolution: {integrity: sha512-5yTCJ1/0bBjdVDsZsqPpLMVZLLN/G39b+ONnwv4vjz2jDes4YAd63cVwti5RtWuGuS1yLc5tVrGl1rWyVYsNGw==} '@tanstack/history@1.162.0': resolution: {integrity: sha512-79pf/RkhteYZTRgcR4F9kbk84P2N8rugQJswxfIqovlbRiT3yI7eBE+5QorIrZaOKktsgzRlXh1l/du/xpl4iA==} @@ -3955,8 +4005,8 @@ packages: '@tanstack/query-core@5.100.14': resolution: {integrity: sha512-5X41dGpxgeaHISCRW2oYwcSycZeULZzAunaudXT9ov1KOTj9xwt0CH6hbwqP1/z74ZWF7rYFnDpyYH07XFcZew==} - '@tanstack/react-form@1.32.0': - resolution: {integrity: sha512-6WP5SQTA6/H9crCpvpq3ZppYWqtrdE5NjOy6ebABi6uAQPqhfTzrdjS9t40mCZCFtGI5585OhJV6zBP/KN2zcw==} + '@tanstack/react-form@1.32.1': + resolution: {integrity: sha512-GQ6IdIFnAJvhVaBZJIyVzi14c8b02W4SopJFzFZCjYFIAb5CfTZCbvHZ4Cnd5byd0OzTwLLW/R95noRKD+2+ZA==} peerDependencies: '@tanstack/react-start': '*' react: ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -4245,33 +4295,33 @@ packages: '@ts-morph/common@0.27.0': resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==} - '@turbo/darwin-64@2.9.14': - resolution: {integrity: sha512-t7QiPflaEyBE4oayeZtSmu4mEfjgIrcNlNNl1z1dmIVPqEdtA7+CfTf8d7KXsOGPh6aNgWjKxyvQg9uGfDQF+A==} + '@turbo/darwin-64@2.9.15': + resolution: {integrity: sha512-nnDo9R1Df+s2x6jxlERtbg7xRpuicf8p4J2krcnjeaMBt3q9V41pGXa4t9YM2Y4ozozsVJ+CH405CJUrWIQK4Q==} cpu: [x64] os: [darwin] - '@turbo/darwin-arm64@2.9.14': - resolution: {integrity: sha512-d23147mC9BsCPA9mJ0h/ubcpbRgcJBXbcG3+Vq7YLhjz3IXuvQsJ1UXH8f4MD76ZjJ4m/E4aRdJV+MW88CDfbw==} + '@turbo/darwin-arm64@2.9.15': + resolution: {integrity: sha512-fDSx56oqoFuS+yUQw7hqjQTkjrSLdMcplhuLC8HcSkWC6YrpwEmUUYsPYHPxy4ALvLxnmPQuk6XoSD8tdkjP+g==} cpu: [arm64] os: [darwin] - '@turbo/linux-64@2.9.14': - resolution: {integrity: sha512-P3ZKB5tuUDdDQWuAsACGUR1qv9W7BNWxdxqVJ0kZNuNNPRaVYTPPikLcp79+GiEcW3npsR+KyP38lnQiBc5aSA==} + '@turbo/linux-64@2.9.15': + resolution: {integrity: sha512-/bmxn+x/xE+oh0VzEXt/zf2zsORAYZPrL3db5/VrXzYt0Z4wxcvffwJBGlSfla2smfS1BLGBiyWldJlWDXJVXA==} cpu: [x64] os: [linux] - '@turbo/linux-arm64@2.9.14': - resolution: {integrity: sha512-ZRTlzcUMrrPv9ZuDzRF9n60Ym13bKeG9jDB8WjxyLhWNzV+AJQN+zdpIk3NJYf2zQsGUm1mNar2P0elRzLw25g==} + '@turbo/linux-arm64@2.9.15': + resolution: {integrity: sha512-cbOaDe1ijz5As+mimOOHgmRMolZZZO7miNBHHp5xdiYMm2Q/Dwu1JVLx/Kw4s7xjocG/oEoHrpHrxpEAIEfNiw==} cpu: [arm64] os: [linux] - '@turbo/windows-64@2.9.14': - resolution: {integrity: sha512-exanwN6sIduZwykYeiTQj8kCmOhazP5WOz3bvXMcYtjhL6Z3iRWLewKrXCBq0bqwSP3iBMb/AerRCnHI4lx46A==} + '@turbo/windows-64@2.9.15': + resolution: {integrity: sha512-/Fzm7afui7uK7dFBwrTXKuDhBBTiHk5I+hMVAPMR7cqQyDo2norCNUsN9PdNuYcmzYbhSOxzz498wQYvSAz29w==} cpu: [x64] os: [win32] - '@turbo/windows-arm64@2.9.14': - resolution: {integrity: sha512-fVdCsnmYoKICsycbWuuGp6Jvi51/3G/UluFWuAUCvR8PIW5IJkAk5BM9UF8PSm0Q2IphWHFZjYEgjHsh3B9y/g==} + '@turbo/windows-arm64@2.9.15': + resolution: {integrity: sha512-fOHEsLcqVdFXLw2ApWv4gxwfHzkUnpo9rHGml+9+dyHj148m/Bc+556kEvb5+4u6prI1LMd8zEZE2HcO6Jn2VQ==} cpu: [arm64] os: [win32] @@ -4335,6 +4385,9 @@ packages: '@types/jsesc@2.5.1': resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} @@ -4398,50 +4451,50 @@ packages: resolution: {integrity: sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260526.1': - resolution: {integrity: sha512-7kxHwV+zjANOeDn236ob/2nZy72slfid0ukGd0ApVRmDfG3iob4YEO8TuDet1RxNClis+k9l6zaPBJkujczzkA==} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260527.1': + resolution: {integrity: sha512-bDi6FJ644n3uKdp/ZI7j50ChVyGOsrJrkwihQb6x3yByFQkTINLu3e6ZkY+HveQ2Zw2vy9SGN8E7b3A5iSOO0A==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [darwin] - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260526.1': - resolution: {integrity: sha512-V2RY5nKkNyBQMK39lCdG//VvCVwKhibQBhuelIm8TR4P2EduhJHB42ftr6lEVgZjW03qlrAEE/FWwTV6Wku2vA==} + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260527.1': + resolution: {integrity: sha512-r6GXrTdalXZu1/b5goMpAe+efZvOfwdE45gl8Tti3fckP9icK3xdiN+VnNi0RL2/c2L86RyN8nGxihaCHGCKbw==} engines: {node: '>=16.20.0'} cpu: [x64] os: [darwin] - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260526.1': - resolution: {integrity: sha512-IVDSXjEyKgfa1SvwWux77cdb6W4ReycvLM9hYiJv42vzDJRt3g+H4LmaI6Ng+0Lq5syylzpVhJe4nh3logUsag==} + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260527.1': + resolution: {integrity: sha512-QJAFyPJgJqJVLbVPHl5xL7FCn3HNPLdpEm8l7KBgiYpltLhU1p/LJ3iN0XpFRAhq9ojWbZebo8t/h8MX35QjTQ==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [linux] - '@typescript/native-preview-linux-arm@7.0.0-dev.20260526.1': - resolution: {integrity: sha512-gEctrS100iZEwZgrJYuVN4pPW922CcozDneNwJkQld3zmFfSeGRLzj9oXCzP1JbMLq2np+DYrIkJ5ErPF1mwlA==} + '@typescript/native-preview-linux-arm@7.0.0-dev.20260527.1': + resolution: {integrity: sha512-BlfQBatMkZHi3o+atxoUW0czGJNjo9cpO1BoQeB3gxZ7D/cDZHYHmKFSSRx8UxMktwP5k5lPxi0wgA3Ic2mQyQ==} engines: {node: '>=16.20.0'} cpu: [arm] os: [linux] - '@typescript/native-preview-linux-x64@7.0.0-dev.20260526.1': - resolution: {integrity: sha512-3t4r52PTZgRh+dO0me8o4GCPj3PEixtIEC1vqmQxR/JjDunLn4YOFwfDLyNWEflOxauPRKV5pWkSpd6HiBsZfw==} + '@typescript/native-preview-linux-x64@7.0.0-dev.20260527.1': + resolution: {integrity: sha512-UFB7ZdK2/vIIi62nfn3JhyGV7qR/qXjKPQaPVXwzCvaPieTZcsNsALjKU0W5WHThyi+5p3U7O3dGE7n6P4q4Yw==} engines: {node: '>=16.20.0'} cpu: [x64] os: [linux] - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260526.1': - resolution: {integrity: sha512-0uR0M92lNwpgOyGBk+eZAAFuCsa3lc2l3IkvGWMo3fkD+eIBb8NwaqIhKpQ9VGvr6xfjJ3uf210j0Vxhl4HcLQ==} + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260527.1': + resolution: {integrity: sha512-rp/q9+9H77JQvepC/UpDP8CdeTGSGyhp9BVbmFwqUV2NhMHPldfys3ihY7OQdoVBgWIKQyxEHB+FTr8Z7kre1Q==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [win32] - '@typescript/native-preview-win32-x64@7.0.0-dev.20260526.1': - resolution: {integrity: sha512-UvwEPV9tDgV8GAWkp5Kmq2LRKUlhwtrl0PUpMDOKInaXAndMtUCOQ+aFqd015jPmfNPKs84BRQoQYXyeaHVJDw==} + '@typescript/native-preview-win32-x64@7.0.0-dev.20260527.1': + resolution: {integrity: sha512-864Pq4qoDcacUJhs2/kQplyfwNO0APUmx1k8qUaJt2P9ZGF0Pu++afJi7OagImHMiEQcmigjmZPuOodOk5YmqQ==} engines: {node: '>=16.20.0'} cpu: [x64] os: [win32] - '@typescript/native-preview@7.0.0-dev.20260526.1': - resolution: {integrity: sha512-ys/rYFNEVb4LZ7zUFxWZ6iv5gnIW29WRv3L9+hJKfAF3d/ekI76B4Ej4dU7odNCFiX85sCotljzq8N0QyruuoA==} + '@typescript/native-preview@7.0.0-dev.20260527.1': + resolution: {integrity: sha512-j81qKiwCPgMEjtk8uDLP+TDW60l6mugoJ7SNzfHWv1PJ6bUjIAHuag4P1jSLm1IpKuMuB3TTi4f61n7TJi8Jog==} engines: {node: '>=16.20.0'} hasBin: true @@ -4562,6 +4615,16 @@ packages: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} @@ -4584,6 +4647,9 @@ packages: ajv: optional: true + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} @@ -4914,8 +4980,8 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} - cluster-key-slot@1.1.2: - resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + cluster-key-slot@1.1.1: + resolution: {integrity: sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw==} engines: {node: '>=0.10.0'} cmdk@1.1.1: @@ -5101,6 +5167,9 @@ packages: babel-plugin-macros: optional: true + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge-ts@7.1.5: resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} engines: {node: '>=16.0.0'} @@ -5136,8 +5205,8 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - deslop-js@0.0.12: - resolution: {integrity: sha512-DsoVhhrgdqjkHxD9zFFJS7r+wDkbbpbdYVfwTNo6s89xTDbZQ9xW0EOd17a45Cfo1z0KxJQkjtJi/fHZ/LpBPA==} + deslop-js@0.0.13: + resolution: {integrity: sha512-gIXD+wY2/NHkZHpNrb8MWS6NJs3ee0XunAenBCvwHJlWnedDbIrg70hdNR7bDzYZLe9LGJZMLEI1w2CC72Gk5A==} detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} @@ -5176,8 +5245,8 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} - dompurify@3.4.5: - resolution: {integrity: sha512-OrwIBKsdNSVEeubdJ1HBv/wNENRM9ytAVCv7YXt//A3vPdVMNuACRqK9mXCGCBW2ln7BT/A4X0jXHo2Gu89miA==} + dompurify@3.4.6: + resolution: {integrity: sha512-+7gzEI8trIIQkVCvQ3ucGtNfH3nOmDgVTzc62rAAOlMxLth78pwpPoZCPc7CyRzAQF89MqcfPdEWkDwnjgqktg==} domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} @@ -5343,8 +5412,8 @@ packages: effect@4.0.0-beta.70: resolution: {integrity: sha512-8AwGTRiNriirHGEYHrOS0E9fzdhIqCdZjiHP1YXmNo2UyPGS43ILsymsSHT7V0DJS+8dvlKq2RxnrDBUhDNZHg==} - electron-to-chromium@1.5.361: - resolution: {integrity: sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA==} + electron-to-chromium@1.5.362: + resolution: {integrity: sha512-PUY2DrLvkjkUuWqq+KPL2iWshrJsZOcIojzRQ7eXFacc9dWga7MGMJAa15VbiejSZB1PAXaRLAiKgruHP8LB1w==} emoji-regex-xs@1.0.0: resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} @@ -5433,23 +5502,55 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + escape-string-regexp@5.0.0: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} + eslint-plugin-react-hooks@7.1.1: + resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 + eslint-scope@9.1.2: resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-visitor-keys@5.0.1: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + eslint@10.4.0: + resolution: {integrity: sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} @@ -5464,6 +5565,10 @@ packages: estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} @@ -5523,6 +5628,12 @@ packages: fast-json-patch@3.1.1: resolution: {integrity: sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==} + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-string-truncated-width@3.0.3: resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} @@ -5572,6 +5683,10 @@ packages: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} engines: {node: '>=18'} + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -5583,6 +5698,17 @@ packages: find-my-way-ts@0.1.6: resolution: {integrity: sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==} + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + fontkit@2.0.4: resolution: {integrity: sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==} @@ -5688,6 +5814,10 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + glob@13.0.6: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} @@ -5743,6 +5873,12 @@ packages: headers-polyfill@5.0.1: resolution: {integrity: sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA==} + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + hono@4.12.23: resolution: {integrity: sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==} engines: {node: '>=16.9.0'} @@ -5810,6 +5946,10 @@ packages: resolution: {integrity: sha512-NkJQA7oZ4YHQhd2+H3BoRFKF3d/XNsiKpHZCQEMH9pDX27hQQLsTyOocyRgaIVtf8gHX3Nt3LPkR4e5EdtPAGQ==} engines: {node: ^22.18.0 || >=24.0.0} + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} @@ -5828,8 +5968,8 @@ packages: inline-style-parser@0.2.7: resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} - ioredis@5.10.1: - resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==} + ioredis@5.11.0: + resolution: {integrity: sha512-EZBErytyVovD8f6pDfG3Kb37N6Y3lmDA9NNj+4+IP13CzzHGeX+OyeRM2Um13khRzoBSzzL+5lVnCX8V2RLeMg==} engines: {node: '>=12.22.0'} ip-address@10.2.0: @@ -6018,9 +6158,15 @@ packages: engines: {node: '>=6'} hasBin: true + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} @@ -6030,6 +6176,9 @@ packages: json-schema@0.4.0: resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -6048,6 +6197,9 @@ packages: jszip@3.10.1: resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} @@ -6129,6 +6281,10 @@ packages: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + libphonenumber-js@1.13.3: resolution: {integrity: sha512-xMkdAMqcyG7iN2WZZmGIfWbYxW4orRkny+0/AXIbwL0xll2zkDX0Vzo/BXFa6+7mh2UvJl9MbcTtHk0YXkFtBA==} @@ -6222,15 +6378,13 @@ packages: linkifyjs@4.3.3: resolution: {integrity: sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg==} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} - lodash.defaults@4.2.0: - resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} - - lodash.isarguments@3.1.0: - resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} - log-symbols@6.0.0: resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} engines: {node: '>=18'} @@ -6501,6 +6655,9 @@ packages: resolution: {integrity: sha512-XPUa/jz+P1oJvN9VBxw4L9MtdFfaH3DAryqPssqhb2kXjmb9npz0dly6rCsgFWOPr4Yg9mTfM3MDZgZZ+7A3lA==} engines: {node: ^20.0.0 || >=22.0.0} + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} @@ -6574,8 +6731,8 @@ packages: node-rsa@1.1.1: resolution: {integrity: sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==} - nodemailer@8.0.8: - resolution: {integrity: sha512-p+XsnzXGdtIHXUu2ugxdfG+eX2nehsGhMjW9h0CWj1BhE30hrFz0kh0yIM0/VjUgVsRrDj+80ZO+I1nSkGE4tA==} + nodemailer@8.0.9: + resolution: {integrity: sha512-5ofa7BUN8+C+Hckh5V2GjeeOGRQBx0CJQA6KxrvuZfC8iU4/q7sLn8XrtEEhJkjV6HdyIiQs7Bba6bTao8JhkA==} engines: {node: '>=6.0.0'} normalize-path@3.0.0: @@ -6650,6 +6807,10 @@ packages: openapi-types@12.1.3: resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + ora@8.2.0: resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} engines: {node: '>=18'} @@ -6675,8 +6836,8 @@ packages: oxc-resolver@11.19.1: resolution: {integrity: sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg==} - oxlint-plugin-react-doctor@0.2.6: - resolution: {integrity: sha512-af0bMoHfVI3DawVJKmsi/tLTpigSK/AXs8HNpkV98Bj8T5jPqN+N9LJVuCIUt7qk4O7rpAXXh/9oMHyVEs9kNA==} + oxlint-plugin-react-doctor@0.2.9: + resolution: {integrity: sha512-wnmC4OBbog9IQuZY2evNNe1P5IacUDLnYqOOpOSq8Adb+kjFmNxPPG8SMH+/OWkKld3NVC5KMM2pSw+oxnEIUw==} engines: {node: ^20.19.0 || >=22.12.0} oxlint@1.67.0: @@ -6692,6 +6853,14 @@ packages: vite-plus: optional: true + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + pako@0.2.9: resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} @@ -6726,6 +6895,10 @@ packages: path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + path-expression-matcher@1.5.0: resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==} engines: {node: '>=14.0.0'} @@ -6865,6 +7038,10 @@ packages: resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} engines: {node: '>=20'} + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + prettier@3.8.3: resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} engines: {node: '>=14'} @@ -6944,6 +7121,10 @@ packages: engines: {node: '>=16.0.0'} hasBin: true + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + pure-rand@8.4.0: resolution: {integrity: sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==} @@ -6984,23 +7165,18 @@ packages: resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} engines: {node: '>= 0.10'} - react-doctor@0.2.6: - resolution: {integrity: sha512-Dwl6WY5wLCuy/i59rSK2veoJrTEw1hLzR7TXg5rjlvFR0JHsPW3kq2zFwHj/amtYESEmKCShIvAubzzy2b4nyw==} + react-doctor@0.2.9: + resolution: {integrity: sha512-lfTA5OOQmy95nreKad+UueFAsVLxtjRTwZQpo50VAfsFytpDjiAb+caB2DV+Tdga8pk9th18DUZjTuSDWFFvZQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - peerDependencies: - eslint-plugin-react-hooks: ^6 || ^7 - peerDependenciesMeta: - eslint-plugin-react-hooks: - optional: true react-dom@19.2.6: resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==} peerDependencies: react: ^19.2.6 - react-email@6.3.3: - resolution: {integrity: sha512-mkHgVfuoDLM1To/u0ZcdJy/3q1Z3SdOe1A5/GtsUqApP68RQOztBe+AOk3BIJC1POexnqwDZZwV+Pw+srVlH7Q==} + react-email@6.4.0: + resolution: {integrity: sha512-WDS6zl60zPllvNj14xj9nOB2uJwhyNWB+PI+YpXukpAhIsc6zo3QcAVreZ7chZLMZRM2X/dzF1d6rRrZ7TGKmw==} engines: {node: '>=20.0.0'} hasBin: true peerDependencies: @@ -7268,8 +7444,8 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - shadcn@4.8.0: - resolution: {integrity: sha512-LAm3I/1FdoU/zu5GVG8Hbna4X9zlzEG5TeeCPXqsopkjvGk8QUF9OFhqeRN8oM6Oh/ynUI/yQHZxQAO3Ymcqsg==} + shadcn@4.8.1: + resolution: {integrity: sha512-B2J63uYwqfLJPNzZk2AAlonXG1aJhwv8xmXlUag8OZdV61CQB6570nkXLjU1u9XN1qXm0LpJhSFQ0ZhlOruA4Q==} hasBin: true sharp@0.34.5: @@ -7616,13 +7792,17 @@ packages: resolution: {integrity: sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==} engines: {node: '>= 6.0.0'} - turbo@2.9.14: - resolution: {integrity: sha512-BQqXRr4UoWI3UPFrtznCLykYHxwxWh53iCB57x092jPMjIlW1wnm3N895g5irpiXmnxUhREBB0n6+y8BHhs4nw==} + turbo@2.9.15: + resolution: {integrity: sha512-VpKvD9Z0Hu/xrGUAYX1wnhfpqv835wIwGqeKfulvBPTOcDap0E3nFwyzCAVV85fB1sBcBDEfTP+7FSW7GzwWSQ==} hasBin: true tw-animate-css@1.4.0: resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + type-fest@5.6.0: resolution: {integrity: sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==} engines: {node: '>=20'} @@ -7707,6 +7887,9 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-callback-ref@1.3.3: resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} @@ -7891,6 +8074,10 @@ packages: wildcard-match@5.1.4: resolution: {integrity: sha512-wldeCaczs8XXq7hj+5d/F38JE2r7EXgb6WQDM84RVwxy81T/sxB5e9+uZLK9Q9oNz1mlvjut+QtvgaOQFPVq/g==} + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -7992,6 +8179,10 @@ packages: resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + yocto-spinner@1.2.0: resolution: {integrity: sha512-Yw0hUB6UA3o4YUgKy3oSe9a4cxoaZ9sBfYDw+JSxo6Id0KoJGoxzPA24qqUXYKBWABs/zDSGTz9kww7t3F0XGw==} engines: {node: '>=18.19'} @@ -8008,6 +8199,12 @@ packages: peerDependencies: zod: ^3.25.28 || ^4 + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -8146,20 +8343,20 @@ snapshots: '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 - '@aws-sdk/client-s3@3.1053.0': + '@aws-sdk/client-s3@3.1054.0': dependencies: '@aws-crypto/sha1-browser': 5.2.0 '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.974.13 - '@aws-sdk/credential-provider-node': 3.972.44 - '@aws-sdk/middleware-bucket-endpoint': 3.972.15 + '@aws-sdk/core': 3.974.14 + '@aws-sdk/credential-provider-node': 3.972.45 + '@aws-sdk/middleware-bucket-endpoint': 3.972.16 '@aws-sdk/middleware-expect-continue': 3.972.13 - '@aws-sdk/middleware-flexible-checksums': 3.974.21 + '@aws-sdk/middleware-flexible-checksums': 3.974.22 '@aws-sdk/middleware-location-constraint': 3.972.11 - '@aws-sdk/middleware-sdk-s3': 3.972.42 + '@aws-sdk/middleware-sdk-s3': 3.972.43 '@aws-sdk/middleware-ssec': 3.972.11 - '@aws-sdk/signature-v4-multi-region': 3.996.28 + '@aws-sdk/signature-v4-multi-region': 3.996.29 '@aws-sdk/types': 3.973.9 '@smithy/core': 3.24.4 '@smithy/fetch-http-handler': 5.4.4 @@ -8167,10 +8364,10 @@ snapshots: '@smithy/types': 4.14.2 tslib: 2.8.1 - '@aws-sdk/core@3.974.13': + '@aws-sdk/core@3.974.14': dependencies: '@aws-sdk/types': 3.973.9 - '@aws-sdk/xml-builder': 3.972.25 + '@aws-sdk/xml-builder': 3.972.26 '@aws/lambda-invoke-store': 0.2.4 '@smithy/core': 3.24.4 '@smithy/signature-v4': 5.4.4 @@ -8183,17 +8380,17 @@ snapshots: '@smithy/types': 4.14.2 tslib: 2.8.1 - '@aws-sdk/credential-provider-env@3.972.39': + '@aws-sdk/credential-provider-env@3.972.40': dependencies: - '@aws-sdk/core': 3.974.13 + '@aws-sdk/core': 3.974.14 '@aws-sdk/types': 3.973.9 '@smithy/core': 3.24.4 '@smithy/types': 4.14.2 tslib: 2.8.1 - '@aws-sdk/credential-provider-http@3.972.41': + '@aws-sdk/credential-provider-http@3.972.42': dependencies: - '@aws-sdk/core': 3.974.13 + '@aws-sdk/core': 3.974.14 '@aws-sdk/types': 3.973.9 '@smithy/core': 3.24.4 '@smithy/fetch-http-handler': 5.4.4 @@ -8201,75 +8398,75 @@ snapshots: '@smithy/types': 4.14.2 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.972.43': + '@aws-sdk/credential-provider-ini@3.972.44': dependencies: - '@aws-sdk/core': 3.974.13 - '@aws-sdk/credential-provider-env': 3.972.39 - '@aws-sdk/credential-provider-http': 3.972.41 - '@aws-sdk/credential-provider-login': 3.972.43 - '@aws-sdk/credential-provider-process': 3.972.39 - '@aws-sdk/credential-provider-sso': 3.972.43 - '@aws-sdk/credential-provider-web-identity': 3.972.43 - '@aws-sdk/nested-clients': 3.997.11 + '@aws-sdk/core': 3.974.14 + '@aws-sdk/credential-provider-env': 3.972.40 + '@aws-sdk/credential-provider-http': 3.972.42 + '@aws-sdk/credential-provider-login': 3.972.44 + '@aws-sdk/credential-provider-process': 3.972.40 + '@aws-sdk/credential-provider-sso': 3.972.44 + '@aws-sdk/credential-provider-web-identity': 3.972.44 + '@aws-sdk/nested-clients': 3.997.12 '@aws-sdk/types': 3.973.9 '@smithy/core': 3.24.4 '@smithy/credential-provider-imds': 4.3.4 '@smithy/types': 4.14.2 tslib: 2.8.1 - '@aws-sdk/credential-provider-login@3.972.43': + '@aws-sdk/credential-provider-login@3.972.44': dependencies: - '@aws-sdk/core': 3.974.13 - '@aws-sdk/nested-clients': 3.997.11 + '@aws-sdk/core': 3.974.14 + '@aws-sdk/nested-clients': 3.997.12 '@aws-sdk/types': 3.973.9 '@smithy/core': 3.24.4 '@smithy/types': 4.14.2 tslib: 2.8.1 - '@aws-sdk/credential-provider-node@3.972.44': + '@aws-sdk/credential-provider-node@3.972.45': dependencies: - '@aws-sdk/credential-provider-env': 3.972.39 - '@aws-sdk/credential-provider-http': 3.972.41 - '@aws-sdk/credential-provider-ini': 3.972.43 - '@aws-sdk/credential-provider-process': 3.972.39 - '@aws-sdk/credential-provider-sso': 3.972.43 - '@aws-sdk/credential-provider-web-identity': 3.972.43 + '@aws-sdk/credential-provider-env': 3.972.40 + '@aws-sdk/credential-provider-http': 3.972.42 + '@aws-sdk/credential-provider-ini': 3.972.44 + '@aws-sdk/credential-provider-process': 3.972.40 + '@aws-sdk/credential-provider-sso': 3.972.44 + '@aws-sdk/credential-provider-web-identity': 3.972.44 '@aws-sdk/types': 3.973.9 '@smithy/core': 3.24.4 '@smithy/credential-provider-imds': 4.3.4 '@smithy/types': 4.14.2 tslib: 2.8.1 - '@aws-sdk/credential-provider-process@3.972.39': + '@aws-sdk/credential-provider-process@3.972.40': dependencies: - '@aws-sdk/core': 3.974.13 + '@aws-sdk/core': 3.974.14 '@aws-sdk/types': 3.973.9 '@smithy/core': 3.24.4 '@smithy/types': 4.14.2 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.972.43': + '@aws-sdk/credential-provider-sso@3.972.44': dependencies: - '@aws-sdk/core': 3.974.13 - '@aws-sdk/nested-clients': 3.997.11 - '@aws-sdk/token-providers': 3.1052.0 + '@aws-sdk/core': 3.974.14 + '@aws-sdk/nested-clients': 3.997.12 + '@aws-sdk/token-providers': 3.1054.0 '@aws-sdk/types': 3.973.9 '@smithy/core': 3.24.4 '@smithy/types': 4.14.2 tslib: 2.8.1 - '@aws-sdk/credential-provider-web-identity@3.972.43': + '@aws-sdk/credential-provider-web-identity@3.972.44': dependencies: - '@aws-sdk/core': 3.974.13 - '@aws-sdk/nested-clients': 3.997.11 + '@aws-sdk/core': 3.974.14 + '@aws-sdk/nested-clients': 3.997.12 '@aws-sdk/types': 3.973.9 '@smithy/core': 3.24.4 '@smithy/types': 4.14.2 tslib: 2.8.1 - '@aws-sdk/middleware-bucket-endpoint@3.972.15': + '@aws-sdk/middleware-bucket-endpoint@3.972.16': dependencies: - '@aws-sdk/core': 3.974.13 + '@aws-sdk/core': 3.974.14 '@aws-sdk/types': 3.973.9 '@smithy/core': 3.24.4 '@smithy/types': 4.14.2 @@ -8282,12 +8479,12 @@ snapshots: '@smithy/types': 4.14.2 tslib: 2.8.1 - '@aws-sdk/middleware-flexible-checksums@3.974.21': + '@aws-sdk/middleware-flexible-checksums@3.974.22': dependencies: '@aws-crypto/crc32': 5.2.0 '@aws-crypto/crc32c': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/core': 3.974.13 + '@aws-sdk/core': 3.974.14 '@aws-sdk/crc64-nvme': 3.972.9 '@aws-sdk/types': 3.973.9 '@smithy/core': 3.24.4 @@ -8300,13 +8497,12 @@ snapshots: '@smithy/types': 4.14.2 tslib: 2.8.1 - '@aws-sdk/middleware-sdk-s3@3.972.42': + '@aws-sdk/middleware-sdk-s3@3.972.43': dependencies: - '@aws-sdk/core': 3.974.13 - '@aws-sdk/signature-v4-multi-region': 3.996.28 + '@aws-sdk/core': 3.974.14 + '@aws-sdk/signature-v4-multi-region': 3.996.29 '@aws-sdk/types': 3.973.9 '@smithy/core': 3.24.4 - '@smithy/signature-v4': 5.4.4 '@smithy/types': 4.14.2 tslib: 2.8.1 @@ -8316,12 +8512,12 @@ snapshots: '@smithy/types': 4.14.2 tslib: 2.8.1 - '@aws-sdk/nested-clients@3.997.11': + '@aws-sdk/nested-clients@3.997.12': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.974.13 - '@aws-sdk/signature-v4-multi-region': 3.996.28 + '@aws-sdk/core': 3.974.14 + '@aws-sdk/signature-v4-multi-region': 3.996.29 '@aws-sdk/types': 3.973.9 '@smithy/core': 3.24.4 '@smithy/fetch-http-handler': 5.4.4 @@ -8329,18 +8525,17 @@ snapshots: '@smithy/types': 4.14.2 tslib: 2.8.1 - '@aws-sdk/signature-v4-multi-region@3.996.28': + '@aws-sdk/signature-v4-multi-region@3.996.29': dependencies: '@aws-sdk/types': 3.973.9 - '@smithy/core': 3.24.4 '@smithy/signature-v4': 5.4.4 '@smithy/types': 4.14.2 tslib: 2.8.1 - '@aws-sdk/token-providers@3.1052.0': + '@aws-sdk/token-providers@3.1054.0': dependencies: - '@aws-sdk/core': 3.974.13 - '@aws-sdk/nested-clients': 3.997.11 + '@aws-sdk/core': 3.974.14 + '@aws-sdk/nested-clients': 3.997.12 '@aws-sdk/types': 3.973.9 '@smithy/core': 3.24.4 '@smithy/types': 4.14.2 @@ -8355,9 +8550,8 @@ snapshots: dependencies: tslib: 2.8.1 - '@aws-sdk/xml-builder@3.972.25': + '@aws-sdk/xml-builder@3.972.26': dependencies: - '@nodable/entities': 2.1.0 '@smithy/types': 4.14.2 fast-xml-parser: 5.7.3 tslib: 2.8.1 @@ -8649,7 +8843,7 @@ snapshots: optionalDependencies: drizzle-orm: 1.0.0-rc.3(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(effect@4.0.0-beta.70)(pg@8.21.0)(zod@4.4.3) - '@better-auth/infra@0.2.8(97af957868093b766f74c847443b5514)': + '@better-auth/infra@0.2.10(97af957868093b766f74c847443b5514)': dependencies: '@better-auth/core': 1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) '@better-auth/sso': 1.6.11(@better-auth/core@1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-auth@1.6.11(@opentelemetry/api@1.9.1)(drizzle-orm@1.0.0-rc.3(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(effect@4.0.0-beta.70)(pg@8.21.0)(zod@4.4.3))(next@16.2.6(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(pg@8.21.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(vitest@4.1.7))(better-call@1.3.5(zod@4.4.3)) @@ -9109,6 +9303,36 @@ snapshots: '@esbuild/win32-x64@0.28.0': optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@10.4.0(jiti@2.7.0))': + dependencies: + eslint: 10.4.0(jiti@2.7.0) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.5': + dependencies: + '@eslint/object-schema': 3.0.5 + debug: 4.4.3 + minimatch: 10.2.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.6.0': + dependencies: + '@eslint/core': 1.2.1 + + '@eslint/core@1.2.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/object-schema@3.0.5': {} + + '@eslint/plugin-kit@0.7.1': + dependencies: + '@eslint/core': 1.2.1 + levn: 0.4.1 + '@floating-ui/core@1.7.5': dependencies: '@floating-ui/utils': 0.2.11 @@ -9138,6 +9362,22 @@ snapshots: dependencies: hono: 4.12.23 + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + '@iarna/toml@2.2.5': {} '@img/colour@1.1.0': {} @@ -9263,7 +9503,7 @@ snapshots: optionalDependencies: '@types/node': 25.9.1 - '@ioredis/commands@1.5.1': {} + '@ioredis/commands@1.10.0': {} '@jest/schemas@29.6.3': dependencies: @@ -10291,7 +10531,7 @@ snapshots: react: 19.2.6 react-dom: 19.2.6(react@19.2.6) - '@react-email/ui@6.3.3(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@react-email/ui@6.4.0(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: esbuild: 0.28.0 next: 16.2.6(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -10562,7 +10802,7 @@ snapshots: dependencies: tslib: 2.8.1 - '@swc/helpers@0.5.22': + '@swc/helpers@0.5.23': dependencies: tslib: 2.8.1 @@ -10654,7 +10894,7 @@ snapshots: '@tanstack/devtools-event-client@0.4.3': {} - '@tanstack/form-core@1.32.0': + '@tanstack/form-core@1.32.1': dependencies: '@tanstack/devtools-event-client': 0.4.3 '@tanstack/pacer-lite': 0.1.1 @@ -10670,9 +10910,9 @@ snapshots: '@tanstack/query-core@5.100.14': {} - '@tanstack/react-form@1.32.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@tanstack/react-form@1.32.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@tanstack/form-core': 1.32.0 + '@tanstack/form-core': 1.32.1 '@tanstack/react-store': 0.9.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 transitivePeerDependencies: @@ -11006,22 +11246,22 @@ snapshots: minimatch: 10.2.5 path-browserify: 1.0.1 - '@turbo/darwin-64@2.9.14': + '@turbo/darwin-64@2.9.15': optional: true - '@turbo/darwin-arm64@2.9.14': + '@turbo/darwin-arm64@2.9.15': optional: true - '@turbo/linux-64@2.9.14': + '@turbo/linux-64@2.9.15': optional: true - '@turbo/linux-arm64@2.9.14': + '@turbo/linux-arm64@2.9.15': optional: true - '@turbo/windows-64@2.9.14': + '@turbo/windows-64@2.9.15': optional: true - '@turbo/windows-arm64@2.9.14': + '@turbo/windows-arm64@2.9.15': optional: true '@tybys/wasm-util@0.10.2': @@ -11097,6 +11337,8 @@ snapshots: '@types/jsesc@2.5.1': {} + '@types/json-schema@7.0.15': {} + '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 @@ -11158,36 +11400,36 @@ snapshots: '@typescript-eslint/types@8.60.0': {} - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260526.1': + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260527.1': optional: true - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260526.1': + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260527.1': optional: true - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260526.1': + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260527.1': optional: true - '@typescript/native-preview-linux-arm@7.0.0-dev.20260526.1': + '@typescript/native-preview-linux-arm@7.0.0-dev.20260527.1': optional: true - '@typescript/native-preview-linux-x64@7.0.0-dev.20260526.1': + '@typescript/native-preview-linux-x64@7.0.0-dev.20260527.1': optional: true - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260526.1': + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260527.1': optional: true - '@typescript/native-preview-win32-x64@7.0.0-dev.20260526.1': + '@typescript/native-preview-win32-x64@7.0.0-dev.20260527.1': optional: true - '@typescript/native-preview@7.0.0-dev.20260526.1': + '@typescript/native-preview@7.0.0-dev.20260527.1': optionalDependencies: - '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260526.1 - '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260526.1 - '@typescript/native-preview-linux-arm': 7.0.0-dev.20260526.1 - '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260526.1 - '@typescript/native-preview-linux-x64': 7.0.0-dev.20260526.1 - '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260526.1 - '@typescript/native-preview-win32-x64': 7.0.0-dev.20260526.1 + '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260527.1 + '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260527.1 + '@typescript/native-preview-linux-arm': 7.0.0-dev.20260527.1 + '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260527.1 + '@typescript/native-preview-linux-x64': 7.0.0-dev.20260527.1 + '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260527.1 + '@typescript/native-preview-win32-x64': 7.0.0-dev.20260527.1 '@uiw/color-convert@2.10.3(@babel/runtime@7.29.7)': dependencies: @@ -11317,6 +11559,12 @@ snapshots: mime-types: 3.0.2 negotiator: 1.0.0 + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + agent-base@7.1.4: {} agent-install@0.0.5: @@ -11340,6 +11588,13 @@ snapshots: optionalDependencies: ajv: 8.20.0 + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 @@ -11529,7 +11784,7 @@ snapshots: dependencies: baseline-browser-mapping: 2.10.32 caniuse-lite: 1.0.30001793 - electron-to-chromium: 1.5.361 + electron-to-chromium: 1.5.362 node-releases: 2.0.46 update-browserslist-db: 1.2.3(browserslist@4.28.2) @@ -11629,7 +11884,7 @@ snapshots: clsx@2.1.1: {} - cluster-key-slot@1.1.2: {} + cluster-key-slot@1.1.1: {} cmdk@1.1.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: @@ -11796,6 +12051,8 @@ snapshots: optionalDependencies: babel-plugin-macros: 3.1.0 + deep-is@0.1.4: {} + deepmerge-ts@7.1.5: {} deepmerge@4.3.1: {} @@ -11817,7 +12074,7 @@ snapshots: dequal@2.0.3: {} - deslop-js@0.0.12(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0): + deslop-js@0.0.13(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0): dependencies: '@oxc-project/types': 0.132.0 fast-glob: 3.3.3 @@ -11866,7 +12123,7 @@ snapshots: dependencies: domelementtype: 2.3.0 - dompurify@3.4.5: + dompurify@3.4.6: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -11939,7 +12196,7 @@ snapshots: uuid: 14.0.0 yaml: 2.9.0 - electron-to-chromium@1.5.361: {} + electron-to-chromium@1.5.362: {} emoji-regex-xs@1.0.0: {} @@ -12066,8 +12323,21 @@ snapshots: escape-html@1.0.3: {} + escape-string-regexp@4.0.0: {} + escape-string-regexp@5.0.0: {} + eslint-plugin-react-hooks@7.1.1(eslint@10.4.0(jiti@2.7.0)): + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.7 + eslint: 10.4.0(jiti@2.7.0) + hermes-parser: 0.25.1 + zod: 4.4.3 + zod-validation-error: 4.0.2(zod@4.4.3) + transitivePeerDependencies: + - supports-color + eslint-scope@9.1.2: dependencies: '@types/esrecurse': 4.3.1 @@ -12075,10 +12345,59 @@ snapshots: esrecurse: 4.3.0 estraverse: 5.3.0 + eslint-visitor-keys@3.4.3: {} + eslint-visitor-keys@5.0.1: {} + eslint@10.4.0(jiti@2.7.0): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.6.0 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.1 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.7.0 + transitivePeerDependencies: + - supports-color + + espree@11.2.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 + esprima@4.0.1: {} + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + esrecurse@4.3.0: dependencies: estraverse: 5.3.0 @@ -12091,6 +12410,8 @@ snapshots: dependencies: '@types/estree': 1.0.9 + esutils@2.0.3: {} + etag@1.8.1: {} events@3.3.0: {} @@ -12188,6 +12509,10 @@ snapshots: fast-json-patch@3.1.1: {} + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + fast-string-truncated-width@3.0.3: {} fast-string-width@3.0.2: @@ -12243,6 +12568,10 @@ snapshots: dependencies: is-unicode-supported: 2.1.0 + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -12260,9 +12589,21 @@ snapshots: find-my-way-ts@0.1.6: {} + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + fontkit@2.0.4: dependencies: - '@swc/helpers': 0.5.22 + '@swc/helpers': 0.5.23 brotli: 1.3.3 clone: 2.1.2 dfa: 1.2.0 @@ -12363,6 +12704,10 @@ snapshots: dependencies: is-glob: 4.0.3 + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + glob@13.0.6: dependencies: minimatch: 10.2.5 @@ -12437,6 +12782,12 @@ snapshots: '@types/set-cookie-parser': 2.4.10 set-cookie-parser: 3.1.0 + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + hono@4.12.23: {} hookable@6.1.1: {} @@ -12504,6 +12855,8 @@ snapshots: import-without-cache@0.4.0: {} + imurmurhash@0.1.4: {} + indent-string@4.0.0: {} inherits@2.0.4: {} @@ -12514,14 +12867,12 @@ snapshots: inline-style-parser@0.2.7: {} - ioredis@5.10.1: + ioredis@5.11.0: dependencies: - '@ioredis/commands': 1.5.1 - cluster-key-slot: 1.1.2 + '@ioredis/commands': 1.10.0 + cluster-key-slot: 1.1.1 debug: 4.4.3 denque: 2.1.0 - lodash.defaults: 4.2.0 - lodash.isarguments: 3.1.0 redis-errors: 1.2.0 redis-parser: 3.0.0 standard-as-callback: 2.1.0 @@ -12657,14 +13008,20 @@ snapshots: jsesc@3.1.0: {} + json-buffer@3.0.1: {} + json-parse-even-better-errors@2.3.1: {} + json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} json-schema-typed@8.0.2: {} json-schema@0.4.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} + json5@2.2.3: {} jsonc-parser@3.3.1: {} @@ -12684,6 +13041,10 @@ snapshots: readable-stream: 2.3.8 setimmediate: 1.0.5 + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + kleur@3.0.3: {} kleur@4.1.5: {} @@ -12759,6 +13120,11 @@ snapshots: leven@3.1.0: {} + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + libphonenumber-js@1.13.3: {} lie@3.3.0: @@ -12825,12 +13191,12 @@ snapshots: linkifyjs@4.3.3: {} + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + lodash.debounce@4.0.8: {} - lodash.defaults@4.2.0: {} - - lodash.isarguments@3.1.0: {} - log-symbols@6.0.0: dependencies: chalk: 5.6.2 @@ -13213,6 +13579,8 @@ snapshots: nanostores@1.3.0: {} + natural-compare@1.4.0: {} + negotiator@0.6.3: {} negotiator@1.0.0: {} @@ -13283,7 +13651,7 @@ snapshots: dependencies: asn1: 0.2.6 - nodemailer@8.0.8: {} + nodemailer@8.0.9: {} normalize-path@3.0.0: {} @@ -13354,6 +13722,15 @@ snapshots: openapi-types@12.1.3: {} + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + ora@8.2.0: dependencies: chalk: 5.6.2 @@ -13457,7 +13834,7 @@ snapshots: - '@emnapi/core' - '@emnapi/runtime' - oxlint-plugin-react-doctor@0.2.6: + oxlint-plugin-react-doctor@0.2.9: dependencies: '@typescript-eslint/types': 8.60.0 eslint-scope: 9.1.2 @@ -13485,6 +13862,14 @@ snapshots: '@oxlint/binding-win32-ia32-msvc': 1.67.0 '@oxlint/binding-win32-x64-msvc': 1.67.0 + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + pako@0.2.9: {} pako@1.0.11: {} @@ -13523,6 +13908,8 @@ snapshots: path-browserify@1.0.1: {} + path-exists@4.0.0: {} + path-expression-matcher@1.5.0: {} path-key@3.1.1: {} @@ -13636,6 +14023,8 @@ snapshots: powershell-utils@0.1.0: {} + prelude-ls@1.2.1: {} + prettier@3.8.3: {} pretty-format@27.5.1: @@ -13749,6 +14138,8 @@ snapshots: dependencies: commander: 10.0.1 + punycode@2.3.1: {} + pure-rand@8.4.0: {} pvtsutils@1.3.6: @@ -13784,22 +14175,25 @@ snapshots: iconv-lite: 0.7.2 unpipe: 1.0.0 - react-doctor@0.2.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0): + react-doctor@0.2.9(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(eslint@10.4.0(jiti@2.7.0)): dependencies: '@effect/platform-node-shared': 4.0.0-beta.70(effect@4.0.0-beta.70) agent-install: 0.0.5 conf: 15.1.0 - deslop-js: 0.0.12(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + deslop-js: 0.0.13(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) effect: 4.0.0-beta.70 + eslint-plugin-react-hooks: 7.1.1(eslint@10.4.0(jiti@2.7.0)) oxlint: 1.67.0 - oxlint-plugin-react-doctor: 0.2.6 + oxlint-plugin-react-doctor: 0.2.9 prompts: 2.4.2 typescript: 6.0.3 transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' - bufferutil + - eslint - oxlint-tsgolint + - supports-color - utf-8-validate - vite-plus @@ -13808,7 +14202,7 @@ snapshots: react: 19.2.6 scheduler: 0.27.0 - react-email@6.3.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + react-email@6.4.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: '@babel/parser': 7.27.0 '@babel/traverse': 7.27.0 @@ -14000,7 +14394,7 @@ snapshots: reusify@1.1.0: {} - rolldown-plugin-dts@0.25.1(@typescript/native-preview@7.0.0-dev.20260526.1)(oxc-resolver@11.19.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rolldown@1.0.2)(typescript@6.0.3): + rolldown-plugin-dts@0.25.1(@typescript/native-preview@7.0.0-dev.20260527.1)(oxc-resolver@11.19.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rolldown@1.0.2)(typescript@6.0.3): dependencies: '@babel/generator': 8.0.0-rc.5 '@babel/helper-validator-identifier': 8.0.0-rc.5 @@ -14012,7 +14406,7 @@ snapshots: obug: 2.1.1 rolldown: 1.0.2 optionalDependencies: - '@typescript/native-preview': 7.0.0-dev.20260526.1 + '@typescript/native-preview': 7.0.0-dev.20260527.1 typescript: 6.0.3 transitivePeerDependencies: - oxc-resolver @@ -14129,7 +14523,7 @@ snapshots: setprototypeof@1.2.0: {} - shadcn@4.8.0(@types/node@25.9.1)(babel-plugin-macros@3.1.0)(typescript@6.0.3): + shadcn@4.8.1(@types/node@25.9.1)(babel-plugin-macros@3.1.0)(typescript@6.0.3): dependencies: '@babel/core': 7.29.7 '@babel/parser': 7.29.7 @@ -14470,7 +14864,7 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 - tsdown@0.22.0(@typescript/native-preview@7.0.0-dev.20260526.1)(oxc-resolver@11.19.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(tsx@4.22.3)(typescript@6.0.3): + tsdown@0.22.0(@typescript/native-preview@7.0.0-dev.20260527.1)(oxc-resolver@11.19.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(tsx@4.22.3)(typescript@6.0.3): dependencies: ansis: 4.3.0 cac: 7.0.0 @@ -14481,7 +14875,7 @@ snapshots: obug: 2.1.1 picomatch: 4.0.4 rolldown: 1.0.2 - rolldown-plugin-dts: 0.25.1(@typescript/native-preview@7.0.0-dev.20260526.1)(oxc-resolver@11.19.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rolldown@1.0.2)(typescript@6.0.3) + rolldown-plugin-dts: 0.25.1(@typescript/native-preview@7.0.0-dev.20260527.1)(oxc-resolver@11.19.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rolldown@1.0.2)(typescript@6.0.3) semver: 7.8.1 tinyexec: 1.2.2 tinyglobby: 0.2.16 @@ -14510,17 +14904,21 @@ snapshots: dependencies: tslib: 1.14.1 - turbo@2.9.14: + turbo@2.9.15: optionalDependencies: - '@turbo/darwin-64': 2.9.14 - '@turbo/darwin-arm64': 2.9.14 - '@turbo/linux-64': 2.9.14 - '@turbo/linux-arm64': 2.9.14 - '@turbo/windows-64': 2.9.14 - '@turbo/windows-arm64': 2.9.14 + '@turbo/darwin-64': 2.9.15 + '@turbo/darwin-arm64': 2.9.15 + '@turbo/linux-64': 2.9.15 + '@turbo/linux-arm64': 2.9.15 + '@turbo/windows-64': 2.9.15 + '@turbo/windows-arm64': 2.9.15 tw-animate-css@1.4.0: {} + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + type-fest@5.6.0: dependencies: tagged-tag: 1.0.0 @@ -14613,6 +15011,10 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + use-callback-ref@1.3.3(@types/react@19.2.15)(react@19.2.6): dependencies: react: 19.2.6 @@ -14735,6 +15137,8 @@ snapshots: wildcard-match@5.1.4: {} + word-wrap@1.2.5: {} + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -14811,6 +15215,8 @@ snapshots: y18n: 5.0.8 yargs-parser: 22.0.0 + yocto-queue@0.1.0: {} + yocto-spinner@1.2.0: dependencies: yoctocolors: 2.1.2 @@ -14827,6 +15233,10 @@ snapshots: dependencies: zod: 4.4.3 + zod-validation-error@4.0.2(zod@4.4.3): + dependencies: + zod: 4.4.3 + zod@3.25.76: {} zod@4.4.3: {} diff --git a/tooling/package.json b/tooling/package.json index 3f803ab47..0048128ab 100644 --- a/tooling/package.json +++ b/tooling/package.json @@ -12,7 +12,7 @@ "@reactive-resume/config": "workspace:*", "@reactive-resume/env": "workspace:*", "@types/pg": "^8.20.0", - "@typescript/native-preview": "7.0.0-dev.20260526.1", + "@typescript/native-preview": "7.0.0-dev.20260527.1", "drizzle-orm": "1.0.0-rc.3", "pg": "^8.21.0", "tsx": "^4.22.3"