# 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 5. Client submits feedback: POST /api/feedback (with screenshotBlobPath) 6. Admin views feedback in dashboard with image preview ``` --- ## Data Model Changes ### Option A: Inline Screenshot (Simple) ```typescript // feedback/types.ts — Add to existing FeedbackDoc interface FeedbackDoc { // ... existing fields ... // Screenshot attachment (single) screenshotBlobPath?: string; // "feedback/{productId}/{feedbackId}/{screenshotId}.png" screenshotUrl?: string; // Time-limited SAS URL for viewing screenshotUrlExpiresAt?: string; // When SAS URL expires // Device context for debugging deviceContext?: { osVersion: string; appVersion: string; deviceModel: string; screenResolution: string; locale: string; }; } // Add to CreateFeedbackSchema screenshotBlobPath: z.string().optional(), 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 | Description | |--------|----------|------|-------------| | `POST` | `/api/feedback/sas` | User | Get SAS URL for screenshot upload | | `POST` | `/api/feedback` | User | Submit feedback (with optional screenshotBlobPath) | | `GET` | `/api/feedback/:id/screenshot` | Admin | Get fresh SAS URL for viewing screenshot | | `DELETE` | `/api/feedback/:id/screenshot` | Admin | 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": "feedback/lysnrai/feedback_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": "feedback/lysnrai/feedback_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) #### 1.1 Data Model Extension - [ ] Add `screenshotBlobPath` to `FeedbackDoc` interface - [ ] Add `deviceContext` to `FeedbackDoc` interface - [ ] Update `CreateFeedbackSchema` with new fields - [ ] Add `feedback_screenshots` container to `cosmos-init.ts` (if Option B) #### 1.2 Repository Layer - [ ] Extend `createFeedback()` to handle screenshot metadata - [ ] Add `generateScreenshotSas()` function (wrapper around blob module) - [ ] Add `getFeedbackWithScreenshot()` with fresh SAS URL generation - [ ] Add `deleteFeedbackScreenshot()` for GDPR compliance #### 1.3 API Routes - [ ] `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 - [ ] Update `POST /api/feedback` — Accept screenshot metadata - [ ] `GET /api/feedback/:id/screenshot` — Get fresh view URL - [ ] `DELETE /api/feedback/:id/screenshot` — Admin delete #### 1.4 Integration - [ ] Wire new routes into `server.ts` - [ ] Add blob container `user-feedback` to blob module config - [ ] Set lifecycle policy: 90-day TTL for user screenshots #### 1.5 Testing - [ ] Unit tests for SAS generation - [ ] Unit tests for feedback with screenshot submission - [ ] Integration test: full flow (SAS → upload → submit → view) - [ ] GDPR deletion test ### Phase 2: Client SDK Updates (3-4 days) #### 2.1 TypeScript SDK (`@bytelyst/feedback-client`) - [ ] `FeedbackClient.submitWithScreenshot(params)` - Internal flow: get SAS → upload blob → submit feedback - Progress callbacks for upload - [ ] `FeedbackClient.captureAndSubmit()` — Auto-capture current screen #### 2.2 Swift SDK (iOS) - [ ] `FeedbackManager.submit(title:body:screenshot:)` - [ ] `FeedbackManager.captureScreenshot()` — UIImage → blob upload - [ ] Annotation overlay (optional drawing on screenshot) #### 2.3 Kotlin SDK (Android) - [ ] `FeedbackManager.submitWithScreenshot()` - [ ] MediaProjection integration for screenshot capture - [ ] Composable for in-app feedback sheet with screenshot preview ### Phase 3: Admin Dashboard UI (2-3 days) #### 3.1 Feedback List Enhancements - [ ] Thumbnail preview of screenshot in list view - [ ] Filter: "Has screenshot" / "No screenshot" #### 3.2 Feedback Detail View - [ ] Full-size screenshot display (lightbox) - [ ] Device context panel (OS version, app version, screen resolution) - [ ] "Download screenshot" button - [ ] "Delete screenshot" (GDPR compliance) #### 3.3 Client Library - [ ] `lib/feedback-client.ts` — Add `getScreenshotUrl(feedbackId)` --- ## 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 can only view their own screenshots - [ ] Admins can view all screenshots for their product - [ ] Fresh SAS URLs generated per view (time-limited, 15 minutes) --- ## 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/ ├── sas.ts # SAS generation helpers └── 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 ``` --- **Last Updated:** 2026-03-02