high priority medium complexity infrastructure pending infrastructure specialist Tier 0

Acceptance Criteria

captureWidget(globalKey, pixelRatio) returns a non-empty Future<Uint8List> when the provided GlobalKey is attached to a rendered RenderRepaintBoundary
The returned Uint8List begins with the PNG magic bytes [137, 80, 78, 71, 13, 10, 26, 10], confirming a valid PNG encoding
Output image dimensions scale correctly with the pixelRatio parameter: passing pixelRatio=2.0 on a 200×100 logical-pixel widget produces a 400×200 physical-pixel PNG
captureToFile(globalKey, filePath) writes the PNG bytes to the specified path and returns the File object; the file is readable and non-empty
If the GlobalKey is not attached to any RenderObject, captureWidget throws a typed ScreenshotCaptureException with message 'RenderObject not found' rather than an unhandled StateError
On devices with fewer than 512 MB available RAM (simulated via reduced pixelRatio or forced OOM in tests), the utility catches the OOM error, releases the image object, and rethrows a ScreenshotMemoryException with actionable context
The RenderRepaintBoundary.toImage() Future completes within 3 seconds for widgets up to 1080×1920 px at pixelRatio=3.0
The captured PNG does not include any sensitive PII visible in the Wrapped summary beyond what the user intentionally shares (verified by design review checklist)
No dart:io file handles or ByteData buffers remain open after captureToFile resolves or throws
The class is annotated with @visibleForTesting on internal helpers so they can be mocked in unit tests without exposing them in the public API

Technical Requirements

frameworks
Flutter (dart:ui, RenderRepaintBoundary, RepaintBoundary)
flutter/rendering.dart
dart:typed_data (Uint8List, ByteData)
dart:io (File, Directory)
apis
RenderRepaintBoundary.toImage(pixelRatio)
ui.Image.toByteData(format: ui.ImageByteFormat.png)
path_provider (getTemporaryDirectory) for temp file paths
data models
annual_summary
performance requirements
PNG encoding must complete within 3 seconds for a full-screen 1080×1920 px capture at device pixel ratio
Peak heap allocation during capture must not exceed 50 MB above baseline; release ui.Image after ByteData extraction
captureToFile must use async File.writeAsBytes to avoid blocking the main isolate
security requirements
Captured PNG files written to the system temp directory (getTemporaryDirectory) must not be written to app Documents or Downloads directories to avoid unintended exposure
Temporary files created by captureToFile must be deleted by the caller after use; provide a deleteFile(path) convenience method
No PII from contact or assignment data models should be rendered inside the Wrapped summary widget subtree being captured without explicit user consent flow completed upstream
Exif metadata must not be embedded in the PNG output (dart:ui toByteData does not embed Exif, confirm this in integration test)

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Wrap the target widget in a RepaintBoundary with the provided GlobalKey. At capture time, resolve the key to a RenderRepaintBoundary via `key.currentContext?.findRenderObject()`. Call `boundary.toImage(pixelRatio: pixelRatio)` and then `image.toByteData(format: ui.ImageByteFormat.png)`. Always call `image.dispose()` in a finally block to release GPU memory.

For OOM, catch `Exception` broadly around the toImage call and inspect the message for 'out of memory'; rethrow as ScreenshotMemoryException. For captureToFile, use `path_provider`'s `getTemporaryDirectory()` to build a safe temp path with a UUID filename. Do not use dart:ffi or platform channels — dart:ui provides all necessary primitives. Avoid calling toImage() on a boundary that has not been laid out (check `hasSize` before proceeding).

Consider adding a `WidgetsBinding.instance.addPostFrameCallback` wrapper if callers invoke capture before the frame is complete.

Testing Requirements

Unit tests using a mock RenderRepaintBoundary (via GlobalKey injection) to verify: (1) valid PNG magic bytes in output, (2) correct exception type on missing RenderObject, (3) OOM path triggers ScreenshotMemoryException. Widget tests to verify captureWidget resolves without error when a real RepaintBoundary-wrapped widget is mounted. Integration tests on device/emulator (see task-014). Target 90%+ line coverage on the ScreenshotCaptureUtility class.

Use flutter_test for unit and widget layers.

Component
Screenshot Capture Utility
infrastructure medium
Epic Risks (3)
medium impact medium prob dependency

Rive animation files may not be available at implementation time, blocking the wrapped-animation-controller from being fully tested. If asset delivery is delayed, the controller cannot be validated for memory-leak-free disposal.

Mitigation & Contingency

Mitigation: Implement the animation controller with stub/placeholder AnimationController instances first so the lifecycle and disposal logic can be unit-tested independently of Rive assets. Define a named animation registry interface early so UI components can reference animations by name without coupling to specific Rive files.

Contingency: If Rive assets are not delivered before Epic 3 begins, replace Rive animations with Flutter implicit animations (AnimatedOpacity, ScaleTransition) as a drop-in and schedule Rive integration as a follow-on task once assets arrive.

high impact medium prob technical

The annual_summaries Supabase RPC aggregating 12 months of activity records per mentor may exceed acceptable query latency (>2s) for mentors with high activity volumes such as the HLF mentor with 380 registrations cited in workshop notes.

Mitigation & Contingency

Mitigation: Design the RPC to materialise summary results into the annual_summaries table via a scheduled edge function rather than computing on demand. The repository reads pre-computed rows, keeping query latency constant regardless of activity volume.

Contingency: If on-demand queries are required for real-time period switching, add a PostgreSQL partial index on (mentor_id, activity_date) and implement a client-side loading skeleton so slow queries degrade gracefully rather than blocking the UI.

medium impact low prob technical

iOS requires photo library permission before saving a screenshot to the gallery. If the permission prompt is triggered at an unexpected point in the share flow, the UX breaks and users may deny permission permanently, making gallery save unavailable.

Mitigation & Contingency

Mitigation: Trigger the permission request only when the user explicitly chooses 'Save to gallery' in the share overlay, not on screen load. Implement a pre-prompt explanation screen following Apple HIG so users understand why the permission is needed before the system dialog appears.

Contingency: If permission is denied, gracefully fall back to clipboard copy and system share sheet options which do not require photo library access, and surface a non-blocking snackbar explaining the limitation.