Skip to content

Guidance Display UI #998

@vadimalpha

Description

@vadimalpha

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions