refactor: model TreeNode as a discriminated union

Phase 3 (behavior-preserving):
- Replace the 45-optional-field TreeNode interface with a tagged union over
  category: OriginNode | ExperienceNode | ProjectNode | SkillNode | EducationNode,
  sharing a BaseNode. Category-specific fields now live only on their member.
- Modal template narrows by category (category === 'experience' && ...) instead
  of bare field-presence checks; equivalent at runtime since each field is
  exclusive to its category (proven by the union compiling against the data).
- stats utils narrow correctly: aggregateSkills filters to SkillNode[] via a type
  predicate; countByProficiency and getPlayerClass guard on category.

svelte-check: 0 errors (every data node conforms). lint/test/build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
TehRiehlDealandClaude Opus 4.8 committed 2026-06-03 09:59:25 -07:00
1 parent 6091c62654
commit bf235680a4
4 files changed
+68 -51

No files matched your search

+7 -7
View File
@@ -1240,7 +1240,7 @@
{/if}
<!-- Experience-specific content -->
{#if selectedNode.company}
{#if selectedNode.category === 'experience' && selectedNode.company}
<div class="meta-info">
<span class="company">{selectedNode.company}</span>
{#if selectedNode.startDate}
@@ -1252,7 +1252,7 @@
{/if}
<!-- Education-specific content -->
{#if selectedNode.institution}
{#if selectedNode.category === 'education' && selectedNode.institution}
<div class="meta-info">
<span class="company">{selectedNode.institution}</span>
{#if selectedNode.degree}
@@ -1265,7 +1265,7 @@
{/if}
<!-- Skill-specific content -->
{#if selectedNode.proficiency}
{#if selectedNode.category === 'skills' && selectedNode.proficiency}
<div class="meta-info">
<span class="proficiency-badge {selectedNode.proficiency}"
>{selectedNode.proficiency}</span
@@ -1281,7 +1281,7 @@
{/if}
<!-- Highlights (for experience) -->
{#if selectedNode.highlights && selectedNode.highlights.length > 0}
{#if selectedNode.category === 'experience' && selectedNode.highlights && selectedNode.highlights.length > 0}
<ul class="highlights">
{#each selectedNode.highlights as highlight (highlight)}
<li>{highlight}</li>
@@ -1290,7 +1290,7 @@
{/if}
<!-- Tech stack (for projects) -->
{#if selectedNode.techStack && selectedNode.techStack.length > 0}
{#if selectedNode.category === 'projects' && selectedNode.techStack && selectedNode.techStack.length > 0}
<div class="tech-stack">
{#each selectedNode.techStack as tech (tech)}
<span class="tech-tag">{tech}</span>
@@ -1299,14 +1299,14 @@
{/if}
<!-- Project status badge -->
{#if selectedNode.status}
{#if selectedNode.category === 'projects' && selectedNode.status}
<div class="project-status">
<span class="status-badge {selectedNode.status}">{selectedNode.status}</span>
</div>
{/if}
<!-- Links (for projects) -->
{#if selectedNode.projectUrl || selectedNode.repoUrl || selectedNode.pypiUrl || selectedNode.npmUrl}
{#if selectedNode.category === 'projects' && (selectedNode.projectUrl || selectedNode.repoUrl || selectedNode.pypiUrl || selectedNode.npmUrl)}
<div class="links">
{#if selectedNode.projectUrl}
<a