Refactors code to improve maintainability#39
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. Note Other AI code review bot(s) detectedCodeRabbit 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. 📝 WalkthroughWalkthroughRefactors 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (3)
Comment |
There was a problem hiding this comment.
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 nameButton 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.hrefQuickFact.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 withsatisfies+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/libtosrc/utils. Ifsocial-linksis 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.tsto re-exportcnso callsites canimport { 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.
Addloading="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 locationRefactor 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 contentHOME_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/typesFor 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 capitalizationUse “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 itemsNav 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
resolveSkillsas 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: SkillsEntriesAlso applies to: 14-14
src/app/achievements/page.tsx (1)
11-11: Remove unnecessary await on cookies().
cookies()is sync; theawaitadds 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 < 0Also 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 namesAdd 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 namesPETS_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
📒 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.tsxsrc/components/layout/experience/skills/_content.tsxscripts/build-presence-runtime.tssrc/types/navigation-item.d.tssrc/components/layout/header/nav-lava.tsxsrc/components/ui/alert-dialog.tsxsrc/components/ui/section-label.tsxsrc/components/layout/projects/project-card/_content.tsxsrc/components/ui/skill-icons.tsxsrc/types/social-links.d.tssrc/components/layout/about/aboutme/quick-fact.tsxsrc/utils/theme-css.tssrc/components/ui/card.tsxsrc/components/providers/theme-provider.tsxsrc/types/theme-bundle.d.tssrc/components/ui/button.tsxsrc/components/layout/about/aboutme/mode-toggle-link.tsxsrc/types/themes.d.tssrc/components/ui/link-button.tsxsrc/types/projects.d.tssrc/types/achievements.d.tssrc/components/ui/navigation-menu.tsxsrc/types/home-content.d.tssrc/types/work-experience.d.tssrc/components/ui/tooltip.tsxsrc/types/light-grid.d.tssrc/components/ui/flip-indicator.tsxsrc/types/presence-bundle.d.tssrc/components/ui/scroll-area.tsxsrc/components/layout/experience/work-history/company-marker.tsxsrc/components/layout/home/hero/hero-content.tsxsrc/components/layout/pet-gallery/_content.tsxsrc/components/ui/flipping-card.tsxsrc/components/layout/header/mobile-nav-button.tsxsrc/components/layout/projects/project-card/card-back.tsxsrc/types/skillicons.d.tssrc/components/ui/bottom-drawer.tsxsrc/utils/achievements.tssrc/components/layout/projects/projects-grid.tsxsrc/components/layout/header/mobile-nav.tsxsrc/utils/skillicons.tssrc/lib/experience.tssrc/types/quick-facts.d.tssrc/components/ui/separator.tsxeslint.config.jssrc/components/ui/map-tooltip.tsxsrc/components/layout/projects/project-card/tech-icons.tsxsrc/components/layout/experience/work-history/skills-block.tsxsrc/components/layout/experience/work-history/work-history-item.tsxsrc/components/layout/about/pets/pet-card/card-back.tsxsrc/lib/content.tssrc/components/layout/experience/work-history/collapsible-highlights.tsxsrc/components/layout/about/pets/pet-card/_content.tsxsrc/types/pets.d.tssrc/app/achievements/page.tsxsrc/components/ui/skill-icons.stories.tsxsrc/utils/themes.tssrc/lib/navmenu.tssrc/components/ui/dropdown-menu.tsxsrc/components/providers/providers.tsxsrc/lib/social-links.tssrc/components/layout/home/hero/profile-image.tsxsrc/utils/theme-runtime.tssrc/lib/projects.tssrc/components/ui/theme-toggle.tsxsrc/lib/pets.tssrc/lib/light-grid.tssrc/lib/quickfacts.tssrc/lib/themes.tssrc/lib/skillicons.tssrc/components/ui/drawer.tsxscripts/build-theme-runtime.tssrc/components/providers/achievements-provider.tsxsrc/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.tsxsrc/components/layout/experience/skills/_content.tsxscripts/build-presence-runtime.tssrc/types/navigation-item.d.tssrc/components/layout/header/nav-lava.tsxsrc/components/ui/alert-dialog.tsxsrc/components/ui/section-label.tsxsrc/components/layout/projects/project-card/_content.tsxsrc/components/ui/skill-icons.tsxsrc/types/social-links.d.tssrc/components/layout/about/aboutme/quick-fact.tsxsrc/utils/theme-css.tssrc/components/ui/card.tsxsrc/components/providers/theme-provider.tsxsrc/types/theme-bundle.d.tssrc/components/ui/button.tsxsrc/components/layout/about/aboutme/mode-toggle-link.tsxsrc/types/themes.d.tssrc/components/ui/link-button.tsxsrc/types/projects.d.tssrc/types/achievements.d.tssrc/components/ui/navigation-menu.tsxsrc/types/home-content.d.tssrc/types/work-experience.d.tssrc/components/ui/tooltip.tsxsrc/types/light-grid.d.tssrc/components/ui/flip-indicator.tsxsrc/types/presence-bundle.d.tssrc/components/ui/scroll-area.tsxsrc/components/layout/experience/work-history/company-marker.tsxsrc/components/layout/home/hero/hero-content.tsxsrc/components/layout/pet-gallery/_content.tsxsrc/components/ui/flipping-card.tsxsrc/components/layout/header/mobile-nav-button.tsxsrc/components/layout/projects/project-card/card-back.tsxsrc/types/skillicons.d.tssrc/components/ui/bottom-drawer.tsxsrc/utils/achievements.tssrc/components/layout/projects/projects-grid.tsxsrc/components/layout/header/mobile-nav.tsxsrc/utils/skillicons.tssrc/lib/experience.tssrc/types/quick-facts.d.tssrc/components/ui/separator.tsxsrc/components/ui/map-tooltip.tsxsrc/components/layout/projects/project-card/tech-icons.tsxsrc/components/layout/experience/work-history/skills-block.tsxsrc/components/layout/experience/work-history/work-history-item.tsxsrc/components/layout/about/pets/pet-card/card-back.tsxsrc/lib/content.tssrc/components/layout/experience/work-history/collapsible-highlights.tsxsrc/components/layout/about/pets/pet-card/_content.tsxsrc/types/pets.d.tssrc/app/achievements/page.tsxsrc/components/ui/skill-icons.stories.tsxsrc/utils/themes.tssrc/lib/navmenu.tssrc/components/ui/dropdown-menu.tsxsrc/components/providers/providers.tsxsrc/lib/social-links.tssrc/components/layout/home/hero/profile-image.tsxsrc/utils/theme-runtime.tssrc/lib/projects.tssrc/components/ui/theme-toggle.tsxsrc/lib/pets.tssrc/lib/light-grid.tssrc/lib/quickfacts.tssrc/lib/themes.tssrc/lib/skillicons.tssrc/components/ui/drawer.tsxscripts/build-theme-runtime.tssrc/components/providers/achievements-provider.tsxsrc/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 typeLightGridand 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/utilshas 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/utilshas no Node-only deps.src/components/layout/header/nav-lava.tsx (1)
9-9: LGTM.Path update for
cnaligns 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 leftoversMultiple 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 verifiedBuild 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 gendersrc/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 completedMatches 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 || truesrc/components/providers/providers.tsx (1)
4-4: Import move looks goodType-only import now sourced from utils; external API unchanged.
src/components/layout/about/aboutme/mode-toggle-link.tsx (1)
8-11: Import path updates: LGTMUtilities consolidated under /utils; no behavior change.
src/components/layout/experience/work-history/work-history-item.tsx (1)
1-3: Type/util import relocations: LGTMImports 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: LGTMSwitch to src/types/pets maintains type boundaries cleanly.
src/lib/experience.ts (1)
4-4: Type import centralization: LGTMMoving 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 verifiedNo 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 confirmedNavigationItem.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 — LGTMType-only import from '@/types/pets' matches the PR goals.
src/components/providers/achievements-provider.tsx (1)
3-4: Refactor: achievements/theme utils moved — LGTMType-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 — LGTMsrc/components/ui/theme-toggle.tsx (2)
7-14: LGTM — helpers moved to '@/utils'; no stragglers foundRepo 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 — verifiedNEXT_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_CONTENTNo 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.PresenceRuntimeStringifying 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;satisfiessupported.
package.json declares "typescript": "5.9.2", so thesatisfiesoperator (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' | |||
There was a problem hiding this comment.
🧩 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 || trueLength 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 || trueLength 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.
| 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.
| import type { SkillEntry } from '@/lib/skillicons' | ||
| import { getSkillIconUrl } from '@/lib/skillicons' | ||
| import { getSkillIconUrl } from '@/utils/skillicons' | ||
| import Image from 'next/image' |
There was a problem hiding this comment.
🧩 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/**' || trueLength 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.
| 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' |
There was a problem hiding this comment.
🧩 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.tsLength 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 || trueLength 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 || trueLength 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.
| import type { SkillName } from '@/lib/skillicons' | ||
| import type { Route } from 'next' | ||
| import type { StaticImageData } from 'next/image' |
There was a problem hiding this comment.
🧩 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.tsLength 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.
| 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' | |||
There was a problem hiding this comment.
🧩 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 tsxLength 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 || trueLength 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.
| 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' | |||
There was a problem hiding this comment.
🛠️ 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 tsxLength 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 || trueLength 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/**' || trueLength 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.
| 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.
| import type { Route } from 'next' | ||
|
|
||
| export interface SocialLinks { | ||
| GITHUB: Route | ||
| LINKEDIN: Route | ||
| } |
There was a problem hiding this comment.
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.
| 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.
| import type { SkillName } from '@/lib/skillicons' | ||
| import type { Route } from 'next' | ||
| import type { StaticImageData } from 'next/image' | ||
|
|
There was a problem hiding this comment.
🧩 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 | ExternalUrlAlso applies to: 15-16
🏁 Script executed:
#!/bin/bash
rg -nP -C2 'companyUrl:\s*' src | catLength 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 | ExternalUrlAlso 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.
| 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).
There was a problem hiding this comment.
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')) returnsrc/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/repostring(orRoute | 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 URLsSkillInfo.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 noteImport looks correct, and the previously flagged unused cookie constant is no longer present.
🧹 Nitpick comments (28)
src/lib/social-links.ts (1)
3-6: Usesatisfiesfor 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 SocialLinkssrc/components/layout/about/aboutme/quick-fact.tsx (3)
11-37: dl semantics: dt/dd should be direct children of dl.Wrapping
dt/ddin adivbreaks definition list semantics. Consider returning a fragment and moving layout classes to the parent<dl>or ontodt/ddthemselves 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 onkind.
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 thekeyon 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.
QuickFactcomponent renders a toggle whenlabel === 'Mode', ignoringvalue: 'Dark mode'. Either removevaluehere 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 moduleTo 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.CSSPropertiesmay error under the automatic JSX runtime without a React type import. Prefer importing the type and usingCSSProperties.+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 mutationThese 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: MakeunlockHintoptional to avoid forcing placeholder strings.Some achievements won’t have hints. Making this optional reduces friction and avoids meaningless defaults.
- unlockHint: string + unlockHint?: stringsrc/types/projects.d.ts (1)
1-1: Decouple shared types from runtimelibby movingSkillNametosrc/types.
src/typesshould not depend onsrc/lib. Consider exportingSkillName(andSkillEntry) fromsrc/types/skillicons.d.tsto keep layering clean.src/types/work-experience.d.ts (2)
2-2: Allow external/company links by usingstringforcompanyUrl.As with projects,
Routewon’t accepthttps://domains. Switch tostringand remove the now-unusedRouteimport.-import type { Route } from 'next' @@ - companyUrl?: Route + companyUrl?: stringAlso 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 HomeContentsrc/types/themes.d.ts (1)
2-2: BroadenIconComponentto 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: Prefersatisfiesfor 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 imageCurrent 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
📒 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.tsxsrc/components/ui/separator.tsxsrc/components/layout/header/mobile-nav.tsxscripts/build-presence-runtime.tssrc/types/light-grid.d.tssrc/utils/theme-css.tsscripts/build-theme-runtime.tssrc/components/ui/scroll-area.tsxsrc/components/layout/pet-gallery/_content.tsxeslint.config.jssrc/types/quick-facts.d.tssrc/components/layout/projects/project-card/card-back.tsxsrc/components/ui/navigation-menu.tsxsrc/components/ui/button.tsxsrc/components/ui/dropdown-menu.tsxsrc/types/home-content.d.tssrc/components/layout/experience/skills/_content.tsxsrc/components/layout/projects/project-card/_content.tsxsrc/components/layout/about/aboutme/quick-fact.tsxsrc/components/ui/link-button.tsxsrc/components/ui/bottom-drawer.tsxsrc/types/work-experience.d.tssrc/components/providers/providers.tsxsrc/types/achievements.d.tssrc/components/layout/about/pets/pet-card/card-back.tsxsrc/components/layout/home/hero/hero-content.tsxsrc/types/theme-bundle.d.tssrc/components/ui/section-label.tsxsrc/types/social-links.d.tssrc/components/ui/drawer.tsxsrc/utils/themes.tssrc/components/layout/experience/work-history/company-marker.tsxsrc/components/ui/card.tsxsrc/components/layout/projects/projects-grid.tsxsrc/components/layout/header/mobile-nav-button.tsxsrc/types/themes.d.tssrc/types/skillicons.d.tssrc/app/achievements/page.tsxsrc/components/ui/flipping-card.tsxsrc/components/ui/skill-icons.tsxsrc/components/layout/experience/work-history/skills-block.tsxsrc/components/ui/flip-indicator.tsxsrc/components/layout/experience/work-history/work-history-item.tsxsrc/types/presence-bundle.d.tssrc/types/navigation-item.d.tssrc/components/layout/about/aboutme/mode-toggle-link.tsxsrc/utils/achievements.tssrc/components/layout/experience/work-history/collapsible-highlights.tsxsrc/components/layout/about/pets/pet-card/_content.tsxsrc/components/layout/projects/project-card/tech-icons.tsxsrc/components/layout/header/nav-lava.tsxsrc/components/layout/home/hero/profile-image.tsxsrc/lib/light-grid.tssrc/utils/skillicons.tssrc/utils/theme-runtime.tssrc/lib/content.tssrc/lib/quickfacts.tssrc/components/ui/skill-icons.stories.tsxsrc/lib/social-links.tssrc/components/ui/map-tooltip.tsxsrc/components/ui/theme-toggle.tsxsrc/lib/navmenu.tssrc/types/projects.d.tssrc/lib/pets.tssrc/components/providers/theme-provider.tsxsrc/components/ui/alert-dialog.tsxsrc/lib/themes.tssrc/lib/projects.tssrc/lib/experience.tssrc/lib/skillicons.tssrc/components/ui/tooltip.tsxsrc/lib/achievements.tssrc/components/providers/achievements-provider.tsxsrc/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.tsxsrc/components/ui/separator.tsxsrc/components/layout/header/mobile-nav.tsxscripts/build-presence-runtime.tssrc/types/light-grid.d.tssrc/utils/theme-css.tsscripts/build-theme-runtime.tssrc/components/ui/scroll-area.tsxsrc/components/layout/pet-gallery/_content.tsxsrc/types/quick-facts.d.tssrc/components/layout/projects/project-card/card-back.tsxsrc/components/ui/navigation-menu.tsxsrc/components/ui/button.tsxsrc/components/ui/dropdown-menu.tsxsrc/types/home-content.d.tssrc/components/layout/experience/skills/_content.tsxsrc/components/layout/projects/project-card/_content.tsxsrc/components/layout/about/aboutme/quick-fact.tsxsrc/components/ui/link-button.tsxsrc/components/ui/bottom-drawer.tsxsrc/types/work-experience.d.tssrc/components/providers/providers.tsxsrc/types/achievements.d.tssrc/components/layout/about/pets/pet-card/card-back.tsxsrc/components/layout/home/hero/hero-content.tsxsrc/types/theme-bundle.d.tssrc/components/ui/section-label.tsxsrc/types/social-links.d.tssrc/components/ui/drawer.tsxsrc/utils/themes.tssrc/components/layout/experience/work-history/company-marker.tsxsrc/components/ui/card.tsxsrc/components/layout/projects/projects-grid.tsxsrc/components/layout/header/mobile-nav-button.tsxsrc/types/themes.d.tssrc/types/skillicons.d.tssrc/app/achievements/page.tsxsrc/components/ui/flipping-card.tsxsrc/components/ui/skill-icons.tsxsrc/components/layout/experience/work-history/skills-block.tsxsrc/components/ui/flip-indicator.tsxsrc/components/layout/experience/work-history/work-history-item.tsxsrc/types/presence-bundle.d.tssrc/types/navigation-item.d.tssrc/components/layout/about/aboutme/mode-toggle-link.tsxsrc/utils/achievements.tssrc/components/layout/experience/work-history/collapsible-highlights.tsxsrc/components/layout/about/pets/pet-card/_content.tsxsrc/components/layout/projects/project-card/tech-icons.tsxsrc/components/layout/header/nav-lava.tsxsrc/components/layout/home/hero/profile-image.tsxsrc/lib/light-grid.tssrc/utils/skillicons.tssrc/utils/theme-runtime.tssrc/lib/content.tssrc/lib/quickfacts.tssrc/components/ui/skill-icons.stories.tsxsrc/lib/social-links.tssrc/components/ui/map-tooltip.tsxsrc/components/ui/theme-toggle.tsxsrc/lib/navmenu.tssrc/types/projects.d.tssrc/lib/pets.tssrc/components/providers/theme-provider.tsxsrc/components/ui/alert-dialog.tsxsrc/lib/themes.tssrc/lib/projects.tssrc/lib/experience.tssrc/lib/skillicons.tssrc/components/ui/tooltip.tsxsrc/lib/achievements.tssrc/components/providers/achievements-provider.tsxsrc/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 usagesReplace SocialLinks' GITHUB/LINKEDIN with an external-URL string (drop the next import). Apply diff below, then audit other files that still use
Routeand 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
Routeusages 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.comwill not satisfyRoute. After updating the type tostring | URL, these will typecheck and work withnext/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/utilsimports 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/utilslooks 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.
Projectfrom@/types/projectsandresolveSkillsfrom@/utils/skilliconslook 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.
cnnow 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.
UnlockedMapnow 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 usageImporting the value as type-only and referencing it via
typeofis correct and keeps runtime lean.src/components/layout/experience/work-history/company-marker.tsx (1)
4-4: LGTM: type import path updateType-only import from '@/types/work-experience' matches the refactor plan.
src/app/achievements/page.tsx (1)
5-6: LGTM: split lib vs utils responsibilitiesKeeping 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 sufficientIf any nav items link externally (http(s), mailto),
Routewill 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 textImports 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 updatesImports now correctly reference centralized types and utils.
src/utils/theme-runtime.ts (1)
1-2: LGTM: import consolidationPulling values from lib and types from types keeps boundaries clear.
src/lib/navmenu.ts (1)
4-9: LGTM.Using
NavigationItemwith internalRoute-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 optionalWorkExperience 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
isValidAchievementIdand non‑empty timestamps.src/lib/themes.ts (1)
5-5: Type extraction to '@/types/themes' looks good —satisfiessupported.
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 utilsLooks 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 correcttsconfig.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).
src/components/layout/experience/work-history/collapsible-highlights.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
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‑namespacedsetItemwas found. Add a fallback/migration: on load, if legacythemeexists, coerce it and write the namespacedstorageKey('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 invalidtypeofon 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: AlignSkillEntryimport with the new/typesboundary.Given the PR’s goal to move types into
src/types, importingSkillEntryfrom@/lib/skilliconslooks 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/skilliconsexists (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 firesonClick. AddingonKeyDownrisks duplicate analytics/style injection if a click is still dispatched on keyup. Simplify by removingonKeyDownand 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; Secureis 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 usesthemeUpdatedAtwhile localStorage usestheme_updatedAt; standardize onthemeUpdatedAtand 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
📒 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.tsxsrc/components/providers/theme-provider.tsxsrc/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.tsxsrc/components/providers/theme-provider.tsxsrc/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
skillsEntriesasReturnType<typeof resolveSkills>(src/components/layout/experience/work-history/skills-block.tsx:9).SkillEntryis exported from src/lib/skillicons.ts (export type SkillEntry = ...).- Action: make the types consistent — either change SkillsBlock props to
skillsEntries: SkillEntry[](importSkillEntry) or ensureresolveSkillsreturnsSkillEntry[]and useReturnType<typeof resolveSkills>everywhere.resolveSkillsdefinition 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
themesmodule remains under@/lib/themesand is still imported from multiple places — either movethemesinto@/utilsfor 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.
Moves utilities and related types out of
src/libtosrc/utilsandsrc/typesto improve code organization and maintainability.This change involves:
src/utilsdirectory.src/typesdirectory.Summary by CodeRabbit
Refactor
Chores
Documentation