# 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` 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