fix: ChatBubble crash and DeepSeek API compatibility

- Fix ChatBubble to handle non-string content with String() wrapper
- Fix API route to use generateText for non-streaming requests
- Add @ai-sdk/openai-compatible for non-OpenAI providers (DeepSeek, etc.)
- Use Chat Completions API instead of Responses API for compatible providers
- Update ChatBubble tests and fix component exports to kebab-case
- Remove stale PascalCase ChatBubble.tsx file
This commit is contained in:
Max
2026-01-26 16:55:05 +07:00
parent 6b113e0392
commit e9e6fadb1d
544 changed files with 113077 additions and 427 deletions

View File

@@ -0,0 +1,775 @@
# Story 2.4: Export & Copy Actions
Status: review
<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
## Story
As a user,
I want to copy the text or save the post,
So that I can publish it on LinkedIn or save it for later.
## Acceptance Criteria
1. **Copy to Clipboard on Thumbs Up**
- Given the user likes the draft
- When they click "Thumbs Up" or "Copy"
- Then the full Markdown text is copied to the clipboard
- And a success toast/animation confirms the action
2. **Save Draft as Completed**
- Given the draft is finalized
- When the user saves it
- Then it is marked as "Completed" in the local database
- And the user is returned to the Home/History screen
## Tasks / Subtasks
- [x] Implement Clipboard Copy Functionality
- [x] Create `copyToClipboard()` utility in `src/lib/utils/clipboard.ts`
- [x] Support copying Markdown formatted text
- [x] Handle clipboard API errors gracefully
- [x] Add fallback for older browsers
- [x] Create Success Feedback Toast
- [x] Create `CopySuccessToast.tsx` component in `src/components/features/feedback/`
- [x] Show confetti or success animation on copy
- [x] Auto-dismiss after 3 seconds
- [x] Include haptic feedback on mobile devices
- [x] Accessible: announce to screen readers
- [x] Extend DraftActions with Copy/Save
- [x] Add "Copy" button variant (separate from Thumbs Up)
- [x] Wire Thumbs Up to both copy AND save actions
- [x] Add "Just Copy" option (copy without closing sheet)
- [x] Add "Save & Close" action
- [x] Implement Draft Completion Logic
- [x] Add `completeDraft(draftId: number)` action to ChatStore
- [x] Update draft status from 'draft' to 'completed' in IndexedDB
- [x] Clear `currentDraft` from store after completion
- [x] Close DraftViewSheet after completion
- [ ] Navigate to Home/History screen (deferred to Epic 3)
- [x] Implement DraftService Completion Method
- [x] Add `markAsCompleted(draftId: string)` to `src/lib/db/draft-service.ts`
- [x] Update DraftRecord status in IndexedDB
- [x] Add `completedAt` timestamp
- [x] Return updated draft record
- [ ] Create History Screen Navigation
- [ ] Add navigation to Home/History after save
- [ ] Ensure completed draft appears in history feed
- [ ] Scroll to newly saved draft in history
- [ ] Highlight the newly saved entry
- [x] Add Copy Accessibility Features
- [x] Add `aria-label` to all copy buttons
- [x] Announce "Copied to clipboard" to screen readers
- [ ] Support keyboard shortcuts (Cmd+C / Ctrl+C) (deferred - enhancement)
- [ ] Ensure focus management after copy action (deferred - enhancement)
- [x] Test Copy & Save End-to-End
- [x] Unit test: clipboard.copyText() with Markdown (12 tests)
- [x] Unit test: DraftService.markAsCompleted() updates status (3 tests)
- [x] Integration test: Thumbs Up -> Copy -> Save flow (DraftViewSheet tests)
- [x] Integration test: Copy button only (no save) (DraftViewSheet tests)
- [x] Edge case: Clipboard permission denied (clipboard fallback tests)
- [x] Edge case: Draft already marked as completed (DraftService tests)
- [ ] Edge case: Copy with very long draft content (deferred - stress test)
- [ ] Test History Integration
- [ ] Integration test: Completed draft appears in history feed (deferred to Epic 3)
- [ ] Integration test: Copy action from history view (Epic 3)
- [ ] Test: Multiple drafts saved in session (deferred to Epic 3)
## Dev Notes
### Architecture Compliance (CRITICAL)
**Logic Sandwich Pattern - DO NOT VIOLATE:**
- **UI Components** MUST NOT import `src/lib/db` or clipboard utilities directly
- All completion logic MUST go through `ChatService` layer
- ChatService then calls `DraftService` for database operations
- Components use Zustand store via atomic selectors only
- Services return plain objects, not Dexie observables
**State Management - Atomic Selectors Required:**
```typescript
// BAD - Causes unnecessary re-renders
const { currentDraft } = useChatStore();
// GOOD - Atomic selectors
const currentDraft = useChatStore(s => s.currentDraft);
const completeDraft = useChatStore(s => s.completeDraft);
```
**Local-First Data Boundary:**
- Completed drafts MUST be stored in IndexedDB
- Status change from 'draft' to 'completed' persists locally
- Clipboard operations are client-side only (no server interaction)
- Draft history is queryable from history view (Epic 3)
**Edge Runtime Constraint:**
- This story does NOT require Edge API calls (clipboard is client-side only)
- All operations happen in the browser for privacy and speed
### Architecture Implementation Details
**Issue Resolution (Review Fixes):**
- **Logic Sandwich Violation:** Fixed `ChatStore.completeDraft` to call `ChatService.approveDraft` instead of direct DB acton.
- **Missing Component:** Created `CopyButton.tsx` and refactored `DraftActions` to use it.
- **Redundancy:** Consolidated `approveDraft` and `completeDraft` in Store.
**Story Purpose:**
This story implements the **"Export & Completion"** flow - the final step where users copy their polished draft to clipboard and save it to their local history. This completes the core value loop: Vent -> Generate -> Refine -> Export.
**State Management Extensions:**
```typescript
// Add to ChatStore (src/lib/store/chat-store.ts)
interface ChatStore {
// Existing state
currentDraft: Draft | null;
// New actions for this story
completeDraft: (draftId: string) => Promise<void>;
copyDraftToClipboard: (draftId: string) => Promise<void>;
copyAndSaveDraft: (draftId: string) => Promise<void>;
}
```
**Completion Logic Flow:**
1. User views draft in DraftViewSheet (Story 2.2)
2. User taps **Thumbs Up** OR **Copy** button
3. **Thumbs Up path:**
- Copy Markdown to clipboard
- Show success toast with confetti
- Mark draft as 'completed' in IndexedDB
- Clear currentDraft from store
- Close DraftViewSheet
- Navigate to Home/History
- Scroll to/highlight newly saved entry
4. **Copy Only path:**
- Copy Markdown to clipboard
- Show success toast
- Keep sheet open (user can continue viewing)
**Clipboard Utility Pattern:**
```typescript
// src/lib/utils/clipboard.ts
export class ClipboardUtil {
static async copyMarkdown(markdown: string): Promise<boolean> {
try {
await navigator.clipboard.writeText(markdown);
return true;
} catch (error) {
// Fallback for older browsers
return this.fallbackCopy(markdown);
}
}
private static fallbackCopy(text: string): boolean {
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
const success = document.execCommand('copy');
document.body.removeChild(textarea);
return success;
}
}
```
**Draft Completion in Database:**
```typescript
// src/lib/db/draft-service.ts
export class DraftService {
static async markAsCompleted(draftId: string): Promise<Draft> {
await db.drafts.update(draftId, {
status: 'completed',
completedAt: Date.now()
});
return this.getDraftById(draftId);
}
}
```
**Files to Create:**
- `src/lib/utils/clipboard.ts` - Clipboard utility with fallback
- `src/components/features/feedback/CopySuccessToast.tsx` - Success feedback
- `src/components/features/draft/CopyButton.tsx` - Standalone copy button
**Files to Modify:**
- `src/lib/store/chat-store.ts` - Add completion actions
- `src/lib/db/draft-service.ts` - Add markAsCompleted() method
- `src/services/chat-service.ts` - Add completion orchestration
- `src/components/features/draft/DraftActions.tsx` - Add Copy/Save buttons
- `src/app/(main)/page.tsx` - Navigate to history after save (optional scroll)
### UX Design Specifications
**From UX Design Document:**
**Completion Reward Pattern:**
- Thumbs Up triggers "You're done!" animation
- Confetti or haptic feedback provides dopamine hit
- Success toast confirms copy action
- Auto-navigation to history reinforces "saved to your journal"
**Visual Feedback - Success State:**
```mermaid
graph TD
A[User Taps Thumbs Up] --> B[Copy to Clipboard]
B --> C[Show Success Toast]
C --> D[Confetti Animation]
D --> E[Mark as Completed]
E --> F[Close Sheet]
F --> G[Navigate to History]
G --> H[Highlight New Entry]
```
**Toast Design Specifications:**
- Position: Top-center or bottom-center (mobile)
- Duration: 3 seconds auto-dismiss
- Icon: Checkmark or clipboard icon
- Text: "Copied to clipboard!" or "Draft saved!"
- Animation: Fade in + slide up
- Confetti: Subtle particle burst on success
**Button Hierarchy:**
- **Primary (Thumbs Up):** Copy + Save + Close (complete action)
- **Secondary (Copy Only):** Copy to clipboard, keep sheet open
- **Tertiary (Close):** Dismiss without saving
**Tone and Emotion:**
- Success state should feel celebratory
- "You're done, great job!" messaging
- Reinforces the value of completing the ritual
**Micro-interactions:**
- Thumbs Up should have "spring" animation
- Haptic feedback on mobile (vibration)
- Smooth transition to history screen
- New entry in history has subtle highlight/fade-in
### Previous Story Intelligence (from Stories 2.1, 2.2, and 2.3)
**Patterns Established (must follow):**
- **Logic Sandwich Pattern:** UI -> Zustand -> Service -> DB (strictly enforced)
- **Atomic Selectors:** All state access uses `useChatStore(s => s.field)`
- **Draft Storage:** Drafts stored in IndexedDB via `drafts` table
- **DraftViewSheet:** Sheet component that responds to `currentDraft` state
- **Draft Status Flow:** 'draft' -> 'regenerated' -> 'completed'
**Key Files from Previous Stories:**
- `src/lib/db/draft-service.ts` - Draft CRUD operations (add completion method)
- `src/lib/store/chat-store.ts` - Has currentDraft, add completion actions
- `src/components/features/draft/DraftActions.tsx` - Thumbs Up/Down, add Copy/Save
- `src/components/features/draft/DraftViewSheet.tsx` - Sheet component
- `src/services/chat-service.ts` - Chat orchestration (add completion flow)
**Learnings to Apply:**
- Story 2.2 established DraftViewSheet auto-opens on currentDraft - clear it to close
- Story 2.3 established draft versioning - 'completed' status marks the final version
- Use same success animation pattern from Story 2.2 (shimmer -> reveal)
- Follow same error handling pattern (retry on clipboard failure)
- Story 2.1 established DraftRecord structure - add `completedAt` timestamp
**Draft Data Structure (extending from Story 2.1):**
```typescript
interface DraftRecord {
id: string;
sessionId: string;
title: string;
content: string; // Markdown formatted
tags: string[];
createdAt: number;
completedAt?: number; // NEW: Set when draft is marked as completed
status: 'draft' | 'completed' | 'regenerated';
}
```
**Integration with Refinement (Story 2.3):**
- If user refined draft, the final version is marked as 'completed'
- Original draft(s) with status 'regenerated' remain in history
- Only the latest draft (the one user approved) gets 'completed' status
- History view (Epic 3) will show all versions, highlighting the completed one
### Clipboard Implementation Specifications
**Browser Clipboard API:**
```typescript
// Modern browsers (Chrome 66+, Firefox 63+, Safari 13.1+)
navigator.clipboard.writeText(markdown)
.then(() => showSuccessToast())
.catch(() => showFallbackMessage());
```
**Fallback for Older Browsers:**
```typescript
// Create hidden textarea, select, execCommand('copy')
// Remove textarea after copy
// Returns boolean success
```
**Clipboard Permissions:**
- Clipboard API requires user gesture (button click)
- No permissions needed for writeText() in active tab
- May fail in iframes or cross-origin contexts
**Accessibility for Copy:**
```typescript
<button
onClick={handleCopy}
aria-label="Copy draft to clipboard"
className="..."
>
<CopyIcon />
<span className="sr-only">Copy</span>
</button>
// After copy, announce to screen readers
useEffect(() => {
if (copied) {
announceToScreenReader('Draft copied to clipboard');
}
}, [copied]);
```
**Keyboard Shortcut Support:**
- Detect Cmd+C / Ctrl+C when draft is visible
- Show tooltip: "Press Cmd+C to copy"
- Prevent interference with browser default
### Testing Requirements
**Unit Tests:**
- `ClipboardUtil.copyMarkdown()` copies text correctly
- `ClipboardUtil.copyMarkdown()` handles errors gracefully
- `ClipboardUtil.fallbackCopy()` works for older browsers
- `DraftService.markAsCompleted()` updates status
- `DraftService.markAsCompleted()` sets completedAt timestamp
- `ChatStore.completeDraft()` clears currentDraft
- `ChatStore.copyDraftToClipboard()` calls ClipboardUtil
**Integration Tests:**
- Full completion flow: Thumbs Up -> Copy -> Save -> Navigate
- Copy only flow: Copy button -> Toast -> Sheet stays open
- Draft appears in history after completion
- Multiple drafts in session show all completed drafts
- Refinement + completion: Refined draft marked as completed
**Edge Cases:**
- Clipboard permission denied: Show error message
- Clipboard API unavailable: Use fallback method
- Draft already completed: Show message, don't duplicate
- Very long draft content: Should handle within clipboard limits
- Copy during refinement: Should work (copy current visible draft)
- Navigate away during copy: Should handle gracefully
**Accessibility Tests:**
- Screen reader announces "Copied to clipboard"
- Keyboard navigation works for all copy buttons
- Focus management after copy
- ARIA labels on all copy buttons
**Performance Tests:**
- Copy action completes within 500ms
- Success toast appears within 100ms
- Navigation to history is smooth (< 300ms)
### Component Implementation Details
**CopySuccessToast Component:**
```typescript
// src/components/features/feedback/CopySuccessToast.tsx
interface CopySuccessToastProps {
message: string;
duration?: number;
onClose: () => void;
}
export function CopySuccessToast({
message,
duration = 3000,
onClose
}: CopySuccessToastProps) {
useEffect(() => {
const timer = setTimeout(onClose, duration);
return () => clearTimeout(timer);
}, [duration, onClose]);
// Trigger confetti on mount
useEffect(() => {
triggerConfetti();
}, []);
return (
<div className="fixed bottom-4 left-1/2 -translate-x-1/2 ...">
<CheckIcon className="text-green-500" />
<span>{message}</span>
</div>
);
}
```
**CopyButton Component:**
```typescript
// src/components/features/draft/CopyButton.tsx
interface CopyButtonProps {
draftId: string;
onCopy?: () => void;
variant?: 'standalone' | 'toolbar';
}
export function CopyButton({
draftId,
onCopy,
variant = 'standalone'
}: CopyButtonProps) {
const [copied, setCopied] = useState(false);
const copyDraftToClipboard = useChatStore(s => s.copyDraftToClipboard);
const handleCopy = async () => {
await copyDraftToClipboard(draftId);
setCopied(true);
onCopy?.();
setTimeout(() => setCopied(false), 2000);
};
return (
<button
onClick={handleCopy}
aria-label={copied ? 'Copied!' : 'Copy to clipboard'}
className="..."
>
{copied ? <CheckIcon /> : <CopyIcon />}
</button>
);
}
```
**ChatService Extensions:**
```typescript
// src/services/chat-service.ts
export class ChatService {
async completeDraft(draftId: string): Promise<void> {
// Copy to clipboard
const draft = await DraftService.getDraftById(draftId);
await ClipboardUtil.copyMarkdown(draft.content);
// Mark as completed in database
await DraftService.markAsCompleted(draftId);
// Update store (clears currentDraft, closes sheet)
ChatStore.getState().completeDraft(draftId);
// Navigate to history
router.push('/history');
}
async copyDraftOnly(draftId: string): Promise<void> {
const draft = await DraftService.getDraftById(draftId);
await ClipboardUtil.copyMarkdown(draft.content);
// Don't clear currentDraft - keep sheet open
}
async copyAndSaveDraft(draftId: string): Promise<void> {
// Same as completeDraft but without navigation
const draft = await DraftService.getDraftById(draftId);
await ClipboardUtil.copyMarkdown(draft.content);
await DraftService.markAsCompleted(draftId);
ChatStore.getState().completeDraft(draftId);
}
}
```
**DraftActions Integration:**
```typescript
// Update DraftActions component
const handleThumbsUp = async () => {
// Full completion flow
await ChatService.completeDraft(draftId);
showSuccessToast('Draft saved to history!');
};
const handleCopyOnly = async () => {
// Copy without closing
await ChatService.copyDraftOnly(draftId);
showSuccessToast('Copied to clipboard!');
};
```
### History Navigation Strategy
**After Completion:**
```typescript
// Navigate to history with highlight
router.push({
pathname: '/history',
query: { highlight: draftId }
});
// In history page, scroll to highlighted draft
useEffect(() => {
if (router.query.highlight) {
const element = document.getElementById(`draft-${router.query.highlight}`);
element?.scrollIntoView({ behavior: 'smooth', block: 'center' });
element?.classList.add('highlight-pulse');
}
}, [router.query.highlight]);
```
**Highlight Animation:**
```css
/* In globals.css */
@keyframes highlight-pulse {
0% { box-shadow: 0 0 0 0 rgba(100, 116, 139, 0.7); }
70% { box-shadow: 0 0 0 10px rgba(100, 116, 139, 0); }
100% { box-shadow: 0 0 0 0 rgba(100, 116, 139, 0); }
}
.highlight-pulse {
animation: highlight-pulse 1s ease-out;
}
```
### Service Layer Extensions
**DraftService Completion:**
```typescript
// src/lib/db/draft-service.ts
export class DraftService {
static async markAsCompleted(draftId: string): Promise<Draft> {
const existing = await this.getDraftById(draftId);
if (existing.status === 'completed') {
// Already completed, just return
return existing;
}
await db.drafts.update(draftId, {
status: 'completed',
completedAt: Date.now()
});
return this.getDraftById(draftId);
}
static async getCompletedDrafts(): Promise<Draft[]> {
return await db.drafts
.where('status')
.equals('completed')
.reverse()
.sortBy('completedAt');
}
}
```
**ChatStore Actions:**
```typescript
// src/lib/store/chat-store.ts
interface ChatStore {
// Existing
currentDraft: Draft | null;
// New actions
completeDraft: (draftId: string) => Promise<void>;
copyDraftToClipboard: (draftId: string) => Promise<void>;
}
export const useChatStore = create<ChatStore>((set, get) => ({
// Existing state...
completeDraft: async (draftId: string) => {
// Mark as completed
await DraftService.markAsCompleted(draftId);
// Clear from store (closes DraftViewSheet)
set({ currentDraft: null });
},
copyDraftToClipboard: async (draftId: string) => {
const draft = await DraftService.getDraftById(draftId);
await ClipboardUtil.copyMarkdown(draft.content);
// Don't clear currentDraft - keep sheet open
},
}));
```
### Project Structure Notes
**Following Feature-First Lite Pattern:**
- New components in `src/components/features/feedback/` (toast)
- New components in `src/components/features/draft/` (copy button)
- Service extensions in existing files
- Store updates in `src/lib/store/chat-store.ts`
**Alignment with Unified Project Structure:**
- All feature code under `src/components/features/`
- Services orchestrate logic, don't touch DB directly from UI
- State managed centrally in Zustand stores
- Utility functions in `src/lib/utils/`
**No Conflicts Detected:**
- Completion is new status value, no conflicts
- Clipboard utility is new, no conflicts
- Navigation to history prepares for Epic 3
### Performance Requirements
**NFR-01 Compliance (Response Latency):**
- Copy action should complete within 500ms
- Success toast should appear within 100ms
- Navigation to history should be smooth (< 300ms)
**State Updates:**
- currentDraft should clear immediately on completion
- DraftViewSheet should close smoothly
- History page should render quickly
### Accessibility Requirements
**WCAG AA Compliance:**
- Copy buttons must have `aria-label`
- Success state must be announced to screen readers
- Focus management: Return focus after copy
- Keyboard shortcuts supported
**Visual Accessibility:**
- Success toast must be high contrast
- Avoid color-only indicators (use icons + text)
- Highlight animation must respect `prefers-reduced-motion`
### Security & Privacy Requirements
**NFR-03 & NFR-04 Compliance:**
- Clipboard operations are client-side only
- No draft content sent to server
- Completed drafts stay in IndexedDB
- No external API calls for copy/save
### References
**Epic Reference:**
- [Epic 2: "The Magic Mirror" - Ghostwriter & Draft Refinement](file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/epics.md#epic-2-the-magic-mirror---ghostwriter--draft-refinement)
- [Story 2.4: Export & Copy Actions](file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/epics.md#story-24-export--copy-actions)
- FR-07: "Users can 'One-Click Copy' the formatted text to clipboard"
- FR-09: "Users can edit the generated draft manually before exporting" (future enhancement)
**Architecture Documents:**
- [Project Context: Logic Sandwich](file:///home/maximilienmao/Projects/Test01/_bmad-output/project-context.md#1-the-logic-sandwich-pattern-service-layer)
- [Project Context: State Management](file:///home/maximilienmao/Projects/Test01/_bmad-output/project-context.md#2-state-management-zustand)
- [Project Context: Local-First Boundary](file:///home/maximilienmao/Projects/Test01/_bmad-output/project-context.md#3-local-first-data-boundary)
**UX Design Specifications:**
- [UX: Completion Reward Pattern](file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/ux-design-specification.md#24-novel-ux-patterns)
- [UX: Success State](file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/ux-design-specification.md#5-completion-if-liked)
- [UX: Micro-interactions](file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/ux-design-specification.md#micro-interactions)
**Previous Stories:**
- [Story 2.1: Ghostwriter Agent & Markdown Generation](file:///home/maximilienmao/Projects/Test01/_bmad-output/implementation-artifacts/2-1-ghostwriter-agent-markdown-generation.md) - Draft data structure
- [Story 2.2: Draft View UI (The Slide-Up)](file:///home/maximilienmao/Projects/Test01/_bmad-output/implementation-artifacts/2-2-draft-view-ui-the-slide-up.md) - DraftViewSheet and DraftActions
- [Story 2.3: Refinement Loop (Regeneration)](file:///home/maximilienmao/Projects/Test01/_bmad-output/implementation-artifacts/2-3-refinement-loop-regeneration.md) - Draft versioning pattern
## Dev Agent Record
### Agent Model Used
Claude Opus 4.5 (model ID: 'claude-opus-4-5-20251101')
### Debug Log References
Session file: `/tmp/claude/-home-maximilienmao-Projects/Test01/4ce6b396-ff74-42ee-be06-133000333628/scratchpad`
### Completion Notes List
**Story Analysis Completed:**
- Extracted story requirements from Epic 2, Story 2.4
- Analyzed previous Stories 2.1, 2.2, and 2.3 for established patterns
- Reviewed architecture for compliance requirements (Logic Sandwich, State Management, Local-First)
- Reviewed UX specification for completion reward and success state patterns
- Identified all files to create and modify
**Implementation Context Summary:**
**Story Purpose:**
This story implements the **"Export & Completion"** flow - the final step where users copy their polished draft to clipboard and save it to their local history. This completes the core value loop: Vent -> Generate -> Refine -> Export -> Save.
**Key Technical Decisions:**
1. **Clipboard Utility:** Create reusable ClipboardUtil with fallback for older browsers
2. **Completion Status:** Add 'completed' status to DraftRecord with completedAt timestamp
3. **Success Feedback:** CopySuccessToast with confetti animation for reward
4. **Two Copy Modes:** (a) Thumbs Up = copy + save + close, (b) Copy Only = copy without closing
5. **Navigation:** Auto-navigate to history with highlight after completion
6. **Haptic Feedback:** Vibration on mobile for tactile confirmation
**Dependencies:**
- No new external dependencies required
- Uses browser Clipboard API with fallback
- Reuses existing Zustand, Dexie infrastructure
- May need confetti library (canvas-confetti) for celebration effect
**Integration Points:**
- Thumbs Up in DraftActions (Story 2.2) triggers ChatService.completeDraft()
- Completion updates DraftRecord status in IndexedDB
- Clearing currentDraft closes DraftViewSheet (auto-close pattern from Story 2.2)
- Navigation to history prepares for Epic 3 implementation
**Files to Create:**
- `src/lib/utils/clipboard.ts` - Clipboard utility with fallback
- `src/components/features/feedback/CopySuccessToast.tsx` - Success feedback component
- `src/components/features/draft/CopyButton.tsx` - Standalone copy button
**Files to Modify:**
- `src/lib/store/chat-store.ts` - Add completion actions (completeDraft, copyDraftToClipboard)
- `src/lib/db/draft-service.ts` - Add markAsCompleted() method
- `src/services/chat-service.ts` - Add completion orchestration
- `src/components/features/draft/DraftActions.tsx` - Wire Thumbs Up to completion flow
**Testing Strategy:**
- Unit tests for clipboard utility (including fallback)
- Integration tests for full completion flow
- Edge case tests (clipboard denied, already completed, very long content)
- Accessibility tests (screen reader announcements, keyboard navigation)
**Draft Completion Pattern:**
- Thumbs Up triggers: Copy -> Toast -> Mark Completed -> Clear State -> Navigate
- Copy Only triggers: Copy -> Toast (no state change)
- Completed draft gets status='completed' and completedAt timestamp
- History view (Epic 3) will query by completedAt for reverse chronological order
**User Experience Flow:**
```
DraftViewSheet (Story 2.2)
User taps Thumbs Up
Clipboard.copy() - Copy Markdown to clipboard
CopySuccessToast - "Copied to clipboard!" + confetti
DraftService.markAsCompleted() - Update IndexedDB
ChatStore.completeDraft() - Clear currentDraft (closes sheet)
Navigate to /history with ?highlight=draftId
History scrolls to and highlights the new entry
```
### File List
**New Files to Create:**
- `src/lib/utils/clipboard.ts` - Clipboard utility with fallback support
- `src/components/features/feedback/CopySuccessToast.tsx` - Success toast with confetti
- `src/components/features/draft/CopyButton.tsx` - Standalone copy button
- `src/lib/utils/clipboard.test.ts` - Clipboard utility tests
- `src/integration/export-copy-actions.test.ts` - End-to-end completion flow tests
**Files to Modify:**
- `src/lib/store/chat-store.ts` - Add completeDraft, copyDraftToClipboard actions
- `src/lib/db/draft-service.ts` - Add markAsCompleted() method and getCompletedDrafts()
- `src/lib/db/draft-service.test.ts` - Add completion method tests
- `src/services/chat-service.ts` - Add completion orchestration (completeDraft, copyDraftOnly)
- `src/components/features/draft/DraftActions.tsx` - Wire Thumbs Up and Copy button to completion