+
{impactLabel(suggestion.impact)}
{suggestion.title}
diff --git a/package.json b/package.json
index bca5ff3f8..f3183846e 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
],
"scripts": {
"build": "turbo run build",
+ "doctor": "turbo run doctor",
"check": "biome check --write --unsafe .",
"db:generate": "turbo run db:generate --filter=@reactive-resume/db",
"db:migrate": "turbo run db:migrate --filter=@reactive-resume/db",
diff --git a/packages/api/src/features/agent/service.ts b/packages/api/src/features/agent/service.ts
index e4f17a69b..f707d2cfe 100644
--- a/packages/api/src/features/agent/service.ts
+++ b/packages/api/src/features/agent/service.ts
@@ -946,11 +946,13 @@ export const agentService = {
await getThread({ id: input.id, userId: input.userId });
- await db.delete(schema.agentAttachment).where(eq(schema.agentAttachment.threadId, input.id));
- await db
- .update(schema.agentThread)
- .set({ status: "deleted", deletedAt: new Date() })
- .where(and(eq(schema.agentThread.id, input.id), eq(schema.agentThread.userId, input.userId)));
+ await Promise.all([
+ db.delete(schema.agentAttachment).where(eq(schema.agentAttachment.threadId, input.id)),
+ db
+ .update(schema.agentThread)
+ .set({ status: "deleted", deletedAt: new Date() })
+ .where(and(eq(schema.agentThread.id, input.id), eq(schema.agentThread.userId, input.userId))),
+ ]);
try {
await getStorageService().delete(`uploads/${input.userId}/agent/${input.id}`);
diff --git a/packages/email/package.json b/packages/email/package.json
index 2976ec63d..86754b0ca 100644
--- a/packages/email/package.json
+++ b/packages/email/package.json
@@ -9,6 +9,7 @@
},
"scripts": {
"dev": "email dev --dir src/templates --port 3002",
+ "doctor": "react-doctor",
"typecheck": "tsgo --noEmit",
"test": "vitest run --passWithNoTests",
"test:coverage": "vitest run --coverage --passWithNoTests",
@@ -27,6 +28,7 @@
"@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": "^6.0.3"
}
}
diff --git a/packages/pdf/package.json b/packages/pdf/package.json
index e62c47b9d..554291b55 100644
--- a/packages/pdf/package.json
+++ b/packages/pdf/package.json
@@ -15,6 +15,7 @@
"#react-pdf-renderer": "@react-pdf/renderer"
},
"scripts": {
+ "doctor": "react-doctor",
"typecheck": "tsgo --noEmit",
"test": "vitest run --passWithNoTests",
"test:coverage": "vitest run --coverage --passWithNoTests",
@@ -38,6 +39,7 @@
"@reactive-resume/config": "workspace:*",
"@types/react": "^19.2.15",
"@typescript/native-preview": "7.0.0-dev.20260526.1",
+ "react-doctor": "^0.2.6",
"typescript": "^6.0.3"
}
}
diff --git a/packages/pdf/src/context.tsx b/packages/pdf/src/context.tsx
index b409f933a..a45f48731 100644
--- a/packages/pdf/src/context.tsx
+++ b/packages/pdf/src/context.tsx
@@ -1,7 +1,7 @@
import type { ResumeData } from "@reactive-resume/schema/resume/data";
import type { ReactNode } from "react";
import type { SectionTitleResolver } from "./section-title";
-import { createContext, use } from "react";
+import { createContext, use, useMemo } from "react";
import { isRTL } from "@reactive-resume/utils/locale";
type RenderContextValue = ResumeData & {
@@ -19,8 +19,12 @@ export type RenderProviderProps = {
export const RenderProvider = ({ data, resolveSectionTitle, children }: RenderProviderProps) => {
const rtl = isRTL(data.metadata.page.locale);
+ const contextValue = useMemo
(
+ () => ({ ...data, resolveSectionTitle, rtl }),
+ [data, resolveSectionTitle, rtl],
+ );
- return {children};
+ return {children};
};
export const useRender = (): RenderContextValue => {
diff --git a/packages/pdf/src/templates/shared/context.tsx b/packages/pdf/src/templates/shared/context.tsx
index d5daf810c..d88eb8f2d 100644
--- a/packages/pdf/src/templates/shared/context.tsx
+++ b/packages/pdf/src/templates/shared/context.tsx
@@ -10,7 +10,7 @@ import type {
TemplateStyleSlot,
TemplateStyleSlots,
} from "./types";
-import { createContext, use } from "react";
+import { createContext, use, useMemo } from "react";
type TemplateContextValue = {
styles: TemplateStyleSlots;
@@ -60,9 +60,12 @@ export const TemplateProvider = ({
features = EMPTY_FEATURES,
children,
}: TemplateProviderProps) => {
- return (
- {children}
+ const contextValue = useMemo(
+ () => ({ styles, featureStyles, colors, features }),
+ [colors, featureStyles, features, styles],
);
+
+ return {children};
};
export const TemplatePlacementProvider = ({
diff --git a/packages/pdf/src/templates/shared/primitives.tsx b/packages/pdf/src/templates/shared/primitives.tsx
index 6f2d96422..1fb50194d 100644
--- a/packages/pdf/src/templates/shared/primitives.tsx
+++ b/packages/pdf/src/templates/shared/primitives.tsx
@@ -4,17 +4,11 @@ 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 { safeTextStyle } from "./safe-text-style";
import { composeLinkStyles, composeStyles } from "./styles";
const asStyleInput = (style: unknown): StyleInput => style as StyleInput;
-export const safeTextStyle = {
- minWidth: 0,
- maxWidth: "100%",
- flexShrink: 1,
- overflow: "hidden",
-} satisfies Style;
-
export const Div = ({ style, ...props }: ComponentProps) => {
const divStyle = useTemplateStyle("div");
diff --git a/packages/pdf/src/templates/shared/rich-text-html.ts b/packages/pdf/src/templates/shared/rich-text-html.ts
index 5cae7cb79..d9649941c 100644
--- a/packages/pdf/src/templates/shared/rich-text-html.ts
+++ b/packages/pdf/src/templates/shared/rich-text-html.ts
@@ -72,9 +72,11 @@ const tryConvertPseudoBulletParagraph = (paragraphInnerHtml: string): string | n
const cleaned = stripEmptyInlineWrappers(paragraphInnerHtml);
if (!/
segment.trim())
- .filter((segment) => segment.length > 0);
+ const segments: string[] = [];
+ for (const segment of splitByBreaks(cleaned)) {
+ const trimmed = segment.trim();
+ if (trimmed.length > 0) segments.push(trimmed);
+ }
if (segments.length < 2) return null;
if (!segments.every((segment) => PSEUDO_BULLET_LEAD.test(segment))) return null;
diff --git a/packages/pdf/src/templates/shared/rich-text.tsx b/packages/pdf/src/templates/shared/rich-text.tsx
index b8bff8240..6a504668b 100644
--- a/packages/pdf/src/templates/shared/rich-text.tsx
+++ b/packages/pdf/src/templates/shared/rich-text.tsx
@@ -5,7 +5,6 @@ import { Html } from "react-pdf-html";
import { useRender } from "../../context";
import { Text as PdfText, View } from "../../renderer";
import { useTemplateStyle } from "./context";
-import { safeTextStyle } from "./primitives";
import { convertPseudoBulletParagraphs, normalizeRichTextHtml, richTextMarkClassName } from "./rich-text-html";
import { renderRichTextParagraph, toRichTextStyleArray } from "./rich-text-renderers";
import {
@@ -15,6 +14,7 @@ import {
resolveRichTextBodyLineHeight,
stripRichTextVerticalMargins,
} from "./rich-text-spacing";
+import { safeTextStyle } from "./safe-text-style";
import { composeStyles, mergeLinkStyles, mergeStyles } from "./styles";
const richListItemContentStackStyle = {
diff --git a/packages/pdf/src/templates/shared/safe-text-style.ts b/packages/pdf/src/templates/shared/safe-text-style.ts
new file mode 100644
index 000000000..220d8ca9a
--- /dev/null
+++ b/packages/pdf/src/templates/shared/safe-text-style.ts
@@ -0,0 +1,8 @@
+import type { Style } from "@react-pdf/types";
+
+export const safeTextStyle = {
+ minWidth: 0,
+ maxWidth: "100%",
+ flexShrink: 1,
+ overflow: "hidden",
+} satisfies Style;
diff --git a/packages/ui/package.json b/packages/ui/package.json
index e92ef1d74..2998874c3 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -11,6 +11,7 @@
},
"scripts": {
"shadcn": "shadcn",
+ "doctor": "react-doctor",
"typecheck": "tsgo --noEmit",
"test": "vitest run --passWithNoTests",
"test:coverage": "vitest run --coverage --passWithNoTests",
@@ -43,6 +44,7 @@
"@types/react-dom": "^19.2.3",
"@typescript/native-preview": "7.0.0-dev.20260526.1",
"postcss": "^8.5.15",
+ "react-doctor": "^0.2.6",
"tailwindcss": "^4.3.0",
"typescript": "^6.0.3"
}
diff --git a/packages/ui/src/components/badge.test.tsx b/packages/ui/src/components/badge.test.tsx
index 475100d89..8126973b3 100644
--- a/packages/ui/src/components/badge.test.tsx
+++ b/packages/ui/src/components/badge.test.tsx
@@ -32,8 +32,8 @@ describe("Badge", () => {
});
it("supports a custom render function", () => {
- render( }>link);
- const anchor = screen.getByRole("link", { name: "link" });
+ render( }>View profile);
+ const anchor = screen.getByRole("link", { name: "View profile" });
expect(anchor).toBeInTheDocument();
});
});
diff --git a/packages/ui/src/components/form.tsx b/packages/ui/src/components/form.tsx
index f1fe6880e..1e96d4e2f 100644
--- a/packages/ui/src/components/form.tsx
+++ b/packages/ui/src/components/form.tsx
@@ -18,9 +18,10 @@ type FormItemProps = React.ComponentProps<"div"> & { hasError?: boolean };
function FormItem({ className, hasError = false, ...props }: FormItemProps) {
const id = React.useId();
+ const contextValue = React.useMemo(() => ({ id, hasError }), [hasError, id]);
return (
-
+
);
diff --git a/packages/ui/src/components/input-group.tsx b/packages/ui/src/components/input-group.tsx
index 4e58b12a1..46e391307 100644
--- a/packages/ui/src/components/input-group.tsx
+++ b/packages/ui/src/components/input-group.tsx
@@ -49,7 +49,6 @@ function InputGroupAddon({
className={cn(inputGroupAddonVariants({ align }), className)}
onKeyDown={(e) => {
if (!(e.target instanceof Element) || !e.currentTarget.contains(e.target)) return;
- // Only respond to Space or Enter
if (e.key !== " " && e.key !== "Enter") return;
if (!(e.target as HTMLElement).closest("button")) {
e.preventDefault();
diff --git a/packages/ui/src/components/sidebar.tsx b/packages/ui/src/components/sidebar.tsx
index 3ab8dcd88..5a01bf9e9 100644
--- a/packages/ui/src/components/sidebar.tsx
+++ b/packages/ui/src/components/sidebar.tsx
@@ -268,6 +268,7 @@ function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
return (