Skip to content

v3.24.2 — ATS engine, claims accuracy audit, account deletion, and legal remediation - #197

Merged
Gautam25Raj merged 32 commits into
masterfrom
feat/site-optimization
Sep 1, 2026
Merged

Gautam25Raj merged 32 commits into
masterfrom
feat/site-optimization

Conversation

@Gautam25Raj

@Gautam25Raj Gautam25Raj commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

32 commits taking apps/site from a marketing surface that overstated the product to one that describes it accurately — plus a rebuilt ATS engine, a modularised component architecture, self-service account deletion, and a full SEO/AEO pass.

516 files changed · v3.24.1 → v3.24.2 · 32 commits

The through-line is the second half: an audit compared every public claim against the code that has to honour it and found 90 defects. Marketing promised features that did not exist, the security page asserted guarantees the repository could not support, and the legal pages had structural gaps. All of it is now either true, labelled as forthcoming, or gone.


1 · ATS engine and checker (7 commits)

ServerAtsResumeParseService with bounding-box layout detection for multi-column resumes; worker-based PDF/DOCX extraction via ExtractPool; contact extraction, section segmentation, keyword tokenisation; live policy tests.

A separate, more generous extract quota (3 anonymous / 6 free / 300 paid) means uploading a file no longer consumes a scan — previously an anonymous visitor spent their only 48-hour scan just by uploading.

Site/ats-checker and /ats-checker/scan rebuilt from monolithic pages into domain-driven modules (components/tool/, components/hero/, components/guide/), with ParsedView exposing the raw parsed stream, detected sections, and linear reading flow.

2 · Component architecture (6 commits)

Navbar split into LogoPill / DesktopNav / ActionsPill / MobileMenu; landing page modularised; /features, /how-it-works, /security, /compare, /contact, /faq decomposed into composable sections with decoupled datasets. Changelog and GitHub services broken out of monolithic files into services/github/ and services/changelog/. Polymorphic LandingButton; responsive work across hero, bento, previews, and segmented controls.

3 · Claims accuracy — the audit remediation

Fabricated features removed. The Student Ambassador page described a points economy in specific numbers — +10 per invite, +30 on upgrade, 1,500 points for a Creator Pro voucher, an "automated node validator" issuing badges instantly. None existed; the server implements apply, review, and status only. The calculator that simulated it (and copied fabricated licence keys to the clipboard) is deleted.

The worst claim. "LinkedIn import is processed client-side in your browser for total privacy" — it POSTs to the server and forwards to a third-party model. A privacy statement that was the exact inverse of the architecture.

Portfolio model stated once, correctly. Nineteen surfaces said publishing was free and unqualified; six said it was paid; pricingData.ts and the README said it was a paid entitlement. The real model — Signal and Atelier free with a badge, Nimbus and Cipher paid — is now written identically everywhere, with an "at launch" qualifier because portfolioController still blocks publishing in production for all non-admin accounts.

Security page rebuilt from what the code proves. Removed: "LocalStorage encryption" (plaintext JSON), "zero unauthenticated uploads" (POST /ats/extract accepts 5 MB anonymously by design), "AES-256 at rest", "TLS 1.3", "zero logging", "cryptographic purge", "WASM/Canvas rendering" (it is @react-pdf/renderer), a GitHub scope string for scopes never configured, and a PGP key that was never published. Third-party guarantees are now attributed to the provider rather than asserted.

Invented numbers labelled or dropped. "99.9% parse rate", "342 stars" on a repository that does not exist, "+38% ATS Impact", an apex-domain portfolio URL the product cannot issue. Interface previews now carry a caption, following the one component that already did it right.

4 · Legal (2 commits)

Governing law and forum named (Indian law, Bengaluru) with a mandatory-rights carve-out — the clause previously named neither, leaving default rules to decide. Age raised 16 to 18. Notice-and-takedown added with counter-notice and repeat-infringer policy; the enforcement machinery already existed, only the policy was missing. Liability cap given express non-excludable carve-outs; indemnity narrowed to third-party claims with a notice-and-defence procedure. Named Grievance Officer with postal address. Breach-notification commitment, concrete retention periods, and the right to complain to a regulator.

A complete subprocessor table replaced three inconsistent prose lists, and rebuilding it from the SDKs found two undisclosed subprocessors: OpenRouter (the AI client is the OpenAI SDK pointed at AI_BASE_URL, so the gateway — not Anthropic or OpenAI directly — receives user text) and the Hostinger SMTP provider that delivers every sign-in code.

Repository: TRADEMARK.md, LICENSING.md, DCO sign-off in CONTRIBUTING.md, safe-harbour clause in SECURITY.md.

5 · Account deletion, hardened

Self-service deletion (Profile to Danger Zone) with type-your-email confirmation. Review of the implementation found three defects, all fixed:

  • Security. DELETE /users/me was gated on user:write — the scope that renames an account. Any API key, including one pasted into an unrelated third-party tool, could irreversibly destroy everything. Now session-only via denyApiKeyAuth, with a test verified by removing the guard.
  • Billing. Deletion dropped the local subscription row but never cancelled at the provider — there is no cancel call in the codebase, since cancellation happens in the Dodo customer portal. Users would keep being charged for an account that no longer existed. Now blocked with a 409.
  • Ordering. R2 assets were deleted before the transaction, so a failure left an intact account with vanished images. Reversed.

6 · Free trials removed

createCheckoutSession passed trial_period_days: 7 for first-time Creator Pro monthly — narrower than the Terms described, which claimed one for the Bundle too. Removed entirely as a product decision, along with TRIALING from the schema (migration included; no subscription ever used it) and the per-checkout query that only existed to decide eligibility. One case "trialing" remains by design, mapping to ACTIVE: we control what we request, not what the provider webhooks send.

7 · Engineering defects

app/not-found.tsx was a copy of error.tsx whose "Try again" button called an undefined reset() — a guaranteed TypeError on every bad URL outside the marketing group. text-muted-foreground was mapped in no @theme block, so 36 usages in apps/site and 98 in apps/studio rendered uncoloured. Skip-to-content link added (WCAG 2.4.1 Level A, previously absent); duplicate <main> landmark on /privacy and /terms removed. Unguarded touches[0]; a no-op Suspense rendering a second copy of a large client tree; dead files and assets.

8 · SEO and AEO

Seven Offer nodes were shipping into <head> on every route including /privacy, and competing with the /pricing offer graph. /ats-checker emitted five verbatim-duplicate Q&As already on /faq. /faq had zero h2 elements in its own content — every question an orphaned h3 under the h1 — now 44. A single h2 to h4 footer skip affected all 8 pages. llms.txt advertised three /compare routes that hard-404 and hid three that exist, and listed every resume template without its resume- prefix, breaking all seven links.

Added: a Person entity with stable @id (three disconnected name-only stubs before), a root WebSite node, @id graph linking. Dropped hreflang — it emitted only en-US, region-locking an India-based service with a global audience.

9 · Guardrails

apps/site/tests/contracts/claims.contract.test.ts asserts the cross-file agreements that kept drifting: template IDs resolve and carry their prefix, every advertised compare route exists and every real one is advertised, the portfolio model matches the server, credit-pack expiry is stated, ATS preview labels match the engine, and no trial is advertised.

check-design-tokens now fails when a token mapped in no @theme block is used as a utility — it already printed the warning that let 134 dead usages accumulate.

Every test was verified against a deliberately reintroduced bug. One passed on broken input and was rewritten.


Verification

Gate Result
tsc --noEmit Clean — all 6 apps + packages/ui
ESLint Clean — site, server, studio
Prettier Clean — site, server, studio
Server tests 294 / 294 (48 files)
Site contract tests 42 / 42 (was 30)
check:design Passing
next build — site 98 static pages
next build — studio Success
tsc build — server Success

Rendered HTML spot-checked: no hreflang, Person present, zero Offer nodes on /privacy, seven on /pricing with derived counts, no duplicate FAQPage on /ats-checker, 404 emits noindex with no canonical.


Known-open, tracked in code

  • Three TODO(legal) markers: no EEA/UK Article 27 representative (position documented, not appointed), per-subprocessor transfer table, and retention periods that are written commitments with no enforcing job.
  • Compare-page meta descriptions run 262–283 chars because competitor.positioning is body copy used as meta copy. Needs six hand-written descriptions.
  • Three h1 to h3 skips on /, /features, /compare.
  • apps/portfolio has one dead text-muted-foreground on a Sonner cancel button; that app uses a separate palette, so the one-line fix used elsewhere would make the text match its own background.

GitHub reports 4 high-severity Dependabot advisories on master — pre-existing, unrelated to this branch.

🤖 Generated with Claude Code

Gautam25Raj and others added 30 commits August 24, 2026 15:30
…nd feature flows

Comprehensive refactor and optimization across apps/site marketing pages and shared UI components:

- Navigation & Shell: Split Navbar into focused subcomponents (LogoPill, DesktopNav, ActionsPill, MobileMenu) and introduce useMounted and useScrolled layout hooks.
- Landing Page Architecture: Modularize HeroHeader with dedicated action and feature card components; polish GaplessBento, AtsGauge, TemplateShowcase, InteractiveProcess, DocumentPortfolioSwitcher, and PrivacyEngineSimulator.
- Sub-pages & Conversion: Deconstruct AmbassadorApplyExperience into dedicated multi-step form views; decompose ContactExperience into modular form and sidebar panels; update template detail header and pricing routes.
- Robustness & Boundaries: Refine global-error, marketing error boundaries, 404 handlers, sitemap, robots, and OG metadata generator.
- Introduce polymorphic LandingButton component for unified CTA handling across internal links, external subdomains, and buttons
- Polish navigation header and mobile menu with responsive breakpoints and dvh height boundaries
- Refactor Hero feature cards, Bento grid, and interactive workflow cards for mobile and tablet screens
- Redesign DocumentPortfolioSwitcher segmented slider to prevent two-line wrapping with responsive labels
- Optimize ATS Resume, Cover Letter, and Portfolio live preview components for mobile viewports
- Enhance Privacy Why Us and Comparison Matrix sections with local-first, open-source focus
- Eliminate inline JSX comments and resolve ESLint lints across marketing components
…gregation, and site UI

- Implement ChangelogRichText inline markdown parser supporting bold, inline code, links, PR references, contributor mentions, and scoped packages (@veriworkly/profile-core)\n- Move release contributors to the top header in ChangelogEntryDetail\n- Enhance backend contributor aggregation across GitHub API, commits, PRs, and text mentions\n- Filter out organization name handles from contributor avatar listings\n- Add github.com to next.config.ts image remotePatterns for optimized avatar delivery\n- Add sync:changelog script to sync releases directly from GitHub\n- Polish UI and components across About, Affiliate, Ambassador, ATS Checker, Compare, Pricing, Roadmap, and Template Explorer pages\n- Add contract tests for changelog rich text parser and unit tests for release note parser
…s and organize site components

- Server:
  - Break down monolithic githubService and changelog services into dedicated subfolders (src/services/github/ and src/services/changelog/) with distinct responsibilities (client, parser, releases, issues, contributors, queries, cache, sync).
  - Remove deprecated root service files (githubService.ts, changelogService.ts, changelogSyncService.ts) in favor of direct canonical imports.
  - Extend changelog search query in queries.ts to match across title, summary, version number (e.g. 3.24.0, v3.24), and ID slugs.
  - Audit and harden changelogValidator.ts with string trimming, URL verification, timezone offset datetimes, and empty payload guards.
  - Fix Prisma JSON typecasting and lifecycle imports in backfill and sync maintenance scripts.

- Site:
  - Reorganize changelog feature components into structured domain directories (controls/, detail/, list/, utils/) with a unified barrel export.
  - Refactor ChangelogSearch.tsx to an event-driven debounced pattern with React 19 render-phase URL synchronization and useTransition.
  - Format and polish changelog marketing pages, detail navigation, and SEO metadata.
- Comparison Hub & Pages:
  - Redesign and modularize competitor comparison components under detail/, hub/, and shared/
  - Fix table DOM hydration error by using React Fragment in FeatureMatrixTable
  - Fix back navigation button spacing and remove clipped background blur artifacts in CompareVsHero
  - Standardize Accordion usage in CompareFAQSection using @veriworkly/ui
  - Update structured data, feature matrices, and pricing breakdowns for all competitors

- Contact Flow & Mail Service:
  - Refactor ContactExperience with floating fields, topic suggestions, and honeypot bot protection
  - Implement contact success confirmation modal with focus trapping
  - Add contactAdmin and contactUser transactional email templates and generalMail service
  - Add comprehensive test suites for contact routes and mail dispatch

- FAQ Interactive Experience:
  - Implement searchable and filterable FAQ interactive section with category pills
  - Update FAQ data sets and expand answers across site and portfolio

- UI Package Polish:
  - Improve keyboard navigation and styling in Accordion component
…hitectures

- Features Domain Architecture:
  - Modularize page into hero, specialized engine sections (Ingestion, Studio, AI Tailoring, ATS Scanner, Portfolio, Privacy/Exports), and developer ecosystem comparison matrix.
  - Decouple static dataset files for pillars, export formats, and feature comparison rows.
  - Remove monolithic CapabilityMosaic in favor of composable engine components.
  - Enrich SEO metadata, openGraph tags, and structured JSON-LD schemas.

- How It Works Architecture:
  - Redesign workflow walkthrough with dedicated HowItWorksHero, interactive StepDetailCards, ArchitectureAndDocsSection, and modular PipelineTimeline.
  - Implement HowTo schema for structured SEO visibility.
  - Centralize workflow step data models and types in data/ and types.ts.

- Security Posture & RFC 9116:
  - Architect domain-driven security sections: SecurityHero, SecurityBoundaryDiagram, DataPartitioningSection, OAuthSecuritySection, DataRetentionSection, and ResponsibleDisclosureSection.
  - Add standard RFC 9116 security.txt files at /public/security.txt and /public/.well-known/security.txt.
  - Model structured security datasets: encryption boundaries, OAuth scopes, retention matrix, and disclosure scope guidelines.

- Legal & Compliance Experience:
  - Build responsive LegalPageShell supporting sticky desktop TOC sidebar, mobile drawer navigation, live full-text search with keyword highlighting, and scroll-spy tracking.
  - Refactor /privacy and /terms pages to consume unified shell, hero, topic grids, and contact banner components.
  - Modularize legal content structures and types with clear section anchors.
- Add BreadcrumbList and AboutPage JSON-LD structured data across all marketing routes (about, features, how-it-works, pricing, privacy, security, terms, contact, faq, stats)

- Polish code formatting, imports, and layout consistency across marketing pages

- Update roadmap seed data and backend features integration for site and studio
…and copy

- Legal & Privacy:
  - Add explicit volatile RAM processing disclosure for ATS Resume Checker scans in privacy policy
  - Expand ATS Checker & Career Tools disclaimer in terms of service regarding heuristic evaluation
  - Add Third-party trademarks nominative use disclosure

- Copy & Landing:
  - Clarify ATS parsing standards in landing and FAQ datasets
  - Refine metrics proof copy to emphasize ATS-scannable layouts
  - Update frontier AI model mentions in root layout and site configuration
…ontract tests

- Domain Services:
  - Move atsCheckerApi, normalizeReport, quotaCopy, and reportText to dedicated services/ subfolder with barrel export.
  - Implement full deterministic report normalization and format mapping.

- Data Architecture & Types:
  - Separate structured datasets into data/ (categories, failureTraps, pipelineSteps, pricingTiers, scoreFacts).
  - Unify shared TypeScript types in types/index.ts.
  - Establish canonical feature barrel export at features/ats-checker/index.ts.

- Contract Testing:
  - Update contract test suites (ats-normalize-report, ats-quota-copy, ats-report-text) to consume modular domain exports.
…ectories

- Interactive Scanner Tooling:
  - Reorganize AtsCheckerTool, Stepper, ResumeStep, ScanLoader, FullResults, RestrictedResults, CategoryBreakdown, ScoreGauge, ScoreSummary, QuotaNotice, and VerdictBadge into components/tool/.

- Landing & Marketing Domain Sections:
  - Hero & Preview: Create AtsHero and ReportPreview under components/hero/.
  - Explanatory Guides: Add AtsDirectAnswer under components/guide/.
  - Technical Pipeline: Build AtsPipelineSection under components/pipeline/.
  - ATS Scoring Dimensions: Add AtsCategoriesGrid under components/dimensions/.
  - Parser Traps: Add AtsFailureTraps under components/traps/.
  - Commercial Tiers: Modularize TierComparison under components/pricing/.
  - ATS FAQ: Implement AtsFaqSection under components/faq/.

- Barrel Exports:
  - Establish unified component exports under components/index.ts and sub-barrel index files.
…an page

- Landing Overview (/ats-checker):
  - Assemble modular architecture composed of AtsHero, AtsDirectAnswer, AtsPipelineSection, AtsCategoriesGrid, AtsFailureTraps, TierComparison, AtsFaqSection, and InteractiveCTA.
  - Enrich FAQPage and BreadcrumbList structured data for search engine visibility.
  - Integrate legal disclaimers and third-party trademark notices.

- Scan Tool Page (/ats-checker/scan):
  - Integrate theme-aware Container and ambient lighting background accents.
  - Update memory-only security badge and breadcrumb navigation.
  - Wire AtsCheckerTool with canonical imports.
…tion pool, and live policy tests

- Parsing & Layout Detection:
  - Add AtsResumeParseService with bounding-box layout detection for multi-column resumes
  - Implement worker-based PDF/DOCX stream extraction with ExtractPool
  - Support contact extraction, section segmentation, and keyword tokenization

- AI & Engine Policy:
  - Implement EnginePolicy for deterministic rule-based checks and AI model routing
  - Standardize error handling, quota deduction, and refund mechanisms

- Test Suites:
  - Add comprehensive test suites for layout detection, AI routing, engine policy, and parser contracts
…metrics, and studio integration

- ATS Scanner Tooling:
  - Add ParsedView component showing raw parsed streams, detected sections, and linear reading flow
  - Update FullResults and RestrictedResults with keyword density scores, fix recommendations, and failure traps
  - Polish copy report actions and contract normalization handlers

- Studio ATS Workspace:
  - Connect Studio AtsWorkspace and import flows to unified ATS extraction and scoring APIs
  - Synchronize domain types with backend models
… platform documentation

- Marketing & Brand Experience:
  - Redesign Brand Kit and Style Guide interactive sections with color tokens, typography, and logo assets
  - Expand document template catalog routes (/templates) and roadmap view components
  - Update open graph generation, sitemaps, and llms.txt knowledge representation

- Documentation & API Specifications:
  - Document ATS Checker parsing pipeline in docs-platform and blog-platform
  - Synchronize shared OpenAPI schemas across platform apps
app/not-found.tsx was a copy of app/error.tsx: the component was named
Error, declared { error, reset } props that Next.js never passes to a
not-found boundary, and rendered "Application Error / Something went
wrong" for a missing page. Its primary "Try again" button called
reset(), which is always undefined there - a guaranteed TypeError on
click for every bad URL outside the (marketing) group, including
/affiliate/* and /ambassador/*.

Rewritten as a real 404 modelled on the (marketing) one: no props, 404
copy, and buildPageMetadata with noIndex so the page stops being
indexable.

Also ran prettier --write across apps/site. Six files were failing
--check (LogoSection, AtsScannerSection, BentoCard, SkeletonLine,
use-scrolled, changelog-rich-text.contract.test), which was the only
failing gate in the app.

Resolves D-13, D-06.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The /ambassador page described a rewards mechanism in specific numbers -
+10 per invite, +30 on upgrade, +50 per video, +40 per blog post, a 250
"platform activity score" gate, a .edu domain check, and 1,500 points for
a 30-day Creator Pro voucher - none of which exists. AmbassadorService
implements apply, getStatus, listApplications, and reviewApplication only.
The Prisma schema has no points model, no voucher model, and no
leaderboard. A mechanism described in numbers reads as a live program, and
the apply form accepts submissions today, so this could not be re-tensed.

- AmbassadorPlaybook: dropped the "automated node validator ... issues
  campus ambassador badges instantly" claim (review is an admin reading a
  PENDING row) and the three mockup cards that depicted the economy - a
  fabricated named applicant with a "validated" university domain,
  "+50 PTS credited" against invented social engagement, and a Creator Pro
  voucher with an activation key and a Claim button.
- AmbassadorPerks / AmbassadorFAQ: rewritten around what the program
  actually does - apply, human review, decision - and what is still being
  designed, with no point values.
- AmbassadorCalculator: deleted. It simulated the points economy end to
  end and copied fabricated licence keys to the clipboard. Nav and hero
  now scroll to the process section instead.
- AmbassadorHero: dropped "1,850 pts", "1,500 pts = Pro Upgrade",
  "Verified .edu link", and the VW-PRO-K98X-841L milestone key.
- Leaderboard kept as an illustration per product decision, but the
  disclaimer no longer says ranks "are updated automatically" - that
  cancelled the "Sample" label it sat beside. Now states plainly that the
  names and figures are samples and the program has not launched.

Also gates /ambassador on AMBASSADOR_PROGRAM_ENABLED the way /affiliate
already is, so the pitch and the apply page agree instead of the CTA
dead-ending on a coming-soon card in production (B-08), and routes the
page through buildPageMetadata (C-07).

Replaced the two Unsplash hero photos with product-truthful UI cards:
third-party requests and an uncontrolled LCP dependency on a page that
sells not making them, reading as fake people beside a sample leaderboard
(A-29).

Dropped "Why thousands are leaving subscription resume builders" from the
homepage comparison header - social proof with no user base behind it, and
a claim about the past that forward-looking framing cannot cover (A-18).

Resolves A-03, A-18, A-29, B-08. Partially resolves A-04, C-07.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`removeWatermark` arrives inside the client-supplied portfolio content
payload, and publish stored it verbatim in the publication snapshot. It
was never cross-checked against the `watermark_removal` entitlement, so a
free-tier publisher could post `removeWatermark: true` and have the server
persist it as though it had been earned.

The public renderer was already safe - it guards on `isPremium`, a real
subscription check, before honouring the flag - so this was not an
exploitable watermark bypass. But the stored snapshot was untrustworthy
for everything else that reads it, and the entitlement key was granted and
never consulted anywhere in the codebase.

publish() now resolves the flag through resolveWatermarkFlag() against
EntitlementService.has(WATERMARK_REMOVAL): the request is honoured only
when the entitlement is held, and stored as false otherwise. Extracted as
a pure helper so the rule is covered without a database.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ound it

Nineteen surfaces said portfolio publishing was free and unqualified; six
said it was paid; pricingData.ts and README.md said publishing was a paid
entitlement outright. None of them matched the code, and none matched each
other.

The model, now written the same way everywhere: Signal and Atelier are the
free core templates and publish with a "Built with VeriWorkly" badge;
Nimbus and Cipher are premium; badge removal, visitor analytics, and SEO
controls come with a paid plan. That is what portfolioService.publish
actually enforces.

Every publishing claim now carries an "at launch" qualifier, because
portfolioController blocks publishing in production for everyone except
the configured admin email - so until that lifts, no publishing claim is
true in the present tense for any user, free or paid.

Also separated the document watermark from the portfolio badge. Document
exports genuinely carry no watermark on any tier; a free published
portfolio does carry a badge. Rows like "100% watermark-free" and
"Watermark removal" were stated unqualified and read as covering both.

Numbers corrected against the server while in the same files:
- Credit packs expire in 90 days (creditPackCatalog.expiresInDays), not
  "never" - the FAQ contradicted our own Terms (A-06).
- Paid ATS scans are 300 per billing period, not "high-volume daily" (A-15).
- Free-tier GitHub/LinkedIn import caps and the one-document-per-type limit
  are now stated in pricing.md instead of being listed unqualified (A-14).
- "Priority GitHub & LinkedIn imports", "priority CDN image bandwidth", and
  "additional designer themes" removed - no entitlement exists for any of
  them (A-22).
- Portfolio theme count fixed: four themes, not three. Cipher was missing
  from /features while the catalog, llms.txt and agent.json all ship it (A-28).
- Zety is quoted once, consistently: $1.95 for 14 days, then $25.95 every 4
  weeks, annual $71.40 upfront. It previously appeared at three different
  prices across four surfaces (B-01).
- Dropped the unsourced "20,000+ bullet points / 1,500+ real resumes"
  attributed to Kickresume (A-31).
- MetricsProofBar: removed an unmeasured "99.9%" that read as a parse-rate
  guarantee, and two counters that animated down from 80 and 10 - start
  values picked for the animation, with no meaning (A-07, D-09).
- "Real free plan (no expiring trials)" lost the second half: the Terms
  describe a 7-day trial, so the matrix was contradicting our own agreement.
  The free tier itself is real, which is the comparable part (A-26).
- Export format count reconciled to six everywhere, and "publish live
  portfolios" is no longer listed as if it were a file format (B-04).
- "Passes unlock advanced exports" removed - every format is free and
  ungated in useDocumentDownloads (B-05).
- Replaced the "100% local / never leaves your browser" absolutes with the
  privacy policy's own accurate sentence (B-07).
- "Zero Hallucination Guardrails" replaced with what actually protects the
  user: grounding in the Master Profile plus a diff you approve (A-17).

Resolves A-06, A-14, A-15, A-17, A-22, A-25, A-26, A-28, A-30, A-31, B-01,
B-04, B-05, B-07, D-09. Partially resolves A-01, A-07, A-19, A-20, A-27.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The security page was marketing copy in the register of a compliance
document, and it is the page most likely to be quoted back at us.

The worst of it was the LinkedIn import claim: "Processed client-side in
your browser for total privacy", with a "Client-Side Parsing" tag and a
"Local Processing Only / zero remote credential transmission" row on the
security page. Import POSTs the pasted text to /profiles/import/linkedin,
which forwards it to a third-party model via parseTextToResumeSchema. It
was a privacy statement, it was wrong, and our own privacy policy already
described the real behaviour. Now it says what happens: parsed on our
servers by an AI model, then discarded, not used for training.

Security page claims removed because the repository cannot back them:
- "LocalStorage encryption" and the "Client-Encrypted" hero badge - the
  studio writes plaintext JSON. The page now says plainly that browser
  storage is not encrypted by us and to treat it as readable on a shared
  machine.
- "Zero unauthenticated uploads" - POST /ats/extract accepts a 5 MB file
  from anonymous callers by design; that is the ATS checker's own upload
  path. Being wrong about our own attack surface on the security page is
  the most expensive place to be wrong.
- "AES-256 at rest", "TLS 1.3" specifically, "zero logging", "cryptographic
  purge", "edge SSL rotation", "encrypted API gateways" - provider-
  dependent with nothing in this repo behind them. Third-party guarantees
  are now attributed to the provider rather than asserted as ours.
- "In-memory vector rendering (WASM / Canvas)" - exports run through
  @react-pdf/renderer, which is neither.
- GitHub scope string "read:user, public_repo" - no scopes are configured
  for the provider, so Better Auth defaults apply. public_repo is also a
  write scope, contradicting the "zero write permissions" cell beside it.
- "GPG / PGP encrypted communication" - no key is published.

Disclosure section: "Bug Bounty" removed (there is no bounty, and
advertising one recruits researchers under a false premise), "24h SLA
Guarantee" replaced with the 24-48h window SECURITY.md already states, and
the "Security Hall of Fame" credit promise replaced with real credit in
release notes.

Added a safe-harbour clause, which was missing entirely. Without it a
researcher testing us has no assurance and real exposure under
computer-misuse law, and we have no stated boundary between authorised
research and an offence - authorisation being the hinge the offence turns
on.

Server: the contact route logged submitters' name, email and subject on
both the accepted and honeypot paths. Our privacy policy describes server
logs as "IP address, user agent, request timestamps, and similar
metadata", so this exceeded what we disclosed. Now logs metadata only; the
anti-bot heuristics keep everything they need.

Capability claims corrected while in the same files: font list (Geist,
Manrope, Inter - not Inter/Source Serif/IBM Plex), margin units (16-52px,
not a 0.5x-1.2x multiplier that does not exist), studio domain
(app.veriworkly.com), "JSON Resume" downgraded to plain JSON since the
export is our internal shape and not the jsonresume.org standard,
preview/PDF parity stated as an automated suite rather than "identical",
ATS scan quotas stated instead of "as many as you need", and the free
tier's zero AI credits disclosed beside the pay-as-you-go copy.

Resolves A-02, A-05, A-08, A-09, A-10, A-11, A-12, A-16, A-20, A-23, A-27,
D-08, L-15, L-16.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…sing

Drafted in full and marked where a practitioner still needs to sign off.
Statute-dependent reasoning here rests on unverified recall, not a checked
source, so treat the TODO(legal) markers as blocking rather than optional.

TERMS

- Governing law and forum: the clause named neither. "Governed by
  applicable laws" is circular and "courts of competent jurisdiction" names
  nothing, so the default rules decided - which for a consumer dispute
  generally means their home court under their own law. Now names Indian
  law and the courts at Bengaluru, with an express carve-out preserving the
  mandatory rights and home forum of consumers who have them (L-01).
- Age raised from 16 to 18, with the reasoning stated: India's DPDP
  framework treats under-18s as children, and a flat 18+ floor removes that
  regime rather than obliging us to build consent machinery we cannot
  staff. The Terms clause is the easy half; an affirmation stored against
  the account is the half that does the work, and is still to be built (L-05).
- Notice and takedown added: reporting address, required contents,
  acknowledgement in 3 business days and a decision in 10, a counter-notice
  route so we are not adjudicating between strangers, a repeat-infringer
  policy, and an abuse-of-process clause. The enforcement machinery already
  existed - adminPortfolioService suspends and flushes caches,
  portfolio.unpublish is audited - so this was a documentation gap (L-06).
- IP sentence split. It bundled brand and template designs into one
  reservation; the brand half is enforceable, the template half was
  contradicted by our own repository. Also dropped "not all code, templates,
  or assets ... are necessarily covered by the same license" - with a single
  root MIT and no headers, they are (L-02).
- Liability cap given express non-excludable carve-outs: death and personal
  injury, fraud, gross negligence and wilful misconduct, statutory consumer
  rights, and data-protection liability. A clause that reserves these is
  harder to attack than one relying on a court reading them in, and total
  exclusion is not achievable in the places we operate anyway (L-19).
- Indemnity narrowed to third-party claims arising from the user's own
  content, breach, or unlawful conduct, with a notice-and-defence procedure
  and a consumer-law limit. An uncapped indemnity from a consumer sitting
  opposite a $100 cap is the textbook shape of a reviewable term (L-14).
- Refunds reconciled with the statutory cooling-off right, plus the
  surrounding auto-renewal mechanics: pre-purchase disclosure,
  post-purchase confirmation, a reminder before a trial converts, and
  cancellation no harder than signup. Notes that Dodo as merchant of record
  may process some refunds (L-12, L-13).
- Arbitration and class-action waiver: decided on the record rather than
  omitted, and declined (L-18).

PRIVACY

- Named contact published: Gautam Raj, Grievance Officer & Data Protection
  Contact, with a dedicated address. Grepping features/legal for
  "grievance", "data protection officer", "supervisory authority", "DPDP"
  and "representative" previously returned zero hits (L-07).
- Retention periods made concrete: 30 days post-deletion, 90-day logs,
  8-year billing records, 3-year takedown records, zero for ATS scans.
  "A reasonable operational period" is neither a period nor a criterion.
  States plainly that the billing floor survives account deletion (L-09).
- Breach notification added: regulator within the applicable deadline,
  affected individuals without undue delay where risk is high, and a
  commitment to notify even when it is embarrassing (L-08).
- Right to complain to a regulator added to the rights list - the one right
  that tells a user what to do when we say no (L-11).
- Subprocessor list rebuilt with what each provider actually sees, and
  third-party compliance attributed rather than asserted ("Dodo Payments
  states that it handles card data under PCI-DSS"). Transfer mechanisms
  named, with the per-recipient table left as a TODO (L-17).
- AI provider list aligned to the policy's own disclosure (Anthropic and
  OpenAI) across siteConfig, the root layout, and the FAQ, which previously
  gave three different lists including Gemini and Deepseek (B-02).
- Children's section moved to 18 so the two documents agree (L-05).
- Invented roles retired: "privacy officer", "Contact Privacy Team", "our
  privacy compliance team". One person, now named (L-20).

REPOSITORY

- TRADEMARK.md: what MIT does not cover, and what to change when forking.
  The reservation previously existed only in the Terms, which a forker
  never reads (L-04).
- LICENSING.md: states plainly that every template including the paid
  portfolio ones is MIT, that relicensing cannot be retroactive, and that
  without contributor sign-off we may not hold enough copyright to
  relicense at all (L-03).
- CONTRIBUTING.md: DCO Signed-off-by requirement. Fixes provenance from
  today; does not fix what is already merged (L-03).
- security.txt: dropped the Acknowledgments field, which pointed at a
  Security Hall of Fame that does not exist, and removed the byte-identical
  duplicate at public/security.txt (D-17, part of L-15).
- SECURITY.md: added a safe-harbour clause and stated plainly that there is
  no paid bounty.

TODO(legal) markers left deliberately, all blocking:
- siteConfig.legalContact.postalAddress is incomplete (needs building, city,
  state, PIN).
- No EEA/UK Article 27 representative appointed, and no documented
  assessment of whether the occasional-processing exemption applies.
- The per-subprocessor location and transfer-mechanism table is unwritten.

Resolves L-01 through L-04, L-06 through L-14, L-17 through L-20, B-02, and
the security.txt half of D-17.

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

llms.txt is the one file written specifically for AI crawlers, and half its
comparison section pointed at routes that do not exist. It advertised
/compare/rxresu.me, /compare/flowcv and /compare/visualcv - all hard-404
under dynamicParams = false - while hiding novoresume, zety and enhancv,
which are real. It also listed every resume template without its `resume-`
prefix, so all seven /templates/resume/<id> links were dead (C-01, C-02).

Rather than only fix the values, added tests/contracts/claims.contract.test.ts
asserting the relationships that were silently drifting: every template ID
in llms.txt resolves in the catalog and carries its prefix, every advertised
compare route exists, every real one is advertised, the portfolio model
matches what the server enforces, and the 90-day credit expiry is stated.
Each test was verified against a deliberately reintroduced bug - the first
draft of the template-ID test passed on broken input because it only checked
IDs that already looked prefixed, which is exactly the value the bug removes.

Also: /stats metadata claimed "documents built, exports triggered, AI feature
logs" while the page renders GitHub issues and PRs (C-10); JSON-LD offers now
carry PreOrder availability instead of advertising products checkout throws
on (C-05); the homepage HowTo's estimatedCost was typed HowToSupply and could
not validate (C-06); /features, /how-it-works and /compare now have navbar
links, /ats-checker and /stats reached the footer, and the duplicated
affiliate/ambassador links are gone (C-08); the compare-page year keyword is
derived rather than hardcoded to 2026 (C-11); sitemap lastModified is
evaluated per regeneration instead of freezing at module load (C-12); and
buildPageMetadata's docblock no longer cites the style guide as a noIndex
example while the page is deliberately indexed (C-13).

ENGINEERING

`text-muted-foreground` generated no CSS: --muted-foreground is defined in
themes.css but mapped into no @theme block, so 36 usages across /affiliate
rendered uncoloured body copy inheriting their parent.

The audit read this as a token-value bug and prescribed changing the dark
value first. That would have been wrong - brand.ts documents #171717 in both
themes as intentional, and correctly so: the token means "text on a muted
fill", and a muted fill is light in both themes. The bug is the usage, not
the value. All 36 now use `text-muted`, which is mapped and readable in both
themes, and the token keeps its documented meaning (D-01, D-02).

check-design-tokens now fails when a token that is mapped in no @theme block
is actually used as a utility. It already printed --muted-foreground as
"mapped in no app"; it just never failed anything. The new check immediately
caught a `fill-muted-foreground` in AffiliateHero that the manual sweep had
missed, which is the argument for having it.

Accessibility: added a skip-to-content link in AppShell - WCAG 2.4.1 is Level
A and there was no bypass mechanism anywhere in the app, so keyboard and
screen-reader users traversed the full navbar on every page load (D-15). The
target carries tabIndex={-1}, without which focus stays behind in the navbar
and the link accomplishes nothing. LegalSections no longer renders its own
<main> inside AppShell's, which gave /privacy and /terms two main landmarks -
on exactly the two pages that most need to be navigable (D-14).

Correctness and dead weight: guarded ev.touches[0], which throws on an empty
touch list and which tsc cannot see with noUncheckedIndexedAccess off (D-16);
replaced the paymentsBlocked comment that described an ADMIN_EMAIL lookup the
site app never performed and cannot perform, having no session (D-03);
removed the Suspense boundary whose child awaited nothing, so it never
suspended and its fallback was a second copy of a large client tree (D-04);
deleted lib/admin.ts, LogoMarkMono.tsx and PublicPageShell.tsx, all with zero
references (D-17).

Verified: tsc clean, eslint clean, prettier clean, 38/38 tests (was 30),
check:design passing, and `next build` completes with all 16 template routes
and 6 compare routes prerendering.

Resolves C-01, C-02, C-05, C-06, C-08, C-10 through C-13, D-01 through D-04,
D-11, D-14 through D-17.

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

- "Imports pinned projects" removed. The importer calls
  /users/{u}/repos?sort=updated&per_page=30 - pinned repositories need the
  GraphQL API and are never fetched, so the copy now describes what it does
  get: most recently updated repos with languages, descriptions, and stars
  (A-13).
- Affiliate tiers no longer "scale dynamically". `affiliateTier` is only ever
  written by adminAffiliateService; the rates and threshold were right, the
  automatic promotion was not. Now says upgrades are applied on review (A-21).
- Dropped "Instant 2s Scan", "in about two seconds", and "in 2 seconds".
  Nothing measures these, and extraction - a PDF parse in a worker pool - is
  the slow part. Publish a real p50 if we ever record one (A-24).
- ReportPreview's category labels now match `categoryMeta` exactly. It showed
  "Parsing & extraction", "Document structure", "Evidence & impact" and
  "Format risk profile"; the report renders "Parsing", "Structure",
  "Evidence" and "Format risk", so the figure promised a vocabulary the
  product does not use. Locked with a contract test (B-09).
- /about now defines open-core once, as a principle: the code and every
  template are MIT, the AI model and prompt configuration is not. The site
  used "open-source" and "open-core" interchangeably across ~20 surfaces
  with neither defined (B-03).

ENGINEERING

- Templates hero fan picks one of each document type instead of `.slice(0, 3)`
  over a catalog where all four portfolios sort first - which rendered three
  landscape web screenshots cropped into a portrait 8.5x11 paper frame. The
  portfolio card now gets a browser-window ratio and is contained rather than
  cropped. Also removed the `!previewImage.includes("veriworkly-logo")` filter
  above it, which matched nothing (D-07).
- manifest.json no longer declares the standard icons as maskable. Android
  crops maskable icons to a safe zone, so an unpadded mark clips on home
  screens - worse than declaring none, which lets the platform letterbox
  instead. A comment records what to export to restore them (D-12).
- tsconfig no longer both includes `.next/dev/types/**/*.ts` and excludes
  `.next/dev`. Kept the include, since Next regenerates that line on dev, and
  dropped the contradicting exclude (D-17).
- Deleted `fetchServerApiData` (exported, never called) and five unreferenced
  assets: three og images, and two unoptimised root-level template PNGs whose
  referenced counterparts live under `templates/resume/` (D-17).

DECLINED, WITH REASONS RECORDED IN CODE

- D-05 (move MotionConfig out of the root layout): declined. The navbar
  imports framer-motion and renders on every page including /privacy and
  /terms, so the library is in every route's bundle regardless. Moving the
  provider would save nothing and would create routes where a motion-
  sensitive user's preference is silently ignored.
- D-01/D-02's prescribed fix: the audit read `--muted-foreground: #171717`
  in both themes as a contrast bug and said to change the dark value first.
  brand.ts documents it as intentional and is right - the token means "text
  on a muted fill", and a muted fill is light in both themes. Fixed the 36
  misuses instead, in the previous commit.

Verified: tsc clean, eslint clean, prettier clean, 39/39 tests,
check:design passing, `next build` generating all 98 static pages.

Resolves A-13, A-21, A-24, B-03, B-09, D-07, D-12, and the remainder of D-17.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fabricated figures were rendering as product facts with nothing marking
them as samples. ReportPreview on /ats-checker already did this correctly -
a figcaption reading "Sample scanner output" - and nothing else followed it.

Deleted outright, because they were not illustrations of anything:
- "veriworkly/core-engine · 342 stars" in the GitHub mockup. That repository
  does not exist and the star count was invented, presented as live data.
  Now a generic your-name/your-project placeholder.
- "gautam.dev" and "gautam.dev/portfolio" in two portfolio mockups. An apex
  domain, while the product offers only *.veriworkly.com subdomains and the
  FAQ says custom domains are still roadmap. Now yourname.veriworkly.com.
- "+38% ATS Impact" and "+38% Match". These implied a measured outcome from
  using the feature, which is the most misleading shape a fabricated number
  can take.
- "98% ATS Score" and "100% Parsed" reduced to their labels. A percentage on
  a mockup reads as a result; the label alone shows the same interface
  without the claim.

Captioned rather than removed, because the interface preview has a job to do:
HeroFeatureCards is now a <figure> with "Interface preview. Scores, match
percentages and view counts shown here are illustrative", following the
ReportPreview convention.

Two more claims corrected while in the same file: "Read-only repository
access. We never request write permissions" softened to what we can actually
say - we request no write, commit or SSH scope, and GitHub's own consent
screen is the authority, since no scopes are configured and Better Auth's
defaults apply. And "Calculates verified language proficiency breakdown"
became "Summarises the language mix", because GitHub reports bytes per
language, which is not verified proficiency.

Verified: tsc clean, eslint clean, prettier clean, 39/39 tests,
`next build` generating all 98 static pages.

Completes A-07 and A-27.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ises

Review of the new delete-account feature found one security defect, one
financial-harm defect, and an ordering bug that could destroy data without
deleting the account.

SECURITY: DELETE /users/me was gated on requireApiKeyScopes("user:write") -
the same scope a key needs to rename an account. Any long-lived API key,
including one a user had pasted into an unrelated third-party tool, was one
call away from irreversibly destroying every document, portfolio, credit,
payout record and R2 object on the account. Scopes are far too coarse a lever
for that. Added denyApiKeyAuth and made the route session-only, so deletion is
reachable only from our own front-ends behind an explicit confirmation. Test
verified by removing the guard and watching it fail.

BILLING: deletion dropped the local Subscription row but never cancelled the
subscription at Dodo - there is no subscription-cancel call anywhere in this
codebase, because cancellation happens in Dodo's customer portal. A user who
deleted while subscribed would keep being charged for an account that no
longer existed, and the renewal webhook would arrive for a user we could no
longer reconcile. deleteUserAccount now refuses with a 409 while a live
subscription would renew (ACTIVE/TRIALING/PAST_DUE and not already set to
cancel at period end), telling them to cancel first. The modal said "any
active subscription will be terminated", which was false; it now says what
actually happens.

ORDERING: R2 assets were deleted before the database transaction, so a failed
transaction left the user with an intact account whose images had silently
vanished. The transaction now runs first - worst case is orphaned R2 objects,
which is waste rather than data loss for someone who still has an account.
Everything after the delete is best-effort via Promise.allSettled, so a failed
cache purge cannot turn a successful deletion into a 500 the user reads as
"it didn't work".

Also: a deleted user's published portfolio stayed served, because nothing
purged the public caches - now captures the subdomain before the delete and
invalidates plus revalidates it. Swapped console.error for the logger. Dropped
the errorMessage override in the studio's deleteAccount service, since
fetchApiData prefers it over the server's own message and would have hidden
the subscription 409 behind "Failed to delete account". Fixed two
text-muted-foreground usages in the new components, which map to no @theme
block and emit no CSS.

Legal copy corrected to match: both documents pointed users to "Account
Settings" for a Danger Zone that lives on the Profile page.

Verified: server tsc/eslint clean, 294/294 server tests, studio and site tsc/
eslint/prettier clean, 39/39 site tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An adversarial review of the previous commits found these. All four were
mine.

CANONICALS ON noIndex PAGES. buildPageMetadata emitted `alternates.canonical`
and `og:url` unconditionally, outside the noIndex branch. The three not-found
boundaries pass `path: "/404"` and `"/roadmap/404"` - routes that do not exist
- so every 404 response advertised a canonical pointing at a URL that itself
404s.

Omitting the key was not enough: Next merges page metadata over the root
layout's, and the root layout sets `canonical: "/"`, so a missing key let that
inherit through and every 404 declared itself the canonical homepage. Now
passes `canonical: null` to clear it explicitly. Verified in the built output:
_not-found.html has robots=noindex, no canonical, no hreflang, no og:url,
while pricing.html keeps its own canonical.

TEMPLATES HERO FAN. The docblock claimed the selection "falls back to catalog
order if a type is ever missing", which was false - `.filter(Boolean)` only
shrinks the array. `rotations[idx]`/`offsets[idx]` are indexed positionally, so
a short array would have interpolated `undefined` into a calc() and collapsed
the layout. Latent rather than live (all three types exist today), but the
comment described a guard that was not there. Implemented the fallback the
comment promised.

Also unified the card sizing. The portfolio branch used an intrinsic
aspect-ratio box while its siblings used `h-full`, so two sizing models shared
one absolutely-positioned stack and the z-order assumption depended on which
type landed in the middle. Both branches now size from the same container
height, and the dead `top-0` class the inline style was overriding is gone.

CONTRACT TESTS THAT COULD NOT FAIL. The two pricing.md tests asserted only
that "signal"/"nimbus" appeared *somewhere* in the file. The test named "keeps
the premium templates on the paid tiers" would still have passed with Nimbus
moved into the free tier - the exact drift this file exists to catch. They now
split pricing.md on its tier headings and assert placement, with a guard test
so an unparsed file cannot make them vacuous. Verified by moving Nimbus into
the free tier and watching the assertion fail by name.

MISLEADING DOCBLOCK. check-design-tokens said its unmapped-token guard exists
because "that is exactly how text-muted-foreground reached 36 usages" - present
tense for a bug already fixed in an earlier commit. Corrected, and noted that
apps/studio still carries ~100 such usages which this script does not scan.

Verified: tsc, eslint, prettier clean, 40/40 tests, check:design passing,
next build generating all 98 static pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`text-muted-foreground` appears ~98 times across the studio's dashboard,
profile, admin, and login views, but `--color-muted-foreground` was mapped
into no `@theme` block. Tailwind therefore emitted no rule for it, and every
one of those elements silently inherited its parent colour instead of reading
as secondary text.

Mapped to `var(--muted)` (#5f5c54 light, #94a3b8 dark), deliberately not to the
shared package's `--muted-foreground`, which is #171717 in both themes. That
token means "text on a muted fill" - a light surface in either theme - so
pointing the utility at it would have put near-black body text on the dark
dashboard. This is the same mapping and the same reasoning already documented
in apps/blog-platform, which hit this exact problem and solved it this way.

One line rather than editing 36 files, and verified in the built output:
`.text-muted-foreground{color:var(--muted)}` is now emitted, along with its
opacity modifiers.

Not fixed here: apps/portfolio has a single occurrence in Toaster.tsx, on a
`bg-muted` cancel button. That app runs an entirely separate palette
(--color-paper/--color-ink) and maps neither token, so both classes on that
element are dead. Mapping muted-foreground to --muted there would also make the
text the same colour as its own background, so it needs a different fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…r table

TRIALS REMOVED FROM THE PRODUCT

createCheckoutSession passed `trial_period_days: 7` to Dodo when the product
was portfolio_pro, the interval was monthly, and the user had never subscribed.
So a trial did exist - narrower than the Terms described, which claimed one for
"Job Hunter Bundle / Creator Pro" when the Bundle never had one. Rather than
correct the wording, the trial is removed entirely as a product decision: the
free tier is how people try VeriWorkly, and trials carry disclosure and
reminder duties under several US state auto-renewal statutes that we would have
to service properly or not offer at all.

Also dropped the `previousSubscription` lookup that existed only to decide
trial eligibility - one database query per checkout for a value nothing reads.

The `trialing` case in statusFromDodo deliberately stays, with a comment. Any
subscription started before this change can still report that status, and
removing the case would map a live trialing subscriber to INACTIVE and revoke
access they paid for.

Copy follows the code: the Terms now state plainly that no plan has a trial,
pricing.md leads with it, and the comparison matrix's "Real free plan (no
expiring trials)" claim is accurate again - it had been softened in an earlier
commit precisely because a trial existed. Contract test added, verified against
a re-added trial.

SUBPROCESSOR TABLE

Rebuilt from what the code actually connects to, not from memory. Two material
disclosures were missing entirely:

- OpenRouter. The AI client is the OpenAI SDK pointed at a configurable
  AI_BASE_URL, which is https://openrouter.ai/api/v1 in both dev and prod, with
  an HTTP-Referer header. So the gateway - not Anthropic or OpenAI directly -
  is the subprocessor that receives your text, and it routes onward to a model
  chosen by private runtime config. The policy previously named the model
  vendors as if we called them directly.
- The SMTP provider (Hostinger) that delivers sign-in codes, purchase
  confirmations, and account-deletion emails. Never listed.

Each entry now states what the provider does, exactly what personal data it
sees, its processing region, and the transfer mechanism. Added a "what is NOT a
subprocessor" section (Better Auth and Redis run on our own infrastructure; no
ad networks, analytics vendors, or session recording exist to list).

Hosting is left named-on-request rather than guessed: deployment is
containerised with no provider config in the repo, and inventing a name is
exactly the failure mode this work exists to prevent. Marked TODO(legal).

RETENTION AND THE EU REPRESENTATIVE

Server logs: replaced "we keep these no longer than 90 days, then rotate them
out" - a specific promise with no rotation job behind it - with what is true.
We store them, they hold request metadata not document contents, there is no
automatic deletion schedule yet, and we say so.

Added a closing note distinguishing account deletion (a real immediate
operation) from the other periods (policies we follow, not timers enforced by
jobs), because a retention period you can hold us to is worth more than one
that reads well.

Article 27 representative: documented the position instead of leaving a gap.
Not appointed, with the reasoning stated openly - an unfunded open-core project,
no special-category data, no advertising or profiling - along with an
acknowledgement that a supervisory authority might weigh it differently, and a
commitment to appoint one if scale or funding changes.

Verified: server tsc/eslint clean and 294/294 tests, site tsc/eslint/prettier
clean and 42/42 tests, next build generating all 98 static pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ture

TRIALING REMOVED ENTIRELY

Trials were removed from checkout in the previous commit, but the status
survived across 28 references - the Prisma enum, every "is this subscription
paying" list on the server, the admin filters and status badge in the studio,
and an `eligibleForTrial` field the billing API still returned. Legacy state
like that is how a removed feature quietly comes back.

Confirmed safe first: no subscription was ever created under the short-lived
Creator Pro trial, so there are no rows carrying the value. Added a migration
that recreates SubscriptionStatus without it - Postgres cannot drop an enum
value in place - with a defensive UPDATE ahead of the ALTER so it cannot fail
on an unexpected row.

Verified by regenerating the Prisma client: TRIALING now appears zero times in
the generated types, which means a leftover reference anywhere would be a type
error rather than dead code. tsc passes.

One `case "trialing"` deliberately remains in statusFromDodo, now mapping to
ACTIVE. We control what we request from the provider, not what its webhooks
send; without the case an unexpected "trialing" event would fall through to
INACTIVE and revoke access from someone who is paying. Mapping it to ACTIVE
fails safe for the subscriber.

INFRASTRUCTURE NAMED HONESTLY

The hosting entry was a placeholder because the provider was not determinable
from the repo. It is a Hostinger VPS, now named, with its processing region
(EU/Lithuania) and transfer basis (EU adequacy - no SCCs needed for EEA
transfers, which is a better position than most of the other entries).

Merged the separate Hostinger SMTP row into it, since it is one vendor, and
corrected the "not a subprocessor" section: Better Auth and Redis run on that
same VPS rather than on abstract "our own infrastructure", so they are covered
by the Hostinger entry rather than being nothing at all.

AI PROVIDERS: NAME THE GATEWAY, NOT THE MODEL

Five surfaces still said "frontier AI models from Anthropic and OpenAI" as
though we hold accounts with them. We do not - every request goes through
OpenRouter, and which model answers is runtime configuration that can change.
Naming specific vendors creates a claim that goes stale silently the next time
that config is edited.

The privacy policy, root layout, siteConfig description, llms.txt, and both FAQ
datasets now name OpenRouter as the subprocessor and describe the model vendors
as reachable through it. The subprocessor entry states plainly that we hold no
direct account with any model vendor.

Verified: server tsc/eslint clean and 294/294 tests, studio and site tsc/eslint/
prettier clean, site 42/42 tests, both apps building.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
STRUCTURED DATA

- Removed the seven-Offer array from the root layout. It shipped pricing
  markup into the <head> of every route including /privacy and /terms, and on
  /pricing it created two competing offer graphs for the same seven products.
  /pricing is now the single source.
- /ats-checker no longer emits FAQPage. Its five questions are selected by id
  from the same array /faq emits in full, so it was publishing verbatim
  duplicate Question/Answer pairs on two URLs - which Google's guidelines call
  out and which risks both pages losing eligibility. The visible FAQ stays; only
  the duplicate markup is gone.
- Added a Person entity with a stable @id, sameAs, jobTitle, description, and
  knowsAbout (moved off Organization, since expertise is a property of people).
  Previously there were three disconnected name-only Person stubs that no parser
  could resolve as one human - the largest EEAT gap in the audit.
- Added a root WebSite node with @id, inLanguage, and publisher linkage.
  Deliberately NO SearchAction: a sitelinks searchbox needs a URL that answers a
  query string, and both /templates and /faq filter client-side by design, so
  declaring one would advertise a deep link that renders the unfiltered page.
- Organization/WebApplication now reference the shared @ids instead of
  re-declaring, and the logo is an ImageObject with dimensions.
- /pricing offers derive offerCount, lowPrice, and highPrice from one array, and
  every offer carries url. All three were hardcoded beside the list, so
  repricing silently produced self-contradicting markup.
- Homepage HowTo steps all carry position and url; two of four had neither.

HEADINGS

- LandingFooter column titles h4 -> h3. They sit under the footer's h2, so this
  was an h2->h4 skip on every page of the site - the most widespread heading
  defect there was.
- AccordionTrigger takes an optional headingLevel. The h3 was hard-coded and
  shared, which is right on /ats-checker (under an h2) and wrong on /faq (under
  the h1). /faq now passes h2: it previously had zero h2 elements in its own
  content, on the page type whose structure matters most for answer extraction.
  Verified in the built HTML - /faq went from 0 to 44 h2 elements.
- /faq's help banner h3 -> h2, and its copy no longer says "talk to our team"
  for what is one person.

TECHNICAL SEO

- Dropped hreflang entirely. It emitted only `en-US`, which is the worst of the
  available options: hreflang is for sites with multiple language or regional
  variants, and a single self-referencing entry conveys nothing - while `en-US`
  additionally region-locks an India-based service with a global English
  audience to United States English. Per Google's spec a one-language site
  should emit none. `inLanguage` corrected to `en`.
- Fixed two broken internal links, both soft-404s: /docs (no such route - docs
  are a subdomain) and /cookies (no such route, and no cookie preference UI
  exists to link to, since only essential cookies are set). The latter now
  points at the privacy policy's cookies section.
- Added Features, How It Works, and Compare to the navbar. A comment there
  claimed this had already been done; it had not, and a comment describing
  absent code is worse than none.
- Removed the images.unsplash.com remotePattern. The two stock photos it existed
  for were replaced with product UI earlier, and every allowed host is one the
  optimizer will fetch on request. Scoped the github.com pattern to /*.png.
- priority on the /templates hero fan, which is that page's LCP element and was
  lazy-loading.

CONTENT

Three FAQ answers contradicted the product, which is the exact class of defect
this work has been about:
- "cancel with one click in your account settings" - cancellation goes through
  the payment provider's portal; there is no in-app cancel.
- The subdomain answer described CDN hosting and DDoS protection in the present
  tense with no mention that publishing is gated, and asserted provider-
  dependent guarantees of the kind already stripped from the security page.
- Portfolio analytics were described as available while publishing is blocked.

Also removed "Empowering job seekers with the most advanced ... 100% free,
forever" from the affiliate footer: filler, an unprovable superlative, and a
free claim that contradicts the paid plans.

Verified in built HTML: no hreflang, Person present, zero Offers on /privacy,
seven on /pricing with derived counts, no FAQPage on /ats-checker.

Gates: site tsc/eslint/prettier clean, 42/42 tests, check:design passing, 98
static pages building; studio and server tsc clean (Accordion consumers).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bump root and workspace package versions to 3.24.2 across apps/site, apps/studio, apps/portfolio, apps/blog-platform, apps/docs-platform, apps/server, and package-lock.json. Update UI shell and not-found build release tags.
@Gautam25Raj Gautam25Raj added documentation Improvements or additions to documentation enhancement New feature or request frontend UI/client-side code backend server-side logic api API-related changes or issues ui/ux Design or user experience improvements performance Performance improvements security Security concerns or fixes labels Sep 1, 2026
Four files failed `prettier --check .` at the repository root: DESIGN.md,
LICENSING.md, the docs-platform ATS checker page, and the changelog seed data.

My verification during this branch ran prettier per-app (apps/site, apps/server,
apps/studio), which never reaches root-level markdown or the docs-platform
content directory - so LICENSING.md, which I authored, shipped unformatted and
CI caught it. Running from the repo root is the check that actually matches CI.

Formatting only. The changelog seed diff is prettier collapsing a six-element
tag array onto one line; all six tags are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Gautam25Raj
Gautam25Raj merged commit dfe1d48 into master Sep 1, 2026
11 checks passed
Gautam25Raj added a commit that referenced this pull request Sep 1, 2026
Documents the release merged in #197: the ATS engine rebuild, the claims
accuracy audit remediation, self-service account deletion, and the legal
and SEO work.

Recorded as a `breaking` item rather than buried under fixes: free trials
are removed from every plan, and TRIALING is gone from the
SubscriptionStatus enum. No subscription was ever created under the trial,
so no existing records are affected, but the capability is gone and the
schema changed.

The `security` section carries five entries, the most significant being
that DELETE /users/me previously accepted any API key holding `user:write`
- the same scope required to rename an account - which put a long-lived
integration token one call away from destroying an entire account.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Gautam25Raj
Gautam25Raj deleted the feat/site-optimization branch September 1, 2026 20:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api API-related changes or issues backend server-side logic documentation Improvements or additions to documentation enhancement New feature or request frontend UI/client-side code performance Performance improvements security Security concerns or fixes ui/ux Design or user experience improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant