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

+25 -3
View File
@@ -2,7 +2,7 @@
// 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). */
export const BRANCH_IDS = [
@@ -29,9 +29,31 @@ export const PROFICIENCY_ORDER: Record<string, number> = {
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 {
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 millisPerYear = 1000 * 60 * 60 * 24 * 365.25;
const years = (now.getTime() - earliestStart.getTime()) / millisPerYear;