- Next.js 14+ with App Router and TypeScript - Tailwind CSS and ShadCN UI styling - Zustand state management - Dexie.js for IndexedDB (local-first data) - Auth.js v5 for authentication - BMAD framework integration Co-Authored-By: Claude <noreply@anthropic.com>
666 lines
25 KiB
Markdown
666 lines
25 KiB
Markdown
# 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.
|