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