From fcfb18fe5ab343c9f19c3b91f0d86f4f7bf0c0c3 Mon Sep 17 00:00:00 2001 From: Amruth Pillai Date: Wed, 30 Sep 2026 22:48:39 +0200 Subject: [PATCH] chore: migrate linting and formatting to Oxc --- .github/copilot-instructions.md | 2 + .oxfmtrc.json | 62 ++ .oxlintrc.json | 107 +++ .vscode/extensions.json | 2 +- .vscode/settings.json | 13 +- AGENTS.md | 56 +- DESIGN.md | 32 +- GLOSSARY.md | 8 +- README.md | 20 +- REDESIGN_PLAN.md | 461 +++++----- apps/server/src/vercel.test.ts | 11 +- apps/web/index.html | 36 +- apps/web/src/components/input/chip-input.tsx | 10 +- .../web/src/components/input/color-picker.tsx | 8 +- .../components/input/github-stars-button.tsx | 2 +- apps/web/src/components/input/url-input.tsx | 2 +- .../layout/breakpoint-indicator.tsx | 2 +- .../src/components/layout/loading-screen.tsx | 2 +- apps/web/src/components/level/combobox.tsx | 2 +- apps/web/src/components/level/display.tsx | 2 +- .../src/components/typography/combobox.tsx | 2 +- .../components/typography/font-display.tsx | 2 +- apps/web/src/dialogs/auth/change-password.tsx | 2 +- .../src/dialogs/auth/disable-two-factor.tsx | 2 +- .../src/dialogs/auth/enable-two-factor.tsx | 2 +- apps/web/src/dialogs/resume/index.tsx | 2 +- apps/web/src/dialogs/schemas.ts | 2 +- .../components/application-actions-menu.tsx | 2 +- .../components/application-card.tsx | 8 +- .../applications/components/board.tsx | 10 +- .../applications/components/calendar-view.tsx | 33 +- .../components/detail/activity.tsx | 16 +- .../components/detail/contacts.tsx | 16 +- .../components/detail/next-step-card.tsx | 10 +- .../components/export-applications-sheet.tsx | 6 +- .../components/file-attachment-field.tsx | 2 +- .../applications/components/insights-view.tsx | 40 +- .../components/interview-dialog.tsx | 8 +- .../applications/components/list-view.tsx | 26 +- .../web/src/features/applications/insights.ts | 2 +- .../src/features/applications/interviews.ts | 2 +- .../src/features/applications/next-step.ts | 2 +- .../applications/use-application-actions.ts | 23 +- .../features/assistant/assistant-panel.tsx | 44 +- apps/web/src/features/assistant/dock.tsx | 8 +- apps/web/src/features/assistant/markdown.tsx | 2 +- .../src/features/assistant/provider-setup.tsx | 10 +- apps/web/src/features/ats-checker/checker.tsx | 66 +- .../ats-checker/report/finding-row.tsx | 8 +- .../ats-checker/report/jd-coverage.tsx | 14 +- .../ats-checker/report/report-view.tsx | 14 +- .../ats-checker/report/score-header.tsx | 10 +- .../src/features/ats-checker/run-ats-check.ts | 9 +- .../features/auth/components/social-auth.tsx | 4 +- apps/web/src/features/auth/layout.tsx | 2 +- .../src/features/auth/login-redirect.test.tsx | 1 + apps/web/src/features/auth/pages/consent.tsx | 12 +- apps/web/src/features/auth/pages/error.tsx | 4 +- .../features/auth/pages/forgot-password.tsx | 4 +- apps/web/src/features/auth/pages/login.tsx | 6 +- apps/web/src/features/auth/pages/register.tsx | 8 +- .../features/auth/pages/reset-password.tsx | 2 +- .../features/auth/pages/resume-password.tsx | 4 +- .../src/features/auth/pages/verify-2fa.tsx | 4 +- apps/web/src/features/auth/redirect.ts | 2 +- .../features/command-palette/pages/ask.tsx | 4 +- .../command-palette/pages/navigation.tsx | 2 +- .../pages/preferences/language.tsx | 4 +- .../pages/preferences/theme.tsx | 2 +- .../command-palette/pages/resumes.tsx | 8 +- .../features/documents/document-actions.tsx | 8 +- .../src/features/documents/document-card.tsx | 32 +- .../src/features/documents/documents-page.tsx | 32 +- .../documents/new-document-dialog.test.tsx | 2 +- .../features/documents/resume-thumbnail.tsx | 4 +- .../web/src/features/documents/trash-page.tsx | 10 +- apps/web/src/features/homepage/background.tsx | 6 +- apps/web/src/features/homepage/check.tsx | 20 +- apps/web/src/features/homepage/design.tsx | 34 +- apps/web/src/features/homepage/faq.tsx | 18 +- apps/web/src/features/homepage/footer.tsx | 6 +- apps/web/src/features/homepage/header.tsx | 12 +- apps/web/src/features/homepage/hero.tsx | 46 +- apps/web/src/features/homepage/page.tsx | 2 +- apps/web/src/features/homepage/prerender.tsx | 2 +- apps/web/src/features/homepage/sections.tsx | 46 +- apps/web/src/features/homepage/share.tsx | 16 +- apps/web/src/features/homepage/sheet.tsx | 8 +- apps/web/src/features/homepage/tailor.tsx | 34 +- apps/web/src/features/homepage/ui.tsx | 4 +- apps/web/src/features/homepage/write.tsx | 28 +- apps/web/src/features/letters/export.ts | 4 +- apps/web/src/features/locale/combobox.tsx | 2 +- .../src/features/locale/locale-options.tsx | 2 +- .../src/features/resume/builder/draft.test.ts | 2 +- apps/web/src/features/resume/builder/draft.ts | 4 +- .../resume/builder/password-dialog.tsx | 4 +- .../features/resume/editor/check/actions.ts | 13 +- .../resume/editor/check/check-panel.tsx | 48 +- .../resume/editor/check/issues.test.ts | 2 +- .../features/resume/editor/check/issues.ts | 2 +- .../resume/editor/check/job-match.tsx | 77 +- .../resume/editor/check/page-layer.tsx | 22 +- .../resume/editor/check/parser-view.tsx | 16 +- .../features/resume/editor/check/review.ts | 2 +- .../features/resume/editor/check/use-check.ts | 4 +- .../resume/editor/check/writing-tab.tsx | 38 +- .../web/src/features/resume/editor/chrome.tsx | 10 +- .../src/features/resume/editor/design/fit.ts | 4 +- .../resume/editor/design/style-groups.tsx | 38 +- .../resume/editor/design/template-group.tsx | 16 +- .../features/resume/editor/page-overlay.tsx | 8 +- .../resume/editor/proposals/proposal-list.tsx | 18 +- .../features/resume/editor/save-status.tsx | 2 +- .../features/resume/editor/write/actions.ts | 2 +- .../resume/editor/write/add-section.tsx | 12 +- .../resume/editor/write/basics-card.tsx | 16 +- .../resume/editor/write/dates-field.tsx | 10 +- .../features/resume/editor/write/entries.tsx | 16 +- .../resume/editor/write/entry-card.tsx | 24 +- .../features/resume/editor/write/fields.tsx | 8 +- .../features/resume/editor/write/improve.tsx | 16 +- .../features/resume/editor/write/outline.tsx | 10 +- .../resume/editor/write/picture-settings.tsx | 8 +- .../features/resume/editor/write/reveal.ts | 2 +- .../resume/editor/write/rich-text-editor.tsx | 16 +- .../resume/editor/write/section-row.tsx | 14 +- .../resume/editor/write/summary-editor.tsx | 2 +- .../resume/editor/write/write-panel.tsx | 16 +- .../src/features/resume/export/pdf.worker.ts | 2 +- .../resume/export/use-resume-export.ts | 4 +- .../features/resume/preview/pdf-canvas.tsx | 4 +- .../features/resume/preview/pdf-thumbnail.ts | 2 +- .../resume/preview/preview.browser.tsx | 8 +- .../resume/preview/preview.shared.tsx | 2 +- .../src/features/resume/public/pdf-viewer.tsx | 4 +- .../features/resume/public/public-resume.tsx | 22 +- .../features/resume/public/resume-reflow.tsx | 12 +- .../features/resume/share/download-tab.tsx | 28 +- .../src/features/resume/share/history-tab.tsx | 14 +- .../src/features/resume/share/link-tab.tsx | 28 +- .../resume/stylesheet/color-format.ts | 6 +- .../resume/stylesheet/editor-extensions.ts | 4 +- .../src/features/resume/stylesheet/editor.tsx | 22 +- .../features/resume/stylesheet/protocol.ts | 4 +- .../src/features/resume/stylesheet/targets.ts | 4 +- .../src/features/settings/account/data.tsx | 8 +- .../features/settings/account/export.test.ts | 2 +- .../src/features/settings/account/page.tsx | 4 +- .../src/features/settings/account/profile.tsx | 10 +- .../features/settings/account/security.tsx | 6 +- .../web/src/features/settings/ai/api-keys.tsx | 16 +- apps/web/src/features/settings/ai/catalog.ts | 2 +- apps/web/src/features/settings/ai/mcp.tsx | 4 +- .../src/features/settings/ai/providers.tsx | 30 +- .../web/src/features/settings/preferences.tsx | 10 +- apps/web/src/features/settings/root.tsx | 8 +- apps/web/src/features/settings/section.tsx | 8 +- apps/web/src/features/theme/provider.tsx | 2 +- apps/web/src/hooks/use-confirm.test.tsx | 2 +- apps/web/src/index.css | 3 +- .../src/libs/resume/section-title-locale.ts | 2 +- apps/web/src/routes/$username/$slug.tsx | 2 +- apps/web/src/routes/__root.tsx | 4 +- .../web/src/routes/_home/-sections/header.tsx | 2 +- .../$resumeId/-components/design-panel.tsx | 22 +- .../-components/document-details.tsx | 2 +- .../$resumeId/-components/document-menu.tsx | 4 +- .../$resumeId/-components/editor-bar.tsx | 6 +- .../$resumeId/-components/editor-shell.tsx | 26 +- .../$resumeId/-components/mode-panels.tsx | 4 +- .../$resumeId/-components/page-canvas.tsx | 16 +- .../-sidebar/right/sections/design.tsx | 4 +- .../-sidebar/right/sections/layout/index.tsx | 4 +- .../-sidebar/right/sections/layout/pages.tsx | 22 +- .../-sidebar/right/sections/notes.tsx | 2 +- .../-sidebar/right/sections/page.tsx | 4 +- .../-sidebar/right/sections/typography.tsx | 6 +- .../src/routes/builder/$resumeId/route.tsx | 2 +- .../routes/builder/letter/$coverLetterId.tsx | 2 +- .../letter/-components/design-panel.tsx | 16 +- .../builder/letter/-components/letter-bar.tsx | 4 +- .../letter/-components/letter-page.tsx | 13 +- .../letter/-components/letter-shell.tsx | 22 +- .../letter/-components/write-panel.tsx | 46 +- .../dashboard/-components/app-shell.tsx | 18 +- apps/web/src/routes/dashboard/route.tsx | 2 +- .../src/routes/dashboard/settings/route.tsx | 12 +- biome.json | 136 --- compose.dev.yml | 8 +- compose.yml | 8 +- docs/agents/container-publishing.md | 10 +- docs/applying-custom-styles.mdx | 224 ++--- docs/changelog/index.mdx | 189 +++-- docs/community/spotlight.mdx | 42 +- .../reactive-resume-vs-adobe-express.mdx | 16 +- docs/comparisons/reactive-resume-vs-canva.mdx | 16 +- .../reactive-resume-vs-careercircle.mdx | 12 +- .../reactive-resume-vs-freesumes.mdx | 14 +- .../reactive-resume-vs-jobscan.mdx | 12 +- .../reactive-resume-vs-kickresume.mdx | 14 +- .../reactive-resume-vs-livecareer.mdx | 12 +- .../reactive-resume-vs-myperfectresume.mdx | 14 +- .../reactive-resume-vs-novoresume.mdx | 10 +- .../reactive-resume-vs-overleaf.mdx | 16 +- .../reactive-resume-vs-resume-com.mdx | 10 +- .../reactive-resume-vs-resume-io.mdx | 14 +- .../reactive-resume-vs-resume-now.mdx | 14 +- .../reactive-resume-vs-resumegemini.mdx | 8 +- .../reactive-resume-vs-resumod.mdx | 12 +- docs/comparisons/reactive-resume-vs-rezi.mdx | 16 +- docs/comparisons/reactive-resume-vs-zety.mdx | 14 +- docs/contributing/architecture.mdx | 72 +- docs/contributing/deployment-checks.mdx | 13 +- docs/contributing/development.mdx | 253 +++--- docs/contributing/translations.mdx | 25 +- docs/getting-started.mdx | 15 +- docs/getting-started/quickstart.mdx | 37 +- docs/guides/arranging-the-layout.mdx | 36 +- .../changing-appearance-and-language.mdx | 13 +- docs/guides/checking-service-status.mdx | 5 +- docs/guides/checking-your-resume.mdx | 66 +- docs/guides/choosing-a-template.mdx | 165 ++-- docs/guides/choosing-colors.mdx | 45 +- docs/guides/creating-an-account.mdx | 38 +- docs/guides/creating-your-first-resume.mdx | 32 +- docs/guides/customizing-typography.mdx | 75 +- docs/guides/deleting-your-account.mdx | 22 +- docs/guides/editing-entries.mdx | 78 +- docs/guides/editing-on-mobile.mdx | 21 +- docs/guides/editor-overview.mdx | 53 +- docs/guides/entering-dates.mdx | 45 +- docs/guides/exporting-resume-to-markdown.mdx | 29 +- docs/guides/exporting-your-data.mdx | 22 +- docs/guides/exporting-your-resume.mdx | 42 +- docs/guides/filling-in-your-details.mdx | 53 +- docs/guides/fitting-content-on-a-page.mdx | 35 +- docs/guides/formatting-text.mdx | 48 +- .../importing-applications-from-csv.mdx | 82 +- docs/guides/importing-resumes.mdx | 51 +- docs/guides/keyboard-shortcuts.mdx | 110 +-- docs/guides/large-rpc-requests.mdx | 52 +- docs/guides/linking-social-accounts.mdx | 16 +- .../guides/managing-applications-with-mcp.mdx | 27 +- docs/guides/managing-sections.mdx | 59 +- docs/guides/organizing-with-tags.mdx | 10 +- docs/guides/scheduling-interviews.mdx | 48 +- docs/guides/selecting-page-format.mdx | 50 +- docs/guides/setting-up-passkeys.mdx | 13 +- .../setting-up-two-factor-authentication.mdx | 17 +- docs/guides/sharing-your-resume-publicly.mdx | 47 +- docs/guides/signing-in.mdx | 30 +- docs/guides/tracking-job-applications.mdx | 30 +- .../undoing-changes-and-version-history.mdx | 45 +- docs/guides/updating-your-profile.mdx | 23 +- docs/guides/using-ai.mdx | 85 +- docs/guides/using-private-notes.mdx | 13 +- docs/guides/using-the-api.mdx | 38 +- docs/guides/using-the-ats-checker.mdx | 34 +- docs/guides/using-the-command-bar.mdx | 48 +- docs/guides/using-the-mcp-server.mdx | 147 ++-- docs/guides/using-the-patch-api.mdx | 70 +- docs/guides/using-the-trash.mdx | 19 +- docs/guides/viewing-application-insights.mdx | 18 +- docs/guides/whats-new-in-v6.mdx | 25 +- docs/guides/writing-a-cover-letter.mdx | 48 +- docs/self-hosting/docker.mdx | 85 +- docs/self-hosting/examples.mdx | 53 +- docs/self-hosting/kubernetes.mdx | 26 +- docs/self-hosting/migration.mdx | 91 +- docs/self-hosting/sso.mdx | 90 +- docs/self-hosting/upgrading-to-v6.mdx | 74 +- docs/self-hosting/vercel.mdx | 98 ++- docs/use-cases/export-and-share-resumes.mdx | 12 +- knip.json | 8 +- lefthook.yml | 12 +- package.json | 13 +- packages/ai/src/prompts/assistant-system.md | 2 +- packages/ai/src/prompts/parser-system.md | 2 +- packages/api/src/features/agent/context.ts | 4 +- packages/api/src/features/agent/runs.test.ts | 5 +- .../api/src/features/cover-letters/draft.ts | 2 +- .../api/src/features/cover-letters/service.ts | 2 +- packages/api/src/features/flags/router.ts | 12 +- .../api/src/features/resume/events.test.ts | 2 +- packages/api/src/features/resume/service.ts | 2 +- packages/api/src/features/resume/sharing.ts | 15 +- .../api/src/middleware/rate-limit/index.ts | 1 + packages/auth/src/config.ts | 2 +- packages/db/src/schema/resume.ts | 5 +- packages/docx/src/index.ts | 4 +- packages/docx/src/section-renderers.ts | 2 +- packages/dsh-plugin/README.md | 12 +- packages/dsh-plugin/docs/2026-08-16-design.md | 45 +- .../docs/plans/2026-08-16-implementation.md | 572 ++++++------- .../spikes/2026-08-16-restrict-semantics.md | 114 +-- packages/dsh-plugin/src/index.ts | 2 +- packages/email/src/templates/auth.tsx | 8 +- packages/import/src/json-resume.test.ts | 2 +- packages/import/src/linkedin.test.ts | 2 +- packages/mcp/src/tools.test.ts | 2 +- packages/pdf/fixtures/ats/README.md | 10 +- .../src/ats-extraction.integration.test.tsx | 2 +- packages/pdf/src/browser.tsx | 4 +- packages/pdf/src/context.tsx | 2 +- packages/pdf/src/document.tsx | 6 +- packages/pdf/src/forme/fonts.ts | 2 +- packages/pdf/src/forme/primitives.tsx | 2 +- packages/pdf/src/forme/reconciler.ts | 6 +- packages/pdf/src/forme/render.ts | 6 +- packages/pdf/src/forme/style.ts | 2 +- packages/pdf/src/forme/testing.ts | 2 +- packages/pdf/src/forme/to-forme.tsx | 4 +- .../pdf/src/hooks/use-register-fonts.test.ts | 2 +- .../pdf/src/page-map.integration.test.tsx | 4 +- packages/pdf/src/semantic/adapter.ts | 2 +- packages/pdf/src/semantic/context.tsx | 2 +- packages/pdf/src/semantic/index.ts | 10 +- packages/pdf/src/semantic/legacy-converter.ts | 2 +- packages/pdf/src/semantic/resolve.ts | 2 +- packages/pdf/src/semantic/shared-parts.ts | 2 +- .../pdf/src/semantic/template-manifest.ts | 2 +- packages/pdf/src/semantic/tree.ts | 8 +- packages/pdf/src/server.tsx | 2 +- .../special-characters.integration.test.tsx | 56 +- packages/pdf/src/templates/index.ts | 2 +- .../pdf/src/templates/rhyhorn/RhyhornPage.tsx | 2 +- .../templates/shared/base-template-styles.ts | 2 +- .../pdf/src/templates/shared/contact-item.tsx | 2 +- packages/pdf/src/templates/shared/context.tsx | 2 +- .../pdf/src/templates/shared/cover-letter.ts | 2 +- .../templates/shared/page-margins.test.tsx | 1 + .../pdf/src/templates/shared/page-size.ts | 2 +- .../pdf/src/templates/shared/primitives.tsx | 2 +- .../templates/shared/rich-text-renderers.ts | 2 +- .../pdf/src/templates/shared/rich-text.tsx | 2 +- .../pdf/src/templates/shared/sections.tsx | 10 +- .../pdf/src/templates/shared/style-rules.ts | 2 +- .../src/templates/shared/template-base.tsx | 2 +- packages/pdf/src/templates/shared/types.ts | 4 +- packages/pdf/src/text.tsx | 2 +- .../resume/src/ats-pdf/analyze/sections.ts | 2 +- packages/resume/src/ats-pdf/rules/sections.ts | 2 +- packages/resume/src/ats-pdf/test-fixtures.ts | 2 +- packages/resume/src/ats-pdf/types.ts | 2 +- packages/resume/src/ats/index.ts | 11 +- packages/resume/src/ats/rules.ts | 2 +- packages/resume/src/stylesheet/compile.ts | 2 +- packages/resume/src/stylesheet/parse.ts | 2 +- .../resume/src/stylesheet/registry/index.ts | 6 +- .../src/stylesheet/registry/properties.ts | 2 +- packages/resume/src/stylesheet/selector.ts | 2 +- .../resume/src/stylesheet/specificity.d.ts | 2 - packages/resume/src/stylesheet/types.ts | 2 +- packages/resume/src/stylesheet/values.ts | 2 +- packages/ui/src/components/accordion.tsx | 6 +- packages/ui/src/components/alert-dialog.tsx | 8 +- packages/ui/src/components/alert.tsx | 2 +- packages/ui/src/components/avatar.tsx | 6 +- packages/ui/src/components/badge.tsx | 2 +- packages/ui/src/components/button.tsx | 2 +- packages/ui/src/components/checkbox.tsx | 4 +- packages/ui/src/components/collapsible.tsx | 2 +- packages/ui/src/components/combobox.tsx | 8 +- packages/ui/src/components/command.tsx | 14 +- packages/ui/src/components/dialog.tsx | 8 +- packages/ui/src/components/form.tsx | 4 +- packages/ui/src/components/icon.tsx | 2 +- packages/ui/src/components/input-group.tsx | 6 +- packages/ui/src/components/input.tsx | 2 +- packages/ui/src/components/kbd.tsx | 2 +- packages/ui/src/components/label.tsx | 4 +- packages/ui/src/components/native-select.tsx | 2 +- packages/ui/src/components/otp-field.tsx | 2 +- packages/ui/src/components/popover.tsx | 2 +- packages/ui/src/components/radio-group.tsx | 4 +- packages/ui/src/components/scroll-area.tsx | 4 +- .../ui/src/components/segmented-control.tsx | 2 +- packages/ui/src/components/sheet.tsx | 18 +- packages/ui/src/components/slider.tsx | 10 +- packages/ui/src/components/switch.tsx | 4 +- packages/ui/src/components/tabs.tsx | 6 +- packages/ui/src/components/textarea.tsx | 2 +- packages/ui/src/components/toast.tsx | 8 +- packages/ui/src/components/tooltip.tsx | 2 +- packages/ui/src/styles/globals.css | 10 +- pnpm-lock.yaml | 787 +++++++++++++++--- pnpm-workspace.yaml | 2 +- tests/e2e/fixtures/auth.ts | 2 +- tests/e2e/fixtures/test.ts | 2 +- tooling/grit/workspace-boundaries.grit | 29 - tooling/lint.test.ts | 52 ++ 392 files changed, 5114 insertions(+), 3711 deletions(-) create mode 100644 .oxfmtrc.json create mode 100644 .oxlintrc.json delete mode 100644 biome.json delete mode 100644 tooling/grit/workspace-boundaries.grit create mode 100644 tooling/lint.test.ts diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 43994f03d..698f5bc88 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,7 +1,9 @@ + Respond terse like smart caveman. All technical substance stay. Only fluff die. Rules: + - Drop: articles (a/an/the), filler (just/really/basically), pleasantries, hedging - Fragments OK. Short synonyms. Technical terms exact. Code unchanged. - Pattern: [thing] [action] [reason]. [next step]. diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 000000000..cba062e08 --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,62 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "useTabs": true, + "printWidth": 120, + "sortPackageJson": false, + "sortImports": { + "newlinesBetween": false, + "customGroups": [ + { + "groupName": "types", + "selector": "type" + }, + { + "groupName": "tests", + "elementNamePattern": ["vitest", "vitest/**", "@testing-library/**"] + }, + { + "groupName": "workspace", + "elementNamePattern": ["@reactive-resume/**"] + } + ], + "groups": [ + "types", + "value-builtin", + "tests", + "value-external", + "workspace", + ["value-internal", "value-parent", "value-sibling", "value-index"], + "unknown" + ] + }, + "sortTailwindcss": { + "stylesheet": "./packages/ui/src/styles/globals.css", + "functions": ["clsx", "cva", "cn"] + }, + "ignorePatterns": [ + "**/.turbo/**", + "**/.output/**", + "**/dist/**", + "**/dist-prerender/**", + "**/.vercel/**", + "**/.wrangler/**", + "**/coverage/**", + "**/reports/**", + "**/routeTree.gen.ts", + "packages/pdf/src/semantic/__fixtures__/**/*.css", + "pnpm-lock.yaml", + "migrations/**", + "docs/spec.json", + "docs/guides/json-resume-schema.mdx", + "skills/resume-builder/references/schema.md" + ], + "overrides": [ + { + "files": ["**/*.md", "**/*.mdx"], + "options": { + "proseWrap": "preserve", + "useTabs": false + } + } + ] +} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 000000000..35358c942 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,107 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["typescript", "unicorn", "oxc", "react", "jsx-a11y"], + "jsPlugins": ["@shadcn/lint"], + "categories": { + "correctness": "error" + }, + "ignorePatterns": [ + "**/.turbo/**", + "**/.output/**", + "**/dist/**", + "**/dist-prerender/**", + "**/.vercel/**", + "**/.wrangler/**", + "**/coverage/**", + "**/reports/**", + "**/routeTree.gen.ts" + ], + "rules": { + "typescript/no-explicit-any": "error", + "require-await": "error", + "typescript/consistent-type-imports": [ + "error", + { + "prefer": "type-imports", + "fixStyle": "separate-type-imports", + "disallowTypeAnnotations": false + } + ], + "typescript/no-non-null-assertion": "error", + "no-restricted-imports": [ + "error", + { + "patterns": [ + { + "regex": "@reactive-resume/[^/]+/src(?:/|$)|(?:^|/)(?:apps|packages)/[^/]+/src(?:/|$)", + "message": "Use the workspace package export map instead of private src paths, including re-exports and dynamic imports." + }, + { + "group": ["apps/**", "packages/**"], + "message": "Do not import another workspace by repository path; use an explicit package export." + } + ] + } + ], + "no-param-reassign": "error", + "typescript/prefer-as-const": "error", + "default-param-last": "error", + "typescript/prefer-enum-initializers": "error", + "react/self-closing-comp": "error", + "one-var": ["error", "never"], + "unicorn/prefer-number-properties": "error", + "typescript/no-inferrable-types": "error", + "no-else-return": "error", + "react/no-array-index-key": "off", + "react/exhaustive-deps": "warn", + "react/unsupported-syntax": "error", + "no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ], + "typescript/triple-slash-reference": [ + "error", + { + "path": "always", + "types": "prefer-import", + "lib": "always" + } + ], + "jsx-a11y/prefer-tag-over-role": "off", + "jsx-a11y/control-has-associated-label": "off", + "jsx-a11y/no-noninteractive-element-interactions": "off", + "jsx-a11y/no-autofocus": [ + "error", + { + "ignoreNonDOM": true + } + ], + "unicorn/no-new-array": "off", + "no-irregular-whitespace": [ + "error", + { + "skipComments": true + } + ], + "react/no-danger": "error", + "react/rules-of-hooks": "error" + }, + "overrides": [ + { + "files": ["**/*.test.{ts,tsx,mts,mjs}", "**/*.spec.{ts,tsx,mts,mjs}"], + "rules": { + "require-await": "off" + } + }, + { + "files": ["tests/e2e/fixtures/test.ts"], + "rules": { + "react/rules-of-hooks": "off" + } + } + ] +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 0e6f72ac0..597186071 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,3 @@ { - "recommendations": ["biomejs.biome", "bradlc.vscode-tailwindcss", "typescriptteam.native-preview"] + "recommendations": ["oxc.oxc-vscode", "bradlc.vscode-tailwindcss", "typescriptteam.native-preview"] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 0cb018201..e723474ba 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,8 @@ { - "biome.enabled": true, "editor.codeActionsOnSave": { - "source.fixAll.biome": "explicit", - "source.organizeImports.biome": "explicit" + "source.fixAll.oxc": "explicit" }, - "editor.defaultFormatter": "biomejs.biome", + "editor.defaultFormatter": "oxc.oxc-vscode", "files.readonlyInclude": { "**/locales/**.po": true, "**/routeTree.gen.ts": true, @@ -26,9 +24,10 @@ ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], ["cn\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"] ], - "tailwindCSS.experimental.configFile": "src/styles/globals.css", + "tailwindCSS.experimental.configFile": "packages/ui/src/styles/globals.css", "typescript.experimental.useTsgo": true, "[json]": { - "editor.defaultFormatter": "biomejs.biome" - } + "editor.defaultFormatter": "oxc.oxc-vscode" + }, + "editor.formatOnSave": true } diff --git a/AGENTS.md b/AGENTS.md index 94bf2fc28..a1f9f824e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,9 +3,11 @@ This file applies across the repository. Follow a closer `AGENTS.md` when one exists. Keep this guide focused on agent workflows; user-facing documentation lives in `README.md` and `docs/`. Format guidance: [agents.md](https://agents.md/). + Respond terse like smart caveman. All technical substance stay. Only fluff die. Rules: + - Drop: articles (a/an/the), filler (just/really/basically), pleasantries, hedging - Fragments OK. Short synonyms. Technical terms exact. Code unchanged. - Pattern: [thing] [action] [reason]. [next step]. @@ -91,24 +93,24 @@ pnpm db:studio Where each concern lives, and where new code for it goes: -| Area | Owner | -|------|-------| -| Web routes, loaders, user-facing workflows | `apps/web/src/routes`, `apps/web/src/features` (file-based; never hand-edit `routeTree.gen.ts`) | -| Server HTTP routes/adapters, startup checks, static handlers, MCP transport, OpenAPI/well-known | `apps/server/src/{http,rpc,mcp,openapi,static,startup}` | -| Authenticated API contracts + business logic | `packages/api/src/features/*` (oRPC routers, DTOs, rate limiting; aggregated at `@reactive-resume/api/routers` for `/api/rpc`) | -| Auth | `packages/auth` (Better Auth config/helpers/types; `apps/server/src/http/auth.ts` delegates to `auth.handler`) | -| DB client + schema | `packages/db` (Drizzle; migrations at repo root `migrations/`) | -| Server env validation | `packages/env` (auto-loads root `.env`) | -| Resume/page/template Zod schemas | `packages/schema` | -| Pure resume-domain behavior (no DB/HTTP/DOM/renderer deps) | `packages/resume` (JSON Patch helpers, social-network icons) | -| Resume PDF rendering | `packages/pdf` (React templates converted through `src/forme` to Forme documents, font resolution, browser/server adapters) | -| PDF.js viewer/canvas UI | `apps/web/src/features/resume` — never in `packages/pdf` | -| DOCX export | `packages/docx` | -| MCP tools/prompts/resources/server-card | `packages/mcp` | -| Generic UI primitives + hooks | `packages/ui` (Base UI/shadcn-style); workflow-specific UI stays in the owning web feature | -| DeepSeek Harness integration | `packages/dsh-plugin` (separately built/published plugin) | -| Focused support surfaces | `packages/fonts`, `packages/email`, `packages/import`, `packages/ai`, `packages/utils`, `packages/config` — prefer existing exports over cross-package shortcuts | -| Dev-only scripts | `tooling/`, not `packages/`, so packages only hold runtime-bundled code | +| Area | Owner | +| ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Web routes, loaders, user-facing workflows | `apps/web/src/routes`, `apps/web/src/features` (file-based; never hand-edit `routeTree.gen.ts`) | +| Server HTTP routes/adapters, startup checks, static handlers, MCP transport, OpenAPI/well-known | `apps/server/src/{http,rpc,mcp,openapi,static,startup}` | +| Authenticated API contracts + business logic | `packages/api/src/features/*` (oRPC routers, DTOs, rate limiting; aggregated at `@reactive-resume/api/routers` for `/api/rpc`) | +| Auth | `packages/auth` (Better Auth config/helpers/types; `apps/server/src/http/auth.ts` delegates to `auth.handler`) | +| DB client + schema | `packages/db` (Drizzle; migrations at repo root `migrations/`) | +| Server env validation | `packages/env` (auto-loads root `.env`) | +| Resume/page/template Zod schemas | `packages/schema` | +| Pure resume-domain behavior (no DB/HTTP/DOM/renderer deps) | `packages/resume` (JSON Patch helpers, social-network icons) | +| Resume PDF rendering | `packages/pdf` (React templates converted through `src/forme` to Forme documents, font resolution, browser/server adapters) | +| PDF.js viewer/canvas UI | `apps/web/src/features/resume` — never in `packages/pdf` | +| DOCX export | `packages/docx` | +| MCP tools/prompts/resources/server-card | `packages/mcp` | +| Generic UI primitives + hooks | `packages/ui` (Base UI/shadcn-style); workflow-specific UI stays in the owning web feature | +| DeepSeek Harness integration | `packages/dsh-plugin` (separately built/published plugin) | +| Focused support surfaces | `packages/fonts`, `packages/email`, `packages/import`, `packages/ai`, `packages/utils`, `packages/config` — prefer existing exports over cross-package shortcuts | +| Dev-only scripts | `tooling/`, not `packages/`, so packages only hold runtime-bundled code | Narrow cross-cutting helpers go in `packages/utils` only after checking no domain package is a better owner. Specifically: resume JSON Patch behavior belongs in `@reactive-resume/resume/patch` and DOCX builders in `@reactive-resume/docx` — not in `@reactive-resume/utils`. @@ -159,7 +161,8 @@ pnpm --filter @reactive-resume/pdf test pnpm --filter @reactive-resume/pdf test src/templates/shared/filtering.test.ts pnpm --filter @reactive-resume/pdf exec vitest run src/templates/shared/filtering.test.ts -t "filterItems" pnpm --filter @reactive-resume/pdf test:coverage -pnpm exec biome check apps/web/src/features/resume +pnpm exec oxlint --deny-warnings apps/web/src/features/resume +pnpm exec oxfmt --check apps/web/src/features/resume pnpm exec turbo boundaries ``` @@ -189,9 +192,18 @@ pnpm test:e2e:ui ## Code style - TypeScript is strict, including `exactOptionalPropertyTypes`, `noUncheckedIndexedAccess`, and unused-symbol checks; packages typecheck with `tsgo --noEmit`. -- Biome uses tabs, double quotes, 120-column lines, separated type imports, organized import groups, and sorted Tailwind classes in `clsx`, `cva`, and `cn`. Use existing file naming and feature-local conventions. -- **`pnpm check` modifies files**: it regenerates PDF translations and runs Biome with `--write --unsafe`. Call out its write behavior and review the diff; use narrow non-mutating commands when inspecting unrelated edits. -- Lefthook's pre-commit hook checks conflict markers and runs write-capable Biome on supported staged files, staging fixes. The commit-message hook enforces Conventional Commits (`fix:`, `feat:`, `docs:`, etc.). +- Oxlint checks code with its native React Compiler and accessibility rules. Oxfmt uses tabs, double quotes, 120-column lines, separated type import groups, and sorted Tailwind classes in `clsx`, `cva`, and `cn`. Use existing file naming and feature-local conventions. +- **`pnpm check` modifies files**: it regenerates PDF translations, applies safe Oxlint fixes, runs Oxfmt, then fails on remaining lint errors or warnings. Review the diff; use `pnpm lint` and `pnpm format:check` for non-mutating checks. +- Lefthook's pre-commit hook checks conflict markers, applies safe Oxlint fixes, runs Oxfmt, then checks staged files with warnings denied and stages the fixes. The commit-message hook enforces Conventional Commits (`fix:`, `feat:`, `docs:`, etc.). + +## Linting and formatting for coding agents + +- After code changes, run `pnpm exec oxlint --fix `, then `pnpm exec oxfmt `. Safe lint fixes run before formatting and import/Tailwind sorting. Keep side-effect import order intact. +- Before finishing, run `pnpm lint:agent` (`oxlint --deny-warnings --format=agent`) and `pnpm format:check`. Fix diagnostics and recheck; do not disable rules merely to make a check pass. Any necessary inline suppression must name its rule and explain why. +- Lint rules live in `.oxlintrc.json`; formatting and import groups live in `.oxfmtrc.json`. Generated route trees, build outputs, migrations, OpenAPI JSON, generated schema references, and byte-sensitive PDF CSS fixtures are excluded where appropriate. +- `@shadcn/lint` is registered as a JS plugin. Its design-system rules are opt-in: configure them in `.oxlintrc.json` after choosing the policy. It discovers the shared UI exports and Tailwind theme through `apps/web/components.json` and `packages/ui/components.json`. See [available rules](https://github.com/shadcn-ui/lint#rules). Keep UI-specific policies scoped to the web app and UI package. +- Async test doubles may return a Promise without awaiting; the test override permits this. Playwright fixture callbacks named `use` are not React hooks, so `rules-of-hooks` is disabled only in the fixture adapter. Path references in declaration shims remain supported. CSS is formatted by Oxfmt; Oxlint checks JavaScript/TypeScript rather than CSS declarations. +- Editor setup is checked in under `.vscode/`: install the recommended Oxc extension for lint fixes and formatting on save. This workflow follows the [Oxc coding-agent guide](https://oxc.rs/docs/guide/usage/coding-agents.html). Restart Codex sessions after changing these instructions. ## Build and deployment diff --git a/DESIGN.md b/DESIGN.md index fde18665f..864ed3506 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -91,10 +91,12 @@ marketing: typography: display: { fontFamily: Anybody, fontWeight: "300–400", fontStretch: "86%–112%" } write-title: { fontFamily: Anybody, fontSize: "clamp(72px, 9vw, 160px)", lineHeight: 0.9, fontWeight: 300 } - numeral: { fontFamily: Anybody, fontSize: "clamp(56px, 7.5vw, 136px)", fontWeight: 300, fontVariantNumeric: tabular-nums } + numeral: + { fontFamily: Anybody, fontSize: "clamp(56px, 7.5vw, 136px)", fontWeight: 300, fontVariantNumeric: tabular-nums } body: { fontFamily: Newsreader, fontSize: "16–21px", lineHeight: "1.45–1.5", fontWeight: 400 } accent: { fontFamily: Newsreader, fontStyle: italic, color: accent-text } - label: { fontFamily: Martian Mono, fontSize: 11px, fontWeight: 500, letterSpacing: 0.08em, textTransform: uppercase } + label: + { fontFamily: Martian Mono, fontSize: 11px, fontWeight: 500, letterSpacing: 0.08em, textTransform: uppercase } wordmark: { fontFamily: Anybody, fontSize: 17.5cqw, lineHeight: 0.84, fontWeight: 800, fontStretch: 78% } colors: graphite: { light: "oklch(0.38 0.01 95 / .3)", dark: "oklch(0.9 0.01 95 / .18)" } @@ -179,11 +181,11 @@ Icons inside resumes use Phosphor names stored in resume data; they remain separ ## Motion -| Token | Duration | Use | -| --- | --- | --- | -| `duration-quick` | 120ms | Hover, press, toggles, checkboxes, and focus | -| `duration-standard` | 200ms | Menus, popovers, expansion, content swaps, and dialogs | -| `duration-emphasized` | 320ms | Sheets, toasts, and the assistant column | +| Token | Duration | Use | +| --------------------- | -------- | ------------------------------------------------------ | +| `duration-quick` | 120ms | Hover, press, toggles, checkboxes, and focus | +| `duration-standard` | 200ms | Menus, popovers, expansion, content swaps, and dialogs | +| `duration-emphasized` | 320ms | Sheets, toasts, and the assistant column | - Entering and changing state use `ease-enter` (`cubic-bezier(0.2, 0.8, 0.2, 1)`). Exits take 70% of the entry duration. - Sliding indicators, reordering, and settling use `ease-in-out-strong` (`cubic-bezier(0.77, 0, 0.175, 1)`). Swipe-dismissed bottom sheets use `ease-drawer`. @@ -254,14 +256,14 @@ The public homepage in `apps/web/src/features/homepage` shares app colors and th Pinned scenes contain a sticky `100svh` stage. Scroll progress is `p = clamp(0, −top / max(1, height − viewportHeight), 1)`; `scroll.ts` writes it to `--p` through one animation-frame-throttled scroll listener. CSS derives continuous motion from progress; React receives only coarse scene and step changes. -| Scene | Below 900px | From 900px | -| --- | --- | --- | -| Hero | 190vh | 260vh | -| Write | 280vh | 330vh | -| Design | 380vh | 440vh | -| Check | 260vh | 320vh | -| Tailor | 280vh | 330vh | -| Share | 240vh | 300vh | +| Scene | Below 900px | From 900px | +| ------ | ----------- | ---------- | +| Hero | 190vh | 260vh | +| Write | 280vh | 330vh | +| Design | 380vh | 440vh | +| Check | 260vh | 320vh | +| Tailor | 280vh | 330vh | +| Share | 240vh | 300vh | Light mode combines breathing window light (16 seconds), a drifting mullion shadow (90 seconds), and 18 dust motes. Dark mode adds a neutral 620px cursor glow at 6% opacity. These are decorative and must not obscure text. diff --git a/GLOSSARY.md b/GLOSSARY.md index a0622506a..85b09a853 100644 --- a/GLOSSARY.md +++ b/GLOSSARY.md @@ -8,7 +8,7 @@ most common English sense, and gets it wrong. Every entry below has been mistran in at least one shipped locale. **If you are translating, read the term here before translating it.** When the English word has -a common sense that is *not* the one used here, that wrong sense is listed explicitly. +a common sense that is _not_ the one used here, that wrong sense is listed explicitly. Terms are grouped by the part of the product they belong to. Source references point at where the string is defined, so you can read the surrounding code when this file is not enough. @@ -137,7 +137,7 @@ variant, and not a "style" or "pattern". **Working resume** — the resume a thread is currently editing. "Working" describes the draft being worked on, not the user's employment. It is not their work history, not a "job resume", -and not a *functional résumé*, which is a real and different résumé format. +and not a _functional résumé_, which is a real and different résumé format. **Tailor** — a verb: to adapt a resume to a specific job description. Nothing to do with dressmaking or sewing. @@ -249,10 +249,10 @@ Button labels and `aria-label` strings are usually **imperative verbs**: they sa control does. Read as a noun or an adjective, they turn into nonsense. This is the most common error in the catalogs after the ambiguous nouns above. -**Open** — the verb. `Open AI agent` means *open the AI agent panel*; it does not describe an +**Open** — the verb. `Open AI agent` means _open the AI agent panel_; it does not describe an agent that is "open", and it is **not a reference to OpenAI, the company**. Around forty-five of the fifty-three catalogs got this wrong, split between "an open AI agent" and a transliteration -of *OpenAI*. The same applies to `Open in builder`. +of _OpenAI_. The same applies to `Open in builder`. **Close** — likewise the verb, as in `Close AI assistant`. Not "an assistant for closing things", and not the adjective "close/nearby". diff --git a/README.md b/README.md index 693a485e8..dae85cc49 100644 --- a/README.md +++ b/README.md @@ -163,17 +163,17 @@ For detailed setup instructions, environment configuration, and self-hosting gui ## Tech Stack -| Category | Technology | -| ---------------- | ------------------------------- | +| Category | Technology | +| ---------------- | -------------------------------- | | Framework | TanStack Router (React 19, Vite) | -| Runtime | Node.js | -| Language | TypeScript | -| Database | PostgreSQL with Drizzle ORM | -| API | ORPC (Type-safe RPC) | -| Auth | Better Auth | -| Styling | Tailwind CSS | -| UI Components | Base UI + shadcn-style package | -| State Management | Zustand + TanStack Query | +| Runtime | Node.js | +| Language | TypeScript | +| Database | PostgreSQL with Drizzle ORM | +| API | ORPC (Type-safe RPC) | +| Auth | Better Auth | +| Styling | Tailwind CSS | +| UI Components | Base UI + shadcn-style package | +| State Management | Zustand + TanStack Query | ## Documentation diff --git a/REDESIGN_PLAN.md b/REDESIGN_PLAN.md index de50594fc..159410734 100644 --- a/REDESIGN_PLAN.md +++ b/REDESIGN_PLAN.md @@ -18,35 +18,35 @@ Path shorthand: `B/` = `apps/web/src/routes/builder/$resumeId/`. ## 1. Repository map -| Concern | Today | Where | Redesign | -|---|---|---|---| -| App and routing | React 19 SPA, TanStack Router file routes, Vite 8. `apps/server` (Hono) serves `index.html` and injects page metadata. No SSR. | `apps/web/src/routes`, `apps/server/src/static/web.ts` | New route tree (§2). Old routes become redirect stubs. | -| Server data | TanStack Query + oRPC over `/api/rpc`. Router context carries `queryClient`, `orpc`, `theme`, `locale`, `session`, `flags`. | `apps/web/src/libs/orpc`, `apps/web/src/router.tsx` | Reused. New procedures in §3. | -| Editor draft and autosave | One zustand 5 + immer store (`useResumeStore`). Full-document `resume.update` 500 ms after the last edit, one save in flight. Remote changes arrive through `resume.updates.subscribe`. No offline detection. Last write wins. | `apps/web/src/features/resume/builder/draft.ts` | Kept as the source of truth. Adds offline and error states, a local pending-save queue, and a session id for version grouping. | -| Undo | Custom stacks of 50 `structuredClone` snapshots, 500 ms global coalescing, `Mod+Z` ignored inside fields | `draft.ts`, `B/-components/dock.tsx` | Reworked: 200 steps, structural sharing, per-field merging, labelled structural steps, undo toast. | -| Versions | `resume_version` with a free-text English label. Newest 30 kept. Auto snapshot at most every 2 min, plus "AI edit", "Imported", "Before restore". | `packages/api/src/features/resume/service.ts`, `versions.ts`, `B/-components/version-history.tsx` | Adds kind, name, session grouping, 90-day retention and read-only preview. | -| Forms | TanStack Form v1 via `createFormHook`, Zod 4 schemas as validators | `apps/web/src/libs/tanstack-form.tsx` | Reused. Entries validate on blur and save on change. | -| Entry editing | One dialog per section type, opened through a global dialog store | `apps/web/src/dialogs/resume/sections/*`, `apps/web/src/dialogs/store.ts` | Replaced by inline entry cards. Field sets move out of the dialogs. | -| Rich text | TipTap 3, stores HTML. 17-button toolbar (headings, colours, alignment, lists, links, code…). | `apps/web/src/components/input/rich-input.tsx` | Kept. Toolbar restricted to spec §5.3. Extensions stay loaded so existing formatting survives. | -| Drag and drop | motion `Reorder` for items (pointer only). dnd-kit for layout pages (pointer only). | `B/-sidebar/left/shared/items-section.tsx`, `B/-sidebar/right/sections/layout/pages.tsx` | Outline uses dnd-kit sortable with `KeyboardSensor`, plus ⌥↑/⌥↓ and menu moves. | -| Overlays | Base UI 1.8 wrappers: dialog, alert-dialog, sheet, popover, menu, context-menu, tooltip, toast. cmdk for the command palette. | `packages/ui/src/components/*` | Restyled. Missing primitives added (§4). | -| Shortcuts | `@tanstack/react-hotkeys` | `apps/web/src/routes/__root.tsx`, builder dock | Spec keymap (README §4.8). | -| Styling | Tailwind 4.3, CSS-first. Achromatic shadcn tokens in oklch. `.dark` class. Theme cookie: light or dark, default dark. | `packages/ui/src/styles/globals.css`, `apps/web/src/libs/theme.ts`, `apps/web/src/features/theme` | Desk & Paper tokens and a System theme (M1). | -| Icons | Phosphor. `@phosphor-icons/react` in about 150 app files. `@phosphor-icons/web` and `phosphor-icons-react-pdf` for icons inside resumes. | | App chrome moves to Material Symbols Rounded. Icons inside resumes stay Phosphor (their names are stored in resume data). | -| Fonts | IBM Plex Sans Variable in the app, Manrope on the landing page, no mono font | `packages/ui/src/styles/globals.css` | Newsreader, Hanken Grotesk and JetBrains Mono via fontsource. | -| Motion | `motion` 13 with `LazyMotion` and `MotionConfig reducedMotion="user"`. Easing tokens exist; durations are hand-typed. | `apps/web/src/libs/motion.ts` | Duration tokens 120/200/320 ms, one entry easing, reduced-motion override. | -| i18n | Lingui 6.8, 55 PO catalogs, macros. PDF section titles are extracted into a catalog by `pnpm pdf:translations`. | `apps/web/lingui.config.ts`, `apps/web/locales`, `tooling/locales` | All new copy through Lingui. "Present" joins the PDF catalog. | -| Renderer | `@react-pdf/renderer` 4.9 (layout 5.2, patched), 15 templates, semantic node tree used for custom CSS | `packages/pdf` | Emits node keys for the page map. Sidebar side. Preview-only proposal marks. | -| Live preview | Main-thread `pdf().toBlob()` 100 ms after edits, then pdf.js 6 paints canvases (no text layer). react-zoom-pan-pinch. Crossfade between renders. | `apps/web/src/features/resume/preview` | Becomes the page canvas with an overlay layer for hover, selection, pins and markers. | -| Thumbnails | Full render, then a page-1 PNG cached per `id:updatedAt`. Template picker uses static JPGs. | `apps/web/src/routes/dashboard/resumes/-components/cards/resume-thumbnail.tsx`, `apps/web/src/features/resume/preview/pdf-thumbnail.ts` | Reused for document cards and for template thumbnails of the user's own content. | -| Export | PDF, DOCX, Markdown, JSON and Print in the browser. Signed server PDF route for MCP. Public PDF fallback. | `apps/web/src/features/resume/export`, `apps/server/src/http/*`, `packages/docx` | Moves into the Share sheet. | -| ATS | Live lint on resume JSON (21 rules with pointers to items). PDF engine (77 checks, weighted score, evidence boxes). Job-description matching. AI review. | `packages/resume/src/ats`, `packages/resume/src/ats-pdf`, `packages/api/src/features/ai` | Check mode and the public checker. | -| AI | 16 providers, AES-GCM keys (`ENCRYPTION_SECRET`). Agent streams through resumable streams (needs Redis). Tools: `read_resume`, `apply_resume_patch`, `ask_user_question`, `web_search`. The builder assistant reuses `AgentChat` and applies patches directly by default. | `packages/ai`, `packages/api/src/features/{ai,agent,ai-providers,applications}`, `apps/web/src/routes/agent` | One assistant panel that only proposes (§3.6). | -| Import | RR v5 and v4 JSON, JSON Resume, LinkedIn ZIP, PDF (AI or local heuristics), Word (AI). Uncertain fields are not reported. | `packages/import`, `apps/web/src/dialogs/resume/import.tsx` | New dialog flow with review flags. | -| MCP | 44 tools. Resume patching uses index-based JSON Pointers. `schema.json` resource. | `packages/mcp` | Updated for the date shape. | -| Schemas | Resume data, cover letters, applications, API keys | `packages/schema`, `packages/db/src/schema` | §3. | -| Auth | Better Auth: email and password, verification, username, Google, GitHub, LinkedIn, custom OIDC, 2FA, passkeys, API keys, OAuth provider for MCP | `packages/auth` | Account settings page. | -| Tests | Vitest 5 (happy-dom, Testing Library), 34 test files in `packages/ui`, 140 in `apps/web`. 24 Playwright specs, 3 of them env-gated. | `tests/e2e/specs` | Updated in the milestone that changes each screen. | +| Concern | Today | Where | Redesign | +| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| App and routing | React 19 SPA, TanStack Router file routes, Vite 8. `apps/server` (Hono) serves `index.html` and injects page metadata. No SSR. | `apps/web/src/routes`, `apps/server/src/static/web.ts` | New route tree (§2). Old routes become redirect stubs. | +| Server data | TanStack Query + oRPC over `/api/rpc`. Router context carries `queryClient`, `orpc`, `theme`, `locale`, `session`, `flags`. | `apps/web/src/libs/orpc`, `apps/web/src/router.tsx` | Reused. New procedures in §3. | +| Editor draft and autosave | One zustand 5 + immer store (`useResumeStore`). Full-document `resume.update` 500 ms after the last edit, one save in flight. Remote changes arrive through `resume.updates.subscribe`. No offline detection. Last write wins. | `apps/web/src/features/resume/builder/draft.ts` | Kept as the source of truth. Adds offline and error states, a local pending-save queue, and a session id for version grouping. | +| Undo | Custom stacks of 50 `structuredClone` snapshots, 500 ms global coalescing, `Mod+Z` ignored inside fields | `draft.ts`, `B/-components/dock.tsx` | Reworked: 200 steps, structural sharing, per-field merging, labelled structural steps, undo toast. | +| Versions | `resume_version` with a free-text English label. Newest 30 kept. Auto snapshot at most every 2 min, plus "AI edit", "Imported", "Before restore". | `packages/api/src/features/resume/service.ts`, `versions.ts`, `B/-components/version-history.tsx` | Adds kind, name, session grouping, 90-day retention and read-only preview. | +| Forms | TanStack Form v1 via `createFormHook`, Zod 4 schemas as validators | `apps/web/src/libs/tanstack-form.tsx` | Reused. Entries validate on blur and save on change. | +| Entry editing | One dialog per section type, opened through a global dialog store | `apps/web/src/dialogs/resume/sections/*`, `apps/web/src/dialogs/store.ts` | Replaced by inline entry cards. Field sets move out of the dialogs. | +| Rich text | TipTap 3, stores HTML. 17-button toolbar (headings, colours, alignment, lists, links, code…). | `apps/web/src/components/input/rich-input.tsx` | Kept. Toolbar restricted to spec §5.3. Extensions stay loaded so existing formatting survives. | +| Drag and drop | motion `Reorder` for items (pointer only). dnd-kit for layout pages (pointer only). | `B/-sidebar/left/shared/items-section.tsx`, `B/-sidebar/right/sections/layout/pages.tsx` | Outline uses dnd-kit sortable with `KeyboardSensor`, plus ⌥↑/⌥↓ and menu moves. | +| Overlays | Base UI 1.8 wrappers: dialog, alert-dialog, sheet, popover, menu, context-menu, tooltip, toast. cmdk for the command palette. | `packages/ui/src/components/*` | Restyled. Missing primitives added (§4). | +| Shortcuts | `@tanstack/react-hotkeys` | `apps/web/src/routes/__root.tsx`, builder dock | Spec keymap (README §4.8). | +| Styling | Tailwind 4.3, CSS-first. Achromatic shadcn tokens in oklch. `.dark` class. Theme cookie: light or dark, default dark. | `packages/ui/src/styles/globals.css`, `apps/web/src/libs/theme.ts`, `apps/web/src/features/theme` | Desk & Paper tokens and a System theme (M1). | +| Icons | Phosphor. `@phosphor-icons/react` in about 150 app files. `@phosphor-icons/web` and `phosphor-icons-react-pdf` for icons inside resumes. | | App chrome moves to Material Symbols Rounded. Icons inside resumes stay Phosphor (their names are stored in resume data). | +| Fonts | IBM Plex Sans Variable in the app, Manrope on the landing page, no mono font | `packages/ui/src/styles/globals.css` | Newsreader, Hanken Grotesk and JetBrains Mono via fontsource. | +| Motion | `motion` 13 with `LazyMotion` and `MotionConfig reducedMotion="user"`. Easing tokens exist; durations are hand-typed. | `apps/web/src/libs/motion.ts` | Duration tokens 120/200/320 ms, one entry easing, reduced-motion override. | +| i18n | Lingui 6.8, 55 PO catalogs, macros. PDF section titles are extracted into a catalog by `pnpm pdf:translations`. | `apps/web/lingui.config.ts`, `apps/web/locales`, `tooling/locales` | All new copy through Lingui. "Present" joins the PDF catalog. | +| Renderer | `@react-pdf/renderer` 4.9 (layout 5.2, patched), 15 templates, semantic node tree used for custom CSS | `packages/pdf` | Emits node keys for the page map. Sidebar side. Preview-only proposal marks. | +| Live preview | Main-thread `pdf().toBlob()` 100 ms after edits, then pdf.js 6 paints canvases (no text layer). react-zoom-pan-pinch. Crossfade between renders. | `apps/web/src/features/resume/preview` | Becomes the page canvas with an overlay layer for hover, selection, pins and markers. | +| Thumbnails | Full render, then a page-1 PNG cached per `id:updatedAt`. Template picker uses static JPGs. | `apps/web/src/routes/dashboard/resumes/-components/cards/resume-thumbnail.tsx`, `apps/web/src/features/resume/preview/pdf-thumbnail.ts` | Reused for document cards and for template thumbnails of the user's own content. | +| Export | PDF, DOCX, Markdown, JSON and Print in the browser. Signed server PDF route for MCP. Public PDF fallback. | `apps/web/src/features/resume/export`, `apps/server/src/http/*`, `packages/docx` | Moves into the Share sheet. | +| ATS | Live lint on resume JSON (21 rules with pointers to items). PDF engine (77 checks, weighted score, evidence boxes). Job-description matching. AI review. | `packages/resume/src/ats`, `packages/resume/src/ats-pdf`, `packages/api/src/features/ai` | Check mode and the public checker. | +| AI | 16 providers, AES-GCM keys (`ENCRYPTION_SECRET`). Agent streams through resumable streams (needs Redis). Tools: `read_resume`, `apply_resume_patch`, `ask_user_question`, `web_search`. The builder assistant reuses `AgentChat` and applies patches directly by default. | `packages/ai`, `packages/api/src/features/{ai,agent,ai-providers,applications}`, `apps/web/src/routes/agent` | One assistant panel that only proposes (§3.6). | +| Import | RR v5 and v4 JSON, JSON Resume, LinkedIn ZIP, PDF (AI or local heuristics), Word (AI). Uncertain fields are not reported. | `packages/import`, `apps/web/src/dialogs/resume/import.tsx` | New dialog flow with review flags. | +| MCP | 44 tools. Resume patching uses index-based JSON Pointers. `schema.json` resource. | `packages/mcp` | Updated for the date shape. | +| Schemas | Resume data, cover letters, applications, API keys | `packages/schema`, `packages/db/src/schema` | §3. | +| Auth | Better Auth: email and password, verification, username, Google, GitHub, LinkedIn, custom OIDC, 2FA, passkeys, API keys, OAuth provider for MCP | `packages/auth` | Account settings page. | +| Tests | Vitest 5 (happy-dom, Testing Library), 34 test files in `packages/ui`, 140 in `apps/web`. 24 Playwright specs, 3 of them env-gated. | `tests/e2e/specs` | Updated in the milestone that changes each screen. | --- @@ -54,17 +54,17 @@ Path shorthand: `B/` = `apps/web/src/routes/builder/$resumeId/`. ### 2.1 New route tree -| IA node | Route | Search params | Notes | -|---|---|---|---| -| Documents (home) | `/dashboard` | `type` (all, resume, letter), `q`, `tags`, `sort` (edited, name, created), `view` (grid, list) | Replaces both libraries. A file dropped anywhere on the page imports it. | -| Trash | `/dashboard/trash` | | Sidebar item appears only when Trash has items. | -| Applications | `/dashboard/applications` | `view` (list, board, insights, plus calendar if Q3g), `q`, `closed`, `applicationId` (detail sheet), `create` (Add dialog) | Keeps today's `view`, `applicationId` and `create`, so deep links and command entries keep working. | -| Settings | `/dashboard/settings/account`, `/dashboard/settings/preferences`, `/dashboard/settings/ai` | | `/dashboard/settings` redirects to Account on desktop and shows the three-row root on mobile. | -| Editor, resume | `/builder/$resumeId` | `mode` (write, design, check), `sheet` (share, download, history), `assistant` (thread id or `new`) | URL unchanged. | -| Editor, letter | `/builder/letter/$coverLetterId` | `mode` (write, design), `sheet`, `assistant` | New. The static `letter` segment outranks `$resumeId`. | -| Shared resume | `/$username/$slug` | | Adds slug redirects. | -| Public ATS checker | `/ats-checker` | | Rebuilt. | -| Landing, auth, templates | `/`, `/auth/*`, `/templates/$` | | The landing page isn't in the spec and stays as is. Auth pages pick up the new tokens. | +| IA node | Route | Search params | Notes | +| ------------------------ | ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | +| Documents (home) | `/dashboard` | `type` (all, resume, letter), `q`, `tags`, `sort` (edited, name, created), `view` (grid, list) | Replaces both libraries. A file dropped anywhere on the page imports it. | +| Trash | `/dashboard/trash` | | Sidebar item appears only when Trash has items. | +| Applications | `/dashboard/applications` | `view` (list, board, insights, plus calendar if Q3g), `q`, `closed`, `applicationId` (detail sheet), `create` (Add dialog) | Keeps today's `view`, `applicationId` and `create`, so deep links and command entries keep working. | +| Settings | `/dashboard/settings/account`, `/dashboard/settings/preferences`, `/dashboard/settings/ai` | | `/dashboard/settings` redirects to Account on desktop and shows the three-row root on mobile. | +| Editor, resume | `/builder/$resumeId` | `mode` (write, design, check), `sheet` (share, download, history), `assistant` (thread id or `new`) | URL unchanged. | +| Editor, letter | `/builder/letter/$coverLetterId` | `mode` (write, design), `sheet`, `assistant` | New. The static `letter` segment outranks `$resumeId`. | +| Shared resume | `/$username/$slug` | | Adds slug redirects. | +| Public ATS checker | `/ats-checker` | | Rebuilt. | +| Landing, auth, templates | `/`, `/auth/*`, `/templates/$` | | The landing page isn't in the spec and stays as is. Auth pages pick up the new tokens. | Mode, sheet and assistant live in the URL because other screens deep-link into them: the public checker's CTA opens Check, "Tailor a resume" opens the assistant, and "Open" on a sent document opens History at that version. Refreshing keeps the state. @@ -72,15 +72,15 @@ Mode, sheet and assistant live in the URL because other screens deep-link into t Old route files become `beforeLoad` redirect stubs for one minor release (Q2), then get deleted. -| Old route | New destination | -|---|---| -| `/dashboard/resumes` (with `view`, `sort`, `tags`, `q`) | `/dashboard?type=resume`, params mapped (`compact` becomes `grid`) | -| `/dashboard/cover-letters` | `/dashboard?type=letter` | -| `/dashboard/settings/profile`, `/dashboard/settings/authentication` | `/dashboard/settings/account` | -| `/dashboard/settings/integrations`, `/dashboard/settings/api-keys`, `/dashboard/settings/job-search` | `/dashboard/settings/ai` (job-search is already a redirect) | -| `/agent` | `/dashboard` | -| `/agent/new?resumeId=X` | `/builder/X?assistant=new`, or `/dashboard` without a resume | -| `/agent/$threadId` | The thread's working resume: `/builder/?assistant=`. `/dashboard` if that resume is gone. | +| Old route | New destination | +| ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | +| `/dashboard/resumes` (with `view`, `sort`, `tags`, `q`) | `/dashboard?type=resume`, params mapped (`compact` becomes `grid`) | +| `/dashboard/cover-letters` | `/dashboard?type=letter` | +| `/dashboard/settings/profile`, `/dashboard/settings/authentication` | `/dashboard/settings/account` | +| `/dashboard/settings/integrations`, `/dashboard/settings/api-keys`, `/dashboard/settings/job-search` | `/dashboard/settings/ai` (job-search is already a redirect) | +| `/agent` | `/dashboard` | +| `/agent/new?resumeId=X` | `/builder/X?assistant=new`, or `/dashboard` without a resume | +| `/agent/$threadId` | The thread's working resume: `/builder/?assistant=`. `/dashboard` if that resume is gone. | Also updated in the same milestone: command bar entries, links in `docs/` pages, any email template links, and path handling in `apps/server/src/static/web.ts`. @@ -227,13 +227,13 @@ Today `cover_letter` has name, recipient (rich-text HTML), content (HTML), `styl ### 3.10 Migration sequence and rollback -| Milestone | DDL | Backfill | Rollback | -|---|---|---|---| -| M3 | None (read-time upgrade + dual write) | None | Older versions read the dual-written `period`/`date` | -| M5 | `resume_version.kind`, `name`, `session_id`; `resume_slug_redirect` | `kind` from labels | Additive; older versions ignore it | -| M6 | `resume.application_id`, `trashed_at`, `auto_name`; `cover_letter.tags`, `is_locked`, `trashed_at` | None | Additive; older versions would show trashed documents again | -| M8 | `application.closed_reason`, `cover_letter_id`, `sent_*`, `requirements` | `rejected`/`archived` → `closed` | Reverse script | -| M9 | `cover_letter.sender_linked`, `design_linked`, `recipient_name`, `recipient_company`, `letter_date`, `layout`; `cover_letter_version` | Existing letters → freeform, unlinked | Additive | +| Milestone | DDL | Backfill | Rollback | +| --------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | ----------------------------------------------------------- | +| M3 | None (read-time upgrade + dual write) | None | Older versions read the dual-written `period`/`date` | +| M5 | `resume_version.kind`, `name`, `session_id`; `resume_slug_redirect` | `kind` from labels | Additive; older versions ignore it | +| M6 | `resume.application_id`, `trashed_at`, `auto_name`; `cover_letter.tags`, `is_locked`, `trashed_at` | None | Additive; older versions would show trashed documents again | +| M8 | `application.closed_reason`, `cover_letter_id`, `sent_*`, `requirements` | `rejected`/`archived` → `closed` | Reverse script | +| M9 | `cover_letter.sender_linked`, `design_linked`, `recipient_name`, `recipient_company`, `letter_date`, `layout`; `cover_letter_version` | Existing letters → freeform, unlinked | Additive | Contract steps (approved on 29 Sep 2026, migration `20260929063245_contract_redesign_legacy_fields`, with `rollback.sql`): @@ -251,55 +251,55 @@ Rule from the spec: keep a library where the repo already has one, and restyle i ### 4.1 `packages/ui` (generic primitives) -| Component | Plan | -|---|---| -| Button | **Restyle.** Primary, secondary, ghost and danger; sizes sm 28, md 36, touch 44; loading (14 px spinner plus a present-participle label, `aria-busy`). | -| Icon button | **Add** (thin wrapper). Required `aria-label`, tooltip with the shortcut. | -| Split button | **Restyle** `button-group` (Download PDF ▾). | -| Input, textarea, input-group | **Restyle.** `input-group` becomes the prefixed input with a status icon (address, username). | -| Select | **Add** a styled native select, as specified. Combobox stays for long searchable lists (fonts, languages). | -| Checkbox, switch, slider | **Restyle.** Switch rows make the whole row the `role="switch"` control. | -| Radio group, radio card | **Add** on Base UI Radio. | -| Segmented control | **Add** on Base UI ToggleGroup. | -| Tabs | **Restyle,** plus an underline variant with mono counts. | -| Menu, context menu, popover, tooltip | **Restyle** (radius 12, e2, enter motion from the trigger). | -| Dialog, alert dialog | **Restyle** (radius 16, scale from 0.98; alert dialogs name what's kept on cancel). | -| Sheet | **Restyle.** Side sheet (440/480/400 widths, 0.18 scrim) and bottom sheet (18 px radius, grabber, half and full stops). | -| Toast | **Restyle.** One at a time, bottom center, ink background, Undo action, 5–6 s, polite live region. | -| Alert, empty, skeleton, spinner, badge, kbd, avatar, label, separator, scroll-area, form, otp-field | **Restyle.** The spinner becomes the CSS spinner. | -| Step list with progress | **Add** (labelled steps plus a 4 px bar) for import, the ATS checker and Fit. | -| File drop zone | **Add** (default, dragging, error) plus the page-wide overlay variant. | -| Command | **Restyle** cmdk into the 560 px command bar. | -| Icon | **Add** (Material Symbols Rounded). | -| Structured date input | **Add.** Two month-year fields, an en dash and a Present switch. Labels come in as props because `packages/ui` can't use Lingui. | -| Sidebar (shadcn) | **Replace** with the web app shell. Removed from `packages/ui` if nothing else uses it. | -| Resizable | No longer used by the builder or the agent page. **Removed** if unused. | -| Accordion | Used for collapsible rows, or **replaced** by a simple disclosure where lighter. | +| Component | Plan | +| --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Button | **Restyle.** Primary, secondary, ghost and danger; sizes sm 28, md 36, touch 44; loading (14 px spinner plus a present-participle label, `aria-busy`). | +| Icon button | **Add** (thin wrapper). Required `aria-label`, tooltip with the shortcut. | +| Split button | **Restyle** `button-group` (Download PDF ▾). | +| Input, textarea, input-group | **Restyle.** `input-group` becomes the prefixed input with a status icon (address, username). | +| Select | **Add** a styled native select, as specified. Combobox stays for long searchable lists (fonts, languages). | +| Checkbox, switch, slider | **Restyle.** Switch rows make the whole row the `role="switch"` control. | +| Radio group, radio card | **Add** on Base UI Radio. | +| Segmented control | **Add** on Base UI ToggleGroup. | +| Tabs | **Restyle,** plus an underline variant with mono counts. | +| Menu, context menu, popover, tooltip | **Restyle** (radius 12, e2, enter motion from the trigger). | +| Dialog, alert dialog | **Restyle** (radius 16, scale from 0.98; alert dialogs name what's kept on cancel). | +| Sheet | **Restyle.** Side sheet (440/480/400 widths, 0.18 scrim) and bottom sheet (18 px radius, grabber, half and full stops). | +| Toast | **Restyle.** One at a time, bottom center, ink background, Undo action, 5–6 s, polite live region. | +| Alert, empty, skeleton, spinner, badge, kbd, avatar, label, separator, scroll-area, form, otp-field | **Restyle.** The spinner becomes the CSS spinner. | +| Step list with progress | **Add** (labelled steps plus a 4 px bar) for import, the ATS checker and Fit. | +| File drop zone | **Add** (default, dragging, error) plus the page-wide overlay variant. | +| Command | **Restyle** cmdk into the 560 px command bar. | +| Icon | **Add** (Material Symbols Rounded). | +| Structured date input | **Add.** Two month-year fields, an en dash and a Present switch. Labels come in as props because `packages/ui` can't use Lingui. | +| Sidebar (shadcn) | **Replace** with the web app shell. Removed from `packages/ui` if nothing else uses it. | +| Resizable | No longer used by the builder or the agent page. **Removed** if unused. | +| Accordion | Used for collapsible rows, or **replaced** by a simple disclosure where lighter. | ### 4.2 `apps/web` (feature components) -| Today | Plan | -|---|---| -| Dashboard layout, sidebar, header | **Replace** with the app shell (sidebar, icon rail, bottom tabs). | -| Resume library (grid, list, compact, cards, menus) and cover-letter library | **Replace** with Documents. Cards reuse the thumbnail pipeline; menus reuse the logic in `use-resume-menu-actions.ts`. | -| Create, import and edit-details dialogs | **Replace** with the New dialog, inline rename, Tags… and the Share address. | -| Builder shell (three resizable panels, two icon rails, dock, header, mobile shell) | **Replace** with the editor shell: editor bar, mode panel, page canvas, zoom bar, overlay layer. | -| Left sidebar sections and the item dialogs | **Replace** with the outline, Basics card and entry cards. Field sets are reused from the dialog code. | -| Right sidebar (template, layout, typography, design, page, custom styles) | **Replace** with Design mode. The CodeMirror stylesheet editor is kept and restyled inside Advanced. | -| Sharing, statistics, export sections, download dialog, version-history dropdown | **Replace** with the Share & export sheet. | -| Builder ATS section and public checker report | **Replace** with Check mode and the rebuilt public page. The ATS engines stay. | -| Notes and Information sections | Move into the document menu. | -| Builder assistant sheet and the agent pages (`AgentChat`, patch approval cards, thread sidebar, new-thread setup) | **Replace** with the assistant panel. The streaming transport, question-card logic and attachment helpers are reused. | -| Rich input | **Restyle,** restrict the toolbar, add Improve (M10). | -| Chip input | **Restyle** as the tag input. | -| Picture section | Becomes the photo popover in Basics. | -| Applications board, insights, calendar, CSV sheets | **Restyle.** | -| Applications table, form sheet, detail sheet, copilot | **Replace** with the grouped list, Add dialog, rebuilt detail sheet and the assistant. | -| Cover-letter editor dialog | **Replace** with the editor shell for letters. | -| Six settings pages | **Replace** with three. Password, 2FA and passkey dialogs are reused and restyled. | -| Public resume page | **Rebuild** (desktop bar and canvas, mobile reflow). | -| Command palette | **Rebuild** its groups. cmdk and the page-stack store stay. | -| Theme toggle and combobox | **Replace** with Appearance tiles and ⌘K entries. | +| Today | Plan | +| ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| Dashboard layout, sidebar, header | **Replace** with the app shell (sidebar, icon rail, bottom tabs). | +| Resume library (grid, list, compact, cards, menus) and cover-letter library | **Replace** with Documents. Cards reuse the thumbnail pipeline; menus reuse the logic in `use-resume-menu-actions.ts`. | +| Create, import and edit-details dialogs | **Replace** with the New dialog, inline rename, Tags… and the Share address. | +| Builder shell (three resizable panels, two icon rails, dock, header, mobile shell) | **Replace** with the editor shell: editor bar, mode panel, page canvas, zoom bar, overlay layer. | +| Left sidebar sections and the item dialogs | **Replace** with the outline, Basics card and entry cards. Field sets are reused from the dialog code. | +| Right sidebar (template, layout, typography, design, page, custom styles) | **Replace** with Design mode. The CodeMirror stylesheet editor is kept and restyled inside Advanced. | +| Sharing, statistics, export sections, download dialog, version-history dropdown | **Replace** with the Share & export sheet. | +| Builder ATS section and public checker report | **Replace** with Check mode and the rebuilt public page. The ATS engines stay. | +| Notes and Information sections | Move into the document menu. | +| Builder assistant sheet and the agent pages (`AgentChat`, patch approval cards, thread sidebar, new-thread setup) | **Replace** with the assistant panel. The streaming transport, question-card logic and attachment helpers are reused. | +| Rich input | **Restyle,** restrict the toolbar, add Improve (M10). | +| Chip input | **Restyle** as the tag input. | +| Picture section | Becomes the photo popover in Basics. | +| Applications board, insights, calendar, CSV sheets | **Restyle.** | +| Applications table, form sheet, detail sheet, copilot | **Replace** with the grouped list, Add dialog, rebuilt detail sheet and the assistant. | +| Cover-letter editor dialog | **Replace** with the editor shell for letters. | +| Six settings pages | **Replace** with three. Password, 2FA and passkey dialogs are reused and restyled. | +| Public resume page | **Rebuild** (desktop bar and canvas, mobile reflow). | +| Command palette | **Rebuild** its groups. cmdk and the page-stack store stay. | +| Theme toggle and combobox | **Replace** with Appearance tiles and ⌘K entries. | --- @@ -314,147 +314,147 @@ Every capability in today's app, and where it lives after the redesign. ### 5.1 Global shell -| Capability today | After | Status | -|---|---|---| -| Main nav: Resumes, Applications, Cover Letters, Agents, ATS Checker, six settings pages | Documents and Applications. Settings from the avatar. Agents → Assistant. ATS Checker → Check mode (public page stays). | Placed | -| Collapsible sidebar (`Mod+B`) | Fixed 240 px sidebar, icon rail on tablet. `Mod+B` removed. | Proposed | -| User menu: language, theme, sign out | Preferences and ⌘K. Sign out at the bottom of Settings → Account and on the mobile Account root. | Proposed | -| Command palette: search resumes, applications and threads; create; theme; language; go to | ⌘K: Search, Go to, Run, Ask. Threads appear as past conversations. | Placed | -| Shortcuts `Mod+K`, `Mod+Z`, `Mod+Shift+Z`, `Ctrl+Y`, `Mod+0`, `Mod+S` | Spec keymap. `Mod+0` fits the page. `Mod+S` keeps its "saved automatically" toast. | Proposed | -| Light and Dark themes | Light, Dark and System | Placed | -| 55 languages, right-to-left | Kept. `DirectionProvider` gets its missing `direction`, so Base UI follows RTL. | Placed | -| Donation toast | Q3n | Decide | +| Capability today | After | Status | +| ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -------- | +| Main nav: Resumes, Applications, Cover Letters, Agents, ATS Checker, six settings pages | Documents and Applications. Settings from the avatar. Agents → Assistant. ATS Checker → Check mode (public page stays). | Placed | +| Collapsible sidebar (`Mod+B`) | Fixed 240 px sidebar, icon rail on tablet. `Mod+B` removed. | Proposed | +| User menu: language, theme, sign out | Preferences and ⌘K. Sign out at the bottom of Settings → Account and on the mobile Account root. | Proposed | +| Command palette: search resumes, applications and threads; create; theme; language; go to | ⌘K: Search, Go to, Run, Ask. Threads appear as past conversations. | Placed | +| Shortcuts `Mod+K`, `Mod+Z`, `Mod+Shift+Z`, `Ctrl+Y`, `Mod+0`, `Mod+S` | Spec keymap. `Mod+0` fits the page. `Mod+S` keeps its "saved automatically" toast. | Proposed | +| Light and Dark themes | Light, Dark and System | Placed | +| 55 languages, right-to-left | Kept. `DirectionProvider` gets its missing `direction`, so Base UI follows RTL. | Placed | +| Donation toast | Q3n | Decide | ### 5.2 Documents and New -| Capability today | After | Status | -|---|---|---| -| Grid, list and compact views | Grid and list | Removed (compact) | -| Sort, tag filter, search on name and slug | Kept. Search also covers linked applications. | Placed | -| Thumbnails, lock overlay | Kept (lock badge) | Placed | -| Edit details: name, slug, tags | Inline rename, Tags… in the card menu, address in Share | Placed | -| Duplicate; lock with confirmation; permanent delete with confirmation | Duplicate; lock without confirmation; Move to Trash with undo; Trash with Restore and Delete now | Placed | -| Create (name, slug, tags); create a sample resume | New → Start blank; Try with a sample resume | Placed | -| Import RR JSON, RR v4, JSON Resume, LinkedIn ZIP, PDF, Word; manual format override | New → Import, same formats (LinkedIn ZIP accepted although the tile copy doesn't name it). Auto-detect, with a "Read as…" choice only when detection is ambiguous. | Proposed | -| Letters library: search, paging, create with template, JSON import and export, duplicate, delete | Letters tab in Documents. Design is inherited from the resume. JSON import through New → Import, JSON export through Share → Download. | Proposed | -| Copying a letter embedded in a resume into the library | Q3k | Decide | +| Capability today | After | Status | +| ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------- | +| Grid, list and compact views | Grid and list | Removed (compact) | +| Sort, tag filter, search on name and slug | Kept. Search also covers linked applications. | Placed | +| Thumbnails, lock overlay | Kept (lock badge) | Placed | +| Edit details: name, slug, tags | Inline rename, Tags… in the card menu, address in Share | Placed | +| Duplicate; lock with confirmation; permanent delete with confirmation | Duplicate; lock without confirmation; Move to Trash with undo; Trash with Restore and Delete now | Placed | +| Create (name, slug, tags); create a sample resume | New → Start blank; Try with a sample resume | Placed | +| Import RR JSON, RR v4, JSON Resume, LinkedIn ZIP, PDF, Word; manual format override | New → Import, same formats (LinkedIn ZIP accepted although the tile copy doesn't name it). Auto-detect, with a "Read as…" choice only when detection is ambiguous. | Proposed | +| Letters library: search, paging, create with template, JSON import and export, duplicate, delete | Letters tab in Documents. Design is inherited from the resume. JSON import through New → Import, JSON export through Share → Download. | Proposed | +| Copying a letter embedded in a resume into the library | Q3k | Decide | ### 5.3 Editor · Write -| Capability today | After | Status | -|---|---|---| -| Picture: upload with crop, URL, show/hide, delete, fit, size, rotation, aspect ratio, radius, border, shadow | Photo popover in Basics, all options | Placed | -| Basics fields and custom fields (icon, text, link) | Basics card. Custom fields as "Add field" under Website. | Proposed | -| Summary | Summary row with guidance and Improve | Placed | -| 12 item sections | The outline lists sections in use; Add section lists the rest. Interests is missing from the spec's list and is added. | Proposed | -| Section menu: add item, sort by date, show/hide, hide heading, rename, keyword layout, columns 1–6, icon, reset, keep together, start on new page | Eye on the row. Everything else in a row ⋯ menu (the mobile spec already has a row menu). Reset shows an undo toast instead of a confirmation. | Proposed | -| Item actions: drag, hide, duplicate, move to another section, a new section or a new page, delete | Drag and ⌥↑/⌥↓. Entry ⋯ menu: Hide from page, Duplicate, Move to…, Delete (undo). | Proposed | -| Custom sections (14 types, including summary and cover letter) | Add section → Custom section → type | Proposed | -| Role progression | Add role | Placed | -| Show link in title | Checkbox under the link field (spec Design System forms) | Placed | -| Type-specific fields: skill level, proficiency, keywords, icon and colour; language level and fluency; profile icon; reference and publication fields | Kept. Secondary fields (level, icon, colour) sit under "More options" in the entry card. | Proposed | -| Hidden-sections list with Show | Hidden sections stay in the outline (strikethrough, eye) | Placed | -| Locked banner with "Enable editing" | Read-only panel with "Locked · Unlock" | Proposed | -| Rich text: headings, underline, strike, code, colours, highlight, alignment, indent, rule | Restricted toolbar. Existing formatting is preserved and removable with Clear formatting. | Placed | -| Fullscreen rich-text editor | Q3m | Decide | -| Multi-page layout: add and delete pages, full width per page, main and sidebar per page, sidebar width | Q3f | Decide | -| Undo, redo, zoom, pan, pinch, double-click zoom | Undo in the bar; zoom bar 60–150 % | Placed | -| Page stacking toggle | Q3m | Decide | -| Copy public URL and Open AI agent in the dock | Share; ⌘J assistant | Placed | -| Cross-tab sync and "updated elsewhere" notice | Kept | Proposed | +| Capability today | After | Status | +| ----------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| Picture: upload with crop, URL, show/hide, delete, fit, size, rotation, aspect ratio, radius, border, shadow | Photo popover in Basics, all options | Placed | +| Basics fields and custom fields (icon, text, link) | Basics card. Custom fields as "Add field" under Website. | Proposed | +| Summary | Summary row with guidance and Improve | Placed | +| 12 item sections | The outline lists sections in use; Add section lists the rest. Interests is missing from the spec's list and is added. | Proposed | +| Section menu: add item, sort by date, show/hide, hide heading, rename, keyword layout, columns 1–6, icon, reset, keep together, start on new page | Eye on the row. Everything else in a row ⋯ menu (the mobile spec already has a row menu). Reset shows an undo toast instead of a confirmation. | Proposed | +| Item actions: drag, hide, duplicate, move to another section, a new section or a new page, delete | Drag and ⌥↑/⌥↓. Entry ⋯ menu: Hide from page, Duplicate, Move to…, Delete (undo). | Proposed | +| Custom sections (14 types, including summary and cover letter) | Add section → Custom section → type | Proposed | +| Role progression | Add role | Placed | +| Show link in title | Checkbox under the link field (spec Design System forms) | Placed | +| Type-specific fields: skill level, proficiency, keywords, icon and colour; language level and fluency; profile icon; reference and publication fields | Kept. Secondary fields (level, icon, colour) sit under "More options" in the entry card. | Proposed | +| Hidden-sections list with Show | Hidden sections stay in the outline (strikethrough, eye) | Placed | +| Locked banner with "Enable editing" | Read-only panel with "Locked · Unlock" | Proposed | +| Rich text: headings, underline, strike, code, colours, highlight, alignment, indent, rule | Restricted toolbar. Existing formatting is preserved and removable with Clear formatting. | Placed | +| Fullscreen rich-text editor | Q3m | Decide | +| Multi-page layout: add and delete pages, full width per page, main and sidebar per page, sidebar width | Q3f | Decide | +| Undo, redo, zoom, pan, pinch, double-click zoom | Undo in the bar; zoom bar 60–150 % | Placed | +| Page stacking toggle | Q3m | Decide | +| Copy public URL and Open AI agent in the dock | Share; ⌘J assistant | Placed | +| Cross-tab sync and "updated elsewhere" notice | Kept | Proposed | ### 5.4 Editor · Design -| Capability today | After | Status | -|---|---|---| -| Template gallery (15, static sample images) | Thumbnails of the user's content, hover preview | Placed | -| Any of about 500 font families for body and heading, weights, size, line height, German hyphenation | Five pairings, size, density. The rest per Q3b. | Decide | -| 22 swatches; primary, text and background colours | Eight accents plus a custom hex with contrast check. Text and background per Q3c. | Decide | -| Level indicator style and icon | Q3d | Decide | +| Capability today | After | Status | +| ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ----------------- | +| Template gallery (15, static sample images) | Thumbnails of the user's content, hover preview | Placed | +| Any of about 500 font families for body and heading, weights, size, line height, German hyphenation | Five pairings, size, density. The rest per Q3b. | Decide | +| 22 swatches; primary, text and background colours | Eight accents plus a custom hex with contrast check. Text and background per Q3c. | Decide | +| Level indicator style and icon | Q3d | Decide | | Page: language, A4/Letter/free-form, margins, gaps, hide link underline, hide icons, hide section icons | Page group. Gaps and hide-section-icons in Advanced (Proposed). Free-form per Q3e. | Proposed / Decide | -| Custom CSS editor | Advanced | Placed | -| Notes; Information (docs, source, bug report, translations, sponsors, donate) | Document menu | Placed | -| Export panel | Share → Download | Placed | +| Custom CSS editor | Advanced | Placed | +| Notes; Information (docs, source, bug report, translations, sponsors, donate) | Document menu | Placed | +| Export panel | Share → Download | Placed | ### 5.5 Editor · Check -| Capability today | After | Status | -|---|---|---| -| Live lint with jump to item | Issues pinned to their lines | Placed | -| Optional job description | Job match from the linked application; paste as fallback | Placed | -| Deep check (PDF report and AI review) | Deep check with a toast; Writing tab | Placed | -| Choosing the provider for the AI review | Model chip in the Writing tab's disclosure ("Change") | Proposed | +| Capability today | After | Status | +| --------------------------------------- | -------------------------------------------------------- | -------- | +| Live lint with jump to item | Issues pinned to their lines | Placed | +| Optional job description | Job match from the linked application; paste as fallback | Placed | +| Deep check (PDF report and AI review) | Deep check with a toast; Writing tab | Placed | +| Choosing the provider for the AI review | Model chip in the Writing tab's disclosure ("Change") | Proposed | ### 5.6 Share & export -| Capability today | After | Status | -|---|---|---| -| PDF, DOCX, Markdown and JSON downloads; resume and letter tabs | Download tab | Placed | -| Letter download "Include resume header" | Checkbox in the Download tab for letters | Proposed | -| Print | Document menu → Print (⌘P becomes Download PDF) | Proposed | -| Public access, download buttons, copy URL | Link tab | Placed | -| Password protection | Q3a | Decide | +| Capability today | After | Status | +| ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -------- | +| PDF, DOCX, Markdown and JSON downloads; resume and letter tabs | Download tab | Placed | +| Letter download "Include resume header" | Checkbox in the Download tab for letters | Proposed | +| Print | Document menu → Print (⌘P becomes Download PDF) | Proposed | +| Public access, download buttons, copy URL | Link tab | Placed | +| Password protection | Q3a | Decide | | Statistics: views, downloads, sparkline, change vs previous 30 days, last viewed and downloaded | Views and downloads (30 bars, time since last view). "Change vs previous 30 days" and "last downloaded" are dropped. | Proposed | -| Version history (automatic, restore with confirmation) | History tab: named versions, preview, restore without confirmation (it's undoable) | Placed | +| Version history (automatic, restore with confirmation) | History tab: named versions, preview, restore without confirmation (it's undoable) | Placed | ### 5.7 Assistant -| Capability today | After | Status | -|---|---|---| -| Agents page: thread list, archive, delete | Past conversations grouped by document; delete kept; archive per Q3j | Decide | -| Model and resume pickers per thread | Model chip; document inferred | Placed | -| Working on an AI copy or a blank draft | Q3j | Decide | -| Attachments, web-search sources, token counts, copy transcript or JSON | Q3j | Decide | -| "Review edits" toggle and auto-apply | Always proposals | Removed (auto-apply) | -| Restoring an applied edit | ⌘Z and History | Placed | -| Agent questions (options or your own words) | Clarifying question card | Placed | -| Builder assistant sheet | Assistant panel | Placed | -| ATS AI review | Check → Writing | Placed | -| Application copilot: tailor resume, draft cover letter | Tailor a resume; Write a letter with Draft from the posting | Placed | -| Application copilot: fit score, follow-up draft | Q3i | Decide | +| Capability today | After | Status | +| ---------------------------------------------------------------------- | -------------------------------------------------------------------- | -------------------- | +| Agents page: thread list, archive, delete | Past conversations grouped by document; delete kept; archive per Q3j | Decide | +| Model and resume pickers per thread | Model chip; document inferred | Placed | +| Working on an AI copy or a blank draft | Q3j | Decide | +| Attachments, web-search sources, token counts, copy transcript or JSON | Q3j | Decide | +| "Review edits" toggle and auto-apply | Always proposals | Removed (auto-apply) | +| Restoring an applied edit | ⌘Z and History | Placed | +| Agent questions (options or your own words) | Clarifying question card | Placed | +| Builder assistant sheet | Assistant panel | Placed | +| ATS AI review | Check → Writing | Placed | +| Application copilot: tailor resume, draft cover letter | Tailor a resume; Write a letter with Draft from the posting | Placed | +| Application copilot: fit score, follow-up draft | Q3i | Decide | ### 5.8 Applications -| Capability today | After | Status | -|---|---|---| -| Board, table, insights views | Board, grouped List, Insights | Placed | -| Calendar view and interview scheduling (#3539, not in the atlas) | Q3g | Decide | -| Search, archived toggle | Search, Show closed | Placed | -| Tag filter, sort (updated, applied, company, role), table bulk actions (move stage, tag, archive, delete) | Q3h | Decide | -| CSV import (upload, paste, sample, preview) and export (current filters or all, date range) | Import/export icon; column matching confirmed before saving; export options kept | Placed / Proposed | -| Board drag; card menu (edit, move, archive, delete) | Drag plus a Move to… menu; Close…; Delete in ⋯ | Placed | -| Add/edit form: job description autofill, company, role, location, salary, source, stage, link, stage date, follow-up, notes | Add dialog from a pasted link or posting; the other fields in the detail sheet | Placed | -| Resume link or PDF upload; cover-letter PDF upload; tags | Links placed. Uploads and tags per Q3h. | Placed / Decide | -| Detail: stage bar and move, notes, documents sent, interviews list and schedule, follow-up, mark rejected, archive, delete | Stepper, Next step (interviews and follow-up), What you sent, Notes (autosave), Close application…, Delete in ⋯ | Placed | -| Contacts (several per application) | Contact cell shows the primary contact; the full list opens from it | Proposed | -| Timeline: add notes, edit notes, edit entry dates, delete entries | Activity, with each row's ⋯ menu for edit and delete | Proposed | -| Interview dialog (type, date and time, duration, location, notes) | "Schedule interview" from Next step → Edit; adds `.ics` | Proposed | -| Insights: tiles, pipeline chart with PNG export, weekly chart, by-source chart | Funnel, heard back, median days, tailored vs base. Extras per Q3p. | Decide | +| Capability today | After | Status | +| --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ----------------- | +| Board, table, insights views | Board, grouped List, Insights | Placed | +| Calendar view and interview scheduling (#3539, not in the atlas) | Q3g | Decide | +| Search, archived toggle | Search, Show closed | Placed | +| Tag filter, sort (updated, applied, company, role), table bulk actions (move stage, tag, archive, delete) | Q3h | Decide | +| CSV import (upload, paste, sample, preview) and export (current filters or all, date range) | Import/export icon; column matching confirmed before saving; export options kept | Placed / Proposed | +| Board drag; card menu (edit, move, archive, delete) | Drag plus a Move to… menu; Close…; Delete in ⋯ | Placed | +| Add/edit form: job description autofill, company, role, location, salary, source, stage, link, stage date, follow-up, notes | Add dialog from a pasted link or posting; the other fields in the detail sheet | Placed | +| Resume link or PDF upload; cover-letter PDF upload; tags | Links placed. Uploads and tags per Q3h. | Placed / Decide | +| Detail: stage bar and move, notes, documents sent, interviews list and schedule, follow-up, mark rejected, archive, delete | Stepper, Next step (interviews and follow-up), What you sent, Notes (autosave), Close application…, Delete in ⋯ | Placed | +| Contacts (several per application) | Contact cell shows the primary contact; the full list opens from it | Proposed | +| Timeline: add notes, edit notes, edit entry dates, delete entries | Activity, with each row's ⋯ menu for edit and delete | Proposed | +| Interview dialog (type, date and time, duration, location, notes) | "Schedule interview" from Next step → Edit; adds `.ics` | Proposed | +| Insights: tiles, pipeline chart with PNG export, weekly chart, by-source chart | Funnel, heard back, median days, tailored vs base. Extras per Q3p. | Decide | ### 5.9 Settings -| Capability today | After | Status | -|---|---|---| -| Profile: name, username, email change with confirmation, resend verification | Account → Profile, plus photo upload (new) | Placed | -| Preferences: theme, language | Appearance tiles including System, Language, Motion note | Placed | -| Password, 2FA with backup codes | Sign-in & security | Placed | -| Passkeys; Google, GitHub, LinkedIn and custom OIDC sign-in | Q3o | Decide | -| API keys: create with 1, 3, 6 or 12-month expiry; shown once; delete | 30 days, 90 days or Never; shown once; revoke with undo | Placed | +| Capability today | After | Status | +| ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------- | +| Profile: name, username, email change with confirmation, resend verification | Account → Profile, plus photo upload (new) | Placed | +| Preferences: theme, language | Appearance tiles including System, Language, Motion note | Placed | +| Password, 2FA with backup codes | Sign-in & security | Placed | +| Passkeys; Google, GitHub, LinkedIn and custom OIDC sign-in | Q3o | Decide | +| API keys: create with 1, 3, 6 or 12-month expiry; shown once; delete | 30 days, 90 days or Never; shown once; revoke with undo | Placed | | Integrations: 16 providers, advanced options, enable switch, test, edit model, delete | Provider rows (Test with latency, Edit). Add provider lists all 16. Enable switch and delete live inside Edit. | Proposed | -| Account: export data (profile and resumes as JSON); delete by typing "delete" | Export everything (zip with documents and applications); Delete account alert dialog with counts | Placed | +| Account: export data (profile and resumes as JSON); delete by typing "delete" | Export everything (zip with documents and applications); Delete account alert dialog with counts | Placed | ### 5.10 Public pages and auth -| Capability today | After | Status | -|---|---|---| -| Public resume: PDF viewer, download if allowed, views counted, OG tags, "Build your own resume" | New desktop page and mobile reflow; footer credit | Placed | -| Password gate for public resumes | Q3a | Decide | -| `ROOT_RESUME_ID` mode, `/templates/$` | Unchanged (the root mode uses the new public page) | Proposed | -| Landing page | Unchanged (not in the spec) | Proposed | +| Capability today | After | Status | +| ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | -------- | +| Public resume: PDF viewer, download if allowed, views counted, OG tags, "Build your own resume" | New desktop page and mobile reflow; footer credit | Placed | +| Password gate for public resumes | Q3a | Decide | +| `ROOT_RESUME_ID` mode, `/templates/$` | Unchanged (the root mode uses the new public page) | Proposed | +| Landing page | Unchanged (not in the spec) | Proposed | | Auth pages: passkey autofill, social sign-in, 2FA, backup codes, OAuth consent, error page, instance switches | Restyled through tokens only (not designed in the spec) | Proposed | -| Public ATS checker: in-browser PDF check, job description, categories, coverage | Rebuilt with the parser view and the fix CTA | Placed | -| Public checker AI review for signed-in users | Q3l | Decide | -| MCP and API | Unchanged apart from dates | Placed | +| Public ATS checker: in-browser PDF check, job description, categories, coverage | Rebuilt with the parser view and the fix CTA | Placed | +| Public checker AI review for signed-in users | Q3l | Decide | +| MCP and API | Unchanged apart from dates | Placed | --- @@ -470,7 +470,7 @@ Sizes are relative (S, M, L, XL). XL milestones split into the listed PRs. After ### M0 · Preparation and spikes (S) - Branch strategy per Q1. -- Handoff folder at the repo root plus a `.gitignore` line. Otherwise Biome, knip and markdownlint lint it, and `pnpm check` rewrites its JavaScript. +- Handoff folder at the repo root plus a `.gitignore` line. Otherwise Oxlint and knip inspect it, and `pnpm check` rewrites its JavaScript with Oxfmt. - Load the TanStack Router skills (`router-core`, `navigation`, `search-params`, `auth-and-guards`) before route work, per `CLAUDE.md`. - **Spike A, render cost.** Add performance marks around `createResumePdfBlob`. Measure p50 and p95 for the sample resume and for 2-page and 4-page resumes on a mid-range laptop. The result sets the typing debounce and decides whether rendering moves to a Web Worker before M3 and M4. - **Spike B, page map.** Emit `data-rr-key` on section and item containers in the `packages/pdf` primitives and `SectionShell`. Read the layout tree that `Document.onRender` receives, sum parent offsets and draw boxes over the canvas for Onyx, Azurill and Bronzor. Confirm that exported PDFs don't carry the keys, or that carrying them is harmless. @@ -759,19 +759,19 @@ PRs: settings; shared resume; ATS checker. ## 8. Risks and spikes -| Risk | Why | Mitigation | -|---|---|---| -| Main-thread rendering | Every edit re-renders the whole document with react-pdf on the main thread. Inline editing renders while typing; the gallery renders 15 documents; hover previews and Fit add more. | Spike A. Cache renders by data hash, render thumbnails at idle time, lengthen the debounce while typing, and move react-pdf to a Web Worker if p95 is too high (plausible because fonts load by URL, but unproven here). | -| Page map relies on an internal react-pdf API | The layout tree passed to `Document.onRender` isn't public. Boxes are relative to parents. Field-level boxes would need a text-layout patch. | Keep the patched renderer pinned. Unit-test the box maths against fixtures. Stay item-level. If layout data is missing, the panel still works. | -| Editing the letter body on the page | The page is a canvas, and HTML and PDF line breaks differ | Spike C at the start of M9. Fallback, with your OK (Q10): edit the body in the panel while the page updates. | -| Sidebar Left/Right | The eight two-column templates hard-code their side, and three put the header in the sidebar | Mirror per template in M4. If a template can't mirror cleanly, its Left/Right control is hidden. | -| Structured dates | Parsed dates print in a normalised format; the public API and MCP shape changes; 55 locales | Dual write, the `raw` fallback, a date format option (Q4), a changelog entry, tests per locale. | -| Assistant behaviour change | Today's builder assistant applies edits directly by default; the new one only proposes | Server tool and prompt changes. MCP and API are untouched. | -| Stage rollback | Older versions can't read `closed` | Reverse script shipped with the migration. | -| E2E churn | Many specs select today's structure (XPath hops, `data-slot`, ids) | Update specs in the milestone that changes each screen; prefer role and label selectors. | -| Translation volume | Hundreds of new strings across 55 catalogs, and Crowdin lag | English fallback until translated. The section-title messages stay untouched (the PDF catalog tooling throws if they change). | -| Branch drift | 12 milestones, several XL | Q1. Merge `main` into the integration branch weekly. Additive migrations can land on `main` early. | -| Self-hosting variations | No Redis, no SMTP, no AI keys | An explicit state for each (assistant unavailable, emails logged, AI features explained). | +| Risk | Why | Mitigation | +| -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Main-thread rendering | Every edit re-renders the whole document with react-pdf on the main thread. Inline editing renders while typing; the gallery renders 15 documents; hover previews and Fit add more. | Spike A. Cache renders by data hash, render thumbnails at idle time, lengthen the debounce while typing, and move react-pdf to a Web Worker if p95 is too high (plausible because fonts load by URL, but unproven here). | +| Page map relies on an internal react-pdf API | The layout tree passed to `Document.onRender` isn't public. Boxes are relative to parents. Field-level boxes would need a text-layout patch. | Keep the patched renderer pinned. Unit-test the box maths against fixtures. Stay item-level. If layout data is missing, the panel still works. | +| Editing the letter body on the page | The page is a canvas, and HTML and PDF line breaks differ | Spike C at the start of M9. Fallback, with your OK (Q10): edit the body in the panel while the page updates. | +| Sidebar Left/Right | The eight two-column templates hard-code their side, and three put the header in the sidebar | Mirror per template in M4. If a template can't mirror cleanly, its Left/Right control is hidden. | +| Structured dates | Parsed dates print in a normalised format; the public API and MCP shape changes; 55 locales | Dual write, the `raw` fallback, a date format option (Q4), a changelog entry, tests per locale. | +| Assistant behaviour change | Today's builder assistant applies edits directly by default; the new one only proposes | Server tool and prompt changes. MCP and API are untouched. | +| Stage rollback | Older versions can't read `closed` | Reverse script shipped with the migration. | +| E2E churn | Many specs select today's structure (XPath hops, `data-slot`, ids) | Update specs in the milestone that changes each screen; prefer role and label selectors. | +| Translation volume | Hundreds of new strings across 55 catalogs, and Crowdin lag | English fallback until translated. The section-title messages stay untouched (the PDF catalog tooling throws if they change). | +| Branch drift | 12 milestones, several XL | Q1. Merge `main` into the integration branch weekly. Additive migrations can land on `main` early. | +| Self-hosting variations | No Redis, no SMTP, no AI keys | An explicit state for each (assistant unavailable, emails logged, AI features explained). | --- @@ -821,7 +821,7 @@ PRs: settings; shared resume; ATS checker. - `pnpm --filter web typecheck`, plus `pnpm --filter @reactive-resume/ typecheck` for each changed package. - `pnpm --filter web test`, plus the tests of each changed package (`ui`, `schema`, `resume`, `pdf`, `api`, `import`, `docx`, `mcp`). - `pnpm exec turbo boundaries`. -- `pnpm exec biome check ` to inspect without writing, then `pnpm check`, which writes changes (Biome `--write --unsafe`, markdownlint `--fix`). +- `pnpm exec oxlint --deny-warnings ` and `pnpm exec oxfmt --check ` to inspect without writing, then `pnpm check`, which applies safe lint fixes and formatting. - `pnpm knip`. - `pnpm lingui:extract` when copy changes. It rewrites the PO catalogs and the PDF section-title catalog. - `pnpm db:generate`, then `pnpm db:migrate` against the local database, for milestones with schema changes. Review the generated SQL. @@ -849,9 +849,9 @@ Measured on 28 Sep 2026 in Node (Apple silicon) with `renderToBuffer`, fonts war **Spike A, render cost (react-pdf layout and serialisation only, no pdf.js painting):** -| Content | Onyx | Azurill | Bronzor | Gengar | -|---|---|---|---|---| -| 1 authored page (1–2 physical pages) | p50 41 ms, p95 52 ms | p50 39 ms, p95 43 ms | p50 37 ms, p95 39 ms | p50 31 ms, p95 33 ms | +| Content | Onyx | Azurill | Bronzor | Gengar | +| ------------------------------------- | -------------------- | -------------------- | --------------------- | -------------------- | +| 1 authored page (1–2 physical pages) | p50 41 ms, p95 52 ms | p50 39 ms, p95 43 ms | p50 37 ms, p95 39 ms | p50 31 ms, p95 33 ms | | 4 authored pages (4–6 physical pages) | p50 87 ms, p95 88 ms | p50 77 ms, p95 87 ms | p50 85 ms, p95 101 ms | p50 82 ms, p95 84 ms | Decisions: @@ -1516,6 +1516,7 @@ Verification: Decided 29 Sep 2026: react-pdf is replaced by [Forme](https://www.formepdf.com/) 0.25.0 in a full cutover, and Semantic CSS is ported to it. The known 0.25.0 defects are accepted until upstream fixes them (the full list is in the log below): + - Extracted text drops the second letter of ligatures ([#156](https://github.com/danmolitor/forme/issues/156)). This one gets a workaround: ligature features are switched off in the bundled font bytes. - Arabic, Hindi and Hebrew extracted text is scrambled. - A link inside part of a paragraph loses its annotation ([#157](https://github.com/danmolitor/forme/issues/157)). diff --git a/apps/server/src/vercel.test.ts b/apps/server/src/vercel.test.ts index f11279a46..d90da8f5a 100644 --- a/apps/server/src/vercel.test.ts +++ b/apps/server/src/vercel.test.ts @@ -9,12 +9,11 @@ const mocks = vi.hoisted(() => ({ configureAgentStreamLifetime: vi.fn(), pool: {}, getPool: vi.fn(), - createApp: - vi.fn< - (options: { serveStatic: boolean; trustedClient: (request: Request) => string }) => { - fetch: (request: Request) => Promise; - } - >(), + createApp: vi.fn< + (options: { serveStatic: boolean; trustedClient: (request: Request) => string }) => { + fetch: (request: Request) => Promise; + } + >(), handle: vi.fn<(request: Request) => Promise>(), })); diff --git a/apps/web/index.html b/apps/web/index.html index 6ba7f8ecd..f2731ab8f 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -11,7 +11,8 @@ try { const match = document.cookie.match(/(?:^|; )theme=([^;]*)/); const theme = match ? decodeURIComponent(match[1]) : "system"; - const dark = theme === "dark" || (theme !== "light" && window.matchMedia("(prefers-color-scheme: dark)").matches); + const dark = + theme === "dark" || (theme !== "light" && window.matchMedia("(prefers-color-scheme: dark)").matches); document.documentElement.classList.toggle("dark", dark); } catch { // Without cookies or matchMedia the page starts light, and the app corrects it once it loads. @@ -24,7 +25,10 @@ - + @@ -44,7 +48,11 @@ Loading