# User-Initiated Issue Reporting with Screenshots — Implementation Roadmap

> **Location:** `docs/devops/USER_ISSUE_REPORTING_ROADMAP.md`  
> **Status:** Draft  
> **Created:** 2026-03-02

---

## Overview

Enable users to self-report issues with automatic/manual screenshots and comments. This extends the existing `feedback` module to support rich media attachments, leveraging the `blob` module for Azure Blob storage.

### Use Cases

1. **Bug Report with Screenshot** — User encounters error → taps "Report Issue" → screenshot captured → typed description submitted
2. **Feature Request with Mockup** — User uploads annotated screenshot showing desired UI change
3. **Crash Auto-Report** — App crashes → on restart, user prompted to "Send crash report with screenshot"
4. **Support Chat Attachment** — Screenshots attached to existing feedback for back-and-forth with support team

---

## Current State

### Existing Feedback Module

| Component | Status |
|-----------|--------|
| `POST /api/feedback` | ✅ Creates feedback (type, title, body, screen) |
| `GET /api/feedback` | ✅ Admin list/query |
| `PUT /api/feedback/:id` | ✅ Admin triage (status, adminNotes) |
| Screenshot support | ❌ Only text `screen` field |
| Multiple attachments | ❌ Not supported |
| Threaded comments | ❌ Not supported |

### Existing Blob Module (Reusable)

| Component | Status |
|-----------|--------|
| `POST /api/blob/sas` | ✅ Generate SAS URL for direct upload |
| Azure Blob integration | ✅ Storage + lifecycle |
| Container management | ✅ Per-product isolation |

---

## Target Architecture

```
User Flow:
1. User taps "Report Issue" in app
2. Client captures screenshot (optional annotation)
3. Client requests SAS URL: POST /api/feedback/sas
4. Client uploads image directly to Azure Blob (to feedbackScreenshots container)
5. Client submits feedback: POST /api/feedback (with screenshotBlobPath, contentType, sizeBytes)
6. Admin views feedback in dashboard with image preview (fresh SAS URL generated on-demand)
```

---

## Data Model Changes

### Option A: Inline Screenshot (Simple)

```typescript
// feedback/types.ts — Add to existing FeedbackDoc
interface FeedbackDoc {
  // ... existing fields ...
  
  // Screenshot attachment (single) — SAS URLs generated on-demand, not stored
  screenshotBlobPath?: string;        // "feedbackScreenshots/{productId}/{feedbackId}/{screenshotId}.png"
  screenshotContentType?: 'image/png' | 'image/jpeg' | 'image/webp';
  screenshotSizeBytes?: number;         // Actual uploaded size
  
  // Device context for debugging
  deviceContext?: {
    osVersion: string;
    appVersion: string;
    deviceModel: string;
    screenResolution: string;
    locale: string;
  };
}

// Add to CreateFeedbackSchema
screenshotBlobPath: z.string().optional(),
screenshotContentType: z.enum(['image/png', 'image/jpeg', 'image/webp']).optional(),
screenshotSizeBytes: z.number().int().min(1).max(5 * 1024 * 1024).optional(), // 5MB limit
deviceContext: z.object({
  osVersion: z.string(),
  appVersion: z.string(),
  deviceModel: z.string(),
  screenResolution: z.string(),
  locale: z.string(),
}).optional(),
```

### Option B: Separate FeedbackAttachments Container (Extensible)

```typescript
// feedback/types.ts — New attachment model
interface FeedbackAttachmentDoc {
  id: string;                         // att_<uuid>
  feedbackId: string;                 // Parent feedback (partition key)
  productId: string;
  
  // Blob storage
  blobPath: string;                   // "feedback/{productId}/{feedbackId}/{id}.png"
  blobUrl: string;                    // SAS URL (refreshed on fetch)
  containerName: string;              // e.g., "user-feedback"
  
  // Metadata
  fileName: string;
  contentType: 'image/png' | 'image/jpeg' | 'image/webp';
  sizeBytes: number;
  width: number;
  height: number;
  
  // Capture context
  capturedAt: string;
  trigger: 'manual' | 'auto_crash' | 'auto_error';
  screenName?: string;
  
  createdAt: string;
}

interface FeedbackDoc {
  // ... existing fields ...
  
  // Reference to attachments
  attachmentCount: number;            // Denormalized counter
  hasScreenshot: boolean;            // Quick check for UI
}

// API changes
POST /api/feedback/:id/attachments      // Add attachment to existing feedback
GET  /api/feedback/:id/attachments      // List attachments
DELETE /api/feedback/:id/attachments/:attId  // Remove attachment (admin)
```

**Recommendation:** Start with **Option A** (single screenshot), migrate to **Option B** if multi-attachment demand arises.

---

## API Specification

### New Endpoints

| Method | Endpoint | Auth | Rate Limit | Description |
|--------|----------|------|------------|-------------|
| `POST` | `/api/feedback/sas` | User | 5 per 10 min | Get SAS URL for screenshot upload |
| `POST` | `/api/feedback` | User | 10 per hour | Submit feedback (with optional screenshotBlobPath) |
| `GET` | `/api/feedback/:id/screenshot` | Admin | 30 per hour | Get fresh SAS URL for viewing screenshot |
| `DELETE` | `/api/feedback/:id/screenshot` | Admin | 10 per hour | Delete screenshot (GDPR/privacy) |

### SAS Generation Endpoint

```typescript
// POST /api/feedback/sas
// Request
{
  "contentType": "image/png",
  "sizeHint": 1024000  // Optional: 1MB hint for validation
}

// Response (201)
{
  "blobPath": "feedbackScreenshots/lysnrai/fb_abc123/screenshot_xyz.png",
  "uploadUrl": "https://bytelyst.blob.core.windows.net/...?sv=...",
  "expiresIn": 300,  // 5 minutes
  "maxSizeBytes": 5242880  // 5MB limit
}
```

### Submit Feedback with Screenshot

```typescript
// POST /api/feedback
// Request
{
  "type": "bug",
  "title": "App crashes when tapping record",
  "body": "Steps: 1. Open app 2. Tap red button 3. Crash",
  "screen": "RecordingScreen",
  "screenshotBlobPath": "feedbackScreenshots/lysnrai/fb_abc123/screenshot_xyz.png",
  "deviceContext": {
    "osVersion": "iOS 17.4",
    "appVersion": "2.3.1",
    "deviceModel": "iPhone15,2",
    "screenResolution": "393x852",
    "locale": "en-US"
  }
}
```

---

## Implementation Phases

### Phase 1: Server Foundation (2-3 days) — ✅ COMPLETE

#### 1.1 Data Model Extension — [`acfbd7c`](https://github.com/saravanakumardb1/learning_ai_common_plat/commit/acfbd7c)
- [x] Add `screenshotBlobPath` to `FeedbackDoc` interface
- [x] Add `deviceContext` to `FeedbackDoc` interface  
- [x] Update `CreateFeedbackSchema` with new fields
- [ ] Add `feedback_screenshots` container to `cosmos-init.ts` (if Option B) — Skipped (using blob storage)

#### 1.2 Repository Layer — [`8d2ba9c`](https://github.com/saravanakumardb1/learning_ai_common_plat/commit/8d2ba9c)
- [x] Extend `createFeedback()` to handle screenshot metadata
- [x] Add `generateScreenshotSas()` function (wrapper around blob module)
- [x] Add `getFeedbackWithScreenshot()` with fresh SAS URL generation
- [x] Add `hasScreenshot()` helper for quick UI checks

#### 1.3 API Routes — [`cfbaa92`](https://github.com/saravanakumardb1/learning_ai_common_plat/commit/cfbaa92)
- [x] `POST /api/feedback/sas` — Generate upload URL
  - Rate limit: 5 requests per 10 minutes per user
  - Validate content type (image/* only)
  - Return blob path + SAS URL
- [x] Update `POST /api/feedback` — Accept screenshot metadata
- [x] `GET /api/feedback/:id/screenshot` — Get fresh view URL
- [x] `DELETE /api/feedback/:id/screenshot` — Admin delete

#### 1.4 Integration — ✅ Already wired in `server.ts`
- [x] Wire new routes into `server.ts` — Feedback routes already registered
- [x] Add blob container `feedbackScreenshots` to blob module config — [`d876bb0`](https://github.com/saravanakumardb1/learning_ai_common_plat/commit/d876bb0)
- [ ] Set lifecycle policy: 90-day TTL for user screenshots — TODO: Azure lifecycle policy

#### 1.5 Testing — [`e712968`](https://github.com/saravanakumardb1/learning_ai_common_plat/commit/e712968)
- [x] Unit tests for SAS generation (schema validation)
- [x] Unit tests for feedback with screenshot submission
- [ ] Integration test: full flow (SAS → upload → submit → view) — TODO: requires blob storage
- [ ] GDPR deletion test — TODO: Phase 3

### Phase 2: Client SDK Updates (3-4 days) — ✅ PARTIAL

#### 2.1 TypeScript SDK (`@bytelyst/feedback-client`) — [`b261cda`](https://github.com/saravanakumardb1/learning_ai_common_plat/commit/b261cda)
- [x] `FeedbackClient.submitWithScreenshot(params)`
  - Internal flow: get SAS → upload blob → submit feedback
  - Progress callbacks for upload
- [x] `FeedbackClient.captureAndSubmit()` — Placeholder (TODO-1: platform-specific capture)
- [x] Vitest tests for client

#### 2.2 Swift SDK (iOS) — TODO
- [ ] `FeedbackManager.submit(title:body:screenshot:)`
- [ ] `FeedbackManager.captureScreenshot()` — UIImage → blob upload
- [ ] Annotation overlay (optional drawing on screenshot)

#### 2.3 Kotlin SDK (Android) — TODO
- [ ] `FeedbackManager.submitWithScreenshot()`
- [ ] MediaProjection integration for screenshot capture
- [ ] Composable for in-app feedback sheet with screenshot preview

### Phase 3: Admin Dashboard UI (2-3 days) — ✅ COMPLETE

#### 3.1 Feedback List Enhancements — [`d4e7f8a`](https://github.com/saravanakumardb1/learning_ai_common_plat/commit/d4e7f8a)
- [x] Screenshot indicator (badge) in list view
- [x] Filter: "Has screenshot" / "No screenshot"

#### 3.2 Feedback Detail View — [`d4e7f8a`](https://github.com/saravanakumardb1/learning_ai_common_plat/commit/d4e7f8a)
- [x] Full-size screenshot display (lightbox)
- [x] Device context panel (OS version, app version, screen resolution)
- [x] "Delete screenshot" (GDPR compliance)
- [ ] "Download screenshot" button — TODO: add direct download

#### 3.3 Client Library — [`d4e7f8a`](https://github.com/saravanakumardb1/learning_ai_common_plat/commit/d4e7f8a)
- [x] Admin dashboard feedback page with screenshot viewer

---

## Privacy & Security Considerations

### PII Handling
- [ ] Screenshots may contain sensitive user data
- [ ] Blur regions API (client-side before upload)
- [ ] Auto-redact: Detect and blur common PII areas (email fields, phone numbers)
- [ ] User consent: "This screenshot may include your data. Continue?"

### Retention
- [ ] Screenshot TTL: 90 days (match feedback lifecycle)
- [ ] On feedback deletion → cascade delete screenshot blob
- [ ] User-initiated deletion: "Delete my feedback and screenshot"

### Access Control
- [ ] Users submit screenshots but cannot directly view/download them (security)
- [ ] Admins can view all screenshots for their product via fresh SAS URLs
- [ ] SAS URLs are time-limited (15 minutes) and generated on-demand per view

---

## Open Questions

| # | Question | Impact | Suggested Answer |
|---|----------|--------|------------------|
| 1 | Max screenshot size? | Storage cost | 5MB limit, WebP compression recommended |
| 2 | Multiple screenshots per feedback? | Complexity | Start with 1, add array support later |
| 3 | Screenshot annotation/drawing? | Client complexity | Phase 2.2 for iOS, others skip |
| 4 | Auto-capture on crash? | Privacy risk | Opt-in only, show preview before submit |
| 5 | Video screen recording? | Storage cost | Future Phase 4, not now |
| 6 | Anonymous feedback allowed? | Auth complexity | No — require auth for accountability |

---

## Success Metrics

- **Adoption:** 30% of bug reports include screenshot within 3 months
- **Resolution time:** -20% time-to-resolution for reports with screenshots
- **Storage:** <100GB/month for screenshots (at 1000 reports/day, 100KB avg)

---

## Appendix: File Changes

### New Files
```
services/platform-service/src/modules/feedback/
  └── attachment.types.ts       # (if Option B)
```

### Modified Files
```
services/platform-service/src/modules/feedback/
  ├── types.ts                  # Add screenshot fields
  ├── repository.ts             # Add SAS functions
  └── routes.ts                 # Add SAS endpoint

services/platform-service/src/lib/cosmos-init.ts
  └── Add feedback_attachments container (if Option B)

services/platform-service/src/server.ts
  └── Register feedback SAS routes
```

---

## TODOs Completed

| # | TODO | Status | Commit |
|---|------|--------|--------|
| TODO-1 | Platform-specific screenshot capture (Web) | ✅ Complete | `921f211` |
| TODO-1 (RN) | React Native screenshot capture placeholder | ✅ Complete | `32c477e` |
| TODO-1 (Electron) | Electron screenshot capture placeholder | ✅ Complete | `32c477e` |
| TODO-2 | Swift SDK (iOS) - BLFeedbackClient.swift | ✅ Complete | `Latest` |
| TODO-3 | Kotlin SDK (Android) - BLFeedbackClient.kt | ✅ Complete | `Latest` |
| TODO-4 | Integration tests for full SAS flow | ✅ Complete | `bd19d44` |
| TODO-5 | GDPR deletion test | ✅ Complete | `Latest` |
| TODO-7 | Azure lifecycle policy configuration | ✅ Complete | `Latest` |

## Remaining TODOs

| # | TODO | Priority | Notes |
|---|------|----------|-------|
| TODO-6 | Download screenshot button | ✅ Fixed | Already implemented |

---

**Last Updated:** 2026-03-03

## Bugs Fixed During Review (2026-03-02)

| # | Bug | Fix |
|---|-----|-----|
| 1 | Inconsistent fields: `screenshotUrl`/`screenshotUrlExpiresAt` not used | Removed - SAS URLs generated on-demand |
| 2 | Blob path pattern mismatch | Updated to `feedbackScreenshots/{productId}/{feedbackId}/{screenshotId}.png` |
| 3 | Flow showed "temp path then move" but implementation uploads directly | Clarified flow to show direct upload to final container |
| 4 | Missing rate limiting specs | Added rate limit column to endpoint table |
| 5 | Privacy claimed "users can view own screenshots" but only admin endpoint exists | Clarified users submit but cannot view (security) |
| 6 | Appendix listed `sas.ts` file that wasn't created | Removed - SAS logic is in routes.ts |
| 7 | Inconsistent size limits: 5MB vs 10MB | Aligned to 5MB throughout |
| 8 | Missing `screenshotContentType` and `screenshotSizeBytes` | Added to data model example |

## Bugs Fixed During Second Review (2026-03-03)

| # | Bug | Fix | Commit |
|---|-----|-----|--------|
| 9 | Search icon positioning broken | Added `relative` to parent div, `pointer-events-none` to icon | Latest |
| 10 | Toast variant 'destructive' not valid | Changed to 'error' to match type definition | Latest |
| 11 | Routes referencing removed fields | Removed `screenshotUrl`/`screenshotUrlExpiresAt` from delete endpoint | Latest |
| 12 | Misleading blob path comment | Fixed comment to reflect blob stays at initial location | Latest |
| 13 | Missing download screenshot button | Added `downloadScreenshot()` function and UI button | Latest |
