wrongmove/docs/plans/2026-02-21-property-decisions-design.md
Viktor Barzin 941004cf1b
docs: add property decisions feature design document
Tinder-style swipe card UI for liking/disliking properties with
shared shortlists. Two-phase rollout: core decisions first, sharing second.
2026-02-21 13:41:30 +00:00

6 KiB

Property Decisions Feature — Design Document

Date: 2026-02-21

Problem

Users browse hundreds of listings but have no way to track which properties they're interested in, dismiss ones they've already rejected, or share a shortlist with a partner or flatmate.

Solution

A Tinder-style swipe card review mode where users swipe right (like), left (dislike), or up (skip) on properties. Liked properties are saved; disliked ones are hidden from all views. Shortlists can be shared via link.

Requirements

  • Three decision states: Like, Dislike, Neutral (default)
  • Disliked properties are fully hidden from map + list views
  • Liked properties viewable via filter in existing views and a dedicated "Saved" tab
  • Shared shortlists via link for collaboration with partner/flatmate
  • Works on mobile (primary: swipe gestures) and desktop (keyboard shortcuts + buttons)

Data Model

listing_decision table

Column Type Description
id int, PK Auto-increment
user_id int, FK -> user Who made the decision
listing_id int Rightmove property ID
listing_type str RENT or BUY
decision str liked or disliked
created_at datetime When the decision was made
updated_at datetime When last changed

Unique constraint on (user_id, listing_id, listing_type).

shortlist table (Phase 2)

Column Type Description
id int, PK Auto-increment
owner_id int, FK -> user Creator
name str e.g. "Flat hunt with Sarah"
share_token str, unique UUID for share link
created_at datetime

shortlist_member table (Phase 2)

Column Type Description
shortlist_id int, FK
user_id int, FK

API Endpoints

Phase 1 — Decisions

Method Endpoint Purpose
PUT /api/decisions/{listing_id} Set decision (body: {decision, listing_type})
GET /api/decisions Get all user's decisions
DELETE /api/decisions/{listing_id} Remove decision (back to neutral)

Existing /api/listing_geojson and /api/listing_geojson/stream get a decision_filter query parameter: all (default, hides disliked), liked, disliked, undecided, everything.

Phase 2 — Shortlists

Method Endpoint Purpose
POST /api/shortlists Create a shortlist
GET /api/shortlists List user's shortlists
GET /api/shortlists/{token} View shared shortlist (public, no auth)
POST /api/shortlists/{id}/members Add member by email

Swipe Card UI

Entry Point

A "Review" button in the toolbar, next to the existing map/list toggle. Enters swipe review mode.

Card Layout (full-screen mobile)

+-----------------------------+
|  <- Back            3 / 47  |  header: back + progress
|                             |
|  +-----------------------+  |
|  |                       |  |
|  |   Property Photo      |  |  main image
|  |                       |  |
|  +-----------------------+  |
|                             |
|  GBP2,150/mo . 2 bed . 65m2|  key stats
|  GBP33/m2                  |
|  Clapham, SW4              |  location
|                             |
|  Northern Line  22 min      |  travel times (from POIs)
|  Victoria       35 min      |
|                             |
|  +-----------------------+  |
|  |   Mini map snippet    |  |  small static map
|  +-----------------------+  |
|                             |
|  [X]    [Undo]    [Heart]   |  action buttons
+-----------------------------+

Interactions

  • Swipe right -> Like (green overlay, slides off right)
  • Swipe left -> Dislike (red overlay, slides off left)
  • Swipe up -> Skip / Neutral (slides up, next card)
  • Tap buttons -> Same as swipe
  • Undo -> Reverses last decision, brings card back
  • Tap photo -> Full-screen image gallery
  • Tap card body -> Opens Rightmove listing in new tab
  • Back -> Exit review mode

Desktop Adaptation

Same card UI centered in modal overlay (max-width ~450px). Keyboard shortcuts: right arrow (like), left arrow (dislike), up arrow (skip), Ctrl+Z (undo).

Card Stack

Show edges of next 2 cards behind current card for depth illusion.

Progress

Counter "3 / 47" — only undecided properties in the review queue.

Filtering

Default Behavior

When logged in, disliked properties are automatically excluded from map and list views. Server-side filtering via join against listing_decision.

Filter Panel

New "Decision" dropdown:

  • All (hide disliked) — default
  • Liked only
  • Undecided only
  • Disliked only
  • Everything

Saved Tab

New navigation option alongside Map / List. Shows:

  • Liked properties in a list, sorted by date saved (most recent first)
  • Same card info as regular list view
  • Tap to open on Rightmove
  • Swipe to remove from saved

Sharing (Phase 2)

  1. "Share" button in Saved tab opens dialog
  2. Create named shortlist
  3. Get share link (/shared/{token})
  4. Public read-only view, no auth required
  5. Authenticated users can join and their likes merge in
  6. Shows which member liked each property

Technical Decisions

  • Animation: react-spring + @use-gesture/react for swipe physics
  • State: Decisions stored server-side. Frontend keeps Map<string, decision> for fast lookup. Optimistic updates with revert on failure.
  • Backend: Follows existing patterns — new model, repository, service, and route modules.
  • Server-side filtering: GeoJSON endpoints join against listing_decision to exclude disliked.

Phasing

Phase 1 — Core Decisions + Swipe UI

  • listing_decision table + migration
  • Decision API endpoints
  • Swipe card review mode component
  • Filter panel integration
  • Saved tab
  • Server-side disliked filtering

Phase 2 — Sharing

  • shortlist + shortlist_member tables + migration
  • Shortlist API endpoints
  • Share link generation + public view
  • Member management