Skip to content

Refactors code to improve maintainability#39

Merged
kiliantyler merged 11 commits intomainfrom
refactor/all
Sep 16, 2025
Merged

Refactors code to improve maintainability#39
kiliantyler merged 11 commits intomainfrom
refactor/all

Conversation

@kiliantyler
Copy link
Copy Markdown
Collaborator

@kiliantyler kiliantyler commented Sep 16, 2025

Moves utilities and related types out of src/lib to src/utils and src/types to improve code organization and maintainability.

This change involves:

  • Moving theme-related and achievement-related logic to the src/utils directory.
  • Moving type definitions to the src/types directory.
  • Updating import paths throughout the codebase to reflect these changes.

Summary by CodeRabbit

  • Refactor

    • Reorganized internal modules and shared types; imports updated across the UI and providers with no behavior changes.
    • Renamed home content export; hero, profile image, and map components now use the new home-specific content.
    • Minor UI tweak: a mode toggle button no longer exposes aria-label/title attributes.
  • Chores

    • Updated build/ignore configuration to reflect relocated generated bundles.
  • Documentation

    • Added TypeScript declaration files for themes, projects, achievements, work experience, pets, quick facts, and related types.

@vercel
Copy link
Copy Markdown

vercel bot commented Sep 16, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
kil-dev Ready Ready Preview Comment Sep 16, 2025 8:47pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Sep 16, 2025

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Refactors move many runtime helpers and generated bundles from src/lib to src/utils, extract inline interfaces into new src/types/*.d.ts, update import paths across components and build scripts, and rename CONTENT → HOME_CONTENT (adding map fields). No significant control-flow changes except ThemeProvider storage handling and Theme defaults used by some UI components.

Changes

Cohort / File(s) Summary
Repo ignores & ESLint / ambient modules
\.gitignore, eslint.config.js, src/types/presence-bundle.d.ts, src/types/theme-bundle.d.ts
Update ignored/generated bundle paths and ambient module declarations from src/lib/*src/utils/*; adjust ESLint ignores and .gitignore entries.
Build scripts
scripts/build-presence-runtime.ts, scripts/build-theme-runtime.ts
Change build entry/out paths for presence and theme runtime bundles from src/lib/*src/utils/*.
Lib data modules (types lifted & renamed)
src/lib/achievements.ts, src/lib/content.ts, src/lib/experience.ts, src/lib/light-grid.ts, src/lib/navmenu.ts, src/lib/pets.ts, src/lib/projects.ts, src/lib/quickfacts.ts, src/lib/skillicons.ts, src/lib/social-links.ts, src/lib/themes.ts
Remove many local interface/type/guard helpers; switch exported data to rely on new src/types/* definitions; rename CONTENTHOME_CONTENT and add MAP_* fields; several helper functions removed from lib modules.
New shared types
src/types/*.d.ts (achievements, home-content, light-grid, navigation-item, pets, presence-bundle, projects, quick-facts, skillicons, social-links, theme-bundle, themes, work-experience)
Add centralized type declarations replacing many former inline interfaces (public type surface extracted into src/types).
New/relocated utilities
src/utils/achievements.ts, src/utils/skillicons.ts, src/utils/themes.ts, src/utils/theme-runtime.ts, src/utils/theme-css.ts
Add/relocate runtime helpers: achievements parsing/serialize + presence script builder; skill icon URL & resolve; theme lookup/guards/labels/icons/headshots/base color; adjust imports and type usage.
Providers & app wiring
src/components/providers/achievements-provider.tsx, src/components/providers/providers.tsx, src/components/providers/theme-provider.tsx, src/app/achievements/page.tsx, src/app/layout.tsx
Update imports to @/utils/* and @/types/*; ThemeProvider storage handling now uses storageKey('theme'), readStorageThemeMeta, coerceToValidTheme, writeCookieTheme and applyClasses flow.
Components — import path updates & small tweaks
src/components/**, .storybook/docs-container.tsx, src/components/layout/about/aboutme/mode-toggle-link.tsx, src/components/ui/map-tooltip.tsx, src/components/layout/home/hero/*, etc.
Mass import path adjustments from @/lib/*@/utils/* or @/types/*; Hero/ProfileImage/MapTooltip now use HOME_CONTENT; ModeToggleLink removed aria/title attributes (kept onKeyDown); many cn, theme, skillicons imports moved.
Config / aliases
components.json
Update alias for utils from "@/lib/utils""@/utils/utils".

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant ThemeProvider
  participant Storage
  participant Cookie
  Note over Browser,ThemeProvider: storage event triggers when theme changes in another tab
  Browser->>ThemeProvider: onStorage(event with key)
  ThemeProvider->>ThemeProvider: if key === storageKey('theme')
  ThemeProvider->>Storage: readStorageThemeMeta()
  Storage-->>ThemeProvider: { value, updatedAt }
  ThemeProvider->>ThemeProvider: next = coerceToValidTheme(value)
  ThemeProvider->>Cookie: writeCookieTheme(next, updatedAt)
  ThemeProvider->>ThemeProvider: setState(next)
  ThemeProvider->>Browser: applyClasses(next, getSystemTheme())
  Note right of ThemeProvider: updated theme applied to document/classes
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Refactors code to improve maintainability" is a concise, single-sentence summary that matches the PR’s main intent of reorganizing modules (moving utilities and types from src/lib into src/utils and src/types) to improve maintainability; it is related to and not misleading about the changes. While somewhat generic, it is clear enough for a reviewer scanning PR history to understand the primary purpose without listing file-level details.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f2eddd3 and 2dde9a3.

📒 Files selected for processing (3)
  • src/app/achievements/page.tsx (1 hunks)
  • src/components/layout/achievements/achievement-card.tsx (1 hunks)
  • src/components/providers/achievements-provider.tsx (2 hunks)

Comment @coderabbitai help to get the list of available commands and usage tips.

cursor[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/components/providers/theme-provider.tsx (1)

242-250: Fix storage event key when a storageNamespace (prefix) is used.
Currently checks e.key === 'theme', which breaks when a prefix is set (keys become ${prefix}_theme). Use storageKey('theme').

-    const onStorage = (e: StorageEvent) => {
-      if (e.key !== 'theme') return
+    const onStorage = (e: StorageEvent) => {
+      if (e.key !== storageKey('theme')) return
       // Re-read storage meta to get both value and timestamp
       const { theme: lsTheme, updatedAt } = readStorageThemeMeta()
       const next = coerceToValidTheme(lsTheme)
       setThemeState(next)
       writeCookieTheme(next, updatedAt)
       applyClasses(next, getSystemTheme())
     }
src/components/layout/about/aboutme/mode-toggle-link.tsx (1)

103-114: Fix a11y: empty aria-label/title remove the accessible name

Button already has visible text. Remove both attributes (or set a meaningful aria-label).

-      onClick={handleClick}
-      onKeyDown={handleKeyDown}
-      aria-label=""
-      title="">
+      onClick={handleClick}
+      onKeyDown={handleKeyDown}>
src/lib/quickfacts.ts (1)

1-10: Allow external URLs for QuickFact.href

QuickFact.href is typed as Route (internal Next route) but QUICK_FACTS contains absolute external URLs. Change in src/types/quick-facts.d.ts: href?: Route → href?: string | Route.

🧹 Nitpick comments (34)
src/lib/social-links.ts (2)

1-6: Harden typing with satisfies + as const (after fixing the type).

Prevents accidental key drift/mutation and keeps literal URL inference.

Apply this diff:

-export const SOCIAL_LINKS: SocialLinks = {
+export const SOCIAL_LINKS = {
   GITHUB: 'https://github.com/kiliantyler/',
   LINKEDIN: 'https://www.linkedin.com/in/kilian-tyler/',
-}
+} as const satisfies Readonly<SocialLinks>

1-1: Confirm placement aligns with refactor intent (utils vs lib).

PR objective mentions moving utilities from src/lib to src/utils. If social-links is a util/config, consider relocating accordingly for consistency.

src/types/light-grid.d.ts (2)

1-13: Make LightGrid immutable and tighten COLOR_RGB type.

This is a config shape; make fields readonly and constrain COLOR_RGB to an RGB triplet string for safer usage.

-export interface LightGrid {
-  GRID_SIZE_PX: number
-  GRID_OFFSET_PX: number
-  NUM_LIGHTS: number
-  DURATION_SECONDS: number
-  STAGGER_SECONDS: number
-  DETOUR_PROBABILITY: number
-  HORIZONTAL_PROBABILITY: number
-  DOT_SIZE_PX: number
-  COLOR_RGB: string
-  GLOW_NEAR_PX: number
-  GLOW_FAR_PX: number
-}
+export interface LightGrid {
+  readonly GRID_SIZE_PX: number
+  readonly GRID_OFFSET_PX: number
+  readonly NUM_LIGHTS: number
+  readonly DURATION_SECONDS: number
+  readonly STAGGER_SECONDS: number
+  readonly DETOUR_PROBABILITY: number
+  readonly HORIZONTAL_PROBABILITY: number
+  readonly DOT_SIZE_PX: number
+  readonly COLOR_RGB: `${number},${number},${number}`
+  readonly GLOW_NEAR_PX: number
+  readonly GLOW_FAR_PX: number
+}

1-13: Consider minimal TSDoc for units and ranges.

Add brief comments (px, seconds, probability 0..1) to aid consumers and future maintainers.

src/lib/light-grid.ts (2)

3-20: Enforce exactness + immutability at the value site.

Use “as const satisfies” to (a) catch extra/missing keys, (b) retain literal values, (c) keep LIGHT_GRID readonly. Pairs with the readonly interface change.

-export const LIGHT_GRID: LightGrid = {
+export const LIGHT_GRID = {
   // layout
   GRID_SIZE_PX: 40,
   GRID_OFFSET_PX: -5,
   NUM_LIGHTS: 16,

   // motion
   DURATION_SECONDS: 20,
   STAGGER_SECONDS: 0,
   DETOUR_PROBABILITY: 0.1,
   HORIZONTAL_PROBABILITY: 0.5,

   // dot appearance
   DOT_SIZE_PX: 5,
   COLOR_RGB: '3,169,244',
   GLOW_NEAR_PX: 5,
   GLOW_FAR_PX: 10,
-}
+} as const satisfies LightGrid

3-20: Optional: align file location with PR objective.

If you’re consolidating utilities under src/utils, consider moving this from src/lib to src/utils/light-grid.ts for consistency.

src/components/ui/link-button.tsx (1)

2-2: LGTM; consider a barrel export for ergonomics.

Optional: add src/utils/index.ts to re-export cn so callsites can import { cn } from '@/utils', reducing deep imports.

Apply:

+// src/utils/index.ts
+export * from './utils'
src/components/ui/bottom-drawer.tsx (1)

17-20: Prefer typing props without the ignored field instead of destructuring _ignoredDirection.

Avoids accepting a prop we intentionally discard; simplifies call sites.

Apply:

-type BottomDrawerProps = React.ComponentPropsWithoutRef<typeof Drawer>
-function BottomDrawer({ direction: _ignoredDirection, ...rest }: BottomDrawerProps) {
+type BottomDrawerProps = Omit<React.ComponentPropsWithoutRef<typeof Drawer>, 'direction'>
+function BottomDrawer(rest: BottomDrawerProps) {
   return <Drawer direction="bottom" {...rest} />
 }
eslint.config.js (1)

12-12: Future‑proof ignore patterns for generated bundles.

Use globs so future relocations won’t require config edits.

-    ignores: ['.next', 'next-env.d.ts', 'src/utils/theme-bundle.ts', 'src/utils/presence-bundle.ts'],
+    ignores: [
+      '.next',
+      'next-env.d.ts',
+      'src/**/theme-bundle.ts',
+      'src/**/presence-bundle.ts',
+    ],
src/types/themes.d.ts (1)

5-15: Consider narrowing MonthDay and making ThemeConfig readonly.

Keeps theme metadata immutable and encourages valid dates.

-export type MonthDay = { month: number; day: number }
+export type MonthDay = { month: number; day: number } // consider runtime guard

-export type ThemeConfig = {
-  name: string
-  icon: IconComponent
-  headshotImage: StaticImageData
-  baseColor: BaseColor
-  darkModeNote?: string
-  timeRange?: { start: MonthDay; end: MonthDay }
-}
+export type ThemeConfig = Readonly<{
+  name: string
+  icon: IconComponent
+  headshotImage: StaticImageData
+  baseColor: BaseColor
+  darkModeNote?: string
+  timeRange?: { start: MonthDay; end: MonthDay }
+}>
src/utils/themes.ts (2)

13-13: Expose THEME_BY_NAME as ReadonlyMap.

Signals immutability and prevents accidental writes.

-export const THEME_BY_NAME = new Map<ThemeName, ThemeEntry>(themes.map(t => [t.name, t] as const))
+export const THEME_BY_NAME: ReadonlyMap<ThemeName, ThemeEntry> =
+  new Map<ThemeName, ThemeEntry>(themes.map(t => [t.name, t] as const))

31-33: Rename isSystemVal → isBaseColor for clarity.

It checks BaseColor, not a “system” value.

-export function isSystemVal(val: unknown): val is BaseColor {
+export function isBaseColor(val: unknown): val is BaseColor {
   return val === 'dark' || val === 'light'
 }

Follow‑up: update imports/calls accordingly.

src/app/layout.tsx (2)

38-51: Use centralized enum for presence keys to avoid string drift.

After adding ACHIEVEMENT_KEY, replace raw strings.

-            __html: buildPresenceScript({
-              key: 'RECURSIVE_REWARD',
+            __html: buildPresenceScript({
+              key: ACHIEVEMENT_KEY.RECURSIVE_REWARD,
               attribute: 'data-has-achievements',
             }),
...
-            __html: buildPresenceScript({
-              key: 'PET_PARADE',
+            __html: buildPresenceScript({
+              key: ACHIEVEMENT_KEY.PET_PARADE,
               attribute: 'data-has-pet-gallery',
             }),

Add: import { ACHIEVEMENT_KEY } from '@/types/achievements'.


34-53: Prep for strict CSP: add nonce to inline scripts.

If/when you enforce a non‑'unsafe-inline' CSP, these will break. Pass a nonce from headers and set it on each script tag.

If you want, I can draft a small helper to plumb a CSP nonce through Next layout/head.

src/utils/theme-css.ts (1)

13-16: Harden against CSS injection and invalid identifiers.
Inputs (baseSelector, variantAttr, theme class names) are interpolated directly into CSS. Guard/escape to prevent malformed CSS or injection via unexpected characters.

Apply escaping and validation:

 function buildNotChain(classes: string[]): string {
-  if (!classes.length) return ''
-  return classes.map(n => `:not(.${n})`).join('')
+  if (!classes.length) return ''
+  return classes.map(n => `:not(.${cssEscape(n)})`).join('')
 }
 
 export function buildPerThemeVariantCss({
   baseSelector,
   variantAttr,
   display = 'block',
 }: {
   baseSelector: string
   variantAttr: string
   display?: DisplayValue
 }): string {
+  const attr = assertSafeAttrName(variantAttr)
   const names = getThemeNames()
   const nonBase = getNonBaseThemes()
   const rules: string[] = []
 
   // Hide all by default
   rules.push(`${baseSelector}{display:none}`)
 
   // Non-base themes take precedence when their class is on <html>
   for (const n of nonBase) {
-    rules.push(`html.${n} ${baseSelector}[${variantAttr}="${n}"]{display:${display}}`)
+    rules.push(`html.${cssEscape(n)} ${baseSelector}[${attr}="${n}"]{display:${display}}`)
   }
 
   // Dark shows when .dark present and no non-base theme class is active
   if (names.includes('dark')) {
     const notNonBase = buildNotChain(nonBase)
-    rules.push(`html.dark${notNonBase} ${baseSelector}[${variantAttr}="dark"]{display:${display}}`)
+    rules.push(`html.dark${notNonBase} ${baseSelector}[${attr}="dark"]{display:${display}}`)
   }
 
   // Light shows when not dark and no non-base theme class is active
   if (names.includes('light')) {
     const notOthers = buildNotChain(['dark', ...nonBase])
-    rules.push(`html${notOthers} ${baseSelector}[${variantAttr}="light"]{display:${display}}`)
+    rules.push(`html${notOthers} ${baseSelector}[${attr}="light"]{display:${display}}`)
   }
 
   return rules.join('')
 }
 
 export function buildBaseColorGateCss({
   baseSelector,
   darkSelector,
   lightSelector,
   display = 'inline',
 }: {
   baseSelector: string
   darkSelector: string
   lightSelector: string
   display?: DisplayValue
 }): string {
   const names = getThemeNames()
   const nonBase = getNonBaseThemes()
   const rules: string[] = []
 
   // Hide all by default
   rules.push(`${baseSelector}{display:none}`)
 
   // Seasonal/custom themes show based on their baseColor
   for (const t of themes) {
     if (t.name === 'light' || t.name === 'dark') continue
     const target = t.baseColor === 'dark' ? darkSelector : lightSelector
-    rules.push(`html.${t.name} ${target}{display:${display}}`)
+    rules.push(`html.${cssEscape(t.name)} ${target}{display:${display}}`)
   }
 
   // Dark shows when .dark present and no non-base theme class active
   if (names.includes('dark')) {
     const notNonBase = buildNotChain(nonBase)
     rules.push(`html.dark${notNonBase} ${darkSelector}{display:${display}}`)
   }
 
   // Light shows when not dark and no non-base theme class active
   if (names.includes('light')) {
     const notOthers = buildNotChain(['dark', ...nonBase])
     rules.push(`html${notOthers} ${lightSelector}{display:${display}}`)
   }
 
   return rules.join('')
 }

Add helpers (outside the ranges above):

// Prefer native CSS.escape; fallback minimal escaper.
function cssEscape(s: string): string {
  const esc = (globalThis as any).CSS?.escape
  return typeof esc === 'function' ? esc(s) : s.replace(/[^a-z0-9_-]/gi, '\\$&')
}
function assertSafeAttrName(name: string): string {
  if (!/^[A-Za-z_][\w:-]*$/.test(name)) throw new Error(`Invalid attribute name: ${name}`)
  return name
}

Also applies to: 18-26, 27-52, 54-64, 65-92

src/components/ui/skill-icons.tsx (1)

5-5: Import type from src/types to match the refactor.
Aligns with the new type location and decouples from lib runtime.

-import type { SkillEntry } from '@/lib/skillicons'
+import type { SkillEntry } from '@/types/skillicons'
src/components/layout/about/aboutme/quick-fact.tsx (1)

11-11: Remove unnecessary key on internal element.
Keys should be on the list item component, not inside it. This key has no effect.

-    <div key={fact.label} className="grid grid-cols-[auto_1fr] items-baseline gap-3">
+    <div className="grid grid-cols-[auto_1fr] items-baseline gap-3">
src/components/ui/map-tooltip.tsx (1)

58-63: Lazy-load embedded maps to reduce cost on initial render.
Add loading="lazy" to both iframes (mobile + desktop).

Apply this diff:

-              <iframe
+              <iframe
                 src={embedMapSrc}
                 title={`Map of ${locationLabel}`}
                 className="relative z-10 h-[240px] w-full rounded-md border border-border shadow-md"
+                loading="lazy"
               />
@@
-              <iframe
+              <iframe
                 src={embedMapSrc}
                 title={`Map of ${locationLabel}`}
                 width={320}
                 height={200}
                 className="relative z-10 h-[200px] w-[320px] rounded-md border border-border shadow-md"
+                loading="lazy"
               />

Also applies to: 80-86

.storybook/docs-container.tsx (1)

9-10: Align Theme type import with new src/types location

Refactor uses src/types for types. Update to prevent accidental runtime coupling with lib.

-import { type Theme } from '../src/lib/themes'
+import type { Theme } from '../src/types/themes'
src/types/home-content.d.ts (1)

1-7: Make fields readonly to reflect immutable content

HOME_CONTENT is static; readonly tightens API and prevents accidental writes.

-export interface HomeContent {
-  NAME: string
-  TITLE: string
-  LOCATION: string
-  MAP_LATITUDE: number
-  MAP_LONGITUDE: number
-}
+export interface HomeContent {
+  readonly NAME: string
+  readonly TITLE: string
+  readonly LOCATION: string
+  readonly MAP_LATITUDE: number
+  readonly MAP_LONGITUDE: number
+}
src/components/providers/providers.tsx (1)

3-3: Consider moving ThemeName type to src/types

For consistency with the refactor, import ThemeName from types.

-import type { ThemeName } from '@/lib/themes'
+import type { ThemeName } from '@/types/themes'
src/lib/experience.ts (1)

14-17: Typo: company name capitalization

Use “QGenda” consistently.

-    summary: 'Qgenda bought New Innovations and I joined as a Senior Site Reliability Engineer.',
+    summary: 'QGenda bought New Innovations and I joined as a Senior Site Reliability Engineer.',
src/types/navigation-item.d.ts (1)

1-8: Prefer readonly properties for nav items

Nav items are static; readonly helps prevent accidental mutation.

 export interface NavigationItem {
-  label: string
-  href: Route
-  icon: IconComponent
+  readonly label: string
+  readonly href: Route
+  readonly icon: IconComponent
 }
src/components/layout/experience/work-history/collapsible-highlights.tsx (1)

5-6: Nit: avoid importing a value symbol as a type; use a local alias for clarity.

Importing resolveSkills as a type works, but can read oddly since it’s a value. Prefer a local alias to make intent explicit and keep the import list tighter.

Apply this diff:

-import type { resolveSkills } from '@/utils/skillicons'
+// Local type alias avoids type‑importing a value symbol
+type SkillsEntries = ReturnType<typeof import('@/utils/skillicons').resolveSkills>

And update the prop type:

-  skillsEntries: ReturnType<typeof resolveSkills>
+  skillsEntries: SkillsEntries

Also applies to: 14-14

src/app/achievements/page.tsx (1)

11-11: Remove unnecessary await on cookies().

cookies() is sync; the await adds an avoidable microtask hop.

Apply this diff:

-  const cookieStore = await cookies()
+  const cookieStore = cookies()
src/components/layout/header/mobile-nav-button.tsx (1)

7-8: Import paths OK — IconComponent missing full SVG props

  • Import path updates look good; type-only import for IconComponent is correct.
  • src/types/themes.d.ts:6 defines IconComponent as ComponentType<{ className?: string }>, so className is present but aria-hidden and other standard React.SVGProps are not. Change to ComponentType<React.SVGProps> (or extend the props) to allow aria-* and other SVG attributes.
src/utils/theme-runtime.ts (1)

21-25: Equal start==end currently means “always on for the full year”. Verify intent.

compareMonthDay treats start==end as crossesYear, so isDateInRecurringRange spans 12 months (inclusive start, exclusive end next year). If this isn't intended, special‑case equality.

 function isDateInRecurringRange(date: Date, start: MonthDay, end: MonthDay): boolean {
-  const crossesYear = compareMonthDay(end, start) <= 0
+  const cmp = compareMonthDay(end, start)
+  if (cmp === 0) return false // or `true` if "always-on" is desired; pick explicitly
+  const crossesYear = cmp < 0

Also applies to: 27-51

src/utils/skillicons.ts (1)

5-18: Nit: unreachable fallback and comment typo.

SkillIconRef union only allows string or {source: 'dashboardicons'…}; the fallback branch is effectively unreachable. Keep it if you want a guard, but fix the comment (“syvixor”).

-  // Fallback to syvixor if an unexpected value slips through
+  // Fallback to generic skills endpoint if an unexpected value slips through
   return `/api/image/skills/`
src/components/layout/home/hero/profile-image.tsx (1)

10-15: Alt text/source updates look good; minor a11y improvement.

Consider reflecting the current variant in aria-label (e.g., “Toggle grumpy profile image” → dynamic label when grumpy). Optional.

-  aria-label="Toggle grumpy profile image"
+  aria-label={isGrumpy ? 'Disable grumpy profile image' : 'Enable grumpy profile image'}

Also applies to: 106-108, 129-171

src/components/providers/achievements-provider.tsx (1)

26-27: Harden cookie: add Secure attribute and centralize names

Add Secure to the achievements cookie; consider centralizing STORAGE_KEY and ACHIEVEMENTS_COOKIE_NAME in a constants module to avoid drift with PETS_COOKIE_NAME.

-      document.cookie = `${ACHIEVEMENTS_COOKIE_NAME}=${encodeURIComponent(value)}; path=/; expires=${expires}; samesite=lax`
+      document.cookie = `${ACHIEVEMENTS_COOKIE_NAME}=${encodeURIComponent(value)}; path=/; expires=${expires}; samesite=lax; secure`

Also applies to: 85-93

src/lib/pets.ts (1)

9-9: Drop PETS_COOKIE_NAME or wire it up & centralize cookie names

PETS_COOKIE_NAME is declared but unused (src/lib/pets.ts:9). Either remove the export to avoid dead code, or move cookie names to a shared constants module and implement read/write where needed (see ACHIEVEMENTS_COOKIE_NAME usage in src/components/providers/achievements-provider.tsx and src/app/achievements/page.tsx).

  • If unused: delete the export in src/lib/pets.ts.
  • If intended: move PETS_COOKIE_NAME to a central constants file and implement cookie get/set consistently with ACHIEVEMENTS_COOKIE_NAME.
src/lib/skillicons.ts (1)

3-19: Catalog expansion looks good — callers switched to new utils (verified)

No imports of getSkillIconUrl or resolveSkills from '@/lib/skillicons' remain; all call sites import those from '@/utils/skillicons'. Type-only imports for SkillName/SkillEntry still come from '@/lib/skillicons' (expected). Optional: alphabetize SKILLS keys for maintainability.

src/utils/achievements.ts (1)

29-38: Provide an encoded cookie helper for symmetry with parse.

parse accepts both raw and percent‑encoded; offering an encoded output helper reduces call‑site drift.

Apply:

 export function serializeUnlockedCookie(map: UnlockedMap): string {
@@
   return JSON.stringify(payload)
 }
+
+// Helper for writing to document.cookie safely
+export function encodeUnlockedCookieValue(map: UnlockedMap): string {
+  return encodeURIComponent(serializeUnlockedCookie(map))
+}
src/lib/achievements.ts (1)

8-8: Remove TODO; make this the source of truth.

This constant is referenced from utils; the comment is misleading.

-export const ACHIEVEMENTS_COOKIE_NAME = 'kil.dev_achievements_v1' // TODO: Find this in the code
+export const ACHIEVEMENTS_COOKIE_NAME = 'kil.dev_achievements_v1' // Source of truth for the achievements cookie key
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c210be3 and 6f70042.

📒 Files selected for processing (76)
  • .gitignore (1 hunks)
  • .storybook/docs-container.tsx (1 hunks)
  • eslint.config.js (1 hunks)
  • scripts/build-presence-runtime.ts (1 hunks)
  • scripts/build-theme-runtime.ts (1 hunks)
  • src/app/achievements/page.tsx (1 hunks)
  • src/app/layout.tsx (1 hunks)
  • src/components/layout/about/aboutme/mode-toggle-link.tsx (1 hunks)
  • src/components/layout/about/aboutme/quick-fact.tsx (1 hunks)
  • src/components/layout/about/pets/pet-card/_content.tsx (1 hunks)
  • src/components/layout/about/pets/pet-card/card-back.tsx (1 hunks)
  • src/components/layout/experience/skills/_content.tsx (1 hunks)
  • src/components/layout/experience/work-history/collapsible-highlights.tsx (1 hunks)
  • src/components/layout/experience/work-history/company-marker.tsx (1 hunks)
  • src/components/layout/experience/work-history/skills-block.tsx (1 hunks)
  • src/components/layout/experience/work-history/work-history-item.tsx (1 hunks)
  • src/components/layout/header/mobile-nav-button.tsx (1 hunks)
  • src/components/layout/header/mobile-nav.tsx (1 hunks)
  • src/components/layout/header/nav-lava.tsx (1 hunks)
  • src/components/layout/home/hero/hero-content.tsx (1 hunks)
  • src/components/layout/home/hero/profile-image.tsx (2 hunks)
  • src/components/layout/pet-gallery/_content.tsx (1 hunks)
  • src/components/layout/projects/project-card/_content.tsx (1 hunks)
  • src/components/layout/projects/project-card/card-back.tsx (1 hunks)
  • src/components/layout/projects/project-card/tech-icons.tsx (1 hunks)
  • src/components/layout/projects/projects-grid.tsx (1 hunks)
  • src/components/providers/achievements-provider.tsx (1 hunks)
  • src/components/providers/providers.tsx (1 hunks)
  • src/components/providers/theme-provider.tsx (1 hunks)
  • src/components/ui/alert-dialog.tsx (1 hunks)
  • src/components/ui/bottom-drawer.tsx (1 hunks)
  • src/components/ui/button.tsx (1 hunks)
  • src/components/ui/card.tsx (1 hunks)
  • src/components/ui/drawer.tsx (1 hunks)
  • src/components/ui/dropdown-menu.tsx (1 hunks)
  • src/components/ui/flip-indicator.tsx (1 hunks)
  • src/components/ui/flipping-card.tsx (1 hunks)
  • src/components/ui/link-button.tsx (1 hunks)
  • src/components/ui/map-tooltip.tsx (2 hunks)
  • src/components/ui/navigation-menu.tsx (1 hunks)
  • src/components/ui/scroll-area.tsx (1 hunks)
  • src/components/ui/section-label.tsx (1 hunks)
  • src/components/ui/separator.tsx (1 hunks)
  • src/components/ui/skill-icons.stories.tsx (1 hunks)
  • src/components/ui/skill-icons.tsx (1 hunks)
  • src/components/ui/theme-toggle.tsx (1 hunks)
  • src/components/ui/tooltip.tsx (1 hunks)
  • src/lib/achievements.ts (1 hunks)
  • src/lib/content.ts (1 hunks)
  • src/lib/experience.ts (1 hunks)
  • src/lib/light-grid.ts (1 hunks)
  • src/lib/navmenu.ts (1 hunks)
  • src/lib/pets.ts (1 hunks)
  • src/lib/projects.ts (1 hunks)
  • src/lib/quickfacts.ts (1 hunks)
  • src/lib/skillicons.ts (1 hunks)
  • src/lib/social-links.ts (1 hunks)
  • src/lib/themes.ts (1 hunks)
  • src/types/achievements.d.ts (1 hunks)
  • src/types/home-content.d.ts (1 hunks)
  • src/types/light-grid.d.ts (1 hunks)
  • src/types/navigation-item.d.ts (1 hunks)
  • src/types/pets.d.ts (1 hunks)
  • src/types/presence-bundle.d.ts (1 hunks)
  • src/types/projects.d.ts (1 hunks)
  • src/types/quick-facts.d.ts (1 hunks)
  • src/types/skillicons.d.ts (1 hunks)
  • src/types/social-links.d.ts (1 hunks)
  • src/types/theme-bundle.d.ts (1 hunks)
  • src/types/themes.d.ts (1 hunks)
  • src/types/work-experience.d.ts (1 hunks)
  • src/utils/achievements.ts (1 hunks)
  • src/utils/skillicons.ts (1 hunks)
  • src/utils/theme-css.ts (1 hunks)
  • src/utils/theme-runtime.ts (1 hunks)
  • src/utils/themes.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx,cjs,mjs}

📄 CodeRabbit inference engine (.cursor/rules/posthog-integration.mdc)

**/*.{ts,tsx,js,jsx,cjs,mjs}: Never hardcode or hallucinate the PostHog API key; always read it from the value populated in the .env file
Create new feature flag names that are clear and descriptive
Gate flag-dependent code on a check that verifies the flag’s values are valid and expected
If a custom property for a person or event is referenced in two or more files or in two or more callsites within the same file, centralize the property name in an enum (TS) or const object (JS)

Files:

  • src/app/layout.tsx
  • src/components/layout/experience/skills/_content.tsx
  • scripts/build-presence-runtime.ts
  • src/types/navigation-item.d.ts
  • src/components/layout/header/nav-lava.tsx
  • src/components/ui/alert-dialog.tsx
  • src/components/ui/section-label.tsx
  • src/components/layout/projects/project-card/_content.tsx
  • src/components/ui/skill-icons.tsx
  • src/types/social-links.d.ts
  • src/components/layout/about/aboutme/quick-fact.tsx
  • src/utils/theme-css.ts
  • src/components/ui/card.tsx
  • src/components/providers/theme-provider.tsx
  • src/types/theme-bundle.d.ts
  • src/components/ui/button.tsx
  • src/components/layout/about/aboutme/mode-toggle-link.tsx
  • src/types/themes.d.ts
  • src/components/ui/link-button.tsx
  • src/types/projects.d.ts
  • src/types/achievements.d.ts
  • src/components/ui/navigation-menu.tsx
  • src/types/home-content.d.ts
  • src/types/work-experience.d.ts
  • src/components/ui/tooltip.tsx
  • src/types/light-grid.d.ts
  • src/components/ui/flip-indicator.tsx
  • src/types/presence-bundle.d.ts
  • src/components/ui/scroll-area.tsx
  • src/components/layout/experience/work-history/company-marker.tsx
  • src/components/layout/home/hero/hero-content.tsx
  • src/components/layout/pet-gallery/_content.tsx
  • src/components/ui/flipping-card.tsx
  • src/components/layout/header/mobile-nav-button.tsx
  • src/components/layout/projects/project-card/card-back.tsx
  • src/types/skillicons.d.ts
  • src/components/ui/bottom-drawer.tsx
  • src/utils/achievements.ts
  • src/components/layout/projects/projects-grid.tsx
  • src/components/layout/header/mobile-nav.tsx
  • src/utils/skillicons.ts
  • src/lib/experience.ts
  • src/types/quick-facts.d.ts
  • src/components/ui/separator.tsx
  • eslint.config.js
  • src/components/ui/map-tooltip.tsx
  • src/components/layout/projects/project-card/tech-icons.tsx
  • src/components/layout/experience/work-history/skills-block.tsx
  • src/components/layout/experience/work-history/work-history-item.tsx
  • src/components/layout/about/pets/pet-card/card-back.tsx
  • src/lib/content.ts
  • src/components/layout/experience/work-history/collapsible-highlights.tsx
  • src/components/layout/about/pets/pet-card/_content.tsx
  • src/types/pets.d.ts
  • src/app/achievements/page.tsx
  • src/components/ui/skill-icons.stories.tsx
  • src/utils/themes.ts
  • src/lib/navmenu.ts
  • src/components/ui/dropdown-menu.tsx
  • src/components/providers/providers.tsx
  • src/lib/social-links.ts
  • src/components/layout/home/hero/profile-image.tsx
  • src/utils/theme-runtime.ts
  • src/lib/projects.ts
  • src/components/ui/theme-toggle.tsx
  • src/lib/pets.ts
  • src/lib/light-grid.ts
  • src/lib/quickfacts.ts
  • src/lib/themes.ts
  • src/lib/skillicons.ts
  • src/components/ui/drawer.tsx
  • scripts/build-theme-runtime.ts
  • src/components/providers/achievements-provider.tsx
  • src/lib/achievements.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/posthog-integration.mdc)

In TypeScript, store feature flag names in an enum with members written in UPPERCASE_WITH_UNDERSCORE and use a consistent naming convention

Files:

  • src/app/layout.tsx
  • src/components/layout/experience/skills/_content.tsx
  • scripts/build-presence-runtime.ts
  • src/types/navigation-item.d.ts
  • src/components/layout/header/nav-lava.tsx
  • src/components/ui/alert-dialog.tsx
  • src/components/ui/section-label.tsx
  • src/components/layout/projects/project-card/_content.tsx
  • src/components/ui/skill-icons.tsx
  • src/types/social-links.d.ts
  • src/components/layout/about/aboutme/quick-fact.tsx
  • src/utils/theme-css.ts
  • src/components/ui/card.tsx
  • src/components/providers/theme-provider.tsx
  • src/types/theme-bundle.d.ts
  • src/components/ui/button.tsx
  • src/components/layout/about/aboutme/mode-toggle-link.tsx
  • src/types/themes.d.ts
  • src/components/ui/link-button.tsx
  • src/types/projects.d.ts
  • src/types/achievements.d.ts
  • src/components/ui/navigation-menu.tsx
  • src/types/home-content.d.ts
  • src/types/work-experience.d.ts
  • src/components/ui/tooltip.tsx
  • src/types/light-grid.d.ts
  • src/components/ui/flip-indicator.tsx
  • src/types/presence-bundle.d.ts
  • src/components/ui/scroll-area.tsx
  • src/components/layout/experience/work-history/company-marker.tsx
  • src/components/layout/home/hero/hero-content.tsx
  • src/components/layout/pet-gallery/_content.tsx
  • src/components/ui/flipping-card.tsx
  • src/components/layout/header/mobile-nav-button.tsx
  • src/components/layout/projects/project-card/card-back.tsx
  • src/types/skillicons.d.ts
  • src/components/ui/bottom-drawer.tsx
  • src/utils/achievements.ts
  • src/components/layout/projects/projects-grid.tsx
  • src/components/layout/header/mobile-nav.tsx
  • src/utils/skillicons.ts
  • src/lib/experience.ts
  • src/types/quick-facts.d.ts
  • src/components/ui/separator.tsx
  • src/components/ui/map-tooltip.tsx
  • src/components/layout/projects/project-card/tech-icons.tsx
  • src/components/layout/experience/work-history/skills-block.tsx
  • src/components/layout/experience/work-history/work-history-item.tsx
  • src/components/layout/about/pets/pet-card/card-back.tsx
  • src/lib/content.ts
  • src/components/layout/experience/work-history/collapsible-highlights.tsx
  • src/components/layout/about/pets/pet-card/_content.tsx
  • src/types/pets.d.ts
  • src/app/achievements/page.tsx
  • src/components/ui/skill-icons.stories.tsx
  • src/utils/themes.ts
  • src/lib/navmenu.ts
  • src/components/ui/dropdown-menu.tsx
  • src/components/providers/providers.tsx
  • src/lib/social-links.ts
  • src/components/layout/home/hero/profile-image.tsx
  • src/utils/theme-runtime.ts
  • src/lib/projects.ts
  • src/components/ui/theme-toggle.tsx
  • src/lib/pets.ts
  • src/lib/light-grid.ts
  • src/lib/quickfacts.ts
  • src/lib/themes.ts
  • src/lib/skillicons.ts
  • src/components/ui/drawer.tsx
  • scripts/build-theme-runtime.ts
  • src/components/providers/achievements-provider.tsx
  • src/lib/achievements.ts
**/*.{js,jsx,cjs,mjs}

📄 CodeRabbit inference engine (.cursor/rules/posthog-integration.mdc)

In JavaScript, store feature flag names as string values in a const object (simulating an enum) with members written in UPPERCASE_WITH_UNDERSCORE and use a consistent naming convention

Files:

  • eslint.config.js
🧬 Code graph analysis (12)
src/types/navigation-item.d.ts (1)
src/types/themes.d.ts (1)
  • IconComponent (6-6)
src/types/projects.d.ts (1)
src/lib/skillicons.ts (1)
  • SkillName (21-21)
src/types/work-experience.d.ts (1)
src/lib/skillicons.ts (1)
  • SkillName (21-21)
src/components/layout/home/hero/hero-content.tsx (1)
src/lib/content.ts (1)
  • HOME_CONTENT (3-9)
src/utils/achievements.ts (2)
src/lib/achievements.ts (3)
  • AchievementId (96-96)
  • ACHIEVEMENTS (10-95)
  • ACHIEVEMENTS_COOKIE_NAME (8-8)
src/types/presence-bundle.d.ts (1)
  • PRESENCE_RUNTIME_BUNDLE (2-2)
src/utils/skillicons.ts (2)
src/types/skillicons.d.ts (2)
  • SkillIconRef (5-11)
  • DashboardIconFormat (3-3)
src/lib/skillicons.ts (3)
  • SkillName (21-21)
  • SkillEntry (23-23)
  • SKILLS (3-19)
src/types/quick-facts.d.ts (1)
src/components/layout/about/aboutme/quick-fact.tsx (1)
  • QuickFact (9-39)
src/components/ui/map-tooltip.tsx (1)
src/lib/content.ts (1)
  • HOME_CONTENT (3-9)
src/lib/content.ts (1)
src/types/home-content.d.ts (1)
  • HomeContent (1-7)
src/utils/themes.ts (2)
src/lib/themes.ts (5)
  • Theme (50-50)
  • ThemeName (49-49)
  • ThemeEntry (48-48)
  • themes (8-46)
  • KNOWN_THEMES (51-51)
src/types/themes.d.ts (2)
  • IconComponent (6-6)
  • BaseColor (4-4)
src/lib/social-links.ts (1)
src/types/social-links.d.ts (1)
  • SocialLinks (3-6)
src/components/layout/home/hero/profile-image.tsx (1)
src/lib/content.ts (1)
  • HOME_CONTENT (3-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (56)
src/lib/light-grid.ts (1)

1-1: Approve — type-only import OK; tsconfig alias present.
tsconfig.json contains "baseUrl": "." and "paths": { "@/": ["./src/"] } so '@/types/light-grid' should resolve to ./src/types/light-grid; ripgrep in the sandbox didn't scan repo files, so confirm src/types/light-grid exports type LightGrid and run build/tests to validate alias resolution.

src/components/ui/dropdown-menu.tsx (1)

7-7: LGTM; ensure utils remains client-safe.

This is a client component; confirm @/utils/utils has no server-only imports (fs/path/process.env) to keep it tree-shakeable in the browser bundle.

src/components/ui/section-label.tsx (1)

1-1: LGTM.

Import path realignment matches the refactor; no behavior change.

src/components/ui/drawer.tsx (1)

6-6: LGTM; client-safety check.

As a client file, it shares the same caveat: ensure @/utils/utils has no Node-only deps.

src/components/layout/header/nav-lava.tsx (1)

9-9: LGTM.

Path update for cn aligns with repo-wide move. No functional changes.

src/components/ui/scroll-area.tsx (1)

6-6: LGTM.

Import path update only; component behavior unchanged.

src/components/ui/tooltip.tsx (1)

6-6: LGTM.

Consistent with the utils relocation; no additional concerns.

src/components/layout/pet-gallery/_content.tsx (1)

2-2: Import path update OK — rerun verification to confirm no leftovers

Multiple files (e.g., src/components/layout/pet-gallery/_content.tsx) now import { cn } from '@/utils/utils' and src/utils/utils.* exports cn; however ripgrep skipped files on follow-up runs and tsconfig.json failed to parse, so I cannot conclusively rule out lingering '@/lib/utils' imports or verify the '@/*' path alias. Re-run the verification script with ripgrep scanning all files (or fix tsconfig.json) to finalize.

src/components/ui/flip-indicator.tsx (1)

3-3: LGTM: cn import path updated consistently with refactor.

src/components/ui/separator.tsx (1)

6-6: LGTM: cn import path updated; no behavior changes.

src/components/ui/bottom-drawer.tsx (1)

15-15: LGTM: cn import path updated to '@/utils/utils'.

src/components/ui/flipping-card.tsx (1)

3-3: LGTM: cn import path updated; component behavior unchanged.

src/components/ui/alert-dialog.tsx (1)

7-7: LGTM: cn import path refactor only.

src/components/ui/card.tsx (1)

3-3: LGTM: cn import path updated; no API changes.

src/components/ui/button.tsx (1)

5-5: Import path swap OK — tsconfig alias missing; verify mapping

  • src/utils/utils.ts:4 exports cn.
  • tsconfig*.json: no "@/" → "src/" path mapping found — add the paths mapping or ensure your build config (Next/webpack/tsconfig-paths) supports the '@/…' alias.
  • No remaining imports from '@/lib/utils' found.
.gitignore (1)

4-5: Ignore paths updated and verified

Build scripts emit to src/utils/{theme,presence}-bundle.ts (scripts/build-theme-runtime.ts:13, scripts/build-presence-runtime.ts:13) and ESLint already ignores those files (ignores list includes 'src/utils/theme-bundle.ts' and 'src/utils/presence-bundle.ts' at line 12).

src/utils/themes.ts (1)

8-19: LGTM on theme helpers.

Functions are pure, handle 'system' branch correctly, and have sensible fallbacks.

src/types/achievements.d.ts (1)

3-12: Don't add a redundant enum — use ACHIEVEMENTS / AchievementId.

src/lib/achievements.ts already centralizes keys (export const ACHIEVEMENTS and export type AchievementId = keyof typeof ACHIEVEMENTS). Replace string-literal usages (e.g., src/components/providers/achievements-provider.tsx, src/components/layout/header/mobile-nav.tsx, src/app/layout.tsx, src/components/layout/about/pets/_content.tsx) with the existing typed keys or export a tiny const key map — do not introduce ACHIEVEMENT_KEY enum.

Likely an incorrect or invalid review comment.

src/types/pets.d.ts (1)

3-12: Tighten Pet field types and mark fields readonly — verify usages before narrowing gender

src/types/pets.d.ts currently defines gender: string; unable to confirm repo-wide usages with the provided search — verify across the repo before changing the union.

 export interface Pet {
-  id: string
-  name: string
-  breed: string
-  birthday: string
-  gender: string
-  description: string
-  image: StaticImageData
-  imageAlt: string
+  readonly id: string
+  readonly name: string
+  readonly breed: string
+  // e.g. 2023-07-14
+  readonly birthday: `${number}-${number}-${number}`
+  readonly gender: 'male' | 'female' | 'unknown'
+  readonly description: string
+  readonly image: StaticImageData
+  readonly imageAlt: string
 }
src/utils/theme-css.ts (1)

1-1: Import path change looks good.
No functional impact; aligns with the refactor.

src/components/layout/experience/skills/_content.tsx (1)

4-4: Path update looks correct.
Matches utils relocation; usage unchanged.

src/components/providers/theme-provider.tsx (1)

4-4: Import path change is correct.
No functional change; aligns with utils move.

src/components/layout/projects/project-card/_content.tsx (1)

5-5: Type import path update looks good.
Consistent with centralized types.

src/components/layout/about/aboutme/quick-fact.tsx (1)

1-1: Type import path update looks good.
Matches types relocation.

src/components/ui/navigation-menu.tsx (1)

6-6: cn import path update looks correct.
No behavior change.

scripts/build-presence-runtime.ts (1)

12-13: Approve path changes; types & .gitignore OK — import usage unverified

  • src/types/presence-bundle.d.ts correctly declares module '@/utils/presence-bundle' and exports PRESENCE_RUNTIME_BUNDLE.
  • .gitignore contains /src/utils/presence-bundle.ts.
  • src/utils/presence-bundle.ts is not committed (expected for a generated file).
  • No importers were found (ripgrep returned "No files were searched" / no matches) — inconclusive. Confirm import usage by running: git grep -n "@/utils/presence-bundle" || true
src/components/layout/projects/projects-grid.tsx (1)

1-1: Type import path update looks good.
This aligns with the PR’s types centralization. No behavior change.

src/components/layout/about/pets/pet-card/card-back.tsx (1)

5-6: Consistent module boundaries.
Type and utility imports correctly point to /types and /utils.

src/components/layout/experience/work-history/company-marker.tsx (1)

4-4: Type import path update looks good.
Matches the centralized WorkExperience type under /types.

src/components/ui/map-tooltip.tsx (1)

12-12: CONTENT → HOME_CONTENT migration looks correct.
Defaults now come from HOME_CONTENT; no behavior change otherwise.

Also applies to: 22-24

scripts/build-theme-runtime.ts (1)

12-13: Entry/output path migration verified — confirm watcher exclusions.

  • No stale imports of '@/lib/theme-bundle' detected.
  • Ambient declaration present: src/types/theme-bundle.d.ts.
  • .gitignore contains /src/utils/theme-bundle.ts; ensure build/watch tools actually ignore src/utils/theme-bundle.ts (add to tsconfig.json exclude, .eslintignore, Vite/Rollup watcher ignore settings, nodemon/watch scripts, etc.).
src/components/layout/header/mobile-nav.tsx (1)

8-8: Approve — cn import path update looks good; sweep couldn't be completed

Matches the utils migration; no runtime changes. Ripgrep returned "No files were searched." Re-run from the repository root to confirm there are no remaining "@/lib/utils" imports:

rg --hidden --no-ignore -nP "from\s+['\"]@/lib/utils['\"]" -S || true
src/components/providers/providers.tsx (1)

4-4: Import move looks good

Type-only import now sourced from utils; external API unchanged.

src/components/layout/about/aboutme/mode-toggle-link.tsx (1)

8-11: Import path updates: LGTM

Utilities consolidated under /utils; no behavior change.

src/components/layout/experience/work-history/work-history-item.tsx (1)

1-3: Type/util import relocations: LGTM

Imports now align with src/types and src/utils without changing behavior.

src/components/layout/about/pets/pet-card/_content.tsx (1)

5-5: Type import relocation: LGTM

Switch to src/types/pets maintains type boundaries cleanly.

src/lib/experience.ts (1)

4-4: Type import centralization: LGTM

Moving WorkExperience/SkillCategory to src/types reduces duplication.

src/components/layout/projects/project-card/card-back.tsx (1)

4-5: Imports shifted to /types and /utils — looks consistent.

No functional changes; import sources match the repo reorg.

src/components/layout/home/hero/hero-content.tsx (1)

3-3: Renamed CONTENT → HOME_CONTENT verified

No remaining CONTENT references (search returned none); import updated and no behavior change.

src/app/achievements/page.tsx (1)

5-6: Import split OK — no remaining parseUnlockedCookie imports from '@/lib/achievements'.

parseUnlockedCookie is defined in src/utils/achievements.ts and only imported in src/app/achievements/page.tsx.

src/components/ui/skill-icons.stories.tsx (1)

4-5: Keep SkillName import from '@/lib/skillicons' — it's defined there.

src/lib/skillicons.ts exports export type SkillName = keyof typeof SKILLS (line 21); src/types/skillicons.d.ts does not export SkillName. Do not change to '@/types/skillicons' unless you move the type and update all usages.

Likely an incorrect or invalid review comment.

src/types/presence-bundle.d.ts (1)

1-3: Verify generated runtime export and .gitignore

  • No stale imports of '@/lib/presence-bundle'; consumer found at src/utils/achievements.ts:2 imports from '@/utils/presence-bundle'.
  • No src/**/presence-bundle.ts found — cannot confirm PRESENCE_RUNTIME_BUNDLE is exported at runtime.
  • Ensure the generated runtime file exists and exports PRESENCE_RUNTIME_BUNDLE (or update the declaration to match). Either add src/utils/presence-bundle.ts to .gitignore if it’s generated, or commit the generated file so the ambient declaration is accurate.
src/types/theme-bundle.d.ts (1)

1-3: Theme bundle ambient module relocation LGTM — confirm export shape.

Change aligns with the utils move; sandbox verification failed due to script errors. Manually confirm src/utils/theme-bundle exports a named const THEME_RUNTIME_BUNDLE (export const THEME_RUNTIME_BUNDLE) and that no imports from '@/lib/theme-bundle' remain — update any leftovers to '@/utils/theme-bundle'.

src/utils/theme-runtime.ts (2)

83-91: Good bundling approach; safe config serialization.

Appending a pre-minified IIFE and JSON-stringified config is sound. The leading semicolon guards concat issues, and try/catch is fine here.


57-61: No change required — Theme already includes 'system'.

Theme is defined as ThemeName | 'system' in src/lib/themes.ts (line 50), so getAvailableThemes()'s return is consistent.

src/utils/skillicons.ts (1)

20-22: LGTM.

resolveSkills correctly preserves typing with SkillName and SKILLS.

src/lib/navmenu.ts (1)

1-9: LGTM — icon type compatibility confirmed

NavigationItem.icon is IconComponent = ComponentType<{ className?: string }> (src/types/themes.d.ts); the lucide-react icons used in src/lib/navmenu.ts conform to that type — no changes required.

src/lib/pets.ts (1)

7-7: Centralized Pet type import — LGTM

Type-only import from '@/types/pets' matches the PR goals.

src/components/providers/achievements-provider.tsx (1)

3-4: Refactor: achievements/theme utils moved — LGTM

Type-only imports and utility relocation improve boundaries without behavior change.

Also applies to: 10-12

src/lib/skillicons.ts (1)

1-1: Type-only import — LGTM

src/components/ui/theme-toggle.tsx (2)

7-14: LGTM — helpers moved to '@/utils'; no stragglers found

Repo search shows theme helpers are imported from '@/utils/*'; only runtime data/types remain under '@/lib/themes'.


7-14: Analytics: PostHog key env-sourced and capture calls are gated — verified

NEXT_PUBLIC_POSTHOG_KEY is used (see .env.example); posthog.init is only called when posthogKey is present and not in dev (src/components/providers/posthog-provider.tsx); captureThemeChanged and all capture* functions early-return when analytics is disabled (src/hooks/posthog.ts); no hardcoded PostHog API key found. Files: src/components/providers/posthog-provider.tsx, src/hooks/posthog.ts, src/lib/posthog.ts, .env.example.

src/lib/content.ts (1)

1-9: Rename verified — all imports updated to HOME_CONTENT

No matches for CONTENT; HOME_CONTENT is defined at src/lib/content.ts and imported in:

  • src/components/layout/home/hero/hero-content.tsx
  • src/components/layout/home/hero/profile-image.tsx
  • src/components/ui/map-tooltip.tsx
src/utils/achievements.ts (2)

14-27: Cookie parse path is defensive and correct.

Graceful handling of undefined, percent-decoding, JSON parse, and sanitization looks good.


61-67: Guarded runtime init — verify bundle exposes window.PresenceRuntime

Stringifying cfg prevents injection and the try-guard is fine; repo search found no occurrences of PresenceRuntime. Confirm the bundled script actually sets window.PresenceRuntime or add an explicit fallback. Location: src/utils/achievements.ts:61-67

src/lib/themes.ts (1)

5-5: Externalizing ThemeConfig type — LGTM; satisfies supported.
package.json declares "typescript": "5.9.2", so the satisfies operator (TS ≥ 4.9) is available.

@@ -1,5 +1,5 @@
import { SkillIcons } from '@/components/ui/skill-icons'
import type { resolveSkills } from '@/lib/skillicons'
import type { resolveSkills } from '@/utils/skillicons'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Type-only import of a value breaks ReturnType<typeof …>; switch to a concrete type or a value import.
import type { resolveSkills } … elides the value at runtime; typeof resolveSkills then fails to compile. Prefer typing the prop as the concrete array type.

Apply this diff (preferred, assuming SkillEntry[] is the expected prop for ):

-import type { resolveSkills } from '@/utils/skillicons'
+import type { SkillEntry } from '@/types/skillicons'
@@
 }: {
   highlightCount: number
-  skillsEntries: ReturnType<typeof resolveSkills>
+  skillsEntries: SkillEntry[]
 }) {

Alternative (if you must derive from the function): import the value instead of a type-only import:

-import type { resolveSkills } from '@/utils/skillicons'
+import { resolveSkills } from '@/utils/skillicons'

Confirm which type expects and whether SkillEntry exists under /types:

Also applies to: 9-10


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "SkillIcons signature:"
rg -nP "export\s+function\s+SkillIcons\(" src/components | sed -n '1,200p'
echo
echo "SkillEntry type availability:"
rg -nP 'export\s+(?:type|interface)\s+SkillEntry\b' src/types || true

Length of output: 118


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== SkillIcons export location ==="
rg -nP "export\s+function\s+SkillIcons\(" src || true

echo
echo "=== src/components/ui/skill-icons.tsx (head) ==="
sed -n '1,240p' src/components/ui/skill-icons.tsx || true

echo
echo "=== src/components/layout/experience/work-history/skills-block.tsx (head) ==="
sed -n '1,240p' src/components/layout/experience/work-history/skills-block.tsx || true

echo
echo "=== Files referencing resolveSkills or skillicons ==="
rg -n --hidden "resolveSkills|skillicons" src || true

echo
echo "=== Show file that defines resolveSkills (first match) ==="
file=$(rg -n --hidden "resolveSkills" src -S | sed -n '1p' | cut -d: -f1 || true)
if [ -n "$file" ]; then
  echo "File: $file"
  sed -n '1,240p' "$file" || true
else
  echo "resolveSkills not found"
fi

echo
echo "=== Search for SkillEntry across repo ==="
rg -n --hidden "\\bSkillEntry\\b" src || true

Length of output: 8801


Replace the type-only import — use SkillEntry[] or import the runtime resolveSkills

import type { resolveSkills } elides the runtime function, so ReturnType won't compile. Use the exported SkillEntry type (from src/lib/skillicons) or import the function value instead.

-import type { resolveSkills } from '@/utils/skillicons'
+import type { SkillEntry } from '@/lib/skillicons'
@@
 }: {
   highlightCount: number
-  skillsEntries: ReturnType<typeof resolveSkills>
+  skillsEntries: SkillEntry[]
 }) {

Alternative (if you need to derive the type from the function):

-import type { resolveSkills } from '@/utils/skillicons'
+import { resolveSkills } from '@/utils/skillicons'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import type { resolveSkills } from '@/utils/skillicons'
import type { SkillEntry } from '@/lib/skillicons'
}: {
highlightCount: number
skillsEntries: SkillEntry[]
}) {
🤖 Prompt for AI Agents
In src/components/layout/experience/work-history/skills-block.tsx around line 2,
the import uses "import type { resolveSkills }" which elides the runtime symbol
so using ReturnType<typeof resolveSkills> fails to compile; replace the
type-only import with either a proper type import (e.g., import { SkillEntry }
from 'src/lib/skillicons' and use SkillEntry[] or the SkillEntry type where
needed) or import the runtime function (import { resolveSkills } from
'src/lib/skillicons') if you need to derive types via ReturnType<typeof
resolveSkills>; update usages accordingly.

Comment on lines 4 to 6
import type { SkillEntry } from '@/lib/skillicons'
import { getSkillIconUrl } from '@/lib/skillicons'
import { getSkillIconUrl } from '@/utils/skillicons'
import Image from 'next/image'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Fix stale type import path for SkillEntry (lib → types).
Utilities moved to /utils and types to /types; keeping the type from '@/lib/skillicons' will break when /lib export disappears.

Apply this diff (if SkillEntry is still the name under /types):

-import type { SkillEntry } from '@/lib/skillicons'
+import type { SkillEntry } from '@/types/skillicons'

If the type was renamed (e.g., SkillInfo), import the new name from '@/types/skillicons' and adjust locally.

Run to confirm the exported type name and find any remaining stale imports:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "Skillicons types file:"
fd -t f "skillicons.d.ts" src/types | xargs -I{} sh -c 'echo "--- {} ---"; sed -n "1,200p" "{}" | sed -n "1,120p"'
echo
echo "Definitions of SkillEntry/SkillInfo:"
rg -nP 'export\s+(?:type|interface)\s+(SkillEntry|SkillInfo)\b' src/types || true
echo
echo "Stale imports from @/lib/skillicons:"
rg -nP "from ['\"]@/lib/skillicons['\"]" -g '!**/node_modules/**' || true

Length of output: 627


Replace stale type import with SkillInfo from '@/types/skillicons'

Type was renamed to SkillInfo and moved to src/types/skillicons.d.ts — update the import and local references.

File: src/components/layout/projects/project-card/tech-icons.tsx (top imports)

-import type { SkillEntry } from '@/lib/skillicons'
+import type { SkillInfo } from '@/types/skillicons'
  • Run a repo-wide search for imports from '@/lib/skillicons' and update remaining occurrences/usages to the new path/name.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import type { SkillEntry } from '@/lib/skillicons'
import { getSkillIconUrl } from '@/lib/skillicons'
import { getSkillIconUrl } from '@/utils/skillicons'
import Image from 'next/image'
import type { SkillInfo } from '@/types/skillicons'
import { getSkillIconUrl } from '@/utils/skillicons'
import Image from 'next/image'
🤖 Prompt for AI Agents
In src/components/layout/projects/project-card/tech-icons.tsx lines 4-6, replace
the stale type import "SkillEntry" from '@/lib/skillicons' with the renamed type
"SkillInfo" from '@/types/skillicons' and update any local
references/annotations in this file from SkillEntry to SkillInfo; then run a
repo-wide search for imports from '@/lib/skillicons' and change remaining
occurrences to import the new name and path (SkillInfo from
'@/types/skillicons'), updating usage sites to the new identifier as needed.

imageSrc: StaticImageData
imageAlt: string
}
import type { Project } from '@/types/projects'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Type mismatch with external URLs via Project type.

This data includes absolute href/repo values; ensure the Project type changes suggested in src/types/projects.d.ts are applied to prevent TS errors.

Also applies to: 8-70


🏁 Script executed:

#!/bin/bash
# Type-check hot spots (static scan)
rg -nP "href:\s*'https?://|repo:\s*'https?://" src/lib/projects.ts

Length of output: 248


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== src/types/projects.d.ts ==="
if [ -f src/types/projects.d.ts ]; then
  nl -ba src/types/projects.d.ts
else
  echo "MISSING: src/types/projects.d.ts"
fi

echo
echo "=== src/lib/projects.ts (first 300 lines) ==="
if [ -f src/lib/projects.ts ]; then
  nl -ba src/lib/projects.ts | sed -n '1,300p'
else
  echo "MISSING: src/lib/projects.ts"
fi

echo
echo "=== Search for Project type definitions ==="
rg -n --hidden --no-ignore-vcs '^(type|interface)\s+Project\b' || true

echo
echo "=== Search for 'Project' usages under src/ ==="
rg -n --hidden --no-ignore-vcs '\bProject\b' src || true

Length of output: 72


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== src/types/projects.d.ts ==="
if [ -f src/types/projects.d.ts ]; then
  sed -n '1,240p' src/types/projects.d.ts
else
  echo "MISSING: src/types/projects.d.ts"
fi

echo
echo "=== src/lib/projects.ts (first 400 lines) ==="
if [ -f src/lib/projects.ts ]; then
  sed -n '1,400p' src/lib/projects.ts
else
  echo "MISSING: src/lib/projects.ts"
fi

echo
echo "=== Look for Project type definitions ==="
rg -n --hidden --no-ignore-vcs '^(export\s+)?(type|interface)\s+Project\b' || true

echo
echo "=== Search for Project usages under src/ ==="
rg -n --hidden --no-ignore-vcs '\bProject\b' src || true

Length of output: 3672


Allow external URLs for Project.href/repo

Project entries include absolute URLs (e.g. "https://...") but src/types/projects.d.ts types href and repo as Route — update both to accept external URLs to avoid TS errors.

Location: src/types/projects.d.ts — change
href?: Route
repo?: Route
to
href?: Route | string | URL
repo?: Route | string | URL

🤖 Prompt for AI Agents
In src/lib/projects.ts around line 6 (but actually update
src/types/projects.d.ts), the Project type currently restricts href and repo to
Route which fails for absolute external URLs; change the declarations in
src/types/projects.d.ts from href?: Route and repo?: Route to href?: Route |
string | URL and repo?: Route | string | URL so both internal routes and
external absolute URLs are accepted, then run TypeScript build/check to ensure
no remaining type errors.

Comment on lines +1 to +3
import type { SkillName } from '@/lib/skillicons'
import type { Route } from 'next'
import type { StaticImageData } from 'next/image'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Route type is too restrictive for external URLs (compile-time break).

Several entries in src/lib/projects.ts use absolute URLs (https://…), which don’t satisfy Next.js Route. Make href/repo accept external URLs.

-import type { Route } from 'next'
+import type { Route } from 'next'
+
+type ExternalUrl = `http://${string}` | `https://${string}`
 
 export interface Project {
   id: string
   title: string
   description: string
   tags: SkillName[]
-  href?: Route
-  repo?: Route
+  href?: Route | ExternalUrl
+  repo?: ExternalUrl
   year?: number
   status?: 'live' | 'wip' | 'archived'
   imageSrc: StaticImageData
   imageAlt: string
 }

Also applies to: 10-15


🏁 Script executed:

#!/bin/bash
# Show offending values that are absolute URLs
rg -nP -C2 "href:\s*'https?://|repo:\s*'https?://" src/lib/projects.ts

Length of output: 872


Allow external URLs for project href/repo

Several entries in src/lib/projects.ts use absolute URLs (repo/href) which are not assignable to Next's Route — widen the Project types to accept external URLs.

File: src/types/projects.d.ts

-import type { Route } from 'next'
+import type { Route } from 'next'
+
+type ExternalUrl = `http://${string}` | `https://${string}`
 
 export interface Project {
   id: string
   title: string
   description: string
   tags: SkillName[]
-  href?: Route
-  repo?: Route
+  href?: Route | ExternalUrl
+  repo?: Route | ExternalUrl
   year?: number
   status?: 'live' | 'wip' | 'archived'
   imageSrc: StaticImageData
   imageAlt: string
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import type { SkillName } from '@/lib/skillicons'
import type { Route } from 'next'
import type { StaticImageData } from 'next/image'
import type { SkillName } from '@/lib/skillicons'
import type { Route } from 'next'
import type { StaticImageData } from 'next/image'
type ExternalUrl = `http://${string}` | `https://${string}`
export interface Project {
id: string
title: string
description: string
tags: SkillName[]
href?: Route | ExternalUrl
repo?: Route | ExternalUrl
year?: number
status?: 'live' | 'wip' | 'archived'
imageSrc: StaticImageData
imageAlt: string
}
🤖 Prompt for AI Agents
In src/types/projects.d.ts around lines 1 to 3, the Project type fields for
href/repo are currently typed as Next's Route which rejects absolute external
URLs; update the Project type definitions so href and repo accept both internal
Routes and external string URLs (e.g., use Route | string for those properties),
and export/update any related interfaces so existing code using absolute
repo/href values compiles without type errors.

@@ -0,0 +1,8 @@
import type { Route } from 'next'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Broaden href type to allow external links; import ReactNode explicitly.

QuickFact UI uses target="_blank", implying external URLs. Typing href as Next’s Route blocks https:// links and will cause type errors. Also, prefer importing ReactNode instead of referencing React.ReactNode.

Apply:

-import type { Route } from 'next'
+import type { Route } from 'next'
+import type { ReactNode } from 'react'

 export interface QuickFact {
   label: string
-  value: string | React.ReactNode
-  href?: Route
+  value: string | ReactNode
+  href?: Route | `https://${string}` | `http://${string}`
   note?: string
 }

Run to confirm any QuickFact hrefs are external:

Also applies to: 5-7


🏁 Script executed:

#!/bin/bash
rg -nC2 'QUICK_FACTS|href:' --type ts --type tsx

Length of output: 27


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== show file src/types/quick-facts.d.ts ==="
if [ -f src/types/quick-facts.d.ts ]; then
  sed -n '1,200p' src/types/quick-facts.d.ts || true
else
  echo "file not found: src/types/quick-facts.d.ts"
fi

echo
echo "=== search for 'QuickFact' symbol in repo ==="
rg -n --hidden -S --no-ignore -g '**/*.{ts,tsx,d.ts}' '\bQuickFact\b' -C3 || true

echo
echo "=== search for QUICK_FACTS constants/usages ==="
rg -n --hidden -S --no-ignore -g '**/*.{ts,tsx,js,jsx}' 'QUICK_FACTS' -i -C3 || true

echo
echo "=== search for 'href:' property occurrences ==="
rg -n --hidden -S --no-ignore -g '**/*.{ts,tsx,js,jsx,html}' -P '\bhref\s*:' -n -C2 || true

echo
echo "=== search for target=\"_blank\" usages ==="
rg -n --hidden -S --no-ignore -g '**/*.{ts,tsx,js,jsx,html}' 'target\s*=\s*"_blank"' -n -P -C2 || true

echo
echo "=== search for Route import from 'next' ==="
rg -n --hidden -S --no-ignore -g '**/*.{ts,tsx,d.ts}' -P "import\s+type\s+\{?\s*Route\s*\}?\s+from\s+'next'" -n -C2 || true

echo
echo "=== search for React.ReactNode usage ==="
rg -n --hidden -S --no-ignore -g '**/*.{ts,tsx,js,jsx,d.ts}' 'React\.ReactNode' -n -C2 || true

Length of output: 21241


Allow external URLs for QuickFact.href; import ReactNode.

QUICK_FACTS contains external https:// links (src/lib/quickfacts.ts) and QuickFact is rendered with Link target="_blank" (src/components/layout/about/aboutme/quick-fact.tsx), so typing href as Next's Route rejects those values.

-import type { Route } from 'next'
+import type { Route } from 'next'
+import type { ReactNode } from 'react'

 export interface QuickFact {
   label: string
-  value: string | React.ReactNode
-  href?: Route
+  value: string | ReactNode
+  href?: Route | `https://${string}` | `http://${string}`
   note?: string
 }

Files to update/verify: src/types/quick-facts.d.ts — evidence: src/lib/quickfacts.ts, src/components/layout/about/aboutme/quick-fact.tsx.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import type { Route } from 'next'
import type { Route } from 'next'
import type { ReactNode } from 'react'
export interface QuickFact {
label: string
value: string | ReactNode
href?: Route | `https://${string}` | `http://${string}`
note?: string
}
🤖 Prompt for AI Agents
In src/types/quick-facts.d.ts around line 1, the QuickFact type restricts href
to Next's Route which rejects external https:// links and it also needs
ReactNode for rendered content; update the file to import type ReactNode (and
keep/import Route) and change QuickFact.href to allow both Route and string
(e.g., href?: Route | string), and update any fields that represent rendered
content to use ReactNode where appropriate so external URLs and JSX children are
accepted.

@@ -0,0 +1,16 @@
import type { Route } from 'next'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

🧩 Analysis chain

Skill URL shouldn’t be constrained to Route; allow external links.

Skill destinations are often external docs/homepages; Route will reject them.

-import type { Route } from 'next'
+import type { Route } from 'next'

 export type SkillInfo = {
   icon: SkillIconRef
-  url: Route
+  url: Route | `https://${string}` | `http://${string}`
 }

Optionally rename url -> href for consistency with Link.

Also applies to: 13-16


🏁 Script executed:

#!/bin/bash
rg -nC1 'url:\s*' src/lib/skillicons.ts src --type ts --type tsx

Length of output: 27


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "pwd: $(pwd)"
echo "Git branch:"
git rev-parse --abbrev-ref HEAD || true
echo
echo "Listing src/types (if present):"
ls -la src/types || true
echo
echo "Show src/types/skillicons.d.ts if it exists:"
if [ -f src/types/skillicons.d.ts ]; then
  nl -ba src/types/skillicons.d.ts | sed -n '1,240p'
else
  echo "src/types/skillicons.d.ts not found; searching repo for 'skillicons' or 'SkillInfo'..."
  rg -n "skillicons|SkillInfo|SkillIconRef" -S || true
fi
echo
echo "Search for 'import type { Route } from' occurrences:"
rg -n "import type \{ Route \} from" -S || true
echo
echo "Search for 'url:' fields across the repo:"
rg -n -C1 "url\s*:" -S || true

Length of output: 1139


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "pwd: $(pwd)"
echo
echo "=== src/types/skillicons.d.ts ==="
if [ -f src/types/skillicons.d.ts ]; then
  sed -n '1,240p' src/types/skillicons.d.ts || true
else
  echo "src/types/skillicons.d.ts not found"
fi
echo
echo "=== Grep occurrences of related symbols ==="
rg -n --hidden -S "SkillInfo|SkillIconRef|skillicons|import type \{ Route \} from 'next'|url\s*:\s*Route" || true
echo
echo "=== Search for 'url:' across repo (TS/TSX/JS/JSX/DTS) ==="
rg -n --hidden -S "url\s*:" --glob '!**/node_modules/**' || true

Length of output: 3842


Allow external skill URLs — don't constrain SkillInfo.url to next's Route

Skill destinations are often external (docs/homepages). src/types/skillicons.d.ts currently types url as Route which will reject external links — change the type and update consumers.

-import type { Route } from 'next'
+import type { Route } from 'next'

 export type SkillInfo = {
   icon: SkillIconRef
-  url: Route
+  url: Route | `https://${string}` | `http://${string}`
 }

Optional: rename url -> href for consistency with Link. Update consumers (e.g. src/lib/skillicons.ts, src/components/ui/skill-icons.tsx and any other usages) to handle internal Route vs external hrefs.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import type { Route } from 'next'
import type { Route } from 'next'
export type SkillInfo = {
icon: SkillIconRef
url: Route | `https://${string}` | `http://${string}`
}
🤖 Prompt for AI Agents
In src/types/skillicons.d.ts around line 1, SkillInfo.url is currently typed as
Next's Route which prevents external links; change the type to accept both
internal routes and external URLs (e.g., string | Route) and update all
consumers (src/lib/skillicons.ts, src/components/ui/skill-icons.tsx and any
other usages) to treat the value as either an internal Route (pass to next/link)
or an external href (use a normal <a> with target/rel), optionally renaming url
to href across files for consistency and adjusting imports/props accordingly.

Comment on lines +1 to +6
import type { Route } from 'next'

export interface SocialLinks {
GITHUB: Route
LINKEDIN: Route
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Route is for internal app paths; these are external URLs — type mismatch will fail TS.

Route from next only accepts internal paths (e.g., "/about"). The GitHub/LinkedIn values are absolute URLs, so SocialLinks should not use Route.

Apply this diff:

-import type { Route } from 'next'
-
-export interface SocialLinks {
-  GITHUB: Route
-  LINKEDIN: Route
-}
+export type ExternalHref = `https://${string}`
+
+export interface SocialLinks {
+  readonly GITHUB: ExternalHref
+  readonly LINKEDIN: ExternalHref
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import type { Route } from 'next'
export interface SocialLinks {
GITHUB: Route
LINKEDIN: Route
}
export type ExternalHref = `https://${string}`
export interface SocialLinks {
readonly GITHUB: ExternalHref
readonly LINKEDIN: ExternalHref
}
🤖 Prompt for AI Agents
In src/types/social-links.d.ts around lines 1 to 6, the SocialLinks interface
wrongly types GITHUB and LINKEDIN as Next.js Route (internal-only); change both
properties to type string (or URL if you prefer a URL object) and remove the
unused import of Route from 'next' so the interface accepts absolute external
URLs.

Comment on lines +1 to +4
import type { SkillName } from '@/lib/skillicons'
import type { Route } from 'next'
import type { StaticImageData } from 'next/image'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

companyUrl should accept external URLs.

companyUrl is likely an external site; Route is too strict.

-import type { Route } from 'next'
+import type { Route } from 'next'
+
+type ExternalUrl = `http://${string}` | `https://${string}`
@@
-  companyUrl?: Route
+  companyUrl?: Route | ExternalUrl

Also applies to: 15-16


🏁 Script executed:

#!/bin/bash
rg -nP -C2 'companyUrl:\s*' src | cat

Length of output: 1112


Allow companyUrl to accept external http(s) URLs (Route | ExternalUrl)

Add an ExternalUrl type and change companyUrl from Route to Route | ExternalUrl in the types file; update the posthog helper to accept the widened type. src/lib/experience.ts already uses https URLs.

-import type { Route } from 'next'
+import type { Route } from 'next'
+
+type ExternalUrl = `http://${string}` | `https://${string}`
@@
-  companyUrl?: Route
+  companyUrl?: Route | ExternalUrl

Also update captureCompanyLogoClicked(..., companyUrl: Route) → companyUrl: Route | ExternalUrl in src/hooks/posthog.ts.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import type { SkillName } from '@/lib/skillicons'
import type { Route } from 'next'
import type { StaticImageData } from 'next/image'
import type { SkillName } from '@/lib/skillicons'
import type { Route } from 'next'
type ExternalUrl = `http://${string}` | `https://${string}`
import type { StaticImageData } from 'next/image'
🤖 Prompt for AI Agents
In src/types/work-experience.d.ts lines 1-4, add a new ExternalUrl type (e.g., a
string branded/alias for 'http(s)://' URLs) and change the companyUrl type from
Route to a union Route | ExternalUrl; then update the posthog helper signature
in src/hooks/posthog.ts (captureCompanyLogoClicked and any related functions) to
accept companyUrl: Route | ExternalUrl and adjust any type imports/exports
accordingly so the widened type is used consistently (note src/lib/experience.ts
already supplies https URLs so ensure they type-check against ExternalUrl).

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
src/components/providers/theme-provider.tsx (1)

242-249: Bug: storage event key ignores storageNamespace prefix.

onStorage compares e.key to 'theme' literal; when storageNamespace is set, the actual key is prefixed via storageKey('theme'), so cross-tab/theme sync will fail in namespaced contexts.

Apply:

-    const onStorage = (e: StorageEvent) => {
-      if (e.key !== 'theme') return
+    const onStorage = (e: StorageEvent) => {
+      if (e.key !== storageKey('theme')) return
src/components/ui/flipping-card.tsx (1)

169-174: Fix empty accessible name on the interactive container.

Empty aria-label overrides the visible text and results in an unnamed button. Prefer a meaningful default or omit the prop when undefined.

Apply this diff:

-      aria-label={ariaLabel}
+      aria-label={ariaLabel ?? 'Flip card'}
src/components/layout/about/aboutme/mode-toggle-link.tsx (1)

111-114: Remove empty aria-label/title on Button.

Empty strings mask the visible text and break a11y. Let the inner text provide the accessible name or set a real label.

Apply this diff:

-      aria-label=""
-      title="">
+      >
src/lib/projects.ts (1)

14-16: Widen href/repo types to accept external URLs and hash fragments.

  • Reason: several entries use a hash fragment or external URLs which a Route-only type will reject.
  • Affected locations: src/lib/projects.ts (l14–15, l26, l37–38, l54), src/lib/quickfacts.ts (l5–8).
  • Fix: update src/types/projects.d.ts to make href/repo string (or Route | string) and adjust any other narrow href types accordingly.
src/components/providers/achievements-provider.tsx (1)

88-92: Set the cookie as Secure.

Add the Secure attribute so the mirror cookie is not sent over HTTP.

Apply:

-      document.cookie = `${ACHIEVEMENTS_COOKIE_NAME}=${encodeURIComponent(value)}; path=/; expires=${expires}; samesite=lax`
+      document.cookie = `${ACHIEVEMENTS_COOKIE_NAME}=${encodeURIComponent(value)}; path=/; expires=${expires}; samesite=lax; secure`
src/lib/skillicons.ts (1)

3-19: Change SkillInfo.url to allow external URLs

SkillInfo.url is typed as Route in src/types/skillicons.d.ts (lines 13–16), which will reject external "https://…" values in SKILLS — change it to string (or string | URL).

Current:

export type SkillInfo = {
  icon: SkillIconRef
  url: Route
}

Proposed:

export type SkillInfo = {
  icon: SkillIconRef
  url: string // or: string | URL
}
♻️ Duplicate comments (1)
src/lib/pets.ts (1)

7-7: Switched to shared Pet type — resolved prior bot note

Import looks correct, and the previously flagged unused cookie constant is no longer present.

🧹 Nitpick comments (28)
src/lib/social-links.ts (1)

3-6: Use satisfies for tighter checks without widening.

Optional, but improves ergonomics and keeps literal types while ensuring the object conforms to SocialLinks.

Apply this diff:

-export const SOCIAL_LINKS: SocialLinks = {
-  GITHUB: 'https://github.com/kiliantyler/',
-  LINKEDIN: 'https://www.linkedin.com/in/kilian-tyler/',
-}
+export const SOCIAL_LINKS = {
+  GITHUB: 'https://github.com/kiliantyler/',
+  LINKEDIN: 'https://www.linkedin.com/in/kilian-tyler/',
+} as const satisfies SocialLinks
src/components/layout/about/aboutme/quick-fact.tsx (3)

11-37: dl semantics: dt/dd should be direct children of dl.

Wrapping dt/dd in a div breaks definition list semantics. Consider returning a fragment and moving layout classes to the parent <dl> or onto dt/dd themselves to preserve a11y.

Example (non-diff):

<>
  <dt className="text-muted-foreground /* col/start classes */">{fact.label}</dt>
  <dd className="text-primary font-medium /* col/start classes */"></dd>
</>

11-18: Avoid label-based sentinel logic.

Using fact.label === 'Mode' couples behavior to display text. Prefer a discriminant on the type, e.g., kind?: 'mode' | 'link' | 'text', and branch on kind.


11-12: Remove local key; set keys at the map site.

key={fact.label} inside the leaf component doesn’t help React reconcile sibling items. Place the key on the element returned from the mapping site instead.

src/lib/quickfacts.ts (1)

4-5: The 'Mode' item’s value is ignored by the component.

QuickFact component renders a toggle when label === 'Mode', ignoring value: 'Dark mode'. Either remove value here or introduce a discriminant (kind: 'mode') and branch on it in the component.

src/components/layout/header/nav-lava.tsx (1)

164-167: Tailwind duration token likely invalid.
Use arbitrary value syntax instead of duration-450 for reliability.

-              'transition-[transform,width,opacity] duration-450 ease-[cubic-bezier(0.2,0.8,0.16,1)]',
+              'transition-[transform,width,opacity] duration-[450ms] ease-[cubic-bezier(0.2,0.8,0.16,1)]',
src/types/skillicons.d.ts (1)

1-16: Nice centralization of SkillIcons types; consider read-only properties.
To prevent accidental mutation in consumers, mark object members as readonly.

 export type SkillIconRef =
   | string
   | {
-      source: 'dashboardicons'
-      name: string
-      format?: DashboardIconFormat
+      readonly source: 'dashboardicons'
+      readonly name: string
+      readonly format?: DashboardIconFormat
     }
 
-export type SkillInfo = {
-  icon: SkillIconRef
-  url: Route
-}
+export type SkillInfo = {
+  readonly icon: SkillIconRef
+  readonly url: Route
+}
src/components/providers/theme-provider.tsx (1)

19-28: Global KEY_PREFIX can cause cross-provider interference.

Module-level mutable state means multiple ThemeProvider instances with different namespaces will race. Consider scoping the prefix in a ref and threading it through helpers.

I can draft a refactor that encapsulates cookie/storage helpers per-provider if helpful.

src/components/ui/flipping-card.tsx (2)

193-196: Gate fetchPriority by priority flag.

Always forcing "high" can hurt LCP elsewhere.

Apply this diff:

-                fetchPriority="high"
+                fetchPriority={backgroundPriority ? 'high' : 'auto'}

152-163: Cancel any pending rAF on unmount to avoid leaks.

Small cleanup to prevent a stray scheduled frame after unmount.

Apply this diff:

   const handlePointerLeave = useCallback(() => {
@@
     tiltEl.style.removeProperty('--card-back-shadow')
   }, [])
+
+  useEffect(() => {
+    return () => {
+      if (frameRequestRef.current != null) {
+        cancelAnimationFrame(frameRequestRef.current)
+        frameRequestRef.current = null
+      }
+    }
+  }, [])
src/components/ui/skill-icons.stories.tsx (1)

4-5: Point type import at the centralized types module

To align with the PR’s “types to src/types” goal, import SkillName from the types package rather than lib (unless lib purposely re‑exports it).

-import type { SkillName } from '@/lib/skillicons'
+import type { SkillName } from '@/types/skillicons'
src/components/layout/experience/work-history/skills-block.tsx (1)

14-14: Import React types explicitly to use React.CSSProperties

React.CSSProperties may error under the automatic JSX runtime without a React type import. Prefer importing the type and using CSSProperties.

+import type { CSSProperties } from 'react'
@@
-      style={{ '--delay-in-skills': `${highlightCount * 80}ms`, '--delay-out-skills': `0ms` } as React.CSSProperties}>
+      style={{ '--delay-in-skills': `${highlightCount * 80}ms`, '--delay-out-skills': `0ms` } as CSSProperties}>
src/app/achievements/page.tsx (1)

11-11: Drop unnecessary await on cookies()

cookies() is synchronous; awaiting it is redundant.

-  const cookieStore = await cookies()
+  const cookieStore = cookies()
src/utils/theme-runtime.ts (1)

15-20: Freeze derived theme lists to prevent accidental mutation

These arrays are shared across functions; freezing them avoids accidental runtime mutation.

-export const SEASONAL_THEMES: SeasonalThemeConfig[] = themes
-  .filter(hasTimeRange)
-  .map(t => ({ theme: t.name, start: t.timeRange.start, end: t.timeRange.end }))
+export const SEASONAL_THEMES = Object.freeze(
+  themes
+    .filter(hasTimeRange)
+    .map(t => ({ theme: t.name, start: t.timeRange.start, end: t.timeRange.end })),
+) satisfies ReadonlyArray<SeasonalThemeConfig>

-const BASE_CSS_THEMES: ThemeName[] = themes.filter(t => !('timeRange' in t)).map(t => t.name)
+const BASE_CSS_THEMES = Object.freeze(
+  themes.filter(t => !('timeRange' in t)).map(t => t.name),
+) as ReadonlyArray<ThemeName>

Also applies to: 19-20

src/types/achievements.d.ts (1)

11-11: Make unlockHint optional to avoid forcing placeholder strings.

Some achievements won’t have hints. Making this optional reduces friction and avoids meaningless defaults.

-  unlockHint: string
+  unlockHint?: string
src/types/projects.d.ts (1)

1-1: Decouple shared types from runtime lib by moving SkillName to src/types.

src/types should not depend on src/lib. Consider exporting SkillName (and SkillEntry) from src/types/skillicons.d.ts to keep layering clean.

src/types/work-experience.d.ts (2)

2-2: Allow external/company links by using string for companyUrl.

As with projects, Route won’t accept https:// domains. Switch to string and remove the now-unused Route import.

-import type { Route } from 'next'
@@
-  companyUrl?: Route
+  companyUrl?: string

Also applies to: 15-15


10-11: Optional: constrain date formats.

If these are calendar dates and not free-form, consider a branded type or documenting the expected format (e.g., 'YYYY' or 'YYYY-MM') to prevent inconsistent strings.

src/lib/projects.ts (1)

61-66: Nit: status semantics.

If “wip” implies not publicly available, consider reflecting that in UI (badge or subdued card) to avoid implying production readiness.

src/lib/content.ts (1)

3-9: LGTM; consider const assertion to prevent accidental mutation.

Keeps values immutable at compile time while still satisfying HomeContent.

-export const HOME_CONTENT: HomeContent = {
+export const HOME_CONTENT = {
   NAME: 'Kilian Tyler',
   TITLE: 'Site Reliability | DevOps Engineer',
   LOCATION: 'Cleveland, Ohio',
   MAP_LATITUDE: 41.4993,
   MAP_LONGITUDE: -81.6944,
-}
+} as const satisfies HomeContent
src/types/themes.d.ts (1)

2-2: Broaden IconComponent to standard SVG props for better icon compatibility.

Lucide icons accept typical SVG props (size, strokeWidth, etc.). Widening improves compatibility without breaking existing usage.

-import type { ComponentType } from 'react'
+import type { ComponentType, SVGProps } from 'react'
@@
-export type IconComponent = ComponentType<{ className?: string }>
+export type IconComponent = ComponentType<SVGProps<SVGSVGElement>>

Also applies to: 6-6

src/utils/skillicons.ts (2)

16-17: Fix typo in comment; unreachable fallback.

  • “syvixor” looks like a typo.
  • Given the union, this branch is effectively unreachable; keeping it is fine as a defensive default.
-  // Fallback to syvixor if an unexpected value slips through
+  // Fallback to default skills endpoint if an unexpected value slips through

20-22: Guard against accidental unknown names at runtime.

The type ensures compile-time safety, but adding a runtime guard can improve DX if data ever comes from JSON or CMS.

-export function resolveSkills(names: SkillName[]): SkillEntry[] {
-  return names.map(name => ({ name, ...SKILLS[name] }))
-}
+export function resolveSkills(names: SkillName[]): SkillEntry[] {
+  return names.map(name => {
+    const info = SKILLS[name]
+    if (!info) {
+      throw new Error(`Unknown skill: ${name as string}`)
+    }
+    return { name, ...info }
+  })
+}
src/components/ui/map-tooltip.tsx (1)

58-63: Add lazy loading and stricter referrer policy on iframes.

Small perf/privacy win for embeds.

Apply:

-              <iframe
+              <iframe
                 src={embedMapSrc}
                 title={`Map of ${locationLabel}`}
-                className="relative z-10 h-[240px] w-full rounded-md border border-border shadow-md"
+                className="relative z-10 h-[240px] w-full rounded-md border border-border shadow-md"
+                loading="lazy"
+                referrerPolicy="no-referrer"
               />
-              <iframe
+              <iframe
                 src={embedMapSrc}
                 title={`Map of ${locationLabel}`}
                 width={320}
                 height={200}
-                className="relative z-10 h-[200px] w-[320px] rounded-md border border-border shadow-md"
+                className="relative z-10 h-[200px] w-[320px] rounded-md border border-border shadow-md"
+                loading="lazy"
+                referrerPolicy="no-referrer"
               />

Also applies to: 80-87

src/utils/themes.ts (1)

1-38: Solid, typed theme helpers with safe fallbacks.

API is cohesive, with sensible defaults (Sun/Headshot, 'light' base). Consider exposing THEME_BY_NAME as ReadonlyMap to prevent accidental mutation.

Example:

-export const THEME_BY_NAME = new Map<ThemeName, ThemeEntry>(themes.map(t => [t.name, t] as const))
+export const THEME_BY_NAME: ReadonlyMap<ThemeName, ThemeEntry> =
+  new Map<ThemeName, ThemeEntry>(themes.map(t => [t.name, t] as const))
src/types/light-grid.d.ts (1)

1-13: Make config type immutable and tighten RGB typing.

These are constants; mark fields readonly and constrain COLOR_RGB to an RGB tuple-like template for safer usage.

-export interface LightGrid {
-  GRID_SIZE_PX: number
-  GRID_OFFSET_PX: number
-  NUM_LIGHTS: number
-  DURATION_SECONDS: number
-  STAGGER_SECONDS: number
-  DETOUR_PROBABILITY: number
-  HORIZONTAL_PROBABILITY: number
-  DOT_SIZE_PX: number
-  COLOR_RGB: string
-  GLOW_NEAR_PX: number
-  GLOW_FAR_PX: number
-}
+export interface LightGrid {
+  readonly GRID_SIZE_PX: number
+  readonly GRID_OFFSET_PX: number
+  readonly NUM_LIGHTS: number
+  readonly DURATION_SECONDS: number
+  readonly STAGGER_SECONDS: number
+  readonly DETOUR_PROBABILITY: number
+  readonly HORIZONTAL_PROBABILITY: number
+  readonly DOT_SIZE_PX: number
+  readonly COLOR_RGB: `${number},${number},${number}`
+  readonly GLOW_NEAR_PX: number
+  readonly GLOW_FAR_PX: number
+}
src/lib/light-grid.ts (1)

3-20: Prefer satisfies for exactness; optionally freeze at runtime.

Avoids widening and catches missing/extra keys without changing external mutability. Freezing prevents accidental mutation.

-export const LIGHT_GRID: LightGrid = {
+export const LIGHT_GRID = {
   // layout
   GRID_SIZE_PX: 40,
   GRID_OFFSET_PX: -5,
   NUM_LIGHTS: 16,

   // motion
   DURATION_SECONDS: 20,
   STAGGER_SECONDS: 0,
   DETOUR_PROBABILITY: 0.1,
   HORIZONTAL_PROBABILITY: 0.5,

   // dot appearance
   DOT_SIZE_PX: 5,
   COLOR_RGB: '3,169,244',
   GLOW_NEAR_PX: 5,
   GLOW_FAR_PX: 10,
-}
+} satisfies LightGrid
+
+// Optional: prevent runtime mutation
+Object.freeze(LIGHT_GRID)

If your TS version is <4.9 (no satisfies), I can provide an alternative.

src/types/pets.d.ts (1)

1-12: Solid extracted Pet type; consider slight flexibility for image

Current definition matches local static imports. If you ever mix remote URLs or CMS-driven paths, consider widening image to string | StaticImageData to reduce future churn.

-export interface Pet {
+export interface Pet {
   id: string
   name: string
   breed: string
   birthday: string
   gender: string
   description: string
-  image: StaticImageData
+  image: StaticImageData | string
   imageAlt: string
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c210be3 and 62a5879.

📒 Files selected for processing (76)
  • .gitignore (1 hunks)
  • .storybook/docs-container.tsx (1 hunks)
  • eslint.config.js (1 hunks)
  • scripts/build-presence-runtime.ts (1 hunks)
  • scripts/build-theme-runtime.ts (1 hunks)
  • src/app/achievements/page.tsx (1 hunks)
  • src/app/layout.tsx (1 hunks)
  • src/components/layout/about/aboutme/mode-toggle-link.tsx (1 hunks)
  • src/components/layout/about/aboutme/quick-fact.tsx (1 hunks)
  • src/components/layout/about/pets/pet-card/_content.tsx (1 hunks)
  • src/components/layout/about/pets/pet-card/card-back.tsx (1 hunks)
  • src/components/layout/experience/skills/_content.tsx (1 hunks)
  • src/components/layout/experience/work-history/collapsible-highlights.tsx (1 hunks)
  • src/components/layout/experience/work-history/company-marker.tsx (1 hunks)
  • src/components/layout/experience/work-history/skills-block.tsx (1 hunks)
  • src/components/layout/experience/work-history/work-history-item.tsx (1 hunks)
  • src/components/layout/header/mobile-nav-button.tsx (1 hunks)
  • src/components/layout/header/mobile-nav.tsx (1 hunks)
  • src/components/layout/header/nav-lava.tsx (1 hunks)
  • src/components/layout/home/hero/hero-content.tsx (1 hunks)
  • src/components/layout/home/hero/profile-image.tsx (2 hunks)
  • src/components/layout/pet-gallery/_content.tsx (1 hunks)
  • src/components/layout/projects/project-card/_content.tsx (1 hunks)
  • src/components/layout/projects/project-card/card-back.tsx (1 hunks)
  • src/components/layout/projects/project-card/tech-icons.tsx (1 hunks)
  • src/components/layout/projects/projects-grid.tsx (1 hunks)
  • src/components/providers/achievements-provider.tsx (1 hunks)
  • src/components/providers/providers.tsx (1 hunks)
  • src/components/providers/theme-provider.tsx (1 hunks)
  • src/components/ui/alert-dialog.tsx (1 hunks)
  • src/components/ui/bottom-drawer.tsx (1 hunks)
  • src/components/ui/button.tsx (1 hunks)
  • src/components/ui/card.tsx (1 hunks)
  • src/components/ui/drawer.tsx (1 hunks)
  • src/components/ui/dropdown-menu.tsx (1 hunks)
  • src/components/ui/flip-indicator.tsx (1 hunks)
  • src/components/ui/flipping-card.tsx (1 hunks)
  • src/components/ui/link-button.tsx (1 hunks)
  • src/components/ui/map-tooltip.tsx (2 hunks)
  • src/components/ui/navigation-menu.tsx (1 hunks)
  • src/components/ui/scroll-area.tsx (1 hunks)
  • src/components/ui/section-label.tsx (1 hunks)
  • src/components/ui/separator.tsx (1 hunks)
  • src/components/ui/skill-icons.stories.tsx (1 hunks)
  • src/components/ui/skill-icons.tsx (1 hunks)
  • src/components/ui/theme-toggle.tsx (1 hunks)
  • src/components/ui/tooltip.tsx (1 hunks)
  • src/lib/achievements.ts (1 hunks)
  • src/lib/content.ts (1 hunks)
  • src/lib/experience.ts (1 hunks)
  • src/lib/light-grid.ts (1 hunks)
  • src/lib/navmenu.ts (1 hunks)
  • src/lib/pets.ts (1 hunks)
  • src/lib/projects.ts (1 hunks)
  • src/lib/quickfacts.ts (1 hunks)
  • src/lib/skillicons.ts (1 hunks)
  • src/lib/social-links.ts (1 hunks)
  • src/lib/themes.ts (1 hunks)
  • src/types/achievements.d.ts (1 hunks)
  • src/types/home-content.d.ts (1 hunks)
  • src/types/light-grid.d.ts (1 hunks)
  • src/types/navigation-item.d.ts (1 hunks)
  • src/types/pets.d.ts (1 hunks)
  • src/types/presence-bundle.d.ts (1 hunks)
  • src/types/projects.d.ts (1 hunks)
  • src/types/quick-facts.d.ts (1 hunks)
  • src/types/skillicons.d.ts (1 hunks)
  • src/types/social-links.d.ts (1 hunks)
  • src/types/theme-bundle.d.ts (1 hunks)
  • src/types/themes.d.ts (1 hunks)
  • src/types/work-experience.d.ts (1 hunks)
  • src/utils/achievements.ts (1 hunks)
  • src/utils/skillicons.ts (1 hunks)
  • src/utils/theme-css.ts (1 hunks)
  • src/utils/theme-runtime.ts (1 hunks)
  • src/utils/themes.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx,cjs,mjs}

📄 CodeRabbit inference engine (.cursor/rules/posthog-integration.mdc)

**/*.{ts,tsx,js,jsx,cjs,mjs}: Never hardcode or hallucinate the PostHog API key; always read it from the value populated in the .env file
Create new feature flag names that are clear and descriptive
Gate flag-dependent code on a check that verifies the flag’s values are valid and expected
If a custom property for a person or event is referenced in two or more files or in two or more callsites within the same file, centralize the property name in an enum (TS) or const object (JS)

Files:

  • src/app/layout.tsx
  • src/components/ui/separator.tsx
  • src/components/layout/header/mobile-nav.tsx
  • scripts/build-presence-runtime.ts
  • src/types/light-grid.d.ts
  • src/utils/theme-css.ts
  • scripts/build-theme-runtime.ts
  • src/components/ui/scroll-area.tsx
  • src/components/layout/pet-gallery/_content.tsx
  • eslint.config.js
  • src/types/quick-facts.d.ts
  • src/components/layout/projects/project-card/card-back.tsx
  • src/components/ui/navigation-menu.tsx
  • src/components/ui/button.tsx
  • src/components/ui/dropdown-menu.tsx
  • src/types/home-content.d.ts
  • src/components/layout/experience/skills/_content.tsx
  • src/components/layout/projects/project-card/_content.tsx
  • src/components/layout/about/aboutme/quick-fact.tsx
  • src/components/ui/link-button.tsx
  • src/components/ui/bottom-drawer.tsx
  • src/types/work-experience.d.ts
  • src/components/providers/providers.tsx
  • src/types/achievements.d.ts
  • src/components/layout/about/pets/pet-card/card-back.tsx
  • src/components/layout/home/hero/hero-content.tsx
  • src/types/theme-bundle.d.ts
  • src/components/ui/section-label.tsx
  • src/types/social-links.d.ts
  • src/components/ui/drawer.tsx
  • src/utils/themes.ts
  • src/components/layout/experience/work-history/company-marker.tsx
  • src/components/ui/card.tsx
  • src/components/layout/projects/projects-grid.tsx
  • src/components/layout/header/mobile-nav-button.tsx
  • src/types/themes.d.ts
  • src/types/skillicons.d.ts
  • src/app/achievements/page.tsx
  • src/components/ui/flipping-card.tsx
  • src/components/ui/skill-icons.tsx
  • src/components/layout/experience/work-history/skills-block.tsx
  • src/components/ui/flip-indicator.tsx
  • src/components/layout/experience/work-history/work-history-item.tsx
  • src/types/presence-bundle.d.ts
  • src/types/navigation-item.d.ts
  • src/components/layout/about/aboutme/mode-toggle-link.tsx
  • src/utils/achievements.ts
  • src/components/layout/experience/work-history/collapsible-highlights.tsx
  • src/components/layout/about/pets/pet-card/_content.tsx
  • src/components/layout/projects/project-card/tech-icons.tsx
  • src/components/layout/header/nav-lava.tsx
  • src/components/layout/home/hero/profile-image.tsx
  • src/lib/light-grid.ts
  • src/utils/skillicons.ts
  • src/utils/theme-runtime.ts
  • src/lib/content.ts
  • src/lib/quickfacts.ts
  • src/components/ui/skill-icons.stories.tsx
  • src/lib/social-links.ts
  • src/components/ui/map-tooltip.tsx
  • src/components/ui/theme-toggle.tsx
  • src/lib/navmenu.ts
  • src/types/projects.d.ts
  • src/lib/pets.ts
  • src/components/providers/theme-provider.tsx
  • src/components/ui/alert-dialog.tsx
  • src/lib/themes.ts
  • src/lib/projects.ts
  • src/lib/experience.ts
  • src/lib/skillicons.ts
  • src/components/ui/tooltip.tsx
  • src/lib/achievements.ts
  • src/components/providers/achievements-provider.tsx
  • src/types/pets.d.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/posthog-integration.mdc)

In TypeScript, store feature flag names in an enum with members written in UPPERCASE_WITH_UNDERSCORE and use a consistent naming convention

Files:

  • src/app/layout.tsx
  • src/components/ui/separator.tsx
  • src/components/layout/header/mobile-nav.tsx
  • scripts/build-presence-runtime.ts
  • src/types/light-grid.d.ts
  • src/utils/theme-css.ts
  • scripts/build-theme-runtime.ts
  • src/components/ui/scroll-area.tsx
  • src/components/layout/pet-gallery/_content.tsx
  • src/types/quick-facts.d.ts
  • src/components/layout/projects/project-card/card-back.tsx
  • src/components/ui/navigation-menu.tsx
  • src/components/ui/button.tsx
  • src/components/ui/dropdown-menu.tsx
  • src/types/home-content.d.ts
  • src/components/layout/experience/skills/_content.tsx
  • src/components/layout/projects/project-card/_content.tsx
  • src/components/layout/about/aboutme/quick-fact.tsx
  • src/components/ui/link-button.tsx
  • src/components/ui/bottom-drawer.tsx
  • src/types/work-experience.d.ts
  • src/components/providers/providers.tsx
  • src/types/achievements.d.ts
  • src/components/layout/about/pets/pet-card/card-back.tsx
  • src/components/layout/home/hero/hero-content.tsx
  • src/types/theme-bundle.d.ts
  • src/components/ui/section-label.tsx
  • src/types/social-links.d.ts
  • src/components/ui/drawer.tsx
  • src/utils/themes.ts
  • src/components/layout/experience/work-history/company-marker.tsx
  • src/components/ui/card.tsx
  • src/components/layout/projects/projects-grid.tsx
  • src/components/layout/header/mobile-nav-button.tsx
  • src/types/themes.d.ts
  • src/types/skillicons.d.ts
  • src/app/achievements/page.tsx
  • src/components/ui/flipping-card.tsx
  • src/components/ui/skill-icons.tsx
  • src/components/layout/experience/work-history/skills-block.tsx
  • src/components/ui/flip-indicator.tsx
  • src/components/layout/experience/work-history/work-history-item.tsx
  • src/types/presence-bundle.d.ts
  • src/types/navigation-item.d.ts
  • src/components/layout/about/aboutme/mode-toggle-link.tsx
  • src/utils/achievements.ts
  • src/components/layout/experience/work-history/collapsible-highlights.tsx
  • src/components/layout/about/pets/pet-card/_content.tsx
  • src/components/layout/projects/project-card/tech-icons.tsx
  • src/components/layout/header/nav-lava.tsx
  • src/components/layout/home/hero/profile-image.tsx
  • src/lib/light-grid.ts
  • src/utils/skillicons.ts
  • src/utils/theme-runtime.ts
  • src/lib/content.ts
  • src/lib/quickfacts.ts
  • src/components/ui/skill-icons.stories.tsx
  • src/lib/social-links.ts
  • src/components/ui/map-tooltip.tsx
  • src/components/ui/theme-toggle.tsx
  • src/lib/navmenu.ts
  • src/types/projects.d.ts
  • src/lib/pets.ts
  • src/components/providers/theme-provider.tsx
  • src/components/ui/alert-dialog.tsx
  • src/lib/themes.ts
  • src/lib/projects.ts
  • src/lib/experience.ts
  • src/lib/skillicons.ts
  • src/components/ui/tooltip.tsx
  • src/lib/achievements.ts
  • src/components/providers/achievements-provider.tsx
  • src/types/pets.d.ts
**/*.{js,jsx,cjs,mjs}

📄 CodeRabbit inference engine (.cursor/rules/posthog-integration.mdc)

In JavaScript, store feature flag names as string values in a const object (simulating an enum) with members written in UPPERCASE_WITH_UNDERSCORE and use a consistent naming convention

Files:

  • eslint.config.js
🧬 Code graph analysis (12)
src/types/quick-facts.d.ts (1)
src/components/layout/about/aboutme/quick-fact.tsx (1)
  • QuickFact (9-39)
src/types/work-experience.d.ts (1)
src/lib/skillicons.ts (1)
  • SkillName (21-21)
src/components/layout/home/hero/hero-content.tsx (1)
src/lib/content.ts (1)
  • HOME_CONTENT (3-9)
src/utils/themes.ts (2)
src/lib/themes.ts (5)
  • Theme (50-50)
  • ThemeName (49-49)
  • ThemeEntry (48-48)
  • themes (8-46)
  • KNOWN_THEMES (51-51)
src/types/themes.d.ts (2)
  • IconComponent (6-6)
  • BaseColor (4-4)
src/types/navigation-item.d.ts (1)
src/types/themes.d.ts (1)
  • IconComponent (6-6)
src/utils/achievements.ts (2)
src/lib/achievements.ts (3)
  • AchievementId (96-96)
  • ACHIEVEMENTS (10-95)
  • ACHIEVEMENTS_COOKIE_NAME (8-8)
src/types/presence-bundle.d.ts (1)
  • PRESENCE_RUNTIME_BUNDLE (2-2)
src/components/layout/home/hero/profile-image.tsx (1)
src/lib/content.ts (1)
  • HOME_CONTENT (3-9)
src/utils/skillicons.ts (2)
src/types/skillicons.d.ts (2)
  • SkillIconRef (5-11)
  • DashboardIconFormat (3-3)
src/lib/skillicons.ts (3)
  • SkillName (21-21)
  • SkillEntry (23-23)
  • SKILLS (3-19)
src/lib/content.ts (1)
src/types/home-content.d.ts (1)
  • HomeContent (1-7)
src/lib/social-links.ts (1)
src/types/social-links.d.ts (1)
  • SocialLinks (3-6)
src/components/ui/map-tooltip.tsx (1)
src/lib/content.ts (1)
  • HOME_CONTENT (3-9)
src/types/projects.d.ts (1)
src/lib/skillicons.ts (1)
  • SkillName (21-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (64)
src/lib/social-links.ts (1)

1-1: LGTM: type-only import keeps bundle clean.
No runtime impact from the import.

src/types/social-links.d.ts (1)

1-6: Fix external URLs: use ExternalHref for social links; audit other Route usages

Replace SocialLinks' GITHUB/LINKEDIN with an external-URL string (drop the next import). Apply diff below, then audit other files that still use Route and run a full typecheck.

-import type { Route } from 'next'
-
-export interface SocialLinks {
-  GITHUB: Route
-  LINKEDIN: Route
-}
+export type ExternalHref = `https://${string}`
+
+export interface SocialLinks {
+  readonly GITHUB: ExternalHref
+  readonly LINKEDIN: ExternalHref
+}

Files found with Route usages to audit:

  • src/types/social-links.d.ts
  • src/types/work-experience.d.ts (companyUrl)
  • src/types/skillicons.d.ts (url)
  • src/types/quick-facts.d.ts (href)
  • src/types/projects.d.ts (href, repo)
  • src/types/navigation-item.d.ts (href)

Run a local typecheck after changes: pnpm -s tsc --noEmit (or npm/yarn equivalent).

src/lib/quickfacts.ts (2)

1-1: Type import relocation LGTM.


4-10: External hrefs require the href type change in QuickFact.

Entries like https://fishshell.com will not satisfy Route. After updating the type to string | URL, these will typecheck and work with next/link.

src/components/layout/pet-gallery/_content.tsx (1)

2-2: Import path update LGTM.
No issues with the cn import relocation.

src/components/layout/header/mobile-nav.tsx (1)

8-8: Import path update LGTM.
Looks consistent with the repo-wide utils move.

src/components/layout/header/nav-lava.tsx (1)

9-9: Import path update LGTM.
Consistent with utils migration.

scripts/build-theme-runtime.ts (1)

12-13: Path updates LGTM.
Entry/output now align with src/utils; build flow unchanged.

src/types/theme-bundle.d.ts (1)

1-3: Ambient module path updated correctly.
Matches new output location.

src/types/home-content.d.ts (1)

1-7: Type addition LGTM.
Clear shape for HOME_CONTENT; no issues.

scripts/build-presence-runtime.ts (1)

12-13: Path updates LGTM.
Consistent with utils relocation; generation logic intact.

.gitignore (1)

4-5: Approve — .gitignore changes verified.
No stale src/lib bundle references found; theme/presence bundles are not present or tracked. Ambient d.ts declarations and ESLint ignores point to the new src/utils paths.

src/components/layout/header/mobile-nav-button.tsx (1)

7-8: Import moves are correct and type-only import is appropriate.
No behavioral changes; resolves to the new utils/types locations.

src/components/ui/tooltip.tsx (1)

6-6: cn import path swap is consistent with the utils move.
No other changes; safe.

src/components/ui/flip-indicator.tsx (1)

3-3: cn import updated correctly.
No runtime impact.

src/components/ui/dropdown-menu.tsx (1)

7-7: cn import path updated; matches repo-wide refactor.
All good.

src/components/ui/button.tsx (1)

5-5: cn import path updated; no behavior change.
Consistent with utils relocation.

.storybook/docs-container.tsx (1)

9-11: Theme type declared in src/lib/themes — current import is correct.
Export found at src/lib/themes.ts:50: export type Theme = ThemeName | 'system'; leave the import from ../src/lib/themes as-is.

eslint.config.js (1)

12-12: Ignore path updates look good — re-check for stragglers (rg skipped files in your run).

Re-run this locally and paste any matches:

#!/bin/bash
set -euo pipefail
echo "Searching tracked files for stale '@/lib/utils' and old bundle paths..."
git ls-files -z | xargs -0 rg -nP --hidden --no-ignore-vcs -e "@/lib/utils" -e "src/(?:lib|utils)/(?:theme|presence)-bundle\.ts" || true
echo "Search complete."
src/components/layout/experience/skills/_content.tsx (1)

4-4: resolveSkills import moved to utils — looks good.

No behavior change; just ensure the named export remains stable.

If skillicons was reorganized, please verify there’s a single source of truth (no duplicate files under lib and utils).

src/components/ui/section-label.tsx (1)

1-1: cn import realigned — OK.

No functional changes to SectionLabel.

src/components/ui/alert-dialog.tsx (1)

7-7: cn import updated — OK.

Radix UI wiring unchanged.

src/components/providers/theme-provider.tsx (1)

4-4: theme-runtime import moved to utils — OK.

Assuming named exports are unchanged.

src/utils/theme-css.ts (1)

1-1: Import points to '@/lib/themes' — OK.

Consistent with ThemeProvider; no duplication detected here.

src/components/ui/scroll-area.tsx (1)

6-6: cn import updated — OK.

No API changes to ScrollArea/ScrollBar.

src/components/ui/bottom-drawer.tsx (1)

15-15: cn import updated — OK.

BottomDrawer and content remain unchanged.

src/components/ui/link-button.tsx (1)

2-2: Import-path migration incomplete — '@/lib/*' references remain.

Scan found no direct @/lib/utils imports in TS/TSX files, but many files still import other @/lib/* modules and components.json still maps "utils": "@/lib/utils". Update the remaining imports (or the alias mapping) before approving.

Files with @/lib/* imports:

  • src/utils/themes.ts
  • src/utils/theme-runtime.ts
  • src/utils/theme-css.ts
  • src/utils/skillicons.ts
  • src/utils/achievements.ts
  • src/types/work-experience.d.ts
  • src/types/projects.d.ts
  • src/components/ui/skill-icons.stories.tsx
  • src/components/ui/theme-toggle.tsx
  • src/components/ui/skill-icons.tsx
  • src/components/ui/map-tooltip.tsx
  • src/components/providers/theme-provider.tsx
  • src/components/providers/providers.tsx
  • src/components/providers/achievements-provider.tsx
  • src/components/layout/achievements/unlock-on-mount.tsx
  • src/components/layout/achievements/ladybird-secret-listener.tsx
  • src/components/layout/projects/_content.tsx
  • src/components/layout/achievements/achievement-card.tsx
  • src/components/layout/projects/project-card/tech-icons.tsx
  • src/components/layout/home/hero/profile-image.tsx
  • src/components/layout/header/nav-lava.tsx
  • src/components/layout/header/mobile-nav.tsx
  • src/components/layout/home/hero/hero-content.tsx
  • src/components/layout/header/linkedin-button.tsx
  • src/components/layout/header/github-button.tsx
  • src/components/layout/background/grid-lights.tsx
  • src/components/layout/experience/work-history/_content.tsx
  • src/components/layout/experience/_content.tsx
  • src/components/layout/experience/skills/_content.tsx
  • src/components/layout/background/circuit-background.tsx
  • src/components/layout/about/pets/_content.tsx
  • src/components/layout/about/_content.tsx
  • src/components/layout/about/aboutme/mode-toggle-link.tsx
  • src/components/layout/about/aboutme/aboutme-quickfacts.tsx
  • src/app/projects/head.tsx
  • src/app/achievements/page.tsx
  • components.json

Likely an incorrect or invalid review comment.

src/components/ui/navigation-menu.tsx (1)

6-6: LGTM: util path update only.

Import path swap to @/utils/utils looks correct and non-functional.

src/components/ui/separator.tsx (1)

6-6: LGTM: util path update only.

Consistent with the repo-wide move to @/utils/utils.

src/components/layout/projects/project-card/_content.tsx (1)

5-5: LGTM: type import migrated to @/types/projects.

Matches the new typings location; no runtime changes.

src/components/layout/home/hero/hero-content.tsx (1)

3-12: LGTM: switched to HOME_CONTENT.

Name/title now sourced from HOME_CONTENT; aligns with the content/type refactor.

src/components/layout/projects/project-card/card-back.tsx (1)

4-6: LGTM: imports aligned with new type/utils locations.

Project from @/types/projects and resolveSkills from @/utils/skillicons look correct.

src/components/layout/projects/projects-grid.tsx (1)

1-1: LGTM: Project type import path updated.

No functional impact.

src/app/layout.tsx (1)

7-8: No change required — imports are correct. buildPresenceScript is exported from src/utils/achievements.ts (export function buildPresenceScript at line 62); buildThemeScript is exported from src/utils/theme-runtime.ts (export function buildThemeScript at line 74). Theme helper utilities live under src/utils/themes.

src/components/ui/card.tsx (1)

3-3: Import path update looks good.

cn now points to the new utils location as per the refactor.

src/components/ui/drawer.tsx (1)

6-6: Consistent utils import.

Matches the repo-wide move from lib → utils.

src/components/ui/flipping-card.tsx (1)

3-3: Import path update is correct.

src/components/providers/providers.tsx (1)

4-4: Type import path change looks correct.

UnlockedMap now sourced from utils as intended.

src/components/layout/about/aboutme/mode-toggle-link.tsx (1)

8-11: Path updates look good.

src/types/presence-bundle.d.ts (1)

1-1: Ambient module path updated correctly.
tsconfig.json defines "paths": { "@/": ["./src/"] } — the ambient module declare for '@/utils/presence-bundle' will resolve; approving changes.

src/components/layout/projects/project-card/tech-icons.tsx (1)

4-6: Do not change the import — SkillEntry is still exported from src/lib/skillicons.ts.

src/types/skillicons.d.ts only defines SkillInfo/SkillIconRef/DashboardIconFormat; SkillEntry and SkillName remain in src/lib/skillicons.ts, so the suggested diff would be incorrect.

Likely an incorrect or invalid review comment.

src/components/layout/experience/work-history/skills-block.tsx (1)

2-2: LGTM on type-only import usage

Importing the value as type-only and referencing it via typeof is correct and keeps runtime lean.

src/components/layout/experience/work-history/company-marker.tsx (1)

4-4: LGTM: type import path update

Type-only import from '@/types/work-experience' matches the refactor plan.

src/app/achievements/page.tsx (1)

5-6: LGTM: split lib vs utils responsibilities

Keeping constants/types in lib and parsing logic in utils is a clean separation.

src/types/navigation-item.d.ts (1)

4-8: Confirm Route-only hrefs are sufficient

If any nav items link externally (http(s), mailto), Route will be too narrow. Widen if needed.

Possible alternative:

-  href: Route
+  href: Route | `https://${string}` | `http://${string}` | `mailto:${string}`
src/components/layout/home/hero/profile-image.tsx (1)

10-15: LGTM: content/theming imports and improved alt text

Imports moved to utils as intended, and using HOME_CONTENT.NAME in alt text is a11y-friendly.

Also applies to: 107-107

src/components/layout/experience/work-history/work-history-item.tsx (1)

1-3: LGTM: import path updates

Imports now correctly reference centralized types and utils.

src/utils/theme-runtime.ts (1)

1-2: LGTM: import consolidation

Pulling values from lib and types from types keeps boundaries clear.

src/lib/navmenu.ts (1)

4-9: LGTM.

Using NavigationItem with internal Route-typed hrefs is appropriate here; all entries are internal.

src/components/ui/map-tooltip.tsx (2)

12-12: Content source rename LGTM.

Switching to HOME_CONTENT aligns with the new content module.


22-24: Sensible defaults from HOME_CONTENT.

Defaults keep the component ergonomic with no breaking API.

src/components/providers/achievements-provider.tsx (1)

3-4: Utils/types relocation looks correct.

Provider references the moved helpers from '@/utils' without changing runtime behavior.

Also applies to: 10-12

src/lib/skillicons.ts (1)

1-1: Type extraction to '@/types' is a good decoupling.

Keeps data shape centralized and reusable.

src/lib/experience.ts (1)

4-4: Approve — WorkExperience.to is optional

WorkExperience declares to?: string in src/types/work-experience.d.ts (line 11), so current roles are allowed.

src/components/ui/theme-toggle.tsx (1)

7-14: Import path migration is consistent — no lib↔utils cycle detected.
Using themes/Theme from '@/lib/themes' and helpers from '@/utils' matches the new boundaries. Search output: utils→lib = 7 matches; lib→utils = 0 matches (no cyclic import).

src/lib/achievements.ts (2)

6-6: Switch to external AchievementDefinition type: LGTM.


8-8: Good constant extraction for cookie name.

Centralizing the cookie name reduces drift across modules.

src/utils/achievements.ts (2)

14-27: Robust cookie parsing: LGTM.

Handles percent‑encoding and malformed JSON gracefully.


29-49: Serialize/validate only well‑formed entries: LGTM.

Good filtering with isValidAchievementId and non‑empty timestamps.

src/lib/themes.ts (1)

5-5: Type extraction to '@/types/themes' looks good — satisfies supported.
package.json reports typescript 5.9.2, so no action required.

src/lib/light-grid.ts (1)

1-1: Good: type‑only import avoids bundling types.

src/types/light-grid.d.ts (1)

1-13: Confirmed: tsconfig maps @/ → ./src/ and .d.ts under src/types are included.**
tsconfig.json contains "baseUrl": "." and "paths": { "@/": ["./src/"] }; the include globs (e.g. "/*.ts", "/.tsx", next-env.d.ts) cover src/types/.d.ts, and only src/types/light-grid.d.ts declares LightGrid.

src/components/layout/about/pets/pet-card/card-back.tsx (1)

5-6: Imports realigned to shared types and utils

Looks consistent with the refactor. Confirm calculateAgeYears/formatDateFull are exported from "@/utils/utils" to avoid runtime import errors.

src/components/layout/about/pets/pet-card/_content.tsx (1)

5-5: Approve — type-only import path switch is correct

tsconfig.json has baseUrl "." with paths "@/" → "./src/", and searches show no lingering Pet type imports from "@/lib/pets" (only a runtime import of PETS at src/components/layout/about/pets/_content.tsx:7).

This comment was marked as spam.

@kiliantyler kiliantyler merged commit af364f5 into main Sep 16, 2025
6 checks passed
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/providers/theme-provider.tsx (1)

243-249: Good: storage event now respects the namespaced key.

Filtering on storageKey('theme') prevents cross-namespace bleed between Storybook/app or tests. Search found an un‑namespaced read at src/utils/theme-script.ts:80 — localStorage.getItem('theme'); no un‑namespaced setItem was found. Add a fallback/migration: on load, if legacy theme exists, coerce it and write the namespaced storageKey('theme') (or explicitly drop legacy support if backward compatibility is not required).

♻️ Duplicate comments (1)
src/components/layout/experience/work-history/collapsible-highlights.tsx (1)

5-6: Good fix: removed invalid typeof on a type‑only import.

The past issue about import type { resolveSkills } ... ReturnType<typeof resolveSkills> is gone here. Thanks for cleaning that up.

🧹 Nitpick comments (5)
src/components/layout/experience/work-history/collapsible-highlights.tsx (1)

5-5: Align SkillEntry import with the new /types boundary.

Given the PR’s goal to move types into src/types, importing SkillEntry from @/lib/skillicons looks inconsistent. Prefer importing from a dedicated type module under @/types (or re‑export it there) to keep runtime utils and types separated.

Apply if src/types/skillicons exists (or add it and re‑export):

-import type { SkillEntry } from '@/lib/skillicons'
+import type { SkillEntry } from '@/types/skillicons'
src/components/layout/about/aboutme/mode-toggle-link.tsx (1)

76-94: Avoid double-firing on keyboard; rely on native button semantics and center the animation for keyboard clicks.

<button> already handles Enter/Space and fires onClick. Adding onKeyDown risks duplicate analytics/style injection if a click is still dispatched on keyup. Simplify by removing onKeyDown and treating keyboard‑initiated clicks (event.detail === 0) as centered origins.

Apply:

@@
-  const handleClick = useCallback(
-    (event?: React.MouseEvent<HTMLButtonElement>) => {
+  const handleClick = useCallback(
+    (event?: React.MouseEvent<HTMLButtonElement>) => {
       if (resolvedTheme === 'dark') return
-
-      const viewportWidth = window.innerWidth || 1
-      const viewportHeight = window.innerHeight || 1
-      const clickX = event?.clientX ?? viewportWidth / 2
-      const clickY = event?.clientY ?? viewportHeight / 2
+      const viewportWidth = window.innerWidth || 1
+      const viewportHeight = window.innerHeight || 1
+      // detail === 0 => keyboard-triggered "click"; center the animation
+      const isKeyboard = !event || event.detail === 0
+      const clickX = isKeyboard ? viewportWidth / 2 : event!.clientX
+      const clickY = isKeyboard ? viewportHeight / 2 : event!.clientY
       const originXPercent = Math.max(0, Math.min(100, (clickX / viewportWidth) * 100))
       const originYPercent = Math.max(0, Math.min(100, (clickY / viewportHeight) * 100))
@@
-  function handleKeyDown(event: React.KeyboardEvent<HTMLButtonElement>) {
-    if (event.key === 'Enter' || event.key === ' ') {
-      event.preventDefault()
-      handleClick()
-    }
-  }
@@
-      onClick={handleClick}
-      onKeyDown={handleKeyDown}>
+      onClick={handleClick}>

Optional: to reflect “no-op when already dark” in UX/a11y, consider disabling the button when resolvedTheme === 'dark'.

Also applies to: 96-101, 110-110

components.json (1)

16-16: TS paths and cn export verified; optional barrel refactor recommended.

  • tsconfig*.json contains "@/" → "./src/".
  • src/utils/utils.ts exists and exports function cn(...).
  • components.json currently maps "utils": "@/utils/utils".
  • Verify no lingering imports from "@/lib/utils" remain (quick search reported ripgrep skipped files).

Apply optional refactor:

-    "utils": "@/utils/utils",
+    "utils": "@/utils",

Add src/utils/index.ts:

export * from "./utils";
src/components/providers/theme-provider.tsx (2)

93-95: Nit: use canonical cookie attribute casing.

Browsers are case-insensitive, but SameSite=Lax; Secure is conventional.

-    document.cookie = `${cookieKey('theme')}=${encodeURIComponent(v)}; path=/; max-age=31536000; samesite=lax${isSecure}`
-    document.cookie = `${cookieKey('themeUpdatedAt')}=${ts}; path=/; max-age=31536000; samesite=lax${isSecure}`
+    document.cookie = `${cookieKey('theme')}=${encodeURIComponent(v)}; Path=/; Max-Age=31536000; SameSite=Lax${isSecure}`
+    document.cookie = `${cookieKey('themeUpdatedAt')}=${ts}; Path=/; Max-Age=31536000; SameSite=Lax${isSecure}`
@@
-    document.cookie = `${cookieKey('systemTheme')}=${encodeURIComponent(value)}; path=/; max-age=31536000; samesite=lax${isSecure}`
+    document.cookie = `${cookieKey('systemTheme')}=${encodeURIComponent(value)}; Path=/; Max-Age=31536000; SameSite=Lax${isSecure}`

Also applies to: 112-113


64-70: Normalize timestamp key naming across cookie/localStorage.
Cookie uses themeUpdatedAt while localStorage uses theme_updatedAt; standardize on themeUpdatedAt and read both keys during a transition window. Locations: src/components/providers/theme-provider.tsx — lines 64, 78, 94, 103.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 62a5879 and f2eddd3.

📒 Files selected for processing (6)
  • components.json (1 hunks)
  • src/components/layout/about/aboutme/mode-toggle-link.tsx (2 hunks)
  • src/components/layout/experience/work-history/collapsible-highlights.tsx (1 hunks)
  • src/components/providers/theme-provider.tsx (2 hunks)
  • src/lib/achievements.ts (2 hunks)
  • src/utils/achievements.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/utils/achievements.ts
  • src/lib/achievements.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js,jsx,cjs,mjs}

📄 CodeRabbit inference engine (.cursor/rules/posthog-integration.mdc)

**/*.{ts,tsx,js,jsx,cjs,mjs}: Never hardcode or hallucinate the PostHog API key; always read it from the value populated in the .env file
Create new feature flag names that are clear and descriptive
Gate flag-dependent code on a check that verifies the flag’s values are valid and expected
If a custom property for a person or event is referenced in two or more files or in two or more callsites within the same file, centralize the property name in an enum (TS) or const object (JS)

Files:

  • src/components/layout/experience/work-history/collapsible-highlights.tsx
  • src/components/providers/theme-provider.tsx
  • src/components/layout/about/aboutme/mode-toggle-link.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/posthog-integration.mdc)

In TypeScript, store feature flag names in an enum with members written in UPPERCASE_WITH_UNDERSCORE and use a consistent naming convention

Files:

  • src/components/layout/experience/work-history/collapsible-highlights.tsx
  • src/components/providers/theme-provider.tsx
  • src/components/layout/about/aboutme/mode-toggle-link.tsx
🧬 Code graph analysis (1)
src/components/layout/experience/work-history/collapsible-highlights.tsx (2)
src/types/work-experience.d.ts (1)
  • WorkExperience (5-18)
src/lib/skillicons.ts (1)
  • SkillEntry (23-23)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (5)
src/components/layout/experience/work-history/collapsible-highlights.tsx (1)

14-15: Align skillsEntries prop types

  • collapsible-highlights.tsx passes skillsEntries: SkillEntry[] (src/components/layout/experience/work-history/collapsible-highlights.tsx).
  • SkillsBlock types skillsEntries as ReturnType<typeof resolveSkills> (src/components/layout/experience/work-history/skills-block.tsx:9).
  • SkillEntry is exported from src/lib/skillicons.ts (export type SkillEntry = ...).
  • Action: make the types consistent — either change SkillsBlock props to skillsEntries: SkillEntry[] (import SkillEntry) or ensure resolveSkills returns SkillEntry[] and use ReturnType<typeof resolveSkills> everywhere.
  • resolveSkills definition was not found by the search — verify its return type before changing.
src/components/layout/about/aboutme/mode-toggle-link.tsx (1)

8-10: Confirm themes module location (lib vs utils).

theme-css is present at src/utils/theme-css.ts, but the canonical themes module remains under @/lib/themes and is still imported from multiple places — either move themes into @/utils for consistency or confirm it should stay in @/lib. Affected files:

  • src/utils/themes.ts
  • src/utils/theme-runtime.ts
  • src/utils/theme-css.ts
  • src/components/ui/theme-toggle.tsx
  • src/components/providers/theme-provider.tsx
  • src/components/layout/about/aboutme/mode-toggle-link.tsx
  • src/components/providers/providers.tsx
  • src/components/providers/achievements-provider.tsx
  • src/components/layout/home/hero/profile-image.tsx
components.json (1)

22-22: No action needed.

Change appears to be a trailing newline/formatting artifact.

src/components/providers/theme-provider.tsx (2)

4-4: Import path migration to utils is correct.

Matches the PR’s directory refactor; no functional change.


19-28: No action required — single ThemeProvider mount; KEY_PREFIX stable.

ThemeProvider is only mounted at src/components/providers/providers.tsx:20 and calls setKeyPrefix(storageNamespace) inside src/components/providers/theme-provider.tsx (no other mounts found); providers.tsx does not pass a storageNamespace so KEY_PREFIX remains the default and cannot be mutated mid-lifecycle in this repo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants