-
Notifications
You must be signed in to change notification settings - Fork 633
Open
Description
Task: Guidance Display UI
Description
Build React components to display the 3 types of AI guidance each partner receives: Individual-Only Guidance (Phase 1), Joint-Context Personal Guidance (Phase 2), and Relationship Guidance (Phase 3). Support both Structured mode (formatted JSON sections) and Conversational mode (plain text paragraphs). Implement Partner B's "reveal" flow where Partner A's description is shown after Partner B submits.
Acceptance Criteria
- Three guidance card components:
IndividualGuidanceCard(Phase 1: individual-only analysis)JointContextGuidanceCard(Phase 2: joint-context personal guidance)RelationshipGuidanceCard(Phase 3: shared relationship guidance)
- Mode-aware rendering:
- Structured mode: Format JSON into readable sections (emotions, needs, conversation starters, etc.)
- Conversational mode: Display plain text paragraphs
- Partner B "reveal" flow:
- Before submission: Partner B sees only conflict title
- After submission: Partner A's description revealed (if visibility = 'shared')
- Visual indicator: "Your partner's perspective" section appears
- Pattern insights display (if detected)
- Mobile-responsive design
- Loading states while AI processes (spinner + "Generating guidance..." message)
Technical Details
Component Structure:
src/components/guidance/
├── IndividualGuidanceCard.tsx
├── JointContextGuidanceCard.tsx
├── RelationshipGuidanceCard.tsx
├── StructuredGuidanceRenderer.tsx # Formats structured JSON
├── ConversationalGuidanceRenderer.tsx # Formats plain text
└── PatternInsights.tsx # Displays pattern recognition alerts
IndividualGuidanceCard.tsx (example):
interface IndividualGuidanceCardProps {
guidance: AIGuidance;
mode: 'structured' | 'conversational';
}
export function IndividualGuidanceCard({ guidance, mode }: IndividualGuidanceCardProps) {
if (mode === 'structured') {
return (
<Card>
<CardHeader>
<h3>Your Individual Guidance</h3>
<p>Understanding your emotions and needs</p>
</CardHeader>
<CardContent>
<StructuredGuidanceRenderer content={guidance.structured_content} />
</CardContent>
</Card>
);
}
return (
<Card>
<CardHeader>
<h3>Your Individual Guidance</h3>
</CardHeader>
<CardContent>
<ConversationalGuidanceRenderer content={guidance.conversational_content} />
</CardContent>
</Card>
);
}StructuredGuidanceRenderer.tsx:
interface StructuredContent {
summary: string;
emotions_identified?: {
primary: string[];
secondary: string[];
explanation: string;
};
needs_identified?: string[];
needs_explanation?: string;
four_horsemen_detected?: string[];
four_horsemen_explanation?: string;
conversation_starters?: Array<{
observation: string;
feeling: string;
need: string;
request: string;
}>;
techniques?: Array<{
name: string;
description: string;
example: string;
}>;
pattern_insights?: string[];
}
export function StructuredGuidanceRenderer({ content }: { content: StructuredContent }) {
return (
<div className="space-y-6">
{/* Summary */}
<section>
<h4 className="font-semibold mb-2">Summary</h4>
<p>{content.summary}</p>
</section>
{/* Emotions */}
{content.emotions_identified && (
<section>
<h4 className="font-semibold mb-2">Emotions I'm Noticing</h4>
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm text-gray-600">Primary (Underlying)</p>
<ul className="list-disc ml-4">
{content.emotions_identified.primary.map(e => <li key={e}>{e}</li>)}
</ul>
</div>
<div>
<p className="text-sm text-gray-600">Secondary (Surface)</p>
<ul className="list-disc ml-4">
{content.emotions_identified.secondary.map(e => <li key={e}>{e}</li>)}
</ul>
</div>
</div>
<p className="mt-2 text-sm">{content.emotions_identified.explanation}</p>
</section>
)}
{/* Needs */}
{content.needs_identified && (
<section>
<h4 className="font-semibold mb-2">Your Underlying Needs</h4>
<ul className="list-disc ml-4">
{content.needs_identified.map(need => <li key={need}>{need}</li>)}
</ul>
<p className="mt-2">{content.needs_explanation}</p>
</section>
)}
{/* Four Horsemen Alert */}
{content.four_horsemen_detected && content.four_horsemen_detected.length > 0 && (
<section className="bg-yellow-50 p-4 rounded border border-yellow-200">
<h4 className="font-semibold mb-2 flex items-center gap-2">
<AlertTriangleIcon className="w-5 h-5 text-yellow-600" />
Communication Pattern Alert
</h4>
<p>{content.four_horsemen_explanation}</p>
</section>
)}
{/* Conversation Starters */}
{content.conversation_starters && (
<section>
<h4 className="font-semibold mb-2">Conversation Starters (NVC Format)</h4>
{content.conversation_starters.map((starter, i) => (
<div key={i} className="bg-blue-50 p-4 rounded mb-2">
<p><strong>Observation:</strong> {starter.observation}</p>
<p><strong>Feeling:</strong> {starter.feeling}</p>
<p><strong>Need:</strong> {starter.need}</p>
<p><strong>Request:</strong> {starter.request}</p>
</div>
))}
</section>
)}
{/* Techniques */}
{content.techniques && (
<section>
<h4 className="font-semibold mb-2">Techniques to Try</h4>
{content.techniques.map(tech => (
<div key={tech.name} className="mb-3">
<h5 className="font-medium">{tech.name}</h5>
<p className="text-sm">{tech.description}</p>
<p className="text-sm italic text-gray-600 mt-1">Example: {tech.example}</p>
</div>
))}
</section>
)}
{/* Pattern Insights */}
{content.pattern_insights && content.pattern_insights.length > 0 && (
<section className="bg-purple-50 p-4 rounded border border-purple-200">
<h4 className="font-semibold mb-2">Pattern Recognition</h4>
<ul className="list-disc ml-4">
{content.pattern_insights.map((insight, i) => (
<li key={i}>{insight}</li>
))}
</ul>
</section>
)}
</div>
);
}ConflictDetailPage.tsx (main page):
export function ConflictDetailPage() {
const { conflictId } = useParams();
const { data: conflict, isLoading } = useQuery(['conflict', conflictId], () =>
fetchConflict(conflictId)
);
if (isLoading) return <LoadingSpinner />;
const userRole = conflict.created_by === currentUserId ? 'partner_a' : 'partner_b';
return (
<div className="max-w-4xl mx-auto space-y-6">
{/* Conflict Title */}
<h1 className="text-3xl font-bold">{conflict.title}</h1>
{/* Partner A's Perspective */}
{(userRole === 'partner_a' || conflict.partner_b_submitted) && (
<Card>
<CardHeader>
<h3>Partner A's Perspective</h3>
</CardHeader>
<CardContent>
{conflict.partner_a_visibility === 'shared' || userRole === 'partner_a'
? <p>{conflict.partner_a_description}</p>
: <p className="italic text-gray-500">Private (not shared)</p>
}
</CardContent>
</Card>
)}
{/* Partner B's Perspective (if submitted) */}
{conflict.partner_b_submitted && (
<Card>
<CardHeader>
<h3>Partner B's Perspective</h3>
</CardHeader>
<CardContent>
{conflict.partner_b_visibility === 'shared' || userRole === 'partner_b'
? <p>{conflict.partner_b_description}</p>
: <p className="italic text-gray-500">Private (not shared)</p>
}
</CardContent>
</Card>
)}
{/* Individual Guidance */}
{userRole === 'partner_a' && conflict.partner_a_individual_guidance && (
<IndividualGuidanceCard
guidance={conflict.partner_a_individual_guidance}
mode={conflict.guidance_mode}
/>
)}
{userRole === 'partner_b' && conflict.partner_b_individual_guidance && (
<IndividualGuidanceCard
guidance={conflict.partner_b_individual_guidance}
mode={conflict.guidance_mode}
/>
)}
{/* Joint-Context Personal Guidance (only after both submitted) */}
{conflict.partner_b_submitted && (
<>
{userRole === 'partner_a' && conflict.partner_a_joint_context_guidance && (
<JointContextGuidanceCard
guidance={conflict.partner_a_joint_context_guidance}
mode={conflict.guidance_mode}
/>
)}
{userRole === 'partner_b' && conflict.partner_b_joint_context_guidance && (
<JointContextGuidanceCard
guidance={conflict.partner_b_joint_context_guidance}
mode={conflict.guidance_mode}
/>
)}
{/* Relationship Guidance (shared by both) */}
{conflict.relationship_guidance && (
<RelationshipGuidanceCard
guidance={conflict.relationship_guidance}
mode={conflict.guidance_mode}
/>
)}
</>
)}
</div>
);
}Loading States:
- Show spinner while AI is processing (BullMQ job in progress)
- Polling or WebSocket to detect when guidance is ready
- Auto-refresh conflict data when new guidance arrives
Dependencies
- Task 4 complete (AI Orchestrator returns guidance)
- Frontend React app with Tailwind CSS
- React Query for data fetching
Effort Estimate
- Size: M
- Hours: 32-40 hours (4-5 days)
- Parallel: false (depends on Task 4 for AI responses)
Definition of Done
- Three guidance card components render correctly
- Structured mode displays all JSON sections properly formatted
- Conversational mode displays plain text paragraphs
- Partner B "reveal" flow works (Partner A's description shown after submission)
- Pattern insights display in UI (if detected)
- Loading states show while AI processes
- Mobile-responsive (tested on 320px, 768px, 1920px viewports)
- Accessible (keyboard navigation, screen reader support)
- E2E test: View all 3 guidance types in both modes
- Visual polish: Typography, spacing, colors match design
coderabbitai
Metadata
Metadata
Assignees
Labels
No labels