Commit Graph
60 Commits
Author SHA1 Message Date
Amruth Pillai 5850230f89 feat(builder): add hidden section recovery (#3462)
* feat(builder): recover hidden sections

* fix(builder): reopen hidden section recovery
2026-09-05 18:33:11 -07:00
Amruth Pillai 2e711fd14c fix(web): render thumbnails at displayed pixel density (#3454)
* fix(web): render thumbnails at displayed pixel density

* fix: cancel obsolete thumbnail raster work
2026-09-05 18:31:21 -07:00
Amruth Pillai a4bdc54b2c fix(builder): save pending drafts before navigation (#3453)
* fix(builder): save pending drafts before navigation

* fix(builder): bound navigation waits for slow saves
2026-09-05 18:31:10 -07:00
Amruth Pillai 5c8338c175 fix(builder): preserve PDF glyph positions in RTL previews (#3447) 2026-09-05 10:31:39 -07:00
Amruth Pillai 14c7c06516 feat: add per-resume public download button preference (#3419) 2026-09-05 10:10:00 -07:00
Amruth Pillai 1d4194a207 feat: manage cover letters in a shared library (#3423)
* feat: add shared cover-letter library with resume styling

* fix: retain required sanitizer dependency in CI

* fix(cover-letters): prevent concurrent AI draft requests

* chore(codacy): exclude generated migrations

* fix(applications): keep Lingui macro out of callback dependencies
2026-09-05 10:01:22 -07:00
Syed Ali Abbas ZaidiandAmruth Pillai cce6d64afa feat(import): parse a PDF resume without an AI provider (#3400)
* feat(import): parse a PDF resume without an AI provider

Importing a PDF required a connected AI provider, so anyone without a
paid API key could only import the three JSON formats. Almost nobody
arrives with one of those files; they arrive with a PDF. The first thing
a new user tries to do was blocked behind bringing their own key.

Adds a deterministic parser that reads the text out of the PDF in the
browser and prefills the builder. It pulls the contact block, segments
the body on conventional headings, and maps entries to real items,
reusing the ATS period parser for dates so a date range is not mistaken
for a phone number.

Nothing is thrown away: header parts that do not map to a field go into
the description, and unrecognized headings become custom sections. The
imported sections are placed on the page so the result renders straight
away. Output is validated against the resume schema before it is
returned.

Text extraction groups items by baseline rather than trusting hasEOL,
and turns wide column gaps into a double space, which is what lets a
row split into company, position and location.

The AI path still runs when a provider is connected. Word import is
unchanged and still requires one.

Closes #3334

* fix(import): keep every section and entry the PDF actually contains

Review found three ways the parser lost or mangled content, all of them
reproducible.

A document whose first heading was not one of the known aliases never
started a section, because unknown-heading detection was gated on a
section already being open. Everything after it was swallowed as contact
header text. The header block is now bounded by where the contact
details stop, so a heading is recognized wherever it appears.

An entry spreading company, position and dates over three lines was
imported as two malformed items. A line that introduces an entry now
merges into the open entry instead of starting a second one.

An uppercase company such as ACME CORPORATION was read as a section
heading and fragmented the entry. A heading candidate followed by a date
line is now treated as an entry header, which is what it is.

Also escape single quotes, and construct the PDF worker inside the try
so the nested worker is terminated even if construction throws.

Title-case headings are deliberately still not treated as headings:
company and school names are title case too, and splitting on them would
fragment real entries. Such a section stays in the preceding one with its
text intact rather than risking loss.

* fix(import): look past a multi-line preamble before calling a line a heading

The previous guard only inspected the next line, so an uppercase company
followed by a separate role line and then the dates was still read as a
section heading. The experience or education entry was moved into a
custom section and lost.

Heading detection now scans a two-line window for the date that marks an
entry, and stops early at a bullet so a genuine heading whose section
opens with bullet points is still recognized.

The window can suppress a real heading whose first entry puts a bare date
two lines below it. That is the deliberate direction to fail in: a missed
heading leaves the text in the preceding section, while a misread entry
fragments structured content.

* fix(import): collect an entry preamble until its dates appear

An entry that spread company, role, location and dates over four lines
was imported as two broken items: the company with no dates, and the
location carrying the period.

The cause was in entry grouping rather than heading detection. Lines
before a date were only folded into the entry header when the date sat
on the very next line; anything earlier fell through to the description.
Preamble lines are now collected into the entry header until the dates
turn up, bounded by the same lookahead and stopping at a bullet, so an
undated section cannot swallow itself.

The heading lookahead widens to four lines to match, which is the
realistic maximum for company, role, location and dates.

* fix(import): harden local PDF resume parsing

* chore(import): document audited HTML construction

---------

Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
2026-09-05 09:53:37 -07:00
Emanuele Tonello ef47baf243 fix(applications): handle cover letter copy failures (#3394)
* fix(applications): handle cover letter copy failures

* style(applications): format clipboard error toast

* refactor(applications): memoize copy draft handler
2026-09-05 09:51:24 -07:00
Emanuele TonelloandAmruth Pillai 1f0844b39c feat(applications): add contact email and phone (#3396)
* feat(applications): add contact email and phone

* fix(applications): validate imported contact emails

* fix(applications): validate all imported contact fields

* fix(applications): preserve data when contact validation fails

* test(applications): complete contact export fixture

---------

Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
2026-09-05 09:51:21 -07:00
Amruth Pillai cddb01f037 fix(sharing): record public PDF download statistics (#3414)
* fix(sharing): record public PDF download statistics

* docs(api): explain download statistics access cookie
2026-09-05 09:34:52 -07:00
Amruth Pillai fe9b59e111 fix: restore MCP OAuth registration and authorization (#3421)
* fix: align MCP OAuth provider schema and authorization flow

* test: isolate OpenAPI generation from OAuth initialization

* fix: accept auth routes without a callback query

* fix: require explicit OAuth consent and preserve signed requests

* test: verify OAuth audiences through real MCP initialization

* test(e2e): isolate OAuth token audience validation
2026-09-05 09:33:15 -07:00
Emanuele Tonello bf27792ca0 feat(applications): attach generated cover letter PDFs (#3395)
* feat(applications): attach generated cover letter PDFs

* fix(applications): isolate generated cover letter PDFs
2026-09-05 08:51:03 -07:00
Amruth Pillai a3585a24e0 feat(applications): export filtered applications as CSV (#3426)
* feat(applications): export applications as CSV

* fix(applications): strip export CSV formula guard on import and resort catalogs

Re-importing an exported CSV kept the apostrophe that csvCell prepends to
formula-triggering cells, so a note starting with "- " came back as "'- ".
mapCsvToApplications now drops a leading apostrophe when the remainder would
have been guarded, sharing the predicate with csvCell so both sides stay in
sync.

Also runs pnpm lingui:extract: the new msgids were hand-appended to en-US.po
and missing from the other 54 catalogs.

* fix(applications): preserve CSV import values
2026-09-05 08:46:52 -07:00
Amruth Pillai 321f2fb43f fix(builder): validate and confirm resume passwords (#3407)
* fix(builder): validate and confirm resume passwords

* test(sharing): exercise password confirmation in browser flow
2026-09-05 07:32:10 -07:00
Amruth Pillai 18b5aa4745 fix(sharing): hide signup link when registration is disabled (#3409) 2026-09-05 07:32:07 -07:00
Amruth Pillai 735e700929 fix(stylesheet): keep color picker state aligned with source (#3431)
* fix(stylesheet): keep color picker state aligned with source

* fix(stylesheet): serialize picker edits as hex with alpha

* fix: preserve contextual colors in stylesheet editor
2026-09-05 07:29:39 -07:00
Amruth Pillai 00a1357deb fix(applications): show saved notes in detail view (#3402) 2026-09-05 13:26:52 +02:00
Amruth Pillai f29b92e2fb chore(copy): rewrite marketing, app, and docs copy to read less AI-generated
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.
2026-08-28 22:18:29 +02:00
Amruth Pillai c288675b16 Release v5.2.9 (#3382)
* 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.
2026-08-27 03:37:01 +02:00
Amruth Pillai 3c195dc3f8 Release v5.2.8 (#3375)
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
2026-08-24 21:44:16 +02:00
Amruth Pillai da2f1f8244 refactor(applications): autofill from a pasted posting instead of a URL
Fetching an arbitrary job URL server side meant owning SSRF defence, redirect
and size limits, and per-site scraping quirks. The autofill tool now takes only
pasted text, so the URL input, the fetch path and its MCP annotation are gone.

The sheet gates the call behind a tested AI provider and a minimum paste length
so a stray snippet does not spend an AI call.
2026-08-17 22:32:32 +02:00
Amruth Pillai 7a14b0dfbc refactor(settings): rename the danger zone page to account
The page now holds account-level actions rather than only destructive ones, so
it is reachable at /dashboard/settings/account and presented with a neutral
icon in the sidebar and command palette.
2026-08-17 22:32:32 +02:00
Amruth Pillai 23ceee2148 refactor(web): move toast call sites to the new component
Swaps sonner's toast.success/error/loading/dismiss for the new toast.add({ type,
description }) and toast.close across dialogs, auth pages, the builder, the
dashboard and the applications views. Behaviour is unchanged.
2026-08-17 22:32:32 +02:00
Amruth Pillai f4bf6887b9 test(stylesheet): complete system variables at the end of the prefix
The case passed cursor position 5 into "--resume-", which lands mid-token and
reads as a selector context, so it received the selector list. Every other case
in the file uses source.length.
2026-08-17 22:19:52 +02:00
Amruth Pillai 9509b5bc2e refactor(stylesheet): move Semantic CSS to the browser (#3329) 2026-08-16 16:50:27 +02:00
a4bc2693be fix(ai): bound the provider test and explain why it failed (#3319)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
2026-08-16 12:45:00 +02:00
Amruth Pillai 8affc567e3 fix: show non-expiring API keys (#3324) 2026-08-16 10:58:55 +02:00
Amruth Pillai 2d6ea9ce8d fix(auth): allow implicit social signup 2026-08-13 09:23:04 +02:00
Amruth Pillai 035d94183b fix(web): show static template previews in gallery (#3302) 2026-08-10 12:18:47 +02:00
autofix-ci[bot] 36232b631d [autofix.ci] apply automated fixes 2026-07-31 15:25:43 +00:00
Amruth PillaiandCursor Agent ba8e1be2ab fix(stylesheet): harden PDF preflight and surface worker failures (#3284)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-07-31 07:57:54 +02:00
Amruth PillaiandCursor Agent 4a8f87ab8f fix(web): stop custom styles from sticking on Checking (#3283)
Concurrent compile requests from editor intelligence were rejecting
in-flight edit compiles as stale, and the store swallowed that rejection
without leaving compiling. Resolve all compile results and surface
compile failures as an error status so styles can apply again.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-07-31 01:00:45 +02:00
Amruth Pillai 3589b534f5 feat: enable semantic CSS by default 2026-07-30 16:28:44 +02:00
Amruth PillaiandCursor Agent d2ffbf9618 feat: add semantic CSS stylesheets (#3274)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-07-30 12:39:15 +02:00
Amruth Pillai 9110e86997 refactor: ponytail audit 2026-07-27 20:26:16 +02:00
Emanuele TonelloandAmruth Pillai 47349e7ab3 feat: support editing AI provider models in the UI and auto-fill LinkedIn job postings (#3259)
Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
2026-07-27 13:21:57 +02:00
Amruth Pillai 18d0c14aa1 feat: add application timeline history (#3237)
* feat: add application timeline history

* fix: address application timeline review

* fix: keep application tracker e2e stable

* fix: use stable timeline e2e selector

* fix: target timeline note input in e2e
2026-07-09 00:36:45 +02:00
Amruth Pillai 90105cb148 chore: integrate improve-integration 2026-07-08 19:08:31 +02:00
Amruth Pillai fb9c217af2 feat: add command palette entity search 2026-07-06 00:29:06 +02:00
Amruth Pillai b404dbd42a Add application tracker (#3220)
* feat(applications): job application tracker with AI copilot

Add an Applications module at /dashboard/applications: pipeline board
(dnd-kit), table view with bulk actions, Insights (fit tiles, funnel,
sources, shareable funnel-flow SVG), campaigns, tags, CSV import, and
Add/Edit/Detail slide-overs. Each application links a live Reactive
Resume.

AI "Application Copilot" (applications.ai.*): job-posting autofill,
resume↔job match score (fit ring), resume tailoring, and cover-letter /
follow-up drafting — via the user's configured provider.

Board cards + table rows get context menus (edit / move / archive /
delete). Charts are CSS/SVG (no new chart dep); adds a UI Checkbox.

Also includes local TanStack devtools setup and toolchain bumps.

Claude-Session: https://claude.ai/code/session_01TEeRHnEayw2MFCShFRyL5f

* feat(applications): close follow-up gaps + squash migrations

Finish the deferred/open items on the applications tracker:

- Cover-letter upload re-enabled. Fix the storage blocker by deriving the
  key extension from content type (buildFileKey/EXTENSION_BY_CONTENT_TYPE)
  instead of hardcoding .jpeg, so PDFs serve correctly and non-JPEG image
  avatars keep working under FLAG_DISABLE_IMAGE_PROCESSING. Add
  coverLetterUrl/coverLetterName columns + Documents-section upload/remove.
- Contacts editor in the detail sheet (add/edit/remove, keyed per app).
- Board caps rendered cards per column (COLUMN_PAGE_SIZE=50 + "Show more").
- Extract new Lingui messages across locales.
- Guard coverLetterUrl to http(s)/relative at the API boundary.

Squash the five branch-only application-table migrations (create -> +tags
-> +cover-letter -> drop -> re-add) into a single clean CREATE TABLE via
drizzle-kit generate.

Claude-Session: https://claude.ai/code/session_01TEeRHnEayw2MFCShFRyL5f

* chore: update dependencies

* fix(web): address React Doctor findings — compiler, purity, query, component structure

prefer-module-scope-pure-function: hoist buildSubtitle, getDecimalPlaces,
handleLocaleChange, onLocaleChange, stop, listContent/groupedListContent to
module scope so they aren't rebuilt on every render.

react-compiler-todo (??=): rewrite draft.metadata.styleRules ??= [] to the
non-assignment form to unblock auto-memoization.

set-state-in-effect: derive updatedAtLabel at render time instead of syncing
it through useState + useEffect.

query-destructure-result: destructure useQuery results at call site in
resume-analysis and resume-thumbnail to follow TanStack Query v5 convention.

only-export-components: extract non-component exports to sibling .ts files so
Fast Refresh can preserve component state:
  - getNextWeights → typography/get-next-weights.ts
  - detectJsonImportType + ImportType → dialogs/resume/import.utils.ts
  - getLocaleOptions → features/locale/locale-options.tsx
  - preview helpers + DEFAULT_PDF_PAGE_SIZE → preview.shared.utils.ts
  - resolveHighlightToolbarState + defaultHighlightColor → rich-input.utils.ts
  - computeDelta + getSparklinePoints → statistics.utils.ts

no-multi-comp: split multi-component files into focused companions:
  - ResumePane + ToolbarButton → routes/agent/-components/resume-pane.tsx
  - DesktopBuilderShell → builder/$resumeId/-components/desktop-builder-shell.tsx
  - MobileBuilderShell + helpers → builder/$resumeId/-components/mobile-builder-shell.tsx
  - setBuilderLayout/getBuilderLayout moved to -store/sidebar.ts

fix(tests): add Resume type import to section-builder mocks and cast partial
mock data as unknown as Resume to satisfy stricter type checking; fix
noExplicitAny Biome errors in the same mocks.

* feat(applications): improve performance

* chore: fix knip issues

* perf(builder): halve per-keystroke render cost

Section-form fields called `form.handleSubmit()` on every keystroke, which
re-validated the whole form and toggled submit state — firing the render
cascade twice per character (~6809 renders/keystroke, FPS dropping to 9).

Persist via a form-level `listeners.onChange` instead and drop the per-field
`handleSubmit()` (basics, custom-fields, design). Narrow header/dock resume
subscriptions to metadata slices so they no longer re-render on content edits.

Cuts renders 6809 -> 3403 per keystroke (50%), 0 frame drops. Save, preview,
and design controls verified working; 449/449 web tests pass.

* perf(home): eliminate hero CLS from unreserved video box

The hero <section> is `flex items-center` (shrink-to-fit), so the video
wrapper's width depended on the video's intrinsic size, which only resolves
after the media loads. aspect-ratio couldn't reserve height without a definite
width, so the video grew from ~190px to ~563px after first paint and shoved the
centered hero text down ~373px (CLS ~0.095).

Give the wrapper a definite width (w-full + mx-auto on the CometCard) and set an
explicit aspect ratio + width/height on the video so its box is reserved before
load. CLS 0.095 -> 0; hero stays visually centered at max-w-4xl.

* docs: add application tracker guides

* chore(db): squash application migrations

* fix(email): import React in auth template for server-side rendering compatibility

* chore(release): v5.2.1

* Refactor resume rendering and builder workflows

* fix: address application tracker review findings
2026-07-05 23:44:04 +02:00
Amruth Pillai 20c803e934 feat(export): separate resume/cover-letter downloads, redesign dialog, add Markdown export (#3217)
* 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".
2026-07-05 14:40:49 +02:00
autofix-ci[bot] d5b177aa89 [autofix.ci] apply automated fixes 2026-07-04 20:29:16 +00:00
Amruth Pillai 70df113ee6 refactor(web): finding 9 — public-resume reuses useResumeExport hook
Loosen useResumeExport param to ExportableResume { name, slug, data } so the
public resume page (where name may be '' for non-owner viewers) can reuse it.
getExportName() falls back to data.basics.name then slug, matching the
original inline logic.

Claude-Session: https://claude.ai/code/session_012Bnvt1MghwHj4qQRxuQUGa
2026-07-04 21:48:04 +02:00
Amruth Pillai 44e9a8a29f refactor(web): finding 6 — delete normalizeResumePreviewProps, inline defaults
normalizeResumePreviewProps had exactly one production caller (preview.tsx).
Defaults now live in the ResumePreview destructuring; the normalizer and its
test cases are removed.

Claude-Session: https://claude.ai/code/session_012Bnvt1MghwHj4qQRxuQUGa
2026-07-04 21:47:53 +02:00
Amruth Pillai 02538836a9 refactor(web): finding 5 — replace match(boolean) with ternary + ActionButton wrapper
Three auth-settings components (password, two-factor, social-provider) each
duplicated an identical m.div hover/tap wrapper for both branches of
match(boolean). Extracted one ActionButton wrapper and replaced match with
a plain ternary; removed ts-pattern imports.

Claude-Session: https://claude.ai/code/session_012Bnvt1MghwHj4qQRxuQUGa
2026-07-04 21:47:22 +02:00
Amruth Pillai 22398a502b refactor(web): finding 4 — extract runSignIn helper in SocialAuthButtons
Three near-identical toast→auth→error→invalidate handlers collapsed into
one generic runSignIn(fn) function.

Claude-Session: https://claude.ai/code/session_012Bnvt1MghwHj4qQRxuQUGa
2026-07-04 21:47:11 +02:00
Amruth Pillai 34398a578b refactor(web): finding 8 - inline 7 one-caller alias hooks from draft.ts
useInitializeResumeStore, useMergeResumeMetadata, useSaveStatus,
useCanUndo, useCanRedo, useUndoResume, useRedoResume each had exactly
one caller. Inline useResumeStore selectors at call sites and remove
the wrappers. Skipped usePatchResume (4 callers).

Claude-Session: https://claude.ai/code/session_012Bnvt1MghwHj4qQRxuQUGa
2026-07-04 21:29:32 +02:00
Amruth Pillai 82d961241e fix: polish ai agent and command palette 2026-07-04 20:24:39 +02:00
Amruth Pillai 3e96605d4c fix(web): avoid retesting new AI providers 2026-07-04 19:42:41 +02:00
Amruth Pillai 27df724d2a feat(builder): show live PDF previews in template gallery, remove hover card 2026-07-04 18:05:45 +02:00