Skip to content

POSITION ONLY PLAN

Implementation Plan: Support Position-Only Org Charts (Name Optional)

Overview

Allow org charts with titles/positions but no employee names. Make name field optional throughout the system and gracefully handle display when names are missing.


  1. Schema & Validation Changes

API Schema (workers/api/src/services/extraction.ts or similar)

Current: name: z.string().min(1) // Required

Change to: name: z.string().nullable().optional() title: z.string().min(1) // Make title required instead

Validation logic:

  • At least ONE of name or title must be present
  • If both missing, fail validation
  • Add validation: z.refine((data) => data.name || data.title, { message: “Either name or title required” })

  1. Extraction Logic Updates

AI Prompt Enhancement (workers/api/src/services/extraction.ts)

Add to prompt:

  • If the org chart shows positions/titles without employee names, extract the title as the primary identifier
  • Set “name” to the person’s name if visible, otherwise set to null
  • ALWAYS extract the “title” field (position/role)
  • For position-only charts, use the title as the unique identifier

Post-Extraction Processing

File: workers/api/src/services/extraction.ts

Add after schema validation: // Normalize: Use title as name if name is missing people.forEach(person => { if (!person.name && person.title) { person.name = person.title; person._isPositionOnly = true; // Flag for UI } });


  1. Database Schema (No Changes Needed)

The database already allows name to be nullable: name TEXT NOT NULL — Currently NOT NULL

Action: Check if migration needed to make name nullable

  • If needed: Create migration 003_make_name_nullable.sql
  • Change: ALTER TABLE org_chart_people ALTER COLUMN name DROP NOT NULL;

  1. API Response Handling

Update Type Definitions

File: packages/shared/src/types.ts (or wherever types are defined)

interface Person { name?: string | null; // Make optional title: string; // Make required // … other fields _isPositionOnly?: boolean; // Flag for frontend }

API Endpoints

Files to update:

  • workers/api/src/routes/people.ts - List people endpoint
  • workers/api/src/routes/orgcharts.ts - CSV upload endpoint

Change: Remove validation that requires name to be non-empty


  1. Frontend Display Updates

Template Rendering (All Templates)

Files to update:

  • workers/api/src/templates/top-down-tree.ts
  • workers/api/src/templates/horizontal-tree.ts
  • workers/api/src/templates/radial/index.ts
  • All other template files…

Display Logic: // Current: ${person.name} ${person.title}

// Change to: const displayName = person.name || person.title; const displayTitle = person.name ? person.title : ”;

${displayName} {displayTitle && ${displayTitle}}

Radial Template Updates

File: apps/web/src/components/templates/org-chart-radial/RadialOrgChart.tsx

Line ~233 (node rendering): // Show title as primary if no name const displayName = d.data.name || d.data.title; const displaySubtitle = d.data.name ? d.data.title : null;

Tooltip display:

{hoveredNode.name || hoveredNode.title}

{hoveredNode.name &&

{hoveredNode.title}

}

Accessible View

File: apps/web/src/components/templates/org-chart-radial/AccessibleOrgChart.tsx

Line ~30 (OrgTreeNode):

{node.name || node.title}
{node.name &&
{node.title}
}
  1. Editor UI Updates

People List

File: apps/web/src/app/editor/page.tsx

Display in table: // Show placeholder for missing names

{person.name || Position: {person.title}}

Add Person Form

Make name field optional: <input placeholder=“Employee Name (optional if position-only chart)” value={newName} // Remove required validation />

Validation: if (!newName.trim() && !newTitle.trim()) { setError(‘Either name or title is required’); return; }


  1. CSV Import/Export

CSV Parser

File: workers/api/src/utils/csv-parser.ts

Allow empty names: // Current validation (line ~234): if (!person.name) { errors.push(Row ${i + 1}: Missing name); continue; }

// Change to: if (!person.name && !person.title) { errors.push(Row ${i + 1}: Either name or title required); continue; }

// Use title as name if name is empty person.name = person.name || person.title;

CSV Template

File: apps/web/src/app/editor/page.tsx

Update template example: name,title,department,email,phone,location,employeeId,managerName,photoUrl,linkUrl "",“CEO”,“Executive”,"","","","","","","" "",“CTO”,“Engineering”,"","","","",“CEO”,"",""

Update help text:

Either name or title is required. For position-only charts, leave name empty and provide title.


  1. Testing Checklist
  • Upload position-only PDF - extraction succeeds
  • Mixed chart (some names, some positions) - both display correctly
  • CSV import with empty names - uses titles
  • CSV export preserves empty names
  • All templates render position-only charts correctly
  • Hover tooltips show appropriate info
  • Accessible view displays correctly
  • Manager relationships work with position-only nodes
  • Search finds positions by title when name is empty
  • Share links work for position-only charts

  1. Edge Cases to Handle

  2. Duplicate titles without names - Multiple “Manager” positions - Solution: Append department or unique ID

  3. Manager matching in CSV - Match by title when name is empty - Use case-insensitive matching

  4. Search functionality - Search both name AND title fields - Highlight matches in either field

  5. Sorting - Sort by name if present, else by title


  1. Migration Path

  2. Update schema validation (API)

  3. Update database if needed (migration)

  4. Update extraction prompts

  5. Update all template rendering

  6. Update CSV parser

  7. Update frontend displays

  8. Test with position-only chart

  9. Test with mixed chart

  10. Deploy API first, then frontend


Estimated Changes

  • API files: ~8-10 files
  • Frontend files: ~6-8 files
  • Database migration: 1 file (if needed)
  • Total LOC: ~150-200 lines changed

Save this plan and share it with me when you’re ready to implement!