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.
- 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” })
- 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 } });
- 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;
- 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
- 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:
// Change to: const displayName = person.name || person.title; const displayTitle = person.name ? person.title : ”;
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):
- 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; }
- 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.
- 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
-
Edge Cases to Handle
-
Duplicate titles without names - Multiple “Manager” positions - Solution: Append department or unique ID
-
Manager matching in CSV - Match by title when name is empty - Use case-insensitive matching
-
Search functionality - Search both name AND title fields - Highlight matches in either field
-
Sorting - Sort by name if present, else by title
-
Migration Path
-
Update schema validation (API)
-
Update database if needed (migration)
-
Update extraction prompts
-
Update all template rendering
-
Update CSV parser
-
Update frontend displays
-
Test with position-only chart
-
Test with mixed chart
-
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!