Resource-oriented REST aliases cover file imports (PDF text layer, JSON
Resume, Reactive Resume v4/current, LinkedIn), PDF/DOCX/Markdown/JSON
exports for resumes and cover letters, editor content checks, job-term
matching and a public PDF readability checker. Collection endpoints accept
limit/offset with X-Total-Count headers, and PATCH routes cover partial
updates. Existing routes and response shapes stay unchanged.
The OpenAPI spec marks public operations with `security: []`, the REST
surface gets a 40 MiB body limit, and Better Auth publishes its own
OpenAPI schema. Cookie sessions are rejected for cross-origin requests.
DOCX export parses HTML with node-html-parser so it runs on the server,
and PDF line extraction moves to @reactive-resume/import for reuse.
* fix(dsh-plugin): accept the DSH 0.2 host line in peer ranges
The Harness peer ranges pinned `^0.1.0-rc.6`, which the 0.2.0-rc.2 host
rejects, so a profile on the new core refused to load the plugin:
Plugin dsh-plugin-reactive-resume@0.1.0 is incompatible with dsh 0.2.0-rc.2
The host decides that with
`semver.satisfies(runtime, range, { includePrerelease: true })`. Under that
mode the upper bound is what fails: `^0.1.0-rc.6` expands to
`>=0.1.0-rc.6 <0.2.0-0`, and `0.2.0-rc.2` sorts above `0.2.0-0` because the
numeric identifier `0` precedes `rc`.
npm's default mode, which the plugin market's checker uses, is stricter: a
prerelease only satisfies a comparator set that pins the same
major.minor.patch tuple with a prerelease of its own. There the bare
`^0.1.0-rc.6` admits only `0.1.0-rc.6` through `0.1.0-rc.8`;
`>=0.1.0-rc.6 <0.3.0-0` still admits only those three, and `*` admits none of
the 29 published releases. An explicit union is the only form both modes
accept, so the MCP bridge peer and the prompt peer both become
`^0.1.0-rc.6 || ^0.2.0-rc.1`.
The union is additive: everything the old range admitted is still admitted.
`devDependencies` move to `^0.2.0-rc.2` so the package develops against the
core it now claims.
No source change was needed. `dsh-mcp-client@0.2.0-rc.2` keeps its `Config`
union, its `apply(ctx, config)` signature, and the
`mcp__<serverName>__<rawName>` public tool name, while
`dsh-system-prompt@0.2.0-rc.2` keeps `section({ name, order, text })`. The
0.2 additions to the bridge — MCP resource publishing and a
server-instructions prompt section — are additive and scoped to
`ctx.inject(["mcpResources"])` and `ctx.inject(["systemPrompt"])`, neither of
which this package depends on.
The README records the two comparison modes, since the prerelease rule makes
the obvious alternatives silently wrong.
* fix(dsh-plugin): refresh DSH 0.2 lockfile
---------
Co-authored-by: ddddd-ren <ddddd-ren@users.noreply.github.com>
Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
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.
"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.
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.
Under @babel/core 8, babel-plugin-react-compiler 1.0 skipped every function with a destructuring default without saying so, which left about 70 components unmemoized, including every packages/ui primitive. A test compiles a component with a default prop through the same preset and fails if that comes back.
Custom styles no longer need @version 1; at the top: the language
version already lives beside the text, so new and converted stylesheets
leave it out, the editor hides it in older ones, and the compiler
ignores it. The validity status and inline diagnostics are gone too:
whatever applies shows in the preview and anything else is left out.
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.
Work around more Forme 0.25 layout defects in the converter: row-reverse
laid out as row, min and max widths ignored along a row, text dropping
view children, empty text taking a line, overflowing optimal line
breaks, a text in a splitting row wrecking later pages, page breaks on
row items, four equal border sides, percentage corner radii, pictures
under their shadow, and imported table rows. Rotation is left out while
Forme misplaces rotated boxes; the stylesheet editor warns about it and
about percentage padding.
The page map rebuilds blocks Forme drops from its layout, the browser
renderer rejects documents whose fonts can't load so callers fall back
to the server PDF, and every render gets fresh font entries. Glalie's
sidebar is one band at the combined tint.
Tests are re-baselined where they encoded react-pdf metrics, and engine
limits (list marker keep-together, RTL line order, characters above
U+FFFF, percentage padding) are expected failures. The plan logs the
migration in section 13.
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).
Templates keep their react-pdf-style primitives. A small React renderer
records what they draw, and the result is converted to a Forme document:
styles, fonts (with ligatures disabled for text extraction), Phosphor
icons as SVG paths, pictures fitted by object-fit, and the page map from
source locations. Workarounds for Forme 0.25 layout defects live in the
converter: opaque translucent colours, border insets, row splitting,
column gaps across pages, absolute positioning and repeated backgrounds.
Consumers, the Semantic CSS adapter and the old react-pdf dependencies
follow in later commits.
Adds @axe-core/playwright and audits the editor's modes, the assistant, both Share tabs, Documents, New, the letter editor, the command palette, Applications, Settings, the shared resume, the ATS checker and sign-in against WCAG 2.1 AA, in light and dark and at phone width. A keyboard spec checks that the Share sheet, the assistant, New and the command palette return focus to what opened them.
Attachment, bubble, empty, marker, message, message scroller, questionnaire, resizable and sidebar had no users after the assistant moved into the editor, and neither did react-resizable-panels or @shadcn/react.
Six settings pages become Account, Preferences and AI & developer, with an
in-page nav beside a 680px column (tabs on tablets, a three-row root on
phones, which the Account tab opens).
- Account: photo upload, name, username with the instance host, and email,
each saving on blur; password, a two-step verification switch, passkeys and
connected sign-in; Export everything as one zip of documents and
applications; Delete account with the counts of what goes and typing
"delete"; Sign out.
- Preferences: Light, Dark and System tiles, the interface language and the
motion note.
- AI & developer: provider rows with Test ("Connected · 420 ms" or the exact
error) and an Edit dialog holding the switch and delete; Add provider with
all sixteen; API keys in a table with a New key dialog (30 days, 90 days,
Never), the key shown once, and Revoke with Undo; the MCP address with Copy.
The old settings addresses redirect, and links across the app, the command
palette and the user menu point at the new pages.
The assistant opens beside the resume or letter it works on: a third column
at 1280px and wider, in place of the left panel from 1024px, a drawer below
that, and full screen on phones. The bar's button and Mod+J toggle it.
The panel sets up a provider in place, suggests what to ask, streams replies
with Stop and Continue, asks clarifying questions, and shows proposed edits as
change sets with page marks, the outline's "n proposed" pill and the page
caption. Context chips decide whether the next message shares the document and
the posting. Errors keep the message with Retry and Switch model, past
conversations are grouped by document with their outcomes, and the
conversation can be copied as a transcript.
Improve in the rich-text toolbar suggests a stronger verb, a result, a shorter
line or the user's own request for the line holding the caret, and replaces it
only on Replace.
The Agents pages, the builder's assistant sheet and the application copilot
panel are removed; /agent links redirect to the document with the assistant
open. Command palette Ask, Job match's missing terms, Applications' Prepare for
next step and Copy for a job all open the assistant on the right document.
Removes the unused react-resizable-panels and @shadcn/helpers dependencies.
One editor at /builder/$resumeId: a bar with the document menu and save
status, the Write, Design and Check modes (in ?mode=), undo, history,
assistant, Share and the Download PDF split button, over the panel and the
page canvas. Tablets get a drawer that can be pinned in landscape; phones
get Write, Page, Design and Check tabs.
The canvas renders pages at the zoom level (60-150%, Fit), outlines the
selected entry, links page clicks to the panel and panel focus to the page,
and offers Edit entry on phones. Share & export hosts sharing, statistics and
every download format. Offline, the panel explains the state and Share and
Download wait for a connection.
Until their milestones land, the modes host today's section editors, design
sections and ATS check. The resizable-panel shells, rails, dock, header,
mobile shell and react-zoom-pan-pinch are removed.
Replace the achromatic shadcn palette with the Desk & Paper tokens (warm
neutrals, one moss accent, danger, warn and info), exposed to Tailwind
under their spec names; the previous names resolve to the new tokens
until every screen is rebuilt. Hard-coded palette classes become semantic
tokens and stage colors follow the spec.
Fonts move to Newsreader, Hanken Grotesk and JetBrains Mono. The theme
gains a System option that follows the operating system live, applied
before first paint, and Base UI now receives the locale's direction.
Primitives follow the component spec: button variants primary,
secondary, ghost, danger and link with a loading state, accent focus
rings on inputs, SwitchRow, semantic badges, segmented and underline
tabs, restyled menus, dialogs, sheets, tooltips and command bar, and a
single bottom-center toast with an Undo action. New: IconButton,
SegmentedControl, RadioGroup and NativeSelect.
* feat(import): add LinkedIn data export as a resume import source
LinkedIn's "Get a copy of your data" export ships a ZIP of per-topic
CSVs (Profile, Positions, Education, Skills, Languages,
Certifications). Reading these directly gives structured data without
needing a connected AI provider, unlike the existing PDF/DOCX import
path.
Also fixes a latent bug found while building this: parseJSONResume
(and the new LinkedIn parser) built their result via a shallow spread
of the shared `defaultResumeData` singleton, so assigning into
`result.sections.x` mutated that singleton in place and leaked section
data into the next unrelated import call in the same process. Both now
start from a structuredClone.
* fix(import): escape LinkedIn text, harden zip parsing and date handling
Move escapeHtml and toHtml from the plain-text importer into html.ts and
use them for LinkedIn summary, position descriptions and education notes,
so CSV text is HTML-escaped and line breaks become paragraphs or bullet
lists instead of collapsing into one run-on paragraph.
Only an empty end date now marks an entry as ongoing. Date cells that are
not "Mon YYYY" are kept verbatim, so a finished role with an unexpected
date format no longer reads as "Present".
Unzip only the six CSVs the importer reads, matched by exact file name,
and reject any of them larger than 5 MB. This avoids inflating the rest
of a complete LinkedIn export in the browser and stops Learning_Profile.csv
being read as Profile.csv.
Map LinkedIn's five language proficiency options onto levels 5 to 1,
falling back to parseLevel for anything else. Drop the literal BOM strip,
which TextDecoder already handles.
The import dialog no longer mentions an AI provider in the loading toast
for LinkedIn imports, which are parsed entirely in the browser.
Add a regression test for the JSON Resume importer leaking section data
through the shared defaultResumeData object, plus LinkedIn tests for HTML
escaping, unrecognised end dates, BOM headers, exact file name matching,
language levels and oversized entries.
---------
Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
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.
Prepare v5.3.1 with dashboard search and thumbnail improvements, PDF layout fixes, cover-letter integrations, and self-hosting updates.
- Bump the root version and add release notes with contributor credits, cover-letter REST migration instructions, and the new GHCR image path.
- Align the dashboard authentication plugin with Better Auth's fetch dependency to restore auth-client type inference.
- Regenerate the OpenAPI specification so published validation limits match runtime schemas.
Validation: `pnpm lingui:extract` (no missing translations), `pnpm check`, `pnpm typecheck`, and `pnpm test`.
1.0.9 rejects the Path2D objects pdfjs-dist 6.3.289 passes to fill/clip,
failing every raster test with "Value is none of these types `String`, `Path`".
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.