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>
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>
Phase 1 of the cohesion refactor (behavior-preserving):
Single source of truth:
- New src/lib/theme.ts: category colors (moved from data.ts, re-exported
for back-compat), derived hex strings, nebula palette, UI colors, CSS var map
- New src/lib/config.ts: breakpoint, zoom, parallax, node radii, particle
counts, animation timings (previously scattered magic numbers)
- New src/lib/utils/tree.ts: getNodeById, getNodeRadius, getDistanceFromOrigin,
getBeamIntensity (pure helpers lifted out of onMount)
- New src/lib/utils/stats.ts: branch ids/labels, proficiency order, and the
stats-panel aggregation helpers (deduped from inline copies)
- SkillTree.svelte now imports all of the above; removed the duplicated
getCategoryColorHex, stats helpers, branch id/label lists, and inline magic numbers
- Populated src/lib/index.ts barrel
Bug fixes:
- data.ts: tmdbAPI pypiUrl pointed at tvdbAPI -> corrected to tmdbAPI
- Removed/ gated stray console.log noise (skill-tree-loaded, SW logs)
- Removed dead vars (maxAlpha, glowMaxAlpha) and unused catch binding
Tooling: ESLint + Prettier now clean; added Vitest unit tests for the new
pure utils (14 tests). svelte-check: 0 errors. build: ok.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>