Detail page now has a "Copy URL" button that copies the image's direct file URL to the clipboard. A toast service (BehaviorSubject-backed, auto-dismissing after 3s) confirms success or failure. ToastComponent is registered at the app root and available to all future features. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3.6 KiB
Research: Copy URL & Toast Notifications
Decision 1: Toast Service Architecture
Decision: BehaviorSubject<Toast | null> singleton service, one active toast at a time — new toasts replace the current one.
Rationale: The simplest approach that satisfies FR-007 (reusable from anywhere) and FR-008 (multiple toasts don't overlap illegibly). A queue adds complexity with no meaningful UX benefit for this app's usage pattern (copy URL, upload confirm, etc. — actions that don't overlap in practice). Replacing the current toast on rapid successive calls is acceptable and visually cleaner than a stack. The BehaviorSubject integrates naturally with Angular's async pipe and OnPush change detection.
Alternatives considered:
Subject(notBehaviorSubject): Late subscribers miss toasts that already fired. Rejected — component may subscribe after service emits if change detection is deferred.- Toast queue (array): Adds observable complexity and UI layout decisions. Rejected — over-engineered for this use case.
- Angular CDK Overlay: Official but heavy. Pulls in CDK dependency for a feature that needs ~30 lines of code. Rejected per §2.6 (no speculative abstraction) and §6 (no new dependencies).
Decision 2: Clipboard API Usage
Decision: navigator.clipboard.writeText(url) — no polyfill, no fallback to document.execCommand.
Rationale: execCommand('copy') is deprecated and removed in some browsers. The Clipboard API is supported in all modern browsers (Chrome 66+, Firefox 63+, Safari 13.1+). The app already requires HTTPS in production (Let's Encrypt via cert-manager), which satisfies the Clipboard API's secure context requirement. On failure (permission denied, API unavailable), catch the rejected Promise and show an error toast.
Alternatives considered:
execCommand('copy')fallback: Deprecated, inconsistent, adds code complexity. The failure path (error toast) covers the rare unavailability case more cleanly.
Decision 3: What URL to Copy
Decision: Copy image.file_url as-is (the direct image file URL).
Rationale: file_url is the CDN URL in production (e.g. https://cdn.reactbin.juggalol.com/…) — already absolute. In development it is relative (/api/v1/images/{id}/file); for dev use, prepend window.location.origin. The direct file URL is the right thing to share for a reaction image library: it embeds inline when pasted into Discord/Slack without requiring a click-through.
Alternatives considered:
- Detail page URL (
/images/{id}): The user can already copy this from the browser address bar. The file URL is the value-add. - Always prepend
window.location.origin: Works for both environments, adds a guard. Included as a defensive measure for the dev case.
Decision 4: Toast Positioning
Decision: Fixed position, bottom-center of the viewport.
Rationale: Bottom-center is less intrusive than top-right for a brief confirmation toast. It doesn't overlap the image or the copy button. pointer-events: none ensures it never blocks interaction.
Alternatives considered:
- Top-right: Common convention (Material, Bootstrap) but overlaps the header/nav area in this layout.
- Top-center: Similar issue.
Decision 5: OnPush compatibility
Decision: ToastComponent uses ChangeDetectionStrategy.OnPush with the async pipe consuming toastService.current$. Angular's async pipe calls markForCheck() automatically when the observable emits, making it fully compatible with OnPush.
Rationale: Consistent with all other components in the project. No manual markForCheck() calls needed in ToastComponent.