Commit Graph
2 Commits
Author SHA1 Message Date
TehRiehlDealandClaude Opus 5 746d81b151 fix: audit phase 2 — teardown, failure paths, service worker, tooltip, resize
Correctness phase of the frontend audit. Nothing here is visible on a happy
path; it is what the app does when something goes wrong, when the window
changes, or when the component goes away.

Teardown (new src/lib/pixi/lifecycle.ts):
- Nothing was ever released: no app.destroy(), no ticker.remove(), no
  killTweensOf, no onDestroy. ~480 GSAP tweens (most repeat:-1), a ticker
  callback, and a dozen DOM listeners lived forever.
- 112 of those tweens are created inside an onComplete, so they don't exist
  when a call-site sweep would run. gsap.context() handles exactly this:
  GSAP records each animation's context on creation and restores it while
  invoking that animation's callbacks (see _callback in gsap-core), so
  late-created pulses land in the same context. One ctx.kill() gets all of
  them. kill(), not revert(), so GSAP doesn't write properties back onto
  objects about to be destroyed.
- All DOM listeners now go through life.listen(), which registers removal at
  the same time. Previously 6 canvas listeners were never removed, each
  closing over the whole ~713-object scene graph.
- The unmount race: cleanup used to be registered on the LAST line of an
  async onMount, after the asset waterfall, so unmounting mid-load cleaned up
  nothing. The lifecycle is now created before any await, with life.disposed
  guards after each one.
- Hover tweens re-enter the context per event and use overwrite:'auto' so
  rapid sweeps don't stack conflicting tweens.

Failure paths:
- onMount had no try/catch and no WebGL detection. Any init or asset failure
  left the spinner up forever, the stats toggle at opacity:0, and Escape dead
  (attachKeyboard never ran). Now caught, and it hands off to +page.svelte to
  reveal the text version.
- There was no <noscript> anywhere, and SeoContent is clipped to 1x1px with
  html/body overflow:hidden — so JS-disabled visitors got a pure black page.
  A <noscript><style> block now un-hides it with no JS involved.

Service worker (moved static/ -> src/, so SvelteKit builds it):
- It can now use $service-worker, so precache comes from the real build
  manifest instead of a hand-maintained 5-entry list that omitted all JS/CSS,
  and the cache name embeds `version` so old caches actually get pruned.
- cache.addAll was atomic: one 404 failed the whole install silently. Now
  per-asset with a warning on partial failure.
- The catch returned undefined for non-image assets, which makes
  respondWith throw a TypeError — a soft offline failure became a hard one.
  Every path now resolves to a Response.
- Unhashed assets (icons, headshot, resume PDF) were cache-first with no
  expiry, so replacing the resume at the same URL never reached a returning
  visitor. Those are network-first now; only hashed build output is
  cache-first.
- Navigation responses were cached without a status check, so a 404/500 body
  could become the offline fallback.
- Dropped skipWaiting() from install and added a real update prompt +
  controllerchange reload. The SKIP_WAITING handler was previously dead code
  and the "check for updates periodically" comment described an interval that
  did not exist.

Tooltip:
- Wrote Svelte state on every canvas mousemove even with nothing hovered.
  Now gated on hover and coalesced to one write per frame; raw coords are
  still tracked cheaply so the first hover isn't positioned from stale data.
- Animated left/top (layout + paint per frame on an element with two
  color-mix shadows); now translate3d.
- Listener moved to window, so coordinates no longer freeze when a drag
  leaves the canvas.
- Flip logic measured a hardcoded 280x120; now measures the real element
  (actual height is 113, so tall tooltips mis-flipped near the bottom edge).
- Dropped role="tooltip" + aria-live="polite", which announced the full
  label and description on every hover. Nothing referenced it via
  aria-describedby, so the role was inert. Now aria-hidden.

Resize:
- Star positions, gradient radius, vignette size and dust wrap bounds were
  all baked at first paint, so enlarging the window left an unlit band. The
  background is now rebuilt on a rAF-coalesced resize, under its own
  sub-lifecycle. The view is shifted by half the delta rather than re-centred,
  so the user's current view stays anchored.
- isTouchDevice used 'ontouchstart', true on touch-capable laptops and thus
  permanently killing mouse hover. Now matchMedia('(hover: hover)').

Known gap: node radii still don't change when the 768px breakpoint is
crossed mid-session; re-creating all 57 nodes would reset interaction state.

Verified in the production build: resize 1278x1220 -> 1734x821 rebuilds with
zero errors, tooltip positions via transform and stays on screen, fallback
renders as a readable resume page. svelte-check clean, lint clean, 14/14.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 00:27:30 -07:00
TehRiehlDealandClaude Opus 4.8 df78dc694f feat: make portfolio content crawlable + add SEO structured data
The entire portfolio rendered only on the PixiJS canvas, so crawlers saw a
near-empty body and the site ranked poorly for non-personalized searches. Fixes:

- NEW SeoContent.svelte: screen-reader/crawler-only (.sr-only) semantic HTML
  (h1 + bio + contact + Experience/Projects/Skills/Education), generated from
  data.ts (single source of truth, reuses aggregateSkills). Prerenders into
  build/index.html — verified it now contains the job/project/education text
  that was previously canvas-only. Also a big screen-reader accessibility win.
- JSON-LD schema.org Person in +layout (name, jobTitle, alumniOf, knowsAbout,
  sameAs LinkedIn/GitHub) — strong entity signal for name searches.
- Canonical URL; static/sitemap.xml + robots.txt Sitemap reference.
- Keyword-tuned <title>, description, og/twitter titles (name + role + stack).
- Demoted the transient welcome <h1> to a <div> so SeoContent's <h1> is the
  single canonical heading. Bumped SW cache to v2.

.sr-only utility added to app.css. check/lint/test/build clean; verified no
visual regression and no runtime exceptions (background + canvas unchanged).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 11:16:17 -07:00