content: audit phase 4 — lead with the work that differentiates

Brand/content phase. The engineering was never the weak part of this site;
the copy was. Decisions on title, bio angle, download figures and node copy
were the owner's — this implements them.

Identity:
- jobTitle was 'Software Development - Lead Associate', an internal HR grade
  that means nothing to a recruiter or a search engine. Now 'Lead Software
  Engineer' — a faithful translation, not a promotion.
- aboutMe opened with "Intermediate Software Engineer", grading himself down
  in his own headline, and named the most generic possible stack
  (React/Node/SQL/ShadCN/Docker). It now leads with the work almost nobody
  else has done: WebRTC telehealth, real-time gesture recognition that made
  chat usable for speech-impaired patients, and air-gapped deployment
  tooling. Those were previously buried 3-4 clicks deep in leaf nodes and
  appeared nowhere on the surface.
- Title/description/OG/Twitter copy realigned to match. JSON-LD needed no
  change — it already derives from the origin node.

Node copy:
- The five job nodes had placeholder descriptions ('Junior developer at the
  start of my career.', 'Where I am now.') that are the first thing a tooltip
  shows. Each is now a one-line hook naming the most substantial thing from
  that role.
- Normalised highlight punctuation: only exp-job5's bullets ended in periods.

Proof of work:
- `downloads` existed on ProjectNode and was used by zero nodes despite two
  published PyPI packages. Populated with verified lifetime figures from
  pypistats.org (tvdbAPI 2,277, tmdbAPI 784, checked 2026-08-09) and dated in
  a comment. Lifetime, deliberately, not the recent rate — 27 and 12 in the
  last month is not a number worth advertising.
- Nothing rendered the field. NodeModal now shows it beside the status badge,
  and SeoContent renders it too so it's crawlable.

Data integrity:
- edu-degree's description said "Computer Science" while its own structured
  fields said Applied Science / IT-Software Development. Its label was the
  generic "University"; now Green River College.
- calculatePlayerLevel() hardcoded '2020-10-01', duplicating
  exp-job1.startDate — two sources for one fact, free to drift. It now
  derives the earliest start from the experience nodes, via an explicit
  "Month YYYY" parser rather than handing a bare "October 2020" to
  `new Date` (not a format engines are required to accept).
- Added the first tests for calculatePlayerLevel, previously the only
  untested export in stats.ts. 14 -> 16 tests.
- Fixed the 'tool-vscod' id typo.

Corrections to the audit this phase acted on: the self-hosted repos at
git.tehriehldeal.com ARE publicly reachable (all three return 200
anonymously) — the earlier claim that recruiters couldn't reach them was
asserted without checking. All three live project sites are up too.

Verified in the production build: modal shows the new title and bio, the
downloads badge renders as "↓ 2,277 downloads", figures are crawlable in the
prerendered HTML, and no filler copy survives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
TehRiehlDealandClaude Opus 5 committed 2026-08-09 00:42:45 -07:00
1 parent 746d81b151
commit f83a317520
7 files changed
+91 -26

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Kevin Riehl — Software Engineer (React, Node.js, TypeScript) | Portfolio</title> <title>Kevin Riehl — Lead Software Engineer (React, Node.js, WebRTC) | Portfolio</title>
%sveltekit.head% %sveltekit.head%
</head> </head>
+20 -3
View File
@@ -143,10 +143,15 @@
</div> </div>
{/if} {/if}
<!-- Project status badge --> <!-- Project status badge + third-party usage -->
{#if node.category === 'projects' && node.status} {#if node.category === 'projects' && (node.status || node.downloads)}
<div class="project-status"> <div class="project-status">
<span class="status-badge {node.status}">{node.status}</span> {#if node.status}
<span class="status-badge {node.status}">{node.status}</span>
{/if}
{#if node.downloads}
<span class="downloads-badge">↓ {node.downloads}</span>
{/if}
</div> </div>
{/if} {/if}
@@ -471,6 +476,18 @@
transform: translateY(0); transform: translateY(0);
} }
.downloads-badge {
display: inline-block;
padding: 0.2rem 0.6rem;
border: 1px solid rgba(255, 215, 0, 0.35);
border-radius: 12px;
background: rgba(255, 215, 0, 0.08);
color: var(--color-gold);
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.02em;
}
.project-status { .project-status {
margin: 0.75rem 0; margin: 0.75rem 0;
} }
+3
View File
@@ -79,6 +79,9 @@
{#if project.techStack?.length} {#if project.techStack?.length}
<p>Tech: {project.techStack.join(', ')}</p> <p>Tech: {project.techStack.join(', ')}</p>
{/if} {/if}
{#if project.downloads}
<p>{project.downloads}</p>
{/if}
<ul> <ul>
{#if project.projectUrl}<li><a href={project.projectUrl}>Live site</a></li>{/if} {#if project.projectUrl}<li><a href={project.projectUrl}>Live site</a></li>{/if}
{#if project.repoUrl}<li><a href={project.repoUrl}>Source code</a></li>{/if} {#if project.repoUrl}<li><a href={project.repoUrl}>Source code</a></li>{/if}
+19 -13
View File
@@ -88,9 +88,9 @@ export const treeNodes: TreeNode[] = [
size: 'x-large', size: 'x-large',
icon: '', icon: '',
fullName: 'Kevin Riehl', fullName: 'Kevin Riehl',
jobTitle: 'Software Development - Lead Associate', jobTitle: 'Lead Software Engineer',
aboutMe: aboutMe:
'Intermediate Software Engineer with 5+ years of experience architecting full-stack applications using React, Node.js (Hapi), and SQL. Expertise includes modernizing legacy codebases, implementing rigorous testing standards, and leading frontend architecture improvements. Passionate about leveraging modern tools like ShadCN and Docker to streamline development workflows.', "Software engineer with 5+ years building full-stack applications for Defense Health Agency programs. I've architected a WebRTC telehealth platform, shipped real-time gesture recognition that made chat usable for speech-impaired patients, and built the deployment tooling that unblocked a team after an air-gapped infrastructure migration. React, Node.js, NestJS, TypeScript, SQL.",
location: 'Kent, WA', location: 'Kent, WA',
email: 'kevinriehl@gmail.com', email: 'kevinriehl@gmail.com',
linkedIn: 'https://www.linkedin.com/in/kevinriehl', linkedIn: 'https://www.linkedin.com/in/kevinriehl',
@@ -134,7 +134,7 @@ export const treeNodes: TreeNode[] = [
{ {
id: 'exp-job1', id: 'exp-job1',
label: 'Software Engineer', label: 'Software Engineer',
description: 'Junior developer at the start of my career.', description: 'Refactored legacy PHP endpoints onto Hapi.js and ran a MySQL to MSSQL migration.',
x: 420, x: 420,
y: -280, y: -280,
parentId: 'exp-contract', parentId: 'exp-contract',
@@ -155,7 +155,7 @@ export const treeNodes: TreeNode[] = [
{ {
id: 'exp-job2', id: 'exp-job2',
label: 'Software Engineer', label: 'Software Engineer',
description: 'Growing my skills and taking on more responsibility.', description: 'Built a React + Hapi.js analytics search engine over complex event datasets.',
x: 500, x: 500,
y: -350, y: -350,
parentId: 'exp-job1', parentId: 'exp-job1',
@@ -177,7 +177,7 @@ export const treeNodes: TreeNode[] = [
{ {
id: 'exp-job3', id: 'exp-job3',
label: 'Intermediate Software Engineer', label: 'Intermediate Software Engineer',
description: 'Stepping into a more senior role.', description: 'Web Bluetooth biometric monitoring, plus a team-wide move to ShadCN and Docker.',
x: 560, x: 560,
y: -430, y: -430,
parentId: 'exp-job2', parentId: 'exp-job2',
@@ -199,7 +199,8 @@ export const treeNodes: TreeNode[] = [
{ {
id: 'exp-job4', id: 'exp-job4',
label: 'Intermediate Software Engineer', label: 'Intermediate Software Engineer',
description: 'Where one journey ends, another begins.', description:
'WebRTC telehealth platform and real-time gesture recognition for speech-impaired users.',
x: 600, x: 600,
y: -510, y: -510,
parentId: 'exp-job3', parentId: 'exp-job3',
@@ -221,7 +222,8 @@ export const treeNodes: TreeNode[] = [
{ {
id: 'exp-job5', id: 'exp-job5',
label: 'Lead Associate', label: 'Lead Associate',
description: 'Where I am now.', description:
'Air-gapped deployment automation, a NestJS audit interceptor, and 90%+ test coverage.',
x: 620, x: 620,
y: -590, y: -590,
parentId: 'exp-job4', parentId: 'exp-job4',
@@ -233,9 +235,9 @@ export const treeNodes: TreeNode[] = [
startDate: 'January 2026', startDate: 'January 2026',
endDate: 'Present', endDate: 'Present',
highlights: [ highlights: [
'Proactively engineered a suite of automation tools to streamline code deployment workflows to an air-gapped Git instance following a critical infrastructure migration, eliminating manual bottlenecks and restoring team development velocity.', 'Proactively engineered a suite of automation tools to streamline code deployment workflows to an air-gapped Git instance following a critical infrastructure migration, eliminating manual bottlenecks and restoring team development velocity',
'Designed and implemented a custom NestJS API interceptor to dynamically detect and audit specific user activities, providing robust, real-time daily activity tracking for compliance and analytics.', 'Designed and implemented a custom NestJS API interceptor to dynamically detect and audit specific user activities, providing robust, real-time daily activity tracking for compliance and analytics',
'Spearheaded a rigorous testing initiative to elevate code quality, systematically architecting and executing unit tests to scale platform test coverage from a legacy deficit to a resilient 90%+ baseline.' 'Spearheaded a rigorous testing initiative to elevate code quality, systematically architecting and executing unit tests to scale platform test coverage from a legacy deficit to a resilient 90%+ baseline'
] ]
}, },
@@ -306,6 +308,8 @@ export const treeNodes: TreeNode[] = [
techStack: ['Python', 'REST API', 'PyPI', 'GitHub Actions', 'Unit Testing'], techStack: ['Python', 'REST API', 'PyPI', 'GitHub Actions', 'Unit Testing'],
repoUrl: 'https://github.com/TehRiehlDeal/TVDB-API', repoUrl: 'https://github.com/TehRiehlDeal/TVDB-API',
pypiUrl: 'https://pypi.org/project/tvdbAPI/', pypiUrl: 'https://pypi.org/project/tvdbAPI/',
// Lifetime PyPI downloads, checked 2026-08-09 (pypistats.org).
downloads: '2,277 downloads',
status: 'maintained' status: 'maintained'
}, },
{ {
@@ -322,6 +326,8 @@ export const treeNodes: TreeNode[] = [
techStack: ['Python', 'REST API', 'PyPI', 'GitHub Actions', 'Unit Testing'], techStack: ['Python', 'REST API', 'PyPI', 'GitHub Actions', 'Unit Testing'],
repoUrl: 'https://github.com/TehRiehlDeal/tmdbAPI', repoUrl: 'https://github.com/TehRiehlDeal/tmdbAPI',
pypiUrl: 'https://pypi.org/project/tmdbAPI/', pypiUrl: 'https://pypi.org/project/tmdbAPI/',
// Lifetime PyPI downloads, checked 2026-08-09 (pypistats.org).
downloads: '784 downloads',
status: 'maintained' status: 'maintained'
}, },
{ {
@@ -629,7 +635,7 @@ export const treeNodes: TreeNode[] = [
proficiency: 'intermediate' proficiency: 'intermediate'
}, },
{ {
id: 'tool-vscod', id: 'tool-vscode',
label: 'VSCode', label: 'VSCode',
description: description:
'Powerful, lightweight code editor by Microsoft with built-in Git support, debugging, and a vast extension ecosystem.', 'Powerful, lightweight code editor by Microsoft with built-in Git support, debugging, and a vast extension ecosystem.',
@@ -920,8 +926,8 @@ export const treeNodes: TreeNode[] = [
}, },
{ {
id: 'edu-degree', id: 'edu-degree',
label: 'University', label: 'Green River College',
description: "Bachelor's degree in Computer Science.", description: "Bachelor's of Applied Science in IT and Software Development.",
x: -300, x: -300,
y: 80, y: 80,
parentId: 'edu-branch', parentId: 'edu-branch',
+18 -1
View File
@@ -4,7 +4,8 @@ import {
BRANCH_LABELS, BRANCH_LABELS,
aggregateSkills, aggregateSkills,
countByProficiency, countByProficiency,
getPlayerClass getPlayerClass,
calculatePlayerLevel
} from './stats'; } from './stats';
import { treeNodes } from '$lib/data'; import { treeNodes } from '$lib/data';
@@ -55,3 +56,19 @@ describe('getPlayerClass', () => {
expect(getPlayerClass()).toBe(origin?.jobTitle); expect(getPlayerClass()).toBe(origin?.jobTitle);
}); });
}); });
describe('calculatePlayerLevel', () => {
it('derives the level from the earliest experience start date', () => {
// exp-job1 starts October 2020. Previously this date was hardcoded in
// stats.ts as well, so the two could drift apart silently.
const earliest = new Date('October 1, 2020').getTime();
const expected = Math.floor((Date.now() - earliest) / (1000 * 60 * 60 * 24 * 365.25));
expect(calculatePlayerLevel()).toBe(expected);
});
it('returns a plausible, non-negative level', () => {
const level = calculatePlayerLevel();
expect(level).toBeGreaterThanOrEqual(5);
expect(level).toBeLessThan(60);
});
});
+25 -3
View File
@@ -2,7 +2,7 @@
// STATS UTILS — pure aggregation for the RPG-style character panel. // STATS UTILS — pure aggregation for the RPG-style character panel.
// ========================================================================== // ==========================================================================
import { treeNodes, type SkillNode } from '$lib/data'; import { treeNodes, type SkillNode, type ExperienceNode } from '$lib/data';
/** Skill branch ids and their display labels (single source of truth). */ /** Skill branch ids and their display labels (single source of truth). */
export const BRANCH_IDS = [ export const BRANCH_IDS = [
@@ -29,9 +29,31 @@ export const PROFICIENCY_ORDER: Record<string, number> = {
beginner: 3 beginner: 3
}; };
/** Character "level" derived from years since the first job started. */ /**
* Parse a "Month YYYY" string (the format used by every startDate in data.ts)
* into a timestamp. Built explicitly rather than handed straight to `new Date`:
* "October 2020" alone is not a format the spec requires engines to accept.
*/
function parseMonthYear(value: string): number | null {
const [month, year] = value.trim().split(/\s+/);
if (!month || !year) return null;
const parsed = new Date(`${month} 1, ${year}`).getTime();
return Number.isNaN(parsed) ? null : parsed;
}
/**
* Character "level" derived from years since the first job started.
*
* The start date is read from the experience nodes rather than hardcoded — it
* previously duplicated exp-job1.startDate, so the two could silently drift.
*/
export function calculatePlayerLevel(): number { export function calculatePlayerLevel(): number {
const earliestStart = new Date('2020-10-01'); // First job const starts = treeNodes
.filter((n): n is ExperienceNode => n.category === 'experience' && !!n.startDate)
.map((n) => parseMonthYear(n.startDate as string))
.filter((t): t is number => t !== null);
const earliestStart = new Date(starts.length ? Math.min(...starts) : Date.now());
const now = new Date(); const now = new Date();
const millisPerYear = 1000 * 60 * 60 * 24 * 365.25; const millisPerYear = 1000 * 60 * 60 * 24 * 365.25;
const years = (now.getTime() - earliestStart.getTime()) / millisPerYear; const years = (now.getTime() - earliestStart.getTime()) / millisPerYear;
+5 -5
View File
@@ -159,7 +159,7 @@
<link rel="canonical" href="https://tehriehldeal.com/" /> <link rel="canonical" href="https://tehriehldeal.com/" />
<meta <meta
name="description" name="description"
content="Kevin Riehl — Software Engineer with 5+ years building full-stack apps in React, Node.js, and TypeScript. Explore his experience, projects, and skills in an interactive skill-tree portfolio." content="Kevin Riehl — Lead Software Engineer with 5+ years on Defense Health Agency programs — WebRTC telehealth, real-time accessibility, and air-gapped deployment tooling. Explore his experience, projects, and skills in an interactive skill-tree portfolio."
/> />
<meta <meta
name="keywords" name="keywords"
@@ -171,11 +171,11 @@
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta <meta
property="og:title" property="og:title"
content="Kevin Riehl — Software Engineer (React, Node.js, TypeScript)" content="Kevin Riehl — Lead Software Engineer (React, Node.js, WebRTC)"
/> />
<meta <meta
property="og:description" property="og:description"
content="Software Engineer with 5+ years building full-stack apps in React, Node.js, and TypeScript — shown as an interactive Path of Exile-inspired skill tree." content="Lead Software Engineer with 5+ years on Defense Health Agency programs — WebRTC telehealth, real-time accessibility, and air-gapped deployment tooling. Shown as an interactive Path of Exile-inspired skill tree."
/> />
<meta property="og:url" content="https://tehriehldeal.com" /> <meta property="og:url" content="https://tehriehldeal.com" />
<meta property="og:site_name" content="Atlas of Skills" /> <meta property="og:site_name" content="Atlas of Skills" />
@@ -190,11 +190,11 @@
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<meta <meta
name="twitter:title" name="twitter:title"
content="Kevin Riehl — Software Engineer (React, Node.js, TypeScript)" content="Kevin Riehl — Lead Software Engineer (React, Node.js, WebRTC)"
/> />
<meta <meta
name="twitter:description" name="twitter:description"
content="Software Engineer with 5+ years building full-stack apps in React, Node.js, and TypeScript — shown as an interactive Path of Exile-inspired skill tree." content="Lead Software Engineer with 5+ years on Defense Health Agency programs — WebRTC telehealth, real-time accessibility, and air-gapped deployment tooling. Shown as an interactive Path of Exile-inspired skill tree."
/> />
<meta name="twitter:image" content="https://tehriehldeal.com/og-preview.jpg" /> <meta name="twitter:image" content="https://tehriehldeal.com/og-preview.jpg" />
<meta <meta