Squashed commit of the following:

commit b2b0470a1d9267d042ec0ac66523c6635bf5b199
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Tue May 19 13:13:38 2026 +0200

    chore: update .gitignore to include .vite-hooks and modify pnpm-lock.yaml for dependencies

commit d28fadb5cd8706c874e616102878b4a394ec84c1
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Tue May 19 13:08:04 2026 +0200

    fix: remove timestamp conflict guard

commit c6998d9dbab19d09d3c8054feef1d2e4117555eb
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Tue May 19 12:11:51 2026 +0200

    chore(release): v5.1.5

commit f33d168711804880e1f12e88d24290aae16cc258
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Tue May 19 11:58:35 2026 +0200

    revert: compose.yml

commit d961e6535811a10c335525fb33a08d03e737278d
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Tue May 19 11:58:08 2026 +0200

    refactor(agent): replace 'revert' terminology with 'restore' for clarity, resolves #3086

commit 17f351171be218e33f01c469d95e4164d4c8dc57
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Tue May 19 11:10:41 2026 +0200

    refactor(pdf): simplify sidebar section filtering and update summary feature logic

commit d55179b9d76879e3204de185e8b53fadd0a107ed
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Tue May 19 09:53:37 2026 +0200

    chore: update pnpm-lock.yaml and turbo.json

commit 7cade6980e1a04352536bd44ef773f338c4ef599
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Tue May 19 09:38:30 2026 +0200

    fix(polyfill): add tested polyfill for Map Upsert methods

commit 26d175bb9c53d93225d1e907678445252c13d660
Merge: 1cf33dc6c 5b1297fa2
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Tue May 19 09:23:29 2026 +0200

    Merge remote-tracking branch 'origin/main' into feat/explore-hono-orpc-migration

    # Conflicts:
    #	packages/api/src/services/agent-url.ts
    #	packages/runtime-externals/package.json

commit 1cf33dc6c9d81735730ad656e16dab6501c6d6a1
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Tue May 19 09:22:12 2026 +0200

    chore: preserve branch changes before main sync

commit b380a4b00fdbcdd81ff4f8ef72b330fd027ccda5
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Mon May 18 07:50:28 2026 +0200

    chore: lot of fixes for monorepo migration

commit 8fcf0ec64e1c29572ebaff494338368bfcf75760
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 13:57:17 2026 +0200

    chore: update knip version and refine web app routing with new SEO endpoints

commit 234e68086ff15610a93877354c98e2c020364533
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 12:10:06 2026 +0200

    refactor(auth): update OAuth routes to include API prefix and remove unused schema endpoint

commit 91c84b9a8496b0ce21d71cae9f8b2a027638c9ac
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 11:54:29 2026 +0200

    chore: update dependencies and enhance PWA metadata in web app

commit 150117d4a5a9dd6cd92c64891aad8cae90f6a7af
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 11:12:35 2026 +0200

    docs: revise manifest-only pwa testing scope

commit 6b939a55661aec9dd8122b184e4b60a5c7325fb5
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 11:11:33 2026 +0200

    docs: add manifest-only pwa design

commit 1422e1fc96c400948b273210a1067251087d15d4
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 11:05:04 2026 +0200

    chore(dev): simplify server proxy config

commit bc2ff5a9f6fda41e6c40333c8f163aa23a6c5e48
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 11:04:50 2026 +0200

    docs: add unsafe oauth redirect plan

commit 445359ebe9b96c1515bf1c4c3f73ba8a8448ec12
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 11:04:34 2026 +0200

    feat(auth): add unsafe oauth redirect flag

commit 73fffdd24598e56b2793f7657919bc794835892e
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 10:55:02 2026 +0200

    docs: design unsafe oauth redirect flag

commit c0066aa19c15fc8a4c8e5179ed49889c117519f4
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 10:22:04 2026 +0200

    chore: update translation source paths

commit 9033da082418d252aafd6c2eed72f71f014be3d9
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 10:09:25 2026 +0200

    refactor(arch): react spa + hono migration

commit 6f27936c11bda895977dc63ee550c3346d4ce24b
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 01:10:47 2026 +0200

    docs: add docker nightly tagging design

commit ecc1fd9a88a0ee1dca2f1977dfc17f74527fe1da
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Thu May 14 20:05:44 2026 +0200

    feat: migrate to hono spa server
This commit is contained in:
Amruth Pillai
2026-05-19 13:14:21 +02:00
parent 5b1297fa2b
commit 62f8270b3e
518 changed files with 29398 additions and 26871 deletions
@@ -20,14 +20,14 @@ import { useCallback, useMemo, useState } from "react";
import { useControls } from "react-zoom-pan-pinch";
import { toast } from "sonner";
import { useCopyToClipboard } from "usehooks-ts";
import { buildDocx } from "@reactive-resume/docx";
import { Button } from "@reactive-resume/ui/components/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@reactive-resume/ui/components/tooltip";
import { downloadWithAnchor, generateFilename } from "@reactive-resume/utils/file";
import { buildDocx } from "@reactive-resume/utils/resume/docx";
import { cn } from "@reactive-resume/utils/style";
import { useCurrentResume } from "@/components/resume/builder-resume-draft";
import { useCurrentResume } from "@/features/resume/builder/draft";
import { createResumePdfBlob } from "@/features/resume/export/pdf-document";
import { authClient } from "@/libs/auth/client";
import { createResumePdfBlob } from "@/libs/resume/pdf-document";
type BuilderDockProps = {
pageLayout: BuilderPreviewPageLayout;
@@ -21,8 +21,8 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@reactive-resume/ui/components/dropdown-menu";
import { useCurrentResume, usePatchResume } from "@/components/resume/builder-resume-draft";
import { useDialogStore } from "@/dialogs/store";
import { useCurrentResume, usePatchResume } from "@/features/resume/builder/draft";
import { useConfirm } from "@/hooks/use-confirm";
import { getResumeErrorMessage } from "@/libs/error-message";
import { orpc } from "@/libs/orpc/client";
@@ -5,7 +5,7 @@ import { Suspense, useState } from "react";
import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
import { toast } from "sonner";
import { LoadingScreen } from "@/components/layout/loading-screen";
import { ResumePreview } from "@/components/resume/preview";
import { ResumePreview } from "@/features/resume/preview/preview";
import { BuilderDock } from "./dock";
import { DEFAULT_BUILDER_PREVIEW_PAGE_LAYOUT, getNextBuilderPreviewPageLayout } from "./page-layout";
@@ -6,7 +6,7 @@ import { Button } from "@reactive-resume/ui/components/button";
import { ScrollArea } from "@reactive-resume/ui/components/scroll-area";
import { Separator } from "@reactive-resume/ui/components/separator";
import { getInitials } from "@reactive-resume/utils/string";
import { UserDropdownMenu } from "@/components/user/dropdown-menu";
import { UserDropdownMenu } from "@/features/user/dropdown-menu";
import { getSectionIcon, getSectionTitle, leftSidebarSections } from "@/libs/resume/section";
import { BuilderSidebarEdge } from "../../-components/edge";
import { useBuilderSidebar } from "../../-store/sidebar";
@@ -3,7 +3,7 @@ import type z from "zod";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import { cn } from "@reactive-resume/utils/style";
import { useCurrentResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -4,7 +4,7 @@ import { basicsSchema } from "@reactive-resume/schema/resume/data";
import { FormControl, FormItem, FormLabel, FormMessage } from "@reactive-resume/ui/components/form";
import { Input } from "@reactive-resume/ui/components/input";
import { URLInput } from "@/components/input/url-input";
import { useCurrentBuilderResumeSelector, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useCurrentBuilderResumeSelector, useUpdateResumeData } from "@/features/resume/builder/draft";
import { useSyncFormValues } from "@/hooks/use-sync-form-values";
import { useAppForm } from "@/libs/tanstack-form";
import { SectionBase } from "../shared/section-base";
@@ -3,7 +3,7 @@ import type z from "zod";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import { cn } from "@reactive-resume/utils/style";
import { useCurrentResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -32,8 +32,8 @@ import {
} from "@reactive-resume/ui/components/dropdown-menu";
import { stripHtml } from "@reactive-resume/utils/string";
import { cn } from "@reactive-resume/utils/style";
import { useCurrentResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useDialogStore } from "@/dialogs/store";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { useConfirm } from "@/hooks/use-confirm";
import { getSectionTitle } from "@/libs/resume/section";
import { SectionBase } from "../shared/section-base";
@@ -61,7 +61,7 @@ const experienceItems = vi.hoisted(() => [
},
]);
vi.mock("@/components/resume/builder-resume-draft", () => ({
vi.mock("@/features/resume/builder/draft", () => ({
useCurrentResume: () => ({
data: {
sections: {
@@ -3,7 +3,7 @@ import type z from "zod";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import { cn } from "@reactive-resume/utils/style";
import { useCurrentResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -4,7 +4,7 @@ import { plural } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import { cn } from "@reactive-resume/utils/style";
import { useCurrentResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -3,7 +3,7 @@ import type z from "zod";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import { cn } from "@reactive-resume/utils/style";
import { useCurrentResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -3,7 +3,7 @@ import type z from "zod";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import { cn } from "@reactive-resume/utils/style";
import { useCurrentResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -72,7 +72,7 @@ const sections = vi.hoisted(() => ({
],
}));
vi.mock("@/components/resume/builder-resume-draft", () => ({
vi.mock("@/features/resume/builder/draft", () => ({
useCurrentResume: () => ({
data: {
sections: {
@@ -17,7 +17,7 @@ import {
InputGroupText,
} from "@reactive-resume/ui/components/input-group";
import { ColorPicker } from "@/components/input/color-picker";
import { useCurrentResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { useSyncFormValues } from "@/hooks/use-sync-form-values";
import { getReadableErrorMessage } from "@/libs/error-message";
import { orpc } from "@/libs/orpc/client";
@@ -26,7 +26,7 @@ const sectionItems = vi.hoisted(() => [
},
]);
vi.mock("@/components/resume/builder-resume-draft", () => ({
vi.mock("@/features/resume/builder/draft", () => ({
useCurrentResume: () => ({
data: {
sections: { profiles: { title: "Profiles", columns: 1, hidden: false, items: sectionItems } },
@@ -3,7 +3,7 @@ import type z from "zod";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import { cn } from "@reactive-resume/utils/style";
import { useCurrentResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -35,7 +35,7 @@ const items = vi.hoisted(() => [
},
]);
vi.mock("@/components/resume/builder-resume-draft", () => ({
vi.mock("@/features/resume/builder/draft", () => ({
useCurrentResume: () => ({
data: { sections: { projects: { title: "Projects", columns: 1, hidden: false, items } } },
}),
@@ -3,7 +3,7 @@ import type z from "zod";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import { cn } from "@reactive-resume/utils/style";
import { useCurrentResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -3,7 +3,7 @@ import type z from "zod";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import { cn } from "@reactive-resume/utils/style";
import { useCurrentResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -3,7 +3,7 @@ import type z from "zod";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import { cn } from "@reactive-resume/utils/style";
import { useCurrentResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -10,7 +10,7 @@ const sectionItems = vi.hoisted(() => [
{ id: "s2", name: "Go", proficiency: "Intermediate", level: 3, keywords: [], description: "", hidden: false },
]);
vi.mock("@/components/resume/builder-resume-draft", () => ({
vi.mock("@/features/resume/builder/draft", () => ({
useCurrentResume: () => ({
data: { sections: { skills: { title: "Skills", columns: 1, hidden: false, items: sectionItems } } },
}),
@@ -3,7 +3,7 @@ import type z from "zod";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import { cn } from "@reactive-resume/utils/style";
import { useCurrentResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -1,5 +1,5 @@
import { RichInput } from "@/components/input/rich-input";
import { useCurrentResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { SectionBase } from "../shared/section-base";
export function SummarySectionBuilder() {
@@ -3,7 +3,7 @@ import type z from "zod";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import { cn } from "@reactive-resume/utils/style";
import { useCurrentResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -4,7 +4,7 @@ import { CaretDownIcon } from "@phosphor-icons/react";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@reactive-resume/ui/components/accordion";
import { Button } from "@reactive-resume/ui/components/button";
import { cn } from "@reactive-resume/utils/style";
import { useCurrentResume } from "@/components/resume/builder-resume-draft";
import { useCurrentResume } from "@/features/resume/builder/draft";
import { getSectionIcon, getSectionTitle } from "@/libs/resume/section";
import { useSectionStore } from "../../../-store/section";
import { SectionDropdownMenu } from "./section-menu";
@@ -36,8 +36,8 @@ import {
DropdownMenuTrigger,
} from "@reactive-resume/ui/components/dropdown-menu";
import { cn } from "@reactive-resume/utils/style";
import { useCurrentResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useDialogStore } from "@/dialogs/store";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { useConfirm } from "@/hooks/use-confirm";
import {
addItemToSection,
@@ -24,8 +24,8 @@ import {
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@reactive-resume/ui/components/dropdown-menu";
import { useCurrentResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useDialogStore } from "@/dialogs/store";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { useConfirm } from "@/hooks/use-confirm";
import { usePrompt } from "@/hooks/use-prompt";
@@ -11,7 +11,7 @@ import { ColorPicker } from "@/components/input/color-picker";
import { IconPicker } from "@/components/input/icon-picker";
import { LevelTypeCombobox } from "@/components/level/combobox";
import { LevelDisplay } from "@/components/level/display";
import { useCurrentResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { useSyncFormValues } from "@/hooks/use-sync-form-values";
import { useAppForm } from "@/libs/tanstack-form";
import { SectionBase } from "../shared/section-base";
@@ -17,9 +17,9 @@ vi.mock("@reactive-resume/utils/file", () => ({
downloadWithAnchor,
generateFilename: (name: string, ext: string) => `${name}.${ext}`,
}));
vi.mock("@reactive-resume/utils/resume/docx", () => ({ buildDocx }));
vi.mock("@/libs/resume/pdf-document", () => ({ createResumePdfBlob }));
vi.mock("@/components/resume/builder-resume-draft", () => ({
vi.mock("@reactive-resume/docx", () => ({ buildDocx }));
vi.mock("@/features/resume/export/pdf-document", () => ({ createResumePdfBlob }));
vi.mock("@/features/resume/builder/draft", () => ({
useResume: () => ({ id: "r1", name: "My Resume", data: defaultResumeData }),
}));
@@ -3,11 +3,11 @@ import { Trans } from "@lingui/react/macro";
import { CircleNotchIcon, FileDocIcon, FileJsIcon, FilePdfIcon } from "@phosphor-icons/react";
import { useCallback, useState } from "react";
import { toast } from "sonner";
import { buildDocx } from "@reactive-resume/docx";
import { Button } from "@reactive-resume/ui/components/button";
import { downloadWithAnchor, generateFilename } from "@reactive-resume/utils/file";
import { buildDocx } from "@reactive-resume/utils/resume/docx";
import { useResume } from "@/components/resume/builder-resume-draft";
import { createResumePdfBlob } from "@/libs/resume/pdf-document";
import { useResume } from "@/features/resume/builder/draft";
import { createResumePdfBlob } from "@/features/resume/export/pdf-document";
import { SectionBase } from "../shared/section-base";
export function ExportSectionBuilder() {
@@ -9,7 +9,7 @@ import {
InputGroupText,
} from "@reactive-resume/ui/components/input-group";
import { Slider } from "@reactive-resume/ui/components/slider";
import { useCurrentResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { useSyncFormValues } from "@/hooks/use-sync-form-values";
import { useAppForm } from "@/libs/tanstack-form";
import { SectionBase } from "../../shared/section-base";
@@ -1,5 +1,4 @@
import type { DragEndEvent, DragStartEvent } from "@dnd-kit/core";
import type { SectionType } from "@reactive-resume/schema/resume/data";
import type { CSSProperties, HTMLAttributes } from "react";
import {
closestCorners,
@@ -20,9 +19,9 @@ import { match } from "ts-pattern";
import { Button } from "@reactive-resume/ui/components/button";
import { Switch } from "@reactive-resume/ui/components/switch";
import { cn } from "@reactive-resume/utils/style";
import { useCurrentResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { templates } from "@/dialogs/resume/template/data";
import { getSectionTitle } from "@/libs/resume/section";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { resolveLayoutSectionTitle } from "./title";
import { filterVisibleLayoutSectionIds } from "./visibility";
type ColumnId = "main" | "sidebar";
@@ -403,15 +402,7 @@ type LayoutItemContentProps = HTMLAttributes<HTMLDivElement> & {
const LayoutItemContent = forwardRef<HTMLDivElement, LayoutItemContentProps>(
({ id, isDragging, isOverlay, className, style, ...rest }, ref) => {
const resume = useCurrentResume();
const title = (() => {
if (!resume) return id;
if (id === "summary") return resume.data.summary.title || getSectionTitle("summary");
if (id in resume.data.sections)
return resume.data.sections[id as SectionType].title || getSectionTitle(id as SectionType);
const customSection = resume.data.customSections.find((section) => section.id === id);
if (customSection) return customSection.title;
return id;
})();
const title = resume ? resolveLayoutSectionTitle(resume.data, id) : id;
return (
<div
@@ -0,0 +1,45 @@
import type { ResumeData } from "@reactive-resume/schema/resume/data";
import { beforeAll, describe, expect, it } from "vitest";
import { i18n } from "@lingui/core";
import { defaultResumeData } from "@reactive-resume/schema/resume/default";
import { resolveLayoutSectionTitle } from "./title";
const createResumeData = (): ResumeData => structuredClone(defaultResumeData);
beforeAll(() => {
i18n.loadAndActivate({ locale: "en", messages: {} });
});
describe("resolveLayoutSectionTitle", () => {
it("uses a custom section title when present", () => {
const data = createResumeData();
data.customSections = [
{
id: "custom-awards",
type: "awards",
title: "Industry Recognition",
columns: 1,
hidden: false,
items: [],
},
] as never;
expect(resolveLayoutSectionTitle(data, "custom-awards")).toBe("Industry Recognition");
});
it("falls back to the custom section type title when the custom title is empty", () => {
const data = createResumeData();
data.customSections = [
{
id: "custom-awards",
type: "awards",
title: "",
columns: 1,
hidden: false,
items: [],
},
] as never;
expect(resolveLayoutSectionTitle(data, "custom-awards")).toBe("Awards");
});
});
@@ -0,0 +1,24 @@
import type { CustomSectionType, ResumeData, SectionType } from "@reactive-resume/schema/resume/data";
import { getSectionTitle } from "@/libs/resume/section";
const hasTitle = (title: string): boolean => title.trim().length > 0;
export const resolveLayoutSectionTitle = (data: ResumeData, sectionId: string): string => {
if (sectionId === "summary") return hasTitle(data.summary.title) ? data.summary.title : getSectionTitle("summary");
if (sectionId in data.sections) {
const section = data.sections[sectionId as SectionType];
return hasTitle(section.title) ? section.title : getSectionTitle(sectionId as SectionType);
}
const customSection = data.customSections.find((section) => section.id === sectionId);
if (customSection) {
return hasTitle(customSection.title)
? customSection.title
: getSectionTitle(customSection.type as CustomSectionType);
}
return sectionId;
};
@@ -21,7 +21,7 @@ vi.mock("@/components/input/rich-input", () => ({
return <textarea data-testid="rich-input" value={props.value} readOnly />;
},
}));
vi.mock("@/components/resume/builder-resume-draft", () => ({
vi.mock("@/features/resume/builder/draft", () => ({
useCurrentResume: () => ({
data: { metadata: { notes: "saved notes" } },
}),
@@ -1,6 +1,6 @@
import { Trans } from "@lingui/react/macro";
import { RichInput } from "@/components/input/rich-input";
import { useCurrentResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { SectionBase } from "../shared/section-base";
export function NotesSectionBuilder() {
@@ -10,9 +10,9 @@ import {
InputGroupText,
} from "@reactive-resume/ui/components/input-group";
import { Switch } from "@reactive-resume/ui/components/switch";
import { getLocaleOptions } from "@/components/locale/combobox";
import { useResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { Combobox } from "@/components/ui/combobox";
import { getLocaleOptions } from "@/features/locale/combobox";
import { useResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { useSyncFormValues } from "@/hooks/use-sync-form-values";
import { useAppForm } from "@/libs/tanstack-form";
import { SectionBase } from "../shared/section-base";
@@ -9,7 +9,7 @@ import { match } from "ts-pattern";
import { Alert, AlertDescription } from "@reactive-resume/ui/components/alert";
import { Badge } from "@reactive-resume/ui/components/badge";
import { Button } from "@reactive-resume/ui/components/button";
import { useResume } from "@/components/resume/builder-resume-draft";
import { useResume } from "@/features/resume/builder/draft";
import { getOrpcErrorMessage } from "@/libs/error-message";
import { orpc } from "@/libs/orpc/client";
import { SectionBase } from "../shared/section-base";
@@ -10,7 +10,7 @@ import { Button } from "@reactive-resume/ui/components/button";
import { Input } from "@reactive-resume/ui/components/input";
import { Label } from "@reactive-resume/ui/components/label";
import { Switch } from "@reactive-resume/ui/components/switch";
import { useCurrentResume, usePatchResume } from "@/components/resume/builder-resume-draft";
import { useCurrentResume, usePatchResume } from "@/features/resume/builder/draft";
import { useConfirm } from "@/hooks/use-confirm";
import { usePrompt } from "@/hooks/use-prompt";
import { authClient } from "@/libs/auth/client";
@@ -9,7 +9,7 @@ import { useDialogStore } from "@/dialogs/store";
vi.mock("../shared/section-base", () => ({
SectionBase: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
}));
vi.mock("@/components/resume/builder-resume-draft", () => ({
vi.mock("@/features/resume/builder/draft", () => ({
useCurrentResume: () => ({
data: { metadata: { template: "ditto" } },
}),
@@ -2,9 +2,9 @@ import { useLingui } from "@lingui/react";
import { SwapIcon } from "@phosphor-icons/react";
import { Badge } from "@reactive-resume/ui/components/badge";
import { Button } from "@reactive-resume/ui/components/button";
import { useCurrentResume } from "@/components/resume/builder-resume-draft";
import { templates } from "@/dialogs/resume/template/data";
import { useDialogStore } from "@/dialogs/store";
import { useCurrentResume } from "@/features/resume/builder/draft";
import { SectionBase } from "../shared/section-base";
export function TemplateSectionBuilder() {
@@ -10,8 +10,8 @@ import {
InputGroupText,
} from "@reactive-resume/ui/components/input-group";
import { Separator } from "@reactive-resume/ui/components/separator";
import { useResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { FontFamilyCombobox, FontWeightCombobox, getNextWeights } from "@/components/typography/combobox";
import { useResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { useSyncFormValues } from "@/hooks/use-sync-form-values";
import { useAppForm } from "@/libs/tanstack-form";
import { SectionBase } from "../shared/section-base";
@@ -2,5 +2,4 @@ import { createFileRoute, lazyRouteComponent } from "@tanstack/react-router";
export const Route = createFileRoute("/builder/$resumeId/")({
component: lazyRouteComponent(() => import("./-components/preview-page"), "PreviewPage"),
ssr: false,
});
+15 -14
View File
@@ -3,8 +3,7 @@ import type { Layout } from "react-resizable-panels";
import type { BuilderLayout } from "./-store/sidebar";
import { useSuspenseQuery } from "@tanstack/react-query";
import { createFileRoute, Outlet, redirect } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/react-start";
import { getCookie, setCookie } from "@tanstack/react-start/server";
import Cookies from "js-cookie";
import { useEffect, useRef } from "react";
import { usePanelRef } from "react-resizable-panels";
import { ResizableGroup, ResizablePanel, ResizableSeparator } from "@reactive-resume/ui/components/resizable";
@@ -14,9 +13,10 @@ import {
useResumeCleanup,
useResumeStore,
useResumeUpdateSubscription,
} from "@/components/resume/builder-resume-draft";
} from "@/features/resume/builder/draft";
import { useIsMobile } from "@/hooks/use-mobile";
import { orpc } from "@/libs/orpc/client";
import { createNoindexFollowMeta } from "@/libs/seo";
import { BuilderHeader } from "./-components/header";
import { BuilderSidebarLeft } from "./-sidebar/left";
import { BuilderSidebarRight } from "./-sidebar/right";
@@ -37,14 +37,16 @@ export const Route = createFileRoute("/builder/$resumeId")({
},
loader: async ({ params, context }) => {
const [layout, resume] = await Promise.all([
getBuilderLayoutServerFn(),
getBuilderLayout(),
context.queryClient.ensureQueryData(orpc.resume.getById.queryOptions({ input: { id: params.resumeId } })),
]);
return { layout, name: resume.name };
},
head: ({ loaderData }) => ({
meta: loaderData ? [{ title: `${loaderData.name} - Reactive Resume` }] : undefined,
meta: loaderData
? [{ title: `${loaderData.name} - Reactive Resume` }, createNoindexFollowMeta()]
: [createNoindexFollowMeta()],
}),
});
@@ -116,7 +118,7 @@ function BuilderLayoutShell({ initialLayout }: BuilderLayoutShellProps) {
const nextLayout = mapPanelLayoutToBuilderLayout(layout);
if (!canPersistLayoutRef.current) return;
setLayout(nextLayout);
void setBuilderLayoutServerFn({ data: nextLayout });
setBuilderLayout(nextLayout);
};
useEffect(() => {
@@ -171,14 +173,13 @@ function BuilderLayoutShell({ initialLayout }: BuilderLayoutShellProps) {
);
}
const setBuilderLayoutServerFn = createServerFn({ method: "POST" })
.inputValidator((data): BuilderLayout => parseBuilderLayoutCookie(JSON.stringify(data)))
.handler(async ({ data }) => {
setCookie(BUILDER_LAYOUT_COOKIE_NAME, JSON.stringify(data), { path: "/" });
});
const setBuilderLayout = (data: BuilderLayout) => {
const layout = parseBuilderLayoutCookie(JSON.stringify(data));
Cookies.set(BUILDER_LAYOUT_COOKIE_NAME, JSON.stringify(layout), { path: "/" });
};
const getBuilderLayoutServerFn = createServerFn({ method: "GET" }).handler(async (): Promise<BuilderLayout> => {
const layout = getCookie(BUILDER_LAYOUT_COOKIE_NAME);
const getBuilderLayout = (): BuilderLayout => {
const layout = Cookies.get(BUILDER_LAYOUT_COOKIE_NAME);
if (!layout) return DEFAULT_BUILDER_LAYOUT;
return parseBuilderLayoutCookie(layout);
});
};