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:
parent
13637ec881
commit
941004cf1b
1 changed files with 184 additions and 0 deletions
184
docs/plans/2026-02-21-property-decisions-design.md
Normal file
184
docs/plans/2026-02-21-property-decisions-design.md
Normal 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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue