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
- Bug Report with Screenshot — User encounters error → taps "Report Issue" → screenshot captured → typed description submitted
- Feature Request with Mockup — User uploads annotated screenshot showing desired UI change
- Crash Auto-Report — App crashes → on restart, user prompted to "Send crash report with screenshot"
- 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)
// 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)
// 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
// 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
// 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
1.2 Repository Layer — 8d2ba9c
1.3 API Routes — cfbaa92
1.4 Integration — ✅ Already wired in server.ts
Phase 2: Client SDK Updates (3-4 days) — ✅ PARTIAL
2.1 TypeScript SDK (@bytelyst/feedback-client) — b261cda
2.2 Swift SDK (iOS) — TODO
2.3 Kotlin SDK (Android) — TODO
Phase 3: Admin Dashboard UI (2-3 days) — ✅ COMPLETE
3.1 Feedback List Enhancements — d4e7f8a
3.2 Feedback Detail View — d4e7f8a
3.3 Client Library — d4e7f8a
Privacy & Security Considerations
PII Handling
Retention
Access Control
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
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 |