critical priority medium complexity integration pending fullstack developer Tier 3

Acceptance Criteria

AttachmentBloc exposes the following sealed state variants: AttachmentInitial, AttachmentUploading(double progress), AttachmentUploadSuccess(AttachmentMetadata metadata), AttachmentUploadFailure(AttachmentUploadError error), AttachmentUrlLoading, AttachmentUrlReady(String signedUrl), AttachmentUrlFailure(SignedUrlError error)
AttachmentUploading state carries upload progress (0.0–1.0) sourced from the service's progress stream, emitted as incremental state updates
On Left<AttachmentUploadError> from the service, the BLoC maps each error variant to AttachmentUploadFailure with the typed error preserved — no raw exception types leak into state
On successful upload, AttachmentUploadService.invalidateCacheEntry is NOT called (cache has no entry for a newly uploaded file); on successful delete event, cache invalidation IS triggered before emitting a delete success state
All state transitions are accessible: each failure state must carry a human-readable `accessibleMessage` string property formatted for WCAG 2.2 AA live region announcement (e.g., 'Upload failed: file too large. Maximum size is 10 MB.')
The BLoC handles concurrent upload and URL-fetch events correctly — a URL fetch while an upload is in progress does not cancel the upload
BLoC is disposed correctly when the widget it is scoped to is removed from the tree — no dangling stream subscriptions

Technical Requirements

frameworks
Flutter
BLoC
apis
AttachmentUploadService (internal)
AttachmentSignedUrlService (internal)
data models
activity
performance requirements
Upload progress states emitted at most every 100 ms to avoid excessive UI rebuilds during large file uploads
BLoC event queue must not block — service calls must be awaited inside event handlers, not on the event queue thread
security requirements
Signed URLs must not be stored in BLoC state beyond the widget's lifecycle — state is in-memory only, not persisted
Error messages in AccessibleMessage must not include internal storage paths, bucket names, or org IDs
ui components
Semantics widget wrapping upload progress indicator (live region role='status')
Error announcement widget using Semantics with liveRegion: true for upload/URL failure states

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use `flutter_bloc` Cubit if state transitions are simple and unidirectional; use full BLoC with Events if you need event deduplication or event transformers (e.g., debouncing URL fetches). Prefer Cubit for this scope. Use `emit.onEach` (bloc 8.x) or `StreamSubscription` to relay upload progress from the service's `Stream` into incremental `Uploading(progress)` states. Define the `accessibleMessage` as a getter on each failure state class derived from the typed error — e.g., `AttachmentUploadError.fileTooLarge.toAccessibleMessage()` returns a localized, human-readable string.

Keep error-to-message mapping in the error sealed class, not in the BLoC, to keep the BLoC thin. Dispose all stream subscriptions in `close()`.

Testing Requirements

Bloc unit tests (bloc_test package): (1) UploadAttachmentEvent → emits [Uploading(0.0), Uploading(0.5), Uploading(1.0), UploadSuccess] for happy path with progress stream, (2) UploadAttachmentEvent with fileTooLarge validation error → emits [UploadFailure(fileTooLarge)] with correct accessibleMessage string, (3) UploadAttachmentEvent with storageFailure → emits [Uploading, UploadFailure(storageFailure)], (4) FetchSignedUrlEvent → emits [UrlLoading, UrlReady(url)], (5) FetchSignedUrlEvent with service failure → emits [UrlLoading, UrlFailure], (6) DeleteAttachmentEvent → verify invalidateCacheEntry called, emits deleteSuccess state. Use bloc_test's `expect:` array to assert exact state sequences. Test accessibleMessage content for each error variant.

Component
Attachment Upload Service
service medium
Epic Risks (3)
medium impact medium prob technical

The storage upload succeeds but the subsequent metadata insert fails. The rollback delete call to Supabase Storage could itself fail (network error, transient timeout), leaving an orphaned object in the bucket with no database record pointing to it — a cost and compliance risk that also breaks delete-on-cascade logic.

Mitigation & Contingency

Mitigation: Wrap the rollback delete in a retry loop (3 attempts, exponential back-off). Log orphaned-object incidents to a dedicated structured log stream for periodic audit. Consider a scheduled Supabase Edge Function that reconciles storage objects against database records and flags orphans.

Contingency: If orphaned objects accumulate, run the reconciliation edge function manually to identify and purge them. Add a monitoring alert for metadata insert failures after successful uploads so the issue is caught within minutes.

medium impact medium prob scope

If the signed URL TTL is set too short, users browsing the attachment preview modal on slow connections will receive expired URLs before the content loads, causing a broken experience. If set too long, a URL shared outside the app (e.g., pasted into a chat) remains valid beyond the intended access window.

Mitigation & Contingency

Mitigation: Default TTL to 60 minutes, configurable via a named constant. The in-memory cache TTL should be set to TTL minus 5 minutes to ensure cached URLs are refreshed before they expire. Document the trade-off in code comments.

Contingency: If users report broken previews, shorten the cache TTL hotfix. If a URL leak is reported, rotate the Supabase storage signing secret to invalidate all outstanding signed URLs immediately.

medium impact medium prob technical

The multi-attachment user story requires parallel uploads with individual progress indicators. Managing concurrent BLoC events for 3–5 simultaneous uploads risks state collisions, progress indicator mixups, or partial rollbacks that are difficult to reason about.

Mitigation & Contingency

Mitigation: Design the BLoC to maintain a per-attachment upload state map keyed by a client-generated UUID. Each upload runs as an isolated Future with its own result emitted as a typed event. Write integration tests for 3-concurrent-upload scenarios.

Contingency: If state collisions occur in production, fall back to sequential upload processing (one at a time) gated behind a feature flag until the concurrent model is stabilised.