Skip to content

MyManny — Product Catalog

MyManny is a skilled-trades marketplace connecting Italian consumers with vetted professionals (plumbers, electricians, HVAC, locksmiths, and more). The product has two intended modes: a normal booking flow for planned jobs and an SOS emergency-dispatch flow. Product direction is not a claim that every described flow or payment is currently shipped.

This document catalogs MyManny product surfaces and their implementation states, linking to roadmap.md §Feature Matrix. Design direction and planned flows are distinguished from shipped behavior.

The catalog is Phase 1 of a three-phase product-doc strategy. Phase 2 (a Slidev/Marp cofounder + investor demo deck auto-generated from this catalog) and Phase 3 (a public Storybook component gallery for apps/mobile/components/) are deferred — see Future tiers below.

  • stable — production-ready, hardening complete
  • beta — feature-complete, hardening still in flight
  • alpha — usable end to end, polish + edge cases pending
  • shipped (date) — landed in the most recent sprint
  • stub — placeholder UI present, behaviour not wired
  • planned — roadmap-only, no code yet

User identity, authentication, authorization. Backed by Clerk (@clerk/expo on mobile, @clerk/backend JWT verification on API). Italian-first identifier flow (email + social) feeding role-based access control across consumer / professional / admin surfaces.

Status: stable · Platforms: iOS, Android, Web · ADR 0024 · ADR 0005

Single Clerk identifier-first flow that accepts email or any social provider in one prompt rather than branching upfront. Returning users land on the home screen with a session in under two seconds; first-timers are seamlessly forwarded into Sign Up without re-typing their identifier. Italian copy is the default surface, English mirrors it via react-i18next.

Status: stable · Platforms: iOS, Android, Web · ADR 0005

Three-slide welcome tour leads into a social-first Sign Up (Google, Apple, Facebook) with email + OTP as the fallback. Clerk components are deeply themed to the Italian Sole palette so the auth surface feels native, not bolted-on. JIT (just-in-time) provisioning writes the Prisma User row on first authenticated call to /users/me.

Status: alpha · Platforms: iOS, Android, Web

Clerk TOTP support is wired but currently dashboard-managed — users opt in via the Clerk-hosted account portal. App-level enrolment UI is tracked as roadmap task #140. All existing user MFA was audited and wiped 2026-04-21 to reset to a clean baseline before public launch.

Status: stable · Platforms: API · ADR 0024

Three roles — CONSUMER, PROFESSIONAL, ADMIN — gate every API surface via NestJS @Roles() metadata + RolesGuard. The role lives on the Prisma User row and is flipped atomically by the onboarding flow when a consumer becomes a professional.

Status: stable · Platforms: API

Every request is authenticated by JwtAuthGuard, which delegates token verification to the IDENTITY_PROVIDER seam (ClerkIdentityProvider → @clerk/backend verifyToken() today) and attaches a typed AuthenticatedUser to the request. Controllers read identity exclusively via @CurrentUser() — never from request bodies — eliminating an entire class of impersonation bugs. Per-user cache (auth:user:<clerkId>, 60s TTL) keeps the verify path off the hot loop.

Status: stable · Platforms: API

user.created, user.updated, and user.deleted events are signed by svix and verified with req.rawBody (NOT a re-stringified body — that breaks signature verification). Webhook handlers keep the Prisma User mirror consistent with the Clerk source of truth and bust the auth Redis cache on role changes.

How consumers find professionals. Trade-category browsing, full-text search, PostGIS-backed proximity, and a map-first results view powered by Mapbox.

Status: stable · Platforms: iOS, Android, Web

Browse-by-trade entry point on the home screen — plumber, electrician, HVAC, locksmith, and the long tail. Each tap fans out into a PostGIS ST_DWithin query against the consumer’s current location with a 30-second Redis result cache to keep the home grid snappy on repeat visits.

Status: stable · Platforms: iOS, Android, Web

Free-text search bar covers professional names, trade keywords, and service descriptions. Postgres full-text index drives the query; Redis caches every search slug for 30 seconds to absorb double-taps and back-button repeats without re-hitting the database.

Status: stable · Platforms: iOS, Android, Web · Spec · ADR 0028

Map-first list/map split that anchors discovery to geography — a 60/40 split with cards underneath the map, not behind a tab. Filters (trade, distance, trust tier, availability) and sort (proximity, rating, price) hang off the top bar. The map renders in the custom Italian Sole Mapbox Studio style so the surface feels brand-consistent rather than generic Google blue.

Status: stable · Platforms: iOS, Android, Web · Spec

Detail page surfaces the data that builds trust before booking — trust tier badge (BASIC / VERIFIED / ELITE), credential summary, recent reviews with rating histogram, service area, base price band, onboarding-configured service line items, portfolio/avatar photo, and a prominent CTA to start a booking or send a chat message. When the consumer shares coordinates, the API returns the same haversine distance used by search; otherwise distance is null. Search cards expose the recorded average response minutes as a fast-responder signal — travel ETA is deliberately absent until SOS tracking has a real ETA source. Profile data is cached in Redis for five minutes to keep the page snappy on warm visits.

Status: beta · Platforms: iOS, Android, Web · ADR 0008

Native maps via @rnmapbox/maps plus the Mapbox APIs (Geocoding, Directions, Matrix, Places). The custom Sole-palette style was authored in Mapbox Studio so the map matches the rest of the app rather than fighting it. The free tier (50K MAU + 100K API requests/month) carries us comfortably through MVP 0.

Normal-mode booking direction is to select a published professional slot, pay without a second professional confirmation, and reserve that slot for ten minutes during checkout. This is approved product direction, not a claim that it has shipped; a slot reservation does not itself establish provider-side payment authorization or capture.

Status: planned · Platforms: iOS, Android, Web

The 2026-09-30 design direction selects a published professional slot, reserves it for ten minutes during checkout, and completes payment without a second professional confirmation. Existing implementation and prototype do not establish this flow as shipped; the slot reservation does not guarantee provider-side payment authorization or capture.

Status: planned · Platforms: iOS, Android, API

Current booking records move through workflow states including PENDING, CONFIRMED, IN_PROGRESS, COMPLETED, CANCELLED, and NO_SHOW. Persisted state transitions do not establish payment authorization, capture, release, or payout.

Status: stable · Platforms: iOS, Android, API

Change-order approval updates the booking total; it does not itself establish that a payment authorization or captured amount has been updated.

Status: stable · Platforms: iOS, Android

Both consumer and professional get a chronological list of past and upcoming bookings with status, counterpart, address, and total. A displayed receipt or booking total does not independently prove Stripe payment authorization, capture, transfer, or payout.

Status: stable · Platforms: iOS, Android

Consumer-initiated cancellation with a confirm sheet and reason capture. Current behavior updates the booking and escrowStatus workflow marker; the marker alone does not prove a Stripe refund has completed.

SOS is an emergency-dispatch product direction. The current dispatcher auto-assigns the first professional to accept. The approved 2026-09-30 consumer flow requires pro acceptance first, then consumer acceptance of the same bound total and payment; the new sequence is not established as shipped.

Status: planned · Platforms: iOS, Android · Historical flow spec

The 2026-09-30 product direction is professional acceptance first, then consumer acceptance of the same bound total, then payment. The design export is a reference; existing dispatch behavior does not establish this full sequence as shipped.

Status: stable · Platforms: API · Spec

Server-side ranker queries pros by PostGIS radius (ST_DWithin) intersected with current availability, SOS opt-in flag, trust tier, and accept-rate history. The output is a candidateQueue of up to 30 pros that drives sequential dispatch (AUTO) or top-10 push (PICK_LIST).

Status: planned · Platforms: iOS, Android, API · Historical flow spec

The 2026-09-30 product direction binds the total when the professional accepts; the consumer must then accept that same total and pay before the job proceeds. Displayed example prices are not tariffs. The flow is not established as shipped. Release is eligible only after both parties record their required completion; absence of either completion does not trigger automatic release. See ADR 0006.

Status: planned · Platforms: iOS, Android · Historical flow spec

The approved product sequence is professional acceptance, consumer acceptance of the same total, and payment. Existing accept/decline mechanics are not evidence that this complete sequence is implemented.

Status: shipped (2026-04-23) · Platforms: iOS, Android, Web · ADR 0030 · Spec

The existing dispatch option broadcasts the request to the top 10 candidates, who may submit quotes; the consumer selects one. This dispatch selection flow predates the 2026-09-30 product direction and does not by itself implement the later pro-acceptance → consumer-acceptance-of-the-same-total → payment sequence.

Status: beta · Platforms: iOS, Android · Spec

Consumer sees the pro’s position on a live map after acceptance. The pro’s useProLocationStream hook emits GPS at a 7-second cadence to a Socket.IO /tracking namespace; the consumer’s LiveTrackingMap subscribes and renders the marker with a status pill (Confermato → In arrivo → Arrivato → Completato). Tier 2 (full Directions API ETA + route polyline) is queued post-MVP 0.

Real-time messaging between consumer and professional during a booking. Socket.IO over the NestJS WebSocket gateway, message persistence in Postgres, history scrollback, and unread badges.

Status: beta · Platforms: iOS, Android

Per-booking chat thread with message history, typing indicator, and unread counter. The Socket.IO gateway authenticates connections via the same Clerk JWT used by REST, so identity is consistent across both surfaces. Hardening still in flight: media attachments, read receipts, and push fallback when the socket is closed.

Post-booking reviews that feed back into the trust tier engine. Unlocked only after COMPLETED status, with a duplicate-submission guard at the database level.

Status: stable · Platforms: iOS, Android

5-star rating + optional text review. The CTA only unlocks after the booking transitions to COMPLETED, and a @@unique([bookingId, authorId]) Prisma constraint prevents accidental duplicate submissions on flaky networks. Submissions update the pro’s aggregate rating cache in Redis for fast profile reads.

Status: stable · Platforms: iOS, Android, Web

Pro profile shows aggregate rating, a 5-star breakdown histogram, and the recent reviews list (newest first, paginated “load more”). Author identity is composed BE-side from Clerk — first name + last-name initial plus avatar — so no full name, email, or phone is ever exposed on the review surface. Italian + English copy is localised on the server so the review surface respects the consumer’s Accept-Language even when the original review was written in the other language.

Consumers store the addresses they book at most so the booking form fills in with one tap instead of a full address search every time.

Status: beta · Platforms: iOS, Android, API

Address rows (label, formatted address, coordinates, default flag) with full CRUD at /addresses. Default is exclusive — setting one clears the others in a transaction. Soft cap of 20 addresses per user; rows cascade-delete with the user so GDPR erasure needs no special case. The booking itself keeps its own serviceAddress string: the address book is a form-filling convenience, not a booking dependency.

Real support resources, no invented content.

Status: beta · Platforms: iOS, Android

/help aggregates only resources that exist: the support mailbox (support@mymanny.it, same address referenced by the Terms), the Terms of Service and Privacy Policy, and the running app version. FAQ editorial content is deliberately absent until product writes it — the screen ships no dead taps and no placeholder copy.

Stripe Connect Express remains the selected integration. Payment readiness, capture, funds movement, and payout are not established by booking status or prototype screens; provider legal go/no-go and verified end-to-end payment behavior remain outstanding. See ADR 0006.

Status: beta · Platforms: iOS, Android, API · ADR 0006

Pros connect their bank account via Stripe Connect Express onboarding — KYC and payout details handled by Stripe-hosted forms, never our UI. The StripeService wraps account creation, status polling, and capability requirements so the rest of the app sees a single typed surface.

Status: planned · ADR 0006

Booking records expose application workflow markers such as escrowStatus; those markers alone do not prove that a PaymentIntent was created, authorized, captured, held, released, or refunded. The currently implemented Connect PaymentIntent shape and consumer-confirmation booking update do not demonstrate those external funds movements or a payout. No automatic release after 48 hours is implemented. For the approved SOS lifecycle, release is eligible only after both the professional and consumer complete; if either completion is missing, there is no automatic release. The provider-specific payment model and end-to-end flow require written provider/legal confirmation before use. These are payment design constraints, not shipped behavior.

Status: planned · Platforms: API · ADR 0006

Application refund markers and webhook code do not by themselves establish an end-to-end Stripe refund path for the approved charge model. Reconcile and verify Stripe-side refund completion before presenting a refund as completed.

Status: stable · Platforms: API

Raw-body signature verification (NOT JSON.stringify — that breaks signing), immediate ACK to Stripe, and async BullMQ job processing with two-layer idempotency: a StripeEventLog processedAt guard plus BullMQ jobId: stripe:{eventId} dedup. Admin endpoint GET /admin/stripe-events?status=stuck surfaces stalled events for ops triage. This infrastructure does not itself establish end-to-end booking payment, refund, or payout behavior.

Multi-channel transactional notifications via Novu Cloud (EU region). Email through Resend, SMS through Twilio, push through Expo Push.

Status: stable · Platforms: API · ADR 0007

Six Novu workflow templates cover the booking + SOS lifecycle — confirmation, reminder, cancellation, completion, refund, dispute. Channel preference is per-subscriber and per-event, so users can mute SMS without losing email or push for the same event class. Server stays vendor-agnostic by speaking only to Novu; channel providers swap underneath.

Status: beta · Platforms: iOS, Android · ADR 0007

Expo Push Token registered on app start, posted to the API, and forwarded to Novu as the push channel for that subscriber. Tap-handling routes the user back into the relevant booking, dispatch, or chat thread via Expo Router deep links. Hardening pending: badge-count synchronisation across iOS + Android.

The professional-side workspace — onboarding, dashboard, availability management, earnings, and a planned calendar view.

Status: shipped (2026-04-23) · Platforms: iOS, Android, Web · Spec

A “Become a professional” Briefcase row on the consumer profile tab routes into the 8-step onboarding wizard. The OnboardingController accepts both CONSUMER and PROFESSIONAL roles per-method (rather than class-level), so consumers can start the wizard mid-session. The final step’s completeOnboarding runs in a $transaction that flips User.role to PROFESSIONAL and busts the auth:user:${clerkId} Redis cache so the next request sees the new role immediately.

Status: stable · Platforms: iOS, Android

Single-screen view of what the pro needs to know on a working day — earnings to date, upcoming bookings, pending change-order approvals, and credential expiry warnings. Designed to feel like a tools-truck command-strip rather than a corporate dashboard.

Status: stable · Platforms: iOS, Android, API

Weekly recurring schedule + one-off slot blocks. Stored as a normalised slot table so consumer-side booking can intersect with ST_DWithin proximity in a single Postgres query rather than an N+1 lookup.

Status: stable · Platforms: iOS, Android

Payout history, pending balance, and per-booking breakdown. Reads from the local mirror of Stripe transfer events rather than calling Stripe live on every screen render — keeps the page fast and Stripe rate-limits comfortable.

Status: stub · Platforms: iOS, Android

Visual calendar with blocked-slot management is a placeholder — the data model exists (the slot table from Availability) but the visual UI is roadmap Phase 1. Today, pros manage availability from the list view.

Trust-building credential upload + admin review pipeline. P_IVA, insurance certificates, ALBO membership, F-Gas certifications, ID documents, training diplomas, manufacturer certs. Drives the BASIC / VERIFIED / ELITE trust tier badge.

Status: stable · Platforms: iOS, Android, API · Spec

Pro picks a document type, the API mints a 600-second presigned R2 URL, the mobile client uploads directly to Cloudflare R2 (no API server in the data path). Throttled at 20 uploads/minute via @nestjs/throttler to absorb bursts without abuse. The presigned-URL pattern keeps the API server out of the way of multi-MB document uploads.

Status: stable · Platforms: API · Spec

Admin queue at GET /admin/credentials lists PENDING credentials with the document, the pro, and a side-by-side approve/reject form. Approval triggers a trust-score recompute; rejection captures a reason that the pro sees on their credentials screen so they can re-upload a better scan.

Status: stable · Platforms: API · Spec

Weighted score: P_IVA 30, insurance 25, ALBO 20, F-Gas 15, others 10/5. Tiers: BASIC 0–30, VERIFIED 31–70, ELITE 71+. Recomputed on every credential approval/rejection. The tier badge surfaces on the pro profile, results cards, and SOS quote cards — so consumers see trust before they see price.

LangGraph.js state machines fronting GPT-5.4 Mini (primary) and Gemini 2.5 Flash (fallback). LangSmith EU traces every run for offline review.

Status: alpha · Platforms: API · ADR 0011

LangGraph state machine consumes the consumer’s job description (text + optional photo) and returns a structured estimate: trade category, estimated duration band, parts likely needed, and a price range. Wired into the SOS triage chat so the pro sees a structured summary, not a wall of free text.

Status: alpha · Platforms: API · ADR 0011

LangGraph graph with tool nodes for availability lookup, distance matrix, and pro filtering. Suggests time-slot + pro pairings that maximise pro utilisation and minimise consumer wait. Currently dogfooded; consumer-facing rollout queued post-MVP 0.

The production-readiness underneath everything — health probes, internationalisation, and security headers.

Status: stable · Platforms: API

/health exposes liveness + readiness payloads consumed by the smoke-prod GitHub Actions job (curl retry loop + Playwright authed flows post-deploy) and by Better Stack’s 1800s external monitor (deliberately not on the 180s cadence — /health runs SELECT 1, and polling it every 3 minutes would keep the Neon free-tier compute awake 24/7). Readiness covers Postgres + Redis + Stripe + Clerk so a partial outage is visible before user traffic hits.

Status: stable · Platforms: iOS, Android, API · ADR 0023

Italian-first by default, English mirrors. Server: nestjs-i18n keyed on Accept-Language / x-lang header with a t(key, fallback, args?) helper that no-ops outside HTTP context (so unit tests don’t have to mock the full i18n module). Mobile: i18next + react-i18next + expo-localization for auto-detection.

Status: stable · Platforms: API

@fastify/helmet ships every response with CSP, HSTS, X-Frame-Options, Referrer-Policy, and a permissions policy tuned for the mobile-API surface. CORS is locked to the canonical FE host; raw-body verification on Stripe + Clerk webhooks lives outside the global parser.

This catalog is Phase 1 of a three-phase product-doc strategy.

  • Phase 2 — Cofounder/investor demo deck. Slidev or Marp generation from this catalog, auto-rebuilt on roadmap commit. Hosted at docs.ideony.is-a.dev/demo. Estimated effort: ~2 hours. Not yet scheduled.
  • Phase 3 — Storybook component gallery. Public showcase of apps/mobile/components/ w/ Sole tokens. Hosted at docs.ideony.is-a.dev/storybook. Estimated effort: ~1 day. Not yet scheduled.

This catalog references screenshots at docs/assets/product/<feature-slug>.png that are not yet captured. To populate them:

  1. Run the relevant Playwright spec: pnpm --filter @mymanny/e2e exec playwright test e2e/web/demo/<spec>.spec.ts --update-snapshots
  2. Move the captured PNG into docs/assets/product/.
  3. The catalog references the file relatively; add a fallback <!-- SCREENSHOT MISSING --> HTML comment if the file does not yet exist.

Screenshots are NOT generated automatically yet. Phase 2 (demo deck) will introduce automated capture as part of the slide build. Until then, this is a manual one-time task.