Compare commits

...
95 Commits
Author SHA1 Message Date
Amruth Pillai 47abce351b sync translations with crowdin 2026-04-27 15:22:13 +02:00
Amruth Pillai 8f6c65b7fd chore(release): 🚀 v5.0.20 2026-04-27 15:21:07 +02:00
github-actions[bot]andCrowdin Bot 5a88ac12bf Sync Translations from Crowdin (#2935)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-04-27 15:13:05 +02:00
RuzenieAmruth Pillaiautofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
118004b3d3 feat:icon colors (#2928)
Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-04-27 15:12:55 +02:00
github-actions[bot]andCrowdin Bot 6a8dd480cb Sync Translations from Crowdin (#2934)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-04-27 13:39:40 +02:00
Amruth Pillai 5d8126d4b0 feat: combine settings pages into a single integrations page 2026-04-27 12:56:15 +02:00
JamesGoslings 8566a9fd0f fix(i18n): translate Skills section title to Chinese (zh-CN) (#2931) 2026-04-27 10:47:46 +02:00
github-actions[bot]andCrowdin Bot 0d0bc61582 Sync Translations from Crowdin (#2932)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-04-27 10:47:22 +02:00
JamesGoslings e3af637100 docs: list Meowth alongside existing templates (#2929) 2026-04-27 10:47:14 +02:00
github-actions[bot]andCrowdin Bot f752145c57 Sync Translations from Crowdin (#2926)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-04-27 10:45:59 +02:00
Amruth Pillai b87f200767 feat: Add better email templates for password reset and email verification. 2026-04-27 10:45:44 +02:00
Amruth Pillai 73ec8b2ffb chore: release version 5.0.20 with new Meowth resume template and updated translations 2026-04-27 08:52:18 +02:00
github-actions[bot]andCrowdin Bot 107537999f Sync Translations from Crowdin (#2925)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-04-27 08:50:43 +02:00
Amruth Pillai e1bccbcc93 chore: update dependencies, cleanup exports (knip) 2026-04-27 08:48:35 +02:00
JamesGoslingsAmruth Pillaiautofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
54e256be09 feat: add Meowth template with inline three-column entry header (Asian-style compact ATS) (#2923)
Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-04-27 08:45:08 +02:00
github-actions[bot]andCrowdin Bot b26d510294 Sync Translations from Crowdin (#2922)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-04-26 11:46:56 +02:00
Amruth Pillai 623ca5c675 allow loopback to localhost on mcp, only for local MCP clients 2026-04-26 10:59:10 +02:00
Amruth PillaiandClaude 76e00ae019 Revert "fix(resume): prevent orphaned section headings at page break (#2851)" (#2920)
This reverts commit 775f625f6e.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-26 01:23:22 +02:00
Amruth Pillai ff0ef12ecc Merge branch 'main' of github.com:amruthpillai/reactive-resume 2026-04-26 01:08:00 +02:00
Amruth Pillai 1bcb2e2af6 update changelog 2026-04-26 01:07:58 +02:00
github-actions[bot]andCrowdin Bot 796dc2ca41 Sync Translations from Crowdin (#2919)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-04-26 01:01:34 +02:00
github-actions[bot]andCrowdin Bot 80bf41254b Sync Translations from Crowdin (#2918)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-04-26 00:58:11 +02:00
Amruth Pillai bbc38d2f09 implement logic from #2853, thanks to @trigger-xyz 2026-04-26 00:57:50 +02:00
Christian PojoniClaude Opus 4.6autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
775f625f6e fix(resume): prevent orphaned section headings at page break (#2851)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-04-26 00:49:03 +02:00
2203933fac Sync Translations from Crowdin (#2917)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
2026-04-26 00:48:24 +02:00
Platinum1154andAmruth Pillai 907e32a731 feat: add text color support to the rich text editor (#2903)
* feat: add text color support to the rich text editor

* improve design of text color picker

* Update translations for color picker features in multiple languages

---------

Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
2026-04-26 00:48:03 +02:00
autofix-ci[bot] d6919e340b [autofix.ci] apply automated fixes 2026-04-25 22:34:53 +00:00
Amruth Pillai e05ae42fbb Remove "Reactive Resume" translations from multiple language files to streamline localization efforts. 2026-04-26 00:33:45 +02:00
github-actions[bot]andCrowdin Bot e99f39d139 Sync Translations from Crowdin (#2916)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-04-26 00:33:12 +02:00
Platinum1154andAmruth Pillai a4e7d6680d feat: add Chinese font options (#2905)
Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
2026-04-26 00:32:42 +02:00
iago macedoandAmruth Pillai 77ad14b359 feat: add OpenRouter as AI provider (#2906)
Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
2026-04-26 00:00:14 +02:00
Amruth Pillai 9b3916d43e chore: ⬆️ update dependencies 2026-04-25 23:34:19 +02:00
github-actions[bot]andCrowdin Bot 29f1aaf0da [skip ci] chore(i18n): sync translations from crowdin (#2914)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-04-25 17:32:04 +02:00
github-actions[bot]andCrowdin Bot 73cef02a4f Sync Translations from Crowdin (#2913)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-04-25 15:47:33 +02:00
github-actions[bot]andCrowdin Bot 2cb1897922 Sync Translations from Crowdin (#2912)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-04-25 15:32:52 +02:00
Amruth Pillai d0af9f4b4f test(security): cover url validation and form edge cases
Add and update tests for new security utilities and tightened UI behavior to prevent regressions in validation and error handling paths.

Made-with: Cursor
2026-04-25 15:31:19 +02:00
Amruth Pillai 08e9c80037 refactor(ui): improve error handling and input safety in app flows
Normalize frontend error rendering and tighten input/path handling across auth, builder, dashboard, and shared components for more resilient UX behavior.

Made-with: Cursor
2026-04-25 15:31:13 +02:00
Amruth Pillai a42dbcd452 feat(security): harden auth, oauth, and printer endpoints
Add stricter URL and redirect validation, endpoint rate limiting, safer defaults for printer and compose config, and CSP protections across server and API surfaces.

Made-with: Cursor
2026-04-25 15:31:06 +02:00
Amruth Pillai d3102565e4 chore(i18n): sync translation catalogs
Update locale catalogs to include the latest extracted strings used by the recent auth and security UX updates.

Made-with: Cursor
2026-04-25 15:30:51 +02:00
Amruth Pillai c0387298c5 docs(self-hosting): refresh docker and setup guidance
Clarify self-hosting, migration, and quickstart instructions to reflect the new secure defaults and deployment flow.

Made-with: Cursor
2026-04-25 15:30:47 +02:00
Amruth Pillai 9e1e593dc4 add SECURITY.md 2026-04-25 11:29:27 +02:00
Amruth Pillai 849aad6497 allow for running dev environment inside docker 2026-04-25 11:26:02 +02:00
Amruth Pillai 847d69b621 pin vite-plus versions to 0.1.19 2026-04-25 10:53:19 +02:00
Yeung Lihan 6ff754d125 fix: reduce preview wheel zoom sensitivity (#2911) 2026-04-25 09:43:58 +02:00
JamesGoslings ff3d4b1337 fix: align role period text to end in experience item (#2876) (#2908)
When an experience entry has multiple roles (Role Progression),
the period/date for each role was left-aligned in the grid layout,
causing it to appear off-center instead of right-aligned under the
location field.

Added `text-end` class to the role period element to match the
alignment behavior of the single-role header period. Uses `text-end`
instead of `text-right` for proper RTL language support.
2026-04-25 09:43:31 +02:00
github-actions[bot]andCrowdin Bot 2d2d5cd505 Sync Translations from Crowdin (#2902)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-04-23 14:17:30 +02:00
Amruth Pillai c6e8df0a00 - Pinned some packages to specific versions to avoid potential security vulnerabilities through transitive dependencies. (through running pnpm audit)
- Updated dependencies and lockfile.
- Synced translation catalogs from Crowdin.
2026-04-22 15:15:10 +02:00
Amruth Pillai 7df9b1e4b5 autocomplete passkey, if exists 2026-04-14 15:04:19 +02:00
github-actions[bot]andCrowdin Bot 0b4bac62a7 [skip ci] chore(i18n): sync translations from crowdin (#2892)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-04-14 14:42:06 +02:00
Amruth Pillai 82cb6e7590 update translations for passkeys 2026-04-14 14:40:07 +02:00
github-actions[bot]andCrowdin Bot 46dac6d3b1 [skip ci] chore(i18n): sync translations from crowdin (#2891)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-04-14 12:16:24 +02:00
Amruth Pillai b0b0d27c1f try again 2026-04-14 12:15:01 +02:00
Amruth Pillai b12d1184bc switch to crowdin github action 2026-04-14 11:53:20 +02:00
Amruth Pillai d6e0d9ac61 switch to crowdin github action 2026-04-14 11:50:07 +02:00
Amruth Pillai c19b9746c8 📦 v5.0.18 - https://docs.rxresu.me/changelog (Passkeys Support) 2026-04-14 10:51:04 +02:00
Amruth Pillai 4ed6177aee New translations en-us.po (Dutch) (#2886)
[ci skip]
2026-04-09 20:46:17 +02:00
Amruth Pillai 5900de101c update peer dependencies 2026-04-09 16:05:14 +02:00
Vedant Shankar BhavsarandAmruth Pillai 61b3324941 fix: improve email handling and user lookup in OAuth configuration (#2874)
* refactor: improve email handling and user lookup in OAuth configuration

* refactor: enhance OAuth user mapping and improve email handling

---------

Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
2026-04-09 16:02:30 +02:00
Amruth Pillai 0e858c5967 update webfontlist.json 2026-04-09 15:15:08 +02:00
russandClaude 8c968e92f4 fix: correct Computer Modern Sans italic font file mappings (#2881)
The 400italic and 700italic variants for Computer Modern Sans pointed
to incorrect filenames. `cmunsl.woff` does not exist in the upstream
bitmaks/cm-web-fonts repository (returns 404 from jsDelivr CDN).

This causes PDF export to fail with "Waiting failed: 5000ms exceeded"
because Puppeteer's waitForFonts stalls on the 404, preventing the
page from signalling data-wf-loaded="true" within the timeout.

Correct mapping verified against the upstream @font-face declarations
in bitmaks/cm-web-fonts font/Sans/cmun-sans.css:

  - 400 normal:  cmunss.woff (unchanged)
  - 400 italic:  cmunsi.woff (was cmunsl.woff, which does not exist)
  - 700 normal:  cmunsx.woff (unchanged)
  - 700 italic:  cmunso.woff (was cmunsi.woff, which is the 400 italic)

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-09 15:12:30 +02:00
Amruth Pillai bea8ff1beb Fix MCP tool names for Claude Desktop incompatibility (#2885)
* fixes #2884, rename tool names for claude to work

* update dependencies
2026-04-09 15:03:18 +02:00
Amruth Pillai 85e0b0a96d further improvements to the mcp server 2026-04-09 10:06:50 +02:00
Amruth Pillai 1b266ba7ac Implement Resume Analysis (#2882)
* Implement Resume Analysis

* 📦 v5.0.17 - https://docs.rxresu.me/changelog
2026-04-09 09:04:27 +02:00
Amruth Pillai 1810dc8b07 better mcp server 2026-04-09 00:28:31 +02:00
Amruth Pillai 06b9da39ed Sync Translations from Crowdin (#2864)
* New translations en-us.po (Romanian)
[ci skip]

* New translations en-us.po (French)
[ci skip]

* New translations en-us.po (Spanish)
[ci skip]

* New translations en-us.po (Afrikaans)
[ci skip]

* New translations en-us.po (Arabic)
[ci skip]

* New translations en-us.po (Bulgarian)
[ci skip]

* New translations en-us.po (Catalan)
[ci skip]

* New translations en-us.po (Czech)
[ci skip]

* New translations en-us.po (Danish)
[ci skip]

* New translations en-us.po (German)
[ci skip]

* New translations en-us.po (Greek)
[ci skip]

* New translations en-us.po (Finnish)
[ci skip]

* New translations en-us.po (Hebrew)
[ci skip]

* New translations en-us.po (Hungarian)
[ci skip]

* New translations en-us.po (Italian)
[ci skip]

* New translations en-us.po (Japanese)
[ci skip]

* New translations en-us.po (Korean)
[ci skip]

* New translations en-us.po (Lithuanian)
[ci skip]

* New translations en-us.po (Dutch)
[ci skip]

* New translations en-us.po (Norwegian)
[ci skip]

* New translations en-us.po (Polish)
[ci skip]

* New translations en-us.po (Portuguese)
[ci skip]

* New translations en-us.po (Russian)
[ci skip]

* New translations en-us.po (Slovak)
[ci skip]

* New translations en-us.po (Slovenian)
[ci skip]

* New translations en-us.po (Albanian)
[ci skip]

* New translations en-us.po (Serbian (Cyrillic))
[ci skip]

* New translations en-us.po (Swedish)
[ci skip]

* New translations en-us.po (Turkish)
[ci skip]

* New translations en-us.po (Ukrainian)
[ci skip]

* New translations en-us.po (Chinese Simplified)
[ci skip]

* New translations en-us.po (Chinese Traditional)
[ci skip]

* New translations en-us.po (Vietnamese)
[ci skip]

* New translations en-us.po (Portuguese, Brazilian)
[ci skip]

* New translations en-us.po (Indonesian)
[ci skip]

* New translations en-us.po (Persian)
[ci skip]

* New translations en-us.po (Khmer)
[ci skip]

* New translations en-us.po (Tamil)
[ci skip]

* New translations en-us.po (Bengali)
[ci skip]

* New translations en-us.po (Marathi)
[ci skip]

* New translations en-us.po (Thai)
[ci skip]

* New translations en-us.po (Latvian)
[ci skip]

* New translations en-us.po (Azerbaijani)
[ci skip]

* New translations en-us.po (Hindi)
[ci skip]

* New translations en-us.po (Malay)
[ci skip]

* New translations en-us.po (Telugu)
[ci skip]

* New translations en-us.po (English, United Kingdom)
[ci skip]

* New translations en-us.po (Malayalam)
[ci skip]

* New translations en-us.po (Uzbek)
[ci skip]

* New translations en-us.po (Kannada)
[ci skip]

* New translations en-us.po (Odia)
[ci skip]

* New translations en-us.po (Amharic)
[ci skip]

* New translations en-us.po (Nepali)
[ci skip]

* New translations en-us.po (Swedish)
[ci skip]
2026-04-05 09:50:40 +02:00
Amruth Pillai 11373763fd fixes #2868 2026-04-05 09:03:49 +02:00
Amruth Pillai b3224ce59b Merge branch 'main' of github.com:amruthpillai/reactive-resume 2026-04-04 20:59:38 +02:00
Amruth Pillai 145b17de0f fix #2865, spacing between section items messed up 2026-04-04 20:59:30 +02:00
Amruth Pillai 623c5f6f81 Downgrade Cosign installer version to v3 2026-04-04 12:53:26 +02:00
Amruth Pillai bcbe70d231 📦 v5.0.16 - https://docs.rxresu.me/changelog 2026-04-04 12:31:24 +02:00
Amruth Pillai 8b52b9d8fc update translations 2026-04-04 12:29:10 +02:00
Amruth Pillai 4ecc66d081 Update title/metadata of generated PDF (#2863) 2026-04-04 12:28:32 +02:00
Amruth Pillai 10d58175f1 refactor: update layout of resume items for better alignment and spacing 2026-04-04 12:21:05 +02:00
Amruth Pillai 923f5f6173 fixes #2733: Bug where date range is displayed on separate line (#2862) 2026-04-04 12:03:09 +02:00
Amruth Pillai 9332e1e3ff chore: update GitHub Actions workflows to use Node 24 and upgrade action versions 2026-04-02 00:28:36 +02:00
AaronJoel Dev 77f31fa127 fix: prevent desktop horizontal overflow in home hero (#2854) 2026-04-02 00:16:16 +02:00
Amruth Pillai 4fd43657dc 📦 v5.0.15 - https://docs.rxresu.me/changelog 2026-04-02 00:14:54 +02:00
Aman Gupta d9a24448e8 fix: refactor useCSSVariables to ensure valid highest font weights (#2852) 2026-03-31 22:41:24 +02:00
Amruth Pillai 69ff78b656 Sync Translations from Crowdin (#2848)
* New translations en-us.po (Romanian)
[ci skip]

* New translations en-us.po (French)
[ci skip]

* New translations en-us.po (Spanish)
[ci skip]

* New translations en-us.po (Afrikaans)
[ci skip]

* New translations en-us.po (Arabic)
[ci skip]

* New translations en-us.po (Bulgarian)
[ci skip]

* New translations en-us.po (Catalan)
[ci skip]

* New translations en-us.po (Czech)
[ci skip]

* New translations en-us.po (Danish)
[ci skip]

* New translations en-us.po (German)
[ci skip]

* New translations en-us.po (Greek)
[ci skip]

* New translations en-us.po (Finnish)
[ci skip]

* New translations en-us.po (Hebrew)
[ci skip]

* New translations en-us.po (Hungarian)
[ci skip]

* New translations en-us.po (Italian)
[ci skip]

* New translations en-us.po (Japanese)
[ci skip]

* New translations en-us.po (Korean)
[ci skip]

* New translations en-us.po (Lithuanian)
[ci skip]

* New translations en-us.po (Dutch)
[ci skip]

* New translations en-us.po (Norwegian)
[ci skip]

* New translations en-us.po (Polish)
[ci skip]

* New translations en-us.po (Portuguese)
[ci skip]

* New translations en-us.po (Russian)
[ci skip]

* New translations en-us.po (Slovak)
[ci skip]

* New translations en-us.po (Slovenian)
[ci skip]

* New translations en-us.po (Albanian)
[ci skip]

* New translations en-us.po (Serbian (Cyrillic))
[ci skip]

* New translations en-us.po (Swedish)
[ci skip]

* New translations en-us.po (Turkish)
[ci skip]

* New translations en-us.po (Ukrainian)
[ci skip]

* New translations en-us.po (Chinese Simplified)
[ci skip]

* New translations en-us.po (Chinese Traditional)
[ci skip]

* New translations en-us.po (Vietnamese)
[ci skip]

* New translations en-us.po (Portuguese, Brazilian)
[ci skip]

* New translations en-us.po (Indonesian)
[ci skip]

* New translations en-us.po (Persian)
[ci skip]

* New translations en-us.po (Khmer)
[ci skip]

* New translations en-us.po (Tamil)
[ci skip]

* New translations en-us.po (Bengali)
[ci skip]

* New translations en-us.po (Marathi)
[ci skip]

* New translations en-us.po (Thai)
[ci skip]

* New translations en-us.po (Latvian)
[ci skip]

* New translations en-us.po (Azerbaijani)
[ci skip]

* New translations en-us.po (Hindi)
[ci skip]

* New translations en-us.po (Malay)
[ci skip]

* New translations en-us.po (Telugu)
[ci skip]

* New translations en-us.po (English, United Kingdom)
[ci skip]

* New translations en-us.po (Malayalam)
[ci skip]

* New translations en-us.po (Uzbek)
[ci skip]

* New translations en-us.po (Kannada)
[ci skip]

* New translations en-us.po (Odia)
[ci skip]

* New translations en-us.po (Amharic)
[ci skip]

* New translations en-us.po (Nepali)
[ci skip]
2026-03-29 23:34:21 +02:00
Amruth Pillai 0794b5c955 update dependencies 2026-03-29 23:09:25 +02:00
Amruth Pillai 5c986cc68d Sync Translations from Crowdin (#2838)
* New translations en-us.po (Romanian)
[ci skip]

* New translations en-us.po (French)
[ci skip]

* New translations en-us.po (Spanish)
[ci skip]

* New translations en-us.po (Afrikaans)
[ci skip]

* New translations en-us.po (Arabic)
[ci skip]

* New translations en-us.po (Bulgarian)
[ci skip]

* New translations en-us.po (Catalan)
[ci skip]

* New translations en-us.po (Czech)
[ci skip]

* New translations en-us.po (Danish)
[ci skip]

* New translations en-us.po (German)
[ci skip]

* New translations en-us.po (Greek)
[ci skip]

* New translations en-us.po (Finnish)
[ci skip]

* New translations en-us.po (Hebrew)
[ci skip]

* New translations en-us.po (Hungarian)
[ci skip]

* New translations en-us.po (Italian)
[ci skip]

* New translations en-us.po (Japanese)
[ci skip]

* New translations en-us.po (Korean)
[ci skip]

* New translations en-us.po (Lithuanian)
[ci skip]

* New translations en-us.po (Dutch)
[ci skip]

* New translations en-us.po (Norwegian)
[ci skip]

* New translations en-us.po (Polish)
[ci skip]

* New translations en-us.po (Portuguese)
[ci skip]

* New translations en-us.po (Russian)
[ci skip]

* New translations en-us.po (Slovak)
[ci skip]

* New translations en-us.po (Slovenian)
[ci skip]

* New translations en-us.po (Albanian)
[ci skip]

* New translations en-us.po (Serbian (Cyrillic))
[ci skip]

* New translations en-us.po (Swedish)
[ci skip]

* New translations en-us.po (Turkish)
[ci skip]

* New translations en-us.po (Ukrainian)
[ci skip]

* New translations en-us.po (Chinese Simplified)
[ci skip]

* New translations en-us.po (Chinese Traditional)
[ci skip]

* New translations en-us.po (Vietnamese)
[ci skip]

* New translations en-us.po (Portuguese, Brazilian)
[ci skip]

* New translations en-us.po (Indonesian)
[ci skip]

* New translations en-us.po (Persian)
[ci skip]

* New translations en-us.po (Khmer)
[ci skip]

* New translations en-us.po (Tamil)
[ci skip]

* New translations en-us.po (Bengali)
[ci skip]

* New translations en-us.po (Marathi)
[ci skip]

* New translations en-us.po (Thai)
[ci skip]

* New translations en-us.po (Latvian)
[ci skip]

* New translations en-us.po (Azerbaijani)
[ci skip]

* New translations en-us.po (Hindi)
[ci skip]

* New translations en-us.po (Malay)
[ci skip]

* New translations en-us.po (Telugu)
[ci skip]

* New translations en-us.po (English, United Kingdom)
[ci skip]

* New translations en-us.po (Malayalam)
[ci skip]

* New translations en-us.po (Uzbek)
[ci skip]

* New translations en-us.po (Kannada)
[ci skip]

* New translations en-us.po (Odia)
[ci skip]

* New translations en-us.po (Amharic)
[ci skip]

* New translations en-us.po (Nepali)
[ci skip]

* New translations en-us.po (Russian)
[ci skip]
2026-03-29 23:07:48 +02:00
autofix-ci[bot] 688c002822 [autofix.ci] apply automated fixes 2026-03-24 10:58:09 +00:00
Amruth Pillai ced49d5314 Update Crowdin configuration file 2026-03-24 11:57:03 +01:00
Amruth Pillai 3653baad9b 📦 v5.0.14 - https://docs.rxresu.me/changelog 2026-03-24 11:22:45 +01:00
b4aaf9712f feat(mcp): add OAuth 2.1 for claude.ai MCP connector (#2829)
* feat(mcp): add OAuth 2.1 authentication for claude.ai MCP connector

Enable OAuth 2.1 (RFC 8414 + RFC 7591) for the MCP endpoint using
better-auth's MCP plugin. This allows claude.ai and other MCP clients
to authenticate via Dynamic Client Registration and Authorization Code
flow with PKCE, using the existing login page.

- Add `mcp()` plugin to better-auth config with login page redirect
- Add `.well-known/oauth-authorization-server` discovery endpoint
- Add `.well-known/oauth-protected-resource` metadata endpoint
- Update MCP handler to accept Bearer tokens via `getMcpSession`
- Retain `x-api-key` fallback for backward compatibility
- Return proper HTTP 401 + WWW-Authenticate header for unauthed requests
- Add `oauthApplication`, `oauthAccessToken`, `oauthConsent` tables

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(mcp): use typed AuthError and suppress noisy verifyApiKey throws

- Replace string-matching error detection with instanceof AuthError
- Wrap verifyApiKey in try-catch to avoid logging malformed key errors
- Move console.error below auth check so 401s don't pollute logs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(mcp): add database migration for OAuth tables

Creates oauth_application, oauth_access_token, and oauth_consent tables
required for MCP OAuth 2.1 Dynamic Client Registration flow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(mcp): resolve OAuth Bearer token auth for oRPC tool calls

The oRPC context only checked session cookies and API keys, causing
MCP tool calls from OAuth clients (claude.ai) to fail with Unauthorized
even though the MCP endpoint itself authenticated successfully.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(mcp): look up user by userId from OAuth access token

getMcpSession returns OAuthAccessToken (with userId), not a session
object with a user property. Must query the user table by userId.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(mcp): migrate from deprecated mcp() plugin to @better-auth/oauth-provider

The better-auth MCP plugin is marked for deprecation in favor of the
OAuth Provider plugin. This refactors the entire OAuth 2.1 flow to use
@better-auth/oauth-provider with JWT-based token verification, replacing
the opaque token lookup via getMcpSession().

Key changes:
- Replace mcp() with jwt() + oauthProvider() in auth config
- Replace getMcpSession() with verifyAccessToken() (JWT/JWKS)
- Replace oauthApplication table with oauthClient (RFC 7591 compliant)
- Add oauthRefreshToken table and jwks table for JWT signing keys
- Extract shared authBaseUrl and verifyOAuthToken helper
- Hoist McpServer to module scope (avoid per-request reconstruction)
- Update .well-known discovery endpoints for OAuth Provider

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(mcp): resolve OAuth 2.1 flow for claude.ai MCP connector

Multiple fixes required to make the full MCP OAuth flow work with
claude.ai's implementation:

- Add RFC 8414 discovery route at /.well-known/oauth-authorization-server/api/auth
  (claude.ai appends the issuer path per spec)
- Add /auth/oauth server route to handle login/consent flow
  (generates auth codes directly, bypassing h3 cookie issues)
- Default token_endpoint_auth_method to "none" via onRequest plugin hook
  (claude.ai omits this field, causing confidential client rejection)
- Strip prompt=consent from authorize requests via onRequest hook
  (better-auth checks prompt before skipConsent, causing redirect loops)
- Add validAudiences for MCP resource URL
  (JWT aud claim contains the MCP URL, not the base URL)
- Disable CSRF check for cross-origin OAuth flows
- Log token endpoint errors for debugging
- Set skipConsent on OAuth clients via /auth/oauth route

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(mcp): harden OAuth security and enforce lock on delete

- Scope CSRF bypass to OAuth2 paths only instead of disabling globally
- Validate redirect_uri against registered client URIs (prevents code interception)
- Use pathname matching instead of fragile url.includes() for route guards
- Replace biased modulo code generation with crypto.randomBytes
- Enforce resume lock check on delete (previously silently ignored)
- Remove debug console.error logging of OAuth token response bodies
- Use Response.json() consistently for MCP 401 response

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Update dependencies, refine ignore patterns, and enhance documentation

- Updated various dependencies in package.json and pnpm-lock.yaml for improved stability and features.
- Adjusted ignore patterns in knip.json to include specific component directories.
- Enhanced documentation for the MCP server, clarifying authentication methods and configuration options.
- Made minor adjustments to VSCode settings for better code organization.

* fix(mcp): resolve OAuth client registration and stale token handling

Claude.ai sends token_endpoint_auth_method: "client_secret_post" without
a client_secret during Dynamic Client Registration, causing Better Auth to
reject it as an unauthenticated confidential client. Force to "none" for
unauthenticated registrations.

Also catch JWKS verification errors (e.g. key rotation after redeployment)
so stale Bearer tokens return 401 instead of 200 with an error body,
allowing clients to re-initiate the OAuth flow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* reiterate on tests

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
2026-03-24 11:03:56 +01:00
Amruth Pillai e3274d7c95 New translations en-us.po (French) (#2834)
[ci skip]
2026-03-22 17:49:55 +01:00
Copilotamruthpillaicopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
a7a3d53dbd [WIP] Fix issue 2830 related to sidebar width slider (#2832)
* Initial plan

* fix: sidebar width slider not updating resume layout

Co-authored-by: amruthpillai <1134738+amruthpillai@users.noreply.github.com>
Agent-Logs-Url: https://github.com/amruthpillai/reactive-resume/sessions/55cb1943-2066-4871-8a12-0a0a4cb35fa6

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: amruthpillai <1134738+amruthpillai@users.noreply.github.com>
2026-03-21 08:24:42 +01:00
Amruth Pillai f8d776106f fixes #2826 2026-03-19 19:16:28 +01:00
Amruth Pillai e34f648455 remove input-otp component, upstream issues 2026-03-19 18:43:05 +01:00
Amruth Pillai f4e2d05f25 Merge branch 'main' of github.com:amruthpillai/reactive-resume 2026-03-19 12:55:46 +01:00
Amruth Pillai bbc17b8995 cleaner logging in dev and prod 2026-03-19 12:55:37 +01:00
Amruth Pillai a66bc13f4f Sync Translations from Crowdin (#2825)
* New translations en-us.po (Romanian)
[ci skip]

* New translations en-us.po (French)
[ci skip]

* New translations en-us.po (Spanish)
[ci skip]

* New translations en-us.po (Afrikaans)
[ci skip]

* New translations en-us.po (Arabic)
[ci skip]

* New translations en-us.po (Bulgarian)
[ci skip]

* New translations en-us.po (Catalan)
[ci skip]

* New translations en-us.po (Czech)
[ci skip]

* New translations en-us.po (Danish)
[ci skip]

* New translations en-us.po (German)
[ci skip]

* New translations en-us.po (Greek)
[ci skip]

* New translations en-us.po (Finnish)
[ci skip]

* New translations en-us.po (Hebrew)
[ci skip]

* New translations en-us.po (Hungarian)
[ci skip]

* New translations en-us.po (Italian)
[ci skip]

* New translations en-us.po (Japanese)
[ci skip]

* New translations en-us.po (Korean)
[ci skip]

* New translations en-us.po (Lithuanian)
[ci skip]

* New translations en-us.po (Dutch)
[ci skip]

* New translations en-us.po (Norwegian)
[ci skip]

* New translations en-us.po (Polish)
[ci skip]

* New translations en-us.po (Portuguese)
[ci skip]

* New translations en-us.po (Russian)
[ci skip]

* New translations en-us.po (Slovak)
[ci skip]

* New translations en-us.po (Slovenian)
[ci skip]

* New translations en-us.po (Albanian)
[ci skip]

* New translations en-us.po (Serbian (Cyrillic))
[ci skip]

* New translations en-us.po (Swedish)
[ci skip]

* New translations en-us.po (Turkish)
[ci skip]

* New translations en-us.po (Ukrainian)
[ci skip]

* New translations en-us.po (Chinese Simplified)
[ci skip]

* New translations en-us.po (Chinese Traditional)
[ci skip]

* New translations en-us.po (Vietnamese)
[ci skip]

* New translations en-us.po (Portuguese, Brazilian)
[ci skip]

* New translations en-us.po (Indonesian)
[ci skip]

* New translations en-us.po (Persian)
[ci skip]

* New translations en-us.po (Khmer)
[ci skip]

* New translations en-us.po (Tamil)
[ci skip]

* New translations en-us.po (Bengali)
[ci skip]

* New translations en-us.po (Marathi)
[ci skip]

* New translations en-us.po (Thai)
[ci skip]

* New translations en-us.po (Latvian)
[ci skip]

* New translations en-us.po (Azerbaijani)
[ci skip]

* New translations en-us.po (Hindi)
[ci skip]

* New translations en-us.po (Malay)
[ci skip]

* New translations en-us.po (Telugu)
[ci skip]

* New translations en-us.po (English, United Kingdom)
[ci skip]

* New translations en-us.po (Malayalam)
[ci skip]

* New translations en-us.po (Uzbek)
[ci skip]

* New translations en-us.po (Kannada)
[ci skip]

* New translations en-us.po (Odia)
[ci skip]

* New translations en-us.po (Amharic)
[ci skip]

* New translations en-us.po (Nepali)
[ci skip]
2026-03-19 10:23:34 +01:00
Amruth Pillai 7858efbd2b remove prepare script 2026-03-19 10:22:37 +01:00
Amruth Pillai 7da5e2c0c2 update schema.json and openapi/spec.json 2026-03-19 10:07:19 +01:00
Amruth Pillai 177c550a0c fix changelog formatting 2026-03-19 10:03:15 +01:00
371 changed files with 99233 additions and 29062 deletions
+14 -1
View File
@@ -10,7 +10,7 @@ APP_URL="http://localhost:3000"
PRINTER_APP_URL="http://host.docker.internal:3000"
# --- Printer ---
PRINTER_ENDPOINT="ws://localhost:4000?token=1234567890"
PRINTER_ENDPOINT="ws://localhost:4000?token=change-me"
# --- Database (PostgreSQL) ---
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"
@@ -30,12 +30,21 @@ GOOGLE_CLIENT_SECRET=""
GITHUB_CLIENT_ID=""
GITHUB_CLIENT_SECRET=""
# Social Auth (LinkedIn, optional)
LINKEDIN_CLIENT_ID=""
LINKEDIN_CLIENT_SECRET=""
# Custom OAuth Provider (optional)
OAUTH_PROVIDER_NAME=""
OAUTH_CLIENT_ID=""
OAUTH_CLIENT_SECRET=""
OAUTH_DISCOVERY_URL=""
OAUTH_AUTHORIZATION_URL=""
OAUTH_DYNAMIC_CLIENT_REDIRECT_HOSTS=""
# AI provider base URL allowlist (optional, comma-separated hosts/origins)
# Example: api.openai.com,https://gateway.ai.vercel.com
AI_ALLOWED_BASE_URLS=""
# --- Email (optional) ---
# If all keys are disabled, the app logs the email to be sent to the console instead.
@@ -78,3 +87,7 @@ FLAG_DISABLE_IMAGE_PROCESSING="false"
# This is not used within Reactive Resume, but in src/scripts/fonts/generate.ts to generate a list of fonts served by Google Fonts.
# Note: Make sure "Google Fonts Developer API" is unrestricted.
GOOGLE_CLOUD_API_KEY=""
# --- Crowdin ---
CROWDIN_PROJECT_ID=""
CROWDIN_API_TOKEN=""
+16 -6
View File
@@ -8,21 +8,31 @@ on:
permissions:
contents: read
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
autofix:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Checkout Repository
uses: actions/checkout@v6
- uses: voidzero-dev/setup-vp@v1
- name: Setup Vite+
uses: voidzero-dev/setup-vp@v1
with:
node-version: "24"
cache: true
- run: vp install
- name: Install Dependencies
run: vp install --frozen-lockfile
- run: vp fmt
- run: vp lint --fix
- name: Run Tests
run: vp test
- uses: autofix-ci/action@7a166d7532b277f34e16238930461bf77f9d7ed8
- name: Run Lint and Format
run: vp check --fix
- name: Autofix
uses: autofix-ci/action@7a166d7532b277f34e16238930461bf77f9d7ed8
+36
View File
@@ -0,0 +1,36 @@
name: Sync Translations from Crowdin
on:
push:
branches: ["main"]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
crowdin-sync:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Sync Translations from Crowdin
uses: crowdin/github-action@v2
with:
download_translations: true
localization_branch_name: "l10n"
commit_message: "[skip ci] chore(i18n): sync translations from crowdin"
pull_request_title: "Sync Translations from Crowdin"
pull_request_body: "This is an automated pull request to sync translations from [Crowdin](https://crowdin.com/project/reactive-resume)."
pull_request_labels: "l10n"
pull_request_reviewers: "amruthpillai"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
+12 -11
View File
@@ -9,6 +9,7 @@ concurrency:
env:
IMAGE: ${{ github.repository }}
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
build:
@@ -41,16 +42,16 @@ jobs:
run: echo "version=$(jq -r .version package.json)" >> "$GITHUB_OUTPUT"
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Login to Docker Hub
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -58,7 +59,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: |
ghcr.io/${{ env.IMAGE }}
@@ -68,7 +69,7 @@ jobs:
- name: Build and Push by Digest
id: build
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
context: .
sbom: true
@@ -88,7 +89,7 @@ jobs:
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: digests-${{ matrix.arch }}
path: /tmp/digests/*
@@ -118,23 +119,23 @@ jobs:
run: echo "version=$(jq -r .version package.json)" >> "$GITHUB_OUTPUT"
- name: Download digests
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Login to Docker Hub
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -152,7 +153,7 @@ jobs:
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: |
ghcr.io/${{ env.IMAGE }}
+2
View File
@@ -2,9 +2,11 @@ dist
.env*
/data
.nitro
.agents
.output
.vercel
.cursor
.claude
TODO.md
coverage
.netlify
+4 -5
View File
@@ -1,11 +1,8 @@
{
"[typescript]": {
"editor.defaultFormatter": "oxc.oxc-vscode"
},
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit"
},
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true,
"editor.formatOnSaveMode": "file",
"files.associations": {
@@ -25,6 +22,7 @@
"i18n-ally.sourceLanguage": "en-US",
"oxc.enable.oxfmt": true,
"oxc.enable.oxlint": true,
"oxc.configPath": "./vite.config.ts",
"oxc.fmt.configPath": "./vite.config.ts",
"search.exclude": {
"**/routeTree.gen.ts": true,
@@ -38,5 +36,6 @@
["cn\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
],
"tailwindCSS.experimental.configFile": "src/styles/globals.css",
"typescript.experimental.useTsgo": true
"typescript.experimental.useTsgo": true,
"i18n-ally.keystyle": "nested"
}
+96 -91
View File
@@ -1,12 +1,69 @@
# AGENTS.md
## Overview
## Cursor Cloud specific instructions
Reactive Resume is a single-package full-stack TypeScript app (not a monorepo) built with [TanStack Start](https://tanstack.com/start/latest/docs/framework/react/overview) (React, Vite, Nitro). It serves both frontend and API on port 3000.
### Overview
This project uses [Vite+](https://vite.dev/blog/announcing-viteplus), a unified toolchain built on top of Vite, Rolldown, Vitest, tsdown, Oxlint, Oxfmt, and Vite Task. Vite+ wraps runtime management, package management, and frontend tooling in a single global CLI called `vp`. All modules should be imported from the `vite-plus` dependency (e.g., `import { defineConfig } from 'vite-plus'` or `import { expect, test, vi } from 'vite-plus/test'`).
Reactive Resume is a single-package full-stack TypeScript app (not a monorepo) built with TanStack Start (React 19, Vite, Nitro). It serves both frontend and API on port 3000.
## Key Libraries
### Infrastructure services
| Area | Library | Docs |
| -------------------- | ------------------------------------------------------------------------ | ---------------------------------- |
| Frontend framework | React | https://react.dev |
| Full-stack framework | TanStack Start | https://tanstack.com/start/latest |
| Router | TanStack React Router | https://tanstack.com/router/latest |
| Server state | TanStack React Query | https://tanstack.com/query/latest |
| Client state | Zustand (+ Zundo for undo/redo, Immer for immutable updates) | https://zustand.docs.pmnd.rs |
| Type-safe API | oRPC | https://orpc.unnoq.com |
| Database ORM | Drizzle ORM (PostgreSQL) | https://orm.drizzle.team |
| Authentication | Better Auth (+ Drizzle adapter, OAuth provider, API keys, 2FA, Passkeys) | https://www.better-auth.com |
| Styling | Tailwind CSS | https://tailwindcss.com |
| UI Components | shadcn/ui (built on Base UI) | https://ui.shadcn.com |
| Icons | Phosphor Icons | https://phosphoricons.com |
| Forms | React Hook Form (+ Zod resolvers) | https://react-hook-form.com |
| Rich text editor | Tiptap | https://tiptap.dev |
| Validation | Zod | https://zod.dev |
| AI | Vercel AI SDK (OpenAI, Anthropic, Google, Ollama providers) | https://ai-sdk.dev |
| MCP | Model Context Protocol SDK | https://modelcontextprotocol.io |
| i18n | Lingui | https://lingui.dev |
| Animations | Motion (Framer Motion) | https://motion.dev |
| PDF export | Puppeteer Core (via Browserless) | https://pptr.dev |
| Drag and drop | dnd-kit | https://dndkit.com |
| Server engine | Nitro | https://nitro.build |
| PWA | Vite PWA Plugin | https://vite-pwa-org.netlify.app |
| Unused deps | Knip | https://knip.dev |
## Project Structure
```
src/
components/ UI, resume, layout, animation, theme, locale components
routes/ File-based routing (TanStack React Router)
integrations/ Feature modules (auth, drizzle, orpc, ai, email, jobs, mcp, storage)
schema/ Zod schemas for resume data validation
utils/ Utility functions (locale, theme, env, resume processing)
dialogs/ Modal/dialog components
hooks/ Custom React hooks
styles/ CSS and Tailwind configuration
stores/ Zustand stores (resume, AI, dialog, command palette)
migrations/ Drizzle database migrations
locales/ Lingui i18n message catalogs (47+ locales)
```
### Key Config Files
- `vite.config.ts` — Vite + Nitro + TanStack Start + PWA + Tailwind + Lingui
- `drizzle.config.ts` — PostgreSQL dialect, schema at `./src/integrations/drizzle/schema.ts`
- `tsconfig.json` — ES2022, strict mode, path alias `@/*` → `./src/*`
- `lingui.config.ts` — i18n extraction and locale configuration
- `components.json` — shadcn CLI configuration
### API Architecture
- **oRPC API** (`/api/rpc/*`) — Type-safe RPC with routers for: `ai`, `auth`, `resume`, `storage`, `printer`, `jobs`, `statistics`, `flags`. Three procedure types: `publicProcedure`, `protectedProcedure`, `serverOnlyProcedure`.
- **Better Auth API** (`/api/auth/*`) — OAuth, session management, social provider callbacks.
- **MCP Server** (`/mcp/`) — Model Context Protocol with OAuth Bearer tokens and API key auth. Exposes resumes as resources and tools for resume CRUD.
## Infrastructure Services
Before running the dev server, Docker must be running with at least PostgreSQL. Start services via `compose.dev.yml`:
@@ -18,7 +75,7 @@ sudo docker compose -f compose.dev.yml up -d postgres browserless
- **PostgreSQL** (port 5432) — required. The app auto-runs Drizzle migrations on startup via a Nitro plugin.
- **Browserless** (port 4000) — required for PDF export. Maps container port 3000 to host port 4000.
### Environment variables
## Environment Variables
Copy `.env.example` to `.env` if not present. Key notes for local dev:
@@ -28,99 +85,47 @@ Copy `.env.example` to `.env` if not present. Key notes for local dev:
- `DATABASE_URL` — PostgreSQL connection using `postgres:postgres` credentials on localhost:5432.
- S3/Storage and SMTP vars can be left empty — the app falls back to local filesystem and console-logged emails.
### Common commands
## Common Commands
See `scripts` in `package.json`. Key ones:
`vp` is the global CLI for Vite+. Do not use pnpm/npm/yarn directly — Vite+ wraps the underlying package manager.
| Task | Command |
| -------------- | --------------------------------------------------------------- |
| Dev server | `pnpm dev` (port 3000) |
| Lint (Oxlint) | `pnpm lint` |
| Format (Oxfmt) | `pnpm fmt` |
| Typecheck | `pnpm typecheck` |
| DB migrations | `pnpm db:generate` / `pnpm db:migrate` (auto-runs on dev start) |
| Task | Command |
| ----------------------------- | --------------------------------------------------------------- |
| Install dependencies | `vp install` |
| Dev server (port 3000) | `vp dev` |
| Lint (Oxlint, type-aware) | `vp lint --type-aware` |
| Format (Oxfmt) | `vp fmt` |
| Check (lint + format + types) | `vp check` |
| Typecheck | `pnpm typecheck` (uses tsgo) |
| Run tests | `vp test` |
| DB migrations | `pnpm db:generate` / `pnpm db:migrate` (auto-runs on dev start) |
| DB studio | `pnpm db:studio` |
| i18n extraction | `pnpm lingui:extract` |
| Add a dependency | `vp add <package>` |
| Remove a dependency | `vp remove <package>` |
| One-off binary | `vp dlx <package>` |
| Build for production | `vp build` |
| Preview production build | `vp preview` |
| Start production server | `pnpm start` |
### Gotchas
## Vite+ Pitfalls
- **Do not use pnpm/npm/yarn directly** for package operations — use `vp add`, `vp remove`, `vp install`, etc.
- **Do not run `vp vitest` or `vp oxlint`** — they don't exist. Use `vp test` and `vp lint`.
- **Do not install Vitest, Oxlint, Oxfmt, or tsdown directly** — Vite+ bundles them.
- **Import from `vite-plus`**, not from `vite` or `vitest` directly (e.g., `import { defineConfig } from 'vite-plus'`).
- **Vite+ commands take precedence** over `package.json` scripts. If there's a naming conflict, use `vp run <script>`.
- **Use `vp dlx`** instead of `npx` or `pnpm dlx`.
- **Type-aware linting** works out of the box with `vp lint --type-aware` — no need to install `oxlint-tsgolint`.
## Gotchas
- The Docker daemon needs `fuse-overlayfs` storage driver and `iptables-legacy` in the cloud VM (nested container environment).
- `pnpm.onlyBuiltDependencies` in `package.json` controls which packages are allowed to run install scripts — no interactive `pnpm approve-builds` needed.
- Email verification is optional in dev — after signup, click "Continue" to skip.
- Vite 8 is beta (`^8.0.0-beta.15`); Nitro uses a nightly build. Occasional upstream issues may occur.
<!--VITE PLUS START-->
# Using Vite+, the Unified Toolchain for the Web
This project is using Vite+, a unified toolchain built on top of Vite, Rolldown, Vitest, tsdown, Oxlint, Oxfmt, and Vite Task. Vite+ wraps runtime management, package management, and frontend tooling in a single global CLI called `vp`. Vite+ is distinct from Vite, but it invokes Vite through `vp dev` and `vp build`.
## Vite+ Workflow
`vp` is a global binary that handles the full development lifecycle. Run `vp help` to print a list of commands and `vp <command> --help` for information about a specific command.
### Start
- create - Create a new project from a template
- migrate - Migrate an existing project to Vite+
- config - Configure hooks and agent integration
- staged - Run linters on staged files
- install (`i`) - Install dependencies
- env - Manage Node.js versions
### Develop
- dev - Run the development server
- check - Run format, lint, and TypeScript type checks
- lint - Lint code
- fmt - Format code
- test - Run tests
### Execute
- run - Run monorepo tasks
- exec - Execute a command from local `node_modules/.bin`
- dlx - Execute a package binary without installing it as a dependency
- cache - Manage the task cache
### Build
- build - Build for production
- pack - Build libraries
- preview - Preview production build
### Manage Dependencies
Vite+ automatically detects and wraps the underlying package manager such as pnpm, npm, or Yarn through the `packageManager` field in `package.json` or package manager-specific lockfiles.
- add - Add packages to dependencies
- remove (`rm`, `un`, `uninstall`) - Remove packages from dependencies
- update (`up`) - Update packages to latest versions
- dedupe - Deduplicate dependencies
- outdated - Check for outdated packages
- list (`ls`) - List installed packages
- why (`explain`) - Show why a package is installed
- info (`view`, `show`) - View package information from the registry
- link (`ln`) / unlink - Manage local package links
- pm - Forward a command to the package manager
### Maintain
- upgrade - Update `vp` itself to the latest version
These commands map to their corresponding tools. For example, `vp dev --port 3000` runs Vite's dev server and works the same as Vite. `vp test` runs JavaScript tests through the bundled Vitest. The version of all tools can be checked using `vp --version`. This is useful when researching documentation, features, and bugs.
## Common Pitfalls
- **Using the package manager directly:** Do not use pnpm, npm, or Yarn directly. Vite+ can handle all package manager operations.
- **Always use Vite commands to run tools:** Don't attempt to run `vp vitest` or `vp oxlint`. They do not exist. Use `vp test` and `vp lint` instead.
- **Running scripts:** Vite+ commands take precedence over `package.json` scripts. If there is a `test` script defined in `scripts` that conflicts with the built-in `vp test` command, run it using `vp run test`.
- **Do not install Vitest, Oxlint, Oxfmt, or tsdown directly:** Vite+ wraps these tools. They must not be installed directly. You cannot upgrade these tools by installing their latest versions. Always use Vite+ commands.
- **Use Vite+ wrappers for one-off binaries:** Use `vp dlx` instead of package-manager-specific `dlx`/`npx` commands.
- **Import JavaScript modules from `vite-plus`:** Instead of importing from `vite` or `vitest`, all modules should be imported from the project's `vite-plus` dependency. For example, `import { defineConfig } from 'vite-plus';` or `import { expect, test, vi } from 'vite-plus/test';`. You must not install `vitest` to import test utilities.
- **Type-Aware Linting:** There is no need to install `oxlint-tsgolint`, `vp lint --type-aware` works out of the box.
- Vite and Nitro use beta/nightly builds. Occasional upstream issues may occur.
## Review Checklist for Agents
- [ ] Run `vp install` after pulling remote changes and before getting started.
- [ ] Run `vp check` and `vp test` to validate changes.
<!--VITE PLUS END-->
- [ ] Run `pnpm lint:fix`, `pnpm format:fix`, `pnpm typecheck` and `vp test` to validate changes.
Symlink
+1
View File
@@ -0,0 +1 @@
AGENTS.md
+21
View File
@@ -0,0 +1,21 @@
# syntax=docker/dockerfile:1
# ---------- Development Layer ----------
FROM node:24-slim AS development
ENV NODE_ENV="development"
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
pnpm install --frozen-lockfile
EXPOSE 3000
CMD ["pnpm", "run", "dev"]
+4
View File
@@ -126,6 +126,10 @@ Built with privacy as a core principle, Reactive Resume gives you complete owner
<img src="public/templates/jpg/ditgar.jpg" alt="Ditgar" width="150" />
<br /><sub><b>Ditgar</b></sub>
</td>
<td align="center">
<img src="public/templates/jpg/meowth.jpg" alt="Meowth" width="150" />
<br /><sub><b>Meowth</b></sub>
</td>
</tr>
</table>
+34
View File
@@ -0,0 +1,34 @@
# Security Policy
## Supported Versions
Reactive Resume provides security updates for version `5.0.x` and newer.
Earlier major versions are no longer supported.
| Version | Supported |
| ------- | ------------------ |
| >= 5.0 | :white_check_mark: |
| 4.x | :x: |
| < 4.0 | :x: |
## Reporting a Vulnerability
If you believe you have found a security vulnerability, please report it
privately instead of opening a public issue.
Preferred reporting channels:
- Use GitHub's private vulnerability reporting for this repository.
- If that is unavailable, email `hello@amruthpillai.com` with the subject
`Security Vulnerability: Reactive Resume`.
Please include as much detail as possible, including:
- A description of the vulnerability and its impact.
- Steps to reproduce or a proof of concept.
- Affected versions, configuration, or deployment details.
- Any relevant logs, screenshots, or links.
You can expect an initial response within 7 days. If the report is accepted,
we will coordinate remediation and disclosure timing with you before publishing
details publicly.
-3
View File
@@ -19,8 +19,5 @@
"ui": "@/components/ui",
"lib": "@/utils",
"hooks": "@/hooks"
},
"registries": {
"@animate-ui": "https://animate-ui.com/r/{name}.json"
}
}
+65 -12
View File
@@ -20,32 +20,34 @@ services:
image: postgres:latest
restart: unless-stopped
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- postgres_data:/var/lib/postgresql
ports:
- "5432:5432"
healthcheck:
test: ["CMD", "pg_isready", "-U", "postgres", "-d", "postgres"]
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
start_period: 10s
interval: 30s
timeout: 10s
retries: 3
browserless:
image: ghcr.io/browserless/chromium:latest
image: ${BROWSERLESS_IMAGE:-ghcr.io/browserless/chromium:latest}
restart: unless-stopped
ports:
- "4000:3000"
environment:
- QUEUED=10
- HEALTH=true
- CONCURRENT=5
- TOKEN=1234567890
QUEUED: 10
HEALTH: "true"
CONCURRENT: 5
TOKEN: ${BROWSERLESS_TOKEN:-change-me}
extra_hosts:
- "host.docker.internal:host-gateway"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/pressure?token=1234567890"]
test: ["CMD-SHELL", 'curl -f "http://localhost:3000/pressure?token=${BROWSERLESS_TOKEN:-change-me}"']
interval: 10s
timeout: 5s
retries: 10
@@ -57,14 +59,16 @@ services:
restart: unless-stopped
ports:
- "9222:9222"
extra_hosts:
- "host.docker.internal:host-gateway"
seaweedfs:
image: chrislusf/seaweedfs:latest
restart: unless-stopped
command: server -s3 -filer -dir=/data -ip=0.0.0.0
environment:
- AWS_ACCESS_KEY_ID=seaweedfs
- AWS_SECRET_ACCESS_KEY=seaweedfs
AWS_ACCESS_KEY_ID: seaweedfs
AWS_SECRET_ACCESS_KEY: seaweedfs
volumes:
- seaweedfs_data:/data
ports:
@@ -106,7 +110,56 @@ services:
timeout: 10s
retries: 3
web_app:
profiles: ["app"]
build:
context: .
dockerfile: Dockerfile.dev
restart: unless-stopped
ports:
- "3000:3000"
env_file:
- .env
environment:
PRINTER_APP_URL: http://web_app:3000
PRINTER_ENDPOINT: ws://browserless:3000?token=${BROWSERLESS_TOKEN:-change-me}
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/postgres
SMTP_HOST: mailpit
S3_ENDPOINT: http://seaweedfs:8333
CHOKIDAR_INTERVAL: 100
WATCHPACK_POLLING: "true"
CHOKIDAR_USEPOLLING: "true"
volumes:
- ./:/app
- web_app_data:/app/data
- web_app_pnpm_store:/pnpm/store
- web_app_nitro_cache:/app/.nitro
- web_app_tanstack_cache:/app/.tanstack
- web_app_node_modules:/app/node_modules
depends_on:
postgres:
condition: service_healthy
browserless:
condition: service_healthy
seaweedfs:
condition: service_healthy
seaweedfs_create_bucket:
condition: service_completed_successfully
extra_hosts:
- "host.docker.internal:host-gateway"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
start_period: 10s
interval: 30s
timeout: 10s
retries: 3
volumes:
mailpit_data:
postgres_data:
seaweedfs_data:
web_app_data:
web_app_pnpm_store:
web_app_nitro_cache:
web_app_node_modules:
web_app_tanstack_cache:
+32 -8
View File
@@ -4,6 +4,8 @@ services:
postgres:
image: postgres:latest
restart: unless-stopped
networks:
- data_network
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=postgres
@@ -18,15 +20,17 @@ services:
retries: 3
browserless:
image: ghcr.io/browserless/chromium:latest
image: ${BROWSERLESS_IMAGE:-ghcr.io/browserless/chromium:latest}
restart: unless-stopped
networks:
- printer_network
environment:
- QUEUED=10
- HEALTH=true
- CONCURRENT=5
- TOKEN=1234567890
- TOKEN=${BROWSERLESS_TOKEN:-change-me}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/pressure?token=1234567890"]
test: ["CMD-SHELL", 'curl -f "http://localhost:3000/pressure?token=${BROWSERLESS_TOKEN:-change-me}"']
interval: 10s
timeout: 5s
retries: 10
@@ -36,6 +40,8 @@ services:
# chrome:
# image: chromedp/headless-shell:latest
# restart: unless-stopped
# networks:
# - printer_network
# ports:
# - "9222:9222"
@@ -43,6 +49,8 @@ services:
image: chrislusf/seaweedfs:latest
restart: unless-stopped
command: server -s3 -filer -dir=/data -ip=0.0.0.0
networks:
- storage_network
environment:
- AWS_ACCESS_KEY_ID=seaweedfs
- AWS_SECRET_ACCESS_KEY=seaweedfs
@@ -65,21 +73,32 @@ services:
mc mb seaweedfs/reactive-resume;
exit 0;
"
networks:
- storage_network
depends_on:
seaweedfs:
condition: service_healthy
reactive_resume:
image: amruthpillai/reactive-resume:latest
# image: amruthpillai/reactive-resume:latest
# image: ghcr.io/amruthpillai/reactive-resume:latest
build:
context: .
dockerfile: Dockerfile
networks:
- data_network
- printer_network
- storage_network
ports:
- "3000:3000"
environment:
# Server
- TZ=Etc/UTC
- NODE_ENV=production
- APP_URL=http://localhost:3000
- PRINTER_APP_URL=http://reactive_resume:3000
- PRINTER_APP_URL=http://host.docker.internal:3000
# Printer
- PRINTER_ENDPOINT=ws://browserless:3000?token=1234567890
- PRINTER_ENDPOINT=ws://browserless:3000?token=${BROWSERLESS_TOKEN:-change-me}
# - PRINTER_ENDPOINT=http://chrome:9222 # Or, if you're using chromedp/headless-shell
# Database
- DATABASE_URL=postgresql://postgres:postgres@postgres:5432/postgres
@@ -93,8 +112,6 @@ services:
- S3_FORCE_PATH_STYLE=true
volumes:
- reactive_resume_data:/app/data
ports:
- "3000:3000"
depends_on:
postgres:
condition: service_healthy
@@ -102,6 +119,8 @@ services:
condition: service_healthy
seaweedfs_create_bucket:
condition: service_completed_successfully
extra_hosts:
- "host.docker.internal:host-gateway"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
start_period: 10s
@@ -113,3 +132,8 @@ volumes:
postgres_data:
seaweedfs_data:
reactive_resume_data:
networks:
data_network:
printer_network:
storage_network:
+8 -5
View File
@@ -1,10 +1,13 @@
preserve_hierarchy: true
commit_message: "[ci skip]"
project_id_env: CROWDIN_PROJECT_ID
api_token_env: CROWDIN_PERSONAL_TOKEN
commit_message: "[skip ci] chore(i18n): sync translations from crowdin"
pull_request_title: "Sync Translations from Crowdin"
pull_request_labels: ["l10n"]
pull_request_reviewers: ["amruthpillai"]
pull_request_body: "This is an automated pull request to sync translations from [Crowdin](https://crowdin.com/project/reactive-resume)."
pull_request_labels:
- l10n
pull_request_reviewers:
- amruthpillai
files:
- source: /locales/en-US.po
translation: /locales/%locale%.%file_extension%
+309 -149
View File
@@ -4,198 +4,358 @@ description: "List of all notable changes and updates to Reactive Resume"
rss: true
---
<Update label="v5.0.20" description="27th April 2026">
## Features & Improvements
- Added the new **Meowth** resume template (compact ATS-friendly layout with an inline three-column entry header), thanks to [@JamesGoslings](https://github.com/JamesGoslings). [#2923](https://github.com/amruthpillai/reactive-resume/pull/2923)
- Added support for custom icon colors on section items, thanks to [@Ruzenie](https://github.com/Ruzenie). [#2928](https://github.com/amruthpillai/reactive-resume/pull/2928)
- Improved account-related email UX with better templates for password reset and email verification. [b87f2007](https://github.com/amruthpillai/reactive-resume/commit/b87f2007)
- Consolidated separate settings pages into a single **Integrations** page for a more streamlined configuration flow. [5d8126d4](https://github.com/amruthpillai/reactive-resume/commit/5d8126d4)
## Fixes
- Added a targeted Chinese localization fix for the `Skills` section title (`zh-CN`). [#2931](https://github.com/amruthpillai/reactive-resume/pull/2931)
- Allowed MCP localhost loopback only for local MCP clients, improving local integration behavior while keeping external access restrictions intact. [623ca5c6](https://github.com/amruthpillai/reactive-resume/commit/623ca5c6)
## Maintenance & Localization
- Updated dependencies and lockfile, including cleanup of unused exports (Knip). [e1bccbcc](https://github.com/amruthpillai/reactive-resume/commit/e1bccbcc)
- Synced translation catalogs from Crowdin. [#2922](https://github.com/amruthpillai/reactive-resume/pull/2922), [#2925](https://github.com/amruthpillai/reactive-resume/pull/2925), [#2926](https://github.com/amruthpillai/reactive-resume/pull/2926), [#2932](https://github.com/amruthpillai/reactive-resume/pull/2932), [#2934](https://github.com/amruthpillai/reactive-resume/pull/2934), [#2935](https://github.com/amruthpillai/reactive-resume/pull/2935)
- Updated docs to list **Meowth** alongside existing templates. [#2929](https://github.com/amruthpillai/reactive-resume/pull/2929)
**Full Changelog**: [v5.0.19...v5.0.20](https://github.com/amruthpillai/reactive-resume/compare/v5.0.19...v5.0.20)
</Update>
<Update label="v5.0.19" description="25th April 2026">
## Highlights
- Added **OpenRouter** as an AI provider, thanks to [@iagodemacedo](https://github.com/iagodemacedo). [#2906](https://github.com/amruthpillai/reactive-resume/pull/2906)
- Added Chinese font options, thanks to [@Platinum1154](https://github.com/Platinum1154). [#2905](https://github.com/amruthpillai/reactive-resume/pull/2905)
- Added text color support to the rich text editor, including DOCX export handling for colored text, thanks to [@Platinum1154](https://github.com/Platinum1154). [#2903](https://github.com/amruthpillai/reactive-resume/pull/2903)
- Fixed MCP tool names for Claude Desktop compatibility. [#2885](https://github.com/amruthpillai/reactive-resume/pull/2885)
- Improved Docker-based local development support and refreshed self-hosting setup guidance. [849aad64](https://github.com/amruthpillai/reactive-resume/commit/849aad64), [c0387298](https://github.com/amruthpillai/reactive-resume/commit/c0387298)
## Fixes & Security
- Thank you to [@shaxbozaka](https://github.com/shaxbozaka) for running a security audit on Reactive Resume and helping make the app more secure.
- Hardened auth, OAuth, AI URL validation, printer/export endpoints, resume access controls, Browserless configuration, and public route exposure. [a42dbcd4](https://github.com/amruthpillai/reactive-resume/commit/a42dbcd4)
- Removed the public RPC surface for the printer-only resume fetch endpoint by moving it behind a server function, further shrinking the attack surface for `getByIdForPrinter`, thanks to [@shaxbozaka](https://github.com/shaxbozaka).
- Corrected Computer Modern Sans italic font file mappings, thanks to [@russellbrenner](https://github.com/russellbrenner). [#2881](https://github.com/amruthpillai/reactive-resume/pull/2881)
- Improved email handling and user lookup in OAuth configuration, thanks to [@VedantBhawsar](https://github.com/VedantBhawsar). [#2874](https://github.com/amruthpillai/reactive-resume/pull/2874)
- Aligned role period text in experience items, thanks to [@JamesGoslings](https://github.com/JamesGoslings). [#2908](https://github.com/amruthpillai/reactive-resume/pull/2908)
- Reduced resume preview wheel zoom sensitivity, thanks to [@GoldenSection0618](https://github.com/GoldenSection0618). [#2911](https://github.com/amruthpillai/reactive-resume/pull/2911)
- Prevented orphaned section headings at page breaks, thanks to [@5queezer](https://github.com/5queezer). [#2851](https://github.com/amruthpillai/reactive-resume/pull/2851)
- Added top margin handling for subsequent PDF pages, based on feedback from [@trigger-xyz](https://github.com/trigger-xyz). [bbc38d2f](https://github.com/amruthpillai/reactive-resume/commit/bbc38d2f)
## Maintenance & Localization
- Updated dependencies and lockfile, including security-focused package pins and Vite+ version pins. [c6e8df0a](https://github.com/amruthpillai/reactive-resume/commit/c6e8df0a), [847d69b6](https://github.com/amruthpillai/reactive-resume/commit/847d69b6), [9b3916d4](https://github.com/amruthpillai/reactive-resume/commit/9b3916d4)
- Added `SECURITY.md`. [9e1e593d](https://github.com/amruthpillai/reactive-resume/commit/9e1e593d)
- Added focused tests for auth, printer/export, URL validation, form edge cases, rich text DOCX output, font handling, and CSS sanitization hardening. [d0af9f4b](https://github.com/amruthpillai/reactive-resume/commit/d0af9f4b)
- Synced translation catalogs from Crowdin and moved syncing to GitHub Actions. [#2886](https://github.com/amruthpillai/reactive-resume/pull/2886), [#2918](https://github.com/amruthpillai/reactive-resume/pull/2918)
## New Contributors
- [@russellbrenner](https://github.com/russellbrenner), [@VedantBhawsar](https://github.com/VedantBhawsar), [@JamesGoslings](https://github.com/JamesGoslings), [@GoldenSection0618](https://github.com/GoldenSection0618), [@iagodemacedo](https://github.com/iagodemacedo), and [@Platinum1154](https://github.com/Platinum1154)
**Full Changelog**: [v5.0.17...v5.0.19](https://github.com/amruthpillai/reactive-resume/compare/v5.0.17...v5.0.19)
</Update>
<Update label="v5.0.18" description="14th April 2026">
## Features & Improvements
- Re-enabled **Passkeys** across authentication flows, including passkey sign-in on the login page and passkey management in account settings.
## Maintenance
- Updated dependencies and lockfile.
- Synced translation catalogs from Crowdin.
</Update>
<Update label="v5.0.17" description="9th April 2026">
## Features & Improvements
- Added **Resume Analysis** in the resume builder right sidebar: run an on-demand AI review for an overall score (0 to 100), a dimension scorecard, strengths, and prioritized suggestions. Results are saved per resume and reload after refresh.
- When AI features are disabled, the analysis section shows a short notice with a link to **AI Settings** so you can enable your provider and model first.
- Improved the **MCP server** experience: updated tools, resources, and prompts; added an MCP server card at `/.well-known/mcp/server-card.json` for compatible clients; refreshed the **Using the MCP server** guide.
## Fixes
- Fixed [#2865](https://github.com/amruthpillai/reactive-resume/issues/2865), incorrect spacing between resume section items in shared layouts.
- Fixed [#2868](https://github.com/amruthpillai/reactive-resume/issues/2868) affecting resume item presentation and linked titles in shared item components.
## Maintenance & CI
- Downgraded the Cosign installer action to v3 in the Docker build workflow for more reliable image signing.
- Synced translation catalogs from Crowdin. [#2864](https://github.com/amruthpillai/reactive-resume/pull/2864)
- Dependency and lockfile updates (including changes bundled with MCP and analysis work).
</Update>
<Update label="v5.0.16" description="4th April 2026">
## Features & Improvements
- Updated shared resume item layouts (Experience, Education, Awards, Certifications, Publications, References, Volunteer) for better alignment and spacing consistency.
- Generated printer pages now set a meaningful document title from the resume owner's name (`<Name> - Resume`) for cleaner PDF metadata and tab titles. [#2863](https://github.com/amruthpillai/reactive-resume/pull/2863)
## Fixes
- Fixed [#2733](https://github.com/amruthpillai/reactive-resume/issues/2733), where date ranges could appear on a separate line unexpectedly in resume items. [#2862](https://github.com/amruthpillai/reactive-resume/pull/2862)
- Adjusted Experience section form field ordering/labels so `Location`, `Position`, and `Period` are mapped and displayed correctly.
## Maintenance & Localization
- Synced translation catalogs across all supported locales.
- Updated dependencies and lockfile, including AI SDK, TipTap, TanStack Query, and Vite+ package aliases.
</Update>
<Update label="v5.0.15" description="1st April 2026">
## Features & Improvements
- Added **LinkedIn sign-in** support for self-hosted instances, including sign-in on the auth page and account linking in settings.
- Improved the sign-in experience by showing loading placeholders while social login providers are being fetched.
- Resume builder panel sizes are now persisted more reliably, making the layout feel more consistent between sessions.
- Added clearer labels for resume sorting and filtering controls on the dashboard.
## Fixes
- Improved autosave reliability in the resume builder:
- Unsaved edits are flushed before page unload.
- You now get a clear persistent warning if changes fail to save (for example, due to network issues).
- Strengthened authentication defaults by increasing the minimum password length requirement to 8 characters.
- Improved reliability around resume deletion and server-side error handling for create/update operations.
- Fixed a rare language mix-up during printing/export when multiple print jobs run at the same time, so each PDF/screenshot now reliably uses the correct locale.
- Improved print stability under heavy usage by reusing in-progress PDF and screenshot generation requests for the same resume instead of starting duplicate jobs.
- Hardened printer token signing and token/session/API-key verification paths for better security.
- Improved resilience in AI tailoring output parsing and JSON Resume import normalization.
- Printer service now provides descriptive error messages ("Failed to generate PDF" / "Failed to capture screenshot") instead of generic internal server errors.
- Job descriptions in the job detail sheet now render HTML formatting (headings, lists, bold text) instead of showing raw tags.
- Fixed the "Tailor Resume" button being incorrectly disabled when AI is not configured — the dialog already supports plain resume duplication as a fallback.
- Fixed the tailor resume flow to open the resume builder in a new tab while keeping the job listing open for reference, instead of navigating away and auto-opening the application page.
- Apply option links in the job detail sheet are now validated before rendering, preventing malformed or potentially unsafe URLs.
- Improved job card loading skeletons to match the actual card layout (logo, title, badges) for a smoother loading experience.
- Centered the "Configure Job Search" empty state on the page.
- Extracted the `JobCard` component into its own file for better code organization.
## Docs & Localization
- Updated self-hosting and environment variable documentation to include LinkedIn OAuth configuration.
- Synced translations across locales for the latest authentication and save-status messaging.
</Update>
<Update label="v5.0.14" description="24th March 2026">
## Features
- Implemented OAuth 2.1 support for MCP authentication, thanks to @5queezer. [#2829](https://github.com/amruthpillai/reactive-resume/pull/2829)
- Migrated from using Biome to Oxlint/Oxfmt for linting and formatting, with a new autofix CI workflow. [#2822](https://github.com/amruthpillai/reactive-resume/pull/2822)
## Fixes
- Fixed an issue with the slider component for "Sidebar Width" in the resume builder. [#2830](https://github.com/amruthpillai/reactive-resume/issues/2830)
</Update>
<Update label="v5.0.13" description="19th March 2026">
- **Features & Improvements**
- Added **Job Listings with AI-Powered Resume Tailoring**, a major new feature that integrates job search (via JSearch API) directly into the dashboard with AI-driven resume tailoring to match job descriptions. [#2788](https://github.com/amruthpillai/reactive-resume/pull/2788)
- Search for jobs with filters for location (city, state, country), employment type, remote status, and more.
- View detailed job listings in a slide-out panel with apply links and tags.
- Tailor your resume to a specific job posting using AI — rewrites skills, experience, and references for optimal fit.
- Per-user API quota management with usage tracking in settings.
- Pagination with 30 results per page.
- Added **DOCX resume export** — generate and download tailored resumes as Word documents. [#2788](https://github.com/amruthpillai/reactive-resume/pull/2788)
- Migrated the entire project toolchain to **Vite+** (`vp` CLI), unifying Vite, Vitest, Oxlint, and Oxfmt under a single developer experience.
- Migrated from **Biome to Oxlint/Oxfmt** for linting and formatting, with a new autofix CI workflow. [#2822](https://github.com/amruthpillai/reactive-resume/pull/2822)
- Added **Slovenian** locale support.
- Added a new `Progress` UI component.
## Features & Improvements
- Added **Job Listings with AI-Powered Resume Tailoring**, a major new feature that integrates job search (via JSearch API) directly into the dashboard with AI-driven resume tailoring to match job descriptions. [#2788](https://github.com/amruthpillai/reactive-resume/pull/2788)
- Search for jobs with filters for location (city, state, country), employment type, remote status, and more.
- View detailed job listings in a slide-out panel with apply links and tags.
- Tailor your resume to a specific job posting using AI — rewrites skills, experience, and references for optimal fit.
- Per-user API quota management with usage tracking in settings.
- Pagination with 30 results per page.
- Added **DOCX resume export** — generate and download tailored resumes as Word documents. [#2788](https://github.com/amruthpillai/reactive-resume/pull/2788)
- Migrated the entire project toolchain to **Vite+** (`vp` CLI), unifying Vite, Vitest, Oxlint, and Oxfmt under a single developer experience.
- Migrated from **Biome to Oxlint/Oxfmt** for linting and formatting, with a new autofix CI workflow. [#2822](https://github.com/amruthpillai/reactive-resume/pull/2822)
- Added **Slovenian** locale support.
- Added a new `Progress` UI component.
- **Fixes**
- Fixed type issues in auth client and configuration.
- Used RTL-friendly CSS classes across the app for improved bidirectional layout support.
- Removed dead code and unused imports.
- Updated route tree generation and added `routeTree.gen.ts` to `.gitignore`.
- Improved animation performance with `willChange` styles on animation components.
## Fixes
- Fixed type issues in auth client and configuration.
- Used RTL-friendly CSS classes across the app for improved bidirectional layout support.
- Removed dead code and unused imports.
- Updated route tree generation and added `routeTree.gen.ts` to `.gitignore`.
- Improved animation performance with `willChange` styles on animation components.
- **Maintenance & Dependencies**
- Updated dependencies across the board, including `@typescript/native-preview`, `vite-plus`, and others.
- Added database migrations for missing indexes.
- Synced translations from Crowdin. [#2820](https://github.com/amruthpillai/reactive-resume/pull/2820), [#2824](https://github.com/amruthpillai/reactive-resume/pull/2824)
- Updated French translation for 'Volunteer' to 'Bénévolat'. [#2823](https://github.com/amruthpillai/reactive-resume/pull/2823)
- Multiple translation and language updates across all locales.
## Maintenance & Dependencies
- Updated dependencies across the board, including `@typescript/native-preview`, `vite-plus`, and others.
- Added database migrations for missing indexes.
- Synced translations from Crowdin. [#2820](https://github.com/amruthpillai/reactive-resume/pull/2820), [#2824](https://github.com/amruthpillai/reactive-resume/pull/2824)
- Updated French translation for 'Volunteer' to 'Bénévolat'. [#2823](https://github.com/amruthpillai/reactive-resume/pull/2823)
- Multiple translation and language updates across all locales.
- **New Contributors** - @lukadfagundes - @Hephaisto-dev
</Update>
## New Contributors
- @lukadfagundes
- @Hephaisto-dev
</Update>
<Update label="v5.0.12" description="17th March 2026">
- **Features & Improvements**
- Refactored the component system to `@base-ui/react` across the app for standardized UI behavior (dialogs, menus, accordions, comboboxes, switches, etc.).
- Improved auth infrastructure with Better Auth dashboard/sentinel integration and expanded IP/header handling for proxied deployments.
- Added a dedicated `src/server.ts` server entry using `FastResponse` for better runtime compatibility.
- Sidebar button list now scrolls internally on overflow.
## Features & Improvements
- Refactored the component system to `@base-ui/react` across the app for standardized UI behavior (dialogs, menus, accordions, comboboxes, switches, etc.).
- Improved auth infrastructure with Better Auth dashboard/sentinel integration and expanded IP/header handling for proxied deployments.
- Added a dedicated `src/server.ts` server entry using `FastResponse` for better runtime compatibility.
- Sidebar button list now scrolls internally on overflow.
- **Fixes**
- Printer endpoint now resolves to IP when using chromedp, improving compatibility with proxied deployments.
- Switched OpenAI integration to `chat/completions` for resume import and AI features.
- AI PDF/DOCX resume parsing is now more robust via direct file send (multimodal input).
- Resume import normalization hardened: section defaults merged, required fields enforced, missing IDs/flags autogenerated.
- Fixed UI migration regressions (accordion behavior, button interaction, right sidebar scrollbar).
- Fixed multi-page PDF crashes and Gemini API ingestion errors.
- Fixed role title styling, role spacing, and issues with skills item rendering and template headings.
## Fixes
- Printer endpoint now resolves to IP when using chromedp, improving compatibility with proxied deployments.
- Switched OpenAI integration to `chat/completions` for resume import and AI features.
- AI PDF/DOCX resume parsing is now more robust via direct file send (multimodal input).
- Resume import normalization hardened: section defaults merged, required fields enforced, missing IDs/flags autogenerated.
- Fixed UI migration regressions (accordion behavior, button interaction, right sidebar scrollbar).
- Fixed multi-page PDF crashes and Gemini API ingestion errors.
- Fixed role title styling, role spacing, and issues with skills item rendering and template headings.
- **Maintenance & Dependencies**
- Updated dependencies and lockfile.
- Synced translation strings across locales, including multiple contributions from Crowdin.
- General dependency and code style improvements, plus removal of redundant indexes and plugins.
## Maintenance & Dependencies
- Updated dependencies and lockfile.
- Synced translation strings across locales, including multiple contributions from Crowdin.
- General dependency and code style improvements, plus removal of redundant indexes and plugins.
- **Community & Contributors** - Users can now view other public resumes while logged in. - New Contributors: @yang-summer, @FrancocDev, @obitton, @jondycz. - Full changelog: https://docs.rxresu.me/changelog
</Update>
## New Contributors
- @yang-summer
- @FrancocDev
- @obitton
- @jondycz
</Update>
<Update label="v5.0.10" description="24th February 2026">
- **Fixes**
- Show section titles for summary-type custom sections in the resume builder. [#2744](https://github.com/amruthpillai/reactive-resume/pull/2744)
- Prevent browser password managers and Edge autofill/save prompts from appearing on AI settings API key fields. [#2719](https://github.com/amruthpillai/reactive-resume/pull/2719)
- Replace deprecated Tailwind CSS classes: use `inset-s-*`/`inset-e-*` instead of `start-*`/`end-*`.
- Fix PDF downloader to work correctly in offline mode. [#2743](https://github.com/amruthpillai/reactive-resume/pull/2743)
- Make bold formatting visible for `<strong>` in the resume rich text editor (uses plain `font-weight: bold` fallback if the CSS variable is unset). Fixes [#2730](https://github.com/amruthpillai/reactive-resume/issues/2730)
- Prevent credentials sign-in from dropping `session_token` Set-Cookie, improves login reliability. [#2718](https://github.com/amruthpillai/reactive-resume/pull/2718)
- Remove redundant `resume-` prefix from download filename; add spacing between pages in shared view. [#2709](https://github.com/amruthpillai/reactive-resume/pull/2709)
- Normalize autocomplete tokens for login and register forms. [#2714](https://github.com/amruthpillai/reactive-resume/pull/2714)
- Fix improper chips reordering and update dependency/translations. [#2711](https://github.com/amruthpillai/reactive-resume/issues/2711)
- Fix issue with clipping of heading in Lapras resume template.
- Remove error-causing plugins in oRPC integration.
- Remove duplicate database indexes; add index for `created_at` on user and resume tables.
## Fixes
- Show section titles for summary-type custom sections in the resume builder. [#2744](https://github.com/amruthpillai/reactive-resume/pull/2744)
- Prevent browser password managers and Edge autofill/save prompts from appearing on AI settings API key fields. [#2719](https://github.com/amruthpillai/reactive-resume/pull/2719)
- Replace deprecated Tailwind CSS classes: use `inset-s-*`/`inset-e-*` instead of `start-*`/`end-*`.
- Fix PDF downloader to work correctly in offline mode. [#2743](https://github.com/amruthpillai/reactive-resume/pull/2743)
- Make bold formatting visible for `<strong>` in the resume rich text editor (uses plain `font-weight: bold` fallback if the CSS variable is unset). Fixes [#2730](https://github.com/amruthpillai/reactive-resume/issues/2730)
- Prevent credentials sign-in from dropping `session_token` Set-Cookie, improves login reliability. [#2718](https://github.com/amruthpillai/reactive-resume/pull/2718)
- Remove redundant `resume-` prefix from download filename; add spacing between pages in shared view. [#2709](https://github.com/amruthpillai/reactive-resume/pull/2709)
- Normalize autocomplete tokens for login and register forms. [#2714](https://github.com/amruthpillai/reactive-resume/pull/2714)
- Fix improper chips reordering and update dependency/translations. [#2711](https://github.com/amruthpillai/reactive-resume/issues/2711)
- Fix clipping of heading in Lapras resume template.
- Remove error-causing plugins in oRPC integration.
- Remove duplicate database indexes; add index for `created_at` on user and resume tables.
- **Features & Improvements**
- Add new feature flag: `FLAG_DISABLE_IMAGE_PROCESSING` (allows disabling of image processing site-wide).
- Update Discord invite link in the app and documentation.
- Add comprehensive codebase and architecture documentation in `CLAUDE.md`.
- Sync latest translations from Crowdin (updated and added multiple languages).
## Features & Improvements
- Added feature flag: `FLAG_DISABLE_IMAGE_PROCESSING` (allows disabling image processing site-wide).
- Updated Discord invite link in the app and documentation.
- Added comprehensive codebase and architecture documentation in `CLAUDE.md`.
- Synced latest translations from Crowdin (multiple language updates and additions).
- **Other** - Refactor: rename auth utility from `originWith` to `withHostname`, preserve localhost/127.0.0.1 sibling trust. - General dependency updates and code style improvements. - Documentation and README updates.
</Update>
## Maintenance & Other
- Refactored auth utility from `originWith` to `withHostname` to preserve localhost/127.0.0.1 sibling trust.
- Updated dependencies and improved code style.
- Updated documentation and README files.
</Update>
<Update label="v5.0.9" description="9th February 2026">
- Add Computer Modern web fonts to the font selector, allowing the user to choose from a variety of "Computer Modern"
(LaTeX) fonts. - This lets you create a resume that looks just like a LaTeX document, with the same fonts and styles.
- Update dependencies to the latest versions.
## Features & Improvements
- Added Computer Modern web fonts to the font selector, allowing users to choose from LaTeX-style "Computer Modern" fonts for a classic scientific look.
- Updated dependencies to the latest versions.
</Update>
<Update label="v5.0.8" description="9th February 2026">
- Remove Passkey support from the authentication system, as it was causing issues with the authentication provider. -
Update dependencies to the latest versions.
## Fixes
- Removed Passkey support from the authentication system due to provider compatibility issues.
- Updated dependencies to the latest versions.
</Update>
<Update label="v5.0.7" description="9th February 2026">
- Introduce a new **MCP (Model Context Protocol) server** that lets you manage and edit resumes from any
MCP-compatible AI tool — Claude Desktop, Cursor, Codex, and more. Supports listing, reading, creating, deleting,
locking/unlocking, and patching resumes via natural language. [(guide)](/guides/using-the-mcp-server) - Add an **AI
Chat** panel to the resume builder, allowing you to modify your resume through conversational AI directly within the
editor. The chat uses tool-calling to apply JSON Patch operations to your resume in real-time, with visual feedback
for each change. - Add a system prompt and `patch_resume` tool for the AI chat, enabling structured, minimal-diff
resume edits following RFC 6902 JSON Patch operations. - Chat history is now persisted per resume in localStorage, so
conversations are preserved across sessions. - Fix rendering issues in the Lapras and Onyx resume templates. -
Improvements to the Combobox and ScrollArea UI components. - Fix an issue with skills item rendering in the shared
resume components. - Update authentication configuration and auth route handling. - Update the JSON Schema to reflect
the latest resume data model. - Update dependencies to the latest versions.
## Features & Improvements
- Introduced a new **MCP (Model Context Protocol) server** for managing and editing resumes from MCP-compatible AI tools (Claude Desktop, Cursor, Codex, and others). Supports listing, reading, creating, deleting, locking/unlocking, and patching resumes via natural language. [(guide)](/guides/using-the-mcp-server)
- Added an **AI Chat** panel to the resume builder, allowing conversational resume modifications through tool-calling (JSON Patch operations applied to the resume in real time with visual feedback).
- Added a system prompt and `patch_resume` tool for structured, minimal-diff resume edits via RFC 6902 JSON Patch.
- Chat history is now persisted per resume in localStorage.
## Fixes
- Fixed rendering issues in the Lapras and Onyx resume templates.
- Improved Combobox and ScrollArea UI components.
- Fixed skills item rendering in shared resume components.
- Updated authentication configuration and route handling.
- Updated JSON Schema to match the latest resume data model.
## Maintenance
- Updated dependencies to latest versions.
</Update>
<Update label="v5.0.6" description="8th February 2026">
- Implement Atomic Resume Patching API for fine-grained resume updates, allowing partial, atomic updates to resumes
via a new PATCH endpoint. [#2692](https://github.com/amruthpillai/reactive-resume/pull/2692) - The API endpoint `PUT
/resume/{id}` now returns the updated resume object instead of void. Also, resume routing/services now use explicit
DTOs for input and output schemas. [#2688](https://github.com/amruthpillai/reactive-resume/pull/2688) - Add error
logging for API server errors (server-side only) to improve debugging and reliability. - Refactor and clean up
imports/exports for clarity and maintainability. - Added `.devcontainer` configuration for improved contributor
development environment. - Update dependencies to the latest versions. - Add build status badge and documentation link
to README. - Sync latest translations from Crowdin (notably: French, other languages). - Other minor fixes and
improvements.
## Features & Improvements
- Implemented Atomic Resume Patching API for fine-grained, partial updates to resumes via a new PATCH endpoint. [#2692](https://github.com/amruthpillai/reactive-resume/pull/2692)
- API endpoint `PUT /resume/{id}` now returns the updated resume object rather than void, with explicit DTOs for input/output. [#2688](https://github.com/amruthpillai/reactive-resume/pull/2688)
- Added error logging for API server errors (server-side only) to improve debugging and reliability.
- Refactored and cleaned up imports/exports for clarity and maintainability.
- Added `.devcontainer` configuration for improved contributor development environment.
- Added build status badge and documentation link to README.
## Fixes
- General bug fixes and minor improvements.
- Synced latest translations from Crowdin (notably: French, other languages).
- Updated dependencies to latest versions.
</Update>
<Update label="v5.0.5" description="31st January 2026">
- Implement Cover Letter functionality in the resume builder, allowing the user to create cover letters as custom
sections. [(link)](/guides/adding-a-cover-letter) - Implement full-screen mode for the rich text editor in the resume
builder, allowing the user to write in a more focused environment. - Implement a new custom section type: `summary`,
which allows the user to add another "summary" like section to the resume. - Implement a `useFormBlocker` hook to
prevent the user from closing a dialog while the form has unsaved changes. - Fix an issue where keywords spacing was
not consistent in the interests section. [#2631](https://github.com/amruthpillai/reactive-resume/pull/2631) - Fix an
issue where the AI connection test was not working correctly, also return appropriate error messages for AI provider
issues.
## Features & Improvements
- Implemented Cover Letter functionality as a custom section in the resume builder. [(link)](/guides/adding-a-cover-letter)
- Added full-screen mode to the rich text editor for distraction-free writing.
- Introduced a new custom section type: `summary`, enabling users to add extra summary sections.
- Added `useFormBlocker` hook to prevent dialogs from being closed with unsaved form changes.
## Fixes
- Fixed keyword spacing issue in the interests section. [#2631](https://github.com/amruthpillai/reactive-resume/pull/2631)
- Fixed AI connection test and improved error reporting for AI provider issues.
</Update>
<Update label="v5.0.4" description="28th January 2026">
- Bring back Undo/Redo functionality in the resume builder for improved editing experience. - Arrange the sidebar
builder dynamically based on the section type in each template.
[#2564](https://github.com/amruthpillai/reactive-resume/pull/2603) - Remove extra spacing when proficiency is empty.
[#2607](https://github.com/amruthpillai/reactive-resume/pull/2626) - Fix rendering in Pikachu template: conditionally
render header and page picture using `isFirstPage`, and respect the `fullWidth` property for page layout. - Fixes to
templates to improve layout and rendering consistency. - Fix GitHub OAuth login for users migrated from previous
versions. - Improve communication with the printer service and reduce resource usage for better PDF generation
reliability. - Update and sync translations from Crowdin (Afrikaans, Persian, Portuguese/Brazilian, and other
languages). - Update translation sources and configuration. - Fix "empty" Git merge remnants in codebase. - Update
package dependencies and fix self-hosting guide links in README. - Remove dead code, update screenshots, and add PWA
(Progressive Web App) support. - Update links to PDF example files in documentation. - Other bug fixes and minor
improvements (#2542, #2573, #2598).
## Features & Improvements
- Brought back Undo/Redo functionality in the resume builder for an improved editing experience.
- Sidebar builder now arranges dynamically based on the section type in each template. [#2564](https://github.com/amruthpillai/reactive-resume/pull/2603)
- Added PWA (Progressive Web App) support and updated PDF example file links in documentation.
## Fixes
- Removed extra spacing when proficiency is empty. [#2607](https://github.com/amruthpillai/reactive-resume/pull/2626)
- Fixed Pikachu template: header/page picture rendering, and respect for `fullWidth` property.
- General template improvements for layout and rendering consistency.
- Fixed GitHub OAuth login for users migrated from previous versions.
- Improved printer service communication and reduced resource usage for more reliable PDF generation.
- Fixed "empty" Git merge remnants in codebase.
## Maintenance
- Synced translations from Crowdin (Afrikaans, Persian, Portuguese/Brazilian, others).
- Updated translation sources and configuration.
- Updated package dependencies and fixed self-hosting guide links in README.
- Removed dead code and updated screenshots.
- Other bug fixes and minor improvements (#2542, #2573, #2598).
</Update>
<Update label="v5.0.3" description="25th January 2026">
- Implement the ability to print Free-Form PDFs which do not have a fixed page height, allowing the user to fit the
content on a page as they see fit. [(link)](/guides/selecting-page-format) - Allow the user to override the default
endpoint for all AI providers, not just Ollama. Also display the default endpoint for each provider in the AI settings
page. - Updated the chip input component, to allow the user to add, edit, remove or reorder keywords for skills and
other sections. - Improved RTL support across the app, thanks to @obreo for the contribution.
[(link)](https://github.com/amruthpillai/reactive-resume/pull/2583) - Updates to the translation configuration to
remove line numbers from the translation files, as this was causing unnecessary diffs. - Updated the video on the
homepage to be lighter and faster to load, while still maintaining the same quality. - Increased the screenshot TTL
for resumes, to avoid regenerating screenshots unnecessarily unless the resume has been updated recently (in the last
hour). - Update dependencies and translations to the latest versions.
## Features & Improvements
- Added support for printing Free-Form PDFs (no fixed page height), allowing for custom-fit page content. [(link)](/guides/selecting-page-format)
- Allow overriding the default endpoint for all AI providers, not just Ollama; default endpoint now visible in AI settings.
- Updated the chip input component: supports adding, editing, removing, and reordering keywords for skills and other sections.
- Improved RTL support across the app, thanks to @obreo. [(link)](https://github.com/amruthpillai/reactive-resume/pull/2583)
## Fixes
- Updated translation configuration to remove line numbers, reducing unnecessary diffs.
- Homepage video optimized for faster loading without quality loss.
- Increased screenshot TTL for resumes to reduce unnecessary regenerations.
- Updated dependencies and translations to latest versions.
</Update>
<Update label="v5.0.2" description="24th January 2026">
- Added an agent skill `skills/resume-builder` for agentic AI assistants to build resumes for Reactive Resume through
conversational AI. - Added a new guide on how to fit content on a page, to avoid issues when exporting to PDF.
[(link)](/guides/fitting-content-on-a-page) - Display an alert when the content is too tall for a page, to help the
user fit the content on a page. - Fix an issue with the Ditgar template, where the page was not respecting the
`fullWidth` setting. - Updated the JSON Schema to conform to a proper format. - Updated the Discord Server invite link
to a new one. - Updated dependencies to the latest versions.
## Features & Improvements
- Added an agent skill `skills/resume-builder` for agentic AI assistants, enabling resume construction through conversational AI.
- Added a guide for how to fit content on a page when exporting to PDF. [(link)](/guides/fitting-content-on-a-page)
- Alert user when content is too tall for a page for easier export troubleshooting.
## Fixes
- Ditgar template now respects `fullWidth` setting.
- Updated JSON Schema for proper format.
- Updated Discord server invite link.
- Updated dependencies to latest versions.
</Update>
<Update label="v5.0.1" description="23th January 2026">
- Updated translations from Crowdin. - Added a Community Spotlight section to the documentation. - Remove `-r
require-metadata` from the Dockerfile as it was not needed. - Fixed inconsistencies in the docker compose examples in
the documentation. - Fixed an issue with usernames not allowing hyphens in them. - Fixed issues with the printer
service, when using the `getResumeScreenshot` or `printResumeAsPDF` endpoints.
## Maintenance & Fixes
- Updated translations from Crowdin.
- Added Community Spotlight section to the documentation.
- Removed `-r require-metadata` from the Dockerfile (was not needed).
- Fixed inconsistencies in Docker compose documentation examples.
- Fixed username validation to allow hyphens.
- Fixed issues with the printer service when using `getResumeScreenshot` or `printResumeAsPDF` endpoints.
</Update>
<Update label="v5.0.0" description="22th January 2026">
This has been a major overhaul from the previous version of Reactive Resume. The app has been completely redesigned and rebuilt from scratch, to be more intuitive and user-friendly.
This release is a major overhaul of Reactive Resume. The app has been completely redesigned and rebuilt from scratch for a more intuitive, user-friendly experience.
**Here are some of the key changes from the previous version:**
- 2 new templates: _Ditgar_ and _Lapras_
- Authentication via Passkeys
- New user interface and refreshed design
- API Access and Reference Documentation
**Highlights:**
- 2 new resume templates: _Ditgar_ and _Lapras_
- Passkeys for authentication
- Entirely new user interface and refreshed design
- API access and reference docs
- AI Integration with OpenAI, Google, Anthropic, and Ollama
- A better font selector, with real-time preview of the font
- More comprehensive documentation, with guides for specific features
- An improved templates gallery, with clear overview of each template
- Ability to import resumes from PDF or DOCX (requires AI Integration)
- Reliable server infrastructure, with improved performance and scalability
- Ability to move items between sections and pages, for better organization
- A more powerful CSS Editor, with better autocomplete and syntax highlighting
- Extended Custom Sections to include a type, for better customization and organization
- Ability to choose the kind of icons to display for level indicators (stars, circles, custom icons, etc.)
- Ability to resize how wide/narrow the sidebar should be on the resume, or convert a page to be full width
There's still a lot more that I'm forgetting, but I'm sure you'll find out soon enough as you explore the new version. I hope you enjoy building your resume now, better than ever. If you have any feedback, please feel free to [contact me](https://amruthpillai.com/#contact) or [open an issue](https://github.com/amruthpillai/reactive-resume/issues) on GitHub.
- Improved font selector with real-time preview
- Expanded and improved documentation, with more how-to guides
- Redesigned template gallery with detailed views
- Resume import from PDF/DOCX (requires AI Integration)
- Stronger infrastructure for better reliability and scalability
- Drag-and-drop for moving items between sections and pages
- Powerful new CSS Editor with autocompletion and highlighting
- Custom sections now support typed extension for finer control
- Icon style selector for indicators (stars, circles, custom, etc.)
- Sidebar width adjustment and full-width page support
And much more! Explore the new version and share your feedback via [contact](https://amruthpillai.com/#contact) or [GitHub issues](https://github.com/amruthpillai/reactive-resume/issues).
</Update>
+3 -3
View File
@@ -126,7 +126,7 @@ Here are the most commonly used scripts during development:
| `pnpm run build` | Build the application for production |
| `pnpm run start` | Start the production server |
| `pnpm run lint` | Run Oxlint linter and formatter |
| `pnpm run fmt` | Run Oxfmt formatter |
| `pnpm run format` | Run Oxfmt formatter |
| `pnpm run typecheck` | Run TypeScript type checking |
### Database
@@ -252,8 +252,8 @@ Uses [Oxlint](https://oxlint.dev/) for linting and [Oxfmt](https://oxfmt.dev/) f
```bash
# Check and auto-fix issues
pnpm run lint
pnpm run fmt
pnpm run lint:fix
pnpm run format:fix
```
### Type Checking
+2 -2
View File
@@ -39,8 +39,8 @@ description: "Welcome to the documentation for Reactive Resume, a free and open-
</Accordion>
<Accordion title="Multiple Templates" icon="grid-2">
Choose from a variety of professionally designed templates including Azurill, Bronzor, Chikorita, Ditto, Gengar,
Glalie, Kakuna, Lapras, Leafish, Onyx, Pikachu, and Rhyhorn - each with unique layouts and styles.
Choose from a variety of professionally designed templates including Azurill, Bronzor, Chikorita, Ditgar, Ditto,
Gengar, Glalie, Kakuna, Lapras, Leafish, Meowth, Onyx, Pikachu, and Rhyhorn - each with unique layouts and styles.
</Accordion>
<Accordion title="Rich Text Editor" icon="text">
+95 -54
View File
@@ -67,28 +67,60 @@ Before you begin, ensure you have the following installed:
<Steps>
<Step title="Clone the Repository">
```bash git clone https://github.com/amruthpillai/reactive-resume.git cd Reactive-Resume ```
```bash
git clone https://github.com/amruthpillai/reactive-resume.git
cd Reactive-Resume
```
</Step>
<Step title="Configure Environment Variables">
Create a `.env` file in the root directory with the following variables: ```bash # Application
APP_URL=http://localhost:3000 # Database DATABASE_URL=postgresql://postgres:postgres@postgres:5432/postgres #
Authentication (generate a secure secret) AUTH_SECRET=your-secure-secret-key-here # Storage (S3-compatible)
S3_ACCESS_KEY_ID=seaweedfs S3_SECRET_ACCESS_KEY=seaweedfs S3_ENDPOINT=http://seaweedfs:8333
S3_BUCKET=reactive-resume S3_FORCE_PATH_STYLE=true ```
Create a `.env` file in the root directory with the following variables:
```bash .env
# Application
APP_URL=http://localhost:3000
# Printer (Browserless)
BROWSERLESS_TOKEN=change-me
PRINTER_ENDPOINT=ws://browserless:3000?token=change-me
# Database
DATABASE_URL=postgresql://postgres:postgres@postgres:5432/postgres
# Authentication (generate a secure secret)
AUTH_SECRET=your-secure-secret-key-here
# Storage (S3-compatible via SeaweedFS)
S3_ACCESS_KEY_ID=seaweedfs
S3_SECRET_ACCESS_KEY=seaweedfs
S3_ENDPOINT=http://seaweedfs:8333
S3_BUCKET=reactive-resume
S3_FORCE_PATH_STYLE=true
```
<Warning>
For production deployments, always use strong, unique values for `AUTH_SECRET` and database credentials.
For production deployments, always use strong, unique values for `AUTH_SECRET`, `BROWSERLESS_TOKEN`, and database credentials.
</Warning>
</Step>
<Step title="Start the Services">
```bash docker compose up -d ``` This will start: - **PostgreSQL** — Database for storing user data and resumes -
**SeaweedFS** — S3-compatible storage for file uploads - **Printer** — PDF and screenshot generation service
(browserless/chromium) - **Reactive Resume** — The main application
```bash
docker compose up -d
```
This starts:
- **PostgreSQL** — Database for storing user data and resumes
- **SeaweedFS** — S3-compatible storage for file uploads
- **Browserless** — Headless Chromium service for PDF/screenshot generation
- **Reactive Resume** — The main application
</Step>
<Step title="Access Your Instance">
Once all services are running, access your Reactive Resume instance at: ``` http://localhost:3000 ```
Once all services are running, access your Reactive Resume instance at:
```text
http://localhost:3000
```
</Step>
</Steps>
@@ -97,12 +129,12 @@ Before you begin, ensure you have the following installed:
Here's what each service in the stack does:
| Service | Port | Description |
| ----------- | ---- | ----------------------------------------------------------- |
| `postgres` | 5432 | PostgreSQL database for storing all application data |
| `seaweedfs` | 8333 | S3-compatible object storage for file uploads |
| `printer` | 4000 | Headless Chromium service for PDF and screenshot generation |
| `app` | 3000 | The main Reactive Resume application |
| Service | Port | Description |
| ----------------- | ---------------------------------- | ----------------------------------------------------------- |
| `postgres` | 5432 | PostgreSQL database for storing all application data |
| `seaweedfs` | 8333 | S3-compatible object storage for file uploads |
| `browserless` | 3000 (internal) / 4000 (published) | Headless Chromium service for PDF and screenshot generation |
| `reactive_resume` | 3000 | The main Reactive Resume application |
### Health Checks
@@ -122,49 +154,58 @@ Here's a complete list of environment variables you can configure:
### Required Variables
| Variable | Description | Example |
| ------------------ | ------------------------------ | --------------------------------------- |
| `DATABASE_URL` | PostgreSQL connection string | `postgresql://user:pass@host:5432/db` |
| `AUTH_SECRET` | Secret key for authentication | Generate with `openssl rand -base64 32` |
| `APP_URL` | Public URL of your Application | `https://rxresu.me` |
| `PRINTER_ENDPOINT` | URL of the printer service | `http://printer:3000` |
| Variable | Description | Example |
| ------------------ | ------------------------------ | ------------------------------------- |
| `DATABASE_URL` | PostgreSQL connection string | `postgresql://user:pass@host:5432/db` |
| `AUTH_SECRET` | Secret key for authentication | Generate with `openssl rand -hex 32` |
| `APP_URL` | Public URL of your Application | `https://rxresu.me` |
| `PRINTER_ENDPOINT` | URL of the printer service | `ws://browserless:3000?token=...` |
### Optional Variables
| Variable | Description | Default |
| ------------------------------- | ----------------------------------------------------------- | ---------------------- |
| `PRINTER_APP_URL` | Public URL for the printer to access the Application | — |
| `GOOGLE_CLIENT_ID` | Google OAuth Client ID | — |
| `GOOGLE_CLIENT_SECRET` | Google OAuth Client Secret | — |
| `GITHUB_CLIENT_ID` | GitHub OAuth Client ID | — |
| `GITHUB_CLIENT_SECRET` | GitHub OAuth Client Secret | — |
| `OAUTH_PROVIDER_NAME` | Custom OAuth Provider Name | — |
| `OAUTH_CLIENT_ID` | Custom OAuth Client ID | — |
| `OAUTH_CLIENT_SECRET` | Custom OAuth Client Secret | — |
| `OAUTH_DISCOVERY_URL` | OIDC Discovery URL (use this OR manual URLs below) | — |
| `OAUTH_AUTHORIZATION_URL` | OAuth Authorization URL (manual config) | — |
| `OAUTH_TOKEN_URL` | OAuth Token URL (manual config) | — |
| `OAUTH_USER_INFO_URL` | OAuth User Info URL (manual config) | — |
| `OAUTH_SCOPES` | OAuth Scopes (space-separated) | `openid profile email` |
| `SMTP_HOST` | SMTP Server Host (for email features) | — |
| `SMTP_PORT` | SMTP Server Port | `587` |
| `SMTP_USER` | SMTP Username | — |
| `SMTP_PASS` | SMTP Password | — |
| `SMTP_FROM` | Default FROM address for emails | — |
| `SMTP_SECURE` | Use secure SMTP connection (`true` or `false`) | `false` |
| `S3_ACCESS_KEY_ID` | S3 Access Key | — |
| `S3_SECRET_ACCESS_KEY` | S3 Secret Key | — |
| `S3_REGION` | S3 Region | `us-east-1` |
| `S3_ENDPOINT` | S3-compatible Endpoint URL | — |
| `S3_BUCKET` | S3 Bucket Name | — |
| `S3_FORCE_PATH_STYLE` | Use path-style URLs for S3 (set `true` for MinIO/SeaweedFS) | `false` |
| `FLAG_DEBUG_PRINTER` | Used for debugging the printer route | `false` |
| `FLAG_DISABLE_SIGNUPS` | Disables new user signups | `false` |
| `FLAG_DISABLE_EMAIL_AUTH` | Disables email/password login (SSO only) | `false` |
| `FLAG_DISABLE_IMAGE_PROCESSING` | Disables image processing | `false` |
| Variable | Description | Default |
| ------------------------------------- | ----------------------------------------------------------- | ---------------------- |
| `PRINTER_APP_URL` | Public URL for the printer to access the Application | — |
| `GOOGLE_CLIENT_ID` | Google OAuth Client ID | — |
| `GOOGLE_CLIENT_SECRET` | Google OAuth Client Secret | — |
| `GITHUB_CLIENT_ID` | GitHub OAuth Client ID | — |
| `GITHUB_CLIENT_SECRET` | GitHub OAuth Client Secret | — |
| `LINKEDIN_CLIENT_ID` | LinkedIn OAuth Client ID | — |
| `LINKEDIN_CLIENT_SECRET` | LinkedIn OAuth Client Secret | — |
| `OAUTH_PROVIDER_NAME` | Custom OAuth Provider Name | — |
| `OAUTH_CLIENT_ID` | Custom OAuth Client ID | — |
| `OAUTH_CLIENT_SECRET` | Custom OAuth Client Secret | — |
| `OAUTH_DISCOVERY_URL` | OIDC Discovery URL (use this OR manual URLs below) | — |
| `OAUTH_AUTHORIZATION_URL` | OAuth Authorization URL (manual config) | — |
| `OAUTH_TOKEN_URL` | OAuth Token URL (manual config) | — |
| `OAUTH_USER_INFO_URL` | OAuth User Info URL (manual config) | — |
| `OAUTH_DYNAMIC_CLIENT_REDIRECT_HOSTS` | Trusted HTTPS hosts/origins for dynamic OAuth redirects | — |
| `OAUTH_SCOPES` | OAuth Scopes (space-separated) | `openid profile email` |
| `BETTER_AUTH_API_KEY` | Better Auth dashboard API key | — |
| `BETTER_AUTH_URL` | Better Auth base URL override (advanced) | `APP_URL` |
| `BETTER_AUTH_SECRET` | Better Auth secret override (advanced) | `AUTH_SECRET` |
| `AI_ALLOWED_BASE_URLS` | Allowlist for custom AI provider base URLs | — |
| `SMTP_HOST` | SMTP Server Host (for email features) | — |
| `SMTP_PORT` | SMTP Server Port | `587` |
| `SMTP_USER` | SMTP Username | — |
| `SMTP_PASS` | SMTP Password | — |
| `SMTP_FROM` | Default FROM address for emails | — |
| `SMTP_SECURE` | Use secure SMTP connection (`true` or `false`) | `false` |
| `S3_ACCESS_KEY_ID` | S3 Access Key | — |
| `S3_SECRET_ACCESS_KEY` | S3 Secret Key | — |
| `S3_REGION` | S3 Region | `us-east-1` |
| `S3_ENDPOINT` | S3-compatible Endpoint URL | — |
| `S3_BUCKET` | S3 Bucket Name | — |
| `S3_FORCE_PATH_STYLE` | Use path-style URLs for S3 (set `true` for MinIO/SeaweedFS) | `false` |
| `FLAG_DEBUG_PRINTER` | Used for debugging the printer route | `false` |
| `FLAG_DISABLE_SIGNUPS` | Disables new user signups | `false` |
| `FLAG_DISABLE_EMAIL_AUTH` | Disables email/password login (SSO only) | `false` |
| `FLAG_DISABLE_IMAGE_PROCESSING` | Disables image processing | `false` |
> **Note:** Some variables are only required for using related features (OAuth, SMTP, S3, etc.) and can be left unset if unused.
> **Health check behavior:** `/api/health` reports status for database, printer, and storage. A failure in any dependency returns HTTP `503`.
<Note>
**Hybrid Setup Note**: The `PRINTER_APP_URL` variable is required when running Reactive Resume outside of Docker while
the printer service is running inside Docker. In this scenario, the printer needs to reach your local app to render
+4
View File
@@ -132,6 +132,10 @@ Reactive Resume includes a variety of professionally designed templates. Each te
<img src="/images/templates/leafish.webp" alt="Leafish template preview" style={{ aspectRatio: "210/297" }} />
</Frame>
<Frame caption="Meowth">
<img src="/images/templates/meowth.webp" alt="Meowth template preview" style={{ aspectRatio: "210/297" }} />
</Frame>
<Frame caption="Onyx">
<img src="/images/templates/onyx.webp" alt="Onyx template preview" style={{ aspectRatio: "210/297" }} />
</Frame>
+2 -1
View File
@@ -127,7 +127,7 @@ The exported JSON follows the Reactive Resume schema. Here's a high-level overvi
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"properties": {
"picture": {
@@ -937,6 +937,7 @@ The exported JSON follows the Reactive Resume schema. Here's a high-level overvi
"kakuna",
"lapras",
"leafish",
"meowth",
"onyx",
"pikachu",
"rhyhorn"
+587 -38
View File
@@ -24,7 +24,7 @@ The Reactive Resume JSON Schema is publicly available at:
<Info>[https://rxresu.me/schema.json](https://rxresu.me/schema.json)</Info>
This endpoint returns a complete JSON Schema (draft 2020-12 compatible) that describes the structure of a Reactive Resume document. It includes:
This endpoint returns a complete JSON Schema (draft 07 compatible) that describes the structure of a Reactive Resume document. It includes:
- **All resume sections**: basics, summary, experience, education, projects, skills, languages, interests, awards, certifications, publications, volunteer work, and references
- **Custom sections**: user-defined sections with arbitrary content
@@ -149,7 +149,7 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
},
"url": {
"type": "string",
"description": "The URL to the picture to display on the resume. Must be a valid URL with a protocol (http:// or https://)."
"description": "The URL to the picture to display on the resume. Prefer local app-served paths (for example /uploads/...) populated via upload."
},
"size": {
"type": "number",
@@ -221,17 +221,7 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"description": "The headline of the author of the resume."
},
"email": {
"anyOf": [
{
"type": "string",
"format": "email",
"pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
},
{
"type": "string",
"const": ""
}
],
"type": "string",
"description": "The email address of the author of the resume."
},
"phone": {
@@ -277,16 +267,7 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
},
"link": {
"default": "",
"anyOf": [
{
"type": "string",
"format": "uri"
},
{
"type": "string",
"const": ""
}
],
"type": "string",
"description": "If the custom field should be a link, the URL to link to."
}
},
@@ -355,10 +336,31 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"icon": {
"type": "string",
"description": "The icon to display for the custom field. Must be a valid icon name from @phosphor-icons/web icon set, or an empty string to hide. Default to '' (empty string) when unsure which icons are available."
},
"iconColor": {
"default": "",
"description": "Optional custom color for the icon, defined as rgba(r, g, b, a). Leave blank to use the template default icon color.",
"type": "string"
},
"network": {
"type": "string",
"minLength": 1,
@@ -385,7 +387,7 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"description": "The link to the profile of the author on the network or platform, if any."
}
},
"required": ["id", "hidden", "icon", "network", "username", "website"],
"required": ["id", "hidden", "icon", "iconColor", "network", "username", "website"],
"additionalProperties": false
},
"description": "The items to display in the profiles section."
@@ -423,6 +425,22 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"company": {
"type": "string",
"minLength": 1,
@@ -430,7 +448,7 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
},
"position": {
"type": "string",
"description": "The position held at the company or organization."
"description": "The position held at the company or organization. Used when there is only a single role. If multiple roles are provided in the 'roles' field, this serves as a summary title or can be left blank."
},
"location": {
"type": "string",
@@ -438,7 +456,7 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
},
"period": {
"type": "string",
"description": "The period of time the author was employed at the company or organization."
"description": "The overall period of time at the company. When multiple roles are used, this should reflect the total tenure."
},
"website": {
"type": "object",
@@ -459,9 +477,47 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"description": {
"type": "string",
"description": "The description of the experience. This should be a HTML-formatted string."
},
"roles": {
"default": [],
"description": "Optional list of individual roles held at this company to show career progression.",
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The unique identifier for the role. Usually generated as a UUID."
},
"position": {
"type": "string",
"description": "The position or job title for this role."
},
"period": {
"type": "string",
"description": "The period of time this role was held."
},
"description": {
"type": "string",
"description": "The description of this specific role. This should be a HTML-formatted string."
}
},
"required": ["id", "position", "period", "description"],
"additionalProperties": false
}
}
},
"required": ["id", "hidden", "company", "position", "location", "period", "website", "description"],
"required": [
"id",
"hidden",
"company",
"position",
"location",
"period",
"website",
"description",
"roles"
],
"additionalProperties": false
},
"description": "The items to display in the experience section."
@@ -499,6 +555,22 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"school": {
"type": "string",
"minLength": 1,
@@ -594,6 +666,22 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"name": {
"type": "string",
"minLength": 1,
@@ -662,10 +750,31 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"icon": {
"type": "string",
"description": "The icon to display for the custom field. Must be a valid icon name from @phosphor-icons/web icon set, or an empty string to hide. Default to '' (empty string) when unsure which icons are available."
},
"iconColor": {
"default": "",
"description": "Optional custom color for the icon, defined as rgba(r, g, b, a). Leave blank to use the template default icon color.",
"type": "string"
},
"name": {
"type": "string",
"minLength": 1,
@@ -691,7 +800,7 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
}
}
},
"required": ["id", "hidden", "icon", "name", "proficiency", "level", "keywords"],
"required": ["id", "hidden", "icon", "iconColor", "name", "proficiency", "level", "keywords"],
"additionalProperties": false
},
"description": "The items to display in the skills section."
@@ -729,6 +838,22 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"language": {
"type": "string",
"minLength": 1,
@@ -784,10 +909,31 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"icon": {
"type": "string",
"description": "The icon to display for the custom field. Must be a valid icon name from @phosphor-icons/web icon set, or an empty string to hide. Default to '' (empty string) when unsure which icons are available."
},
"iconColor": {
"default": "",
"description": "Optional custom color for the icon, defined as rgba(r, g, b, a). Leave blank to use the template default icon color.",
"type": "string"
},
"name": {
"type": "string",
"minLength": 1,
@@ -802,7 +948,7 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
}
}
},
"required": ["id", "hidden", "icon", "name", "keywords"],
"required": ["id", "hidden", "icon", "iconColor", "name", "keywords"],
"additionalProperties": false
},
"description": "The items to display in the interests section."
@@ -840,6 +986,22 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"title": {
"type": "string",
"minLength": 1,
@@ -912,6 +1074,22 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"title": {
"type": "string",
"minLength": 1,
@@ -984,6 +1162,22 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"title": {
"type": "string",
"minLength": 1,
@@ -1056,6 +1250,22 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"organization": {
"type": "string",
"minLength": 1,
@@ -1128,6 +1338,22 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"name": {
"type": "string",
"minLength": 1,
@@ -1214,6 +1440,7 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": {
"type": "string",
"enum": [
"summary",
"profiles",
"experience",
"education",
@@ -1225,7 +1452,8 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"certifications",
"publications",
"volunteer",
"references"
"references",
"cover-letter"
],
"description": "The type of items this custom section contains. Determines which item schema and form fields to use."
},
@@ -1244,10 +1472,105 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"recipient": {
"type": "string",
"description": "The recipient's address block as HTML (name, title, company, address, email)."
},
"content": {
"type": "string",
"description": "The cover letter body as HTML (salutation, paragraphs, closing, signature)."
}
},
"required": ["id", "hidden", "recipient", "content"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The unique identifier for the item. Usually generated as a UUID."
},
"hidden": {
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"content": {
"type": "string",
"description": "The rich text content of the summary item. This should be a HTML-formatted string."
}
},
"required": ["id", "hidden", "content"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The unique identifier for the item. Usually generated as a UUID."
},
"hidden": {
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"icon": {
"type": "string",
"description": "The icon to display for the custom field. Must be a valid icon name from @phosphor-icons/web icon set, or an empty string to hide. Default to '' (empty string) when unsure which icons are available."
},
"iconColor": {
"default": "",
"description": "Optional custom color for the icon, defined as rgba(r, g, b, a). Leave blank to use the template default icon color.",
"type": "string"
},
"network": {
"type": "string",
"minLength": 1,
@@ -1274,7 +1597,7 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"description": "The link to the profile of the author on the network or platform, if any."
}
},
"required": ["id", "hidden", "icon", "network", "username", "website"],
"required": ["id", "hidden", "icon", "iconColor", "network", "username", "website"],
"additionalProperties": false
},
{
@@ -1288,6 +1611,22 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"company": {
"type": "string",
"minLength": 1,
@@ -1295,7 +1634,7 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
},
"position": {
"type": "string",
"description": "The position held at the company or organization."
"description": "The position held at the company or organization. Used when there is only a single role. If multiple roles are provided in the 'roles' field, this serves as a summary title or can be left blank."
},
"location": {
"type": "string",
@@ -1303,7 +1642,7 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
},
"period": {
"type": "string",
"description": "The period of time the author was employed at the company or organization."
"description": "The overall period of time at the company. When multiple roles are used, this should reflect the total tenure."
},
"website": {
"type": "object",
@@ -1324,9 +1663,47 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"description": {
"type": "string",
"description": "The description of the experience. This should be a HTML-formatted string."
},
"roles": {
"default": [],
"description": "Optional list of individual roles held at this company to show career progression.",
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The unique identifier for the role. Usually generated as a UUID."
},
"position": {
"type": "string",
"description": "The position or job title for this role."
},
"period": {
"type": "string",
"description": "The period of time this role was held."
},
"description": {
"type": "string",
"description": "The description of this specific role. This should be a HTML-formatted string."
}
},
"required": ["id", "position", "period", "description"],
"additionalProperties": false
}
}
},
"required": ["id", "hidden", "company", "position", "location", "period", "website", "description"],
"required": [
"id",
"hidden",
"company",
"position",
"location",
"period",
"website",
"description",
"roles"
],
"additionalProperties": false
},
{
@@ -1340,6 +1717,22 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"school": {
"type": "string",
"minLength": 1,
@@ -1411,6 +1804,22 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"name": {
"type": "string",
"minLength": 1,
@@ -1455,10 +1864,31 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"icon": {
"type": "string",
"description": "The icon to display for the custom field. Must be a valid icon name from @phosphor-icons/web icon set, or an empty string to hide. Default to '' (empty string) when unsure which icons are available."
},
"iconColor": {
"default": "",
"description": "Optional custom color for the icon, defined as rgba(r, g, b, a). Leave blank to use the template default icon color.",
"type": "string"
},
"name": {
"type": "string",
"minLength": 1,
@@ -1484,7 +1914,7 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
}
}
},
"required": ["id", "hidden", "icon", "name", "proficiency", "level", "keywords"],
"required": ["id", "hidden", "icon", "iconColor", "name", "proficiency", "level", "keywords"],
"additionalProperties": false
},
{
@@ -1498,6 +1928,22 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"language": {
"type": "string",
"minLength": 1,
@@ -1529,10 +1975,31 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"icon": {
"type": "string",
"description": "The icon to display for the custom field. Must be a valid icon name from @phosphor-icons/web icon set, or an empty string to hide. Default to '' (empty string) when unsure which icons are available."
},
"iconColor": {
"default": "",
"description": "Optional custom color for the icon, defined as rgba(r, g, b, a). Leave blank to use the template default icon color.",
"type": "string"
},
"name": {
"type": "string",
"minLength": 1,
@@ -1547,7 +2014,7 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
}
}
},
"required": ["id", "hidden", "icon", "name", "keywords"],
"required": ["id", "hidden", "icon", "iconColor", "name", "keywords"],
"additionalProperties": false
},
{
@@ -1561,6 +2028,22 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"title": {
"type": "string",
"minLength": 1,
@@ -1609,6 +2092,22 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"title": {
"type": "string",
"minLength": 1,
@@ -1657,6 +2156,22 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"title": {
"type": "string",
"minLength": 1,
@@ -1705,6 +2220,22 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"organization": {
"type": "string",
"minLength": 1,
@@ -1753,6 +2284,22 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"type": "boolean",
"description": "Whether to hide the item from the resume."
},
"options": {
"description": "Display options for this item.",
"default": {
"showLinkInTitle": false
},
"type": "object",
"properties": {
"showLinkInTitle": {
"default": false,
"description": "If true, the website URL is rendered as a hyperlink on the title instead of a separate link at the bottom.",
"type": "boolean"
}
},
"required": ["showLinkInTitle"],
"additionalProperties": false
},
"name": {
"type": "string",
"minLength": 1,
@@ -1811,13 +2358,14 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"azurill",
"bronzor",
"chikorita",
"ditto",
"ditgar",
"ditto",
"gengar",
"glalie",
"kakuna",
"lapras",
"leafish",
"meowth",
"onyx",
"pikachu",
"rhyhorn"
@@ -1907,9 +2455,10 @@ Below is the complete JSON Schema for Reactive Resume. You can also fetch the la
"description": "The vertical margin of the page, defined in points (pt)."
},
"format": {
"default": "a4",
"type": "string",
"enum": ["a4", "letter", "free-form"],
"description": "The format of the page. Can be 'a4', 'letter', or 'free-form'."
"description": "The format of the page. Can be 'a4', 'letter' or 'free-form'."
},
"locale": {
"default": "en-US",
+6 -12
View File
@@ -3,15 +3,6 @@ title: "Setting up passkeys"
description: "Learn how to register passkeys (WebAuthn) to sign in securely using biometrics or your device PIN"
---
<Warning>
<strong>Passkeys Temporarily Disabled:</strong> Passkey registration and sign-in are currently unavailable in Reactive Resume due to an upstream issue with our authentication provider. We are closely monitoring the situation and will re-enable passkeys once it is resolved.
For more details and technical updates, see the <a href="https://github.com/better-auth/better-auth/issues/7463" target="_blank" rel="noopener">GitHub issue #7463</a>.
</Warning>
---
<Steps>
<Step title="Sign in to the dashboard">
Head over to [https://rxresu.me](https://rxresu.me) and sign in with your account credentials.
@@ -27,8 +18,7 @@ For more details and technical updates, see the <a href="https://github.com/bett
</Step>
<Step title="Name your passkey">
You'll be prompted to enter a name for the passkey. Use something descriptive so you can recognize it later (for
example, “MacBook Touch ID” or “iPhone Face ID”).
Enter a descriptive name so you can recognize it later (for example, "MacBook Touch ID" or "iPhone Face ID").
</Step>
<Step title="Complete the passkey prompt">
@@ -41,11 +31,15 @@ For more details and technical updates, see the <a href="https://github.com/bett
</Step>
<Step title="Manage passkeys (optional)">
After registering, your passkey will show up in the list. You can delete a passkey using the trash icon next to it.
After registering, your passkey will show up in the list. You can rename it or delete it from the same section.
<Warning>
Deleting a passkey cannot be undone. After deletion, you won't be able to sign in using that passkey anymore.
</Warning>
</Step>
<Step title="Sign in with a passkey">
On the login page, click <Badge>Sign in with Passkey</Badge> to authenticate without entering your password.
</Step>
</Steps>
+183 -27
View File
@@ -12,7 +12,17 @@ The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is a standar
## Prerequisites
<Steps>
<Step title="Create an API key">
<Step title="Choose your authentication method">
Reactive Resume MCP supports two authentication methods:
- **OAuth2 (recommended):** best user experience for clients that support MCP OAuth.
- **API key (fallback):** works in all clients that can send custom headers.
Use OAuth2 whenever your MCP client supports it. Use API key only when OAuth is unavailable in that client.
</Step>
<Step title="If using API key, create one">
Head over to [https://rxresu.me](https://rxresu.me) (or your self-hosted instance), sign in, and navigate to **Settings → API Keys**. Click **Create a new API key**, give it a name, and copy the secret — it's only shown once.
For the full walkthrough, see [Using the API](/guides/using-the-api).
@@ -22,11 +32,31 @@ The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is a standar
## Configuration
There are two ways to connect, depending on whether your MCP client supports the Streamable HTTP transport natively.
There are two transport options, and each can use either OAuth2 or API key depending on your client capabilities.
### Method 1: Streamable HTTP (recommended)
If your client supports the `url` field (e.g. **Cursor**), use this — no extra dependencies required:
If your client supports the `url` field (e.g. **Cursor**, **Codex**, Claude custom connectors), use this.
#### Option A: OAuth2 (recommended)
Most OAuth-capable clients only need the MCP URL:
```json
{
"mcpServers": {
"reactive-resume": {
"url": "https://rxresu.me/mcp"
}
}
}
```
Then connect/sign in from the client UI (or with the client's OAuth login command).
#### Option B: API key (fallback)
If OAuth is not supported in your client, send `x-api-key`:
```json
{
@@ -43,7 +73,9 @@ If your client supports the `url` field (e.g. **Cursor**), use this — no extra
### Method 2: mcp-remote
If your client only supports `command` / `args` (e.g. **Claude Desktop**), use [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) as a bridge. This requires [Node.js](https://nodejs.org) **20 or later**.
If your client only supports `command` / `args` (for example, local-only Claude Desktop config), use [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) as a bridge. This requires [Node.js](https://nodejs.org) **20 or later**.
`mcp-remote` is most commonly used with API keys:
```json
{
@@ -63,9 +95,103 @@ If your client only supports `command` / `args` (e.g. **Claude Desktop**), use [
| Client | Config file |
| ----------------- | ------------------------------------------------------------------------------------------------ |
| Cursor | `.cursor/mcp.json` in your project or home directory |
| Claude Desktop | `claude_desktop_config.json` ([docs](https://modelcontextprotocol.io/docs/tools/claude-desktop)) |
| Claude Desktop | `claude_desktop_config.json` ([docs](https://modelcontextprotocol.io/quickstart/user)) |
| Codex | `~/.codex/config.toml` or `.codex/config.toml` ([docs](https://developers.openai.com/codex/mcp)) |
| Other MCP clients | Refer to the client's documentation |
## Authentication Details (How Reactive Resume MCP Works)
Reactive Resume MCP accepts authentication in this order:
1. **Bearer token (OAuth2 access token)** via `Authorization: Bearer <token>`
2. **API key fallback** via `x-api-key: <key>`
If neither is valid, the MCP endpoint responds with `401` and advertises OAuth metadata using:
- `WWW-Authenticate: Bearer resource_metadata="<instance>/.well-known/oauth-protected-resource"`
This lets OAuth-capable MCP clients discover and complete the OAuth flow automatically.
### OAuth2 flow used by this server
Reactive Resume is configured as an OAuth authorization server for MCP clients:
- The MCP endpoint is `https://rxresu.me/mcp`.
- OAuth discovery metadata is exposed under `/.well-known/*` endpoints.
- The login/authorization route is `/auth/oauth`.
- If the user is not signed in, `/auth/oauth` redirects to `/auth/login`, then resumes OAuth.
- If the user is signed in, `/auth/oauth` validates `client_id` and `redirect_uri`, issues an authorization code, and redirects back to the client.
- PKCE parameters (`code_challenge`, `code_challenge_method`) are preserved in the authorization flow.
## Popular Client Setup
### Cursor
**OAuth2 (recommended):**
```json
{
"mcpServers": {
"reactive-resume": {
"url": "https://rxresu.me/mcp"
}
}
}
```
**API key fallback:**
```json
{
"mcpServers": {
"reactive-resume": {
"url": "https://rxresu.me/mcp",
"headers": {
"x-api-key": "your-api-key"
}
}
}
}
```
### Codex (CLI / IDE extension)
Add server:
```bash
codex mcp add reactive-resume --url https://rxresu.me/mcp
```
Then log in with OAuth:
```bash
codex mcp login reactive-resume
```
API key fallback (`config.toml`):
```toml
[mcp_servers."reactive-resume"]
url = "https://rxresu.me/mcp"
http_headers = { "x-api-key" = "your-api-key" }
```
### Claude (web app custom connector)
Add `https://rxresu.me/mcp` as a custom remote MCP connector, then connect with OAuth in Claude's connector UI.
### Claude Desktop (local config file)
Use `mcp-remote` bridge with API key (example shown above in **Method 2**).
## External References
- [Cursor MCP docs](https://cursor.sh/docs/mcp)
- [MCP quickstart for users (Claude Desktop example)](https://modelcontextprotocol.io/quickstart/user)
- [OpenAI Codex MCP docs](https://developers.openai.com/codex/mcp)
- [Claude custom connectors (remote MCP)](https://claude.com/docs/connectors/custom/remote-mcp)
- [MCP Authorization spec](https://modelcontextprotocol.io/specification/latest/basic/authorization)
## Self-Hosting
If you're running a self-hosted Reactive Resume instance, replace `https://rxresu.me/mcp` with your instance URL:
@@ -81,32 +207,56 @@ If you're running a self-hosted Reactive Resume instance, replace `https://rxres
## Available Tools
The MCP server exposes the following tools:
Tool names use a `reactive_resume_` prefix so they stay distinct when multiple MCP servers are enabled in the same client.
| Tool | Description |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `list_resumes` | List all resumes with IDs, names, tags, and status. Supports filtering by tags and sorting by last updated, creation date, or name |
| `get_resume` | Get the full data of a specific resume by ID |
| `create_resume` | Create a new, empty resume with a name and slug. Optionally pre-fill with sample data |
| `duplicate_resume` | Create a copy of an existing resume with a new name and slug |
| `patch_resume` | Apply JSON Patch (RFC 6902) operations to modify a resume's data |
| `delete_resume` | Permanently delete a resume and all associated files. **Irreversible** |
| `lock_resume` | Lock a resume to prevent edits, patches, and deletion |
| `unlock_resume` | Unlock a previously locked resume to re-enable editing |
| `export_resume_pdf` | Generate a PDF from the resume and return a download URL |
| `get_resume_screenshot` | Get a visual preview of the resume's first page as a WebP image URL |
| `get_resume_statistics` | Get view and download statistics for a resume |
| Tool | Description |
| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `reactive_resume_list_resumes` | List all resumes with IDs, names, tags, and status. Supports filtering by tags and sorting by last updated, creation date, or name |
| `reactive_resume_list_resume_tags` | List every distinct tag in use across your resumes (sorted) |
| `reactive_resume_get_resume` | Get the full data of a specific resume by ID |
| `reactive_resume_get_resume_analysis` | Get the latest saved AI analysis for a resume (from the web app), if any |
| `reactive_resume_create_resume` | Create a new, empty resume with a name and slug. Optionally pre-fill with sample data |
| `reactive_resume_import_resume` | Create a resume from a full ResumeData JSON export (random name/slug). Large files may exceed client limits |
| `reactive_resume_duplicate_resume` | Create a copy of an existing resume with a new name and slug |
| `reactive_resume_patch_resume` | Apply JSON Patch (RFC 6902) operations to modify a resume's data |
| `reactive_resume_update_resume` | Update metadata only: name, slug, tags, `isPublic`. Returns canonical share URL; passwords are not managed via MCP |
| `reactive_resume_delete_resume` | Permanently delete a resume and all associated files. **Irreversible** |
| `reactive_resume_lock_resume` | Lock a resume to prevent edits, patches, and deletion |
| `reactive_resume_unlock_resume` | Unlock a previously locked resume to re-enable editing |
| `reactive_resume_export_resume_pdf` | Generate a PDF from the resume and return a download URL |
| `reactive_resume_get_resume_screenshot` | Get a visual preview of the resume's first page as a WebP image URL |
| `reactive_resume_get_resume_statistics` | Get view and download statistics for a resume |
### Breaking change (tool names)
Older clients may refer to unprefixed names (`list_resumes`, `get_resume`, …) or dot-separated names (`reactive_resume.list_resumes`, …). Those names are no longer used; update automations and saved prompts to the `reactive_resume_*` names above.
## Available Resources
| Resource | Description |
| ----------------- | --------------------------------------------------------------------------------------------------------------- |
| `resume://{id}` | The full resume data as a readable JSON resource. Lists all resumes and supports reading individual ones by ID |
| `resume://schema` | The ResumeData JSON Schema — reference this to understand valid paths and value types for JSON Patch operations |
Resources follow MCP conventions: **static** items appear in `resources/list`; **parameterized** access is declared in `resources/templates/list` and read via `resources/read` once you know the ID.
| Discovery | What you get |
| ------------------------------------- | --------------------------------------------------------------------------------------------- |
| `resources/list` | Static resources only — currently **`resume://_meta/schema`** (ResumeData JSON Schema) |
| `resources/templates/list` | **`resume://{id}`** — template for reading full resume JSON by ID (not enumerated per resume) |
| `reactive_resume_list_resumes` (tool) | **Primary way to discover resume IDs** — resumes are not listed as separate MCP resources |
| URI | Description |
| ----------------------- | ------------------------------------------------------------------------ |
| `resume://_meta/schema` | ResumeData JSON Schema — use for valid JSON Patch paths and value types |
| `resume://{id}` | Full resume data as JSON — use an ID from `reactive_resume_list_resumes` |
### Breaking change (schema URI)
The schema resource was previously `resume://schema`. It is now **`resume://_meta/schema`**. Update any saved prompts, automations, or client configs that referenced the old URI.
### Static server card (`/.well-known/mcp/server-card.json`)
`GET /.well-known/mcp/server-card.json` returns a JSON document ([SEP-1649](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1649)) with `serverInfo`, optional authentication metadata, and summaries of tools, resources, resource templates, and prompts. It is generated to match the live MCP server and can be used for discovery when a client cannot run a full capability scan against `/mcp/`.
## Available Prompts
Prompts are pre-built workflows that provide the AI with structured instructions and context. Each prompt embeds the resume data and schema automatically.
Prompts are pre-built workflows that provide the AI with structured instructions and context. Each prompt embeds the resume data and the schema resource (`resume://_meta/schema`) automatically.
| Prompt | Description |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
@@ -129,7 +279,10 @@ Once your MCP client is connected, you can use natural language to interact with
### Creating & Managing
- "Create a new resume called 'Frontend Engineer 2026'"
- "Import this exported ResumeData JSON as a new resume"
- "What tags do I use across my resumes?"
- "Duplicate my 'Software Engineer' resume for a product manager role"
- "Make my resume public and give me the share link"
- "Lock my finalized resume so it can't be accidentally edited"
- "Delete my old draft resume"
@@ -160,16 +313,19 @@ Once your MCP client is connected, you can use natural language to interact with
- "Tailor my resume for this job description: ..." (uses `tailor_resume`)
<Tip>
The AI will use `get_resume` to inspect your current resume before making changes with `patch_resume`. This ensures
the correct JSON paths are used.
The AI will use `reactive_resume_get_resume` to inspect your current resume before making changes with
`reactive_resume_patch_resume`. This ensures the correct JSON paths are used. Use `reactive_resume_update_resume` for
name, slug, tags, and public visibility (not for section content).
</Tip>
## Troubleshooting
| Issue | Solution |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| "Unauthorized" with no login prompt | Your client may not support MCP OAuth discovery. Use API key mode (`x-api-key`) |
| OAuth login opens but fails redirect/callback | Confirm your client's MCP OAuth callback settings and retry the connection |
| "API error (401)" | Your API key is invalid or expired. Create a new one in **Settings → API Keys** |
| "API error (404)" | The resume ID doesn't exist. Use `list_resumes` to find valid IDs |
| "API error (404)" | The resume ID doesn't exist. Use `reactive_resume_list_resumes` to find valid IDs |
| "API error (403)" | The resume is locked. Unlock it in the Reactive Resume dashboard |
| Connection refused | Check that the URL is correct and the instance is running |
| "ReferenceError: File is not defined" when using `mcp-remote` | You're running Node.js 18. `mcp-remote` requires **Node.js 20 or later** — upgrade with `nvm use 20` or `nvm alias default 20` |
Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

+96 -44
View File
@@ -54,9 +54,9 @@ APP_URL="http://localhost:3000"
PRINTER_APP_URL="http://host.docker.internal:3000"
# --- Printer ---
# If using browserless with token authentication, include the token as a query parameter:
# PRINTER_ENDPOINT="ws://printer:3000?token=your-secret-token"
PRINTER_ENDPOINT="ws://printer:3000"
# Keep this token in sync with the Browserless TOKEN value.
BROWSERLESS_TOKEN="change-me"
PRINTER_ENDPOINT="ws://printer:3000?token=change-me"
# --- Database (PostgreSQL) ---
DATABASE_URL="postgresql://postgres:postgres@postgres:5432/postgres"
@@ -64,6 +64,8 @@ DATABASE_URL="postgresql://postgres:postgres@postgres:5432/postgres"
# --- Authentication ---
# Generated using `openssl rand -hex 32`
AUTH_SECRET=""
# Better Auth dashboard API key (optional)
BETTER_AUTH_API_KEY=""
# Social Auth (Google, optional)
GOOGLE_CLIENT_ID=""
@@ -73,6 +75,10 @@ GOOGLE_CLIENT_SECRET=""
GITHUB_CLIENT_ID=""
GITHUB_CLIENT_SECRET=""
# Social Auth (LinkedIn, optional)
LINKEDIN_CLIENT_ID=""
LINKEDIN_CLIENT_SECRET=""
# Custom OAuth Provider
OAUTH_PROVIDER_NAME=""
OAUTH_CLIENT_ID=""
@@ -83,20 +89,30 @@ OAUTH_DISCOVERY_URL=""
OAUTH_AUTHORIZATION_URL=""
OAUTH_TOKEN_URL=""
OAUTH_USER_INFO_URL=""
OAUTH_DYNAMIC_CLIENT_REDIRECT_HOSTS=""
# Custom scopes (space-separated, defaults to "openid profile email")
OAUTH_SCOPES=""
# Optional Better Auth runtime overrides for advanced deployments:
# BETTER_AUTH_URL="https://auth.example.com"
# BETTER_AUTH_SECRET=""
# --- AI (optional) ---
# Comma-separated hostnames/origins for custom AI base URLs
# Example: api.openai.com,https://gateway.ai.vercel.com
AI_ALLOWED_BASE_URLS=""
# --- Email (optional) ---
# If all keys are disabled, the app logs the email to be sent to the console instead.
SMTP_HOST=""
SMTP_PORT="465"
SMTP_PORT="587"
SMTP_USER=""
SMTP_PASS=""
SMTP_FROM="Reactive Resume <noreply@rxresu.me>"
SMTP_SECURE="false"
# --- Storage (optional) ---
# If all keys are disabled, the app uses local filesystem (/data) to store uploads instead.
# If all keys are disabled, the app uses local filesystem (usually /app/data) to store uploads instead.
# Make sure to mount this directory to a volume or the host filesystem to ensure data integrity.
S3_ACCESS_KEY_ID=""
S3_SECRET_ACCESS_KEY=""
@@ -111,6 +127,7 @@ S3_FORCE_PATH_STYLE="false"
FLAG_DEBUG_PRINTER="false"
FLAG_DISABLE_SIGNUPS="false"
FLAG_DISABLE_EMAIL_AUTH="false"
FLAG_DISABLE_IMAGE_PROCESSING="false"
```
</Step>
@@ -130,9 +147,6 @@ FLAG_DISABLE_EMAIL_AUTH="false"
[byte[]]$bytes = New-Object byte[] 32; (New-Object System.Security.Cryptography.RNGCryptoServiceProvider).GetBytes($bytes); $bytes | ForEach-Object { "{0:x2}" -f $_ } | Out-String -Stream | ForEach-Object { $_.Trim() } | Write-Host -NoNewline
```
```cmd Windows (alternative)
certutil -generateSRS 32 | findstr /r /v "^$" | findstr /v ":" | findstr /v " " | findstr /v "-" | findstr /v "certutil"
```
</CodeGroup>
</Step>
@@ -168,10 +182,9 @@ services:
- HEALTH=true
- CONCURRENT=20
- QUEUED=10
# Optional: Set a token for authentication
# - TOKEN=your-secret-token
- TOKEN=${BROWSERLESS_TOKEN}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/pressure?token=your-secret-token"]
test: ["CMD-SHELL", 'curl -fsS "http://localhost:3000/pressure?token=${BROWSERLESS_TOKEN}" > /dev/null']
interval: 10s
timeout: 5s
retries: 10
@@ -278,11 +291,15 @@ docker compose logs -f reactive-resume
SMTP (<code>SMTP_&#42;</code>)
</li>
<li>
Social auth (<code>GOOGLE_&#42;</code>, <code>GITHUB_&#42;</code>)
Social auth (<code>GOOGLE_&#42;</code>, <code>GITHUB_&#42;</code>, <code>LINKEDIN_&#42;</code>,{" "}
<code>OAUTH_&#42;</code>)
</li>
<li>
S3 storage (<code>S3_&#42;</code>)
</li>
<li>
AI URL allowlist (<code>AI_ALLOWED_BASE_URLS</code>)
</li>
<li>
Feature flags (<code>FLAG_&#42;</code>)
</li>
@@ -298,7 +315,9 @@ docker compose logs -f reactive-resume
</Accordion>
<Accordion title="Printer">
- **`PRINTER_ENDPOINT`**: Base URL where Reactive Resume reaches the printer service. In Compose: `http://printer:3000`. If using browserless with token authentication, include the token as a query parameter: `ws://printer:3000?token=your-secret-token`.
- **`PRINTER_ENDPOINT`**: Endpoint where Reactive Resume connects to the printer browser.
- **Recommended (Browserless)**: `ws://printer:3000?token=...` and keep the token value in sync with Browserless `TOKEN`.
- **Also supported**: `http://chrome:9222` for Chrome DevTools Protocol endpoints.
<Note>
**Alternative to browserless**: You can use a lightweight headless Chrome Docker image like `chromedp/headless-shell`:
@@ -318,7 +337,9 @@ docker compose logs -f reactive-resume
<Accordion title="Database (PostgreSQL)">
- **`DATABASE_URL`**: Postgres connection string in the format `postgresql://USER:PASSWORD@HOST:PORT/DATABASE`. - In
Docker Compose, set `HOST` to the Postgres service name (e.g. `postgres`), not `localhost`.
Docker Compose, set `HOST` to the Postgres service name (e.g. `postgres`), not `localhost`. - If your password
contains special characters (`@`, `#`, `:`), URL-encode it. - For managed Postgres, add provider-specific params (for
example `?sslmode=require`) when needed.
</Accordion>
<Accordion title="Authentication">
@@ -338,9 +359,18 @@ openssl rand -hex 32
**`GITHUB_CLIENT_ID`** / **`GITHUB_CLIENT_SECRET`** (optional): Enables GitHub sign-in.
**`LINKEDIN_CLIENT_ID`** / **`LINKEDIN_CLIENT_SECRET`** (optional): Enables LinkedIn sign-in.
**`BETTER_AUTH_API_KEY`** (optional): Enables Better Auth dashboard integrations.
**`BETTER_AUTH_URL`** (optional, advanced): Overrides auth base URL if it must differ from `APP_URL` (for split-host deployments).
**`BETTER_AUTH_SECRET`** (optional, advanced): Overrides `AUTH_SECRET` for Better Auth internals.
**Custom OAuth provider** (optional):
- **`OAUTH_PROVIDER_NAME`**: Display name in the UI
- **`OAUTH_CLIENT_ID`** / **`OAUTH_CLIENT_SECRET`**: Required for any custom OAuth provider
- **`OAUTH_DYNAMIC_CLIENT_REDIRECT_HOSTS`**: Comma-separated allowlist for extra dynamic OAuth redirect hosts/origins (HTTPS only, non-private hosts).
- **`OAUTH_SCOPES`**: Space-separated scopes (defaults to `openid profile email`)
Configure endpoints using **one** of these methods:
@@ -352,8 +382,9 @@ openssl rand -hex 32
<Accordion title="Email (SMTP, optional)">
If SMTP is not configured, the app logs emails to the server console instead of sending them.
- Email delivery is enabled only when **all** of `SMTP_HOST`, `SMTP_USER`, `SMTP_PASS`, and `SMTP_FROM` are set.
- **`SMTP_HOST`**: SMTP host (if empty, email sending is disabled).
- **`SMTP_PORT`**: Usually `465` (implicit TLS) or `587` (STARTTLS).
- **`SMTP_PORT`**: Defaults to `587` in the app.
- **`SMTP_USER`** / **`SMTP_PASS`**: SMTP credentials.
- **`SMTP_FROM`**: Default from address (for example, `Reactive Resume <noreply@rxresu.me>`).
- **`SMTP_SECURE`**: `"true"` or `"false"` (string). Match your provider settings.
@@ -361,21 +392,23 @@ openssl rand -hex 32
</Accordion>
<Accordion title="Storage (S3 or local)">
- **Default (local)**: If all `S3_*` values are empty, uploads are stored under `/app/data`. Mount it to persistent
storage (for example `./data:/app/data`) or uploads can be lost on container recreation. - **S3/S3-compatible**:
Configure these to store uploads in an S3-compatible service (SeaweedFS, MinIO, AWS S3, etc.): -
**`S3_ACCESS_KEY_ID`** - **`S3_SECRET_ACCESS_KEY`** - **`S3_REGION`** - **`S3_ENDPOINT`** (for S3-compatible
providers; may be blank for AWS depending on your setup) - **`S3_BUCKET`** - **`S3_FORCE_PATH_STYLE`**: Controls how
the bucket is addressed in URLs. Defaults to `"false"`. - Set to `"true"` for **path-style** URLs
(`https://s3-server.com/bucket`). Common with **MinIO**, **SeaweedFS**, and other self-hosted S3-compatible services.
- Set to `"false"` for **virtual-hosted-style** URLs (`https://bucket.s3-server.com`). Common with **AWS S3**,
**Cloudflare R2**, and most cloud providers.
- **Default (local)**: If all `S3_*` values are empty, uploads are stored under `<cwd>/data` (usually `/app/data` in the official image).
- Mount local uploads to persistent storage (for example `./data:/app/data`) or uploads can be lost on container recreation.
- **S3/S3-compatible**: Configure `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, `S3_REGION`, `S3_ENDPOINT`, and `S3_BUCKET`.
- **`S3_FORCE_PATH_STYLE`** controls bucket addressing (defaults to `"false"`):
- `"true"` for path-style URLs (`https://endpoint/bucket`) common with MinIO/SeaweedFS.
- `"false"` for virtual-hosted-style URLs (`https://bucket.endpoint`) common with AWS S3 / Cloudflare R2.
</Accordion>
<Accordion title="AI (optional)">
- **`AI_ALLOWED_BASE_URLS`**: Comma-separated hosts or origins allowed as custom AI API base URLs. - Use this when
routing AI requests through your own gateway/proxy. - Example: `api.openai.com,https://gateway.ai.vercel.com`
</Accordion>
<Accordion title="Feature Flags">
- **`FLAG_DEBUG_PRINTER`**: Bypasses the printer-only access restriction (useful when debugging `/printer/{resumeId}`). Recommended: keep `"false"` in production.
- **`FLAG_DISABLE_SIGNUPS`**: Disables new signups (web app and server). Useful for private instances.
- **`FLAG_DISABLE_EMAIL_AUTH`**: Disables email/password login entirely. Also disables email verification, forgot password, and reset password flows. Users can still sign up via social auth (Google/GitHub/Custom OAuth), unless FLAG_DISABLE_SIGNUPS is also set to true. Useful when only SSO is required.
- **`FLAG_DISABLE_EMAIL_AUTH`**: Disables email/password login entirely. Also disables email verification, forgot password, and reset password flows. Users can still sign up via social auth (Google/GitHub/LinkedIn/Custom OAuth), unless FLAG_DISABLE_SIGNUPS is also set to true. Useful when only SSO is required.
- **`FLAG_DISABLE_IMAGE_PROCESSING`**: Disables image processing. This is useful if you are using a machine with limited resources, like a Raspberry Pi.
</Accordion>
</AccordionGroup>
@@ -384,24 +417,32 @@ openssl rand -hex 32
To update your Reactive Resume installation to the latest available version, follow these steps:
1. **Pull the latest images** for all services defined in your Docker Compose file.
1. **Back up your database and uploads first** (highly recommended before every update).
2. **Pull the latest images** for all services defined in your Docker Compose file.
```bash
docker compose pull
```
2. **Restart the containers** to run the new images.
3. **Restart the containers** to run the new images.
```bash
docker compose up -d
```
3. **(Optional) Remove old, unused Docker images** to free up disk space.
4. **Check migration/startup logs** after deploy.
```bash
docker compose logs -f reactive-resume
```
5. **(Optional) Remove old, unused Docker images** to free up disk space.
```bash
docker image prune -f
```
This process ensures your app, database, and printer are all up-to-date while keeping your data and configuration intact.
This process updates app services and automatically runs DB migrations on startup. If migration fails, restore from backup and fix configuration before retrying.
## Backups (recommended)
@@ -419,7 +460,7 @@ If you're using S3-compatible storage, consider enabling versioning on your buck
## Health Checks
Reactive Resume exposes a health check endpoint at `/api/health` that verifies the application and its dependencies are functioning correctly. If any critical service (such as the database connection) fails, the health check will return an unhealthy status.
Reactive Resume exposes a health check endpoint at `/api/health` that verifies the application and its dependencies. It checks **database**, **printer**, and **storage**; if any one is unhealthy, the endpoint returns HTTP `503`.
### How it works
@@ -442,7 +483,7 @@ Most reverse proxies (such as **Traefik**, **Caddy**, or **nginx** with upstream
- **Healthy containers** receive traffic as normal
- **Unhealthy containers** are automatically removed from the load balancer pool
This is particularly useful in high-availability setups where you have multiple instances of Reactive Resume. If one instance becomes unhealthy (for example, it loses its database connection), the reverse proxy will stop routing traffic to it until it recovers.
This is particularly useful in high-availability setups where you have multiple instances of Reactive Resume. If one instance becomes unhealthy (for example, it loses database, printer, or storage connectivity), the reverse proxy will stop routing traffic to it until it recovers.
<Tip>
If you're using **Traefik**, it automatically respects Docker health checks when using the Docker provider. Unhealthy
@@ -461,40 +502,51 @@ curl -f http://localhost:3000/api/health
docker compose ps
```
A healthy response returns HTTP 200. Any other response (or a connection failure) indicates a problem that should be investigated in the container logs.
A healthy response returns HTTP 200. Any other response (or a connection failure) indicates a problem that should be investigated in the JSON response body and container logs.
## Troubleshooting
<AccordionGroup>
<Accordion title="The app container exits immediately">
- **Common cause**: database migrations failed (often a bad `DATABASE_URL`).
- **What to do**:
When the app container exits right away, you'll want to check the logs for more information about the error. Run the following command to view real-time logs from the Reactive Resume container:
- **What to do**:
Check logs for migration errors and database connectivity details:
```bash
docker compose logs -f reactive-resume
```
</Accordion>
<Accordion title="Can't sign in / redirects loop / cookies don't stick">
- **Common cause**: `APP_URL` doesn't match the URL you're actually using (especially behind a reverse proxy). -
**Fix**: set `APP_URL` to the public URL (preferably HTTPS) and restart the container.
- **Common cause**: `APP_URL` doesn't match the URL you're actually using (especially behind a reverse proxy), or
you're serving HTTPS while `APP_URL` is `http://...`. - **Fix**: set `APP_URL` to your canonical public HTTPS URL and
restart the container.
</Accordion>
<Accordion title="PDF export fails / printing is stuck">
- **Common cause**: Reactive Resume can't reach the printer or the printer can't reach your app. - **Checks**: -
`PRINTER_ENDPOINT` should usually be `http://printer:3000` in Compose. - If you use
`PRINTER_APP_URL="http://host.docker.internal:3000"`, ensure `extra_hosts: host-gateway` is present for the printer
service.
- **Common cause**: Reactive Resume can't reach the printer, token mismatch, or the printer can't reach your app. -
**Checks**: - `PRINTER_ENDPOINT` should usually be `ws://printer:3000?token=...` in Compose. - Browserless `TOKEN` and
the token in `PRINTER_ENDPOINT` must match. - If you use `PRINTER_APP_URL="http://host.docker.internal:3000"` on
Linux, set `extra_hosts: ["host.docker.internal:host-gateway"]` for the printer service.
</Accordion>
<Accordion title="/api/health returns 503 even though Postgres is up">
- **Common cause**: printer or storage health failed (not only database). - **Fix**: inspect the endpoint response
payload and check `printer` / `storage` fields: ```bash curl -s http://localhost:3000/api/health ```
</Accordion>
<Accordion title="Uploads disappear after restart">
- **Cause**: you didn't mount persistent storage for `/app/data` (when not using S3). - **Fix**: add a volume mount
like `./data:/app/data` and redeploy.
- **Cause**: local upload storage wasn't mounted to a persistent volume. - **Fix**: add a volume mount like
`./data:/app/data` and redeploy.
</Accordion>
<Accordion title="Emails aren't being delivered">
- **Expected behavior**: if SMTP vars are empty, the app logs emails to the console instead. - **Fix**: configure SMTP
and verify your provider's TLS/port settings.
- **Expected behavior**: if SMTP isn't fully configured, the app logs emails to the console. - **Fix**: set
`SMTP_HOST`, `SMTP_USER`, `SMTP_PASS`, and `SMTP_FROM`, then verify `SMTP_PORT` and `SMTP_SECURE`.
</Accordion>
<Accordion title="Dynamic OAuth redirect URI is rejected">
- **Common cause**: redirect host is not trusted for dynamic client registration. - **Fix**: add trusted HTTPS
hosts/origins to `OAUTH_DYNAMIC_CLIENT_REDIRECT_HOSTS`.
</Accordion>
<Accordion title="S3 storage error: ENOTFOUND bucket.endpoint">
+17 -12
View File
@@ -85,12 +85,11 @@ services:
- QUEUED=10
- HEALTH=true
- CONCURRENT=5
# Optional: Set a token for authentication
# - TOKEN=your-secret-token
- TOKEN=${BROWSERLESS_TOKEN}
networks:
- reactive_resume_network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/pressure?token=your-secret-token"]
test: ["CMD-SHELL", 'curl -fsS "http://localhost:3000/pressure?token=${BROWSERLESS_TOKEN}" > /dev/null']
interval: 30s
timeout: 10s
retries: 3
@@ -102,7 +101,7 @@ services:
- APP_URL=https://resume.${DOMAIN}
- PRINTER_APP_URL=http://reactive_resume:3000
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/postgres
- PRINTER_ENDPOINT=http://printer:3000
- PRINTER_ENDPOINT=ws://printer:3000?token=${BROWSERLESS_TOKEN}
- AUTH_SECRET=${AUTH_SECRET}
# Add other optional env vars as needed (SMTP, S3, OAuth, etc.)
volumes:
@@ -143,6 +142,7 @@ DOMAIN="example.com"
ACME_EMAIL="admin@example.com"
POSTGRES_PASSWORD="your-secure-postgres-password"
AUTH_SECRET="your-auth-secret-from-openssl-rand-hex-32"
BROWSERLESS_TOKEN="your-browserless-token"
# Optional: Traefik dashboard auth (generate with: htpasswd -nb admin password)
TRAEFIK_DASHBOARD_AUTH="admin:$$apr1$$..."
```
@@ -191,10 +191,14 @@ services:
- QUEUED=10
- HEALTH=true
- CONCURRENT=5
# Optional: Set a token for authentication
# - TOKEN=your-secret-token
- TOKEN=${BROWSERLESS_TOKEN}
networks:
- reactive_resume_network
healthcheck:
test: ["CMD-SHELL", 'curl -fsS "http://localhost:3000/pressure?token=${BROWSERLESS_TOKEN}" > /dev/null']
interval: 30s
timeout: 10s
retries: 3
reactive_resume:
image: amruthpillai/reactive-resume:latest
@@ -203,7 +207,7 @@ services:
- APP_URL=https://resume.${DOMAIN}
- PRINTER_APP_URL=http://reactive_resume:3000
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/postgres
- PRINTER_ENDPOINT=http://printer:3000
- PRINTER_ENDPOINT=ws://printer:3000?token=${BROWSERLESS_TOKEN}
- AUTH_SECRET=${AUTH_SECRET}
# Add other optional env vars as needed (SMTP, S3, OAuth, etc.)
volumes:
@@ -339,10 +343,9 @@ services:
- QUEUED=10
- HEALTH=true
- CONCURRENT=5
# Optional: Set a token for authentication
# - TOKEN=your-secret-token
- TOKEN=$BROWSERLESS_TOKEN
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/pressure?token=your-secret-token"]
test: ["CMD-SHELL", 'curl -fsS "http://localhost:3000/pressure?token=$BROWSERLESS_TOKEN" > /dev/null']
interval: 30s
timeout: 10s
retries: 3
@@ -405,6 +408,8 @@ services:
- GOOGLE_CLIENT_SECRET=$GOOGLE_CLIENT_SECRET
- GITHUB_CLIENT_ID=$GITHUB_CLIENT_ID
- GITHUB_CLIENT_SECRET=$GITHUB_CLIENT_SECRET
- LINKEDIN_CLIENT_ID=$LINKEDIN_CLIENT_ID
- LINKEDIN_CLIENT_SECRET=$LINKEDIN_CLIENT_SECRET
- SMTP_HOST=$SMTP_HOST
- SMTP_PORT=$SMTP_PORT
- SMTP_USER=$SMTP_USER
@@ -467,10 +472,10 @@ docker stack deploy -c compose-swarm.yml reactive_resume
docker stack services reactive_resume
# View logs for the app
docker service logs -f reactive_resume_app
docker service logs -f reactive_resume_reactive_resume
# Scale the app
docker service scale reactive_resume_app=3
docker service scale reactive_resume_reactive_resume=3
# Remove the stack
docker stack rm reactive_resume
+8 -1
View File
@@ -7,6 +7,11 @@ description: "A step-by-step guide to migrate your Reactive Resume instance from
This guide walks you through migrating your Reactive Resume installation from **v4 to v5**. The migration process involves setting up a new v5 instance alongside your existing v4 instance, then transferring your users and resumes to the new system.
<Info>
This page is for **v4 → v5 data migration** only. For normal v5 upgrades, use the [Self-Hosting with
Docker](/self-hosting/docker) guide. v5 schema migrations run automatically on app startup.
</Info>
<Warning>
**Keep your v4 instance running** until you have successfully migrated all data to v5 and verified everything works
correctly. This ensures you have a fallback in case anything goes wrong during the migration.
@@ -86,7 +91,7 @@ To run the migration scripts, you need the following installed on your host mach
</Card>
<Card title="Reactive Resume Source Code">
Clone the Reactive Resume repository to access the migration scripts: ```bash git clone
https://github.com/amruthpillai/reactive-resume.git cd reactive-resume pnpm install ```
https://github.com/amruthpillai/reactive-resume.git cd reactive-resume vp install ```
</Card>
</CardGroup>
@@ -107,6 +112,8 @@ PRODUCTION_DATABASE_URL="postgresql://user:password@localhost:5432/reactive_resu
`PRODUCTION_DATABASE_URL` should point to your **old v4 database**. Mixing these up could cause data loss.
</Warning>
<Warning>`PRODUCTION_DATABASE_URL` is used only by these migration scripts. It is not a runtime app variable.</Warning>
### Step 1: Migrate Users
The user migration script transfers all user accounts, authentication data, and two-factor settings from v4 to v5.
+34 -4
View File
@@ -62,10 +62,13 @@ You must configure endpoints using **one** of these two methods:
### Optional Variables
| Variable | Description | Default |
| --------------------- | ---------------------------------------- | ---------------------- |
| `OAUTH_PROVIDER_NAME` | Display name shown on the sign-in button | `Custom OAuth` |
| `OAUTH_SCOPES` | Space-separated list of OAuth scopes | `openid profile email` |
| Variable | Description | Default |
| ------------------------------------- | -------------------------------------------------------------------------------------- | ---------------------- |
| `OAUTH_PROVIDER_NAME` | Display name shown on the sign-in button | `Custom OAuth` |
| `OAUTH_SCOPES` | Space-separated list of OAuth scopes | `openid profile email` |
| `OAUTH_DYNAMIC_CLIENT_REDIRECT_HOSTS` | Comma-separated allowlist for dynamic OAuth client redirect hosts/origins (HTTPS only) | _empty_ |
| `BETTER_AUTH_URL` | Optional auth base URL override for split-host setups | `APP_URL` |
| `BETTER_AUTH_SECRET` | Optional Better Auth secret override | `AUTH_SECRET` |
## Callback URL
@@ -86,6 +89,18 @@ https://resume.example.com/api/auth/oauth2/callback/custom
authentication to fail.
</Warning>
<Info>
Built-in providers (Google, GitHub, LinkedIn) use callback URLs in this format: `{APP_URL}/api/auth/callback/
{provider}` (for example `.../google`, `.../github`, `.../linkedin`).
</Info>
## URL and Proxy Requirements
- Set `APP_URL` to the exact public URL users access (prefer HTTPS in production).
- If auth metadata/JWKS must be served from a different public host, set `BETTER_AUTH_URL`.
- Behind a reverse proxy, forward `Host` and `X-Forwarded-Proto` correctly, or cookie/session behavior may break.
- `trustedOrigins` are derived from `APP_URL`, so alternate domains are not automatically trusted.
## Profile Mapping
Reactive Resume automatically maps user profile data from the OAuth provider. The following fields are used:
@@ -253,6 +268,8 @@ AUTH_SECRET="your-32-byte-hex-secret"
# GOOGLE_CLIENT_SECRET=""
# GITHUB_CLIENT_ID=""
# GITHUB_CLIENT_SECRET=""
# LINKEDIN_CLIENT_ID=""
# LINKEDIN_CLIENT_SECRET=""
# Custom OAuth Provider (e.g., Authentik)
OAUTH_PROVIDER_NAME="Company SSO"
@@ -278,6 +295,11 @@ OAUTH_DISCOVERY_URL="https://auth.company.com/application/o/reactive-resume/.wel
number differences - Path case sensitivity
</Accordion>
<Accordion title="Session/cookie issues after successful OAuth login">
Common cause: `APP_URL` does not match the real HTTPS public origin (for example, app is behind TLS but `APP_URL` is
`http://...`). Fix: set `APP_URL` to the canonical HTTPS URL and restart the app.
</Accordion>
<Accordion title="Custom OAuth button not appearing">
The custom OAuth option only appears if both `OAUTH_CLIENT_ID` and `OAUTH_CLIENT_SECRET` are set, **and** either:
- `OAUTH_DISCOVERY_URL` is set, **or**
@@ -293,6 +315,11 @@ OAUTH_DISCOVERY_URL="https://auth.company.com/application/o/reactive-resume/.wel
domain
</Accordion>
<Accordion title="Dynamic client redirect URI is rejected">
Dynamic OAuth client registration only allows HTTPS redirect URIs on trusted hosts. Add allowed hosts/origins to
`OAUTH_DYNAMIC_CLIENT_REDIRECT_HOSTS` (comma-separated).
</Accordion>
<Accordion title="User profile data is missing or incorrect">
The profile mapping depends on your provider returning standard claims:
- `email` (required)
@@ -317,6 +344,9 @@ OAUTH_DISCOVERY_URL="https://auth.company.com/application/o/reactive-resume/.wel
<Card title="Verify redirect URIs" icon="shield-check">
Configure your OAuth provider to only allow the exact redirect URI. Avoid wildcards in redirect URI configurations.
</Card>
<Card title="Protect auth internals" icon="shield">
Keep `BETTER_AUTH_SECRET` and `BETTER_AUTH_API_KEY` private. Rotating these values may invalidate active sessions.
</Card>
<Card title="Review scopes" icon="list-check">
Only request the scopes you need. The default (`openid profile email`) is sufficient for Reactive Resume.
</Card>
+13793 -187
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -1,9 +1,9 @@
{
"$schema": "https://unpkg.com/knip@5/schema.json",
"entry": ["src/server.ts", "src/router.tsx"],
"entry": ["src/server.ts", "src/router.tsx", "lingui.config.ts"],
"tailwind": { "entry": ["src/styles.css"] },
"project": ["src/**/*.{js,jsx,ts,tsx,mdx,css}"],
"project": ["src/**/*.{js,jsx,ts,tsx,css}"],
"ignoreBinaries": ["mint"],
"ignoreDependencies": ["npm-check-updates", "node-addon-api", "node-gyp"],
"ignore": ["src/schema/**/*.ts", "src/components/**/*.{ts,tsx}"]
"ignore": ["src/schema/**/*.ts", "src/components/ui/**/*.{ts,tsx}"]
}
+3 -2
View File
@@ -1,4 +1,5 @@
import { defineConfig } from "@lingui/cli";
import { formatter } from "@lingui/format-po";
export default defineConfig({
sourceLocale: "en-US",
@@ -64,9 +65,9 @@ export default defineConfig({
"zu-ZA": "en-US",
default: "en-US",
},
formatOptions: {
format: formatter({
lineNumbers: false,
},
}),
catalogs: [
{
path: "<rootDir>/locales/{locale}",
+904 -228
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+904 -228
View File
File diff suppressed because it is too large Load Diff
+820 -145
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+904 -228
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+907 -231
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+907 -231
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+909 -233
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+908 -232
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+904 -228
View File
File diff suppressed because it is too large Load Diff
+904 -228
View File
File diff suppressed because it is too large Load Diff
+904 -228
View File
File diff suppressed because it is too large Load Diff
+904 -228
View File
File diff suppressed because it is too large Load Diff
+904 -228
View File
File diff suppressed because it is too large Load Diff
+904 -228
View File
File diff suppressed because it is too large Load Diff
+906 -230
View File
File diff suppressed because it is too large Load Diff
+905 -229
View File
File diff suppressed because it is too large Load Diff
+814 -139
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,92 @@
CREATE TABLE "jwks" (
"id" uuid PRIMARY KEY,
"public_key" text NOT NULL,
"private_key" text NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"expires_at" timestamp with time zone
);
--> statement-breakpoint
CREATE TABLE "oauth_access_token" (
"id" uuid PRIMARY KEY,
"token" text NOT NULL UNIQUE,
"client_id" text NOT NULL,
"session_id" uuid,
"user_id" uuid,
"reference_id" text,
"refresh_id" uuid,
"expires_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now(),
"scopes" text[] NOT NULL
);
--> statement-breakpoint
CREATE TABLE "oauth_client" (
"id" uuid PRIMARY KEY,
"client_id" text NOT NULL UNIQUE,
"client_secret" text,
"disabled" boolean DEFAULT false,
"skip_consent" boolean,
"enable_end_session" boolean,
"subject_type" text,
"scopes" text[],
"user_id" uuid,
"created_at" timestamp with time zone DEFAULT now(),
"updated_at" timestamp with time zone DEFAULT now(),
"name" text,
"uri" text,
"icon" text,
"contacts" text[],
"tos" text,
"policy" text,
"software_id" text,
"software_version" text,
"software_statement" text,
"redirect_uris" text[] NOT NULL,
"post_logout_redirect_uris" text[],
"token_endpoint_auth_method" text,
"grant_types" text[],
"response_types" text[],
"public" boolean,
"type" text,
"require_pkce" boolean,
"reference_id" text,
"metadata" jsonb
);
--> statement-breakpoint
CREATE TABLE "oauth_consent" (
"id" uuid PRIMARY KEY,
"client_id" text NOT NULL,
"user_id" uuid,
"reference_id" text,
"scopes" text[] NOT NULL,
"created_at" timestamp with time zone DEFAULT now(),
"updated_at" timestamp with time zone DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "oauth_refresh_token" (
"id" uuid PRIMARY KEY,
"token" text NOT NULL,
"client_id" text NOT NULL,
"session_id" uuid,
"user_id" uuid NOT NULL,
"reference_id" text,
"expires_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now(),
"revoked" timestamp with time zone,
"auth_time" timestamp with time zone,
"scopes" text[] NOT NULL
);
--> statement-breakpoint
CREATE INDEX "oauth_access_token_token_index" ON "oauth_access_token" ("token");--> statement-breakpoint
CREATE INDEX "oauth_client_client_id_index" ON "oauth_client" ("client_id");--> statement-breakpoint
CREATE INDEX "oauth_consent_user_id_client_id_index" ON "oauth_consent" ("user_id","client_id");--> statement-breakpoint
CREATE INDEX "oauth_refresh_token_token_index" ON "oauth_refresh_token" ("token");--> statement-breakpoint
ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_client_id_oauth_client_client_id_fkey" FOREIGN KEY ("client_id") REFERENCES "oauth_client"("client_id") ON DELETE CASCADE;--> statement-breakpoint
ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_session_id_session_id_fkey" FOREIGN KEY ("session_id") REFERENCES "session"("id") ON DELETE SET NULL;--> statement-breakpoint
ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_user_id_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE CASCADE;--> statement-breakpoint
ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_refresh_id_oauth_refresh_token_id_fkey" FOREIGN KEY ("refresh_id") REFERENCES "oauth_refresh_token"("id") ON DELETE CASCADE;--> statement-breakpoint
ALTER TABLE "oauth_client" ADD CONSTRAINT "oauth_client_user_id_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE CASCADE;--> statement-breakpoint
ALTER TABLE "oauth_consent" ADD CONSTRAINT "oauth_consent_client_id_oauth_client_client_id_fkey" FOREIGN KEY ("client_id") REFERENCES "oauth_client"("client_id") ON DELETE CASCADE;--> statement-breakpoint
ALTER TABLE "oauth_consent" ADD CONSTRAINT "oauth_consent_user_id_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE CASCADE;--> statement-breakpoint
ALTER TABLE "oauth_refresh_token" ADD CONSTRAINT "oauth_refresh_token_client_id_oauth_client_client_id_fkey" FOREIGN KEY ("client_id") REFERENCES "oauth_client"("client_id") ON DELETE CASCADE;--> statement-breakpoint
ALTER TABLE "oauth_refresh_token" ADD CONSTRAINT "oauth_refresh_token_session_id_session_id_fkey" FOREIGN KEY ("session_id") REFERENCES "session"("id") ON DELETE SET NULL;--> statement-breakpoint
ALTER TABLE "oauth_refresh_token" ADD CONSTRAINT "oauth_refresh_token_user_id_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE CASCADE;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,5 @@
ALTER TABLE "session" ADD COLUMN "impersonated_by" text;--> statement-breakpoint
ALTER TABLE "user" ADD COLUMN "role" text DEFAULT 'user';--> statement-breakpoint
ALTER TABLE "user" ADD COLUMN "banned" boolean DEFAULT false;--> statement-breakpoint
ALTER TABLE "user" ADD COLUMN "ban_reason" text;--> statement-breakpoint
ALTER TABLE "user" ADD COLUMN "ban_expires" timestamp(6) with time zone;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,10 @@
CREATE TABLE "resume_analysis" (
"id" uuid PRIMARY KEY,
"analysis" jsonb NOT NULL,
"resume_id" uuid NOT NULL UNIQUE,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE INDEX "resume_analysis_resume_id_index" ON "resume_analysis" ("resume_id");--> statement-breakpoint
ALTER TABLE "resume_analysis" ADD CONSTRAINT "resume_analysis_resume_id_resume_id_fkey" FOREIGN KEY ("resume_id") REFERENCES "resume"("id") ON DELETE CASCADE;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
CREATE UNIQUE INDEX "user_email_lower_unique_idx" ON "user" (lower("email"));
File diff suppressed because it is too large Load Diff
+101 -92
View File
@@ -1,6 +1,6 @@
{
"name": "reactive-resume",
"version": "5.0.13",
"version": "5.0.20",
"description": "Reactive Resume is a free and open-source resume builder that simplifies the process of creating, updating, and sharing your resume.",
"license": "MIT",
"author": {
@@ -22,103 +22,109 @@
"db:studio": "drizzle-kit studio",
"dev": "vp dev",
"docs:dev": "cd docs && npx mint dev",
"fmt": "vp fmt --check",
"fmt:fix": "vp fmt",
"email:dev": "email dev --dir src/integrations/email/templates --port 3001",
"format": "vp fmt --check",
"format:fix": "vp fmt",
"knip": "knip",
"lingui:extract": "lingui extract --clean --overwrite",
"lint": "vp lint --type-aware",
"lint:fix": "vp lint --type-aware --fix",
"prepare": "vp config",
"preview": "vp preview",
"start": "node .output/server/index.mjs",
"test": "vp test",
"test:coverage": "vp test --coverage --run",
"typecheck": "tsgo --noEmit"
},
"dependencies": {
"@ai-sdk/anthropic": "^3.0.58",
"@ai-sdk/google": "^3.0.43",
"@ai-sdk/openai": "^3.0.41",
"@ai-sdk/react": "^3.0.118",
"@aws-sdk/client-s3": "^3.1012.0",
"@base-ui/react": "^1.3.0",
"@better-auth/api-key": "^1.5.5",
"@better-auth/drizzle-adapter": "^1.5.5",
"@better-auth/infra": "^0.1.12",
"@ai-sdk/anthropic": "^3.0.71",
"@ai-sdk/google": "^3.0.64",
"@ai-sdk/openai": "^3.0.53",
"@ai-sdk/openai-compatible": "^2.0.41",
"@aws-sdk/client-s3": "^3.1037.0",
"@base-ui/react": "^1.4.1",
"@better-auth/api-key": "^1.6.9",
"@better-auth/drizzle-adapter": "^1.6.9",
"@better-auth/infra": "^0.2.5",
"@better-auth/oauth-provider": "^1.6.9",
"@better-auth/passkey": "^1.6.9",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@fontsource-variable/ibm-plex-sans": "^5.2.8",
"@hookform/resolvers": "^5.2.2",
"@lingui/core": "^5.9.3",
"@lingui/react": "^5.9.3",
"@modelcontextprotocol/sdk": "^1.27.1",
"@lingui/core": "^6.0.0",
"@lingui/react": "^6.0.0",
"@modelcontextprotocol/sdk": "^1.29.0",
"@monaco-editor/react": "4.8.0-rc.3",
"@orpc/client": "^1.13.8",
"@orpc/json-schema": "^1.13.8",
"@orpc/openapi": "^1.13.8",
"@orpc/server": "^1.13.8",
"@orpc/tanstack-query": "^1.13.8",
"@orpc/zod": "^1.13.8",
"@orpc/client": "^1.14.0",
"@orpc/experimental-ratelimit": "^1.14.0",
"@orpc/json-schema": "^1.14.0",
"@orpc/openapi": "^1.14.0",
"@orpc/server": "^1.14.0",
"@orpc/tanstack-query": "^1.14.0",
"@orpc/zod": "^1.14.0",
"@phosphor-icons/react": "^2.1.10",
"@phosphor-icons/web": "^2.1.2",
"@sindresorhus/slugify": "^3.0.0",
"@t3-oss/env-core": "^0.13.10",
"@tanstack/react-query": "^5.91.0",
"@tanstack/react-router": "^1.167.5",
"@tanstack/react-router-ssr-query": "^1.166.9",
"@tanstack/react-start": "^1.166.17",
"@t3-oss/env-core": "^0.13.11",
"@tanstack/react-query": "^5.100.5",
"@tanstack/react-router": "^1.168.25",
"@tanstack/react-router-ssr-query": "^1.166.12",
"@tanstack/react-start": "^1.167.50",
"@tanstack/zod-adapter": "^1.166.9",
"@tiptap/extension-highlight": "^3.20.4",
"@tiptap/extension-table": "^3.20.4",
"@tiptap/extension-text-align": "^3.20.4",
"@tiptap/pm": "^3.20.4",
"@tiptap/react": "^3.20.4",
"@tiptap/starter-kit": "^3.20.4",
"@uiw/color-convert": "^2.9.6",
"@uiw/react-color-colorful": "^2.9.6",
"ai": "^6.0.116",
"ai-sdk-ollama": "^3.8.1",
"@tiptap/extension-color": "^3.22.4",
"@tiptap/extension-highlight": "^3.22.4",
"@tiptap/extension-table": "^3.22.4",
"@tiptap/extension-text-align": "^3.22.4",
"@tiptap/extension-text-style": "^3.22.4",
"@tiptap/pm": "^3.22.4",
"@tiptap/react": "^3.22.4",
"@tiptap/starter-kit": "^3.22.4",
"@uiw/color-convert": "^2.10.1",
"@uiw/react-color-colorful": "^2.10.1",
"ai": "^6.0.168",
"bcrypt": "^6.0.0",
"better-auth": "^1.5.5",
"better-auth": "^1.6.9",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"docx": "^9.6.1",
"dompurify": "^3.3.3",
"drizzle-orm": "1.0.0-beta.18-7eb39f0",
"drizzle-zod": "^1.0.0-beta.14-a36c63d",
"es-toolkit": "^1.45.1",
"dompurify": "^3.4.1",
"drizzle-orm": "1.0.0-beta.22",
"drizzle-zod": "1.0.0-beta.14-a36c63d",
"es-toolkit": "^1.46.0",
"fast-deep-equal": "^3.1.3",
"fast-json-patch": "^3.1.1",
"fuse.js": "^7.1.0",
"fuse.js": "^7.3.0",
"immer": "^11.1.4",
"input-otp": "^1.4.2",
"js-cookie": "^3.0.5",
"jsonrepair": "^3.13.3",
"jsonrepair": "^3.14.0",
"monaco-editor": "^0.55.1",
"motion": "^12.38.0",
"nodemailer": "^8.0.3",
"nodemailer": "^8.0.7",
"ollama-ai-provider-v2": "^3.5.0",
"pg": "^8.20.0",
"puppeteer-core": "^24.39.1",
"puppeteer-core": "^24.42.0",
"qrcode.react": "^4.2.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-hook-form": "^7.71.2",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"react-email": "^6.0.0",
"react-hook-form": "^7.74.0",
"react-hotkeys-hook": "^5.2.4",
"react-resizable-panels": "^4.7.3",
"react-resizable-panels": "^4.10.0",
"react-window": "^2.2.7",
"react-zoom-pan-pinch": "^3.7.0",
"shadcn": "^4.0.8",
"react-zoom-pan-pinch": "^4.0.3",
"shadcn": "^4.5.0",
"sharp": "^0.34.5",
"sonner": "^2.0.7",
"srvx": "^0.11.12",
"srvx": "^0.11.15",
"tailwind-merge": "^3.5.0",
"tailwindcss": "^4.2.2",
"tailwindcss": "^4.2.4",
"ts-pattern": "^5.9.0",
"tw-animate-css": "^1.4.0",
"unique-names-generator": "^4.7.1",
"usehooks-ts": "^3.1.1",
"uuid": "^13.0.0",
"uuid": "^14.0.0",
"workbox-window": "^7.4.0",
"zod": "^4.3.6",
"zundo": "^2.3.0",
@@ -126,55 +132,58 @@
},
"devDependencies": {
"@babel/core": "^7.29.0",
"@lingui/babel-plugin-lingui-macro": "^5.9.3",
"@lingui/cli": "^5.9.3",
"@lingui/vite-plugin": "^5.9.3",
"@rolldown/plugin-babel": "^0.2.2",
"@tailwindcss/vite": "^4.2.2",
"@lingui/babel-plugin-lingui-macro": "^6.0.0",
"@lingui/cli": "^6.0.0",
"@lingui/format-po": "^6.0.0",
"@lingui/vite-plugin": "^6.0.0",
"@react-email/ui": "^6.0.0",
"@rolldown/plugin-babel": "^0.2.3",
"@tailwindcss/vite": "^4.2.4",
"@testing-library/react": "^16.3.2",
"@types/bcrypt": "^6.0.0",
"@types/js-cookie": "^3.0.6",
"@types/node": "^25.5.0",
"@types/nodemailer": "^7.0.11",
"@types/pg": "^8.18.0",
"@types/node": "^25.6.0",
"@types/nodemailer": "^8.0.0",
"@types/pg": "^8.20.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@typescript/native-preview": "7.0.0-dev.20260319.1",
"@typescript/native-preview": "7.0.0-dev.20260427.1",
"@vitejs/plugin-react": "^6.0.1",
"@vitest/coverage-v8": "^4.1.5",
"babel-plugin-macros": "^3.1.0",
"drizzle-kit": "1.0.0-beta.18-7eb39f0",
"jsdom": "^29.0.0",
"knip": "^5.88.0",
"nitro": "3.0.260311-beta",
"node-addon-api": "^8.6.0",
"node-gyp": "^12.2.0",
"npm-check-updates": "^19.6.5",
"tsx": "^4.21.0",
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
"drizzle-kit": "1.0.0-beta.22",
"happy-dom": "^20.9.0",
"jose": "^6.2.2",
"knip": "^6.7.0",
"nitro": "3.0.260415-beta",
"node-addon-api": "^8.7.0",
"node-gyp": "^12.3.0",
"npm-check-updates": "^22.0.1",
"vite": "npm:@voidzero-dev/vite-plus-core@^0.1.19",
"vite-plugin-pwa": "^1.2.0",
"vite-plus": "latest",
"vitest": "npm:@voidzero-dev/vite-plus-test@latest"
"vite-plus": "^0.1.19",
"vitest": "npm:@voidzero-dev/vite-plus-test@^0.1.19"
},
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be",
"packageManager": "pnpm@10.33.2",
"pnpm": {
"overrides": {
"@isaacs/brace-expansion": "^5.0.1",
"ajv": "^8.18.0",
"basic-ftp": "^5.2.0",
"fast-xml-parser": "^5.3.8",
"hono": "^4.11.10",
"lodash": "^4.17.23",
"markdown-it": "^14.1.1",
"minimatch": "^10.2.3",
"qs": "^6.14.2",
"serialize-javascript": "^7.0.4",
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
"vitest": "npm:@voidzero-dev/vite-plus-test@latest"
},
"onlyBuiltDependencies": [
"bcrypt",
"esbuild",
"msw",
"sharp"
]
],
"overrides": {
"dompurify@<=3.3.1": ">=3.3.2",
"dompurify@<=3.3.3": ">=3.4.0",
"dompurify@<3.3.2": ">=3.3.2",
"dompurify@<3.4.0": ">=3.4.0",
"dompurify@>=1.0.10 <3.4.0": ">=3.4.0",
"dompurify@>=3.0.1 <3.4.0": ">=3.4.0",
"dompurify@>=3.1.3 <=3.3.1": ">=3.3.2",
"postcss@<8.5.10": ">=8.5.10",
"serialize-javascript@<=7.0.2": ">=7.0.3",
"serialize-javascript@<7.0.5": ">=7.0.5",
"uuid@<14.0.0": ">=14.0.0"
}
}
}
+3 -3
View File
@@ -4,7 +4,7 @@ import { definePlugin } from "nitro";
import { Pool } from "pg";
async function migrateDatabase() {
console.log("⌛ Running database migrations...");
console.info("Running database migrations...");
const connectionString = process.env.DATABASE_URL;
@@ -17,9 +17,9 @@ async function migrateDatabase() {
try {
await migrate(db, { migrationsFolder: "./migrations" });
console.log("✅ Database migrations completed");
console.info("Database migrations completed");
} catch (error) {
console.error("🚨 Database migrations failed:", error);
console.error({ err: error }, "Database migrations failed");
throw error;
} finally {
await pool.end();
+4983 -4706
View File
File diff suppressed because it is too large Load Diff
+98
View File
@@ -0,0 +1,98 @@
{
"name": "Reactive Resume",
"short_name": "Reactive Resume",
"description": "A free and open-source resume builder.",
"start_url": "/?source=pwa",
"display": "standalone",
"background_color": "#09090B",
"theme_color": "#09090B",
"lang": "en",
"scope": "/",
"id": "/?source=pwa",
"orientation": "portrait",
"icons": [
{ "src": "favicon.ico", "sizes": "128x128", "type": "image/x-icon" },
{ "src": "pwa-64x64.png", "sizes": "64x64", "type": "image/png" },
{ "src": "pwa-192x192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "pwa-512x512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
{ "src": "maskable-icon-512x512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
],
"screenshots": [
{
"src": "screenshots/web/1-landing-page.webp",
"sizes": "1920x1080 any",
"type": "image/webp",
"form_factor": "wide",
"label": "Landing Page"
},
{
"src": "screenshots/web/2-resume-dashboard.webp",
"sizes": "1920x1080 any",
"type": "image/webp",
"form_factor": "wide",
"label": "Resume Dashboard"
},
{
"src": "screenshots/web/3-builder-screen.webp",
"sizes": "1920x1080 any",
"type": "image/webp",
"form_factor": "wide",
"label": "Builder Screen"
},
{
"src": "screenshots/web/4-template-gallery.webp",
"sizes": "1920x1080 any",
"type": "image/webp",
"form_factor": "wide",
"label": "Template Gallery"
},
{
"src": "screenshots/mobile/1-landing-page.webp",
"sizes": "1284x2778 any",
"type": "image/webp",
"form_factor": "narrow",
"label": "Landing Page"
},
{
"src": "screenshots/mobile/2-resume-dashboard.webp",
"sizes": "1284x2778 any",
"type": "image/webp",
"form_factor": "narrow",
"label": "Resume Dashboard"
},
{
"src": "screenshots/mobile/3-builder-screen.webp",
"sizes": "1284x2778 any",
"type": "image/webp",
"form_factor": "narrow",
"label": "Builder Screen"
},
{
"src": "screenshots/mobile/4-template-gallery.webp",
"sizes": "1284x2778 any",
"type": "image/webp",
"form_factor": "narrow",
"label": "Template Gallery"
}
],
"categories": [
"ai",
"builder",
"business",
"career",
"cv",
"editor",
"free",
"generator",
"job-search",
"multilingual",
"open-source",
"privacy",
"productivity",
"resume",
"self-hosted",
"templates",
"utilities",
"writing"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 954 KiB

Some files were not shown because too many files have changed in this diff Show More