Vantage logoVantage
FeaturesPricingBlogChangelog
Sign inGet started

Changelog

Latest improvements, features, and fixes.

Jump to an update
Jul 15, 2026Failed requests no longer count toward usage limitsJul 15, 2026Junk job parses (title "." and nothing else) are rejected instead of savedJul 15, 2026Gemini spend hardening - platform-wide AI budget and pro strategy-feedback capJul 15, 2026Tighter pro daily caps to keep worst-case AI spend under the subscription priceJul 15, 2026Example endpoint no longer calls the AI; Resume Studio pro cap 15/day; platform AI budget raised to ~$15Jul 15, 2026Privacy Policy expanded with data-collection and rights detailJul 15, 2026Pro pricing displayed as CAD instead of USDJul 14, 2026Resume Studio in the sidebar, tighter dev limit, louder feedback promptJul 14, 2026Feedback prompt resurfaces weekly; sidebar footer pinned to the bottomJul 14, 2026Tailor + ATS full-resume mode, tailoring-focused Resume Studio, hyperlink-preserving PDFs, rate-limit hardeningJul 14, 2026Tailor + ATS results tabs no longer send you backwards to run the next actionJul 14, 2026Help chat knowledge base caught up with today's featuresJul 13, 2026Rewrite generated outreach messages with a custom instructionJul 13, 2026Google sign-in, help chat, product emails, data reset, Resume Studio, and a full audit passJul 13, 2026Legal pages rewritten to match the current product, plus a Data Compliance pageJul 12, 2026Auto-apply question answering, account-switch stale data, ATS average skew, trial-status webhookJul 12, 2026Job pickers source from the application tracker (networking, ATS)Jul 12, 2026Substantive resume tailoring, personalized strategy feedback, identity-first outreach, strict contact role filterJul 12, 2026Full tailored resume hidden everywhere - users see only the per-bullet diffJul 12, 2026Manual outreach logging, full application editing, tailor from the trackerJul 12, 2026Job finder - saved named filters, km-radius search, salary/date filters wired to AdzunaJul 12, 20267-day free trial and 7-day self-serve refund on ProJul 12, 2026Mobile and tablet responsiveness pass across the appJul 12, 2026Second-round fixes - tracker-only listings, mobile sidebar, AI capacity errors, job feed persistenceJul 12, 2026Third-round fixes - scored_at bug, desktop-only gate, working salary/date filters, usage limits pageJul 12, 2026Mobile notice leak, filter cleanup, honest Adzuna label, connection requests rewrittenJul 11, 2026Daily tier-aware rate limiting across every AI and external endpoint; Groq references retiredJul 11, 2026Three-tier rate limiting (dev/free-monthly/pro-daily), free-tier 150 application capJul 11, 2026Admin account bypasses all per-user rate limits for testingJul 10, 2026Dark-glass visual overhaul - gold removed from buttons/cards/badges/inputs, kept only as a restrained accentJul 10, 2026Landing hero rebuilt as an overlay composition - metallic headline in front of a dimmed workspace with a studio-light beamJul 10, 2026Hero mockup unified into one app screen with the headline bottom-leftJul 10, 2026Hero lighting reworked to match the reference - diagonal shaft from the header, dark cornersJul 10, 2026Landing cards get a metallic-glass treatment - light diffuses inward from the borderJul 10, 2026Bespoke feature artifacts and a premium closing CTAJul 10, 2026Landing page containers widened to the hero mockup's lineJul 10, 2026Hero lighting tuned, metallic gold CTA button, Pro card elevatedJul 10, 2026Landing polish - features header animation, macOS window dots, real autofill data, level pricing cards, CTA detailsJul 10, 2026Closing CTA becomes live metal - cursor-tracked lightingJul 10, 2026Cursor light on all cards and mockups; closing CTA composition upgradedJul 10, 2026Metallic-glass rollout across the dashboard, resources, company, and legal pagesJul 10, 2026Light-mode hero slash goes gold; Pro pricing card differentiated in light modeJul 10, 2026Dark mode is the default; Pro card becomes a dark island in light mode; docs/blog cursor light removedJul 10, 2026Feedback page, changelog timeline, custom scrollbars, external-link icon, wider reading pages, gilded Pro cardJul 10, 2026Contact merged into feedback, signup agreements, changelog jump list, Pro card gold line, footer linksJul 10, 2026Glass sidebar highlight, connection-code copy cleanup, tailor toggle arrowJul 10, 2026Sidebar highlight keeps its gold bar and tint with a glass fill; extension toast uses the real logoJul 10, 2026Extension popup restyled to the dark-glass system; SEO hardeningJul 10, 2026Google Search Console verification tag wired to an env varJul 8, 2026Plain-language pass across all user-facing copyJul 8, 2026Blog post - The New Vantage design overhaulJul 8, 2026Subtle gold particle background on the landing pageJul 8, 2026Unified page letterhead across every dashboard pageJul 8, 2026One answer-method control per interview session instead of per cardJul 8, 2026Auto-apply page now leads with Chrome extension setupJul 8, 2026Documentation accuracy pass across all docs pagesJul 8, 2026Stop showing raw errors to users; log them to console.error insteadJul 8, 2026Logo now legible in light modeJul 8, 2026Light mode is now the default themeJul 7, 2026Design system v2 - Outfit/Manrope fonts, gold accent, elevation tokensJul 7, 2026Sidebar nav hover stateJul 7, 2026Landing page redesign - gold signature treatment, Outfit/Manrope type systemJul 7, 2026Landing page density + gold visibility passJul 7, 2026Landing hero eyebrow re-specced, pain-point cards raisedJul 7, 2026Dashboard design overhaul - Outfit/Manrope type, gold signature, new Auto-apply pageJul 7, 2026Landing page depth pass - feature hierarchy, section headlines, quote cards, gold glowJul 7, 2026Closing CTA section as a hero bookendJul 7, 2026Closing CTA rebuilt as a glowing band with a before/after diff artifactJul 7, 2026Consistent skeleton loading states across all dashboard pagesJul 7, 2026Dashboard consistency pass - custom selects, gold primary buttons, unified badges/cards/labelsJul 7, 2026Job pickers everywhere now source exclusively from the application trackerJul 7, 2026Full audit - everything that consumes jobs sources from the application trackerJul 7, 2026CustomSelect component replaces every native dashboard dropdownJul 7, 2026Dashboard polish pass - input focus glow, zebra rows, rich empty states, danger zone, interview mid-sectionJul 7, 2026Full SEO pass - metadata, sitemap, robots, structured dataJul 6, 2026Chrome extension - version marker for stale-build detectionJul 6, 2026Chrome extension - prompt option clicks now register, closest-match selection, websites slot discoveryJul 6, 2026Chrome extension - full-name skill queries, single-click prompt selection, degree listbox supportJul 6, 2026Tailoring results as a per-bullet diff view; PDF flow removedJul 6, 2026Tailoring - active JD keyword weaving, per-entry grouping in the diff view, tighter cardsJul 6, 2026Reverted shadcn init - restored original design systemJul 4, 2026Chrome extension - Workday multi-entry auto-add fixed; fill logic now verified by a DOM harnessJul 4, 2026Chrome extension - Workday Add-button pointer clicks, restricted skills early abort, LinkedIn/Websites dedupeJul 4, 2026Chrome extension - Workday label-based field discovery for tenants without automation-ids, section DOM dumpsJul 4, 2026Chrome extension - Workday spinner dates via keydown, multiselect school prompts, partial skill queries, websites fill-onlyJul 4, 2026Codebase-wide - replaced em/en/long dashes with regular hyphensJul 4, 2026Interview prep - tracker job dropdown was always emptyJul 4, 2026Chrome extension - Workday spinner overwrite, multiselect activation, websites manual-add noticeJul 4, 2026Chrome extension - Workday dates via ARIA arrow-stepping, prompt search committed with EnterJul 2, 2026Chrome extension - kit unwrap bug caused 0-field fills (Workday and all platforms)Jul 2, 2026Chrome extension - Workday stale-element fills, skills tag selection, website slot mapping, full fill diagnosticsJul 1, 2026Chrome extension auto-fill overhauled - two-tier fill, zero hallucination policyJun 9, 2026Cerebras model updated to gpt-oss-120bMay 25, 2026PDF printBackground, spacing, and tailor tech stack preservationMay 25, 2026Profile form - generic placeholders and month picker date inputsMay 25, 2026Networking - inline delete confirmation, remove AI-generated contact fallbackMay 25, 2026Networking - Apollo People Search replaces Jina/AI contact scrapingMay 22, 2026parse-profile extracts work experience and projects from resumeMay 22, 2026Experience section on profile page - editable cards with inline saveMay 22, 2026Projects section on profile page - editable cards with tag-based tech stackMay 22, 2026Experience and projects injected into all AI promptsMay 22, 2026PDF generation - no browser headers, HTML-preserved formatting, 1-page layoutMay 22, 2026Cover letter - proper letter format + personal template supportMay 22, 2026Puppeteer PDF generation - pixel-perfect server-side resume PDF via ChromiumMay 22, 2026Surgical tailor prompt + professional PDF CSS templateMay 22, 2026Tailor metrics preservation + PDF bullet rendering + hyperlink retryMay 22, 2026PDF spacing, bullet disc, metrics preservation, keyword stuffing, project link injectionMay 18, 2026AI-powered form fill via CerebrasMay 18, 2026.docx resume upload support + graduation year range fixMay 18, 2026Extension - phone dropdown filter, proximity panel detection, and open-ended question fillMay 17, 2026Strategy report - data-driven job search insightsMay 17, 2026Job feed "Tailor resume" pre-fill + secondary Groq key for job scoringMay 17, 2026Stripe billing - checkout, webhook, customer portal, and billing pageMay 17, 2026Networking - contact discovery, outreach message generation, and trackerMay 17, 2026Networking contact search - URL encoding, logging, and AI fallbackMay 17, 2026Cerebras AI for interview prep + full UI restyleMay 17, 2026Chrome Extension for job application auto-fillMay 17, 2026Extension - custom div-based dropdown handler for Greenhouse and LeverMay 17, 2026Extension - fix dropdown bleeding and Lever location selectMay 16, 2026Auto pre-fill profile from uploaded resumeMay 16, 2026Resume upload embedded in profile form with onboarding bannerMay 16, 2026Application detail modal with ATS scores and tailored documentsMay 16, 2026Status dropdown renders as portal to fix overflow clipping in tracker tableMay 16, 2026Settings panel with account, appearance, resume, and danger zone sectionsMay 16, 2026Documents page with filter tabs, expandable rows, copy and downloadMay 16, 2026Answer application form questions with AIMay 16, 2026Apply Prep page — application kit, question answerer, submit checklistMay 16, 2026Job Feed — Adzuna-powered personalized job discovery with AI relevance scoringMay 15, 2026App landing page at / with full marketing sectionsMay 15, 2026Shared marketing layout with nav, footer, and dark/light mode toggleMay 15, 2026Launch 1 - ATS-first job search workspaceMay 15, 2026Blog pages with MDX rendering, search, and category filteringMay 15, 2026Admin analytics dashboardMay 15, 2026Contact page with public form and rate-limited API route
Jul 15, 2026
Fix

Failed requests no longer count toward usage limits

  • •lib/rateLimit.ts: checkLimit() and checkRateLimit() are now read-only checks; new consumeLimit() and recordRateLimitUse() charge the monthly quota and per-route rate limit only when a route reaches its success path - AI errors, timeouts, upstream failures, and database errors no longer burn a use
  • •every rate-limited route updated to charge on success only: tailor-resume, ats-score, generate-cover-letter, parse-job, answer-question, analyze-form, extension/ai-fill, generate-fill-snippet, interview-prep generate + assess, generate-outreach, find-contacts, discover-jobs, strategy-feedback, parse-resume, parse-profile, app-chat, resume-studio + pdf, tailor-resume/render, and the example template route
  • •parse-job, analyze-form, extension/ai-fill, resume-studio, and resume-studio/pdf now validate the request body before the limit checks, so a bad request costs nothing (previously an invalid Resume Studio body still burned an edit)
  • •app/api/find-contacts/route.ts: a SerpApi failure now returns an error instead of a misleading "no contacts found" success (and is not charged)
  • •app/api/extension/ai-fill/route.ts: the degraded partial result returned when the AI fails (deterministic answers only) is no longer charged
  • •lib/appKnowledge.ts: Usage Limits section notes that only successful runs count
  • •no new DB columns, no new env vars, no new npm packages
Jul 15, 2026
Fix

Junk job parses (title "." and nothing else) are rejected instead of saved

  • •app/api/parse-job/route.ts: after the AI parse, the job must have a real title plus at least one of required skills, responsibilities, or keywords - empty-shell parses (usually login walls or scraper-block pages behind the URL) now return a clear 422 pointing at pasting the description instead, and are not charged against any limit
  • •lib/jobFilters.ts: hasRealJobTitle() now also rejects punctuation-only strings (".", "---") and common AI placeholder values ("Unknown", "N/A", "Not specified") - this also cleans those out of strategy-feedback role grouping and the auto-apply job listing
  • •no new DB columns, no new env vars, no new npm packages
Jul 15, 2026
Improvement

Gemini spend hardening - platform-wide AI budget and pro strategy-feedback cap

  • •lib/ai.ts: callGemini() now enforces a platform-wide budget of 3000 Gemini calls per 30 days (platform_limits key gemini_monthly) - an absolute spend ceiling (~$10/month worst case) that holds no matter how many accounts sign up; when exhausted, AI features return the friendly "over capacity" 429 until the window resets, and the counter only increments on calls Google actually served
  • •app/api/strategy-feedback/route.ts: pro users previously skipped the per-route rate limit entirely, and ?force=true bypasses the result cache - an unbounded AI path; pro now has a hidden 10/day abuse cap (dev 2/day and free 2/month unchanged; the /limits page still shows pro as unlimited)
  • •full audit confirmed the Gemini SDK is imported only in lib/ai.ts and all 18 routes that call it check a rate limit before the AI call
  • •no new DB columns, no new env vars, no new npm packages
Jul 15, 2026
Update

Tighter pro daily caps to keep worst-case AI spend under the subscription price

  • •pro per-day limits lowered: Resume Studio edits 60 to 30, job parsing 12 to 10, application question answers 20 to 10, interview answer feedback 15 to 10, strategy feedback hidden abuse cap 10 to 5
  • •app/api/resume-studio, parse-job, answer-question, interview-prep/assess, strategy-feedback route configs and RATE_LIMIT_SPECS in lib/rateLimit.ts updated together
  • •worst-case fully-maxed pro user drops from roughly $12/month to roughly $8/month in Gemini costs
  • •no new DB columns, no new env vars, no new npm packages
Jul 15, 2026
Update

Example endpoint no longer calls the AI; Resume Studio pro cap 15/day; platform AI budget raised to ~$15

  • •app/api/example/route.ts: the live reference template now echoes the prompt instead of calling Gemini - nothing in the app calls it, so it should not be able to spend tokens; a comment marks where the AI call goes when copying the pattern
  • •app/api/resume-studio/route.ts + lib/rateLimit.ts RATE_LIMIT_SPECS: pro Resume Studio edits lowered from 30/day to 15/day
  • •lib/ai.ts: PLATFORM_MONTHLY_CALL_BUDGET raised from 3000 to 4500 calls per 30 days (~$15/month absolute worst-case Gemini spend)
  • •no new DB columns, no new env vars, no new npm packages
Jul 15, 2026
Update

Privacy Policy expanded with data-collection and rights detail

  • •app/(marketing)/privacy/page.tsx: added a "What we don't collect" section (no sensitive personal information, no data about you sourced from third parties), a "Business transfers" section, and a Do-Not-Track note under Cookies
  • •Usage data entry now explicitly names IP address and browser/device type among the technical logs collected
  • •Google (sign-in) entry now notes compliance with Google's API Services User Data Policy, including Limited Use
  • •Your rights section gained an opt-out-of-sale/targeted-advertising bullet and a note that non-Canadian users' local rights will be honored on request
  • •docs/legal-dossier.md updated to mirror the same facts
  • •no new DB columns, no new env vars, no new npm packages
Jul 15, 2026
Update

Pro pricing displayed as CAD instead of USD

  • •app/(marketing)/page.tsx: pricing card now reads "$8 CAD/month"; structured-data offer priceCurrency changed from USD to CAD
  • •app/(marketing)/terms/page.tsx, app/(marketing)/docs/billing/page.tsx, app/(dashboard)/billing/page.tsx, lib/appKnowledge.ts: "$8/month" copy now says CAD instead of (or in addition to) USD
  • •app/admin/page.tsx: formatMoney() now formats MRR as CAD (en-CA/CAD) instead of USD - display-only, does not change what Stripe actually charges
  • •CLAUDE.md and docs/legal-dossier.md updated to match
  • •owner action still required: the Stripe Price behind STRIPE_PRO_PRICE_ID must itself be a CAD price for checkout to actually charge CAD - currency lives on the Stripe Price object, not in this codebase, and Stripe prices can't be edited in place, so a new CAD price must be created and the env var repointed to its id (in .env.local and Vercel)
  • •no new DB columns, no new npm packages
Jul 14, 2026
Update

Resume Studio in the sidebar, tighter dev limit, louder feedback prompt

  • •components/Sidebar.tsx: Resume Studio now appears in the sidebar with a gold "Experimental" tag; the isNew flag was generalized to a badge string so Auto-apply's "New" and this share one style
  • •app/api/resume-studio/route.ts + lib/rateLimit.ts: dev-tier limit lowered from 30 to 10 edits/day (free 20/month and pro 60/day unchanged)
  • •components/FeedbackNudge.tsx + app/dashboard/page.tsx: the feedback prompt moved from the bottom of the dashboard to directly under the page header, with a bolder "Found a bug? Missing a feature?" headline and a filled Share feedback button (still one-time dismissible)
  • •app/(dashboard)/resume-studio/page.tsx: stale "unlisted" comment updated
  • •no new DB columns, no new env vars, no new npm packages
Jul 14, 2026
Fix

Feedback prompt resurfaces weekly; sidebar footer pinned to the bottom

  • •components/FeedbackNudge.tsx: dismissing now stores a timestamp instead of a permanent flag - the prompt comes back after 7 days (users who dismissed under the old permanent flag see it again once, then the weekly cycle applies)
  • •components/Sidebar.tsx: on short viewports the nav list overflowed and pushed the footer's link rows past the screen edge, leaving a stranded divider line; the nav now scrolls within the rail (flex minHeight 0 + overflow-y auto), the footer never shrinks, and trailing padding was trimmed so the link rows sit flush at the bottom
  • •no new DB columns, no new env vars, no new npm packages
Jul 14, 2026
Feature

Tailor + ATS full-resume mode, tailoring-focused Resume Studio, hyperlink-preserving PDFs, rate-limit hardening

  • •lib/extractResumeText.ts: new extractResumeLinks() pulls hyperlink destinations out of uploaded resumes (PDF link annotations via pdf.js, DOCX via mammoth HTML) - plain-text extraction was silently dropping every link
  • •lib/resumeHtml.ts created: shared resume-HTML helpers - prompt rules, sanitizer, linkifyResumeHtml() (deterministically re-attaches known links and anchors bare URLs/emails), and applyChangesToHtml() (swaps tailored bullets into profiles.resume_html without AI)
  • •app/api/resume-studio/route.ts: upload extraction now returns the file's links; the generate prompt re-attaches them and every generate/edit response passes through the linkify safety net, so downloaded PDFs keep the hyperlinks the original resume had
  • •app/api/tailor-resume/render/route.ts created: renders a tailored_resume document as full resume HTML - surgical bullet-swap into the stored base-resume HTML when possible (fast, free, link-preserving), one rate-limited AI formatting call as fallback
  • •app/(dashboard)/tailor/page.tsx: the Tailored Resume tab now has two views - "Bullet changes" (the existing diff/copy flow) and "Full resume" (rendered preview + Download PDF named after the company/role)
  • •components/ResumeStudio.tsx + app/(dashboard)/resume-studio/page.tsx: the studio is now tailoring-focused - start from a resume tailored on Tailor + ATS (with skill-gap/keyword suggestion chips that prefill edit instructions) or your base resume, alongside the existing file upload; tailoring itself stays on Tailor + ATS
  • •Rate-limit audit across every AI/external API route (all 17 Gemini routes verified limited, limiter always before the AI call):
    • •app/api/extension/ai-fill/route.ts, app/api/analyze-form/route.ts, app/api/generate-fill-snippet/route.ts: all three auto-apply tiers now charge the monthly auto_apply feature quota (previously only per-route sliding windows, and tier 2 had no limits at all)
    • •app/api/discover-jobs/route.ts: platform-wide Adzuna budget (900 calls/30 days via platform_limits) so multi-user refresh traffic cannot blow the 1000/month free tier
    • •app/api/resume-studio/pdf/route.ts: per-user rate limit on PDF rendering (headless Chromium is expensive even without AI)
    • •lib/rateLimit.ts: RATE_LIMIT_SPECS gains render-resume (5/day dev, 10/mo free, 10/day pro) and resume-pdf (20/40/60) so the /limits page stays accurate
  • •no new DB columns, no new env vars, no new npm packages
Jul 14, 2026
Fix

Tailor + ATS results tabs no longer send you backwards to run the next action

  • •app/(dashboard)/tailor/page.tsx: the ATS Score tab's empty state now runs "Check ATS Score" in place (the tailored resume and cover letter stay put) instead of "Go back to check score", which stranded users on step 2 with no way to return to their results; the Tailored Resume tab's empty state got the same in-place "Tailor My Resume" treatment, and both show action errors inline
  • •app/(dashboard)/tailor/page.tsx: step 2 now shows a "You already have results for this job - View results" banner whenever a tailored resume, ATS score, or cover letter exists, so stepping back is never a one-way door
  • •no new DB columns, no new env vars, no new npm packages
Jul 14, 2026
Update

Help chat knowledge base caught up with today's features

  • •lib/appKnowledge.ts: the chat widget now knows about the Tailor + ATS "Bullet changes"/"Full resume" views with PDF download, in-place tab actions, Resume Studio's tailoring-focused start sources and suggestion chips, hyperlink preservation in generated PDFs, auto-apply credits applying to all three fill tiers, and the per-feature rate limits shown on Usage Limits
  • •no new DB columns, no new env vars, no new npm packages
Jul 13, 2026
Feature

Rewrite generated outreach messages with a custom instruction

  • •app/(dashboard)/networking/page.tsx: after a message is generated, a new "Want changes? Describe them" input lets you type an instruction (e.g. "make it shorter", "mention my co-op") and rewrite the message in place; Enter or the Rewrite button submits, capped at 300 characters
  • •app/api/generate-outreach/route.ts: refine mode (refineMessageId + refineInstruction) was already in place - rewrites the saved message per the instruction and counts toward the same networking limits and rate limits as generating
  • •no new DB columns, no new env vars, no new npm packages
Jul 13, 2026
Major Release

Google sign-in, help chat, product emails, data reset, Resume Studio, and a full audit pass

  • •app/auth/callback/route.ts created + login/signup pages: "Continue with Google" via Supabase OAuth; first-time Google users get their name backfilled and land on profile onboarding (requires Google provider setup in Supabase + Google Cloud)
  • •components/HelpChatWidget.tsx + app/api/app-chat/route.ts + lib/appKnowledge.ts: floating help chat on every dashboard page that answers questions about how Vantage works from a built-in knowledge base; it refuses to generate documents and points to the right feature page instead (rate limited: dev 5/day, free 50/month, pro 10/day)
  • •lib/ai.ts: new generateChat() for multi-turn conversations; retry logic factored into one shared Gemini call path
  • •lib/email.ts + app/api/admin/send-campaign/route.ts + components/AdminCampaignForm.tsx: product-update emails via Resend, composed and sent from /admin with a mandatory "send test to me" flow; every email carries one-click unsubscribe headers and a footer link
  • •app/api/unsubscribe/route.ts + app/(marketing)/unsubscribed/page.tsx: tokenized one-click unsubscribe, no login needed; components/SettingsClient.tsx gains a "Product updates" toggle
  • •app/api/account/reset/route.ts + Settings danger zone: "Reset my data" wipes all app data and uploaded files but keeps the account and subscription (type RESET to confirm); sql/one_time_wipe_2026-07.sql added for the owner-run global fresh start
  • •app/api/admin/reset-analytics/route.ts + components/AdminResetAnalytics.tsx: admin Maintenance section clears events and route logs (type RESET ANALYTICS)
  • •components/FeedbackNudge.tsx on the dashboard home + a "Send feedback" link in the help chat header: the /feedback page is now discoverable in-app (one-time dismissible)
  • •app/(dashboard)/resume-studio/ (unlisted URL /resume-studio) + app/api/resume-studio/: experimental "edit your resume by chatting" - upload a PDF/Word file, give instructions like "cut the summary to two lines", watch the live preview update, download the PDF; nothing is stored server-side (lib/extractResumeText.ts + lib/resumeCss.ts refactored out for reuse)
  • •Security pass: print-PDF iframe now strips executable content; extension token and resume-parse debug logging removed; Stripe webhook/checkout/parse-job logs no longer dump payloads; Resume Studio preview renders in a scriptless sandboxed iframe; audit confirmed webhook signature checks, storage policies, admin guards, and per-user scoping across all routes
  • •Scalability pass: buildUserContext now fetches its six sources in parallel; rate-limit service client is reused across calls; subscriptions query selects explicit columns; admin dashboard caps events/route_logs reads at the newest 50k rows; new indexes on rate_limit_logs, events, route_logs, applications
  • •Comment pass across lib/, components/, and every API route: file headers + JSDoc on exports; CLAUDE.md corrected (AI provider is Gemini via lib/ai.ts, not Groq)
  • •docs/legal-dossier.md created: full data inventory, processor list, and the owner questions needed to generate ToS/privacy/compliance docs with a legal AI tool
  • •new DB columns: profiles.marketing_emails_enabled, profiles.unsubscribe_token + 4 indexes (supabase/migrations/20260713_batch3.sql - run in SQL editor)
  • •new env vars: RESEND_API_KEY, RESEND_FROM_EMAIL
  • •packages installed: resend
Jul 13, 2026
Update

Legal pages rewritten to match the current product, plus a Data Compliance page

  • •app/(marketing)/terms/page.tsx: updated for Google sign-in consent, the 7-day self-serve refund window (the old "no refunds" line contradicted the billing page), trials, opt-in email rules, an experimental-features clause covering Resume Studio, and a changes-to-terms section
  • •app/(marketing)/privacy/page.tsx: removed the stale Cerebras section (all AI runs on Gemini), added Google sign-in, Resend, Adzuna, SerpApi, and the feedback webhook to the third-party list, and documented networking contact data, voice input for interview practice, help chat handling, cookies/browser storage, Word resume uploads, and the new Reset my data control
  • •app/(marketing)/data-compliance/page.tsx created: plain-language PIPEDA posture page with safeguards, the full subprocessor list, AI processing commitments, user controls, and breach notification; linked from the marketing footer Legal column and cross-linked from terms + privacy
  • •docs/legal-dossier.md: rewritten as a single self-contained prompt to paste into a legal chatbot (instructions + facts + [FILL IN] markers), prefilled with the facts already stated on the site (Ontario law, age 16+, PIPEDA, contact email)
  • •no new DB columns, no new env vars, no new npm packages
Jul 12, 2026
Fix

Auto-apply question answering, account-switch stale data, ATS average skew, trial-status webhook

  • •app/(dashboard)/apply/[jobId]/page.tsx + app/api/answer-question/route.ts: question generation no longer sends the route param (an application id) as a job id; the API resolves the application first and treats the jobs lookup as optional, so manual applications (no jobs row) can generate answers instead of erroring "Job not found"
  • •app/(auth)/login/page.tsx + components/Sidebar.tsx: router.refresh() on sign-in and sign-out so the Router Cache never serves the previous account's name/email/data; sign-out also clears per-user localStorage caches
  • •app/(dashboard)/jobs/page.tsx: job feed localStorage cache is now keyed by user id (jobFeedCache:<userId>); the legacy shared key is dropped on load
  • •lib/userContext.ts: average ATS scores now ignore rows with null scores instead of counting them as 0 (this was dragging the average shown in strategy feedback)
  • •app/api/stripe/webhook/route.ts: customer.subscription.updated treats status 'trialing' as plan 'pro'; both handlers store trial_end
Jul 12, 2026
Fix

Job pickers source from the application tracker (networking, ATS)

  • •app/(dashboard)/networking/page.tsx + app/(dashboard)/ats/page.tsx: job dropdowns now list tracked applications (applications table first, enriched from jobs), matching the interview page pattern - manual tracker entries appear instead of only tailored jobs
  • •ATS page: manual applications without job details show "(needs job details)" and prompt to add the posting from the tracker before scoring
  • •networking generate-outreach accepts jobTitle/jobCompany as plain-text context for manual applications (app/api/generate-outreach/route.ts)
Jul 12, 2026
Improvement

Substantive resume tailoring, personalized strategy feedback, identity-first outreach, strict contact role filter

  • •app/api/tailor-resume/route.ts: rewritten prompt bans pure-conciseness and verb-swap-only edits, requires each change to add job terminology / mirror a responsibility / re-frame toward the job's domain, and a server-side filter drops trivial edits the model still returns; per-bullet changes are persisted to documents.changes (new jsonb column)
  • •app/api/strategy-feedback/route.ts: output now addresses the user as "you" instead of "the candidate"
  • •app/api/generate-outreach/route.ts: messages lead with the sender's identity (university/program), cite concrete current work and a flagship project with real technologies, and end with a direct ask; company-flattery openers are banned
  • •app/api/find-contacts/route.ts: an optional target role is now a hard filter (query built around the role + title/snippet post-filter); zero matches return a suggestion to remove the role or try a different one, surfaced on the networking page
  • •new DB column: documents.changes jsonb (supabase/migrations/20260712_batch_fixes.sql)
Jul 12, 2026
Feature

Full tailored resume hidden everywhere - users see only the per-bullet diff

  • •components/TailorDiff.tsx created: shared per-bullet diff view + formatTailoredBullets() (grouped tailored-bullets copy text)
  • •app/(dashboard)/tailor/page.tsx: "Copy all tailored bullets" now copies only the tailored bullets; the full-resume fallback <pre> is gone
  • •app/(dashboard)/apply/[jobId]/page.tsx: kit card shows the change count + copy-bullets button instead of a raw resume preview
  • •components/ApplicationDetailModal.tsx + app/api/applications/[id]/details/route.ts: the API strips tailored-resume content server-side and returns changes; the modal renders the diff (cover letters unchanged, PDF removed for tailored resumes)
  • •app/(dashboard)/documents/page.tsx + components/DocumentsClient.tsx: tailored resumes render the diff and copy bullets only; content is nulled before reaching the client
  • •documents.content is still saved for internal use (ATS scoring, extension auto-fill) but never rendered
Jul 12, 2026
Feature

Manual outreach logging, full application editing, tailor from the tracker

  • •app/api/outreach-messages/route.ts created: POST logs a user-written message (is_manual=true, new DB column) - no AI, no usage limits
  • •app/(dashboard)/networking/page.tsx: "Log a message" panel in the outreach tracker for messages sent outside Vantage
  • •app/api/applications/[id]/route.ts: PATCH now accepts company, role, job_url, and job_id (job ownership verified)
  • •components/ApplicationDetailModal.tsx: edit mode for company/role/URL/date/status/notes, plus a Tailor resume action - job-linked applications jump to /tailor?jobId=..., manual ones paste the posting once (parse-job), get linked, then tailor; tracker rows sync via a new onUpdated callback
  • •app/(dashboard)/tracker/page.tsx: passes onUpdated to keep the table in sync with modal edits
Jul 12, 2026
Feature

Job finder - saved named filters, km-radius search, salary/date filters wired to Adzuna

  • •app/api/discover-jobs/route.ts: refresh now forwards where + distance (km), salary_min, max_days_old, and employment-type flags to Adzuna (salaryMin/datePosted were previously collected but unused)
  • •app/(dashboard)/jobs/page.tsx: radius row (city/postal input, 10/25/50/100 km select, "Use my location" via browser geolocation reverse-geocoded to a city) and a presets row (save current filters under a name, apply, delete)
  • •app/api/job-filter-presets/route.ts + [id]/route.ts created: GET/POST/DELETE, capped at 20 presets per user
  • •new DB table: job_filter_presets (RLS per-user) in supabase/migrations/20260712_batch_fixes.sql
Jul 12, 2026
Feature

7-day free trial and 7-day self-serve refund on Pro

  • •app/api/stripe/checkout/route.ts: subscription checkout starts with trial_period_days 7 (card upfront, billing after the trial, cancellable any time)
  • •app/api/stripe/refund/route.ts created: refunds the latest paid invoice if it is within 7 days, cancels the subscription, downgrades to free; clear error with the cutoff date otherwise
  • •app/(dashboard)/billing/page.tsx + components/BillingActions.tsx: trial badge with "Trial ends <date>", money-back note, and a confirm-step Request refund button
  • •app/(marketing)/page.tsx pricing: Pro card CTA is "Start 7-day free trial" with trial + money-back copy
  • •new DB column: subscriptions.trial_end (supabase/migrations/20260712_batch_fixes.sql)
Jul 12, 2026
Improvement

Mobile and tablet responsiveness pass across the app

  • •app/globals.css: new .rsp-grid-2/.rsp-grid-3 helpers (fixed multi-column grids collapse to one column under 640px) and .tracker-table-row (drops the hidden "Since" track on phones instead of reserving empty space)
  • •converted fixed-column grids to the helpers: ProfileForm (profile fields, experience, projects), networking (search/generate forms + skeletons), interview (manual job form, strengths/improvements), apply page kit + skeleton
  • •app/(dashboard)/jobs/page.tsx: filter bar controls use flexible widths (flex + max-width) instead of hardcoded pixel widths
  • •no visual redesign - layout only responds below existing breakpoints; tables keep their overflow-x scroll wrappers
Jul 12, 2026
Fix

Second-round fixes - tracker-only listings, mobile sidebar, AI capacity errors, job feed persistence

  • •app/(dashboard)/ats/page.tsx: score history only shows jobs that are tracked applications (no more "Untitled job" rows from tailoring runs that were never logged)
  • •app/(dashboard)/documents/page.tsx: the documents list is likewise filtered to tracked applications
  • •components/Sidebar.tsx: the desktop sidebar is now actually hidden under 768px (it previously stayed fixed over the page, breaking every mobile view); the drawer + topbar take over as designed
  • •app/(dashboard)/networking/page.tsx: manual-log inputs use the page background + border so they are visually separated from the raised panel
  • •app/(dashboard)/jobs/page.tsx: filter bar redesigned into labelled controls with the saved-filters group on the right; distance/radius search removed entirely per feedback (app/api/discover-jobs no longer forwards where/distance)
  • •app/api/discover-jobs/route.ts: job relevance scoring is ONE batched Gemini call instead of one call per job (a single refresh used to burn the free tier's whole daily quota and break every other AI feature); scoring failures degrade to a neutral score instead of failing the feed
  • •app/api/discover-jobs/route.ts: each job's link is resolved server-side through Adzuna's redirect (following the details page's apply link when needed) so "View job" opens the employer's actual posting
  • •app/(dashboard)/jobs/page.tsx: on load the feed restores from the database when the per-user cache is empty, so signing out and back in no longer empties the page
  • •app/(dashboard)/apply/[jobId]/page.tsx: back links go to /apply (not the tracker); the saved per-bullet diff is viewable inline via "View changes" without re-running tailoring
  • •lib/ai.ts: Gemini 503 "high demand" responses are retried like 429s; new isAiQuotaError()/AI_BUSY_MESSAGE map capacity failures to a friendly 429 in tailor-resume, ats-score, generate-cover-letter, generate-outreach, answer-question, interview-prep generate/assess, and strategy-feedback (previously these surfaced as generic 500s)
  • •app/api/generate-outreach/route.ts: identity line in the prompt is a placeholder pattern so blank profiles can never echo the example school
  • •no new DB columns, no new env vars, no new npm packages
Jul 12, 2026
Fix

Third-round fixes - scored_at bug, desktop-only gate, working salary/date filters, usage limits page

  • •app/(dashboard)/apply/[jobId]/page.tsx + app/api/applications/[id]/details/route.ts: ATS score lookups ordered by created_at, a column ats_scores does not have (it has scored_at) - the whole query errored silently, which is why a freshly generated ATS score never appeared in auto-apply or the tracker modal
  • •app/dashboard/page.tsx: the Avg ATS Score stat now only counts scores belonging to tracked applications (same rule as /ats)
  • •app/(dashboard)/jobs/page.tsx + app/api/discover-jobs/route.ts: Min salary and Posted filters now actually filter - Adzuna's salary bounds and posted date are stored in raw_data on refresh and applied client-side (jobs fetched before the change are kept until a refresh backfills them)
  • •app/api/discover-jobs/route.ts: removed the job-URL resolver - Adzuna serves 403 to server-side requests and its API deliberately omits the employer URL, so redirect_url (which forwards real browsers to the source) is the only supported link
  • •components/Sidebar.tsx: reverted the sidebar to inline flex layout, fixing the footer floating mid-rail
  • •app/(dashboard)/layout.tsx + app/dashboard/layout.tsx + globals.css: the dashboard is now desktop-only - under 768px the shell is hidden and a "Vantage is built for desktop" notice renders instead
  • •app/api/generate-outreach/route.ts: prompt rebuilt around a style exemplar (flowing prose, purpose-driven work descriptions, one bold ask); connection requests compress to a single work clause to stay under LinkedIn's 280 characters
  • •app/(dashboard)/networking/page.tsx: autoComplete="off" on all inputs so browsers stop offering to save contact fields as an address/ID card
  • •lib/rateLimit.ts: resolvePlan treats status 'trialing' as pro (consistent with the webhook); new RATE_LIMIT_SPECS + getRateLimitOverview() compute per-feature usage from rate_limit_logs
  • •app/(dashboard)/limits/page.tsx created + "Usage Limits" sidebar item: shows used/limit and when each slot frees up for every rate-limited feature
  • •no new DB columns, no new env vars, no new npm packages
Jul 12, 2026
Fix

Mobile notice leak, filter cleanup, honest Adzuna label, connection requests rewritten

  • •app/(dashboard)/layout.tsx + app/dashboard/layout.tsx + globals.css: the desktop-only notice is now hidden inline (style="display:none") and only forced visible by the phone media query with !important - it can never render on desktop again, even with a stale stylesheet
  • •app/(dashboard)/jobs/page.tsx: Min salary and Posted filters removed entirely (they depended on data most stored jobs did not have); the filter bar is Location + Job type + saved presets; applying an old preset ignores the removed fields
  • •app/(dashboard)/jobs/page.tsx: job card link relabelled "View on Adzuna" - Adzuna does not expose the employer URL in its API and blocks server-side resolution, so the Adzuna page is the supported destination
  • •app/api/generate-outreach/route.ts: connection requests are now genuine "asking to connect" messages - identity, one conversational line about current work (numbers/percentages and resume phrasing banned), and a connect-only ask; job/referral asks are reserved for cold emails, which keep the pitch structure
  • •no new DB columns, no new env vars, no new npm packages
Jul 11, 2026
Improvement

Daily tier-aware rate limiting across every AI and external endpoint; Groq references retired

  • •lib/rateLimit.ts: checkRateLimit reworked to a tier-aware daily limiter - RateLimitConfig now takes { key, userId, freeLimit, proLimit, windowMinutes }; resolves the user's plan from subscriptions (pro+active only) when ENABLE_FREEMIUM=true, otherwise always applies freeLimit; returns { allowed, remaining, resetAt, showUpgrade }; rateLimitResponse now emits retryAfter (seconds) and freemium-aware copy ("Daily limit reached. Try again tomorrow." vs "...Upgrade to Pro for higher limits."); added checkSharedQuota/incrementSharedQuota for platform-wide caps plus checkJinaQuota/incrementJinaQuota wrappers
  • •lib/rateLimitMessage.ts created: client-safe rateLimitMessage(retryAfter) helper for consistent 429 copy across the app
  • •app/api routes migrated to the tier-aware limiter (freeLimit/proLimit, windowMinutes 1440): tailor-resume (1/5), ats-score (2/7), generate-cover-letter (1/4), parse-job (5/12), parse-resume (1/1), parse-profile (1/1), interview-prep/generate (1/5), interview-prep/assess (5/15), generate-outreach (2/7), discover-jobs (1/1), extension/ai-fill (2/10), and newly-limited answer-question (5/20), analyze-form (2/10), example (5/10), strategy-feedback (1/3 at generation)
  • •app/api/find-contacts/route.ts: homegrown route_logs day counter replaced with checkRateLimit (1/1) + shared SerpApi budget checkSharedQuota('serpapi_monthly', 250, 30); incrementSharedQuota fires only after a successful SerpApi response
  • •app/api/parse-job + analyze-form: shared Jina budget checkSharedQuota('jina_lifetime', 150, 36500) guards URL scraping (pasted text never charged); incrementSharedQuota after a successful scrape
  • •frontend 429 handling wired into tailor (via apiFetch), ats, interview, networking, jobs pages plus ProfileForm, ResumeUpload, apply page, and extension/popup.js - 429s now render friendly "try again in X" copy, not raw JSON
  • •app/(marketing)/privacy/page.tsx: AI inference vendor section updated from Groq to Google (Gemini)
  • •new DB table: platform_limits (key, count, window_start) with RLS deny-all + increment_platform_limit(p_key) SQL function - see supabase/migrations/20260711_platform_limits.sql (run it if the table is missing; rate_limit_logs already exists)
  • •no new env vars, no new npm packages
Jul 11, 2026
Improvement

Three-tier rate limiting (dev/free-monthly/pro-daily), free-tier 150 application cap

  • •lib/rateLimit.ts: RateLimitConfig now carries devLimit/freeLimit/proLimit plus devWindowMinutes/freeWindowMinutes/proWindowMinutes; checkRateLimit resolves a dev|free|pro tier and applies the matching limit+window (dev 1440, free 43200, pro 1440); result returns tier instead of showUpgrade; added exported resolveUserTier(userId)
  • •lib/rateLimit.ts rateLimitResponse(resetAt, remaining, tier): free tier -> "Monthly limit reached. Upgrade to Pro for higher limits or try again next month."; dev/pro -> "Daily limit reached. Try again tomorrow."
  • •every AI/external route migrated to the three-tier config (dev/free-month/pro-day): tailor-resume 1/10/5, ats-score 2/10/7, cover-letter 1/10/4, parse-job 5/20/12, parse-resume 1/3/1, parse-profile 1/3/1, interview generate 1/5/5, interview assess 5/15/15, networking outreach 2/15/7, find-contacts 1/3/1, discover-jobs 1/3/1, extension/ai-fill 2/20/10, plus answer-question 5/15/20, analyze-form 2/10/10, example 5/10/10
  • •app/api/strategy-feedback/route.ts: dev 2/day, free 2/month, pro skipped entirely via resolveUserTier gate
  • •app/api/applications/route.ts POST: free tier capped at 150 tracked applications (count of non-deleted rows) returning 403 with an upgrade message; dev mode and pro users skip the check; removed the old checkLimit('applications') 429 path (GET still reports atLimit)
  • •frontend 429 handlers (jobs, ats, interview, networking, tailor apiFetch, apply, ProfileForm, ResumeUpload, extension/popup.js) now surface the server's tier-aware message (json.error) instead of a locally computed hours string, so monthly limits read correctly
  • •no new DB columns, no new env vars, no new npm packages
Jul 11, 2026
Update

Admin account bypasses all per-user rate limits for testing

  • •lib/rateLimit.ts: new isAdminUser(userId) helper comparing against ADMIN_USER_ID; checkLimit and getRemainingLimits return unlimited for the admin, and checkRateLimit returns allowed immediately (tier 'dev', no rate_limit_logs row written)
  • •shared platform quotas (SerpApi monthly, Jina lifetime) are NOT bypassed - those protect real external API allowances
  • •no new DB columns, no new env vars, no new npm packages
Jul 10, 2026
Update

Dark-glass visual overhaul - gold removed from buttons/cards/badges/inputs, kept only as a restrained accent

  • •app/globals.css: new tokens --btn-primary-bg/--btn-primary-bg-hover/--btn-primary-text/--glass-hover for dark-filled white-text primary buttons. Rewrote .ds-card/.dashboard-card/.dash-card-row/.lp-feature-card/.lp-pain-card/.glass-rim/.glass-rim-strong/.lp-window to drop borders and gold box-shadow glows, relying on background step-up (--card to --card-raised) plus shadow for depth. Added .ds-btn (dark-glass secondary button), .ds-avatar, .ds-icon-tile, .filter-pill (dark-glass toggle/filter pill with a neutral inset-highlight active state) helper classes. .status-badge, .dashboard-document-label, .lp-badge-gold/.lp-badge-green, .lp-autofill-tag recompacted to the pill spec (padding 2px 7px, font-size 10px, border-radius 20px, no border). .dashboard-stat-label/.lp-mock-stat-label gap tightened to 2px. .filter-control and CustomSelect's default input path now sit on --card-raised with a --border-subtle hairline instead of --border. Input focus rings (.dashboard-main input:focus, .input-gold-focus) switched from gold to neutral --focus-ring. Landing eyebrows (.ds-section-label, .lp-feature-num, EYEBROW/CTA_OUTLINE consts) shrunk to 10px muted instead of gold
  • •components/Sidebar.tsx, components/ui/ScoreBadge.tsx, components/CustomSelect.tsx, components/ApplicationDetailModal.tsx: avatars/badges/inputs/dropdowns updated to the same dark-glass + compact-pill treatment (no gold borders, no colored card borders)
  • •app/(marketing)/page.tsx: CTA_GOLD border dimmed from a solid gold line to a low-opacity tint (fill still carries the restrained gold accent); CTA_OUTLINE, ICON_TILE, pricing/step cards, and the closing-CTA badges converted to dark glass / compact pills; the marketing headline gold accent is unchanged (the one deliberate gold moment on the page)
  • •app/(dashboard)/{apply,ats,billing,interview,jobs,networking,tailor,tracker}/page.tsx, apply/[jobId]/page.tsx, strategy/StrategyReport.tsx: removed gold-filled/gold-bordered primary buttons, icon tiles, filter tabs, and card borders in favor of the dark-filled-white-text primary button and borderless card system; the "applied" status badge is the one place gold remains, as the existing semantic status color
  • •no new DB columns, no new env vars, no new npm packages
Jul 10, 2026
Update

Landing hero rebuilt as an overlay composition - metallic headline in front of a dimmed workspace with a studio-light beam

  • •app/(marketing)/page.tsx: replaced the split-grid hero (headline left, tilted app window right) with a full-bleed aria-hidden backdrop stage of the actual product: ghosted sidebar with the real nav labels, a Tailor + ATS panel (overall score 87, keyword/format/experience/skills subscore bars matching the ats_scores columns, and a per-bullet before/after diff with a lit "After Vantage" line), and an Applications panel reusing TrackerRow with 7 rows (added Anthropic/Snowflake/Duolingo). Headline/CTAs now float in front with metallic gradient text (.lph-metal silver, .lph-metal-gold for "There's a better way.")
  • •app/globals.css: removed .lp-hero/.lp-hero-grid/.lp-hero-float; added the .lph-* hero system (stage, mock grid, panels, subscore bars, diff, beam, shade, metallic text). New :root/light tokens: --hero-ink-strong/--hero-ink-soft (radial ink shade that darkens the mockup behind the copy), --hero-beam/--hero-beam-core (diagonal blurred studio-light beam, fades in once, disabled under prefers-reduced-motion), --metal-a..d and --metal-gold-a..d gradient stops for background-clip headline text in both themes
  • •responsive: right panel hides under 1100px; under 860px the sidebar hides, the mock drops to a faint single-panel backdrop, and the shade strengthens so copy stays readable at 375px
  • •no new DB columns, no new env vars, no new npm packages
Jul 10, 2026
Update

Hero mockup unified into one app screen with the headline bottom-left

  • •app/(marketing)/page.tsx: the hero backdrop is now a single Tailor + ATS screen instead of separate floating panels: sidebar with wordmark and real nav labels, page header row with a job-context chip, ATS score card (87, subscore bars, missing-keyword pills), a bullet-by-bullet changes card with three before/after diff rows (first row lit), and a cropped keyword-coverage table (keyword / mentions in job / mentions in resume / covered-missing status) spanning the bottom. Removed the HERO_ROWS applications panel; added DIFF_ROWS, MISSING_KEYWORDS, COVERAGE_ROWS mock data
  • •app/globals.css: hero copy now anchors bottom-left (align-items flex-end) matching the reference layout; .lph-panel/.lph-main/.lph-rightcol replaced by .lph-body/.lph-pagehead/.lph-chip/.lph-content/.lph-card/.lph-kws/.lph-diff-row/.lph-trow; cards flattened (shadow-sm, sit in the screen instead of floating); sidebar gains a hairline right border so the mock reads as one canvas; new --hero-veil token adds a uniform dim layer over the whole mockup so only the lit diff bullet stays bright (non-lit diff lines dropped to muted); shade/beam repositioned for the bottom-left copy
  • •responsive: coverage table hides under 1100px; under 860px the sidebar and score card hide and the diff card ghosts behind strengthened shading
  • •no new DB columns, no new env vars, no new npm packages
Jul 10, 2026
Update

Hero lighting reworked to match the reference - diagonal shaft from the header, dark corners

  • •app/globals.css: the hero shade now models one light source entering at the top (left of center, appearing to come from under the header) and falling at 45 degrees toward bottom-right: a diagonal transparent corridor in the veil reveals the app example along the light path, a distance-falloff radial dims it before bottom-right, a bottom-right radial pool makes that corner almost black so nothing competes with the hero text, and the top-right end of the corridor gradient keeps that corner dim; the top fade shrank (14% to 7%) so the light touches the header edge
  • •app/globals.css: .lph-beam realigned to the corridor (left 41%, rotate -45deg, wider, brighter core via --hero-beam/--hero-beam-core) with a mask-image fade so the visible shaft dies out along its length; a comment notes the beam and the 45deg corridor in .lph-shade must stay in sync
  • •verified by pixel sampling: shaft path reads ~3x brighter than the dark zones; coverage-table right, bottom-right, and text zones sit at near-background black in dark mode
  • •no new DB columns, no new env vars, no new npm packages
Jul 10, 2026
Update

Landing cards get a metallic-glass treatment - light diffuses inward from the border

  • •app/globals.css: new .glass-card class (landing only for now): the top edge light diffuses INTO the surface as a blurred inset bloom instead of a crisp 1px line, the fill carries a faint top-left sheen (same light direction as the hero beam), a hairline glass edge wraps the card, the outer drop shadow is tight and soft, and a shared ::after adds subtle SVG fractal-noise grain (mix-blend overlay, dimmer in light mode). Light-mode variant uses a white sheen, ink hairline, and soft drop
  • •app/globals.css: .glass-rim-strong (Pro pricing card, closing CTA band) upgraded to the same recipe with a stronger sheen and bloom on --card-raised; .lp-window (feature mockup frames) and .lph-card (hero mockup cards) get the sheen + inner bloom + hairline; .lp-pain-card now composes with .glass-card and its hover brightens the inner bloom instead of deepening an outer shadow
  • •app/(marketing)/page.tsx: pain cards, how-it-works step cards, and the Free pricing card now use the glass-card class instead of inline background/box-shadow styles
  • •pending user review: if approved, this treatment rolls out to every other page in the app
  • •no new DB columns, no new env vars, no new npm packages
Jul 10, 2026
Update

Bespoke feature artifacts and a premium closing CTA

  • •app/(marketing)/page.tsx: the three feature visuals are no longer three identical app windows. 01 Resume tailoring is now a layered "slips" artifact on a glass stage: a dim struck-through Before slip, a small swap chip, and a floating lit After slip with the keyword badge. 03 Auto-apply is a mini application form (name, email, work authorization, drafted answer) with green checks and an overlapping extension toast reading "Form filled from your profile / Review and submit when ready." 02 Application tracking keeps the app window as the one true app screen. Removed JOB_ROWS and the unused .lp-match/.lp-autofill-tag styles; added FORM_ROWS
  • •app/(marketing)/page.tsx: closing CTA band upgraded: headline set in the metallic gradient (.lph-metal), the before/after box replaced with the same floating slips, and a new lp-cta-special class adds a faint warm gold glow from the top-left plus a white counter-glow (the closing CTA is one of the allowed gold moments)
  • •app/globals.css: new .lp-stage, .lp-slip/.lp-slip-before/.lp-slip-after, .lp-swap-chip, .lp-mini-form/.lp-form-row/.lp-form-label/.lp-form-value/.lp-form-check, .lp-ext-toast*, .lp-cta-special classes, all with light-mode variants, following the metallic-glass recipe (inset bloom, top-left sheen, hairline edge, soft drop)
  • •no new DB columns, no new env vars, no new npm packages
Jul 10, 2026
Update

Landing page containers widened to the hero mockup's line

  • •all left-aligned landing containers now share the hero mockup's edge (max-width 1400px, padding clamp(24px, 4vw, 60px)): hero copy (.lph-copy), sound-familiar / how-it-works / pricing / closing-CTA sections (app/(marketing)/page.tsx inline styles), .lp-features, and .lp-support in globals.css
  • •components/marketing/MarketingNav.tsx and MarketingFooter.tsx widened to the same frame so the wordmark, page content, and footer sit on one line
  • •centered content (the 780px pricing card pair, centered section headings) left as is
  • •no new DB columns, no new env vars, no new npm packages
Jul 10, 2026
Update

Hero lighting tuned, metallic gold CTA button, Pro card elevated

  • •app/globals.css: hero ink pool behind the headline enlarged and strengthened (the mockup text no longer ghosts through the headline), the light corridor widened toward the upper right (transparent band 42-64 percent), the distance falloff relaxed, and the beam made wider (clamp 260px-500px), re-centered (left 42 percent), and extended further down its path (mask fade 46-86 percent)
  • •app/globals.css: new .lp-metal-btn - metallic gold pill button built from the --metal-gold-* gradient stops (lighter top, darker base, top glint, soft gold-tinted drop); dark text in dark mode, white text on the deeper light-mode golds. Used on the closing CTA and the Pro pricing card CTA; Free card keeps the dim gold link
  • •app/globals.css: .lp-slip-after brightened to frosted glass (stronger sheen and bloom) and .lp-cta-special::before gains a diagonal brushed-metal sheen plus a stronger gold ambient glow
  • •app/globals.css: new .lp-pro-card - the Pro pricing card lifts 8px, takes a brighter sheen, stronger inner bloom, and a deeper drop shadow than the Free card (lift disabled on mobile single-column)
  • •app/(marketing)/page.tsx: closing CTA and Pro card buttons switched to lp-metal-btn; Pro card gets the lp-pro-card class
  • •no new DB columns, no new env vars, no new npm packages
Jul 10, 2026
Update

Landing polish - features header animation, macOS window dots, real autofill data, level pricing cards, CTA details

  • •app/(marketing)/page.tsx: "Your unfair advantage / Three things that change your odds" header now wrapped in Reveal so it fades in like the rest of the page; auto-apply mock uses Ishvir Chopra / ischopra@uwaterloo.ca and the drafted answer is wrapped in square brackets to read as a placeholder; closing CTA eyebrow gets a small gold kicker tick; CTA_OUTLINE and ICON_TILE gain a subtle top glint
  • •app/globals.css: .lp-window-dot colored as macOS traffic lights (red/amber/green) with a tiny glint; .lp-pro-card no longer lifts upward (stays level with the Free card, still brighter with the metal button); .lp-cta-special gains a brighter top glint and deeper drop shadow; .lp-mock-logo tiles get an inset glint
  • •per user instruction, no new background gradients were added - all premium detail comes from inset glints, rims, and shadows
  • •no new DB columns, no new env vars, no new npm packages
Jul 10, 2026
Feature

Closing CTA becomes live metal - cursor-tracked lighting

  • •components/marketing/CtaSpotlight.tsx (new, client component): wraps the closing CTA card, tracks the pointer, and writes --mx/--my CSS variables directly on the element (no React state, cheap mousemove). Touch devices never set the variables and keep the static lighting
  • •app/globals.css: .lp-cta-light spotlight layer (tight specular + wide halo) follows the cursor and fades in via .is-lit; .lp-cta-metal-text re-angles the headline's metal gradient and slides its highlight band with --mx so the lettering re-lights toward the pointer like engraved metal; .lp-btn-glint sweeps a skewed specular stripe across the gold button every 5.5s (the one automatic motion, so mobile gets the moment). All of it is disabled under prefers-reduced-motion; light mode uses a warm gold spotlight
  • •app/(marketing)/page.tsx: CTA card wrapped in CtaSpotlight; headline span gains lp-cta-metal-text; button gains lp-btn-glint
  • •no new DB columns, no new env vars, no new npm packages
Jul 10, 2026
Update

Cursor light on all cards and mockups; closing CTA composition upgraded

  • •components/marketing/Spotlight.tsx (new, replaces CtaSpotlight.tsx): generic cursor-tracked light wrapper with an optional ariaHidden prop for decorative mockups; renders the .lp-hover-light layer and writes --mx/--my directly on the element
  • •app/(marketing)/page.tsx: Spotlight now wraps the feature mockup stages (tailoring slips, auto-apply form), the AppWindow frames, the how-it-works step cards, and both pricing cards. The pain-quote cards are deliberately excluded per user instruction; the hero backdrop is untouched (pointer-events none)
  • •app/globals.css: .lp-cta-light generalized to .lp-hover-light (quiet version for ordinary cards, stronger override on .lp-cta-special; gold-tinted in light mode)
  • •closing CTA composition: padding up to clamp(40px, 5.5vw, 68px), headline up to clamp(30px, 4vw, 46px) at weight 800, microcopy ("Free during early access - No credit card required") under the metal button, the before/after slips now bridged by the swap chip like the feature-01 artifact, and a giant ghost "Vantage" wordmark (.lp-cta-ghost, opacity 0.04) anchored bottom-right behind the content, echoing the step cards' ghost numerals
  • •no new DB columns, no new env vars, no new npm packages
Jul 10, 2026
Update

Metallic-glass rollout across the dashboard, resources, company, and legal pages

  • •app/globals.css: the shared dashboard surfaces (.ds-card, .dashboard-card, .dash-card-row and its hover) upgraded to the metallic-glass recipe - top-left sheen, inset top bloom, hairline glass edge, tight soft drop - with light-mode variants. Paint-only changes (no position or pseudo-element additions) so no dashboard layout can shift. Blog and changelog cards inherit automatically via .ds-card
  • •app/globals.css: .ds-btn gains a top glint (hover keeps it), .ds-btn-primary and .dashboard-primary-button get a machined top-glint plus bottom-shade pair, .ds-icon-tile gets a glint
  • •app/(marketing)/docs/page.tsx: topic cards wrapped in Spotlight (cursor light) with the link filling the card; all 11 docs subpage titles set in the metallic gradient via a scripted h1 wrap
  • •app/(marketing)/{blog,changelog,about,contact,privacy,terms}/page.tsx: page titles set in the metallic gradient (.lph-metal); blog featured post card wrapped in Spotlight
  • •no new DB columns, no new env vars, no new npm packages
Jul 10, 2026
Update

Light-mode hero slash goes gold; Pro pricing card differentiated in light mode

  • •app/globals.css: light-theme --hero-beam/--hero-beam-core switched from a dark glare to subtle gold (rgba(201,162,39) at 0.09/0.18) so the hero light shaft reads as warm sunlight in light mode; dark mode beam unchanged
  • •app/globals.css: light-theme .lp-pro-card now sits on bright white (--card, was the grayer --card-raised) with a faint warm sheen, a low-opacity gold inset hairline (CTA_GOLD-style), and a warm-tinted deep drop shadow; dark-mode Pro card untouched
  • •no new DB columns, no new env vars, no new npm packages
Jul 10, 2026
Update

Dark mode is the default; Pro card becomes a dark island in light mode; docs/blog cursor light removed

  • •app/layout.tsx: default theme is now dark - both the SSR data-theme attribute and the theme script fallback (localStorage "vantage-theme" still wins for users who chose a theme)
  • •app/globals.css: in light mode the Pro pricing card is now a dark-glass island embedded in the light page - theme tokens (--text/--muted/--gold-dim/--metal-gold-*) are re-scoped locally on .glass-rim-strong.lp-pro-card so all content inverts, on a #131313 glass surface with full-strength grain and the dark-mode metal button face; the extra class in the selector beats the later .glass-rim-strong light rule that was silently overriding the background. Dark-mode Pro card untouched
  • •app/(marketing)/docs/page.tsx and blog/page.tsx: cursor Spotlight removed per user request - docs topic cards and the blog featured card are plain glass link cards again
  • •no new DB columns, no new env vars, no new npm packages
Jul 10, 2026
Feature

Feedback page, changelog timeline, custom scrollbars, external-link icon, wider reading pages, gilded Pro card

  • •app/(marketing)/feedback/page.tsx + FeedbackForm.tsx (new): public "Help make Vantage better" page - bug report / feature request toggle, summary, details, optional email; controlled inputs with onClick submit (no form tag), disabled button with Spinner while sending, inline success and error states. Linked from the footer Resources column
  • •app/api/feedback/route.ts (new): public POST endpoint that validates the submission and forwards it as a JSON row to FEEDBACK_SHEET_WEBHOOK_URL (point it at a Power Automate "HTTP request received - Add a row into an Excel table" flow or any sheet-webhook service); returns a clear 503 message if the env var is not set
  • •new env var: FEEDBACK_SHEET_WEBHOOK_URL (server only)
  • •app/(marketing)/changelog/page.tsx + app/globals.css: changelog rebuilt as a timeline (.cl-*) - date and type pill on a left rail, a continuous line with a node per entry (newest node bright), entries as clean content blocks instead of stacked cards; single-column collapse under 700px; page widened to 980px
  • •components/ui/ExternalLinkIcon.tsx (new): square-with-arrow external-link icon; replaced the literal arrow character in jobs (View job) and networking (View Profile, contact-name links)
  • •app/globals.css: custom scrollbars app-wide - thin rounded thumb on a transparent track via new --scrollbar-thumb/--scrollbar-thumb-hover tokens (both themes), webkit + Firefox
  • •reading pages widened: terms and privacy 680 to 860, about 640 to 820, contact 560 to 760, blog posts 680 to 860
  • •app/globals.css: light-mode Pro pricing card is no longer a dark island - it is gilded: warm ivory surface, metallic gold edge along the top, warm gold hairline and warm deep shadow; stays light, still clearly featured. Dark mode unchanged
  • •no new DB columns, no new npm packages
Jul 10, 2026
Update

Contact merged into feedback, signup agreements, changelog jump list, Pro card gold line, footer links

  • •app/(auth)/signup/page.tsx: signup now requires agreeing to the Terms of Service and Privacy Policy (checkbox, validated before submit) and offers an optional marketing-emails opt-in. Both are stored in auth user metadata (marketing_opt_in, terms_accepted_at) as boilerplate for the future mailing-list feature - no emails are sent yet
  • •contact page merged into /feedback: app/(marketing)/contact/page.tsx now redirects to /feedback; components/marketing/ContactForm.tsx and app/api/contact deleted. The feedback form subject is a CustomSelect with Bug report / Feature request / General question / Partnership / Press / Other (placeholders adapt per subject); /api/feedback accepts the new types and forwards the label to the sheet webhook. Feedback page gains a "Prefer email?" fallback line
  • •links updated: footer Company column now About + Feedback (Contact removed, Feedback out of Resources); dashboard sidebar footer Contact links point to /feedback; terms and about pages reference the feedback page
  • •components/marketing/MarketingFooter.tsx: GitHub (github.com/Ishvirchopra35) and portfolio (ishvirschopra35.tech) icon links added to the social row
  • •app/globals.css: Pro pricing card simplified to a single 3px metallic gold line along the top edge in BOTH themes - light mode drops the ivory wash, sheen, gold hairline, and warm glow; dark mode otherwise unchanged
  • •app/(marketing)/changelog/page.tsx + app/globals.css: "Jump to an update" disclosure under the header - a two-column anchor list (date + title) linking to #update-N ids on each timeline entry, with scroll-margin so anchors land clear of the nav
  • •no new DB columns, no new env vars, no new npm packages
Jul 10, 2026
Update

Glass sidebar highlight, connection-code copy cleanup, tailor toggle arrow

  • •components/Sidebar.tsx: the active nav item is no longer a gold-tinted bar - it is glass (raised surface, top glint, themed hairline via --glass-hover), consistent with the dark-glass system
  • •app/(dashboard)/apply/[jobId]/page.tsx: removed the "Manage connection code" link from the per-application auto-fill card (the main auto-apply setup page keeps its code management); the "Already installed?" link now says the code lives at the bottom of the Profile page
  • •app/(dashboard)/apply/page.tsx: setup step 2 copy points to the bottom of the Profile page and the button links to /profile#extension-token
  • •app/(dashboard)/profile/page.tsx: added the extension-token anchor (with scroll margin) around ExtensionTokenSection so those links actually scroll to it
  • •app/(dashboard)/tailor/page.tsx: "URL not working? Paste the description directly" now ends with the right ArrowIcon
  • •no new DB columns, no new env vars, no new npm packages
Jul 10, 2026
Fix

Sidebar highlight keeps its gold bar and tint with a glass fill; extension toast uses the real logo

  • •components/Sidebar.tsx: active nav item restored to the gold left bar + gold-dim tint, with the fill itself now glass (top-left sheen layer over the tint, top glint, themed hairline)
  • •app/(marketing)/page.tsx + app/globals.css: the auto-apply mockup extension toast shows the Vantage logo image instead of a "V" letter tile
  • •no new DB columns, no new env vars, no new npm packages
Jul 10, 2026
Update

Extension popup restyled to the dark-glass system; SEO hardening

  • •extension/popup.html: popup stylesheet rewritten to the current design system - gold updated to #c9a227 and confined to the status badge (compact pill, no border); primary button is dark-filled white text with the machined glint; secondary/icon buttons are dark glass with inset highlights; cards are borderless glass with sheen, glint, hairline, and soft drop; inputs sit on card-raised with the neutral focus ring; result boxes use tinted inset hairlines for success/error; thin custom scrollbar; hint links are white with underline instead of gold
  • •app/layout.tsx: title template ("%s - Vantage" on every child page), keywords, publisher/category, explicit robots directives (index/follow, large image preview), author URL
  • •app/(marketing)/page.tsx: JSON-LD graph gains a WebSite node and sameAs profiles (GitHub, LinkedIn, Instagram, YouTube, portfolio) on the Organization and founder for entity recognition
  • •app/(marketing)/blog/[slug]/page.tsx: BlogPosting JSON-LD on every post
  • •app/sitemap.ts: /contact replaced with /feedback
  • •no new DB columns, no new env vars, no new npm packages
Jul 10, 2026
Update

Google Search Console verification tag wired to an env var

  • •app/layout.tsx: metadata.verification.google reads NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION; the meta tag is omitted when the variable is unset
  • •new env var: NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION (optional)
Jul 8, 2026
Improvement

Plain-language pass across all user-facing copy

  • •replaced jargon in visible strings app-wide: "parse" to "read"/"analyze", "fetch" to "find"/"load" (tailor, jobs, ats, apply, profile, privacy, docs, blog)
  • •"token" no longer shown to users: the extension pairing secret is now called a "connection code" everywhere (components/ExtensionTokenSection.tsx, docs/extension, docs/auto-fill, apply/[jobId] links); variable and DB names (extension_token) unchanged
  • •tailor page: "Parse job" button is now "Analyze job"; jobs page: "Fetch New"/"Fetch Jobs" now "Find New"/"Find Jobs", "Fetching..." now "Finding..."
  • •ATS now gets a brief plain-language clarification on first mention per page (tailor, ats, dashboard, apply detail, strategy report, landing, privacy, and the getting-started/resume-tailoring/auto-fill/extension/strategy-feedback docs)
  • •removed em dashes from all live user-facing text: app/layout.tsx + (marketing)/layout.tsx + (marketing)/page.tsx titles now use a hyphen; landing quote attribution dash removed
  • •error copy moved to active voice ("We could not read this job posting.", "We could not load jobs right now.")
  • •note: the append-only changelog, SEO meta keyword strings, and terse compact data labels (tracker stat tiles/columns) were intentionally left unchanged
Jul 8, 2026
Feature

Blog post - The New Vantage design overhaul

  • •content/blog/the-new-vantage-design-overhaul.mdx: News-category post announcing the design system v2 overhaul (gold accent, Outfit/Manrope type, redesigned landing + dashboard, per-bullet tailoring diff, plain-language pass)
  • •appears as the featured post on /blog and is picked up automatically by generateStaticParams and the sitemap
Jul 8, 2026
Improvement

Subtle gold particle background on the landing page

  • •components/marketing/Particles.tsx (new): canvas particle field with mouse magnetism, alpha fade near edges, dpr-aware sizing; self-contained (no external cn/util dep), transparent background, pointer-events-none and aria-hidden so it never intercepts clicks or reaches assistive tech
  • •components/marketing/LandingParticles.tsx (new): theme-aware wrapper that feeds a variation of --gold to the field — brighter gold (#e0bd6a) on the dark #0a0a0a bg, deeper amber-gold (#b8862f) on the light #fafafa bg — and swaps live via a MutationObserver on the html data-theme attribute
  • •app/(marketing)/page.tsx: landing content wrapped in a relative container with the particles absolutely positioned behind (z-0) and content above (z-1); scoped to the landing page only, so blog/docs/other marketing pages are unaffected. Placed inside a relative wrapper rather than a fixed -z-1 layer because html/body both paint an opaque var(--bg)
Jul 8, 2026
Improvement

Unified page letterhead across every dashboard page

  • •components/ui/PageHeader.tsx (new): one shared header for every dashboard page - a gold signature tick, an Outfit title (26px, tighter tracking), an optional muted subtitle, and an optional right-aligned action slot. Presentational only, so it works in both server and client pages
  • •app/globals.css: added .page-header / .page-header-kicker / .page-header-action classes, including the mobile stack where the action becomes a full-width tap target
  • •replaced the ad-hoc, incrementally-built headers on every page with PageHeader: dashboard home, jobs, tracker (Applications), tailor, ats, strategy (both locked and report states), networking, interview, apply, billing, documents, settings, profile. Headers were previously a mix of div/h1, 24px, and 4/8/24/28px margins
  • •ats page now has a real page title ("ATS scores") for the first time; its score list keeps a quiet "Score history" section label. Settings dropped its stray in-card back button (navigation is the sidebar)
  • •copy tidied to sentence case where the header changed: "Job feed", "Find new", "Interview practice"
Jul 8, 2026
Improvement

One answer-method control per interview session instead of per card

  • •app/(dashboard)/interview/page.tsx: the Voice/Type toggle used to sit inside every question card, crowding each one. Replaced it with a single segmented "Answer with" control in the session toolbar (next to the category tabs); every card now follows that one selection
  • •answerMode state changed from a per-question Record to a single session-wide AnswerMode; browsers without speech recognition auto-default to Type and the Voice segment is disabled
  • •each card keeps its own "Hear question" button; the crowded hear + voice + type button cluster is gone
Jul 8, 2026
Improvement

Auto-apply page now leads with Chrome extension setup

  • •app/(dashboard)/apply/page.tsx: added a "Set up once" card with a two-step checklist - install the Chrome extension, then connect it with a connection code - above the job selection list, reframed as "Then apply / Choose an application"
  • •step 2 reflects real state: it reads profiles.extension_token in parallel with the applications query and shows a green check when a connection code already exists (links to Profile to manage it, otherwise to generate one)
  • •the extension is not on the Chrome Web Store yet, so step 1's "Get the extension" button uses a CHROME_EXTENSION_URL placeholder constant pointing at the /docs/extension install guide (no dead href="#"); swap the constant for the store URL when the extension ships
  • •applications query parallelized with the new profile lookup via Promise.all
Jul 8, 2026
Fix

Documentation accuracy pass across all docs pages

  • •audited every /docs page against the current app and corrected drift from features that changed
  • •auto-fill: rewrote to match the real flow (Auto-apply page + Chrome extension fill + the Application Questions answerer); removed the "Apply Prep > Console tab / Copy-paste tab" steps for console-snippet and answers-panel UIs that no longer exist
  • •interview-prep: third question category is Role-specific (was wrongly "Company-specific"); answer method is now one session-level "Answer with Voice/Type" control, not a per-question choice; assessment described as Content + Delivery scores with filler-word detection
  • •ats-scoring: scoring happens in the Tailor flow (ATS Score tab); the ATS page is the read-only score history, not a place you paste a job
  • •resume-tailoring: steps now match the real buttons (Analyze job -> Tailor My Resume) and tabbed results
  • •application-tracking: button is "Log application" (not "Add application"); page is "Applications"
  • •cover-letters: generated from the Tailor flow's Cover Letter tab and saved to Documents
  • •extension: noted the Chrome Web Store listing is not live yet (developer-mode install for now); fixed the troubleshooting step that pointed at the removed console-snippet tab; removed the nonexistent "direct link from your Profile page"
  • •networking: contact search targets recruiters/hiring managers via public LinkedIn search and is capped per day
  • •getting-started: tailor and log-application steps corrected; bumped "Last updated" to July 8, 2026 on every edited page (billing was already accurate)
Jul 8, 2026
Fix

Stop showing raw errors to users; log them to console.error instead

  • •lib/apiResponse.ts: serverError() no longer returns the raw exception message to the client. It logs the real error with console.error and returns a generic "Something went wrong on our end. Please try again." This fixes raw-error exposure across all 49 API routes that use it in their catch blocks
  • •app/(auth)/login + signup: Supabase auth errors are logged and mapped to friendly copy ("Incorrect email or password.", "An account with this email already exists.") instead of surfacing the raw message
  • •app/(dashboard)/interview: three spots (tracker load, assess, start session) that showed the raw error/e.message now log it and show a friendly message
  • •app/(dashboard)/ats: raw Supabase error messages on load replaced with a friendly message + console.error
  • •app/(dashboard)/actions.ts (updateProfile): upsert and catch now log the raw error and return a friendly message, so ProfileForm no longer displays raw DB errors
  • •components/ResumeUpload.tsx: upload catch shows a friendly message and logs the raw error
  • •app/error.tsx: the global error boundary now logs the error via console.error (the raw message it displays was already gated to development only)
Jul 8, 2026
Fix

Logo now legible in light mode

  • •app/globals.css: the logo mark is a light silver PNG with no dark-background variant, so it blended into the light theme. Added a light-mode-only CSS filter (brightness(0.45) contrast(1.2)) on img[src="/logo.png"] that shifts it to a dark graphite tone while keeping the metallic gradient. Dark mode is unchanged; no JSX edits needed since the rule targets the logo images directly
Jul 8, 2026
Update

Light mode is now the default theme

  • •app/layout.tsx: the SSR <html data-theme> and the pre-hydration theme script now default to "light" instead of "dark" when no preference is stored
  • •components/Sidebar.tsx + components/SettingsClient.tsx: theme toggles initialize to "light" as the fallback
  • •components/marketing/LandingParticles.tsx: particle color falls back to the light-mode gold before hydration
  • •returning users who previously picked a theme keep their choice (localStorage vantage-theme is unchanged)
Jul 7, 2026
Improvement

Design system v2 - Outfit/Manrope fonts, gold accent, elevation tokens

  • •app/layout.tsx: Google Fonts link tags in head (preconnect + stylesheet) loading Outfit 600/700/800 (display) and Manrope 400/500/600 (body)
  • •app/globals.css: new tokens merged into the existing :root and light-theme blocks (all existing variables and app styles preserved) - --card-raised, --card-sunken, --border-subtle, --gold, --gold-dim, --gold-border, --radius-sm (8px), --radius-lg (16px), --shadow-sm/md/lg/glow, --font-display (Outfit), --font-body (Manrope), each with light-mode variants where they differ; html/body font-family switched to var(--font-body) with the old system stack as fallback, body gains line-height 1.5 and antialiasing; new h1-h6 rule with var(--font-display), weight 700, letter-spacing -0.02em
  • •components/Sidebar.tsx (styles only, desktop + mobile drawer): sidebar root uses var(--font-body); active nav item gets var(--card-raised) background and var(--shadow-sm) on top of the existing accent left border; nav items get transition all 0.15s ease and var(--radius-sm); sidebar/topbar/drawer borders use var(--border-subtle); avatar gets 2px solid var(--gold-border); rectangular border-radius values (4/5/8/10px) standardized to var(--radius-sm) - circular elements (avatar, Pro/Free pills, close button) intentionally kept round
Jul 7, 2026
Improvement

Sidebar nav hover state

  • •components/Sidebar.tsx: NavLink tracks a hovered state via useState with onMouseEnter/onMouseLeave on the link; non-active items show background var(--card-raised) on hover (fades via the existing 0.15s transition), active items unchanged (they already have the raised background plus shadow); coming-soon placeholders stay inert
Jul 7, 2026
Improvement

Landing page redesign - gold signature treatment, Outfit/Manrope type system

  • •components/marketing/MarketingNav.tsx: dark/light toggle removed entirely (theme state, Sun/Moon icons, drawer toggle all deleted - marketing pages now render dark); navbar background rgba(10,10,10,0.8) with backdrop blur(12px) and var(--border) bottom border; wordmark in Outfit 700 at 1.35rem; "Get started" CTA now the signature gold treatment (var(--gold) border, var(--gold-dim) background, gold text, var(--radius)); nav links restyled via .mkt-nav-link
  • •app/(marketing)/page.tsx: hero pill badge replaced with gold "BUILT FOR RECRUITING SEASON" eyebrow (Outfit 500 12px uppercase); headline Outfit 800 clamp(48px, 8vw, 96px) letter-spacing -0.04em with "There's a better way." in gold on its own line; subheadline Manrope 16px max-width 480px; gold primary + outlined secondary CTAs (12px radius, 14px Manrope 600, 12px 24px padding); microcopy updated to "Free during early access"; all section eyebrows unified to gold Outfit 600 11px uppercase; pain-point cards get var(--shadow-md) + var(--radius); feature cards use var(--card-raised) + var(--shadow-md) with hover to var(--gold-border) border + var(--shadow-glow) (0.2s ease), 36px var(--gold-dim) icon tiles with gold icons; how-it-works steps get a large Outfit 800 gold watermark number at 0.15 opacity behind relative content, var(--card) background, 2px var(--gold-border) top border, new per-step icons; pricing Pro card featured with var(--card-raised) + var(--gold-border) + var(--shadow-glow) and gold-dim "Most popular" badge, prices Outfit 800 42px, checkmarks gold for Pro / var(--score-green) for Free, CTAs gold; removed the redundant fixed background div
  • •components/marketing/MarketingFooter.tsx: background var(--bg) (was var(--card)), top border var(--border-subtle), root font Manrope, copyright/tagline 13px, wordmark Outfit 700, social link colors moved from hardcoded hex to var(--muted)/var(--text)
  • •app/globals.css: stale unused .lp-* landing classes replaced with .lp-grid-3/.lp-grid-4/.lp-pricing-grid (responsive: 4-col to 2-col to 1-col) and .lp-feature-card with the gold hover state; .mkt-nav-link restyled to Manrope 14px var(--muted) uppercase letter-spacing 0.06em with color-based hover/active; .mkt-footer-link pinned to Manrope 13px
Jul 7, 2026
Improvement

Landing page density + gold visibility pass

  • •app/(marketing)/page.tsx: section vertical padding cut ~40% (hero 96px -> 56px, mid sections 90px -> 54px, closing CTA 80/96px -> 48/56px) for a denser page
  • •app/globals.css: dark-mode --gold-border strengthened from 0.25 to 0.4 alpha and --shadow-glow's outer glow from 4px 24px at 0.08 to 6px 32px at 0.18 - at the old values the gold ring and glow were imperceptible on the #0a0a0a background, which made feature-card hover and the Pro pricing card read flat (the styles themselves were already wired: shadow-md default on feature cards, card-raised/gold-border/glow/gold checkmarks on Pro); everything referencing the tokens (feature hover, Pro card, how-it-works top borders, sidebar avatar ring) gets the stronger treatment automatically
  • •app/globals.css: .lp-feature-card transition narrowed from all to box-shadow + border-color (0.2s ease)
Jul 7, 2026
Improvement

Landing hero eyebrow re-specced, pain-point cards raised

  • •app/(marketing)/page.tsx: hero eyebrow set to exact spec (gold, Outfit 500, 11px uppercase, 0.1em tracking, 16px bottom margin - was 12px/0.12em/20px); hero section gains paddingTop 80 so it clears the navbar; "Sound familiar?" cards moved to var(--card-raised) background with padding 20, fontSize 15, lineHeight 1.6, color var(--text)
Jul 7, 2026
Improvement

Dashboard design overhaul - Outfit/Manrope type, gold signature, new Auto-apply page

  • •components/Sidebar.tsx: nav items now 40px tall with 0 12px padding and var(--radius-sm); active item gets var(--card-raised) background + 2px var(--gold) left border + 600 weight (transparent left border on inactive prevents layout shift); hover shows var(--card-raised) + var(--text); nav container padding 12px 16px for the consistent 16px horizontal gutter; logo wordmark in var(--font-display) 700 (desktop, topbar, drawer); avatar borderRadius 50% keeping the 2px gold-border ring; new "Auto-apply" nav item between Interview Prep and Profile with a gold dot + NEW badge (Outfit 600 10px uppercase)
  • •app/(dashboard)/apply/page.tsx (new): /apply index page - the apply/[jobId] flow was orphaned (nothing linked to it), so the nav item needed a destination; lists the user's parsed jobs (id/title/company/location/created_at, newest first, 50 cap) as dash-rows linking to /apply/[jobId], EmptyState pointing to /tailor when no jobs
  • •app/globals.css: .dashboard-main-inner padding 32px -> 32px 40px; .status-badge restyled (var(--radius-sm), Outfit 600 11px uppercase 0.06em); status color vars remapped - applied gold-dim/gold, interviewing rgba(34,197,94,0.1)/score-green, rejected+ghosted rgba(239,68,68,0.1)/score-red, offer left green as before (spec didn't cover it); new .dash-row class (14px 16px padding, border-subtle bottom border, hover to card-raised, 0.15s) and .input-gold-focus class (gold-border + 3px gold-dim ring on focus; !important to beat inline border)
  • •app/dashboard/page.tsx: greeting title Outfit 700 24px -0.02em with 14px Manrope subtitle; stat cards on var(--card-raised) + var(--shadow-md), values Outfit 800 32px -0.03em, labels Manrope 12px uppercase 0.06em; section titles now eyebrow style (Outfit 600 12px uppercase 0.08em muted); all three list sections converted from bordered pills to .dash-row rows
  • •page titles normalized to Outfit 700 24px -0.02em + Manrope 14px muted subtitles: tracker, jobs, interview, networking, billing, strategy (both variants), tailor, apply/[jobId], SettingsClient, ProfileForm, DocumentsClient
  • •per-page padding overrides removed (tracker, tailor, strategy had their own 40px 16px 80px stacking on the layout padding) - the layout's 32px 40px now governs spacing on every inner page
  • •app/(dashboard)/tailor/page.tsx: inputs on var(--card-sunken) with var(--radius), 12px 16px padding, Manrope 14 + input-gold-focus focus ring (URL input and paste textarea); primaryBtn (Parse job / Continue / Tailor / cover letter) switched to the gold treatment (gold-dim bg, gold-border border, gold text, Outfit 600, 12px 24px); new pre-parse placeholder under the input - "Paste a job URL above to get started" with three gold-check hint lines (tailored resume, ATS score, cover letter)
Jul 7, 2026
Improvement

Landing page depth pass - feature hierarchy, section headlines, quote cards, gold glow

  • •app/(marketing)/page.tsx: feature section split into a real hierarchy - three lead cards (Resume tailoring, ATS scoring, Auto-apply engine) at 28px padding with 18px Outfit names and fuller descriptions, plus a compact 5-up supporting row (28px icon tiles, 14px names); eyebrow moved to "YOUR UNFAIR ADVANTAGE" with the old eyebrow text promoted to a proper h2
  • •every section now has an Outfit 700 clamp(24-30px) headline under its eyebrow: "Applying more isn't working." / "Everything you need to get hired" / "Upload once. Tailor every time." / "Free to start. Unlimited for $8."
  • •pain points recast as first-person quotes (figure/blockquote with em-dash attributions like "every senior in recruiting season") in .lp-pain-card - gold-border left rule that intensifies to full gold on hover
  • •gold glow depth pass: primary CTAs (hero, both pricing cards, bottom) get .lp-glow-cta ambient gold glow that strengthens on hover; how-it-works cards get an inset gold glow bleeding down from their gold top border; Pro pricing card now carries the most dramatic treatment on the page (shadow-glow + 48px gold halo + shadow-lg); bottom CTA section wrapped in full-bleed border-subtle hairlines top and bottom
  • •app/globals.css: new .lp-glow-cta, .lp-pain-card, .lp-grid-5 (5/3/1 responsive) classes
Jul 7, 2026
Improvement

Closing CTA section as a hero bookend

  • •app/(marketing)/page.tsx: bottom CTA rebuilt to mirror the hero - Outfit 800 at clamp(36px, 6vw, 64px) with -0.04em tracking, headline now two beats with the closer "Start with an unfair advantage." in gold (echoing the hero's gold line and the brand tagline); section padding 96px inside the hairlines; CTA enlarged (15px, 14px 28px padding) with hero-style microcopy beneath ("Free during early access · No credit card required")
  • •app/globals.css: new .lp-glow-cta-lg for the closing CTA - gold-border ring + 32px halo at rest, full-gold ring + 48px halo and a 1px lift on hover, transform disabled under prefers-reduced-motion
Jul 7, 2026
Improvement

Closing CTA rebuilt as a glowing band with a before/after diff artifact

  • •app/(marketing)/page.tsx: the centered bookend closer (64px headline + gold second line + hairlines) is replaced by a full-width glowing band - var(--card-raised) with a gold border and the page's strongest halo (shadow-glow + 64px gold bloom + shadow-lg). Left column: "YOUR NEXT APPLICATION" eyebrow, the approved "Stop sending the same resume to every job." headline at Outfit 700 clamp(26-38px), the glowing CTA and early-access microcopy. Right column: a product-diff artifact on var(--card-sunken) - the hero's own line "Applied to 100 jobs. Heard back from 1." struck through under a BEFORE label, rewritten as "Applied to 12 jobs. Landed 4 interviews." with a gold left bar under AFTER VANTAGE, plus ATS 84/100 (gold) and INTERVIEWING (green) chips echoing the app's badge language; artifact is aria-hidden illustration
  • •app/globals.css: new .lp-cta-band-grid (1.1fr/0.9fr, single column under 768px); .lp-glow-cta-lg retained for the CTA ring/halo hover
Jul 7, 2026
Improvement

Consistent skeleton loading states across all dashboard pages

  • •app/globals.css: @keyframes shimmer (opacity 0.5 -> 1 -> 0.5, used at 1.4s ease-in-out infinite) and @keyframes fade-in with a .fade-in class (0.2s ease) - the shimmer keyframe previously only existed inside SkeletonLoader's scoped style tag, so tracker's and StrategyReport's skeleton animations silently never ran unless that component happened to be mounted
  • •components/ui/SkeletonLoader.tsx: rewritten to the new spec - var(--card-raised) background (was a linear-gradient sweep, which also violated the no-gradients rule), var(--radius), opacity shimmer, aria-hidden, scoped style tag removed
  • •full-page spinners replaced with layout-matched skeletons: interview (title/subtitle bars + start-session card + 3 session rows), apply/[jobId] (back-link + title bars + 3 section cards)
  • •text/spinner placeholders replaced with skeleton rows: ats ("Loading scores..." -> 4 table-row blocks), networking outreach tracker (centered spinner -> 3 row blocks)
  • •tracker: loading state upgraded from 3 generic bars to the real layout (4 stat-tile skeletons in the stats grid + 5 row skeletons in the table card), now via SkeletonLoader
  • •loaded content fades in via .fade-in on: tracker stats grid + table, ats table, networking tracker table, interview list view, apply/[jobId] page root, StrategyReport report body
  • •no changes needed: jobs (localStorage-cached list, loading is a non-blocking refresh banner), tailor (no page-level gate; autoLoading is an inline banner), documents/settings/profile/billing/strategy wrapper/apply index (server-rendered, data resolves before paint)
Jul 7, 2026
Improvement

Dashboard consistency pass - custom selects, gold primary buttons, unified badges/cards/labels

  • •app/globals.css: new .dash-select class (card-sunken background, var(--radius), appearance:none with an inline SVG chevron background image, gold focus ring, card-raised option styling) and .btn-gold-hover (gold ring + 16px glow on hover, disabled-safe); .status-badge padding tightened to 3px 8px; --status-offer-* remapped to rgba(34,197,94,0.15)/score-green
  • •every native select converted to .dash-select: tracker log form, networking message type + job, interview job picker, ats job + resume version, jobs job-type + date-posted filters, ProfileForm graduation year + years of experience
  • •every primary action button now wears the gold treatment with hover glow: tracker (+ Log application, Log), networking (Find contacts, Generate), interview (Start new session x2), ats (Run ATS check), jobs (Fetch New, Fetch Jobs), tailor (Parse job, Continue, Tailor My Resume, Generate Cover Letter, Log)
  • •status colors standardized in the three remaining indigo/amber maps (tracker, ApplicationDetailModal, apply/[jobId]): applied gold-dim/gold, interviewing green 0.1, offer green 0.15, rejected+ghosted red 0.1; badge shape unified (radius-sm, Outfit 600 11px uppercase 0.06em, 3px 8px)
  • •var(--shadow-md) + var(--border) added to every card missing it: tracker, networking, interview (start + past sessions), ats (history/score-now/insights), tailor, billing, strategy locked card, StrategyReport, ProfileForm, SettingsClient, DocumentsClient, apply/[jobId], dashboard section cards; modals (ApplicationDetailModal, Settings delete confirm) get shadow-lg; stray 12/14px radii normalized to var(--radius)
  • •section labels unified to the eyebrow style (Outfit 600 11px uppercase 0.08em muted): networking numbered sections, interview card titles, ats card titles, StrategyReport (Key Insights / Next steps / Skills) with the green FOCUS HERE and red STOP APPLYING HERE coloring removed
  • •row hover states (card-raised, 0.15s): tracker table rows, ats history rows, networking outreach rows, interview past-session rows, DocumentsClient cards (onMouseEnter/Leave)
  • •interview empty state rebuilt: gold-dim icon tile, "No sessions yet" Outfit 600 15px, "Start a session above to practice with AI feedback" Manrope 13px
  • •jobs initial load: full-page spinner replaced with 4 job-card skeletons (missed in the earlier skeleton audit - the gate is initialLoadDone, not a loading useState(true)); saved-job indigo border swapped for var(--gold-border)
Jul 7, 2026
Fix

Job pickers everywhere now source exclusively from the application tracker

  • •app/(dashboard)/apply/page.tsx: the Auto-apply list now queries applications (company, role, job_id, non-deleted, job_id required) instead of the raw jobs table - applications always carry company/role so "Untitled job" rows can no longer appear; deduped by job_id, rows read "Company · Role / Tracked date"; empty state now points to the tracker ("Auto-apply works from your application tracker")
  • •app/(dashboard)/ats/page.tsx + networking/page.tsx: the jobs dropdown queries already required a tracker application via applications!inner(id) but still surfaced jobs whose applications were soft-deleted; both now filter applications.deleted_at is null
  • •interview job picker already sourced from applications with deleted_at excluded - no change needed; tailor and apply/[jobId] fetch single jobs by explicit id, not lists
Jul 7, 2026
Fix

Full audit - everything that consumes jobs sources from the application tracker

  • •lib/userContext.ts: fetchApplicationHistory filtered applications with .eq('deleted', false) - a column that does not exist (schema is deleted_at) - so all four history queries errored silently and buildUserContext reported an empty application history to every AI route (tailoring, cover letters, strategy feedback, interview prep, outreach); all four now use .is('deleted_at', null), restoring real tracker data as the context source
  • •lib/rateLimit.ts: same phantom-column bug in both application-cap counts (checkLimit + getRemainingLimits) meant the 150-application free-tier cap silently never enforced via the intended path; both now use .is('deleted_at', null)
  • •audit of every from('jobs') call confirmed the remaining surface is correct: job pickers/lists (ats, networking, interview, apply index) are tracker-scoped with soft-deleted applications excluded; by-id job fetches (tailor flow, ats-score, tailor-resume, generate-cover-letter, generate-outreach, interview-prep, answer-question, fill-snippet, extension kit, apply/[jobId]) stay unrestricted deliberately - the parse -> tailor -> log flow operates on a job before it is tracked; .in('id', jobIds) enrichment calls (applications API, interview page) already derive their ids from tracker rows; api/strategy-feedback was already fully tracker-sourced and never touches the jobs table
Jul 7, 2026
Improvement

CustomSelect component replaces every native dashboard dropdown

  • •components/CustomSelect.tsx (new): fully custom React dropdown - card-sunken trigger with rotating SVG chevron, absolute-positioned list (zIndex 50, card-raised, gold-border, shadow-lg, 4px offset, width matches trigger, maxHeight 240 with scroll), options with gold-dim/gold hover and gold 600 selected state, outside-click close via document mousedown listener, position:relative wrapper so the list anchors inside its card, placeholder + disabled support, listbox/option ARIA roles
  • •all 10 native selects replaced: tracker log-form status, networking message type + job, interview job picker (the one that overflowed its card - now flex:1 wrapper, clipped list with scroll), ats job + resume version, jobs job-type + date-posted filters, ProfileForm graduation year + years of experience (numeric values bridged via String/parseInt)
  • •app/globals.css: .dash-select class removed - superseded by CustomSelect; remaining native selects are marketing-only (ContactForm, DocsLayout)
  • •page padding audit re-verified: .dashboard-main-inner (32px 40px) is the single content wrapper for every dashboard page including /apply, /profile, /settings; no page carries its own outer padding
Jul 7, 2026
Improvement

Dashboard polish pass - input focus glow, zebra rows, rich empty states, danger zone, interview mid-section

  • •app/globals.css: dashboard-wide gold focus glow - .dashboard-main input:focus / textarea:focus get the gold-border + 3px gold-dim ring, covering every current and future field with one rule; .dash-row gains nth-child(odd) card-sunken zebra striping (hover still wins); new .dash-card-row class - raised card rows (shadow-md) that hover to gold-border + shadow-glow
  • •app/(dashboard)/apply/page.tsx: rows rebuilt as .dash-card-row cards with a gold auto-apply bolt icon tile, Outfit titles, and a gold "Start " affordance, 10px gaps between cards
  • •app/(dashboard)/interview/page.tsx: new "how practice works" strip between the session starter and past sessions - three raised tiles (Real questions / Answer out loud / Scored feedback) with gold icon tiles; past-session rows get zebra striping
  • •app/(dashboard)/strategy/StrategyReport.tsx: Key Insights cards get the landing how-it-works treatment - 2px gold-border top edge with an inset gold glow, #N markers now gold Outfit
  • •components/SettingsClient.tsx: danger zone is now a distinct red-tinted container (rgba red 0.04 bg, 0.25 red border, var(--radius)) with a red eyebrow label and an Outfit 600 red delete button
  • •empty states upgraded to icon + title + subtitle: ats history, networking outreach tracker, tracker (adds gold icon + gold-hover CTA), jobs feed, and the dashboard home's three section empties via a new SectionEmpty helper (emptyStateStyle removed)
  • •zebra row striping with hover-restore added to tracker table, ats history, networking outreach, and interview session rows
Jul 7, 2026
Improvement

Full SEO pass - metadata, sitemap, robots, structured data

  • •app/layout.tsx: metadataBase from NEXT_PUBLIC_APP_URL, richer default title/description naming the core features and audience, authors/creator Ishvir Chopra, default Open Graph + Twitter card tags
  • •app/(marketing)/layout.tsx: title template "%s — Vantage" scoped to marketing pages only (dashboard titles untouched)
  • •app/(marketing)/page.tsx: thorough landing metadata - absolute title "Vantage — AI Resume Tailoring, ATS Scoring & Auto-Apply for Students", long description naming Ishvir Chopra, CS students/new grads/Waterloo, 15 keywords covering the target queries, canonical /, OG + Twitter tags; JSON-LD @graph with Organization (founder Person Ishvir Chopra) + SoftwareApplication (name, description, url, free offer) injected via script tag
  • •every marketing page normalized to the template pattern with canonical URLs: about (rewritten description naming the founder and audience), contact, privacy, terms, docs index + all 11 docs pages (also fixes "Cover Letters -Vantage Docs" style typos); blog index and changelog had no metadata at all - both added
  • •app/(marketing)/blog/[slug]/page.tsx: generateMetadata now emits the bare post title (template adds the suffix), excerpt description, canonical /blog/[slug], OG article tags with publishedTime + author, Twitter card
  • •app/sitemap.ts (new): all 19 marketing/docs routes with priorities + every blog post from content/blog with its publish date as lastModified
  • •app/robots.ts (new): allows all public pages, disallows the authed app routes (/dashboard, /tailor, /tracker, etc.) and /api, points to sitemap.xml
  • •note: canonical/OG absolute URLs resolve against NEXT_PUBLIC_APP_URL - must be set in Vercel for production
Jul 6, 2026
Update

Chrome extension - version marker for stale-build detection

  • •extension/manifest.json: version bumped 1.0.0 -> 1.1.0
  • •extension/content.js: the "workday fill start" log now includes the extension version (via chrome.runtime.getManifest()), so a stale build that was not reloaded at chrome://extensions is immediately visible in the console. The Enter-commit fix for multiselect search (school, field of study, skills) was already shipped in the previous release; the type -> Enter -> wait -> click sequence is verified by the harness (standard 21/21, custom 17/17, restricted 3/3)
Jul 6, 2026
Fix

Chrome extension - prompt option clicks now register, closest-match selection, websites slot discovery

  • •extension/content.js: suggestion clicks were dispatched as bare element.click(), which Workday's prompt options ignore (same as the Add buttons) - new selectPromptOption clicks with the full pointer sequence, retrying on the option's deepest descendant and any inner checkbox, verifying registration between attempts (school/field-of-study: multiselect container gains the option text or a selectedItem; skills: the tag appears in the section), logging the exact element clicked (outerHTML, 300 chars), and skipping the click entirely when the value is already selected (prevents re-run duplicates)
  • •extension/content.js: new bestOptionMatch replaces first-includes-wins matching in both fillWorkdayPrompt and skills - tiers: exact, exact after stripping a parenthetical qualifier ("Java (Programming Language)" for "Java"), shortest option starting with the term at a word boundary, shortest word-boundary contains, then option-contained-in-term ("MongoDB" for "MongoDB Atlas"); "Python" no longer picks "Python IDLE", "React" no longer picks "React VR"
  • •extension/content.js: websites slot discovery broadened - inputs are found via automation-id, name="url", id ending in "--url", id containing "website", or the visible "URL"/"Website" label (this tenant renders slots with no automation-ids at all); when 0 slots are found the section's field inventory and outerHTML (8k) are dumped so the real markup is visible
  • •harness (scratchpad): prompt options now ignore bare .click() (mousedown-armed, like the real tenant); skills taxonomy includes decoys (Python IDLE, Java Servlets, React VR) with assertions that closest matches are picked and decoys rejected; website slots render as label + name="url" with no automation-ids. Standard 22/22, custom tenant 17/17, restricted skills 3/3
Jul 6, 2026
Fix

Chrome extension - full-name skill queries, single-click prompt selection, degree listbox support

  • •extension/content.js: skills now type the FULL skill name before committing with Enter (the 3-character truncation predated the Enter fix and produced queries like "Pyt"/"Jav"); the input is cleared before each search, and both skills and school searches snapshot the option elements already in the DOM and only match FRESH ones - stale popups from other sections (e.g. school suggestions appearing during a skills search) can no longer be matched or picked
  • •extension/content.js: bestOptionMatch gains an opt-in first-result fallback (used for skills only - the search already ran on the full term) and filters "Select One" placeholder rows; tier order remains exact > exact-minus-parenthetical > word-boundary starts-with > word-boundary contains > option-contained-in-term
  • •extension/content.js: selectPromptOption no longer double-clicks - when the option contains a radio/checkbox that control is the single primary target (clicking the div then the radio toggled the selection back off, which is why school appeared unselected); success signals now include the control becoming checked and the popup closing, and the one alternate click is skipped whenever the control already holds the selection
  • •extension/content.js: new fillWorkdayListbox for standard listbox dropdowns (button[aria-haspopup="listbox"] -> [role="listbox"] options) wired into education for the Degree field, filled from kit education[].degree and left blank without kit data; keeps any existing selection
  • •app/api/extension/kit/route.ts: kit education entries now include degree, read from the optional profiles.degree column (same graceful-degradation pattern as location; optional migration: ALTER TABLE profiles ADD COLUMN IF NOT EXISTS degree text)
  • •harness (scratchpad): school options are now radio-based with toggle semantics and a popup that stays open (asserts exactly one click and no toggle-off, plus skills immunity to the stale school popup); skills search is token-based and records committed queries (asserts full names, not "pyt"); degree listbox mock with armed clicks. Standard 23/23, custom tenant 19/19, restricted skills 3/3
Jul 6, 2026
Feature

Tailoring results as a per-bullet diff view; PDF flow removed

  • •app/api/tailor-resume/route.ts: rewritten to return structured JSON instead of raw resume text/HTML. The AI now returns { changes: [{ section, original, tailored, reason }], tailored_resume_text, skill_gaps } in one generateJSON call (60s timeout, 6000 token cap); changes are sanitized server-side (well-formed fields only, original must differ from tailored, capped at 60). The HTML surgical-edit mode, profiles.resume_html fetch, and the entire PDF generation/upload block (generateResumeBuffer, storage upload, pdf_url) are removed - response is now { document, changes, skillGaps, atsScore }. Tailoring rules preserved: no invented facts, metrics verbatim, bullets under 20 words, max 1-2 keywords per bullet, unchanged bullets stay out of changes. Inline ATS scoring and event tracking unchanged; console.log calls removed
  • •app/(dashboard)/tailor/page.tsx: Tailored Resume tab is now a stacked diff grouped by resume section (uppercase section labels): original bullet in muted strikethrough, tailored bullet highlighted green, one-line reason underneath, a "Copy bullet" button per change, and a "Copy all tailored bullets" button at the top that copies the full updated resume as plain text. PDF download buttons removed from the resume tab (cover letter print-to-PDF untouched); after tailoring the page lands on the diff tab; falls back to the full tailored text when no per-bullet changes are returned (e.g. older documents)
  • •lib/ai.ts: generateJSON accepts an optional maxTokens parameter (default 3000, unchanged for existing callers) so the tailor route can request 6000 tokens for the full resume + changes payload
  • •lib/generatePdf.ts: now unused, kept in place in case the PDF flow returns
Jul 6, 2026
Improvement

Tailoring - active JD keyword weaving, per-entry grouping in the diff view, tighter cards

  • •app/api/tailor-resume/route.ts: prompt rewritten around a primary goal of mirroring the job description's language - the AI scans required_skills, key_responsibilities, and keywords and rewrites bullets that touch those areas using the job's exact terminology (e.g. React/Node.js/PostgreSQL/Jest/Agile named where the work plausibly involved them), still capped at 2 keywords per bullet and never forced into unrelated bullets; the reason field must NAME the keyword when one was woven in ("Added PostgreSQL keyword from job description"). Each change now includes an entry field - the company or project name the bullet belongs to, as written in the resume - sanitized alongside the other fields
  • •app/(dashboard)/tailor/page.tsx: diff view now groups two levels deep - section heading, then the company/project name as a subheading above its bullets (changes without an entry render directly under the section); bullet cards' horizontal padding halved (14px 16px -> 14px 8px), vertical unchanged
Jul 6, 2026
Fix

Reverted shadcn init - restored original design system

  • •app/globals.css: restored from git HEAD - removed shadcn's oklch variable overrides (--card, --muted, --border, --accent, --radius were clobbered), the tw-animate-css and shadcn/tailwind.css imports, the sidebar/chart/popover variable sets, the .dark class block, and the @layer base overrides; all original CSS variables and app styles are back
  • •app/layout.tsx: restored from git HEAD - removed the Geist font import and cn() className on the html element
  • •package.json / package-lock.json: restored from git HEAD - removed @base-ui/react, class-variance-authority, clsx, lucide-react, tailwind-merge, tw-animate-css, and the shadcn dev dependency; node_modules pruned via npm install
  • •deleted shadcn-created files: components.json, components/ui/button.tsx, lib/utils.ts (cn helper), .mcp.json (shadcn MCP server registration), skills-lock.json and .agents/ (shadcn skill files), plus the enabledMcpjsonServers shadcn entry in .claude/settings.local.json
  • •tailwind.config.ts was never modified by shadcn (Tailwind v4 config lives in CSS) - no changes needed
Jul 4, 2026
Fix

Chrome extension - Workday multi-entry auto-add fixed; fill logic now verified by a DOM harness

  • •extension/content.js: ensureWorkdayEntries held the section element across Add clicks - Workday replaces the whole section node when a panel is added, so the entry count was polled on the detached old subtree and every Add click after the first hit a detached button (its event never reaches the live DOM). It now takes a section getter and re-resolves the section and Add button fresh on every iteration, with per-click logging. This was the remaining cause of experience/websites sections getting at most one auto-added entry
  • •extension/content.js: skills tag verification now also accepts the clicked suggestion's text, not just the kit wording (tenants render "React" for a kit skill "React.js"), preventing false "did not register" reports
  • •verification: built a jsdom harness (scratchpad, not committed) that loads the real content.js into a replica Workday My Experience page with hostile behaviors - blur re-renders replace field nodes, Add replaces the section node, skills only register via suggestion click and replace their search input, unknown skills get no suggestions, currently-work-here removes the end-date field. 19/19 assertions pass: 23 fields filled across experience (2 panels, one auto-added), education (auto-added), skills (2 tags, bogus skill skipped), websites (slots 1-3 = linkedin/portfolio/github), and a second run is a verified no-op (no duplicate panels, pills, or overwrites)
Jul 4, 2026
Fix

Chrome extension - Workday Add-button pointer clicks, restricted skills early abort, LinkedIn/Websites dedupe

  • •extension/content.js: new simulateClick helper - scrolls the element into view then dispatches the full pointerdown/mousedown/pointerup/mouseup/click sequence (Workday's React Add buttons ignore bare element.click()); ensureWorkdayEntries uses it, logs the Add button's outerHTML before clicking, waits up to 4s per click (500ms polls), and falls back to plain element.click() before giving up; findAddButton also scans shadow roots for Add buttons
  • •extension/content.js: skills - Workday's literal "No Items." suggestion row is now detected as a definitive miss (stops polling immediately instead of waiting out the 3.2s timeout), and after 3 consecutive misses with zero successes the section aborts with "skills field uses a restricted list, manual entry required" instead of attempting all 15 kit skills against a taxonomy that rejects everything
  • •extension/content.js: websites - LinkedIn is excluded from website slots whenever a dedicated LinkedIn field (e.g. socialNetworkAccounts--linkedInAccount) exists on the page, since that field is filled separately; slot order is portfolio github in that case
  • •harness (scratchpad): Add buttons now ignore bare .click() and require the pointer sequence (reproduces the "Add click had no effect" log before the fix); unknown skills render a real "No Items." row; dedicated LinkedIn field uses the real automation-id; added a RESTRICTED_SKILLS scenario asserting zero tags, the abort message, and exactly 3 attempts for 6 kit skills. Main scenario 20/20, restricted scenario 3/3
Jul 4, 2026
Fix

Chrome extension - Workday label-based field discovery for tenants without automation-ids, section DOM dumps

  • •extension/content.js: root cause of "job-title inputs = 0 / school inputs = 0" with a section found - two failure modes fixed. (1) The heading-fallback section lookup returned the first ancestor containing an Add button, which is often the header ROW holding the button but none of the panels; it now walks up to 5 levels preferring the smallest ancestor that actually contains form fields (stopping at main/[role=main]), returning the Add-button container only for truly empty sections, where counts self-heal after the first add re-resolves. (2) Tenants that render experience/education panels with no data-automation-id at all are now handled by label-based discovery: inputsByLabelIn resolves fields from visible labels (for="" association label's sibling container bounded ancestor walk); anchors fall back to /job title/ and /school|university/ labels, per-field lookups fall back to /company/, /location/, /description/ labels, currentlyWorkHere falls back to the "currently work here" label, and dates fall back to the labelled From/To containers - filling month+year spinner pairs, two generic inputs, or a single combined MM/YYYY input, whichever the tenant renders. panelsFor groups anchors into role="group" wrappers or minimal unique ancestors; ensureWorkdayEntries now takes a count function
  • •extension/content.js: diagnostics - each section now logs a field inventory (tag + automation-id/id/name/type/placeholder/aria-label for every input) and the first panel's outerHTML (15k cap); when 0 panels are found despite a section, the whole section's outerHTML is dumped so the tenant's real structure is readable straight from the console
  • •harness (scratchpad): new CUSTOM_TENANT=1 scenario - experience/education with zero automation-ids, label-wired fields, Add button inside the header row, experience pre-expanded with 2 panels, education empty; 14/14 assertions pass (label fills, dates, checkbox, auto-add via header Add, grad year in "To (Actual or Expected)", degree untouched). Standard scenario 20/20, restricted skills 3/3
Jul 4, 2026
Fix

Chrome extension - Workday spinner dates via keydown, multiselect school prompts, partial skill queries, websites fill-only

  • •extension/content.js: date spinbuttons (role="spinbutton" / dateSectionMonth|Year automation-ids) build their value from keydown events - text insertion corrupted "2024" into "0004"; new workdaySpinnerType dispatches keydown/keypress/keyup per digit with human-like delays, verifies the digits landed, and setTextValue routes spinner elements to it automatically (all date fills across experience/education inherit the fix)
  • •extension/content.js: education school on multiselect tenants (multiSelectContainer search input, placeholder="Search") is now filled as a prompt - new fillWorkdayPrompt types the school name, waits up to ~4s for suggestions, clicks the exact/contains match, and treats the "No Items." row as a definitive miss; isWorkdayPrompt routes between plain-input and prompt filling; education panels iterate in reverse and fill graduation year before school, since selecting a school consumes its search input and would shift anchor indices. Degree (button-listbox) and field of study stay blank - the kit has no degree/field-of-study data
  • •extension/content.js: skills - types only the first 3 characters as the query (some tenant taxonomies need partial matches) with a 1000ms first wait, still matching suggestions against the full skill name; restricted-list abort message updated
  • •extension/content.js: websites - no programmatic Add (this tenant's Websites Add button ignores synthetic clicks); fills however many slots the user pre-added, logs "no URL slots found - click Add manually, then re-run" when there are none, and "click Add manually and re-run for the rest" when URLs outnumber slots; LinkedIn still excluded when its dedicated field exists
  • •harness (scratchpad): spinbutton mocks corrupt execCommand insertion into the reported "0004" and only build values from keydown digits (date assertions now prove the keydown path); custom-tenant education uses a multiselect school prompt with suggestion popup + pill replacement and a Select One degree button; websites pre-expanded with 2 slots and asserted no auto-add. Standard 20/20, custom tenant 14/14, restricted skills 3/3
Jul 4, 2026
Update

Codebase-wide - replaced em/en/long dashes with regular hyphens

  • •57 files: replaced every em dash, en dash, figure dash, horizontal bar, minus sign (U+2212), and box-drawing dash (U+2500) with a regular "-" across all source, content, and config files (431 occurrences). Comments, log strings, UI copy, MDX content, and SQL affected; no functional changes. Extension harness re-verified: standard 20/20, custom tenant 14/14, restricted skills 3/3
Jul 4, 2026
Fix

Interview prep - tracker job dropdown was always empty

  • •app/(dashboard)/interview/page.tsx: the dropdown was built from an embedded applications->jobs join whose error was silently ignored, and it filtered out every application without a joined job record - which excludes all manually-added tracker entries (job_id is null for those). Now loads applications without the embed (matching the /api/applications pattern), enriches titles/companies from the jobs table in a second query, includes manual tracker entries (they start sessions via the title/company payload the generate API already accepts), dedupes by job_id or company+role, surfaces the load error in the UI instead of swallowing it, and shows "No applications in your tracker yet" when the tracker is genuinely empty. Past-session rows no longer mis-match sessions with null job_id
Jul 4, 2026
Fix

Chrome extension - Workday spinner overwrite, multiselect activation, websites manual-add notice

  • •extension/content.js: date spinbuttons are now exempt from the keep-existing-value rule - a previous fill attempt can leave corrupt values ("0", "2000") that were being skipped with "keeping existing value"; workdaySpinnerType now selects all existing content first, types each digit as keydown/keypress/keyup with a fixed 50ms gap, and fires a change event after the digits (logs "overwriting spinner value" when replacing)
  • •extension/content.js: new activatePromptInput - Workday multiselects only open their suggestion dropdown after the multiSelectContainer itself is clicked, so both fillWorkdayPrompt (education school / field of study pattern) and the skills loop now click the container (pointer sequence), wait 200ms, focus the inner input, wait 200ms, type the query character by character with 50ms delays, then wait 800ms for server-side suggestions; option matching now also accepts data-automation-id="multiSelectOption" via a shared WD_OPTION_SEL selector
  • •extension/content.js + extension/popup.js: websites - directFill now returns websitesNeedManualAdd (set when the section has no URL slots or URLs outnumber slots, since this tenant's Add button ignores programmatic clicks); the popup success message reads "Fill complete: N fields..." and appends "Note: manually click "Add Another" in the Websites section and re-run to fill your portfolio and GitHub URLs." only when the flag is set
  • •harness (scratchpad): multiselects now require a container click (not an input click) before rendering suggestions - reproducing the "No Items." failure for the old interaction; custom scenario pre-corrupts panel 0's date spinners with 0/2000 and asserts they are overwritten to 01/2025, and has a single website slot asserting the manual-add flag; standard asserts the flag stays false when slots suffice. Standard 21/21, custom tenant 17/17, restricted skills 3/3
Jul 4, 2026
Fix

Chrome extension - Workday dates via ARIA arrow-stepping, prompt search committed with Enter

  • •extension/content.js: date spinbuttons rewritten around the real widget (from the user's panel outerHTML dump: aria-valuenow/valuemin/valuemax, help text "use right and left arrows"). workdaySpinnerType now tries three verified strategies in order: (A) native value setter + beforeinput/input events, (B) ArrowUp/ArrowDown stepping - the ARIA spinbutton pattern guarantees arrow handling, reading aria-valuenow after every press for exact feedback, seeding empty spinners with one press and bailing if a press has no effect or the distance exceeds 60 steps, (C) per-digit keydowns; each strategy is verified against aria-valuenow/value before the next runs, and the log names which strategy landed
  • •extension/content.js: multiselect prompts (school, field of study, skills) - Workday's selectinput widgets only run their backend search when the query is committed with Enter; every query without it shows "No Items.". fillWorkdayPrompt and the skills loop now dispatch Enter keydown/keyup after typing, then poll for suggestions
  • •extension/content.js: selector hardening from the real tenant dump - experience anchors also match input[name="jobTitle"] / id$="--jobTitle", company matches input[name="companyName"], location input[name="location"], education anchors match id$="--school"
  • •websites: unchanged - already fills only pre-existing slots and sets the popup "click Add Another manually" note when slots are missing (verified: flag true with leftover URLs, false when slots suffice)
  • •harness (scratchpad): spinner mocks now replicate the real tenant exactly - typed digits ignored, direct value writes snap back to widget state (React controlled input), only arrows change aria-valuenow (seeding month=1/year=2026 on first press); prompt popups render only on Enter. First-run logs confirm every date fills via arrow-stepping. Standard 21/21, custom tenant 17/17, restricted skills 3/3
Jul 2, 2026
Fix

Chrome extension - kit unwrap bug caused 0-field fills (Workday and all platforms)

  • •extension/popup.js: fixed the root cause of "No fillable fields matched your profile" - /api/extension/kit returns { kit } at the top level (ok() does not wrap responses in data), but the popup read kitData.data.kit, so the content script always received an empty kit and every Tier 1 value was blank; now reads kitData.kit with a data.kit fallback. Added an explicit error message when the profile kit is genuinely empty, and surfaced content-script fill errors instead of reporting them as "no fields matched"
  • •extension/content.js: findWorkdayInputByLabel fixed - Workday labels carry data-automation-id="formLabel" so closest('[data-automation-id]') matched the label itself (which never contains the input); now resolves the label's for="" association first, then walks up to the formField container. fillWorkdayDropdown now polls up to 3s for async search results instead of a fixed 600ms wait, matches promptLeafNode options, and properly closes the popup on failure (Escape keydown+keyup + outside click) instead of leaving the "How did you hear about us" dropdown hanging open
Jul 2, 2026
Fix

Chrome extension - Workday stale-element fills, skills tag selection, website slot mapping, full fill diagnostics

  • •extension/content.js: root cause of sections not filling while simple fields worked - the WORKDAY_FIELD_MAP loop re-queries each element right before filling, but the section fillers captured panel/input references up front; Workday re-renders React subtrees on every blur (which our typing dispatches), detaching those references so focus() no-ops and execCommand typed into nothing while still being counted as filled. All section fillers (experience, education, skills, websites) now re-query the section, panel, and field fresh at each individual fill. workdayTypeValue now verifies the element is attached and actually holds focus before typing, and verifies the value stuck afterward - setTextValue no longer counts or marks fields whose value did not land
  • •extension/content.js: skills rewritten as a strict tag loop - type one skill poll up to 3.2s for the suggestion dropdown click the matching suggestion poll up to 2.4s until it registers as a tag next skill; the search input is re-queried per skill (it detaches after each tag is added); skills with no suggestion are cleared and skipped
  • •extension/content.js: websites section fills one URL per slot in fixed order linkedin portfolio github, re-querying slot inputs before each fill, deduping against URLs already on the page, and auto-adding slots via Add Another when fewer slots than URLs
  • •extension/content.js: DEBUG flag enabled with full fill diagnostics - logs kit fields with data, each section element found (or the page's actual data-automation-id values matching the section when lookup misses), panel count vs kit entries, and per field: the exact element targeted, the value tried, and whether it stuck or why it was skipped (not found / no kit value / hidden / existing value / focus stolen / value did not stick); collectPanels now prefers Workday's role="group" entry wrappers
Jul 1, 2026
Update

Chrome extension auto-fill overhauled - two-tier fill, zero hallucination policy

  • •extension/content.js: complete rewrite. Platform detection (Workday / Greenhouse / Lever / generic); Tier 1 direct fill maps name, email, phone, LinkedIn, GitHub, portfolio, location, current company/title straight from the kit with priority-ordered field matching (name/id/automation-id label placeholder aria-label); fields with no kit data are left blank, never guessed. Workday fills via execCommand character typing with 20-50ms delays and blur per field; Greenhouse uses nativeSetter + React Select menus with __remixContext option extraction and demographic dropdowns defaulted to "I don't wish to answer"; Lever fills name/org/urls[LinkedIn|GitHub|Portfolio], country select by name, label-click checkboxes, and only decline options in the #countrySurvey demographic pass. Removed hardcoded per-job Greenhouse question IDs, removed Lever survey fabricated defaults (age 21-29, Bachelor), removed pronoun "Use name only" default (pronouns now only fill if kit.pronouns is set). "How did you hear about us" always resolves to "Job Board" or stays blank. Consent checkboxes checked; demographic checkboxes never touched. Fill count reports only fields actually changed.
  • •extension/popup.js: orchestrates the two-tier flow - DIRECT_FILL first (no AI), then EXTRACT_QUESTIONS (open-ended only), then /api/extension/ai-fill, then FILL_ANSWERS; total = sum of real fills; AI failure no longer discards Tier 1 fills; removed console logging
  • •app/api/extension/ai-fill/route.ts: deterministic server guards resolve "how did you hear" (Job Board or null), demographics (decline option or null), and any Tier 1-shaped label (null) before the AI is called; rewritten system prompt forbids inventing companies/organizations/websites/referral sources and requires null for anything not in the profile; post-AI validation snaps dropdown answers to verbatim option text or drops them, and re-nulls demographic/Tier 1 answers; deterministic answers still returned if the AI call fails
  • •app/api/extension/kit/route.ts: kit now includes skills[], education[] (derived from university + graduation_year), location (from optional profiles.location column, gracefully empty if the migration has not run), and pronouns: null; referralSource changed from "LinkedIn" to "Job Board"; removed the speculative Gemini call that generated generic application answers on every kit fetch; removed console.error debug logging
  • •optional DB column: ALTER TABLE profiles ADD COLUMN IF NOT EXISTS location text;
Jun 9, 2026
Update

Cerebras model updated to gpt-oss-120b

  • •lib/ai.ts: updated Cerebras model string from llama3.1-70b to gpt-oss-120b in generateTextCerebras
May 25, 2026
Fix

PDF printBackground, spacing, and tailor tech stack preservation

  • •lib/generatePdf.ts: printBackground changed falsetrue; -webkit-print-color-adjust and print-color-adjust: exact added to body; h2 margin tightened to 5px 0 2px; p margin changed to 0 0 2px; letter-spacing on h2 changed to 0.05em
  • •app/api/tailor-resume/route.ts: rule 15 added - tech stack line under project headings must be preserved exactly, not removed or shortened
May 25, 2026
Update

Profile form - generic placeholders and month picker date inputs

  • •components/ProfileForm.tsx: skills placeholder changed to "e.g. Project Management, Data Analysis, Public Speaking"; target roles placeholder changed to "e.g. Marketing Manager, Financial Analyst, Product Designer"; experience job title placeholder changed from "Software Engineer" to "Account Manager"
  • •components/ProfileForm.tsx: experience start_date and end_date inputs changed from type="text" to type="month"; styled with background #1a1a1a, border 1px solid #2a2a2a, color #f2f2f2, borderRadius 6px, padding 8px 12px, colorScheme dark; end_date remains disabled and cleared when Current role is checked
May 25, 2026
Fix

Networking - inline delete confirmation, remove AI-generated contact fallback

  • •app/api/find-contacts/route.ts: removed generateFallbackContacts AI fallback entirely; removed ai_generated field from Contact interface; search now cascades LinkedIn via Jina Google via Jina only; AI extraction prompt explicitly forbids fabricating contacts; returns empty array when no real contacts found
  • •app/(dashboard)/networking/page.tsx: removed ai_generated field from Contact interface; removed AI-suggested amber banner; removed AI badge from ContactCard; replaced window.confirm delete dialog with inline "Delete? Yes / Cancel" confirmation using confirmingDeleteId state
May 25, 2026
Update

Networking - Apollo People Search replaces Jina/AI contact scraping

  • •app/api/find-contacts/route.ts: replaced Jina + AI extraction pipeline with a direct POST to Apollo api/v1/mixed_people/api_search; sends person_titles array (recruiter, talent acquisition, technical recruiter, hiring manager, engineering manager, hr manager, + optional role param) and q_organization_name; maps response to { id, name (first_name + last_name_obfuscated), title, company, linkedin (search URL), hasEmail, source: 'apollo' }; returns { contacts, total, message }; removed Jina fetch, generateJSON, buildUserContext, and all scraping logic
  • •app/(dashboard)/networking/page.tsx: Contact interface updated to Apollo shape (id, linkedin, hasEmail, source); ContactCard updated - "Search on LinkedIn ↗" link always shown (search URL), "Has email on file" greyed badge with tooltip shown when hasEmail is true; prefillFromContact uses contact.linkedin instead of contact.linkedin_url
  • •.env.local: APOLLO_API_KEY= placeholder added (fill in master key from app.apollo.io/settings/integrations/api)
  • •new env var required: APOLLO_API_KEY (master key - the people search endpoint requires it)
May 22, 2026
Update

parse-profile extracts work experience and projects from resume

  • •app/api/parse-profile/route.ts: prompt extended to extract experience (company, title, start_date, end_date, current, location, bullets) and projects (name, description, url, tech_stack, bullets); ParsedProfile interface updated with WorkExperience and Project types; upsert payload always writes experience and projects arrays; select returns both new columns
May 22, 2026
Feature

Experience section on profile page - editable cards with inline save

  • •components/ProfileForm.tsx: WorkExperience interface exported; experience state initialized from profile; Experience section added below Target Roles with per-entry dark cards (title, company, start/end date, current-role checkbox, location, bullet list with add/remove); experience included in handleSubmit and handleUploadComplete sync
  • •app/(dashboard)/actions.ts: Profile interface gains experience field; updateProfile upsert includes experience
  • •app/(dashboard)/profile/page.tsx: select query includes experience column
May 22, 2026
Feature

Projects section on profile page - editable cards with tag-based tech stack

  • •components/ProfileForm.tsx: Project interface exported; projects state + per-card projectTechInputs state; Projects section added below Experience with per-entry dark cards (name, URL, description, tech stack tag input matching skills UX, bullet list with add/remove, × delete); projects included in handleSubmit and handleUploadComplete sync
  • •app/(dashboard)/actions.ts: Profile interface gains projects field; updateProfile upsert includes projects
  • •app/(dashboard)/profile/page.tsx: select query includes projects column
May 22, 2026
Update

Experience and projects injected into all AI prompts

  • •lib/userContext.ts: WorkExperienceEntry and ProjectEntry interfaces exported; experience and projects added to UserContext interface; fetchProfile select includes both columns; buildUserContext return includes both; formatContextForPrompt renders WORK EXPERIENCE and PROJECTS sections above resume snippet
  • •app/api/extension/kit/route.ts: Kit interface gains experience/projects; profile select includes both; returned kit includes both arrays
  • •app/api/extension/ai-fill/route.ts: system prompt instructs AI to draw from WORK EXPERIENCE and PROJECTS when answering open-ended questions
  • •app/api/tailor-resume/route.ts: userPrompt instructs AI to use structured work history for tailored bullet points and surface relevant projects when they match required_skills
  • •app/api/interview-prep/generate/route.ts: expSummary and projSummary built from ctx.experience/projects; behavioral_questions prompt grounded in candidate's actual companies and projects using STAR framing
  • •app/api/generate-outreach/route.ts: mostRecentExp and relevantProject selected from ctx; message instructions for all three types reference candidate-specific background details
May 22, 2026
Fix

PDF generation - no browser headers, HTML-preserved formatting, 1-page layout

  • •app/api/parse-resume/route.ts: after text extraction, calls Groq to convert resume to structured HTML (h1 name, h2 sections, ul/li bullets, a href links, strong/em); extractBodyContent() strips any accidental html/head/body wrappers; HTML saved to profiles.resume_html; response includes resumeHtml field
  • •app/api/tailor-resume/route.ts: parallel fetch adds profiles.resume_html; when HTML is available, uses surgical HTML update prompt (only modifies li text and summary, preserves all tags/hrefs/structure, SKILL_GAPS appended after HTML); falls back to text generation when HTML is absent
  • •app/(dashboard)/tailor/page.tsx: downloadAsPDF injects @page { margin: 0 } + body { margin: 0.5in 0.6in } to suppress browser print headers; renders HTML content directly when body is HTML, wraps plain text in pre; isHtmlContent() and stripHtml() helpers added; copyToClipboard strips HTML tags for plain-text paste; resume preview uses dangerouslySetInnerHTML for HTML content, pre for text
  • •new DB column required: ALTER TABLE profiles ADD COLUMN IF NOT EXISTS resume_html text;
May 22, 2026
Feature

Cover letter - proper letter format + personal template support

  • •app/api/generate-cover-letter/route.ts: parallel fetch adds profiles (full_name, phone, email, linkedin_url, portfolio_url, cover_letter_template); builds letterHeader from profile contact info + today's date; two prompt paths - if cover_letter_template exists, adapts template keeping user's voice; else generates full letter with header/date/Dear/3-4 paragraphs/Sincerely block; key_responsibilities added to job select; output token limit raised to 1500
  • •components/ProfileForm.tsx: cover_letter_template added to Profile interface, state, useEffect sync, handleSubmit payload; Cover Letter Template section added before Save button - labeled textarea with placeholder hints, {company}/{role}/{hiring_manager} placeholder guide in helper text, Clear template button
  • •app/(dashboard)/actions.ts: Profile interface and upsert include cover_letter_template
  • •app/(dashboard)/profile/page.tsx: select query includes cover_letter_template
  • •new DB column required: ALTER TABLE profiles ADD COLUMN IF NOT EXISTS cover_letter_template text;
May 22, 2026
Feature

Puppeteer PDF generation - pixel-perfect server-side resume PDF via Chromium

  • •lib/generatePdf.ts created: generateResumeBuffer(htmlBody) uses @sparticuz/chromium-min + puppeteer-core to render HTML to a Letter-format PDF; CHROMIUM_EXECUTABLE_PATH env var selects local Chrome path (dev) or CDN tarball URL (prod); RESUME_CSS template uses Times New Roman 10.5pt, h1/h2/h3 hierarchy, ul/li with correct indentation, real a href links; returns null gracefully when env var is unset
  • •app/api/tailor-resume/route.ts: export const maxDuration = 120; after saving document, calls generateResumeBuffer when content is HTML; uploads PDF buffer to Supabase Storage bucket pdfs at tailored-resumes/{userId}/{docId}.pdf; stores public URL in documents.pdf_url via update; returns pdfUrl in response
  • •app/api/parse-resume/route.ts: for non-DOCX uploads, stores fileUrl in profiles.resume_pdf_path alongside resume_html update
  • •app/(dashboard)/tailor/page.tsx: Doc interface gains pdf_url field; tailorResume() captures pdfUrl from API response; Download PDF button renders as anchor opening pdf_url in new tab when available, falls back to existing print path otherwise
  • •new DB columns required: ALTER TABLE profiles ADD COLUMN IF NOT EXISTS resume_pdf_path text; ALTER TABLE documents ADD COLUMN IF NOT EXISTS pdf_url text;
  • •new npm packages: puppeteer-core @sparticuz/chromium-min (already installed)
  • •new env var required: CHROMIUM_EXECUTABLE_PATH (local Chrome path for dev; CDN tarball URL for Vercel prod)
May 22, 2026
Fix

Surgical tailor prompt + professional PDF CSS template

  • •app/api/tailor-resume/route.ts: HTML path system prompt rewritten with 12 explicit absolute rules - only li text may change; section headings, name, contact info, job titles, company names, dates, href attributes all frozen; bullet count per section locked; 20-word bullet cap; filler phrases (demonstrating my ability to, showcasing my, leveraging my) banned; direct tone preserved
  • •lib/generatePdf.ts: RESUME_CSS replaced - @page margin 0.55in 0.65in; body Arial/Helvetica 10.5px; h1/h2 Georgia serif (18px centered bold name, 11px uppercase section headings with 1px solid border); h1+p contact line 10px centered; ul padding-left 12px; li 10.5px line-height 1.4 margin 2px; a links black underline
May 22, 2026
Fix

Tailor metrics preservation + PDF bullet rendering + hyperlink retry

  • •app/api/tailor-resume/route.ts: two new absolute rules added to HTML path prompt - rule 11 preserves all specific numbers, percentages, dollar amounts, and metrics verbatim; rule 12 requires every rewritten bullet to retain every quantitative fact from the original
  • •lib/generatePdf.ts: CSS updated - list-style-type: disc on ul and li so bullets render as disc not plain text; ul margin: 0 padding-left: 18px; li margin: 2px 0 line-height: 1.4; p margin: 2px 0 font-size: 10.5px; h1 font-size 18px16px; h2 margin tightened to 8px 0 3px; body line-height 1.41.3
  • •app/api/parse-resume/route.ts: after extractBodyContent, counts <a tags in output; if zero links found and profile has linkedin_url or github_url, reruns AI with an explicit prompt that hard-codes the exact href values in the contact line instruction; logs retry link count
May 22, 2026
Fix

PDF spacing, bullet disc, metrics preservation, keyword stuffing, project link injection

  • •lib/generatePdf.ts: h1 15px margin 0 0 2px; h2 10.5px bold uppercase border-bottom margin 6px 0 2px; p margin 1px 0; ul margin 0 padding-left 16px; li list-style-type disc margin 1px 0 line-height 1.35; body line-height 1.3 - all tightened to fit one page
  • •app/api/tailor-resume/route.ts: rule 11 preserves all metrics verbatim including ~20% and R² notation; rule 12 requires every quantitative fact to survive the rewrite; rule 13 bans forcing keywords into unrelated bullets; rule 14 caps keywords at 1-2 per bullet
  • •app/api/parse-resume/route.ts: ProfileLinks gains projects field; profiles select includes projects; builds projectLinksBlock from projects where url is non-null; prompt injects PROJECT LINKS section with exact URLs; two new rules instruct AI to match project links by name and never fabricate URLs; retry path also injects project links block
May 18, 2026
Feature

AI-powered form fill via Cerebras

  • •app/api/extension/ai-fill/route.ts created: POST endpoint with token auth; accepts extracted form questions, builds user context via buildUserContext, calls generateJSONCerebras to generate field answers, returns { fields: [{ label, answer }] }
  • •extension/content.js: added getLabel() for human-readable label extraction, fill() helper, extractFormQuestions() to scrape all form fields, fillWithAIAnswers() to match AI answers back to DOM elements (native inputs + custom comboboxes); updated message listener for EXTRACT_QUESTIONS and FILL_ANSWERS
  • •extension/popup.js: rearchitected handleFill to use four-step flow - token validation, EXTRACT_QUESTIONS, POST ai-fill, FILL_ANSWERS; replaced sendFill with generic sendMessage helper
May 18, 2026
Update

.docx resume upload support + graduation year range fix

  • •components/ResumeUpload.tsx: accept .pdf/.docx/.doc; validate by extension and MIME type; set correct content-type on Supabase storage upload; pass fileName to parse-resume; updated button label and error message
  • •app/api/parse-resume/route.ts: branches on file extension / Content-Type header - uses mammoth for DOCX/DOC, pdf-parse for PDF; removed pageCount from response (not used downstream)
  • •components/ProfileForm.tsx: graduation year range now 1970 to currentYear+7, generated dynamically, shown in descending order; default and sync logic updated from hardcoded 2026 to currentYear
  • •new package: mammoth
May 18, 2026
Fix

Extension - phone dropdown filter, proximity panel detection, and open-ended question fill

  • •extension/content.js fillCustomDropdowns: phone/country skip now checks trigger className and aria-label for "phone"/"country"/"flag" in addition to container tel input - catches dropdowns where the phone field is not a sibling
  • •extension/content.js fillCustomDropdowns: adds options.length > 50 guard after panel opens - dispatches Escape and skips when the open panel is a country/state list
  • •extension/content.js fillCustomDropdowns: open panel detection replaced with proximity-based search - finds allPanels, picks the one whose top is within 200px of the trigger's bottom, falls back to last panel; prevents a distant open panel from being used for the wrong trigger
  • •extension/content.js textarea loop: open-ended question fill added - if no cover letter key matches, checks label for "excites you", "career goals", "why do you want", "why are you interested", "tell us about yourself", "what would you contribute", "long-term", "passion", "motivated" and fills from kit.applicationQuestions.excited or .goals
  • •app/api/extension/kit/route.ts: Kit interface gains applicationQuestions field
  • •app/api/extension/kit/route.ts: job query updated to select title, company, key_responsibilities, company_description
  • •app/api/extension/kit/route.ts: after fetching cover letter and saved questions, generates applicationQuestions via generateJSON with 15s timeout - excited, goals, why_company, contribution - non-critical, fails silently
May 17, 2026
Feature

Strategy report - data-driven job search insights

  • •app/api/strategy-feedback/route.ts created: GET endpoint with auth, rate limit (2/month), 15-app minimum gate, 5-app cache invalidation threshold, analytics from applications + ats_scores tables (response rate by role, ATS correlation by outcome, top 5 missing keywords), Groq AI generation, upsert to strategy_feedback table on user_id conflict
  • •app/(dashboard)/strategy/page.tsx created: server component with locked state (progress bar, 15-app target) for insufficient data, unlocked state renders StrategyReport client component
  • •app/(dashboard)/strategy/StrategyReport.tsx created: client component, fetches GET /api/strategy-feedback on mount, renders key insights cards, focus/avoid role badges, ATS correlation callout, numbered next steps, skill gap amber badges with Add to profile link, last-updated footer with Refresh button (?force=true)
  • •components/Sidebar.tsx updated: Strategy nav item changed from comingSoon to locked (shows lock icon when applicationCount < 15, navigable and unlocked when >= 15)
May 17, 2026
Update

Job feed "Tailor resume" pre-fill + secondary Groq key for job scoring

  • •app/(dashboard)/jobs/page.tsx updated: "Tailor resume" Link changed from ?url= to ?prefill= so the tailor page receives the Adzuna redirect URL as a prefill param
  • •app/(dashboard)/tailor/page.tsx updated: useEffect on mount reads jobId or prefill query params; parseJob() accepts optional overrideUrl to bypass state; goToStep2() accepts optional jobOverride to bypass state; loadJobById() fetches from jobs table and auto-navigates to step 2; autoLoading banner shows while fetching by jobId; ?prefill auto-triggers parse on mount; ?jobId skips parse and jumps to step 2
  • •lib/ai.ts updated: callGroq() refactored into callGroqWithKey(apiKey, ...) shared implementation; callGroq() and callGroqSecondary() are thin wrappers; added generateTextSecondary() and generateJSONSecondary() that use GROQ_API_KEY_SECONDARY
  • •app/api/discover-jobs/route.ts updated: generateJSON replaced with generateJSONSecondary so job feed ATS scoring uses the secondary key and does not eat into the main key rate limit
  • •.env.local updated: GROQ_API_KEY_SECONDARY= added (fill in key)
May 17, 2026
Feature

Stripe billing - checkout, webhook, customer portal, and billing page

  • •lib/stripe.ts created: singleton getStripe() helper, SERVER-SIDE ONLY
  • •app/api/stripe/checkout/route.ts created: POST, requireAuth, creates Stripe Checkout Session (mode=subscription, success/cancel URLs, subscription_data.metadata.supabase_user_id)
  • •app/api/stripe/webhook/route.ts created: POST, no auth (Stripe calls it), verifies signature via constructEvent, handles customer.subscription.created/updated (upsert plan=pro) and customer.subscription.deleted (plan=free, status=cancelled), returns 200 immediately
  • •app/api/stripe/portal/route.ts created: POST, requireAuth, looks up stripe_customer_id from subscriptions, creates Stripe Customer Portal session, returns redirect URL
  • •app/(dashboard)/billing/page.tsx created: server component showing current plan badge, renewal date, upgrade/manage buttons, per-feature usage bars (tailoring, cover letters, auto-apply, strategy, networking, interview)
  • •components/BillingActions.tsx created: 'use client' UpgradeButton and ManageButton, each POSTs to the respective API route and redirects to the Stripe-hosted page
  • •components/Sidebar.tsx updated: added Billing nav item after Settings
  • •new env vars required: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, STRIPE_PRO_PRICE_ID, NEXT_PUBLIC_APP_URL
  • •new DB columns required: ALTER TABLE subscriptions ADD COLUMN IF NOT EXISTS stripe_customer_id TEXT, ADD COLUMN IF NOT EXISTS current_period_end TIMESTAMPTZ, ADD COLUMN IF NOT EXISTS cancelled_at TIMESTAMPTZ;
May 17, 2026
Feature

Networking - contact discovery, outreach message generation, and tracker

  • •app/api/find-contacts/route.ts created: POST { company, role?, jobId? }, requireAuth, checkLimit('networking'), 5/day hard cap via route_logs; scrapes LinkedIn via Jina.ai with authwall detection, falls back to Google site:linkedin.com/in search; generateJSON extracts up to 8 contacts; optional job-relevance scoring if jobId provided
  • •app/api/generate-outreach/route.ts created: POST { contactName, contactTitle, contactCompany, messageType, contactLinkedinUrl?, jobId? }, requireAuth, checkLimit('networking'), buildUserContext + optional job context, generates connection_request (max 280 chars), cold_email (max 150 words), or follow_up (max 80 words) via Groq; saves to outreach_messages; tracks event
  • •app/(dashboard)/networking/page.tsx created: 3-section client page - (1) Find Contacts: company+role search populates contact cards with pre-fill buttons; (2) Generate Message: contact fields + message type + optional job selector, editable textarea with char counter for connection_request, Copy and Save buttons; (3) Outreach Tracker: table of saved messages with sent toggle, Follow-up action (pre-fills section 2 as follow_up type), and Delete
  • •components/Sidebar.tsx updated: Networking added to NAV_ITEMS between Strategy and Profile
May 17, 2026
Fix

Networking contact search - URL encoding, logging, and AI fallback

  • •app/api/find-contacts/route.ts: encodeURIComponent the full target URL before appending to r.jina.ai/ so LinkedIn query strings are routed correctly; added console.error logging of Jina response length and first 500 chars for debugging; added CAPTCHA detection to isUsableMarkdown; AI fallback now fires when contacts array is empty (not just when markdown is empty) - generates 3 realistic AI-suggested contacts with ai_generated:true; job-relevance scoring skipped for ai_generated source
  • •app/(dashboard)/networking/page.tsx: Contact interface gains ai_generated field; contactSource state tracks the search source from the API; ContactCard shows "AI suggested · verify manually" amber badge when ai_generated is true; amber disclaimer banner shown above contact cards when source is ai_generated
May 17, 2026
Feature

Cerebras AI for interview prep + full UI restyle

  • •lib/ai.ts: added generateTextCerebras and generateJSONCerebras using Cerebras OpenAI-compatible API (https://api.cerebras.ai/v1, model gpt-oss-120b) - separate rate limit from Groq
  • •app/api/interview-prep/generate/route.ts: switched from generateJSON (Groq) to generateJSONCerebras; removed forceSecondary logic; fixed JSON prompt format to use explicit object template; typed result as InterviewQuestions instead of any
  • •app/api/interview-prep/assess/route.ts: switched to generateJSONCerebras; fixed JSON prompt format; typed assessment as Assessment interface instead of any; simplified feedback keying (uses question text directly, consistent with client); removed qHash
  • •app/(dashboard)/interview/page.tsx: full restyle - landing page with Start session card and past sessions table (Company/Date/Progress columns); practice session with pill category tabs, redesigned question cards (question left, buttons right), Voice/Type toggle pills, "I don't know" link, typed textarea, assessment grid with Strengths/Improve columns and filler word badges
  • •.env.local: added CEREBRAS_API_KEY placeholder
  • •new env var required: CEREBRAS_API_KEY
May 17, 2026
Feature

Chrome Extension for job application auto-fill

  • •extension/manifest.json: Manifest V3 extension - activeTab, storage, host_permissions for all URLs
  • •extension/popup.html + popup.js: 280px popup with connect/main views; token saved to chrome.storage.local; fetches kit from /api/extension/kit; sends VANTAGE_FILL message to content script; shows filled count
  • •extension/content.js: fillForm() with nativeSetter pattern (React/Vue/Angular compatible); queryShadow() for Workday Shadow DOM; dispatches input/change/blur events; matches first name, last name, full name, email, phone, LinkedIn, cover letter, and application questions; never auto-submits
  • •extension/background.js: minimal service worker
  • •app/api/extension/kit/route.ts: GET route; Bearer token auth via service role client matching profiles.extension_token; returns firstName, lastName, fullName, email, phone, linkedin, coverLetter, answers; fetches cover letter and application_questions when job URL matches
  • •app/api/extension/token/route.ts: POST route; requireAuth; generates crypto.randomUUID() token; stores in profiles.extension_token + extension_token_created_at
  • •components/ExtensionTokenSection.tsx: client component on profile page; generate/regenerate token; one-time display of new token with copy button; masked display of existing token
  • •components/ProfileForm.tsx: phone field added (between full name and university); phone saved via updateProfile
  • •app/(dashboard)/profile/page.tsx: imports ExtensionTokenSection; fetches extension_token and extension_token_created_at from profiles; passes to component
  • •app/(dashboard)/actions.ts: Profile interface and updateProfile upsert include phone
  • •lib/userContext.ts: UserContext interface and fetchProfile query include phone
  • •SQL migration required: ALTER TABLE profiles ADD COLUMN IF NOT EXISTS extension_token text; ADD COLUMN IF NOT EXISTS extension_token_created_at timestamptz; ADD COLUMN IF NOT EXISTS phone text;
May 17, 2026
Update

Extension - custom div-based dropdown handler for Greenhouse and Lever

  • •extension/content.js: added async fillCustomDropdowns(kit) that finds role="combobox"/role="listbox"/class*="select__control" and similar triggers; clicks to open, waits 300ms, finds role="option" elements, clicks the matching option; handles work authorization, sponsorship, non-compete, compensation, referral source, and location questions
  • •extension/content.js: fillForm() converted to async, calls await fillCustomDropdowns(kit) after the Greenhouse second pass, returns { filled } object instead of a bare number
  • •extension/content.js: chrome.runtime.onMessage listener updated to return true and resolve with fillForm().then(sendResponse) so the async response is delivered correctly
May 17, 2026
Fix

Extension - fix dropdown bleeding and Lever location select

  • •extension/content.js fillCustomDropdowns: skips triggers inside phone fields (country-code pickers) via container querySelector for tel/phone inputs
  • •extension/content.js fillCustomDropdowns: dispatches Escape + 150ms pause before each trigger click to close any previously open panel
  • •extension/content.js fillCustomDropdowns: wait increased from 300ms to 500ms after opening a trigger before searching for options
  • •extension/content.js fillCustomDropdowns: option search scoped to the open panel (select__menu/dropdown-menu/listbox/SelectMenu) instead of document.body, preventing 244 country options from bleeding across all dropdowns
  • •extension/content.js fillCustomDropdowns: after clicking a matching option, waits 400ms then dispatches Escape to cleanly close the panel
  • •extension/content.js fillCustomDropdowns: Lever native-select second pass added at end of function - queries all document selects, matches label containing "location"/"where", fills via value + change event
May 16, 2026
Feature

Auto pre-fill profile from uploaded resume

Resume upload now extracts and pre-fills profile fields using AI parsing.

  • •app/api/parse-profile/route.ts created: POST route that takes rawText, calls generateJSON to extract full_name, university, graduation_year, years_experience, linkedin_url, skills, target_roles, then upserts only non-null fields into profiles table (preserves any existing values)
  • •components/ResumeUpload.tsx updated: after successful upload, fires a fire-and-forget POST to /api/parse-profile; on success calls router.refresh() so the profile page re-fetches updated data
  • •components/ProfileForm.tsx updated: added useEffect that syncs local field state when initialProfile prop changes, pre-filling only empty/default fields without overwriting user edits
May 16, 2026
Feature

Resume upload embedded in profile form with onboarding banner

Resume upload moved into the profile form with an onboarding welcome banner for new users.

  • •components/ResumeUpload.tsx updated: stripped standalone card wrapper and heading, added onUploadComplete prop that fires with rawText after a successful upload, removed internal parse-profile and router.refresh logic
  • •components/ProfileForm.tsx updated: renamed prop initialProfile to initialData, added isNew prop, welcome banner inside card when isNew, Resume section always rendered above form fields with handleUploadComplete that calls /api/parse-profile and updates empty fields directly without a page refresh
  • •app/(dashboard)/profile/page.tsx updated: removed standalone ResumeUpload section and old blue banner, passes initialData and isNew to ProfileForm
May 16, 2026
Feature

Application detail modal with ATS scores and tailored documents

Clickable application rows open a detail modal showing ATS scores, keywords, and document previews.

  • •app/api/applications/[id]/details/route.ts created: GET route that fetches the application, then uses Promise.all to fetch related documents and the latest ATS score, returns application, documents, and atsScore
  • •components/ApplicationDetailModal.tsx created: modal overlay with 800px panel; fetches details on mount; shows skeleton while loading; header with company, role, status badge, applied date; ATS section with overall_score, keyword_score, missing_keywords pills, suggestions list; tabbed document viewer for Tailored Resume and Cover Letter with Copy and Download PDF buttons; Escape key and overlay click to close
  • •app/(dashboard)/tracker/page.tsx updated: added selectedAppId state; each application row is now clickable; ApplicationDetailModal rendered at component root when selectedAppId is set
May 16, 2026
Fix

Status dropdown renders as portal to fix overflow clipping in tracker table

Status dropdown now renders in a portal to prevent clipping inside overflow containers.

  • •app/(dashboard)/tracker/page.tsx: replaced inline absolute-positioned status dropdown with a createPortal render into document.body; button onClick now calls getBoundingClientRect() to calculate absolute page position stored in dropdownPos state; dropdownPortalRef attached to the portal div for outside-click detection
May 16, 2026
Feature

Settings panel with account, appearance, resume, and danger zone sections

Settings page with account info, theme switching, resume upload, and account deletion.

  • •components/Sidebar.tsx updated: added Settings link to nav after Profile
  • •app/(dashboard)/settings/page.tsx created: server component that fetches user and profile, renders SettingsClient with userId, email, fullName
  • •components/SettingsClient.tsx created: 'use client' component with four sections — Account (email read-only + full_name save via /api/profile/update), Appearance (Dark/Light theme pills updating data-theme attribute and localStorage), Resume (inline ResumeUpload panel toggled by button, shows success message on complete), Danger Zone (delete button opens DeleteModal with type-DELETE confirmation, calls /api/account/delete then redirects to /login)
  • •app/api/profile/update/route.ts created: POST route, requireAuth, validateBody with full_name, updates profiles.full_name for the authenticated user
  • •app/api/account/delete/route.ts created: POST route, requireAuth, signs out via SSR client, deletes user via service-role client, returns ok with deleted true
  • •No new database tables or columns
  • •No new npm packages
May 16, 2026
Feature

Documents page with filter tabs, expandable rows, copy and download

Dedicated documents page with type filters, expandable content previews, and PDF download.

  • •app/(dashboard)/documents/page.tsx created: server component that fetches all documents with jobs join, passes to DocumentsClient
  • •components/DocumentsClient.tsx created: filter tabs (All / Tailored Resume / Cover Letter), each document row shows type badge + job title/company + date + Copy and Download PDF buttons, clicking row expands inline pre block with full content; EmptyState when no documents
May 16, 2026
Feature

Answer application form questions with AI

AI-powered question answering for job application forms, saved per application.

  • •app/api/answer-question/route.ts created: POST with jobId, applicationId, and question — requireAuth, validateBody, checkLimit, fetches job (ownership verified), buildUserContext, generateText with grounded-in-resume system prompt, saves to application_questions table, tracks event, returns saved question row
  • •app/api/answer-question/route.ts GET with applicationId param — returns all saved questions for the given applicationId belonging to the authenticated user, ordered by created_at ASC
  • •No new database tables — uses existing application_questions table
  • •No new npm packages
May 16, 2026
Feature

Apply Prep page — application kit, question answerer, submit checklist

All-in-one apply prep page with document kit, AI question answering, and a submit checklist.

  • •app/(dashboard)/apply/[jobId]/page.tsx created: 'use client' page; fetches job, documents, ats_score, and application via Supabase browser client in parallel on mount; loads existing application_questions via GET /api/answer-question
  • •Section 1 (Application Kit): 3-column grid showing latest tailored resume preview, cover letter preview, and ATS ScoreBadge; each card shows a link to /tailor when the corresponding asset is missing
  • •Section 2 (Question Answerer): textarea + Generate answer button; generated answers rendered in editable textareas with 500ms debounced PATCH for user_edited_answer; Saved indicator and Copy button per answer
  • •Section 3 (Submit Checklist): 5 manual checkboxes; when all checked, shows Mark as Applied button that updates status to applied and applied_date to today
  • •app/api/answer-question/[id]/route.ts created: PATCH route updating user_edited_answer on application_questions, ownership verified via user_id
  • •app/api/applications/[id]/route.ts updated: added applied_date to the list of PATCH-able fields
  • •No new database tables or columns
  • •No new npm packages
May 16, 2026
Feature

Job Feed — Adzuna-powered personalized job discovery with AI relevance scoring

Personalized job feed powered by Adzuna API with AI relevance scoring and save/dismiss controls.

  • •.env.local updated: added ADZUNA_APP_ID and ADZUNA_API_KEY placeholders
  • •app/api/discover-jobs/route.ts created: GET with optional refresh param; 24h cache check against job_feed_items; fetches from Adzuna for up to 3 target_roles (10 results each); deduplicates by external_job_id; scores all jobs in parallel with generateJSON; upserts to job_feed_items preserving is_saved and is_dismissed; returns non-dismissed items sorted by relevance_score DESC
  • •app/api/job-feed/[id]/route.ts created: PATCH with is_saved and is_dismissed fields, with ownership check
  • •app/(dashboard)/jobs/page.tsx created: 'use client'; loads on mount via GET /api/discover-jobs; Refresh button; employment_type filter dropdown; 2-col desktop / 1-col mobile grid; each card shows title, company, location, ScoreBadge, employment_type badge, AI reason, View job link, Tailor resume link, heart save toggle, dismiss button; saved items pinned to top
  • •components/Sidebar.tsx updated: removed comingSoon from Jobs nav item
May 15, 2026
Feature

App landing page at / with full marketing sections

Built the Vantage landing page with hero, problem, how-it-works, features, stats, pricing, and final CTA sections - fully static, inherits marketing layout.

What's included

  • •Full landing page at / covering all seven marketing sections.
  • •Hero: outcome-focused headline based on real validation data (75% of users got less than 5% response rate), dual CTAs, social proof line.
  • •Problem section: three data-backed pain point cards.
  • •How it works: three-step flow with connector line on desktop, collapses vertically on mobile.
  • •Features: 4-column grid of all eight product features, 2-column on tablet, single column on mobile.
  • •Stats: three validated data points rendered as large number cards.
  • •Pricing: two-column Free vs Pro layout at max-width 560px, Pro card uses --pro-border CSS variable for theme-aware highlight.
  • •Final CTA: centered section with primary button.

Files

  • •app/(marketing)/page.tsx created: static server component, all seven sections, no interactivity needed
  • •app/page.tsx deleted: removed conflicting boilerplate root page (route group page takes precedence)
  • •app/globals.css updated: added --pro-border CSS variable, scroll-behavior: smooth, and all .lp-* responsive utility classes
May 15, 2026
Feature

Shared marketing layout with nav, footer, and dark/light mode toggle

Added a sticky frosted-glass nav, responsive footer, and persistent dark/light mode toggle across all public marketing pages.

What's included

  • •Shared app/(marketing)/layout.tsx wraps all public pages (blog, changelog, landing page) with a consistent nav and footer.
  • •Sticky nav with frosted-glass background (blur(8px)), center links, Sign in, Get started button, and theme toggle.
  • •Dark/light mode toggle persists to localStorage under vantage-theme. Inline script in root <head> applies theme before first paint to prevent flash.
  • •Mobile responsive: center links and controls collapse into a slide-down drawer at 768px. Drawer traps focus and closes on Escape or outside tap.
  • •Footer with wordmark, tagline, four link columns (Product, Resources, Company, Legal), copyright, attribution, and social text links.
  • •Removed duplicate embedded nav bars from blog/page.tsx, blog/[slug]/page.tsx, and changelog/page.tsx.

Files

  • •app/(marketing)/layout.tsx created: server component, wraps children with MarketingNav and MarketingFooter
  • •components/marketing/MarketingNav.tsx created: client component - sticky nav, usePathname active detection, localStorage theme toggle, mobile drawer with focus trap
  • •components/marketing/MarketingFooter.tsx created: server component - footer with four link columns and social links
  • •app/layout.tsx updated: added <head> with inline FOUC-prevention script and suppressHydrationWarning on <html>
  • •app/globals.css updated: added --nav-bg / --nav-border CSS variables for both themes, added .mkt-nav-* and .mkt-footer-link utility classes with responsive breakpoint
May 15, 2026
Major Release

Launch 1 - ATS-first job search workspace

Launch 1 shipped ATS scoring, resume tailoring, cover letters, application tracking, and the first blog content system in one integrated workflow.

Launch 1 brings the core workflow from job discovery to tracked applications.

Included in this release

  • •ATS scoring for base and tailored resumes with score history and improvement tracking.
  • •Resume tailoring powered by job parsing and structured optimization suggestions.
  • •Cover letter generation aligned to job context and profile data.
  • •Application tracking dashboard with status updates and activity metrics.
  • •Blog content foundation with MDX support and frontmatter-based loaders.
May 15, 2026
Feature

Blog pages with MDX rendering, search, and category filtering

Blog infrastructure with MDX rendering, category filtering, and newsletter signup.

  • •app/(marketing)/blog/page.tsx created: server component, featured 2-up card layout, passes all posts to BlogClient
  • •app/(marketing)/blog/BlogClient.tsx created: client component handling newsletter signup, category filter, search, and post list
  • •app/(marketing)/blog/[slug]/page.tsx created: server component with generateStaticParams, generateMetadata, MDXRemote rendering, and "More from [category]" section
  • •components/mdx/MDXComponents.tsx created: custom renderers for h1 h2 h3 p code pre ul ol blockquote using CSS variables
  • •app/globals.css updated: added @plugin "@tailwindcss/typography" and .blog-prose overrides for CSS variable color integration
  • •new packages: @tailwindcss/typography, next-mdx-remote
May 15, 2026
Feature

Admin analytics dashboard

Admin-only dashboard showing revenue, user, retention, and product usage metrics.

  • •app/admin/page.tsx created: dynamic server component checking user against ADMIN_USER_ID, redirects non-admins to /dashboard, renders revenue, user, retention, product usage, growth, recent events, and route performance metrics via the Supabase service-role client
  • •.env.local updated: ADMIN_USER_ID placeholder added for local admin access
  • •No new database tables, columns, or indexes
  • •No npm packages installed
May 15, 2026
Feature

Contact page with public form and rate-limited API route

Public contact form with server-side rate limiting and Supabase persistence.

  • •app/(marketing)/contact/page.tsx created: server component, 560px centered layout, heading + subtext, renders ContactForm, shows email and location below a border
  • •components/marketing/ContactForm.tsx created: 'use client' form with name/email/subject/message fields, client-side validation, loading/success/error states, spinner in button while submitting
  • •app/api/contact/route.ts created: public POST route (no auth), validateBody, server-side email format check, service-role Supabase insert into contact_submissions, per-email hourly rate limit via SELECT count check
  • •new DB table: contact_submissions (id uuid pk, name text, email text, subject text, message text, created_at timestamptz) — no RLS, insert public, reads service-role only
  • •no new env vars
  • •no npm packages installed
Vantage
Your unfair advantage in job searching.
Product
FeaturesPricing
Resources
DocsBlogChangelog
Company
AboutFeedback
Legal
Privacy PolicyTerms of ServiceData Compliance
© 2026 Vantage, Inc.
Made by Ishvir Chopra