# DEB-90: Save & Bookmark — Technical Requirements Document

> **Jira:** [DEB-90](https://divami.atlassian.net/browse/DEB-90)
> **Scope:** Backend (high-level)
> **Status:** Draft
> **Author:** Abhilash Adunuri
> **Date:** 2026-04-21

---

## 1. Feature Summary

Two distinct features share the same underlying message storage layer but serve different user intents.

| | **Save** | **Bookmark** |
|---|---|---|
| **What it does** | Pins an AI response as a reusable visualization tile on the stage page | Marks a response for recall within its conversation |
| **How it's triggered** | Save icon on an AI response card | Bookmark icon on an AI response card |
| **Where it appears** | Stage page "Saved visualizations" grid (filtered by stage) | Global "Bookmarks" side panel (across all stages) |
| **Click action** | Re-submits the same question directly to the tatasteel agent (fresh data, same chart style) | Loads the full parent conversation and scrolls to that message |
| **Requires category tag** | Yes — stage ID (`bids`, `contracts`, `ew`, `nces`) | No |

---

## 2. Data Model

Both features live on the existing `conversation_message` table. Migration `001_add_save_bookmark.sql` already applied the columns.

```
conversation_message
├── id                 PK
├── conversation_id    FK → conversation.id
├── message_author     'user' | 'assistant'
├── message            text summary
├── metadata           JSONB  ← full widget payload (charts, highlights, etc.)
├── is_saved           BOOLEAN  DEFAULT FALSE   ← Save feature
├── is_bookmarked      BOOLEAN  DEFAULT FALSE   ← Bookmark feature
├── category           VARCHAR  ← stage ID when is_saved = TRUE
└── created_at
```

**Indexes:**
- `idx_conv_msg_is_saved` — partial index on `is_saved = TRUE`
- `idx_conv_msg_category` — partial index on `category IS NOT NULL`
- `idx_conv_msg_is_bookmarked` — partial index on `is_bookmarked = TRUE` *(to be added)*

---

## 3. API Endpoints

### 3.1 Save

| | |
|---|---|
| **Toggle** | `PATCH /conversations/{conv_id}/messages/{msg_id}/save` |
| **Body** | `{ "is_saved": bool, "category": "ew" \| "bids" \| "contracts" \| "nces" \| null }` |
| **List** | `GET /messages/saved?category={stage_id}` |
| **Auth** | Bearer JWT — conversation must belong to the requesting user |

**List response fields:** `id`, `conversation_id`, `message`, `metadata`, `category`, `is_saved`, `created_at`, `question` (preceding user message via sub-query)

---

### 3.2 Bookmark

| | |
|---|---|
| **Toggle** | `PATCH /conversations/{conv_id}/messages/{msg_id}/bookmark` |
| **Body** | `{ "is_bookmarked": bool }` |
| **List** | `GET /messages/bookmarked` |
| **Auth** | Bearer JWT — conversation must belong to the requesting user |

**List response fields:** `id`, `conversation_id`, `conversation_title`, `message`, `metadata`, `is_bookmarked`, `created_at`, `question`

> `conversation_title` is included so the frontend can group bookmarks by session in the side panel.

---

## 4. Flow Diagrams

### 4.1 Save Flow

```mermaid
sequenceDiagram
    participant FE as Frontend
    participant API as rt_agent :8010
    participant DB as PostgreSQL

    FE->>API: PATCH .../save  { is_saved: true, category: "ew" }
    API->>DB: Verify conv ownership
    API->>DB: UPDATE conversation_message SET is_saved=TRUE, category="ew"
    API-->>FE: { success: true, is_saved: true }

    FE->>API: GET /messages/saved?category=ew
    API->>DB: SELECT saved messages WHERE user_id=$1 AND category="ew"
    API-->>FE: [ { id, metadata, question, ... } ]
```

### 4.2 Bookmark Flow

```mermaid
sequenceDiagram
    participant FE as Frontend
    participant API as rt_agent :8010
    participant DB as PostgreSQL

    FE->>API: PATCH .../bookmark  { is_bookmarked: true }
    API->>DB: Verify conv ownership
    API->>DB: UPDATE conversation_message SET is_bookmarked=TRUE
    API-->>FE: { success: true, is_bookmarked: true }

    FE->>API: GET /messages/bookmarked
    API->>DB: SELECT bookmarked messages + conv title WHERE user_id=$1
    API-->>FE: [ { id, conversation_id, conversation_title, question, ... } ]
```

---

## 5. Security

- All four endpoints require a valid Bearer JWT
- Toggle endpoints (`PATCH`) verify the conversation belongs to the authenticated user before updating — return `403` otherwise
- List endpoints (`GET`) scope the DB query to `user_id` — no cross-user leakage

---

## 6. Acceptance Criteria

- [ ] `PATCH .../save` persists `is_saved` + `category`; returns `403` if not owner
- [ ] `GET /messages/saved?category=` returns only the authenticated user's saved messages, filtered by stage
- [ ] `PATCH .../bookmark` persists `is_bookmarked`; returns `403` if not owner
- [ ] `GET /messages/bookmarked` returns only the authenticated user's bookmarks, with `conversation_title` and `question`
- [ ] All four routes registered in `rt_agent/server.py`
- [ ] `idx_conv_msg_is_bookmarked` index applied to DB
