Corrective Action: Org Chart Agent Cards Rendering Blank

Corrective Action: Org Chart Agent Cards Rendering Blank

Date: 2026-03-18 Category: Data Source Migration -- Silent Consumer Failure Impact: Every agent information card on the public AI Org Chart page was blank for 2 days. 69 agents affected. Resolution Time: Same session as report (~30 minutes) Filed by: V (COO)


Incident

What Happened

The AI Org Chart page at valuefirstteam.com/about/ai-team/org-chart showed blank information cards when clicking any agent node. The card header rendered correctly (name, org badge, clearance level), but the body content area displayed nothing -- no bio, no role description, no operational context. Every one of the 69 agents on the chart was affected identically.

Chris reported the issue during a daily recap session on Mar 18. The page had been live since Mar 16.

What Should Have Happened

Clicking an agent node should display a drawer with the agent's biography, role description, and operational context pulled from their Sanity CMS contributor record.


Root Cause

The AgentDetailDrawer component (apps/website/src/components/admin/org-chart/AgentDetailDrawer.tsx) was built to parse structured markdown Job Descriptions containing specific headings (## Identity, ### Key Value Indicators, ## For AI, ## Current State). The parseJdSections() function split the jd_content string on these headings and returned an object with each section's text.

When the data source shifted from static HTML Job Description files to Sanity CMS contributor records, the jd_content field began receiving plain text bios instead of structured markdown. The parser found none of its expected headings and returned an object with all empty strings.

This created a three-way logic gap that made the empty state invisible:

  1. jdSections was truthy -- the parser returned an object (not null), so the fallback at line 156 (!jdSections && agent.role_summary) never fired
  2. All section checks were falsy -- every individual field (jdSections?.identity, jdSections?.kvi, etc.) was an empty string, so no section content rendered
  3. has_jd was true -- because shortBio existed on the record, the "No formal JD available" warning was suppressed

The result: the component rendered a card with a header and a completely empty body. Not an error state. Not a loading state. Just nothing.


Impact

  • Visibility: The public-facing AI Team org chart -- a key differentiator showing the organization's AI-native operating model -- displayed broken agent cards for anyone who clicked to learn more about an agent
  • Duration: ~2 days (Mar 16 through Mar 18)
  • Scope: All 69 agents on the chart
  • Detection: Manual -- Chris noticed during a session. No automated health check caught it.

Additional Issues Discovered During Investigation

  1. Missing agents: Two agents (Audit, Baldwin) existed in Sanity but were absent from the hardcoded ORG_STRUCTURE array, making them invisible on the chart entirely
  2. Hardcoded counts: Page titles and meta descriptions contained hardcoded strings ("69 Agents", "67 specialized agents") instead of dynamic counts derived from the data
  3. Bio priority inversion: The drawer used shortBio || bio when bio (full text) provides richer content for the detail view

Fix Applied

Commit: e30a13f4

AgentDetailDrawer Content Rendering

Added a hasStructuredJd check that detects when parseJdSections returns all empty strings. When no structured headings are found, the component now displays jd_content directly as plain text under an "About" heading. This creates a three-tier content fallback:

  1. Structured JD markdown -- parsed into Identity, KVI, For AI, Current State sections
  2. Plain bio text -- displayed as-is under "About" when no structured headings exist
  3. Role summary -- final fallback when no content is available at all

Org Chart Structure

Added audit to the self-improvement agent group and baldwin to the content-communications group in the ORG_STRUCTURE constant.

Dynamic Agent Counts

Replaced hardcoded "69 Agents" and "67 specialized agents" in page titles and meta descriptions with ${totalAgents} computed from the data.

Bio Priority

Changed drawer content selection from shortBio || bio to bio || shortBio so the fuller text displays when available.


Timeline

Date Event
Mar 16 69 AI agent records created in Sanity CMS with full bios (commit 01bc9eec)
Mar 16 Org chart page created, pulling agent data from Sanity (commit 764ad34e)
Mar 16-18 All agent drawer cards blank in production (undetected)
Mar 18 Chris reports blank cards during daily recap
Mar 18 Root cause identified: parser expecting structured markdown, receiving plain text
Mar 18 Fix applied: three-tier content fallback, missing agents added, counts made dynamic

Prevention

Aegis Agent Definition Updated

The Aegis agent (website health and integrity) has been updated to explicitly own both AI team pages (/about/ai-team and /about/ai-team/org-chart) with documented data flow from Sanity contributor records through the org chart component. Health check procedures now include verifying drawer content renders, not just chart layout.

HTML Job Descriptions Marked as Legacy

Sanity CMS is now the canonical source for all agent data displayed on the website. The static HTML JD files in /mnt/d/Leadership/assessments/job-descriptions/ remain as internal reference but are no longer the consumer-facing data source. This prevents future confusion about which data format a component should expect.

Fallback Chain Documented

The AgentDetailDrawer now handles three content paths explicitly:

  • Structured JD markdown (headings-based parsing)
  • Plain bio text (direct display)
  • Role summary (minimal fallback)

Each path is testable independently.


Enforcement Lesson

When changing a data source, the consumer component's parsing assumptions must be validated against the new data format. A component designed for one data shape can silently produce empty output when it receives another -- and empty output can masquerade as "no data available" rather than "broken parser."

The verification gap here was interaction-depth: the chart layout, colors, and connections all looked correct on visual inspection. The bug only manifested when clicking a node to open the detail drawer. Verification that stops at "the page loads" misses everything behind the first interaction layer.

This is a specific instance of the general principle: verification must reach the actual destination. Reading TypeScript that compiles is not verifying the component renders content. Seeing the chart render nodes is not verifying the drawer displays bios. The check must exercise the complete path from data source through parser through render.