"Everything you've done, on one page." A single resume page is assembled,
written, restyled, checked, tailored and shared as the visitor scrolls,
followed by live community numbers, languages, a support receipt, a
closing call to action and the footer. It replaces the previous homepage
and its playgrounds.
Motion: a small scroll engine (features/homepage/scroll.ts) writes each
section's progress as --p on every animation frame, and the scenes derive
their motion from it with CSS calc(). React only re-renders on coarse
steps held in a zustand store. Reduced motion collapses every pinned
scene to one screen at its end state and stops all ambient motion.
Server-rendered copy: the web build now prerenders the homepage once per
locale (Vite app builder, features/homepage/prerender.tsx) into
apps/web/dist-prerender, which the server sends for "/" by `?locale=`,
then the saved locale cookie. main.tsx waits for the route to load before
React replaces the prerendered page, so it never flashes a loading screen.
dist-prerender is added to turbo outputs, the Docker image and the Vercel
function files.
SEO: localized title and description, a canonical per locale, hreflang
alternates for every locale over `/?locale=` addresses (the app now reads
that parameter), and SoftwareApplication JSON-LD. The FAQ structured data
is dropped because the page shows no FAQ.
Also: self-hosted Anybody and Martian Mono (landing only) and Newsreader
italic, graphite doodles as WebP, new Material Symbols in the icon subset,
the pull-cord theme switch on the app's theme cookie, and new catalog
strings extracted for translation.
The cover-letter and MCP OAuth flow suites were gated on environment variables
CI never set, so they never ran. Point them at the job's PostgreSQL. The
cover-letter suite works in its own schema; the OAuth suite gets a database of
its own because it writes signing keys under its own secret, which the e2e
server can't decrypt.
Nothing reads the unit step's coverage report, so test:ci no longer collects it.
Prune the unit and e2e suites to what protects security, user data, public
contracts and past regressions. Drop what slowed development without catching
bugs: markup and label assertions, wrappers that only proved Base UI works,
copied inventories and snapshots, mock call-shape checks, permutation matrices
across templates, fonts and locales, and env-gated suites that never ran in CI.
- Unit: 4749 tests in 421 files become 1212 in 227. PDF tests that rendered or
rasterized every template, font and locale combination go; one render per
template stays and now checks that every visible section reaches a page,
which a blank page used to pass.
- E2E: 55 tests in 35 specs become 11 in 9, one journey per severe area: sign-up
and sign-in, autosave, a failed save during navigation, JSON export and import,
public and password-protected sharing, slug redirects, OAuth consent for MCP
clients, and the assistant.
- Tests a coverage review found to be the only guard of a contract were kept or
restored, each checked by breaking the code it guards.
- Remove exports, tooling and dev dependencies that only the pruned tests used.
- The Semantic CSS guide check now reads indented code fences, so the guide
example it skipped is compiled too.
S3 and Vercel Blob hand back the content type the client declared at
upload time, and the upload proxy served it inline on the app origin, so
an uploaded text/html or image/svg+xml file could run script as the app.
Only raster images (gif, jpeg, png, webp) now render inline; everything
else, PDFs included, is served as an application/octet-stream
attachment. The check runs at serve time, so it also covers objects
stored before this change.
Self-hosted Docker installs served the SPA's CSS, JS and HTML shells without any Content-Encoding. Hono's compress() now wraps only the web routes: it is registered after every API, MCP and upload route, so their streams are never buffered or re-encoded. The Vercel app keeps relying on its CDN, which already compresses.
A name or summary containing $&, $' or $$ was expanded by String.replace into chunks of index.html, garbling the public resume's head. The inserts now use function replacers.
The conversion of stored legacy style rules to Semantic CSS no longer runs
when the server starts. The image now ships
apps/server/dist/migrate-legacy-styles.mjs, run by hand against
DATABASE_URL:
- without flags it's a dry run that converts in memory and reports counts
- --apply --backup <file> converts, appending every replaced stylesheet to
the backup file before its row is written
- --restore <file> puts those stylesheets back, except on rows edited since
Each table is scanned once for the rows that need converting, then they're
converted in batches with progress logged. Only metadata.stylesheet is
rewritten, a row whose stylesheet changed after it was read is left alone,
and running it again skips what's converted. The data_migration table that
recorded the startup run is gone. The self-hosting guide explains the
one-time run.
Stored resumes, resume versions, letters and letter versions still in
the old editor's legacy mode, or carrying legacy style rules with no
stylesheet, are converted to Semantic CSS once, right after the SQL
migrations, and the result is recorded in a new data_migration table so
later starts skip it. Only metadata.stylesheet is rewritten (the rules
stay for rollback), a row edited meanwhile is retried on the next start,
and a failure leaves the data as it was without stopping the server.
The API no longer converts on every read and save. Imports of old
Reactive Resume JSON exports convert their legacy rules on the way in.
Resumes no longer hold cover-letter sections. A migration saves every
letter a resume carried as a letter linked to that resume's details and
design, hands a resume's only letter to its only letter-less application,
then removes the sections from resumes and their layouts. rollback.sql
puts them back.
Every resume write on the server moves any letter it still carries into
a saved letter in the same transaction, so stale tabs, older files, API
clients and restored versions keep working without duplicating letters.
The resume editor no longer adds or imports letters, the resume download
drops its Cover letter tab, and resume PDF downloads (API, signed links,
MCP) no longer take a cover-letter target. copy_embedded_cover_letter and
POST /cover-letters/from-resume are removed.
The web preview, downloads, template gallery, server export and public
PDF render through Forme. react-pdf, react-pdf-html, the react-pdf
hyphenation package, the Phosphor react-pdf icons and the four patches
are gone. Hyphenation now follows the page language for every language
Forme has patterns for.
Semantic CSS keeps its language; declarations Forme can't draw raise an
ENGINE_UNSUPPORTED warning in the editor. The page map rebuilds blocks
Forme leaves out of its layout when they break across pages, and a
render that misplaces a box is repeated with nested rows kept whole.
Tests move to a small shim with the react-pdf calls they were written
against; tests of react-pdf internals are dropped. Forme limits are
recorded as expected failures (RTL line order, characters above
U+FFFF).
The published spec had fallen behind the runtime routes, including the cover-letter draft and version routes, and the schema guide was missing Check metadata. The OpenAPI test now lists the new letter routes and the Trash wording.
Bump workspace dependencies to their latest versions and dedupe the lockfile.
The upgrade left stale duplicates in pnpm-lock.yaml that broke the build and tests:
- @deepseek-ai/schemastery resolved to both 3.18.2 and 3.18.4. Both copies declare
the global Schemastery namespace, so dsh-plugin's declaration emit failed with
TS2883 on `Config`. `pnpm dedupe` collapses it to 3.18.4.
- vite's optional tsx peer resolved to 4.23.13 for importers without a direct tsx
dependency and 4.23.15 elsewhere, producing two vitest 5.0.2 instances. Loading
both in one run broke `expect(...).rejects`. Re-resolving tsx unifies the graph.
* 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.
Dynamic client registration unconditionally rewrote token_endpoint_auth_method
to "none" for every unauthenticated request, downgrading clients that asked for
client_secret_basic or client_secret_post to public clients. Those clients were
issued no client_secret but still authenticated at the token endpoint with the
method they registered, so the exchange failed with 401 invalid_client.
Connecting Composio to the MCP server hit this on every attempt.
Default to "none" only when the client omits the field, which keeps PKCE-only
MCP clients working while confidential clients receive a usable secret.
Patch and minor bumps across the AI provider SDKs (@ai-sdk/*, ai),
@aws-sdk/client-s3, react-email/@react-email/ui, knip and jszip, with
pnpm-lock.yaml regenerated to match.
Also records the audit overrides and patched dependencies in the root
package.json alongside the existing pnpm-workspace.yaml entries.
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.
* feat(ats): add ATS checker and replace resume analysis
Adds a public, browser-only ATS checker at /ats-checker and an ATS Check
section in the builder's right sidebar. PDFs are parsed locally: text
extraction, reading order, contact and date recovery, section detection,
and file-level readability are scored deterministically, with evidence
cited per finding and skipped checks reported rather than counted as
passes.
Removes the AI-scored resume analysis it supersedes: the resume_analysis
table (dropped via migration), the get_resume_analysis MCP tool, and
POST /ai/analyze-resume. The replacement, POST /ai/ats-review, reviews
extracted resume text and returns qualitative feedback with no score.
Also bumps the version to 5.2.9 and adds the changelog entry.
* chore(deps): bump workspace dependencies
* fix(ats-checker): keep negation inside each 'what this does not do' bullet
The three bullets were bare fragments whose negation came from the
section heading, which translators never see. A dozen locales rendered
them as affirmative assertions or imperatives, so the page claimed the
checker enforces a one-page rule and predicts rejection -- the opposite
of the source, and directly contradicted by the sentence beside it.
Each bullet now carries its own negation, so the polarity cannot be
lost in translation. Re-extracted and refilled across all 53 target
locales.
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(adr): propose agent AI SDK v7 adoption plan
* fix(ai): bind analyzeResume through aiService in service test
The test destructured analyzeResume as a named export that does not exist; main was red.
* test(agent): keep pure ai helpers real via spread-actual mock factory
* feat(agent): add run guards, patch version guard, run wall-clock timeout
* feat(agent): validate UI messages at the send boundary
* feat(agent): crash-safe draft-row persistence and server-side cancellation
* feat(agent): reap stale run claims at boot, on send, and on thread open
* feat(agent): fresh-document patch output and tiered context pruning
* feat(ai): shared agent tool contracts and message metadata schema
* feat(agent): add per-thread review-patches setting with update endpoint
* feat(agent): gate resume patches behind hmac-signed tool approval
* feat(agent): merge question answers and approval decisions before run claim
* feat(agent): approval ui with composed auto-send and fixture-driven tests
* feat(agent): usage metadata, tool activity cards, smoother streaming
* feat(agent): tool-call repair, input examples, structured step logging
* chore(i18n): translate new agent workspace strings across all locales
* fix(agent): gate stale-run draft cancellation on winning the claim clear
Snapshot streaming drafts before the conditional clear and skip the flip entirely when another reaper or a replacement run already cleared the claim. Also address review nits in eleven locale catalogs.
* fix(agent): flip reaped drafts only when their snapshotted state is unchanged
* fix(agent): address review findings across run lifecycle, context budget, and approval flow
- bind patches to the revision the model read via signed baseUpdatedAt
- claim the run before consuming a continuation; recorded-but-unexecuted approvals retry as pending continuations
- keep run ownership on stop() until cancellation persists; preserve the claim for the reaper when final persistence fails
- estimate tokens without serializing binary attachments (tokenx) and enforce the budget by dropping oldest whole turns
- mark crash-recovered patch results as snapshot boundaries; strip /data prefixes at execution time
- retry failed continuations without regenerate; mount a single AgentChat; disable response controls on read-only threads; freeze review toggle during runs (client+server)
- accumulate usage across continuations and match the SDK's nested usage shape; label-form token strings; reorderable source label; accessible note field; state-neutral web-search label
* chore(i18n): translate revised agent strings across all locales
* fix(agent): harden baseUpdatedAt validation and address review follow-ups
- bundle tokenx in the server runtime dependencies (e2e boot failure)
- strict ISO schema for baseUpdatedAt plus loud executor rejection of unparseable values
- it-IT source label consistency (Fonte)
- prove penultimate-turn retention in the context pruning test
* chore(deps): exempt tokenx from knip for the externalized server bundle
Each case rebuilt the whole spec, which walks every router and resume JSON
schema. The first case already carried a raised 15s timeout and still timed out
on a loaded machine. The spec is deterministic and only read here, so build it
once: the file drops from over 15s to 1.86s.
Public resume pages only produced their OpenGraph and Twitter tags client side,
so a shared link had no card at all. The server now injects them into the shell
and swaps in the resume's own title and description.
The lookup is scoped to public, password-free resumes and deliberately avoids
resumeService.getBySlug: that counts a view and would expose a protected
resume's summary to an unauthenticated crawler. User-authored values are escaped
before they reach the HTML, and any lookup failure falls back to the plain shell.
getResumeSocialMeta is shared with the client route head so the two cannot drift.