Stage 0: Report Editing & Display Fixes

Goal

Make reports fully functional for end users who need to edit and present LLM outputs.

Bugs to Fix

Bug #9: Metadata Tab Disappearing

Root Cause: client/src/features/multi-analysis/hooks/useTabAvailability.js:45-47

const hasMetadata = nonEmpty(results?.topic_patterns) ||
                    nonEmpty(results?.emotion_patterns) ||
                    nonEmpty(results?.entity_patterns);

The tab only appears when these 3 specific fields are populated. Reports using custom prompt templates (e.g., PEL GAC persona analyses) produce custom_fields like synthesized_personas instead, causing the tab to never appear.

Evidence from DB:

-- Last 5 multi_analyses all have:
has_topic_patterns: FALSE
has_entity_patterns: FALSE
has_emotion_patterns: FALSE
has_custom_fields: TRUE

Fix: Option A (Quick): Always show Metadata tab when any content exists

const hasMetadata = hasAnyContent(); // Reuse the existing helper

Option B (Better): Rename to “Details” tab and show:

  • Input dataset summary (always)
  • Pattern charts (when patterns exist)
  • Custom fields (when they exist, with better rendering)

Files to Modify:

  • client/src/features/multi-analysis/hooks/useTabAvailability.js
  • client/src/features/multi-analysis/components/tabs/MetadataTab.jsx

Bug #7: Raw JSON Display for Custom Fields

Root Cause: client/src/features/multi-analysis/components/JsonBlock.jsx

The component categorizes object fields into:

  • primaryFields: quote, text, content, etc.
  • metadataFields: emotion, speaker_id, etc.

Fields that don’t match (like capability_gaps, design_relevance, emotion_dynamics in synthesized_personas) fall through to “otherFields” and render as JSON.stringify(val).

Example Custom Field Structure:

{
  "persona_name": "Urban Micro-venture Optimist",
  "capability_gaps": "- Simple bookkeeping...\n- Marketing beyond...",
  "design_relevance": "Represents the bulk of potential users...",
  "emotion_dynamics": "Optimism spikes when sales flow...",
  ...10 more fields
}

None of these match the hardcoded patterns.

Fix Options:

Option A (Generic): Improve JsonBlock to render all string fields as prose, not JSON

// In otherFields rendering, check type first
{otherFields.map(({ key, val }) => (
  <div key={key} className="text-sm">
    <span className="font-medium">{key.replace(/_/g, ' ')}: </span>
    {typeof val === 'string' ? (
      <span className="whitespace-pre-wrap">{val}</span>
    ) : (
      <span>{JSON.stringify(val)}</span>
    )}
  </div>
))}

Option B (Smart): Detect common patterns in custom_fields:

  • If object has persona_name, render as persona card
  • If array of objects with name/description, render as list cards
  • Otherwise, render each field as a labeled section

Option C (Schema-driven): Use the prompt template’s response_format.json_schema to guide rendering

Recommendation: Start with Option A (quick win), then Option B for known patterns.

Files to Modify:

  • client/src/features/multi-analysis/components/JsonBlock.jsx
  • Possibly create PersonaCard.jsx, GenericFieldRenderer.jsx

Bug #8: Custom Fields Not Editable

Root Cause: client/src/features/multi-analysis/components/tabs/OverviewTab.jsx:140-146

{getCustomFields(results).map(([key, value]) => (
  <JsonBlock
    key={key}
    title={formatKeyName(key)}
    value={value}
  />
))}

JsonBlock is display-only. Standard fields have dedicated edit handlers:

  • state.editFields.executive_synthesis
  • state.editRecommendations
  • state.editOrgHealth
  • etc.

Custom fields have no equivalent.

Fix Options:

Option A (Text editing): For each custom field, allow editing the JSON as text

{state.isEditing ? (
  <Textarea
    value={JSON.stringify(value, null, 2)}
    onChange={(e) => updateCustomField(key, JSON.parse(e.target.value))}
  />
) : (
  <JsonBlock value={value} />
)}
  • Pro: Simple, works for all shapes
  • Con: Users must edit JSON directly

Option B (Field-level editing): For string fields within objects, render as editable inputs

// For each persona object:
<input value={persona.persona_name} onChange={...} />
<textarea value={persona.capability_gaps} onChange={...} />
  • Pro: Better UX
  • Con: Only works for known structures

Option C (Monaco editor): Use Monaco with JSON validation

  • Pro: Syntax highlighting, validation
  • Con: Still requires JSON knowledge

Recommendation:

  1. Start with Option A (JSON textarea) as baseline
  2. Add Option B for synthesized_personas specifically (high-value use case)

Backend Changes: The PUT /analyze/multi/{id} endpoint already accepts custom_fields updates:

# api/src/routers/analyses.py
class MultiAnalysisUpdate(BaseModel):
    custom_fields: Optional[Dict[str, Any]] = None

No backend changes needed.

Files to Modify:

  • client/src/features/multi-analysis/components/JsonBlock.jsx (add edit mode)
  • client/src/features/multi-analysis/hooks/useMultiAnalysisState.js (add edit state for custom fields)
  • client/src/features/multi-analysis/components/tabs/OverviewTab.jsx (wire up editing)

Implementation Order

  1. Bug #9 first - Quick fix, high visibility (tab appears/disappears)
  2. Bug #7 second - Improves readability immediately
  3. Bug #8 third - Requires more state management work

Demo Criteria

User opens a completed multi-analysis report with custom fields (e.g., “Women Persona Analysis for PEL GAC”).

  1. All tabs appear (including Metadata/Details)
  2. Custom sections like “Synthesized Personas” render with readable formatting:
    • Persona name as heading
    • Each field as labeled paragraph, not JSON
  3. User clicks Edit on a custom section
  4. User modifies text (e.g., persona description)
  5. User saves - change persists
  6. Page refresh shows updated content

Testing Notes

Use these existing reports for testing:

  • f386393e-8e3f-4815-b2d2-7c63ccb8ce76 - Women Persona Analysis
  • d80062a9-3c6b-4a95-b754-db148f5045a2 - Men Persona Analysis

Both have custom_fields with synthesized_personas (array of 5 persona objects) and persona_landscape_summary (string).