Fix authentication recovery, account imports, application tracking, resume
editing and exports, sharing, API contracts, provider selection, and private
local attachments. Preserve authored content during PDF pagination.
Update guides, generated OpenAPI output, and translation catalogs to match
verified behavior and documented constraints.
Validation: 1,019 tests passed; 12 database/OAuth integration tests skipped.
Ten affected package typechecks, production build, Biome, and package
boundaries passed.
Vercel now deploys Reactive Resume as two services in one project:
`frontend` serves the static Vite build from apps/web/dist, and
`backend` runs the Hono server Function from apps/server. Top-level
rewrites send server-owned paths (/api, /uploads, /mcp, /.well-known,
robots.txt, sitemap.xml, llms.txt, schema.json, index.html) and every
path without a file extension to `backend`, so HTML shells keep their
injected SEO metadata. Paths with a file extension go to `frontend`.
The service builder needs a few accommodations:
- apps/server/vercel.mjs replaces api/index.mjs as the entrypoint,
because a service entrypoint must exist before the build runs.
- `outputDirectory: "."` stops the builder from using the Docker
entrypoint in dist/ as the Function handler.
- The builder loads external CommonJS dependencies through pnpm links
that it leaves out of the Function. ioredis and react-reconciler are
now bundled with their dependencies, and bcrypt is replaced with
bcryptjs, which reads and writes the same $2b$ hashes.
The Vercel compatibility workflow now builds with the services
framework and loads a copy of the backend Function outside the
checkout, so a dependency missing from the Function fails CI. The stale
PDFKit trace checks are removed.
Existing Vercel installations must set Framework Preset to Services
before redeploying; the self-hosting guide documents this.
* feat(deploy): support Vercel Hobby alongside Docker
* fix(deploy): include PDFKit runtime font assets
* docs(deploy): document Vercel and Docker setup
* docs(deploy): record storage persistence checks
* refactor(deploy): drop scheduled staging cleanup
Staging uploads are deleted after finalization and expired ones are swept
on each new upload, so the Vercel cron job, its route, and CRON_SECRET are
no longer needed. The Deploy with Vercel wizard now asks for two secrets.
* docs(deploy): restructure Vercel guides
Split the Vercel page into a how-to with its environment reference, move the
large RPC staging protocol to an API reference page, and move CI deployment
checks to the contributing section. Point Deploy with Vercel buttons at main.
* chore: remove agent planning records and fix web app description
Delete superpowers plans/specs, ADRs, issue plans, execution briefs, domain
context maps, and Europass research. Describe apps/web as a TanStack Router
SPA served by apps/server.
* refactor(deploy): simplify Vercel support code
- Share one Redis client and key namespace through @reactive-resume/db/redis
for API and auth instead of a second auth-only client.
- Drop the auth seeding retry; the provider already treats concurrent inserts
as no-ops and deployment preparation seeds before runtime.
- Detect staging support from POST /api/storage/stage (404 on Docker) instead
of a separate GET probe.
- Read staged bodies directly; the signed upload already caps their size.
- Close per-subscription Redis connections with disconnect() alone.
- Check Blob health with one list call instead of write/read/delete.
- Remove redundant tsdown onlyBundle list, dead namespace fallbacks, and the
conditional spread in the health status.
* fix(deploy): heal stopped runs with dead owners and keep auth up without Redis
- Run owners refresh a Redis heartbeat until they release their claim. Stop
requests reap the run immediately when the owner has stopped heartbeating,
instead of leaving the thread blocked until the 15-minute TTL reaper.
- Auth and oRPC rate limiters fall back to per-instance memory limits when
Redis errors, instead of rejecting every login or failing requests.
* ci: allow esbuild build for Vercel CLI and register deployment deps with knip
pnpm 12 fails dlx installs with ignored build scripts, so allow esbuild
explicitly. The server bundle keeps @vercel/blob, ioredis, and jose external,
and api/index.mjs is the Vercel Function entry.
* fix(web): send buffered RPC bodies instead of teed streams
Reading a request clone turned the original body into a stream, which
browsers send without inspectable request data and which needs duplex
mode. Send the already buffered Blob for direct requests.
* fix(web): send direct RPC bodies as bytes
Blob request bodies are sent as data pipes, so browser tooling cannot
inspect them. Buffer the original request as an ArrayBuffer and send those
bytes; this restores the e2e save assertions that match on request data.
Rewrites the landing page, in-app microcopy, and public docs, then fixes what the rewrite exposed: stale template counts, a broken quickstart anchor, out-of-sync FAQ structured data, dead error-hint branches in the MCP tools, and wrong-sense translations across all 53 locales. Adds GLOSSARY.md so translators get the right sense of the ambiguous UI terms.
Upgrades to Better Auth 1.7, expands Custom Styles coverage of item headers, and adds a human-approval step to the AI agent's resume edits.
Breaking for self-hosters using a custom OAuth provider: the callback path changes from /api/auth/oauth2/callback/custom to /api/auth/callback/custom, and installs using OAUTH_DISCOVERY_URL need one additional UPDATE after upgrading. Both are documented in docs/self-hosting/sso.mdx.
- Better Auth 1.7, with the account issuer migration and the jwks alg/crv columns the 1.7 jwt plugin requires
- Agent edits gated behind an approval step, with crash-safe runs and context pruning
- item-header now covers every section header row on every template; adds the item-header-row part
- Fixes provider unlinking, auth error messages, and version conflicts on freshly created resumes
- New /auth/error page, translated across all 53 target locales
- DeepSeek Harness plugin moved into packages/dsh-plugin
- Dependency bumps across the workspace
* docs(agents): align Redis compose commands with development guide
- Problem: AGENTS.md omitted Redis from dev infrastructure compose commands
while docs/contributing/development.mdx starts redis for local dev.
- Fix: document full postgres/redis/seaweedfs compose command and note that
REDIS_URL and ENCRYPTION_SECRET are required for AI agent features.
- Verification: preflight upstream fetch; manual diff against development.mdx
and compose.dev.yml redis service; duplicate PR gate passed.
* docs(agents): clarify Redis development URLs
---------
Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
* docs(contributing): align development guide with dotenvx workflow
- Problem: development.mdx told contributors to use a root `.env` file and
export DATABASE_URL manually, while AGENTS.md and compose.dev.yml use
`.env.local` loaded through dotenvx for dev and migration commands.
- Fix: update the setup, migration, dev-server, and database sections to
match the dotenvx commands documented in AGENTS.md.
- Verification: preflight_ship.py (upstream bug marker present); duplicate
PR check clean; docs-only change.
* docs(contributing): add cp command to env setup step
- Problem: setup step said to copy .env.example but the bash block only listed variable assignments.
- Fix: add explicit cp .env.example .env.local command and label the following block as edits.
- Verification: manual review of development.mdx; addresses CodeRabbit review on #3286.
* docs(contributing): align AGENTS.md env copy target with dotenvx
- Problem: AGENTS.md told contributors to copy .env.example to .env while all dev commands use .env.local.
- Fix: update the copy instruction to .env.local for consistency with the dotenvx workflow.
- Verification: manual review; folded into #3286 dotenvx alignment PR.
* docs(contributing): dotenvx-wrap remaining dev script references
- Problem: scripts table and troubleshooting still showed bare pnpm dev/db commands after the dotenvx workflow update.
- Fix: prefix dev, db, and port-override examples with dotenvx run -f .env.local --.
- Verification: manual review of development.mdx; folded into #3286.
---------
Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
* 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>
* Migrate from Biome to Oxlint/Oxfmt
* pin version of autofix
* set version of autofix
* pin version of autofix
* [autofix.ci] apply automated fixes
* better comments, test formatter
* [autofix.ci] apply automated fixes
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>