Commit Graph
6137 Commits
Author SHA1 Message Date
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
Amruth Pillai 9e041e140b chore(i18n): extract catalogs for the System theme option
Also picks up LinkedIn import strings that hadn't been extracted yet.
2026-09-28 16:29:36 +02:00
Amruth Pillai 0f934bd849 feat(ui): adopt Desk & Paper tokens, fonts, System theme and restyled primitives
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.
2026-09-28 16:29:30 +02:00
Amruth Pillai 4485825dfe feat(ui): add a self-hosted Material Symbols icon subset
Icon renders Material Symbols Rounded at weight 300 from a subset font
that holds only the glyphs listed in packages/ui/src/icons/names.ts.
pnpm icons:build checks every name against Google's codepoints and
regenerates the font; a test keeps the manifest and the list in sync.
Icons are aria-hidden and untranslated, and directional ones mirror in
right-to-left layouts.
2026-09-28 16:29:17 +02:00
Amruth Pillai def4f72169 feat(pdf): map rendered header, section and item boxes
Templates tag the views that own the header, each section and each item
with data-resume-node. ResumeDocument's new onPageMap callback reads React
PDF's layout tree after each render and returns page-relative boxes, so the
editor can link lines on the page to entries in the panel. The attribute
stays on layout nodes and never reaches the PDF.
2026-09-28 16:01:16 +02:00
Amruth Pillai b0aecdfb5a docs: add Desk & Paper redesign plan
Plan for the UI redesign handoff: repo map, route changes, schema
migrations, component inventory, capability map, milestones and the
answered open questions. The handoff folder stays out of version control.
2026-09-28 16:01:15 +02:00
Amruth Pillai a7f1829484 ci: default to GitHub-hosted runners with opt-in Blacksmith
Workflows now run on GitHub-hosted runners unless the repository
variable USE_BLACKSMITH is "true", so forks work without setup. When
enabled, jobs run on Blacksmith runners (32 vCPU for build/test, 2 vCPU
for lightweight jobs) and use useblacksmith/checkout,
useblacksmith/setup-docker-builder, and useblacksmith/build-push-action.
Docker layer caches are keyed per architecture.

Replaces the CI_RUNNER_X64 and CI_RUNNER_ARM64 variables.
2026-09-28 09:01:00 +02:00
Amruth Pillai 328bf73cee chore(i18n): drop unregistered ckb-IR catalog
Crowdin shipped an empty Central Kurdish (ckb-IR) catalog with no translated
strings. The locale is not registered in the Lingui config or locale schema,
so remove the file and its PDF section title entry.
2026-09-28 08:59:38 +02:00
FalconSpyClaude Opus 5.5Amruth Pillaiautofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1b78e546e2 feat(applications): schedule interviews and view them on a calendar (#3539)
* feat(applications): schedule interviews and view them on a calendar

Interviews (screening, technical, behavioral, onsite, other) are stored as
"interview" entries on an application's activity timeline, so an application
can have any number of them and they show in its timeline without a
migration. Each interview has a start date-time (timezone-aware), duration,
and optional location and notes.

- schema: interview timeline entry type, interviewDetailsSchema, INTERVIEW_KINDS
- api: addInterview / updateInterview procedures (delete via timeline entry);
  generic timeline updates now only edit text on note entries
- web: Calendar view on the Applications page (month grid, type legend,
  upcoming list grouped by day, schedule button with application picker,
  per-day "+" and "+N more" popover), Interviews section and interview
  dialog in the application detail panel, interview rows in the timeline
  and CSV export
- mcp: add_application_interview / update_application_interview tools
- i18n: extract new strings into all locale catalogs (English fallback)
- docs: MCP tool table, scheduling guide section, resume-builder skill

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(applications): keep interview dates in local time and guard generic timeline edits

- Format the timeline date chip for interview entries in the viewer's local
  timezone so it matches the interview's date-time label; stage and note
  entries still render in UTC.
- Reject interview entries in the generic updateTimelineEntry path. A
  day-granular date edit kept the UTC time of day and could move an interview
  to the wrong local day; callers are pointed to updateInterview
  (update_application_interview). The MCP tool description now says so, and
  a service test covers the rejection.
- Associate each interview dialog label with its control via useId/htmlFor.
- Drop the duplicate onInput handler on the date-time input; onChange covers
  controlled inputs.
- Give the calendar's per-day schedule button an accessible name that
  includes the date, and update the extracted locale catalogs for the new
  message.

* [autofix.ci] apply automated fixes

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-09-28 08:57:25 +02:00
Syed Ali Abbas ZaidiandAmruth Pillai fb756026fa feat(import): add LinkedIn data export as a resume import source (#3538)
* 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>
2026-09-28 08:57:20 +02:00
Lihan YANGAmruth Pillaiautofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
73e7a3cb6d fix(dev): make dotenvx available through pnpm (#3537)
* fix(dev): make dotenvx available through pnpm

* fix(dev): load local env from root scripts

* [autofix.ci] apply automated fixes

---------

Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-09-28 08:57:14 +02:00