Skip to content

Epic 42 — Match Engine (Swipe-to-Match) — replaces Job Apply

Reworks/Removes: Epic 5 (apply pipeline BE-JOB-002, applyToJob, FE-JOBS-012 apply wizard), Epic 5 BE-JOB-003 swipe semantics, Epic 6 (Discover), Epic 19↔Shortlist relationship, and Epic 36 (Applications Dashboard → Matches). Decision (user, 2026-06-16): Remove "apply" entirely. Casting becomes a bidirectional swipe: producers swipe talents, talents swipe jobs — Like or Nope only (no Skip). A Match forms when both sides Like in the same job context. Shortlist and Folders are the same thing — remove "shortlist" from jobs; Folders (Epic 19) is the single save concept. When a producer Likes a talent they pick one or more folders to save them; producers can swipe in a job context or "just scouting" (scouting Like = folder-save only, no match). Remember the global rule: talent = Talent + Pet Owner (pet jobs included).

**Status (2026-06-17): 🟢 Backend COMPLETE (both axes); frontend CORE done. BE commits: 92528d8, 5ed2142, a0dd2ac, e12c313. FE commits: 1894a1c (talent like + matches page + remove apply/shortlist), b722a24 (producer per-job match management), 6d07182 (pet-owner likePetJob). 556 FE tests green. FE COMPLETE for every actor (added 5c7b167 producer pet-matches panel, 6680838 producer talent-swipe → likeTalent + folder picker, 1a80a5a pet-owner /pet-matches page + nav, ccf8eb2 discover-grid Like/Nope). Only follow-ups left: in-system casting CAPTURE (Epic 43 ships request only), and harmless dead-FE-hook removal. Backend cleanup of jobApplications/updateApplicationStatus is NOT a clean win — those resolvers still conceptually serve Groups/Agency applicants (which create Applications, out of scope); Job.myApplication is still wired into the talent job detail. Leave them as unused-but-retained endpoints until Groups/Agency apply is reworked. Stage 3 done = full PetOwner↔PetJob mirror (PetJobInteraction/PetInteraction/PetMatch + tryFormPetMatch + likePet/nopePet/likePetJob/nopePetJob + myPetMatches/petJobMatches + remove applyToPetJob/myPetJobApplications + migrate-petjob-applications-to-matches.ts). Epic 43 backend lifecycle done (see Epic 43). Stage 4 = the frontend (only remaining piece): producer swipe UI, talent JobsSwipePage, pet-owner swipe, match-management pages (talent/producer/pet), remove apply wizard + ShortlistPage + MyApplicationsPage→MyMatchesPage, i18n, tests. Also still pending: full in-system casting CAPTURE (Epic 43 ships the request only), and removing the now-superseded producer-side apply surfaces (jobApplications/updateApplicationStatus/applicationMatch/Job.myApplication + pet equivalents) — bundle that cleanup with the FE. Being shipped in tested, coherent sub-stages: (1) ✅ additive backend foundationMatch model + JobInteractionType.LIKE + services/match.tryFormMatch + likeTalent/nopeTalent/likeJob/nopeJob + myMatches/jobMatches + MATCH_CREATED, all ALONGSIDE the still-working apply flow (migration epic42_match_engine, additive); (2) ✅ removed the talent↔job apply pipeline — deleted applyToJob/myApplications/jobSwipeShortlisted, rewired discoverJobs/jobsForTalent to exclude already-swiped (not applied), inviteTalentsToJob now LIKE+match, scripts/migrate-applications-to-matches.ts back-fills ApplicationMatch (Application table retained; 411 rows migrated locally). NOTE: producer-side jobApplications/updateApplicationStatus/applicationMatch/Job.myApplication were KEPT — they're replaced by the Epic 43 match lifecycle; (3) ⏳ mirror everything for the PetOwner↔PetJob axis (user: both axes this pass); (4) ⏳ frontend swipe UIs + remove apply/shortlist UI.

Implementation decisions (locked during build): (a) Non-destructive enum reinterpretation instead of renaming — the producer LIKE/NOPE keep the existing DB enum values TalentInteraction.SHORTLISTED/SKIPPED (exposed as LIKE/NOPE in the API) to avoid a risky Postgres enum-rename migration; talent LIKE is the new additive JobInteractionType.LIKE. (b) Full removal + both axes (user, 2026-06-17). (c) Application table is RETAINED for history (never dropped); Groups apply + Agency bulk-apply keep working on it (separate features, out of these epics' scope — flagged as a follow-up). (d) markHired/bounty-release moves from updateApplicationStatus onto the Match in Epic 43.

Why: Today a talent gets into a producer's pipeline via applyToJob (the Application model) while producers shortlist via TalentInteraction.SHORTLISTED; there is no mutual "match". The stakeholders want a symmetric, dating-app-style match flow. This epic introduces the Match model and the Like/Nope swipe on both sides, makes the producer Like save into Folders (killing the separate shortlist concept), removes the apply path, and migrates existing applications/shortlists into matches so no data is lost. Match lifecycle (reject/casting/casted/hired/message) and in-system casting live in Epic 43.

Architecture: Reuse the AI matching scores (src/services/ai/matching.ts scoreDiscoverFeed/scoreDiscoverJobs) for ordering both swipe stacks. Redefine the two existing swipe models to LIKE | NOPE (producer→talent TalentInteraction; talent→job JobInteraction, which today lacks a "like"). New Match(jobId, talentId, producerId, source, status, …) row is created idempotently the moment both a producer-Like (for that job) and a talent-Like (of that job) exist; both parties are notified. Scouting = producer Like with jobId = null → writes FolderEntry rows only (no Match). The producer-Like folder pick reuses Folders (ProducerFolder/FolderEntry, SaveToFolderButton). All swipe gating from Epic 40 keeps the talent swipe free.

BE-MATCH-001 — Swipe model overhaul (Like/Nope, both directions)

Files:

  • Edit: castyou-backend/prisma/schema.prismaInteractionTypeLIKE | NOPE (migrate SHORTLISTEDLIKE, SKIPPED/BLOCKEDNOPE with optional expiresAt for block-style hides); JobInteractionTypeLIKE | NOPE (migrate SKIPPEDNOPE, keep hide-expiry semantics). Requires prisma migrate ([[CasTyou Tech Stack]]).
  • Edit: castyou-backend/src/graphql/resolvers/job.ts — replace swipeTalent actions with LIKE/NOPE; replace swipeJob with LIKE/NOPE.

Acceptance criteria: Both swipe models accept only LIKE/NOPE; existing interaction rows migrate cleanly; no "skip" path remains.

BE-MATCH-002 — Match model + mutual-match formation

Files:

  • Edit: castyou-backend/prisma/schema.prisma — new Match { id, jobId, talentId, producerId, source (PRODUCER_FIRST|TALENT_FIRST), status (ACTIVE), createdAt, updatedAt, @@unique([jobId, talentId]) } (+ lifecycle fields added in Epic 43). Requires prisma migrate.
  • Create: castyou-backend/src/services/match/index.tstryFormMatch(jobId, talentId) called from both Like mutations: if a producer-Like (this job) and talent-Like (this job) both exist and no Match row exists, create one + notify both (new MATCH_CREATED notification type, [[Notifications vs Messages]]).

Acceptance criteria: A producer Like + talent Like on the same job creates exactly one Match and notifies both sides; a one-sided Like creates no Match.

BE-MATCH-003 — Producer swipe surface (job + scouting) with folder-save

Files:

  • Edit: castyou-backend/src/graphql/resolvers/job.ts — keep job-scoped discoverFeed; add a scouting feed (jobId null) for browsing talents to save.
  • Edit/Create: producer Like mutation likeTalent(talentId, jobId?, folderIds: [ID!]!) — requires ≥1 folder; writes FolderEntry for each chosen folder (reuse src/services/folders), records the TalentInteraction.LIKE, and (if jobId) calls tryFormMatch. nopeTalent(talentId, jobId?) records NOPE.

Acceptance criteria: Producer Like with no folder selected is rejected; Like saves the talent into every chosen folder; job-context Like attempts a match; scouting Like never creates a Match.

BE-MATCH-004 — Talent swipe-jobs surface (free)

Files:

  • Edit: castyou-backend/src/graphql/resolvers/job.tslikeJob(jobId) / nopeJob(jobId); likeJob records JobInteraction.LIKE and calls tryFormMatch; ordering via discoverJobs/scoreDiscoverJobs. This path is exempt from the Epic 40 paywall.

Acceptance criteria: Any talent (free included) can Like/Nope jobs; a Like on a job a producer already Liked them for forms a Match.

BE-MATCH-005 — Remove apply pipeline + fold shortlist into folders (with data migration)

Files:

  • Edit: castyou-backend/src/graphql/resolvers/job.ts — delete applyToJob, jobApplications (apply-based), jobSwipeShortlisted, and the myApplications SHORTLIST/APPLICATION feed; remove ApplicationStatus.SHORTLISTED usage. Replace myApplications with myMatches (talent) and per-job match list (producer) — implemented in Epic 43.
  • Create: castyou-backend/scripts/migrateApplicationsToMatches.ts — convert each non-rejected Application → a Match (source: TALENT_FIRST); convert each TalentInteraction.SHORTLISTED (job-scoped) → producer-Like (+ optionally a Match if the talent had applied). Preserve HIRED state. Keep the Application table for history (no destructive drop), but stop writing to it.
  • Edit: castyou-backend/prisma/schema.prisma — mark Application/ApplicationStatus as deprecated (comment); do not drop.

Acceptance criteria: No applyToJob mutation remains; the "shortlist" concept is gone from jobs (Folders is the only save); existing applications + shortlists appear as matches after migration; HIRED history survives.

FE-MATCH-001 — Producer swipe UI (job + scouting + folder picker)

Files:

  • Create: producer swipe-stack page — Like/Nope (no skip); a job-context vs scouting toggle; on Like, a required folder-picker modal (reuse components/folders/SaveToFolderButton / folder list, ≥1 folder).
  • Edit: nav + lib/pageTitles.ts (+ i18n en/pt/es).

Acceptance criteria: Producer can swipe in a job or scouting; Like always asks for ≥1 folder; everything is sourced from @castyou/design-system ([[Design System Rule]]).

FE-MATCH-002 — Talent swipe-jobs UI (rename Discover → Jobs swipe)

Files:

  • Edit/rename: castyou-frontend/apps/app/src/pages/discover/DiscoverPage.tsx → a JobsSwipePage (Like/Nope); this is the free talent jobs entry.
  • Edit: nav, lib/pageTitles.ts, i18n.

Acceptance criteria: Talent swipe jobs with Like/Nope; page is reachable on free tier.

FE-MATCH-003 — Remove apply + shortlist UI

Files:

  • Delete/repoint: apply wizard (FE-JOBS-012), pages/jobs/ShortlistPage.tsx, shortlist sections of job applicants; convert MyApplicationsPageMyMatchesPage (Epic 43). Remove useMyApplications/useSwipeTalent SHORTLIST paths.

Acceptance criteria: No "Apply" or "Shortlist" UI remains; talent sees Matches; producer sees per-job matches (Epic 43).

TEST-MATCH-001 — Match engine coverage

Acceptance criteria: Like/Nope on both models, mutual-match formation (and non-formation), required-folder on producer Like, scouting=no-match, the free-swipe exemption, and the applications→matches migration are all tested.