Commit Graph
6147 Commits
Author SHA1 Message Date
Amruth Pillai 82fa327900 feat(web): letters on the editor shell, with drafting, History and both files to download 2026-09-28 23:08:10 +02:00
Amruth Pillai aa9a5113c0 fix(pdf): keep the letter's header when the page tree is built 2026-09-28 23:08:10 +02:00
Amruth Pillai fdad39c632 feat(api): structured letters with live links, versions and a streaming draft 2026-09-28 23:08:04 +02:00
Amruth Pillai c061367a27 docs: log M8 in the redesign plan 2026-09-28 22:15:25 +02:00
Amruth Pillai 2a41d45efc chore(i18n): extract catalogs for applications 2026-09-28 22:14:22 +02:00
Amruth Pillai 8dc45ee7e7 test(e2e): drive the rebuilt applications page 2026-09-28 22:14:21 +02:00
Amruth Pillai 462db6011a feat(web): applications as a grouped list, a rebuilt detail sheet and an add dialog
- The page: CSV import/export behind one icon, Add application, a dismissible
  nudge for the application waiting longest without a reply (10+ days),
  List · Board · Insights · Calendar, search across role, company, contacts
  and tags, and Show closed.
- List (the default): grouped by stage in the order that needs you first,
  with collapsible groups, the next step (warn when overdue), what was sent,
  sorting from the headers and row checkboxes for bulk moves, tags, closing
  and deleting. Phones get two-line rows and no board.
- Board: a column per stage; drops and Move to… show the same toast.
- Detail sheet (480 px, full screen on phones): the stage stepper with Move to
  next, the next step (the next interview or follow-up) with Edit and Add to
  calendar (.ics), what was sent (opening the version sent, read-only, in
  History), Tailor a resume and Write a letter, editable salary and source,
  contacts, tags, autosaved notes and the activity timeline. Close
  application… takes a reason; Delete is in ⋯ and asks first.
- Add dialog: paste a link or posting; its role, company and requirements are
  read into editable fields and the posting is saved with the application.
  Add, or Add and tailor a resume.
- Insights: how far applications get (from their stage history), how many
  heard back and how fast, and tailored against base resumes, above the
  existing charts.
- CSV import shows how columns were matched before saving and lets you
  download the rows it skips; export adds the closed reason.
- The builder opens History on a version from ?version=.
2026-09-28 22:14:20 +02:00
Amruth Pillai ce0b4c606a feat(api): a closed stage with reasons, what was sent, posting requirements and a posting reader
- Applications end in a `closed` stage with a reason (not selected, withdrew,
  accepted another offer, no response). The migration moves `rejected` to
  closed + not selected and archived applications to closed, rewrites
  `rejected` in their stage history, and ships rollback.sql for older
  versions. `archived` stays, deprecated; `rejected` is still accepted as
  input and means closed.
- Once an application with a linked resume reaches Applied, the resume is
  saved as a "sent" version named after the company, and the application keeps
  its id and the resume's Check score then.
- New columns: closed_reason, cover_letter_id (backfilled where exactly one
  letter was written for the application), sent_resume_version_id,
  sent_check_score and requirements.
- applications.ai.parsePosting reads a pasted link or posting. Links are
  fetched on the server: https only, public addresses checked at connect time,
  three redirects, 2 MB and 10 s at most. A page's JobPosting data fills the
  fields without AI; with a provider, the model reads role, company,
  location, salary and requirements.
- MCP application tools take closedReason and coverLetterId.
2026-09-28 21:44:55 +02:00
Amruth Pillai f5a3fa35eb docs: note the DOCX page alignment in the M7 log 2026-09-28 21:35:51 +02:00
Amruth Pillai 25306630e7 fix(docx): print pages the way the PDF does for one-column templates and full-width pages 2026-09-28 21:35:39 +02:00
Amruth Pillai 4fbc0d7b1d docs: log M7 in the redesign plan 2026-09-28 21:34:16 +02:00
Amruth Pillai 1253ef08a6 chore(i18n): extract catalogs for check mode 2026-09-28 21:33:43 +02:00
Amruth Pillai 3397c77917 test(e2e): drive check mode's issues, job match and parser view 2026-09-28 21:33:42 +02:00
Amruth Pillai 565424631a feat(web): check mode with pinned issues, job match, a writing review and proposals
- The score ring (live checks only, easing to each new score), the verdict,
  and Issues · Job match · Writing tabs.
- Issues: numbered cards with category, explanation and fix, pinned to their
  lines on the page with warn pins and wavy underlines. One-step fixes apply
  with undo; the rest open the field in Write. Show on page, and Ignore (Keep
  for the two-column issue), stored with the resume. Category rows below, open
  when they need attention. "Also check the exported PDF" reports in a toast.
- Job match reads the linked application's posting, or a pasted one that can
  be saved as an application. Missing terms ask where they belong (Add to
  Skills) or can be hidden; covered terms light up their entries on the page.
- Writing: an opt-in AI review that says what it sends. Rewrites of bullets
  and paragraphs arrive as proposals: struck-through old text and highlighted
  new text on the page, numbered markers, Accept, Reject, Accept all, A/R and
  arrow keys, one undo step. Out-of-date proposals can't be applied.
- "What a person sees / What a parser reads": the parser view extracts the
  text of the PDF on the page, in reading order, flagging issue lines.
- Phones step through issues on the page; tablet pins open the drawer.
- The builder's ATS section and its deep-check UI are gone.
2026-09-28 21:33:41 +02:00
Amruth Pillai 1608d56903 feat(api): passage rewrites in the writing review, and a resume's linked application
- ai.atsReview takes optional passages (id, where, text). A suggestion that
  rewrites one names it in passageId with the whole new passage, which the
  editor offers as a proposal to accept or reject.
- The review prompt fills its placeholders in one pass, so resume text that
  looks like a placeholder is sent as it is.
- resume.getById returns applicationId, the job application a resume was made
  for, so Check's job match can read its posting.
2026-09-28 21:33:18 +02:00
Amruth Pillai e26ce08fc5 feat(resume): check categories, stable issue keys, ignores and a two-column rule
- Every live check has a category (contact details, dates, layout, section
  headings, writing). Reports score the applicable rules, per category too.
- Findings carry a key that uses entry ids instead of array indexes, so it
  survives reordering. Keys in the new metadata.check.ignored set findings
  aside without counting them against the score.
- TWO_COLUMN_LAYOUT flags a two-column template that prints a sidebar.
- Full-width pages print no sidebar, so sections placed only there are now
  reported as never printing instead of passing as main-column content.
- metadata.check also holds job-posting terms hidden as not true; public
  viewers don't receive it.
- ats-pdf exports the job-description matcher, spelling variants and the
  semantics reader for the editor's job match and parser view.
2026-09-28 21:33:11 +02:00
Amruth Pillai 0e36729b80 docs: log M6 in the redesign plan 2026-09-28 20:46:23 +02:00
Amruth Pillai 7f162bf230 fix(web): satisfy Biome in the first-paint theme script and loader styles 2026-09-28 20:45:01 +02:00
Amruth Pillai 7831cc04a7 chore(i18n): extract catalogs for documents, trash and the new-document dialog 2026-09-28 20:39:05 +02:00
Amruth Pillai 443897dcb0 test(e2e): drive documents, trash and the new-document dialog
The shared fixture creates its sample resume through the API and opens
it; documents-new covers Start blank (named after the headline) and Copy
for a job (linked and searchable by the application). dashboard-lifecycle
renames inline, duplicates, trashes with undo, restores and deletes now;
the letter, lock, view, import, auth and direction specs follow the new
library and menus.
2026-09-28 20:39:04 +02:00
Amruth Pillai daa7d331f3 feat(web): documents home, trash and a new-document dialog
The dashboard becomes Documents: resumes and saved letters in one
library, in a new app shell (a 240px sidebar, an icon rail on tablets and
bottom tabs on phones) with Documents, Applications, Trash (when it has
items), New (N) and the account row.

- Documents: All / Resumes / Letters with counts, search (/) across
  titles, tags and linked applications, sort, grid or list (remembered on
  the device), tag chips once tags exist, cards with the real first page,
  "Resume · Edited 2h ago", the linked application, lock and "New" badges,
  and the first-run screen. A file dropped anywhere on the page imports.
- One menu on cards, rows and right-click/long-press: Open, Rename
  (inline), Duplicate, Copy for a job… (resumes) or Link to application…
  (letters), Tags…, Lock editing, and Move to Trash with Undo.
- Trash lists days left, with Restore and Delete now (asks once).
- New: import a file in three labelled steps with the result and flagged
  fields (resumes, and saved letters' JSON), copy a resume for a job,
  start blank (named after its headline until renamed), a new letter, or
  a sample resume.

Also:
- /dashboard/resumes and /dashboard/cover-letters redirect to Documents.
- Settings pages sit behind a tab strip until they're regrouped, and the
  account menu gains Settings.
- A letter written inside a resume can be copied to Documents from its
  entry menu (Q3k), replacing the library's copy action.
- The builder's document menu and the letter editor move documents to
  Trash instead of deleting them.
- Dialogs reopened right after closing start fresh.
- Removed: the resume and letter libraries and the old create and import
  dialogs.
2026-09-28 20:38:54 +02:00
Amruth Pillai d4406c729b feat(api): name imports after their person and keep locked documents' details
An imported resume is named from its content (the person's name, else the
headline) instead of a random name. Renaming, tagging or linking a locked
document is refused, as moving it to Trash already was.
2026-09-28 20:38:32 +02:00
Amruth Pillai a568f64b43 feat(api): one documents library with Trash, job links and automatic names
Schema: resume gains application_id (the job a copy was made for),
trashed_at and auto_name; cover_letter gains tags, is_locked and
trashed_at.

A new documents router treats resumes and saved letters as one library:
list (live or in Trash, with the linked application), counts, rename,
setTags, setLocked, linkApplication, trash, restore, purge (only from
Trash) and copyForJob, which duplicates a resume, links the copy to the
application and gives the application the copy when it has none.

- resume.delete and coverLetters.delete now move to Trash. Documents stay
  there for 30 days and are purged when their owner next lists documents,
  so no scheduler is needed.
- Documents in Trash are left out of resume and letter lists, and a resume
  in Trash isn't shared: getBySlug and verifyPassword skip it.
- Locked documents can't be moved to Trash; locked letters can't be edited.
- A blank resume created with autoName takes its headline as its name
  until someone renames it.
2026-09-28 19:59:03 +02:00
Amruth Pillai dc6de786f7 docs: log M5 in the redesign plan 2026-09-28 19:48:46 +02:00
Amruth Pillai 13787333d4 chore(i18n): extract catalogs for the share and export sheet 2026-09-28 19:48:17 +02:00
Amruth Pillai 6540c2aee9 test(e2e): drive the share and export sheet
The sharing specs use Share → Link (public switch, visitor downloads, the
password switch and the public address from Open public page), and the
export specs pick a format in the Download tab. share-history covers
renaming the address with the old one redirecting, and naming, previewing
and restoring versions.
2026-09-28 19:48:15 +02:00
Amruth Pillai c8df749258 feat(web): one sheet for sharing, downloads and version history
Share & export is one 440px sheet (a full-height bottom sheet on phones)
with Link, Download and History tabs. Share opens Link, the ▾ beside
Download PDF opens Download, the clock opens History, and ⌘⇧S / ⌘⇧E open
their tabs. On desktop the page moves 120px aside so it stays visible.

- Link: the public switch card; the address with a live check (300 ms),
  the reason it can't be used and a suggestion, saved only once valid, so
  the old address stays live; Copy ("Copied" for 2 s); visitor downloads;
  the password (Q3a); Open public page; a QR code; Share via… where the
  platform offers it; and views, downloads and time since the last view
  over 30 days with a daily chart, or the explanation while the link is
  off.
- Download: format cards explained by when to use them, Resume or Cover
  letter (with the resume header option) when the resume has a letter,
  the file name recruiters see (First-Last-Resume by default, unsafe
  characters stripped), a non-blocking note about open Check issues, and
  progress inside the button; a failure offers Try again and PDF.
- History: name the current state; a timeline of Now, sessions, named
  versions, restores and where the document came from. Picking a version
  shows it on the page, read-only and outlined, with its banner; Restore
  saves "Before restore" first. Named versions can be renamed or deleted.

Also:
- Autosaves send this visit's session id, and naming or restoring a
  version saves pending edits first.
- The one-click PDF and every export use the First-Last-Resume name.
- Resume dialogs stop asking for a slug (it's edited in Share), which also
  stops Rename from overwriting a custom slug with one made from the name.
- The public route redirects a renamed resume's old address.
- Removed: the download dialog, the version-history menu and the sharing,
  statistics and export sections.
2026-09-28 19:48:08 +02:00
Amruth Pillai 060750a868 feat(api): version kinds, session autosaves, named versions and slug redirects
Versions:
- resume_version gains kind (created, import, auto, named, before-restore,
  restored, ai, sent), name and session_id. The migration backfills kind
  from the old English labels; label stays for API clients.
- Each editor visit sends a session id with resume.update, and its saves
  keep one autosave version, refreshed at most every two minutes. Calls
  without a session keep the old throttled autosave.
- Creating a resume writes a "created" version and importing an "import"
  one, so history is never empty.
- New procedures: getVersion (for previews), createVersion (name the
  current state), renameVersion and deleteVersion (named versions only).
- Retention replaces the 30-row cap: autosaves, AI edits and restores
  expire after 90 days, pruned when a resume gets a new version (there is
  no scheduler, and the Vercel entry skips startup hooks); a cap of 500
  autosaves per resume bounds storage.

Slugs:
- resume.checkSlug validates ^[a-z0-9]+(-[a-z0-9]+)*$, reports which of the
  user's resumes uses a taken slug, and suggests a free one.
- A changed slug must match the pattern (existing ones keep working until
  changed). The old slug is kept in resume_slug_redirect for 30 days, and
  getBySlug and verifyPassword resolve it; the response carries the current
  slug so clients can redirect.
- create and duplicate make the slug optional and generate a unique one
  from the name; duplicate no longer falls back to the original's slug,
  which always collided.

The migration also applies the pending drop of the redundant
resume_user_id_index removed from the schema in b953435f2.
2026-09-28 19:46:41 +02:00
Amruth Pillai 31e8dc39d4 docs: note the PDF engine and Semantic CSS move to Forme next phase 2026-09-28 19:26:24 +02:00
Amruth Pillai c18911aaca docs: log M4 in the redesign plan 2026-09-28 19:08:42 +02:00
Amruth Pillai 448db84e50 chore(i18n): extract catalogs for the library letter picker 2026-09-28 19:07:04 +02:00
Amruth Pillai 4da00ddce1 fix(web): start a resume cover letter from a library letter again
The entry dialogs removed with the Write panel carried "Import from
library", which copied a saved letter's recipient and text into a new
cover-letter entry. A new, empty cover-letter entry now offers the same
picker inline. The copy stays independent of the library letter.

The cover-letter-library spec drives the inline picker and waits for the
renamed JSON copy before checking the original, which removes a race with
the list refetch.
2026-09-28 19:07:03 +02:00
Amruth Pillai 49464bb7ff chore(i18n): extract catalogs for the Design panel 2026-09-28 19:04:28 +02:00
Amruth Pillai 9dd38433d6 test(e2e): drive the Design panel
template-switch now previews a template on hover, applies it from its card,
checks it after a reload and undoes a switch from the toast. The section
helper opens Design groups and the exact-value sections inside Advanced.
2026-09-28 19:04:27 +02:00
Amruth Pillai 1ef7c9fa10 feat(web): rebuild Design with template previews, presets and Fit
The Design mode is a single panel with a sticky group nav: Template, Type,
Color, Page and a collapsed Advanced.

- Template: filter chips, and thumbnails rendered from the user's own
  content at idle time, cached by template and content. Hovering or
  focusing a card (holding it, on touch) previews the template on the page
  with a "Previewing" chip; leaving the cards or Esc restores it; a click
  applies it with an Undo toast. Two-column templates get a sidebar
  sub-panel for its width and sections.
- Type: five font pairings, text size 9-12.5 pt and density. Color: eight
  accents and a hex field with its contrast on white, offering a darker
  shade below 4.5:1. Page: paper, language, margins, icons and link
  underlines.
- Advanced: the date format, every exact-value editor, custom CSS and
  Reset to template defaults.
- When content runs past the authored pages the canvas says by how many
  lines and offers Fit, which tightens density, then margins, then size
  (never below 9 pt), re-rendering after each step, as one undo step.
- Phones get the groups in a half-height sheet over the live page.

The template gallery dialog, the Template section, the collapsible section
chrome and its collapse store are gone. ATS design findings now open the
Design group that fixes them. The desktop panel is a containing block, so
screen-reader text deep in a long panel no longer stretches the document.
2026-09-28 19:04:21 +02:00
Amruth Pillai 85352ee11b feat(schema): describe template layouts in one place
Columns, sidebar side, header placement and ATS safety for every template
now live in templateLayouts. The template gallery metadata and the layout
editor read it, and a DOCX test checks the two-column configurations
against it.
2026-09-28 19:04:03 +02:00
Amruth Pillai 7244485d6e docs: note when the preview moves to a worker 2026-09-28 18:20:48 +02:00
Amruth Pillai 1b079dd5bd docs: log M3 in the redesign plan 2026-09-28 18:20:29 +02:00
Amruth Pillai de85121f5e chore(i18n): extract catalogs for the Write panel 2026-09-28 18:20:28 +02:00
Amruth Pillai e2e5c15580 test(e2e): cover inline editing and click-to-select
Specs follow the outline rows, the eye on each row, the photo row, the
structured dates and the Full name field, and a new spec checks that a
click on the page opens its entry and focusing a field outlines its block.
The download-preference spec waits for the share address after a reload.
2026-09-28 18:20:27 +02:00
Amruth Pillai a82741f42a feat(web): edit resumes inline in the Write panel
Write becomes the Basics card, the outline of sections in print order and
Add section. Entries open in place (one at a time, the editor selection),
save as you type and show drafts as "Draft · not printed". Dates use a
structured field with a Present switch and the review note for text that
wasn't read exactly; descriptions use a restricted rich-text toolbar shown
on focus. Sections and entries reorder by drag or Alt+Up/Down, rows carry
the eye, count and every section option, and deleting shows Undo.

Clicking the page opens the entry and scrolls it into view; focusing a field
outlines its block. Phones push an open entry full screen. While a field
has focus the preview waits for a pause in typing.

The left section sidebar, the entry dialogs and the hidden-sections list are
removed.
2026-09-28 18:20:26 +02:00
Amruth Pillai bda01febd5 feat(schema): structured resume dates with a read-time upgrade and dual write
Dated entries and roles carry dates (start, end, present, and raw when the
text couldn't be read exactly). The parser moves to the schema package and
reports how each date was written; parseResumeData fills dates, infers the
date format from how dates were typed and rewrites the legacy period/date
text from them, so older clients and API readers keep working. API writes
sync against the stored data, so an edit to the text alone is read back
into dates.

getById and getBySlug return upgraded data, ATS date rules and sorting read
dates, JSON Resume and LinkedIn imports map their dates directly, entry
titles may be empty (drafts aren't printed), and the MCP schema resource is
generated live in place of the stale schema.json.
2026-09-28 18:20:18 +02:00
Amruth Pillai fa19b891db docs: log M2 in the redesign plan 2026-09-28 17:18:12 +02:00
Amruth Pillai 91f1aba2e5 chore(i18n): extract catalogs for the editor shell 2026-09-28 17:18:11 +02:00
Amruth Pillai ffa16f2efa test(e2e): drive the new editor bar, modes and share sheet
openSidebarSection now switches to the mode that hosts a section or opens the
Share & export sheet, and openDownloadDialog opens every format from the
Download PDF split button. Specs follow the back link, the save status line,
the document menu, the zoom bar and page-scale zoom.
2026-09-28 17:18:10 +02:00
Amruth Pillai f2a76b2f69 feat(web): replace the builder with the Desk & Paper editor shell
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.
2026-09-28 17:18:04 +02:00
Amruth Pillai 639abf12b6 feat(web): keep unsaved drafts on the device and deepen undo
Failed saves report Offline or Not saved with a retry, keep the draft in
localStorage per resume, replay it when the connection returns and restore
it when the editor opens again. Undo keeps 200 steps as shared immer trees,
merges typing in one field within a second and keeps structural actions as
steps of their own.
2026-09-28 17:17:56 +02:00
Amruth Pillai f73238ba3b feat(pdf): pass the page map through the browser renderer 2026-09-28 17:17:56 +02:00
Amruth Pillai a887a72d77 feat(ui): add a breakpoint hook, panel icons and CSS-drawn icon glyphs
Icons now draw their ligature from data-icon in a pseudo-element, so icon
names stay out of copied text, find-in-page and text queries. useBreakpoint
reports the design system's mobile, tablet, desktop and wide ranges.
2026-09-28 17:17:54 +02:00
Amruth Pillai 90d7d0a19b docs: document the Desk & Paper design system
Rewrite DESIGN.md for the new tokens, type, icons, motion and
components, add the redesign's terms to GLOSSARY.md, and log M1 in the
redesign plan.
2026-09-28 16:29:36 +02:00