Skip to content

Latest commit

 

History

History
356 lines (231 loc) · 35.9 KB

File metadata and controls

356 lines (231 loc) · 35.9 KB

Part of the Taskly reference library — see README.md for the doc index. This is the end-to-end product spec: what Taskly is, who uses it, and every feature along both journeys, as actually shipped in code. Where a marketing doc describes an aspiration, this file describes the running product. Canonical tie-breakers on conflict: ARCHITECTURE.md (identity/payments system-of-record), BUSINESS-MODEL.md (operating model), BRAND-VOICE.md §5 (honesty boundary).

Taskly.ca — Product Overview

The get-it-done network for the Greater Toronto Area. Taskly is a two-sided home-services task marketplace: customers describe a task in plain words, vetted Taskers make offers, and an accepted offer becomes an escrow-held booking. Taskly keeps a flat 20% commission on completed jobs; the Tasker keeps ~80%. A curated set of fixed-price "vetted flows" sits alongside the open marketplace in code but is dark at launch (see §2.4).

One Next.js 16 app serves every surface — landing pages, the post-a-task funnel, the customer dashboard, the Tasker workspace, and the admin console — gated by Supabase auth + row-level security, paid through Stripe Connect, and assisted throughout by a Groq LLM layer that never dead-ends the user.


Table of contents

  1. Who uses Taskly
  2. Core concepts
  3. The Customer (Poster) journey
  4. The Tasker journey
  5. The shared job lifecycle
  6. Money, fees & escrow
  7. The AI layer
  8. Trust, safety & moderation
  9. Notifications & email
  10. Community (Spaces)
  11. Admin console & operations
  12. Honesty guardrails — what Taskly does & doesn't claim
  13. Feature index

1. Who uses Taskly

Taskly has one account type with three faces:

Persona Who they are Where they live in the app
Poster (Customer) Anyone with a task to get done. Every member is a customer by default. /app/** — the customer dashboard
Tasker A member who has been reviewed and approved to earn. "Tasker" is a capability, not a separate account — any customer can opt in. /provider/** — the Tasker workspace
Admin Taskly staff who review tasks & Taskers, resolve disputes, and run ops. /admin/** — the admin console

A single person can be both a poster and a Tasker on one login — they post tasks in /app and earn in /provider. The only hard fork is admin, who is bounced out of the customer/tasker shells.


2. Core concepts

2.1 Two roles + a Tasker capability

profiles.role is only customer or admin. The old provider role was collapsed into customer (migration 042). "Tasker" is a capability derived from a provider_profiles row, never a role.

  • getTaskerStatus(supabase, userId)none | pending | approved | suspended | rejected (lib/api/auth.ts).
  • getCapabilities() is the single source of truth used app-wide (via the useCapabilities hook): { role, isAdmin, isMember, isTasker, taskerStatus, canOffer, requireApproval, identityFeePaid, identityStatus }.
  • Three independent gates that are easy to conflate:
    • Approval — can this person appear as a Tasker & make live offers? (needs a profile photo + at least one skill).
    • Identity — Stripe Identity gov-ID + selfie ($5 fee, waived for Founding Taskers). Gates payout, not approval.
    • Payout rail — a Stripe Connect Express account with payouts_enabled.
  • user_metadata is only for cosmetic/onboarding flags — never trusted for gating.

2.2 The open marketplace: post → offers → hire → escrow → release

The primary, live product path:

Customer posts a task  →  admin approves it  →  it goes live on the feed
   →  Taskers make offers (bids)  →  customer picks one & pays into escrow
   →  Tasker does the work (door-OTP verified)  →  customer confirms
   →  escrow releases 80% to the Tasker, Taskly keeps 20%

Backed by the tasks + task_bids tables and lib/marketplace/**. All money is integer cents.

2.3 The hybrid offers→fixed pricing model

Bidding's fatal flaw is a poster who waits and gets no offers. Taskly solves it with a hybrid model (lib/marketplace/pricing-mode.ts):

  • Task > 2 days out: opens for offers; auto-converts to a fixed price for the final 2 days (fixed price = median of real offers received). The customer can accept any offer anytime.
  • Task ≤ 2 days out / ASAP: straight to fixed price (no time to bid).
  • An always-present customer toggle: Compare offers vs Book now at fixed price.

Launch note: the auto-convert cron is currently disabled — every posted task stays pricing_mode='offers' and open for offers. The founder concierges/hand-matches the fixed-phase long tail until first-come-first-served liquidity exists.

2.4 Two lanes — only one is live at launch

  1. Open marketplace (tasks / task_bids) — free-text tasks, poster picks one offer. This is the live launch experience.
  2. Curated fixed-price "vetted flows" (UC) (lib/booking/**, the /book wizard) — 9 curated verticals with catalog pricing. Gated behind UC_ENABLED (lib/flags.ts), which is OFF. Both curated surfaces redirect('/app') at launch. Documented here for completeness but not part of the live journey.

The 9 curated verticals (used live only for the AI taxonomy roll-up and Tasker skill selection): Cleaning, Maintenance/Handyman, Cooking, Seasonal, PSW for Seniors, Tutoring, Organizing, Salon & Spa, Pet Care.


3. The Customer (Poster) journey

3.1 Discovery — landing & marketing

The home page (app/page.tsx) is an Airtasker-style "post any task" landing: a HeroPostTask "describe your task" entry, a live TaskFeed for social proof, HowItWorks, SafeByDesign, MeetOurPros, an EarnAsTasker cross-sell, and a closing CTA. Signed-in users are redirected to their dashboard (with a /?home escape hatch).

Supporting marketing routes: the-taskly-advantages, pricing / cost / price-guide, how-it-works, browse-tasks (public feed preview), reviews, compare, services, posters, blog, about, contact, support, plus legal (terms, privacy) and SEO landing directories.

3.2 Sign up / sign in

One page (app/login) handles every auth state. Default tab is Create Account (most arrivals are new posters). Copy adapts when the user is mid-post ("Your task is saved — create a free account to post it live").

Methods:

  • Email + password/api/auth/signup requires full name, a Cloudflare Turnstile token, and a Terms/Privacy consent checkbox → a 6-digit OTP emailed and verified in-app.
  • Google OAuth/auth/callback (PKCE).
  • Passwordless magic code — "Email me a code instead" (also works for WhatsApp-bot-created accounts).
  • Forgot password — 6-digit recovery code → set a new password.

Deep-link niceties: ?email= pre-fills, ?next / ?redirect round-trips through the whole auth flow, expired links surface a "send a fresh code" nudge. On signup a profiles row + customer_profiles row are auto-created by a DB trigger. Post-auth routing: explicit ?next wins, else admin → /admin, Tasker → /provider, everyone else → /app.

3.3 Posting a task — the primary funnel (app/post-task/**)

A 5-step wizard (PostTaskProvider + steps/Step*.tsx). The draft persists to localStorage (24h TTL) so it survives a refresh and the anonymous→login round-trip; the step lives in the URL (?step=N) so browser Back works; every step emits funnel analytics (/admin/funnels).

Validation floors: title 6–100 chars, description 25–6000 chars, budget $10–$50,000 (all cents).

Step Screen What's collected AI assistance (all degrade gracefully)
0 Your task (StepDescribe) Title, description, up to 6 photos (≤20MB, task-photos bucket) AI description draft (/api/suggest-description), Gmail-style inline ghost-text (/api/complete-description), one-tap "Clean up my words" rewrite (/api/enhance-task), tap-select scope pills (/api/suggest-scope), live category classification. AI image generation ("Nano Banana", /api/generate-image) offered when the task needs a photo and none is attached.
1 Category & skills (StepCategorySkills) Category + skills (≤12), pre-filled from AI, editable, optional Live AI label wins until the poster picks manually
2 Where & when (StepWhereWhen) Mode (in-person/remote), address autocomplete (lat/lng fuzzed to ~110m) OR "flexible location" OR remote; timing (asap/flexible/on_date/before_date) + date + optional time window Mode auto-prefilled from category
3 Your budget (StepBudget) — the USP screen Budget (AI-seeded, never overwrites a poster-set amount); boost toggles Live market estimate as a low–high band (/api/suggest-budget/api/predict-price with a "why this price" breakdown)
4 Review & post (StepReview) Final editable everything + a live preview card Inline /api/enhance-task with Undo

Step-3 boost toggles: Urgent (+$5, ⚡ badge, collected only on hire so posting stays free), Tasker brings materials (educational, no fee), Licensed professional required, Repeat booking welcome, Photo proof required (before/after photos gate escrow release).

Autopost across auth: an anonymous poster who hits "Post it live" is sent to login and the wizard auto-completes the post on return.

On submit (POST /api/tasks): client + server content moderation (see §8), contact-info stripping, length/budget validation, UTM attribution stamping, and a computed poster_name ("First L."). The task lands in the tasks table with status='pending_review'hidden from the feed until an admin approves it (→ open). This is a deliberate divergence from older docs: posts are curated before they go live.

The success screen offers a referral nudge and an SMS-updates opt-in.

3.4 The customer dashboard (app/(customer)/app/**)

At launch the dashboard renders PosterHome:

  • TaskComposer — a ChatGPT-style auto-growing textarea with a rotating placeholder, an "Enhance with AI" button, and a Post button that hands off to the wizard.
  • ActiveBookingsBand — live in-flight jobs with status pills and the inline OTP arrival code when the Tasker is arrived.
  • ResumeOnboardingCard (resume a WIP Tasker application), PushPrompt (web-push opt-in), UpcomingList, StartEarningCard (become-a-Tasker upsell), PromoBanner (refer-a-friend), TrustStrip.

My Tasks hub (/app/bookings) — unified activity with 5 count-badged tabs: Posted (live tasks), Under review (pending_review), Needs changes (rejected, with the rejection reason + "Fix & repost"), Assigned (active bookings), History (past bookings, with rebook + review).

Post detail (/app/my-posts/[id]) — the poster's control center for one task: status narrative + a TaskJourney progress rail, stats (Offers / Views / Budget), full details, the Offers section, and public Q&A.

3.5 Receiving & choosing offers (OffersDrawer)

Every offer (BidView) shows the Tasker's avatar (gold ring + check if vetted), name, a trust line (★ rating + review count or a "New Pro" badge, jobs done, online-now / "replies in ~Xm"), the pitch message, portfolio thumbnails, the offer price, and a BudgetChip framing it vs the poster's budget ("$20 under" / "On budget" / "$X over").

  • Ranking: offerScore blends within-budget, rating, jobs, verified, and price-fit → a "Best value" badge; sort by Best / Lowest / Top rated.
  • A client-side shortlist (bookmark/star) with a "Saved" filter.
  • Per-offer actions: Message (pre-hire chat), Save, Accept (→ Hire & pay), and Dismiss (a soft hide — the Tasker is never told; restorable). A real rejection only happens when the poster hires someone else.
  • A profile "peek" sheet shows the full case: rating / jobs / on-time / experience, bio, skills, recent work, link to the public profile.
  • Q&A (TaskComments) — a public per-task thread with realtime updates; anonymous questions are stashed across sign-in and auto-posted on return.

3.6 Hire & pay

"Accept & pay" (/api/tasks/[id]/accept-and-pay) does not accept the bid yet — it validates (poster owns task, bid active & not stale, not self-bid, Tasker payouts-ready, same-day double-booking soft-warn) and mints a pending marketplace booking. The customer then goes through a hosted Stripe Checkout (/api/stripe/marketplace-checkout) — the full deal is captured immediately into platform escrow (not a pre-auth, because tasks can be weeks out), with allow_promotion_codes on for coupons/referral credit. Only when payment succeeds does the Stripe webhook accept the bid and assign the task ("resolve before release") — so the task stays open and others keep bidding until the poster actually pays. A 100%-off ($0) booking skips Stripe entirely.

3.7 Referrals, profile & public identity

  • Referrals (/app/profile/referrals) — "Give $25, Get $25." A shareable taskly.ca/r/<CODE> link (copy / WhatsApp / SMS), a stats grid (Signed up / Available credit / Friends booked). Credit auto-applies as a Stripe promo at checkout.
  • Profile hub (/app/profile) — identity panel (avatar, name, bookings count, customer rating), a capability-aware Earn row, a dark-mode toggle, and Account / Rewards / Support menus (addresses, payments, notifications, edit).
  • Public member profile (/u/[id]) — the public face shown in offers when a Tasker doesn't yet have a /pro/<handle> page.

4. The Tasker journey

4.1 Recruiting

  • /taskers — the canonical Pro recruitment landing, framed for a Pro burned by lead fees: "No lead fees, 20% on completed jobs, paid through escrow." Sections walk through what ends (lead fees), how it works, a worked math example ($150 cleaning → 20% take → "there's nothing else"), how escrow pays out, and the safety net. Copy is honesty-clean — identity-verified via Stripe Identity is real; no background-check / insurance / guarantee claims.
  • /apply → redirects to /onboarding/tasker (auth-first door).
  • /join → permanent 308 redirect to /taskers (the old page carried fabricated stats and was retired).

4.2 Becoming a Tasker — onboarding

The door /onboarding/tasker routes by state (anonymous → login; approved/pending → straight to /provider; member with no row → the wizard). The 3-step wizard (/onboarding):

  1. Identity — full name, date of birth (must be 18+), phone with OTP (Twilio, locked to Canada +1 at launch).
  2. Skills + experience + credentials — a 9-category skill taxonomy (mirrors the curated verticals) with subcategories and granular task registries, chosen via an Uber-style type-ahead (SkillSearch, backed by /api/suggest-skills for clean canonical labels). Experience level (New / Skilled / Expert). Conditional credentials collected inline: cooking → food-handler cert; electrical/plumbing → trade licence numbers (309A/442A electrician, 306A plumber).
  3. Location — city/neighbourhood, lat/lng, service radius or discrete zones (Google Maps autocomplete).

Verification checklist (/onboarding/verify): profile photo (client-side quality validation + circle crop), identity verification via Stripe Identity (gov ID + selfie, $5 fee waived for Founding Taskers, deferrable), conditional professional certifications (up to 4 files), and availability. On submit, a provider_profiles row is created with status pending.

The approval bar (lib/marketplace/approval.ts, canApprove()): a profile photo + at least one skill/service category. Identity and trade-licence verification are separate downstream gates.

The TaskerGate upsell — when a member who isn't yet a Tasker taps a Tasker-only CTA (e.g. "make an offer"), a BecomeTaskerModal pops instead of a hard redirect, stores a returnTo, and routes them through onboarding — then resumes the original action after approval.

4.3 The Tasker workspace (app/(provider)/provider/**)

Gated on getTaskerStatus ∈ {approved, pending}. ProviderShell nav: Home, Browse, Community, Messages, Earnings (+ Account).

  • Pending Taskers see a PendingApprovalView — a 5-step journey (Profile submitted → Payment received → Verification → Training → Go live), a 24–48h ETA, and locked preview tiles.
  • Approved Taskers see ProHome — an online toggle, a this-week net-earnings ribbon + sparkline, and stats (rating, jobs completed, completion rate, response time).

Browsing & offering: /provider/browse reuses the customer feed (the Tasker is a non-owner on every task, so the task detail opens the offer composer). Offers (task_bids, lib/marketplace/bids.ts): moderation precheck, amount floor $5 / ceiling $50,000, task must be open and not their own. A QuickApplyModal is the fast path for anonymous/ineligible visitors (asks location instead of skills; skills seed from the task on bid). The offer/jobs UI always shows payout after the 20% fee.

4.4 The licensed-task bid gate (Path A)

Migration 133 couples three DB-enforced mechanisms:

  1. AI-driven licensed gate — the classifier (not a poster toggle) sets tasks.requires_licensed_provider + required_trade (electrical|plumbing|gas|hvac|null). checkCredentialGate() requires a Tasker to have an uploaded, non-expired licence for that trade to bid (a null trade accepts any uploaded licence). Uploaded is enough to bid; admin verifies asynchronously. A failed gate returns 403 certification_required, which drops the Tasker into the cert-collection step.
  2. Hold-until-approved — a pending Tasker's bid is held_for_review: invisible to the poster and excluded from the offer count until admin approval un-holds it (and notifies the poster). A pending Tasker can't un-hold their own bid.
  3. Rejection feedback loop — a rejected Tasker sees why (rejection_reason, rejected_fields) on their dashboard and can fix + resubmit (rejected → pending self-transition allowed; all other self-status changes blocked).

4.5 Managing jobs

/provider/jobs — tabs Available / Upcoming / Completed. Available cards have Accept / Decline and show payout "after Taskly fees."

Job detail (/provider/jobs/[id]) — the workflow shape depends on booking type:

  • Marketplace in-person (6 steps): accepted → en_route → arrived → otp_verify → in_progress → completed (door OTP, no photo documentation).
  • Marketplace remote (3 steps): accepted → in_progress → completed (no address/OTP; a session-confirm handshake instead).
  • Curated (8 steps): adds before/after photos (dark at launch).

Key Tasker mechanics: OTP arrival verification (customer reads a 4-digit code; submitted with best-effort GPS for a soft geofence that never blocks), realtime location sharing while en-route (customer watches the Tasker move on a map), Taskly-brokered calls/messages/video (contacts stay private), Navigate (Google Maps directions), "Can't make it — release job" (returns it to the pool pre-work), reschedule (propose/confirm/decline), safety reporting, and a dispute-response card.

4.6 Getting paid

  • Fee math (lib/marketplace/fees.ts): poster pays the deal (+$5 if urgent); commission = 20% (HST-inclusive; Taskly remits the embedded HST); Tasker payout = ~80%. The Tasker's payout is never reduced by coupons — discounts come off Taskly's take first, then out of Taskly's pocket (a $0 booking still pays the Tasker in full).
  • Stripe Connect (/api/provider/stripe/connect) — an Express account (country: CA, individual, card_payments + transfers). Mirror columns (stripe_payouts_enabled, etc.) are written via service-role and surfaced in the Earnings page.
  • Escrow release (lib/marketplace/release.ts) — transfers the Tasker's 80% to their Connect account via source_transaction on the captured charge, idempotently keyed so exactly one transfer fires. Triggered by poster confirmation, a 3-day auto-release cron, or admin dispute resolution. Chargeback clawback debt is auto-deducted from the next payout; coupon-subsidized shortfalls are absorbed from the platform balance.
  • Earnings page (/provider/earnings) — money buckets (In escrow / Needs attention / Paid out), period tabs (Today/Week/Month) with a NET headline, gross billed, Taskly fee · 20%, charts (earnings, best-times heatmap, category, top zones), and a link to /provider/tax-summary (per-year CSV export).

4.7 The Tasker profile

  • Editor (/provider/profile/public) — builds the shareable /pro/<handle> page (link + QR): headline, about/bio, languages, skills, interests, experience, portfolio photos, project embeds, social links, service areas, availability, publish toggle.
  • AI enhancement/api/enhance-bio (first-person About, invents nothing), /api/enhance-headline, /api/parse-resume ("Import from CV" — PDF/text → structured fields, extract-only).
  • Enrichment — LinkedIn OAuth import, Google Places (business autocomplete + place_id), portfolio/project uploads.
  • Public profile (/pro/[handle]) — headline/about/skills/languages/portfolio/experience + service-area map, share hub, OG image, visiting card.

5. The shared job lifecycle

bookings.status: pending → assigned → accepted → en_route → arrived → in_progress → completed → paid, with cancelled / disputed branches. Every transition appends to booking_timeline (realtime). Marketplace bookings start pending and are only accepted once payment captures.

From the customer's booking detail (/app/bookings/[id]): a live status banner + Uber-style LiveTracker, a provider card (Call / Message, contacts private), a LiveTrackingMap while en-route, the arrival OTP code, a live checklist + service-photo gallery, the status timeline, and — on completion — a Confirm & release escrow banner (or 3-day auto-release) and a review CTA (two-way ratings). Actions: Report a problem (DisputeModal → holds escrow), Safety issue, Report damage (within 14 days), Reschedule (mutual-agreement), Cancel (marketplace cancellable through in_progress).


6. Money, fees & escrow

All money is integer cents CAD. Two distinct fee engines exist; only the marketplace one is live:

A) Marketplace commission (lib/marketplace/fees.ts) — LIVE

  • COMMISSION_RATE = 0.20 (HST-inclusive), HST_RATE = 0.13, URGENT_FEE_CENTS = 500.
  • Poster pays the deal (+ urgent fee); Tasker gets ~80%; Taskly keeps 20% and remits the embedded HST.
  • Coupons discount Taskly's take, never the Tasker's payout; a $0 booking still pays the Tasker in full.

B) Curated "Trust & Support" fee (lib/booking/pricing.ts) — DARK (UC_ENABLED off)

  • Single Trust & Support fee = 8% of the post-discount base, clamped $5.99–$24.99 (replaced the old regressive $5.99 platform + $9.00 convenience pair).
  • SAME_DAY_SURCHARGE_CENTS = 2500 (snow exempt). Recurring discounts: weekly 20% / bi-weekly 15% / monthly 10% (capped at 10% for wage-sensitive PSW/cooking/tutoring). HST on base + fee.

Escrow (Stripe Connect Express, API pinned 2026-03-25.dahlia):

  • Marketplace = capture up front into platform escrow → transfer 80% on release. Curated = pre-auth → capture on completion.
  • 3DS step-up forced on charges ≥ $500.
  • "Resolve before release": auto-release (AUTO_RELEASE_DAYS = 3) only fires if no dispute or chargeback is open. An atomic DB claim (claim_booking_for_money_movement, SELECT … FOR UPDATE) + a Stripe idempotency key close the release-vs-dispute race so exactly one transfer happens.
  • Chargeback lifecycle — freezes the payout, opens an internal dispute, and on a lost chargeback claws the money back from the Tasker's Connect transfer or tracks it as debt against their next payout.
  • Admin dispute resolution — three outcomes: release (payout in full), refund (poster made whole, booking cancelled), or partial (split), each with outcome-specific idempotency keys.
  • OTP door codes — a 4-digit otp_code per in-person booking; remote work uses a session-confirm handshake.

Full detail: ARCHITECTURE.md §16, docs/PAYMENTS_AND_DISPUTES.md, PRICING_v3.md.


7. The AI layer

LLM features run on Groq with a universal principle: AI never dead-ends the user — every route returns a graceful fallback (503 or fail-open) when GROQ_API_KEY is missing, and the client silently degrades to a manual path.

  • Model tiering: 70B (llama-3.3-70b-versatile, deterministic temp:0/seed:42) for anything that gates a flow — classify, moderate, resolve, enhance-task; 8B (llama-3.1-8b-instant) for cheap cosmetic rewrites — titles, ghost-text, bio/headline, comment moderation.
  • The classifier spine (classifyTask(), lib/classify/**) is the single source of truth: crisis check → Layer-0 pre-check → in-memory cache → shared Redis cache (30-day TTL, so a task classified on any instance costs one Groq call globally — the cost scaler at 10k+ users) → one Groq call → validate every candidate against the locked taxonomy → route by confidence (thresholds HIGH=75 / LOW=45 / GAP=15) into auto | options | confirm | disambiguate | split | coming_soon | ask | block | review | crisis.
  • The taxonomy anti-hallucination spine (lib/taxonomy/**, TAXONOMY_VERSION v3-2026-06-13) — ~131 categories / ~1,376 subcategories. One merged structure builds both the classifier prompt and the validator so they can never disagree. The model can only ever emit a locked (category, subcategory) pair; disabled categories surface as coming-soon + demand-log. An embedding pre-filter sends only the top-K relevant categories to keep token cost down.
  • Deterministic pricing — the model never prices. Structured intake taps ("scope pills") drive a deterministic rate-table lookup × a postal-code region-tier multiplier. /api/suggest-budget reuses the classifier cache (often zero extra Groq calls); a Gemini live-pricer covers long-tail categories with no learned rate.

Per-surface AI features: description drafts & Gmail-style ghost-text, "clean up my words" rewrites, live category suggestions, market price estimates with a "why this price" breakdown, AI image generation for tasks, Tasker bio/headline enhancement, resume import, and voice transcription. Full prompt catalog: AI_PROMPTS.md.


8. Trust, safety & moderation

Content moderation is layered (lib/moderation/**):

  • Layer 0 — deterministic pre-check (0ms, no AI): flags too-short/gibberish text (→ ask for detail), strips contact info (phone/email/URL/@handles replaced with ▮, so deals stay on-platform), and truncates over-long input. Never blocks.
  • Crisis path (looksLikeCrisis()) — self-harm language is checked first and is never a policy violation; it returns supportive copy citing the 988 Suicide Crisis Helpline + 911 and triggers human follow-up.
  • Layer 1 — Groq gate (/api/moderate-task, 70B): judges the requested action, not vocabulary. Four verdicts — allow / ask_details / review / block — with explicit prohibited categories (drugs, weapons, violence, sexual services, fraud, hacking, harm to children/vulnerable, animal cruelty) and gray zones that route to human review (default-deny on anything off-schema). Regulated trades are allowed but flagged requires_licensed_provider. Fails open (allow, still stripping contact) when the key is down — legit posters are never hard-blocked by an AI outage.
  • Comment moderation (public Q&A / messages) judges interpersonal conduct and fails open.
  • Image moderation — a vision check on uploads.

Only block / crisis (and genuinely fragment-short text) hard-stop a post; review is allowed to post and lands in the admin Task-review queue.

Human safety layer: manual Tasker review & approval, Stripe Identity verification, door-OTP arrival codes, masked/brokered calls, two-way ratings, damage claims, safety reports, and a real person decides disputes.


9. Notifications & email

  • In-app notifications (lib/notifications/create.ts) — a ~40-type enum written via service-role; critical types (money, security, disputes) always send, others respect the user's notification_preferences. A high-value subset also fires Web Push (VAPID). Rows land in notifications and update live via Realtime.
  • Email transport (lib/email/send.ts) — Resend HTTP API first (the reliable serverless path), Gmail SMTP fallback (IPv4-forced), no-op if neither is set. Every send carries a CASL List-Unsubscribe header.
  • ~40 transactional templates — customer (welcome, OTP, booked, receipt, review request, task approved/rejected), Tasker (application received/approved/rejected, new offer, job paid, payout failed, identity verified/rejected), and admin (new-task-submission, chargeback, safety incident, damage claim). Canonical firing maps: NOTIFICATIONS-MAP.md + EMAIL_MAP.md.

10. Community (Spaces)

A native in-app Community lounge for Taskers (/provider/community, CommunityLounge) — skill-based "infinite-space" rooms a Tasker is auto-joined to based on their task skills, with live messages + presence over a WebSocket gateway (no iframe). Built out across migrations 142–145:

  • Events — members create events and RSVP.
  • Challenges — prompts + submissions.
  • Study rooms — synchronized focus sessions on a shared server deadline (no streamed clock).
  • Plus DMs, a leaderboard, GIFs, saved messages, link unfurls, uploads, and AI thread summarize (/api/community/**). A ticket is minted per session (/api/community/ticket).

11. Admin console & operations

Admin console (app/(admin)/admin/**, AdminShell): Dashboard (revenue/bookings/providers-online KPIs, live map, stale-bookings triage), Analytics, Funnels (conversion drop-off), Disputes (release/refund/partial), Payouts (flagged + clawback-debt), Chargebacks, Damage-claims, Safety, Providers (approve/reject applications + docs), Task-review (moderation queue), Bookings, Customers, Reviews, Feedback, Calls (masked-call logs), Settings, and Traffic/UTM attribution.

Cron / ops — a single scheduler tick (/api/cron/run, Bearer CRON_SECRET) fans out ~15 idempotent jobs (money-safety ordered): auto-complete stale jobs, auto-release escrow (the 3-day sweep + transfer-retry path), reconcile captures & Connect status, expire stale bids, convert-to-fixed, completion reminders, escrow reconciliation, dropoff emails, taxonomy embeddings, price calibration, and photo cleanup. A dead-man's-switch heartbeat pings healthchecks.io; unhealthy jobs page a human via alertOps (Slack/Discord + admin email, throttled).


12. Honesty guardrails

The "safe" half of Taskly's positioning only works if every trust claim is literally true. The rule: show the mechanic, don't assert the adjective. Canonical boundary: BRAND-VOICE.md §5 + Final business plan.md §7.

Taskly does NOT claim (none are operational): criminal / CPIC background checks; liability insurance or property-protection guarantees; satisfaction / re-clean guarantees; WSIB compliance; trade-licence verification (schema scaffolding only); "24-hour payouts"; any take-rate other than 20%; fabricated volume/rating stats or named testimonials.

Taskly DOES claim (all real & shipped): a manual Tasker review & approval gate; identity verification via Stripe Identity (gov photo ID + selfie — verifies who, not criminal history); escrow held until you confirm (or 3-day auto-release); 4-digit door-OTP arrival codes + remote session-confirm; masked/brokered calling; two-way ratings; and a real person decides disputes.

Language rules: Tasker (not worker/gig/contractor), Poster/you (not customer/user), reviewed & approved (not "vetted"), identity-verified (not "background-checked"), offers (not "bids"), held in escrow (not "protected"). Demo content must be labeled illustrative with no invented names/ratings/quotes.


13. Feature index (quick reference)

Area Feature Where
Auth Email+password, Google OAuth, magic code, forgot-password, Turnstile, 6-digit OTP app/login, app/api/auth/*, app/auth/callback
Post a task 5-step wizard, draft persistence, autopost-across-auth, funnel analytics app/post-task/**
AI (poster) description draft, ghost-text, clean-up rewrite, scope pills, category classify, market estimate, image gen app/api/{suggest-*,complete-description,enhance-task,classify,predict-price,generate-image}
Customer dash task composer, active-bookings band, My Tasks (5 tabs), post detail + journey rail app/(customer)/app/**
Offers ranked offers, Best-value badge, shortlist, message/save/dismiss, profile peek, Q&A components/marketplace/OffersDrawer, TaskComments
Hire & pay accept-and-pay, hosted Stripe Checkout, capture-to-escrow, coupons app/api/tasks/[id]/accept-and-pay, app/api/stripe/marketplace-checkout
Referrals Give $25/Get $25, share links, credit ledger, auto-apply app/(customer)/app/profile/referrals
Become a Tasker 3-step onboarding, skill taxonomy, Stripe Identity, verification checklist, TaskerGate upsell app/onboarding/**, components/tasker/TaskerGate
Tasker workspace Home/Browse/Community/Messages/Earnings, offers, QuickApply, licensed bid gate app/(provider)/provider/**, lib/marketplace/{bids,credentials}
Jobs Available/Upcoming/Completed, OTP verify, live location, calls/video, reschedule app/(provider)/provider/jobs/**
Payouts Stripe Connect Express, escrow release, 3-day auto-release, clawback, tax summary lib/marketplace/{fees,release}, app/(provider)/provider/{earnings,tax-summary}
Tasker profile public /pro/<handle>, AI bio/headline, resume import, LinkedIn/Places, portfolio app/(provider)/provider/profile/public, app/pro/[handle]
Job lifecycle status machine, timeline, LiveTracker, dispute/safety/damage/reschedule/cancel, reviews app/(customer)/app/bookings/[id], app/api/bookings/[id]/**
Trust & safety layered moderation, crisis path, contact stripping, escrow, OTP, masked calls lib/moderation/**, lib/marketplace/release
Community skill rooms, live chat/presence, events, challenges, study rooms, DMs app/(provider)/provider/community, app/api/community/**
Admin dashboard, funnels, disputes, payouts, chargebacks, safety, provider approval, task-review app/(admin)/admin/**
Ops single cron tick → ~15 idempotent jobs, heartbeat, ops alerts app/api/cron/run, lib/ops/alert

This document reflects the product as shipped at the time of writing (2026-07). When a flow changes, update this file in place — it is the end-to-end map, not an aspirational spec. For system internals, defer to ARCHITECTURE.md; for the operating model, BUSINESS-MODEL.md; for claim wording, BRAND-VOICE.md.