Ignore and untrack BMad directories
This commit is contained in:
@@ -1,665 +0,0 @@
|
||||
# Story 2.3: Refinement Loop (Regeneration)
|
||||
|
||||
Status: done
|
||||
|
||||
<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
|
||||
|
||||
## Story
|
||||
|
||||
As a user,
|
||||
I want to provide feedback if the draft isn't right,
|
||||
So that I can get a better version.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
1. **Thumbs Down Triggers Refinement**
|
||||
- Given the user is viewing a draft
|
||||
- When they click "Thumbs Down"
|
||||
- Then the draft sheet closes and returns to the Chat UI
|
||||
- And the AI proactively asks "What should we change?"
|
||||
|
||||
2. **Feedback-Based Regeneration**
|
||||
- Given the user provides specific critique (e.g., "Make it shorter")
|
||||
- When they send the feedback
|
||||
- Then the "Ghostwriter" regenerates the draft respecting the new constraint
|
||||
- And the new draft replaces the old one in the Draft View
|
||||
|
||||
## Tasks / Subtasks
|
||||
|
||||
- [x] Implement Refinement Flow State Management
|
||||
- [x] Add refinement state to ChatStore: `isRefining`, `refinementDraftId`, `originalDraft`
|
||||
- [x] Add `startRefinement(draftId)` action to enter refinement mode
|
||||
- [x] Add `submitRefinementFeedback(feedback: string)` action
|
||||
- [x] Add `cancelRefinement()` action to exit refinement mode
|
||||
- [x] Use atomic selectors for all state access
|
||||
|
||||
- [x] Implement Refinement Prompt Engineering
|
||||
- [x] Create `generateRefinementPrompt()` in `src/lib/llm/prompt-engine.ts`
|
||||
- [x] Include original draft content in prompt context
|
||||
- [x] Include user's feedback/critique as constraint
|
||||
- [x] Include original chat history for context preservation
|
||||
- [x] Add prompt instructions: "respect user's specific critique while maintaining their voice"
|
||||
|
||||
- [x] Extend Ghostwriter Service for Regeneration
|
||||
- [x] Add `regenerateDraft()` method to `src/services/llm-service.ts`
|
||||
- [x] Support streaming response for regenerated draft
|
||||
- [x] Pass original draft, feedback, and chat history to LLM
|
||||
- [x] Return new Draft object with same sessionId but updated content
|
||||
|
||||
- [x] Implement Refinement Mode in ChatService
|
||||
- [x] Add `handleRefinementFeedback()` orchestration method
|
||||
- [x] Load original draft from IndexedDB for context
|
||||
- [x] Call Ghostwriter with refinement prompt
|
||||
- [x] Store regenerated draft in IndexedDB with status 'regenerated'
|
||||
- [x] Update ChatStore with new draft
|
||||
|
||||
- [x] Create Refinement UI Indicators
|
||||
- [x] Create `RefinementModeBadge.tsx` component
|
||||
- [x] Show "Refining your draft..." indicator during regeneration
|
||||
- [x] Add visual cue that chat is in refinement mode (different color/border)
|
||||
- [x] Add system message: "What should we change?" when entering refinement
|
||||
|
||||
- [x] Implement Draft Replacement Logic
|
||||
- [x] Update `currentDraft` in ChatStore with regenerated content
|
||||
- [x] Keep original draft in IndexedDB (create new record with status 'regenerated')
|
||||
- [x] Auto-open DraftViewSheet with new draft after regeneration
|
||||
- [x] Allow unlimited refinement iterations
|
||||
|
||||
- [x] Handle Refinement Cancellation
|
||||
- [x] Allow user to exit refinement mode without regenerating
|
||||
- [x] Add "Cancel Refinement" button or gesture
|
||||
- [x] Restore normal chat mode on cancellation
|
||||
- [x] Keep original draft accessible from history
|
||||
|
||||
- [x] Test Refinement Loop End-to-End
|
||||
- [x] Unit test: Refinement prompt generation with various feedback types
|
||||
- [x] Unit test: Ghostwriter regeneration with original draft context
|
||||
- [x] Integration test: Thumbs Down -> Feedback -> Regeneration flow
|
||||
- [x] Integration test: Multiple refinement iterations
|
||||
- [x] Edge case: Vague feedback ("make it better")
|
||||
- [x] Edge case: Contradictory feedback
|
||||
- [x] Edge case: Very long original draft
|
||||
|
||||
## Dev Notes
|
||||
|
||||
### Architecture Compliance (CRITICAL)
|
||||
|
||||
**Logic Sandwich Pattern - DO NOT VIOLATE:**
|
||||
- **UI Components** MUST NOT import `src/lib/llm` or `src/services/llm-service.ts` directly
|
||||
- All refinement logic MUST go through `ChatService` layer
|
||||
- ChatService then calls `LLMService` as needed
|
||||
- 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 { isRefining, refinementDraftId } = useChatStore();
|
||||
|
||||
// GOOD - Atomic selectors
|
||||
const isRefining = useChatStore(s => s.isRefining);
|
||||
const refinementDraftId = useChatStore(s => s.refinementDraftId);
|
||||
const startRefinement = useChatStore(s => s.startRefinement);
|
||||
const submitRefinementFeedback = useChatStore(s => s.submitRefinementFeedback);
|
||||
```
|
||||
|
||||
**Local-First Data Boundary:**
|
||||
- Regenerated drafts MUST be stored in IndexedDB
|
||||
- Each regeneration creates a new DraftRecord with status 'regenerated'
|
||||
- Original draft is preserved (not overwritten)
|
||||
- Draft history is queryable from history view (Epic 3)
|
||||
|
||||
**Edge Runtime Constraint:**
|
||||
- Regeneration LLM call goes through `/api/llm` edge function (same as original Ghostwriter)
|
||||
- `export const runtime = 'edge';`
|
||||
|
||||
### Architecture Implementation Details
|
||||
|
||||
**Story Purpose:**
|
||||
This story implements the **"Conversational Refinement Loop"** - the ability to iteratively improve drafts through natural language feedback. This is a key differentiator from standard AI writing tools, as it preserves the user's voice while allowing precise control over the output.
|
||||
|
||||
**State Management Extensions:**
|
||||
```typescript
|
||||
// Add to ChatStore (src/lib/store/chat-store.ts)
|
||||
interface ChatStore {
|
||||
// Refinement state
|
||||
isRefining: boolean;
|
||||
refinementDraftId: string | null;
|
||||
originalDraft: Draft | null;
|
||||
startRefinement: (draftId: string) => Promise<void>;
|
||||
submitRefinementFeedback: (feedback: string) => Promise<void>;
|
||||
cancelRefinement: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
**Refinement Logic Flow:**
|
||||
1. User views draft in DraftViewSheet (Story 2.2)
|
||||
2. User taps Thumbs Down
|
||||
3. DraftViewSheet closes
|
||||
4. ChatService.startRefinement(draftId) called
|
||||
5. System message added: "What should we change?"
|
||||
6. User enters feedback: "Make it shorter"
|
||||
7. ChatService.submitRefinementFeedback(feedback) called
|
||||
8. Ghostwriter regenerates with refinement prompt
|
||||
9. New draft stored with status 'regenerated'
|
||||
10. DraftViewSheet re-opens with new draft
|
||||
11. Loop can repeat indefinitely
|
||||
|
||||
**Draft Versioning Strategy:**
|
||||
- Each regeneration creates a NEW DraftRecord (not an update)
|
||||
- Link drafts via `sessionId` and `createdAt` timestamp
|
||||
- Latest draft for a session is shown in current view
|
||||
- History view (Epic 3) can show all versions
|
||||
|
||||
**Files to Create:**
|
||||
- `src/components/features/chat/RefinementModeBadge.tsx` - Visual indicator for refinement mode
|
||||
- `src/components/features/chat/RefinementIndicator.tsx` - Loading state during regeneration
|
||||
|
||||
**Files to Modify:**
|
||||
- `src/lib/store/chat-store.ts` - Add refinement state and actions
|
||||
- `src/lib/llm/prompt-engine.ts` - Add `generateRefinementPrompt()` function
|
||||
- `src/services/llm-service.ts` - Add `regenerateDraft()` method
|
||||
- `src/services/chat-service.ts` - Add refinement orchestration
|
||||
- `src/components/features/draft/DraftActions.tsx` - Thumbs Down triggers refinement
|
||||
|
||||
### UX Design Specifications
|
||||
|
||||
**From UX Design Document:**
|
||||
|
||||
**The "Refinement Loop":**
|
||||
- Correction is done by *talking* to the agent, not editing text
|
||||
- "What didn't you like?" prompt appears after Thumbs Down
|
||||
- New draft replaces old one seamlessly
|
||||
- Fast loop is critical (< 10 seconds total)
|
||||
|
||||
**Visual Feedback - Refinement Mode:**
|
||||
- Chat interface should show visual cue that we're in refinement mode
|
||||
- Different border color or subtle background change
|
||||
- System message clearly prompts: "What should we change?"
|
||||
|
||||
**Tone and Persona:**
|
||||
- AI should accept feedback gracefully
|
||||
- No defensive responses
|
||||
- Clear acknowledgment of the constraint
|
||||
|
||||
**Interaction Pattern:**
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Draft View Open] --> B[User Taps Thumbs Down]
|
||||
B --> C[Sheet Closes]
|
||||
C --> D[System Message: What should we change?]
|
||||
D --> E[User Types Feedback]
|
||||
E --> F[Ghostwriter Regenerates]
|
||||
F --> G[New Draft Appears]
|
||||
G --> H{User Happy?}
|
||||
H -->|No| B
|
||||
H -->|Yes| I[Thumbs Up to Complete]
|
||||
```
|
||||
|
||||
**Micro-interactions:**
|
||||
- Thumbs Down should feel like "let's fix this" not "you failed"
|
||||
- System message should appear as if from a supportive editor
|
||||
- Regeneration should use same shimmer animation as initial draft
|
||||
|
||||
### Previous Story Intelligence (from Stories 2.1 and 2.2)
|
||||
|
||||
**Patterns Established (must follow):**
|
||||
- **Logic Sandwich Pattern:** UI -> Zustand -> Service -> LLM (strictly enforced)
|
||||
- **Atomic Selectors:** All state access uses `useChatStore(s => s.field)`
|
||||
- **Draft Storage:** Drafts stored in IndexedDB via `drafts` table
|
||||
- **Ghostwriter Integration:** `getGhostwriterResponseStream()` pattern for streaming
|
||||
- **DraftViewSheet:** Auto-opens when `currentDraft` is set
|
||||
|
||||
**Key Files from Story 2.1:**
|
||||
- `src/lib/llm/prompt-engine.ts` - Has `generateGhostwriterPrompt()`, add refinement version
|
||||
- `src/services/llm-service.ts` - Has `getGhostwriterResponseStream()`, add regenerate version
|
||||
- `src/lib/db/draft-service.ts` - Draft CRUD operations
|
||||
|
||||
**Key Files from Story 2.2:**
|
||||
- `src/components/features/draft/DraftActions.tsx` - Thumbs Down button, wire to refinement
|
||||
- `src/components/features/draft/DraftViewSheet.tsx` - Sheet component
|
||||
- `src/lib/store/chat-store.ts` - Has `currentDraft`, add refinement state
|
||||
|
||||
**Learnings to Apply:**
|
||||
- Story 2.1 established streaming pattern - reuse for regeneration
|
||||
- Story 2.2 established DraftViewSheet auto-open - reuse for new draft
|
||||
- Use same `DraftingIndicator` animation during regeneration
|
||||
- Follow same error handling pattern (retry on failure)
|
||||
|
||||
**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';
|
||||
}
|
||||
```
|
||||
|
||||
### Refinement Prompt Specifications
|
||||
|
||||
**Prompt Structure:**
|
||||
```typescript
|
||||
function generateRefinementPrompt(
|
||||
originalDraft: Draft,
|
||||
userFeedback: string,
|
||||
chatHistory: ChatMessage[],
|
||||
intent?: 'venting' | 'insight'
|
||||
): string {
|
||||
return `
|
||||
You are the Ghostwriter Agent in REFINEMENT MODE. The user has provided feedback on a draft you wrote.
|
||||
|
||||
ORIGINAL DRAFT:
|
||||
Title: ${originalDraft.title}
|
||||
Content: ${originalDraft.content}
|
||||
|
||||
USER FEEDBACK:
|
||||
"${userFeedback}"
|
||||
|
||||
CONTEXT:
|
||||
- User Intent: ${intent || 'unknown'}
|
||||
- Original Chat History: ${formatChatHistory(chatHistory)}
|
||||
|
||||
REQUIREMENTS:
|
||||
1. Address the user's specific feedback while maintaining their authentic voice
|
||||
2. Do NOT introduce new ideas or hallucinate facts
|
||||
3. Keep what worked in the original, only change what the user criticized
|
||||
4. If feedback is vague, make a reasonable best guess
|
||||
5. Maintain the same professional LinkedIn tone
|
||||
6. Return a NEW draft in the same Markdown format
|
||||
|
||||
OUTPUT FORMAT:
|
||||
\`\`\`markdown
|
||||
# [Refined Title - adjust if needed based on feedback]
|
||||
|
||||
[Revised content that addresses the feedback]
|
||||
|
||||
**Tags:** [3-5 relevant tags - adjust if needed]
|
||||
\`\`\`
|
||||
`;
|
||||
}
|
||||
```
|
||||
|
||||
**Prompt Engineering Notes:**
|
||||
- Emphasize "maintain what worked" to avoid over-correction
|
||||
- Include original draft as context so AI doesn't lose good parts
|
||||
- User feedback can be vague ("make it punchier") - AI must interpret
|
||||
- Title and tags can change if feedback warrants it
|
||||
- Chat history provides context that might be missing from draft
|
||||
|
||||
**Common Feedback Patterns to Handle:**
|
||||
- "Make it shorter" -> Condense while keeping key points
|
||||
- "More casual" -> Reduce corporate language
|
||||
- "More professional" -> Increase formality
|
||||
- "Focus on X" -> Emphasize specific aspect
|
||||
- "Less technical" -> Simplify jargon
|
||||
- "Add more about Y" -> Expand on specific point (careful not to hallucinate)
|
||||
|
||||
### Testing Requirements
|
||||
|
||||
**Unit Tests:**
|
||||
- `PromptEngine`: `generateRefinementPrompt()` includes original draft
|
||||
- `PromptEngine`: `generateRefinementPrompt()` includes user feedback
|
||||
- `PromptEngine`: Handles vague feedback gracefully
|
||||
- `PromptEngine`: Maintains chat history context
|
||||
- `LLMService`: `regenerateDraft()` calls Edge API correctly
|
||||
- `LLMService`: Handles streaming response for regeneration
|
||||
- `ChatStore`: `startRefinement()` sets state correctly
|
||||
- `ChatStore`: `submitRefinementFeedback()` triggers regeneration
|
||||
- `ChatStore`: `cancelRefinement()` clears state
|
||||
|
||||
**Integration Tests:**
|
||||
- Full refinement flow: Thumbs Down -> Feedback -> Regeneration -> New draft
|
||||
- Multiple iterations: Refine -> Refine again -> Final draft
|
||||
- Draft replacement: New draft appears in DraftViewSheet
|
||||
- Original draft preservation: Original still in IndexedDB
|
||||
- Refinement cancellation: Exit without regenerating
|
||||
- Refinement mode indicator: Visual cue appears
|
||||
|
||||
**Edge Cases:**
|
||||
- Vague feedback: "make it better" - should make reasonable guess
|
||||
- Contradictory feedback: "make it shorter but add more detail" - should prioritize or ask
|
||||
- Very long original draft: Should handle within token limits
|
||||
- Empty feedback: Should handle gracefully
|
||||
- Malformed LLM response: Should handle gracefully
|
||||
- LLM API failure during regeneration: Should show retry option
|
||||
|
||||
**Performance Tests:**
|
||||
- Regeneration time: < 5 seconds (same as initial generation)
|
||||
- Refinement indicator appears within 1 second
|
||||
- Large draft with feedback: Should handle efficiently
|
||||
|
||||
### Component Implementation Details
|
||||
|
||||
**RefinementModeBadge Component:**
|
||||
```typescript
|
||||
// src/components/features/chat/RefinementModeBadge.tsx
|
||||
interface RefinementModeBadgeProps {
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export function RefinementModeBadge({ onCancel }: RefinementModeBadgeProps) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 px-3 py-2 bg-amber-50 border border-amber-200 rounded-full text-sm text-amber-800">
|
||||
<span className="flex-1">Refining your draft...</span>
|
||||
<button
|
||||
onClick={onCancel}
|
||||
className="text-amber-600 hover:text-amber-800 underline"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**ChatService Extensions:**
|
||||
```typescript
|
||||
// src/services/chat-service.ts
|
||||
export class ChatService {
|
||||
async startRefinement(draftId: string): Promise<void> {
|
||||
// Load original draft
|
||||
const draft = await DraftService.getDraftById(draftId);
|
||||
|
||||
// Add system message to chat
|
||||
await this.addSystemMessage("What should we change?");
|
||||
|
||||
// Update store
|
||||
ChatStore.getState().startRefinement(draftId, draft);
|
||||
}
|
||||
|
||||
async submitRefinementFeedback(feedback: string): Promise<void> {
|
||||
const { refinementDraftId, originalDraft } = ChatStore.getState();
|
||||
|
||||
// Get chat history for context
|
||||
const session = await SessionService.getCurrentSession();
|
||||
const chatHistory = await ChatLogService.getBySessionId(session.id);
|
||||
|
||||
// Regenerate draft
|
||||
const newDraft = await LLMService.regenerateDraft(
|
||||
originalDraft!,
|
||||
feedback,
|
||||
chatHistory
|
||||
);
|
||||
|
||||
// Save new draft
|
||||
await DraftService.createDraft({
|
||||
...newDraft,
|
||||
sessionId: session.id,
|
||||
status: 'regenerated'
|
||||
});
|
||||
|
||||
// Update store with new draft (triggers DraftViewSheet open)
|
||||
ChatStore.getState().setCurrentDraft(newDraft);
|
||||
ChatStore.getState().exitRefinementMode();
|
||||
}
|
||||
|
||||
cancelRefinement(): void {
|
||||
ChatStore.getState().cancelRefinement();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**DraftActions Integration (from Story 2.2):**
|
||||
```typescript
|
||||
// Update Thumbs Down handler in DraftActions
|
||||
const handleReject = () => {
|
||||
onReject(draftId);
|
||||
// Trigger refinement flow
|
||||
ChatService.startRefinement(draftId);
|
||||
};
|
||||
```
|
||||
|
||||
### Draft Versioning Strategy
|
||||
|
||||
**Storage Pattern:**
|
||||
```typescript
|
||||
// Each regeneration creates a new record
|
||||
const originalDraft = {
|
||||
id: 'draft-1',
|
||||
sessionId: 'session-1',
|
||||
content: 'Original content...',
|
||||
status: 'completed', // User approved after refinement
|
||||
createdAt: 1000
|
||||
};
|
||||
|
||||
const regeneratedDraft = {
|
||||
id: 'draft-2',
|
||||
sessionId: 'session-1', // Same session
|
||||
content: 'Refined content...',
|
||||
status: 'regenerated', // Marked as regenerated version
|
||||
createdAt: 2000 // Later timestamp
|
||||
};
|
||||
|
||||
// Query for latest draft
|
||||
const latestDraft = await db.drafts
|
||||
.where('sessionId')
|
||||
.equals('session-1')
|
||||
.reverse() // Newest first
|
||||
.first();
|
||||
```
|
||||
|
||||
**Version Querying (for Epic 3 History):**
|
||||
```typescript
|
||||
// Get all versions of a draft
|
||||
async getAllDraftVersions(sessionId: string): Promise<Draft[]> {
|
||||
return await db.drafts
|
||||
.where('sessionId')
|
||||
.equals(sessionId)
|
||||
.sortBy('createdAt');
|
||||
}
|
||||
```
|
||||
|
||||
### Service Layer Extensions
|
||||
|
||||
**LLMService Regeneration:**
|
||||
```typescript
|
||||
// src/services/llm-service.ts
|
||||
export class LLMService {
|
||||
async regenerateDraft(
|
||||
originalDraft: Draft,
|
||||
feedback: string,
|
||||
chatHistory: ChatMessage[]
|
||||
): Promise<Draft> {
|
||||
const prompt = PromptEngine.generateRefinementPrompt(
|
||||
originalDraft,
|
||||
feedback,
|
||||
chatHistory
|
||||
);
|
||||
|
||||
const response = await this.callEdgeAPI(prompt);
|
||||
|
||||
// Parse response (same format as initial generation)
|
||||
const { title, content, tags } = this.parseMarkdownResponse(response);
|
||||
|
||||
return {
|
||||
id: generateId(),
|
||||
sessionId: originalDraft.sessionId,
|
||||
title,
|
||||
content,
|
||||
tags,
|
||||
createdAt: Date.now(),
|
||||
status: 'regenerated'
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**PromptEngine Extensions:**
|
||||
```typescript
|
||||
// src/lib/llm/prompt-engine.ts
|
||||
export class PromptEngine {
|
||||
static generateRefinementPrompt(
|
||||
originalDraft: Draft,
|
||||
userFeedback: string,
|
||||
chatHistory: ChatMessage[],
|
||||
intent?: 'venting' | 'insight'
|
||||
): string {
|
||||
// Prompt implementation from specifications above
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Project Structure Notes
|
||||
|
||||
**Following Feature-First Lite Pattern:**
|
||||
- New components in `src/components/features/chat/` (refinement is chat-mode feature)
|
||||
- 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
|
||||
|
||||
**No Conflicts Detected:**
|
||||
- Refinement mode is new state, no conflicts
|
||||
- Extends existing Ghostwriter pattern
|
||||
- Creates new draft records (no migration conflicts)
|
||||
|
||||
### Performance Requirements
|
||||
|
||||
**NFR-01 Compliance (Generation Latency):**
|
||||
- Regeneration should complete in < 5 seconds total
|
||||
- First token should appear within 3 seconds
|
||||
- Same performance as initial Ghostwriter generation
|
||||
|
||||
**State Updates:**
|
||||
- `isRefining` state should update immediately on Thumbs Down
|
||||
- Regeneration indicator should appear instantly
|
||||
- New draft should auto-open DraftViewSheet on completion
|
||||
|
||||
### Accessibility Requirements
|
||||
|
||||
**WCAG AA Compliance:**
|
||||
- Refinement mode indicator must be visible to screen readers
|
||||
- "Cancel Refinement" button keyboard accessible
|
||||
- System message "What should we change?" announced
|
||||
- Focus management: Return focus to chat input after Thumbs Down
|
||||
|
||||
**Visual Accessibility:**
|
||||
- Refinement mode must have clear visual cue (not just color)
|
||||
- Status indicator must be high contrast
|
||||
- Avoid color-only indicators (use icons + text)
|
||||
|
||||
### Security & Privacy Requirements
|
||||
|
||||
**NFR-03 & NFR-04 Compliance:**
|
||||
- Regeneration uses same Edge API proxy as initial generation
|
||||
- No draft content sent to server for storage
|
||||
- Original draft kept client-side only
|
||||
- User feedback processed through same privacy pipeline
|
||||
|
||||
### 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.3: Refinement Loop (Regeneration)](file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/epics.md#story-23-refinement-loop-regeneration)
|
||||
- FR-04: "Users can 'Regenerate' the outcome with specific critique"
|
||||
|
||||
**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: The "Refinement Loop"](file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/ux-design-specification.md#2-experience-mechanics)
|
||||
- [UX: Journey Patterns](file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/ux-design-specification.md#journey-patterns)
|
||||
|
||||
**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
|
||||
- [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 Thumbs Down trigger
|
||||
|
||||
## 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.3
|
||||
- Analyzed previous Stories 2.1 and 2.2 for established patterns
|
||||
- Reviewed architecture for compliance requirements (Logic Sandwich, State Management, Local-First)
|
||||
- Reviewed UX specification for refinement loop interaction pattern
|
||||
- Identified all files to create and modify
|
||||
|
||||
**Implementation Context Summary:**
|
||||
|
||||
**Story Purpose:**
|
||||
This story implements the **"Conversational Refinement Loop"** - the ability to iteratively improve drafts through natural language feedback. Users tap Thumbs Down, provide feedback ("Make it shorter"), and the Ghostwriter regenerates a new version addressing their critique while maintaining their authentic voice.
|
||||
|
||||
**Key Technical Decisions:**
|
||||
1. **State Management:** Add refinement mode state to ChatStore (isRefining, refinementDraftId, originalDraft)
|
||||
2. **Draft Versioning:** Each regeneration creates a new DraftRecord (not an update), linked by sessionId
|
||||
3. **Prompt Engineering:** Include original draft + user feedback + chat history in refinement prompt
|
||||
4. **Visual Feedback:** RefinementModeBadge shows we're in refinement mode
|
||||
5. **Flow Integration:** Thumbs Down from Story 2.2 triggers refinement start
|
||||
6. **Auto-Open:** Regenerated draft auto-opens DraftViewSheet (same as initial draft)
|
||||
|
||||
**Dependencies:**
|
||||
- No new dependencies required
|
||||
- Reuses existing Zustand, Dexie, LLM service infrastructure
|
||||
- Extends existing prompt engine and Ghostwriter service
|
||||
|
||||
**Integration Points:**
|
||||
- Thumbs Down in DraftActions (Story 2.2) triggers ChatService.startRefinement()
|
||||
- Regeneration uses same Edge API proxy as initial Ghostwriter
|
||||
- New draft stored in IndexedDB with status 'regenerated'
|
||||
- Auto-opens DraftViewSheet when regeneration completes
|
||||
|
||||
**Files to Create:**
|
||||
- `src/components/features/chat/RefinementModeBadge.tsx` - Visual indicator for refinement mode
|
||||
- `src/components/features/chat/RefinementIndicator.tsx` - Loading state during regeneration
|
||||
|
||||
**Files to Modify:**
|
||||
- `src/lib/store/chat-store.ts` - Add refinement state and actions
|
||||
- `src/lib/llm/prompt-engine.ts` - Add generateRefinementPrompt() function
|
||||
- `src/services/llm-service.ts` - Add regenerateDraft() method
|
||||
- `src/services/chat-service.ts` - Add refinement orchestration methods
|
||||
- `src/components/features/draft/DraftActions.tsx` - Wire Thumbs Down to refinement start
|
||||
|
||||
**Testing Strategy:**
|
||||
- Unit tests for refinement prompt generation
|
||||
- Integration tests for full refinement loop
|
||||
- Edge case tests (vague feedback, contradictory feedback)
|
||||
- Multiple iteration tests (refine, refine again)
|
||||
|
||||
**Draft Versioning Pattern:**
|
||||
- Each regeneration creates NEW DraftRecord
|
||||
- Original draft preserved (not overwritten)
|
||||
- Latest draft shown in current view
|
||||
- History view (Epic 3) can show all versions
|
||||
|
||||
### File List
|
||||
|
||||
**New Files Created:**
|
||||
- `src/components/features/chat/RefinementModeBadge.tsx` - Visual indicator component
|
||||
- `src/components/features/chat/RefinementIndicator.tsx` - Regeneration loading state
|
||||
- `src/lib/store/refinement-store.test.ts` - Refinement state tests
|
||||
- `src/lib/llm/refinement-prompt.test.ts` - Prompt generation tests
|
||||
|
||||
**Files Modified:**
|
||||
- `src/lib/store/chat-store.ts` - Added refinement state (isRefining, refinementDraftId, originalDraft) and actions (startRefinement, submitRefinementFeedback, cancelRefinement)
|
||||
- `src/lib/llm/prompt-engine.ts` - Added generateRefinementPrompt() function
|
||||
- `src/services/llm-service.ts` - Added regenerateDraft() method with extractOriginalDraft() helper
|
||||
- `src/services/chat-service.ts` - Added startRefinement(), submitRefinementFeedback() methods
|
||||
- `src/components/features/draft/DraftActions.tsx` - Wired Thumbs Down to refinement flow
|
||||
- `src/components/features/chat/ChatWindow.tsx` - Added RefinementModeBadge display
|
||||
- `src/components/features/chat/index.ts` - Exported new components
|
||||
- `src/components/features/draft/DraftViewSheet.test.tsx` - Updated test to expect startRefinement call
|
||||
|
||||
### Code Review Fixes
|
||||
- **Critical Fix**: Updated `ChatStore.addMessage` to correctly intercept user input during refinement mode and route it to `submitRefinementFeedback`, fixing the broken refinement loop.
|
||||
- **High Fix**: Updated `ChatService.submitRefinementFeedback` and `ChatStore` to ensure `currentDraft` is immediately updated with the regenerated content, preventing UI stale state.
|
||||
- **High Fix**: Updated `LLMService.regenerateDraft` and call sites to explicitly accept `originalDraftContent`, preventing potential data loss from unreliable chat history parsing.
|
||||
- **Fix**: Resolved `sessionId` type error in `ChatService` by deriving it from `originalDraft`.
|
||||
- **Verification**: All refinement store and prompt tests passed.
|
||||
Reference in New Issue
Block a user