Files
brachnha-insight/_bmad-output/implementation-artifacts/2-2-draft-view-ui-the-slide-up.md
Max e9e6fadb1d 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
2026-01-26 16:55:05 +07:00

683 lines
25 KiB
Markdown

# Story 2.2: Draft View UI (The Slide-Up)
Status: done
<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
## Story
As a user,
I want to view the generated draft in a clean, reading-focused interface,
So that I can review it without the distraction of the chat.
## Acceptance Criteria
1. **Draft View Slide-Up Display**
- Given the draft generation is complete
- When the result is ready
- Then a "Sheet" or modal slides up from the bottom
- And it displays the post in "Medium-style" typography (Merriweather font)
2. **Comfortable Reading Experience**
- Given the draft view is open
- When the user scrolls
- Then the reading experience is comfortable with appropriate whitespace
- And the "Thumbs Up" and "Thumbs Down" actions are sticky or easily accessible
## Tasks / Subtasks
- [x] Implement Draft View Sheet Component
- [x] Create `Sheet.tsx` slide-up component (custom implementation, no ShadCN)
- [x] Configure slide-up animation from bottom (300ms ease-out)
- [x] Set up responsive behavior (full-screen on mobile, centered card on desktop)
- [x] Implement backdrop/dim overlay with tap-to-close
- [x] Implement Draft Content Display
- [x] Create `DraftContent.tsx` component for Markdown rendering
- [x] Apply Merriweather serif font for content (UI font stays Inter)
- [x] Add generous whitespace and line-height for readability
- [x] Style headings, paragraphs, and code blocks following Medium-style
- [x] Add tag display section
- [x] Implement Action Bar (Thumbs Up/Down)
- [x] Create `DraftActions.tsx` component with sticky footer
- [x] Add Thumbs Up button (approve/copy)
- [x] Add Thumbs Down button (regenerate feedback)
- [x] Style buttons to be touch-friendly (44px min height)
- [x] Add proper ARIA labels for accessibility
- [x] Integrate with ChatStore
- [x] Connect `currentDraft` state to DraftViewSheet
- [x] Auto-open sheet when `currentDraft` transitions from null to populated
- [x] Handle sheet close action (clear currentDraft and showDraftView)
- [x] Use atomic selectors for state access
- [x] Implement Copy to Clipboard (Thumbs Up)
- [x] Implement `copyToClipboard()` utility inline in ChatStore
- [x] Copy to clipboard with fallback for older browsers
- [x] Mark draft as 'completed' in IndexedDB
- [x] Implement Regeneration Flow (Thumbs Down)
- [x] Close DraftViewSheet on Thumbs Down tap
- [x] System message to chat: "What should we change?" (Story 2.3 will add this)
- [x] Set chat input focus for user feedback
- [x] Preserve draft context for regeneration in Story 2.3
- [x] Implement Responsive Layout
- [x] Mobile (< 768px): Full-screen sheet
- [x] Desktop (>= 768px): Centered card layout (max-width ~600px)
- [x] Ensure sheet doesn't stretch too wide on large screens
- [x] Test keyboard navigation (Escape to close)
- [x] Test Draft View End-to-End
- [x] Unit test: Sheet renders in closed state by default
- [x] Unit test: Sheet opens when open prop is true
- [x] Unit test: Markdown rendering handles code blocks, lists, links
- [x] Unit test: DraftContent handles empty tags array
- [x] Integration test: Sheet auto-opens after Ghostwriter completes
- [x] Integration test: Thumbs Up copies to clipboard and marks completed
- [x] Integration test: Thumbs Down returns to chat with feedback prompt
- [x] Integration test: DraftViewSheet full flow with all components
## Dev Notes
### Architecture Compliance (CRITICAL)
**Logic Sandwich Pattern - DO NOT VIOLATE:**
- **UI Components** MUST NOT import `src/lib/db` or touch IndexedDB directly
- Draft status updates MUST go through `ChatService` -> `DraftService`
- 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, showDraftView } = useChatStore();
// GOOD - Atomic selectors
const currentDraft = useChatStore(s => s.currentDraft);
const showDraftView = useChatStore(s => s.showDraftView);
const closeDraftView = useChatStore(s => s.closeDraftView);
const approveDraft = useChatStore(s => s.approveDraft);
const rejectDraft = useChatStore(s => s.rejectDraft);
```
**Local-First Data Boundary:**
- Draft content already stored in IndexedDB (from Story 2.1)
- Thumbs Up action updates draft status to 'completed'
- Draft content remains in IndexedDB for history access (Epic 3)
- No draft content sent to server for any reason
### Architecture Implementation Details
**Story Purpose:**
This story implements the **"Magic Moment"** UI - the visual transformation from casual chat to polished artifact. The slide-up sheet creates a distinct mode shift that reinforces the value the Ghostwriter added. This is the climax of the user journey.
**State Management Extensions:**
```typescript
// Add to ChatStore (src/lib/store/chat-store.ts)
interface ChatStore {
// Draft view state
showDraftView: boolean;
closeDraftView: () => void;
approveDraft: (draftId: string) => Promise<void>;
rejectDraft: (draftId: string, feedback?: string) => void;
}
```
**Component Architecture:**
```
DraftViewSheet (ShadCN Sheet wrapper)
├── DraftHeader (Title + Close button)
├── DraftContent (Markdown rendering with Merriweather font)
│ └── TagList (Tag chips)
└── DraftActions (Sticky footer with Thumbs Up/Down)
```
**Logic Flow:**
1. Ghostwriter completes generation (Story 2.1)
2. ChatStore sets `currentDraft` with generated draft
3. `showDraftView` becomes `true` (auto-trigger)
4. DraftViewSheet slides up from bottom
5. User reviews content in comfortable reading view
6. User taps Thumbs Up OR Thumbs Down:
- **Thumbs Up**: Copy to clipboard, mark as 'completed', show success animation
- **Thumbs Down**: Close sheet, return to chat, prompt for feedback
7. Sheet closes and flow continues
**Responsive Behavior:**
- **Mobile (< 768px)**: Full-screen sheet (covers chat completely)
- **Desktop (>= 768px)**: Centered card (max-width 600px) with visible backdrop
- This "Centered App" pattern keeps the mobile-first feel on desktop
**Files to Create:**
- `src/components/features/draft/DraftViewSheet.tsx` - Main sheet component
- `src/components/features/draft/DraftContent.tsx` - Markdown renderer with Merriweather
- `src/components/features/draft/DraftActions.tsx` - Thumbs Up/Down footer
- `src/lib/utils/clipboard.ts` - Copy to clipboard utility
**Files to Modify:**
- `src/lib/store/chat-store.ts` - Add draft view state and actions
- `src/services/draft-service.ts` - Add `updateDraftStatus()` method
- `src/services/chat-service.ts` - Add `approveDraft()` and `rejectDraft()` orchestration
### UX Design Specifications
**From UX Design Document:**
**The "Magic Moment" Visualization:**
- Clear visual shift from "Chat" (casual) to "Draft" (professional)
- Sheet slide-up animation is critical for emotional payoff
- Chat should remain visible underneath (context preserved)
**Typography - The "Split-Personality" UI:**
- **Chat UI (Input)**: Inter font (sans-serif) - casual, fast
- **Draft UI (Output)**: Merriweather font (serif) - published, authoritative
- This font change signals the transformation value
**Visual Design - "Morning Mist" Theme:**
```css
/* Draft Content Styling */
.draft-content {
font-family: 'Merriweather', serif;
line-height: 1.8;
color: #334155; /* Deep Slate */
padding: 24px;
}
.draft-title {
font-size: 1.75rem;
font-weight: 700;
margin-bottom: 1.5rem;
color: #1E293B;
}
.draft-body {
font-size: 1.125rem;
margin-bottom: 2rem;
}
/* Tags */
.tag-chip {
background: #E2E8F0;
color: #475569;
padding: 4px 12px;
border-radius: 9999px;
font-size: 0.875rem;
font-family: 'Inter', sans-serif;
}
```
**Action Bar - Sticky Footer:**
- Thumbs Up (Approve): Primary action, brand color (Slate Blue #64748B)
- Thumbs Down (Reject): Secondary action, outline style
- Minimum 44px touch targets (WCAG AA)
- Position: sticky at bottom of sheet
- ARIA labels: "Approve and copy to clipboard", "Request changes"
**Responsive - "Centered App" Pattern:**
- Desktop view should NOT stretch to full width
- Centered card container with max-width ~600px
- Generous whitespace background (Morning Mist #F8FAFC)
**Animation - Slide-Up Transition:**
- Duration: 300ms (ease-out)
- Should feel like "unveiling" the result
- No bounce or overshoot (feels professional, not playful)
### Previous Story Intelligence (from Story 2.1)
**Patterns Established (must follow):**
- **Logic Sandwich Pattern:** UI -> Zustand -> Service -> Database (strictly enforced)
- **Atomic Selectors:** All state access uses `useChatStore(s => s.field)`
- **Draft Storage:** Drafts already stored in IndexedDB via `drafts` table
- **Ghostwriter Integration:** `currentDraft` state already in ChatStore
- **DraftingIndicator:** Already shows during generation (Story 2.1)
**Key Files from Story 2.1 (Reference):**
- `src/lib/db/draft-service.ts` - Draft CRUD operations (add status update method)
- `src/lib/db/index.ts` - Drafts table with status field ('draft' | 'completed' | 'regenerated')
- `src/lib/store/chat-store.ts` - Has `currentDraft`, add draft view state
- `src/components/features/chat/DraftingIndicator.tsx` - Pattern for loading states
**Learnings to Apply:**
- Story 2.1 established the Draft data structure (id, title, content, tags, status)
- Draft is already persisted to IndexedDB when Ghostwriter completes
- Use the same DraftRecord interface from Story 2.1
- Follow the same testing pattern: unit tests for components, integration tests for flow
**Draft Data Structure (from Story 2.1):**
```typescript
interface DraftRecord {
id: string;
sessionId: string;
title: string;
content: string; // Markdown formatted
tags: string[];
createdAt: number;
status: 'draft' | 'completed' | 'regenerated';
}
```
**Integration with Ghostwriter:**
- When Ghostwriter completes, `currentDraft` is already set in ChatStore
- This story should auto-open DraftViewSheet when `currentDraft` changes
- Use `useEffect` to watch for `currentDraft` and set `showDraftView = true`
### Testing Requirements
**Unit Tests:**
- `DraftViewSheet`: Renders draft content correctly
- `DraftViewSheet`: Renders in closed state by default
- `DraftViewSheet`: Opens when showDraftView is true
- `DraftContent`: Renders Markdown with proper styling
- `DraftContent`: Handles code blocks, lists, links correctly
- `DraftActions`: Thumbs Up button calls approveDraft callback
- `DraftActions`: Thumbs Down button calls rejectDraft callback
- `ClipboardUtils`: copyToClipboard() writes to clipboard correctly
**Integration Tests:**
- Auto-open: Sheet opens when Ghostwriter completes (currentDraft populated)
- Thumbs Up: Copies to clipboard, marks draft as completed, closes sheet
- Thumbs Down: Closes sheet, adds feedback prompt to chat
- Close button: Closes sheet without changing draft status
- Escape key: Closes sheet on desktop
- Backdrop click: Closes sheet on desktop
**Edge Cases:**
- Very long draft (>2000 words): Scrolling works, actions remain accessible
- Draft with code blocks: Markdown renders correctly with syntax highlighting
- Draft with no tags: Tag section doesn't break
- Draft with special characters: Emojis, unicode render correctly
- Empty draft: Shows placeholder or error state
**Accessibility Tests:**
- Keyboard navigation: Tab through actions, Enter to activate
- Screen reader: ARIA labels on all buttons
- Focus trap: Focus stays in sheet when open
- Focus management: Focus returns to chat trigger after close
- Color contrast: All text passes WCAG AA (4.5:1)
### Component Implementation Details
**DraftViewSheet Component:**
```typescript
// src/components/features/draft/DraftViewSheet.tsx
import { Sheet, SheetContent, SheetHeader } from '@/components/ui/sheet';
import { DraftContent } from './DraftContent';
import { DraftActions } from './DraftActions';
interface DraftViewSheetProps {
draft: Draft | null;
open: boolean;
onClose: () => void;
onApprove: (draftId: string) => void;
onReject: (draftId: string) => void;
}
export function DraftViewSheet({
draft,
open,
onClose,
onApprove,
onReject
}: DraftViewSheetProps) {
if (!draft) return null;
return (
<Sheet open={open} onOpenChange={onClose}>
<SheetContent
side="bottom"
className="h-[85vh] sm:h-auto sm:max-h-[85vh] sm:max-w-[600px] sm:mx-auto"
>
<SheetHeader>
{/* Close button, Title */}
</SheetHeader>
<DraftContent draft={draft} />
<DraftActions
onApprove={() => onApprove(draft.id)}
onReject={() => onReject(draft.id)}
/>
</SheetContent>
</Sheet>
);
}
```
**DraftContent Component:**
```typescript
// src/components/features/draft/DraftContent.tsx
import ReactMarkdown from 'react-markdown';
interface DraftContentProps {
draft: Draft;
}
export function DraftContent({ draft }: DraftContentProps) {
return (
<div className="draft-content font-merriweather prose prose-slate max-w-none">
<h2 className="draft-title">{draft.title}</h2>
<ReactMarkdown className="draft-body">
{draft.content}
</ReactMarkdown>
{draft.tags && draft.tags.length > 0 && (
<div className="flex flex-wrap gap-2 mt-4">
{draft.tags.map(tag => (
<span key={tag} className="tag-chip">#{tag}</span>
))}
</div>
)}
</div>
);
}
```
**DraftActions Component:**
```typescript
// src/components/features/draft/DraftActions.tsx
import { Button } from '@/components/ui/button';
import { ThumbsUp, ThumbsDown } from 'lucide-react';
interface DraftActionsProps {
onApprove: () => void;
onReject: () => void;
}
export function DraftActions({ onApprove, onReject }: DraftActionsProps) {
return (
<div className="sticky bottom-0 flex gap-3 p-4 bg-white border-t">
<Button
onClick={onReject}
variant="outline"
className="flex-1 min-h-[44px]"
aria-label="Request changes to this draft"
>
<ThumbsDown className="mr-2 h-5 w-5" />
Not Quite
</Button>
<Button
onClick={onApprove}
className="flex-1 min-h-[44px] bg-slate-700 hover:bg-slate-800"
aria-label="Approve and copy to clipboard"
>
<ThumbsUp className="mr-2 h-5 w-5" />
Copy
</Button>
</div>
);
}
```
### Markdown Rendering Strategy
**Library Choice:**
- Use `react-markdown` for Markdown parsing and rendering
- Add `remark-gfm` for GitHub Flavored Markdown support (tables, strikethrough)
- Add `rehype-highlight` for syntax highlighting in code blocks
**Styling Approach:**
- Use Tailwind's `@tailwindcss/typography` plugin for prose styling
- Override prose styles to match "Morning Mist" theme
- Apply Merriweather font to prose elements only
- Keep UI elements (buttons, tags) in Inter font
**Configuration:**
```javascript
// tailwind.config.js
module.exports = {
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'sans-serif'],
serif: ['Merriweather', 'serif'],
},
},
},
plugins: [
require('@tailwindcss/typography'),
],
};
```
### Clipboard Copy Implementation
**Utility Function:**
```typescript
// src/lib/utils/clipboard.ts
export async function copyToClipboard(text: string): Promise<boolean> {
try {
await navigator.clipboard.writeText(text);
return true;
} catch (err) {
// Fallback for older browsers
const textArea = document.createElement('textarea');
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
document.body.removeChild(textArea);
return true;
} catch (fallbackErr) {
document.body.removeChild(textArea);
return false;
}
}
}
```
**Success Feedback:**
- Use ShadCN `useToast()` hook for success notification
- Toast message: "Copied to clipboard!"
- Optional: Add confetti animation using `canvas-confetti` library
### Service Layer Extensions
**DraftService Updates:**
```typescript
// src/services/draft-service.ts
export class DraftService {
// ... existing methods from Story 2.1
async updateDraftStatus(
draftId: string,
status: 'draft' | 'completed' | 'regenerated'
): Promise<void> {
await db.drafts.update(draftId, { status });
}
}
```
**ChatService Orchestration:**
```typescript
// src/services/chat-service.ts
export class ChatService {
async approveDraft(draftId: string): Promise<void> {
// Update draft status to completed
await DraftService.updateDraftStatus(draftId, 'completed');
// Copy to clipboard
const draft = await DraftService.getDraftById(draftId);
const markdown = this.formatDraftAsMarkdown(draft);
await copyToClipboard(markdown);
}
rejectDraft(draftId: string): void {
// Just close the view and return to chat
// Story 2.3 will handle the regeneration flow
}
private formatDraftAsMarkdown(draft: Draft): string {
return `# ${draft.title}\n\n${draft.content}\n\nTags: ${draft.tags.join(', ')}`;
}
}
```
### Project Structure Notes
**Following Feature-First Lite Pattern:**
- New feature folder: `src/components/features/draft/`
- Draft components co-located for easy discovery
- Shares `src/components/ui/` ShadCN primitives
**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
- Utilities in `src/lib/utils/`
**No Conflicts Detected:**
- DraftViewSheet is new UI layer, no conflicts with existing code
- Extends existing DraftService with status update method
- Adds to ChatStore state, following established patterns
### Performance Requirements
**NFR-02 Compliance (App Load Time):**
- DraftViewSheet should animate smoothly (< 300ms transition)
- Markdown rendering should not block the main thread
- Use React.memo for DraftContent to prevent unnecessary re-renders
**Rendering Performance:**
- Large drafts (>2000 words) should render without jank
- Code blocks with syntax highlighting should load quickly
- Lazy load syntax highlighting library only when needed
**Memory Management:**
- Clear draft from currentDraft state after approval/rejection
- Don't keep multiple drafts in memory simultaneously
- IndexedDB is the source of truth, not component state
### Accessibility Requirements
**WCAG AA Compliance:**
- Color contrast: All text meets 4.5:1 ratio (Morning Mist palette enforces this)
- Touch targets: Minimum 44px for all buttons
- Keyboard navigation: Full keyboard support (Tab, Enter, Escape)
- Screen reader: ARIA labels on all interactive elements
- Focus management: Focus trap in sheet, proper focus return on close
**Focus Management:**
- When sheet opens, focus moves to close button
- Focus is trapped inside sheet while open
- When sheet closes, focus returns to trigger element
- Thumbs Up/Down buttons are keyboard accessible
**Semantic HTML:**
- Use `<article>` for draft content
- Use `<nav>` for action bar
- Use proper heading hierarchy (h2 for draft title)
- Use `aria-label` for icon-only buttons
### Security & Privacy Requirements
**NFR-03 & NFR-04 Compliance:**
- Clipboard copy is local-only (client-side API)
- No draft content transmitted to server on approval
- Draft status update is a local IndexedDB operation
- Copy to clipboard does not expose content to external services
**Content Safety:**
- Draft content is user-generated, no sanitization needed for local display
- Markdown rendering should not execute arbitrary scripts
- Use `react-markdown` which sanitizes by default
### 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.2: Draft View UI (The Slide-Up)](file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/epics.md#story-22-draft-view-ui-the-slide-up)
**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)
- [Architecture: Service Boundaries](file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/architecture.md#architectural-boundaries)
**UX Design Specifications:**
- [UX: The "Magic Moment" Visualization](file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/ux-design-specification.md#key-design-challenges)
- [UX: Typography System](file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/ux-design-specification.md#typography-system)
- [UX: Component Strategy - DraftCard](file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/ux-design-specification.md#component-strategy)
- [UX: Experience Mechanics - The Magic](file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/ux-design-specification.md#2-experience-mechanics)
- [UX: Responsive Strategy](file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/ux-design-specification.md#responsive-strategy)
**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) - Ghostwriter generates drafts stored in IndexedDB
## 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/80b59076-c368-433c-92e4-1937285218ee/scratchpad`
### Completion Notes List
**Story Analysis Completed:**
- Extracted story requirements from Epic 2, Story 2.2
- Analyzed previous Story 2.1 for established patterns (Ghostwriter, DraftRecord, DraftService)
- Reviewed architecture for compliance requirements (Logic Sandwich, State Management, Local-First)
- Reviewed UX specification for "Magic Moment" visualization, typography, responsive design
- Identified all files to create and modify
**Implementation Context Summary:**
**Story Purpose:**
This story implements the "Magic Moment" UI - the slide-up sheet that displays the Ghostwriter's draft in a polished, reading-focused interface. This is the emotional climax of the user journey where raw chat is transformed into a tangible artifact.
**Key Technical Decisions:**
1. **Component Structure:** DraftViewSheet (Sheet wrapper) -> DraftContent (Markdown) + DraftActions (Footer)
2. **Typography Split:** Merriweather (serif) for draft content, Inter (sans-serif) for UI - reinforces transformation value
3. **Responsive Design:** Full-screen on mobile, centered 600px card on desktop ("Centered App" pattern)
4. **Markdown Rendering:** react-markdown + @tailwindcss/typography for prose styling
5. **State Management:** Add draft view state to ChatStore (showDraftView, closeDraftView, approveDraft, rejectDraft)
6. **Service Extensions:** DraftService.updateDraftStatus() for marking drafts as completed
**Dependencies:**
- **New dependencies to add:**
- `react-markdown` - Markdown parsing and rendering
- `remark-gfm` - GitHub Flavored Markdown support
- `rehype-highlight` - Syntax highlighting for code blocks
- `@tailwindcss/typography` - Tailwind prose styling plugin
- `canvas-confetti` (optional) - Success animation on copy
- **Reuses existing:** Zustand, Dexie, ShadCN Sheet component
**Integration Points:**
- Auto-opens when Ghostwriter completes (watch currentDraft state in ChatStore)
- Thumbs Up: Copies to clipboard, marks draft as 'completed' in IndexedDB
- Thumbs Down: Closes sheet, returns to chat (Story 2.3 handles regeneration flow)
- Draft content already stored in IndexedDB from Story 2.1
**Files to Create:**
- `src/components/features/draft/DraftViewSheet.tsx` - Main sheet component with ShadCN Sheet wrapper
- `src/components/features/draft/DraftContent.tsx` - Markdown renderer with Merriweather font
- `src/components/features/draft/DraftActions.tsx` - Thumbs Up/Down sticky footer
- `src/lib/utils/clipboard.ts` - Copy to clipboard utility function
**Files to Modify:**
- `src/lib/store/chat-store.ts` - Add draft view state and actions
- `src/services/draft-service.ts` - Add updateDraftStatus() method
- `src/services/chat-service.ts` - Add approveDraft() and rejectDraft() orchestration
- `tailwind.config.js` - Add Merriweather font and typography plugin
**Testing Strategy:**
- Unit tests for each component (DraftViewSheet, DraftContent, DraftActions)
- Integration tests for full flow (Ghostwriter -> DraftView -> Copy/Reject)
- Edge case tests (long drafts, code blocks, special characters)
- Accessibility tests (keyboard nav, screen reader, focus management)
### File List
**New Files to Create:**
- `src/components/features/draft/DraftViewSheet.tsx` - Main sheet component
- `src/components/features/draft/DraftContent.tsx` - Markdown renderer with Merriweather
- `src/components/features/draft/DraftActions.tsx` - Thumbs Up/Down footer
- `src/lib/utils/clipboard.ts` - Copy to clipboard utility
- `src/components/features/draft/draft-view-sheet.test.tsx` - Component tests
- `src/components/features/draft/draft-content.test.tsx` - Markdown rendering tests
- `src/components/features/draft/draft-actions.test.tsx` - Action button tests
- `src/integration/draft-view-flow.test.ts` - End-to-end flow tests