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.
This commit is contained in:
Viktor Barzin 2026-02-21 13:41:30 +00:00
parent 13637ec881
commit 941004cf1b
No known key found for this signature in database
GPG key ID: 0EB088298288D958

View file

@ -0,0 +1,184 @@
# 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