* fix(auth): use loopback URL for MCP OAuth JWKS verification
Fetch the JWKS endpoint over the internal loopback address instead of the public APP_URL, so token verification works under Docker port-mapping, reverse proxies, and other deployments where the public URL does not loop back to the Node process.
Also log the specific MCP OAuth verification error instead of swallowing it with a bare catch.
Fixes#3077
* fix(auth): normalize internal JWKS URL and throttle MCP OAuth warnings
- Problem: default loopback JWKS URL used PORT in dev where the server
listens on SERVER_PORT (3001), and trailing-slash overrides produced
//api/auth/jwks; unthrottled warn logs could flood on bad bearer tokens.
- Fix: resolveInternalBaseUrl trims/normalizes BETTER_AUTH_INTERNAL_URL,
mirrors apps/server listen-port selection, and MCP OAuth warnings are
throttled to once per minute.
- Verification: pnpm exec biome check on changed files; pnpm typecheck.
* fix(auth): declare BETTER_AUTH_INTERNAL_URL in turbo globalEnv
- Problem: Turborepo strict env mode strips undeclared BETTER_AUTH_INTERNAL_URL under pnpm dev, so the JWKS override silently falls back to loopback.
- Fix: add BETTER_AUTH_INTERNAL_URL to turbo.json globalEnv (required for any new env var per CLAUDE.md).
- Verification: python3 JSON parse of turbo.json; confirmed var was absent from globalEnv before this change.
---------
Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
Node 24 terminates the whole process on an unhandled promise rejection, so a
single request's stray rejection could take the server down for every user
(as the USER_STOPPED agent-abort bug did). Add a process-level unhandledRejection
handler that logs and keeps serving. Uncaught exceptions are intentionally left
on Node's default crash-and-restart, since process state is unsafe afterward.
Claude-Session: https://claude.ai/code/session_01ULhhLQ24DvnYwzP4afDuye
The server PDF preflight spawned a fresh worker per semantic-CSS edit, each
racing a 15s startup deadline to cold-load the ~721kB+5MB PDF runtime. That
load is super-linear in CPU (~3s at 1 vCPU, >15s on a throttled/shared vCPU),
so on a constrained box every edit hit the startup-timeout path and returned
STYLESHEET_PREFLIGHT_WORKER_FAILED. Since the service only advances the applied
stylesheet when preflight passes, custom styles never applied and the editor
stuck on Checking.
Warm one worker at boot and reuse it (message-based input, respawn on
crash/timeout), so the cold load is paid once instead of per edit. Raise the
render deadline 5s->30s (a rich resume renders ~5-18s on a slow box) and the
readiness ceiling to 120s so the one-time warm completes even when throttled.
Surface worker load failures instead of an unhandled-rejection crash, and log
runner-side failure paths so the previously opaque failure is diagnosable.
Verified in node:24-slim under --cpus=0.25/0.35/0.5: all reused requests pass.
Claude-Session: https://claude.ai/code/session_01ULhhLQ24DvnYwzP4afDuye
* feat(export): separate resume/cover-letter downloads, redesign dialog, add Markdown
Let people export the resume and cover letter as distinct documents, and add a
Markdown format alongside PDF / DOCX / JSON (handy for AI agents).
- Server/API: scope PDF generation and download URLs to a resume/cover-letter target.
- Export domain: getResumeExportData + resumeHasCoverLetter in @reactive-resume/resume.
- Redesign the download dialog: one global "What to export" scope toggle (Tabs) plus
flattened per-format rows, reusing existing UI components and design language.
- Add Markdown export (@reactive-resume/resume/markdown) with a small tiptap-HTML converter.
- Fix blank section headings in DOCX and Markdown by injecting the locale-aware
section-title resolver (titles are stored empty and resolved at render time).
- Locale catalogs updated for the new strings.
* test(e2e): open the download dialog before exporting JSON
The JSON export moved into the redesigned download dialog, so the spec now opens
the dialog from the Export sidebar section before clicking "Download JSON".
- health.ts: swap hand-rolled withTimeout for es-toolkit's (fn-taking API); remove
redundant inner try/catches from checkDatabase/checkStorage since runCheck catches
all errors (findings 13, health cleanup)
- web.ts: merge handleWebApp/handleWebAppHead into one function; method is the only
difference — isHead determines body presence (finding 14)
- app.ts: collapse two separate GET/HEAD wildcard routes into app.on(["GET","HEAD"])
- Update web.test.ts and app.test.ts to drop handleWebAppHead references
Claude-Session: https://claude.ai/code/session_012Bnvt1MghwHj4qQRxuQUGa
- Bump @typescript-eslint packages to 8.23.0
- Update Vitest and related packages to 2.1.9
- Minor version upgrades for ts-api-utils and other related dependencies
- Changed ESLint configuration to target TypeScript files and added parser options for better integration.
- Updated various schemas across the application to replace `nestjs-zod/z` imports with `zod` for consistency.
- Refactored password validation in authentication schemas to use `z.string()` instead of `z.password()`.
- Enhanced date handling in user and resume schemas by introducing a new `dateSchema` utility.
- Updated `.ncurc.json` to target minor upgrades for dependencies.