Skip to content

Conversation

@gocanto
Copy link
Contributor

@gocanto gocanto commented Oct 2, 2025

Summary

  • ensure project card skeletons share the same minimum height as the rendered cards
  • keep skeleton placeholders visible when no projects are returned so the layout remains stable

Testing

  • not run (npm install fails with 403 for @babel/eslint-parser)

https://chatgpt.com/codex/tasks/task_e_68de411c80308333ba740de925adce62

Summary by CodeRabbit

  • New Features

    • Loading skeletons added across Home, About, Resume, Projects, and Talks for smoother perceived performance.
    • Skills widget now shows a skeleton while profile data loads or is unavailable.
    • Talk list displays skeleton cards during loading or when empty.
    • Project cards and skeletons respect animation state during loading.
  • Style

    • Unified project card layout with a new content container and consistent minimum height.
  • Tests

    • Added tests covering skeleton rendering scenarios.
  • Chores

    • Updated linting configuration, formatting rules, and developer commands.

@coderabbitai
Copy link

coderabbitai bot commented Oct 2, 2025

Note

Other AI code review bot(s) detected

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

Walkthrough

Introduces loading skeletons for skills, projects, and talks across multiple pages. Adds parallel profile/projects loading in ProjectsPage. Updates project and talk card skeleton components and styles, including a new CSS class. Adjusts tests for new loading behavior. Updates Makefile and ESLint configuration.

Changes

Cohort / File(s) Summary
Pages: Skills loading skeletons
src/pages/HomePage.vue, src/pages/AboutPage.vue, src/pages/ResumePage.vue
Add isLoadingProfile state; conditionally render WidgetSkillsSkeletonPartial when loading or no profile; import skeleton partial; finalize loading state in finally blocks.
Projects: Parallel loads and skeletons
src/pages/ProjectsPage.vue, tests/pages/ProjectsPage.test.ts
Split loading into loadProfile and loadProjects; run in parallel via Promise.all; show ProjectCardSkeletonPartial when loading/empty; add isLoadingProfile; update tests to mock, await ticks, and assert skeleton rendering/animation state.
Talks: Skeletons and loading state
src/partials/TalksPartial.vue, src/partials/TalkCardSkeletonPartial.vue
New TalkCardSkeletonPartial component; add isLoadingTalks; show 4 skeletons when loading/empty; finalize loading in onMounted finally; minor class fix for image width.
Project card components
src/partials/ProjectCardPartial.vue, src/partials/ProjectCardSkeletonPartial.vue
Change container class to project-card-content; ProjectCardSkeletonPartial now accepts isAnimated prop (default true) and computes animationClass; removes always-on animate-pulse; wraps content in project-card-content.
CSS support and class additions
src/css/support/blog.css, src/css/style.css, src/css/support/theme.css
Add .project-card-content class applying flex/height/min-height; formatting-only updates to style.css and theme.css; switch to single quotes in CSS imports.
Tooling: Makefile and ESLint
Makefile, eslint.config.js
Makefile: drop .env include; broaden prettier glob; guarded lint-fix path. ESLint: replace base with custom baseRecommendedConfig; add TS/.d.ts specific overrides; adjust Vue+TS integration; keep Prettier integration.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant P as ProjectsPage
  participant API as API
  Note over P: onMounted
  P->>P: isLoadingProfile = true<br/>isLoadingProjects = true
  par Load profile
    P->>API: getProfile()
    API-->>P: profile or error
    P->>P: finally { isLoadingProfile = false }
  and Load projects
    P->>API: getProjects()
    API-->>P: projects or error
    P->>P: finally { isLoadingProjects = false }
  end
  alt Loading or empty
    P-->>U: Render skeletons (skills/projects)
  else Data available
    P-->>U: Render skills and project cards
  end
Loading
sequenceDiagram
  autonumber
  participant U as User
  participant T as TalksPartial
  participant API as API
  Note over T: onMounted
  T->>T: isLoadingTalks = true
  T->>API: getTalks()
  API-->>T: talks or error
  T->>T: finally { isLoadingTalks = false }
  alt Loading or empty
    T-->>U: Show 4 TalkCardSkeletonPartial
  else Has talks
    T-->>U: Render talk cards
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

codex

Poem

I thump my paws on loading ground,
Skeletons dance with a whispering sound.
Projects, talks, and skills in queue—
Hop, hop, they render into view!
CSS burrow neat and tight,
Linting carrots lined just right.
🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The title “feat: Skeletons cards + Format” combines two disparate concerns, referencing formatting changes that are not central to the PR objectives and using vague phrasing rather than clearly summarizing the key feature of matching skeleton card heights. Rename the title to focus on the main change, for example: “feat: add project skeleton cards matching project card height.”
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit's high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/ensure-box-skeletons-match-height-of-boxes

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0643f0e and 9b90617.

📒 Files selected for processing (9)
  • Makefile (2 hunks)
  • eslint.config.js (3 hunks)
  • src/css/style.css (3 hunks)
  • src/css/support/blog.css (3 hunks)
  • src/css/support/theme.css (1 hunks)
  • src/pages/ProjectsPage.vue (5 hunks)
  • src/partials/ProjectCardSkeletonPartial.vue (2 hunks)
  • src/partials/TalkCardSkeletonPartial.vue (1 hunks)
  • src/partials/TalksPartial.vue (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/css/support/theme.css
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/partials/TalksPartial.vue
🧰 Additional context used
🪛 Biome (2.1.2)
src/css/style.css

[error] 25-25: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)

🔇 Additional comments (9)
Makefile (1)

23-43: Prettier glob + guarded lint look good

Including CSS in the Prettier glob and short-circuiting eslint --fix when parsers/plugins aren’t installed keeps formatting predictable without breaking local workflows where the install still fails. Nicely done.

src/partials/TalkCardSkeletonPartial.vue (1)

1-25: Skeleton animation toggle implemented cleanly

The optional isAnimated prop, lazy default via nullish coalescing, and decorative aria-hidden wrapper address the earlier review concern perfectly.

src/css/support/blog.css (1)

162-164: New project-card-content hook fits the layout goal

Centralizing the shared flex/min-height styling in .project-card-content keeps the cards and skeletons aligned without duplicating utilities elsewhere.

src/pages/ProjectsPage.vue (1)

34-134: Loading/empty flow and parallel fetch look solid

Rendering four skeletons whenever projects are loading or ultimately empty keeps the grid height steady, and the :is-animated guard cleanly stops the pulse once the fetch settles. Running loadProfile and loadProjects in parallel with error isolation improves responsiveness without compromising logging.

eslint.config.js (3)

98-137: TypeScript configuration looks comprehensive.

The dedicated TypeScript configuration block properly:

  • Sets up the TypeScript parser
  • Applies recommended rules from pluginTypeScript.configs.recommended
  • Disables conflicting base rules (no-unused-vars, no-undef)
  • Disables several type-safety rules (explicit-any, unsafe operations)

Note: The disabled no-await-in-loop rule (line 119) differs from the base config where it's set to 'warn'. This is appropriate for TypeScript where async patterns are more sophisticated.


177-183: LGTM! Declaration file overrides are appropriate.

The .d.ts block correctly relaxes rules that would be too strict for type declaration files, where empty object types and any are sometimes necessary.


10-72: Ensure manual ESLint rules stay in sync with official recommended config: Add @eslint/js as a devDependency and compare baseRecommendedConfig.rules against js.configs.recommended.rules after each ESLint upgrade.

src/partials/ProjectCardSkeletonPartial.vue (2)

26-31: LGTM! Animation control implementation is clean.

The isAnimated prop with a default of true provides good flexibility:

  • Maintains backward compatibility (existing usage gets animation by default)
  • Allows disabling animation when needed (e.g., when testing or when many skeletons are shown)
  • The computed animationClass correctly returns 'animate-pulse' or null

2-3: Min-height is now applied in the .project-card-content shared CSS class


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@gocanto gocanto marked this pull request as draft October 2, 2025 09:14
@gemini-code-assist
Copy link

Summary of Changes

Hello @gocanto, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses layout instability on the projects page by standardizing the minimum height of project cards and their loading skeletons. It also improves the user experience by ensuring that the layout remains consistent and stable, even when no projects are available, by displaying skeleton placeholders rather than an abrupt empty state.

Highlights

  • Consistent Card Height: Ensured that both rendered project cards and their skeleton placeholders share a consistent minimum height to prevent layout shifts.
  • Stable Layout for Empty States: Modified the project listing page to display skeleton placeholders even when no projects are returned, maintaining a stable layout instead of showing an empty message.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request aims to stabilize the project page layout by ensuring project cards and their skeletons have a matching minimum height, and by displaying skeletons when no projects are available. The changes successfully implement this. My review includes suggestions to simplify some of the new template logic in ProjectsPage.vue, improve the user experience for the empty state, and address missing test coverage for the new behavior. I've also pointed out an opportunity to reduce code duplication by creating a shared CSS class for card styles.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/pages/ProjectsPage.vue (1)

34-40: Unified conditional simplifies template logic.

The consolidated rendering logic correctly shows skeletons during loading and renders project cards when data is available. However, two points to consider:

  1. Simplify the second condition: Line 37's v-else-if="projects.length > 0" is redundant. Since the first v-if already handles projects.length === 0, the second template will only be reached when projects.length > 0. Simplify to v-else.

  2. Empty-state UX: Showing skeletons when projects.length === 0 (no projects returned) might confuse users who expect a message like "No projects available." Is this intentional to maintain layout consistency, or should there be a distinct empty-state message?

Apply this diff to simplify the conditional:

-                                                                                                <template v-else-if="projects.length > 0">
+                                                                                                <template v-else>
                                                                                                         <ProjectCardPartial v-for="project in projects" :key="project.uuid" :item="project" />
                                                                                                 </template>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a2acf8e and fb26ab1.

📒 Files selected for processing (3)
  • src/pages/ProjectsPage.vue (1 hunks)
  • src/partials/ProjectCardPartial.vue (1 hunks)
  • src/partials/ProjectCardSkeletonPartial.vue (2 hunks)
🔇 Additional comments (2)
src/partials/ProjectCardPartial.vue (1)

9-9: LGTM! Minimum height ensures layout stability.

Adding min-h-[220px] to the card container aligns with the skeleton partial and prevents layout shift during loading/empty states.

src/partials/ProjectCardSkeletonPartial.vue (1)

3-3: LGTM! Skeleton now matches card minimum height.

Adding min-h-[220px] ensures the skeleton placeholder maintains the same dimensions as the rendered project card, preventing layout shift.

@gocanto gocanto marked this pull request as ready for review October 3, 2025 01:33
@gocanto gocanto removed the codex label Oct 3, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
src/pages/ProjectsPage.vue (1)

34-44: Restore an explicit empty-state message

When the fetch resolves with zero projects, we now leave people with four inert skeletons and no text. That’s a regression from the previous “No projects are available…” copy and leaves screen-reader users with silence. Please keep the skeletons for layout stability but present a real empty-state message once loading finishes. For example:

                                 <template v-if="isLoadingProjects || projects.length === 0">
                                         <ProjectCardSkeletonPartial
                                                 v-for="index in 4"
                                                 :key="`projects-page-skeleton-${index}`"
                                                 :is-animated="isLoadingProjects"
                                         />
+                                        <p
+                                                v-if="!isLoadingProjects && projects.length === 0"
+                                                class="text-sm text-slate-500 mt-3"
+                                        >
+                                                No projects are available right now. Please check back soon.
+                                        </p>
                                 </template>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb26ab1 and 3b860a2.

📒 Files selected for processing (11)
  • src/css/support/utility-patterns.css (1 hunks)
  • src/pages/AboutPage.vue (4 hunks)
  • src/pages/HomePage.vue (4 hunks)
  • src/pages/ProjectsPage.vue (5 hunks)
  • src/pages/ResumePage.vue (4 hunks)
  • src/partials/ProjectCardPartial.vue (1 hunks)
  • src/partials/ProjectCardSkeletonPartial.vue (2 hunks)
  • src/partials/TalkCardSkeletonPartial.vue (1 hunks)
  • src/partials/TalksPartial.vue (2 hunks)
  • src/partials/WidgetSkillsSkeletonPartial.vue (1 hunks)
  • tests/pages/ProjectsPage.test.ts (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/partials/WidgetSkillsSkeletonPartial.vue
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/partials/ProjectCardSkeletonPartial.vue
🧰 Additional context used
🧬 Code graph analysis (1)
tests/pages/ProjectsPage.test.ts (1)
src/stores/api/store.ts (2)
  • getProfile (42-50)
  • getProjects (69-77)
🪛 GitHub Actions: Unit Tests
tests/pages/ProjectsPage.test.ts

[error] 97-97: AssertionError: expected [ 'rounded-lg', 'border', …(7) ] to not include 'animate-pulse'

🪛 GitHub Check: vitest
tests/pages/ProjectsPage.test.ts

[failure] 97-97: tests/pages/ProjectsPage.test.ts > ProjectsPage > renders static skeletons when no projects are returned
AssertionError: expected [ 'rounded-lg', 'border', …(7) ] to not include 'animate-pulse'
❯ tests/pages/ProjectsPage.test.ts:97:56
❯ tests/pages/ProjectsPage.test.ts:96:27

🔇 Additional comments (4)
tests/pages/ProjectsPage.test.ts (4)

3-6: LGTM!

The new imports support the test structure refactoring (beforeEach) and the skeleton rendering test (ProjectCardSkeletonPartial).


40-49: LGTM!

The beforeEach hook properly resets and configures mocks for test isolation, and the mock return values match the expected API response structure.


55-74: LGTM!

The test improvements include a more realistic ProjectCardPartial stub that renders actual project titles, and the addition of FooterPartial to the stubs aligns with the component's layout structure.


101-119: LGTM!

The error handling test correctly verifies that API failures are properly handled and logged via debugError.

@gocanto gocanto marked this pull request as draft October 3, 2025 01:50
@gocanto gocanto added the testing label Oct 3, 2025
@gocanto gocanto marked this pull request as ready for review October 3, 2025 02:39
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/pages/ProjectsPage.vue (1)

118-130: Fixed: reset isLoadingProjects in finally

This addresses the earlier test failure about animate-pulse persisting. Nice.

🧹 Nitpick comments (5)
src/partials/TalksPartial.vue (1)

7-9: Unify skeleton animation logic with Projects

To keep behavior consistent and avoid edge cases during refresh, pass only the loading flag.

- <TalkCardSkeletonPartial v-for="index in 4" :key="`talk-skeleton-${index}`" :is-animated="isLoadingTalks && talks.length === 0" />
+ <TalkCardSkeletonPartial v-for="index in 4" :key="`talk-skeleton-${index}`" :is-animated="isLoadingTalks" />
src/pages/AboutPage.vue (1)

84-147: Consider extracting a reusable profile loader

Profile fetch + isLoadingProfile pattern repeats across pages; a small useProfile composable would reduce duplication.

src/pages/ProjectsPage.vue (1)

34-39: Correct empty/loading skeleton behavior; add a11y hint (optional)

Showing skeletons when loading or empty is correct; consider an sr-only message for screen readers when not loading and empty.

<template v-if="isLoadingProjects || projects.length === 0">
  <ProjectCardSkeletonPartial v-for="index in 4" :key="`projects-page-skeleton-${index}`" :is-animated="isLoadingProjects" />
+ <div v-if="!isLoadingProjects" class="sr-only">No projects found</div>
</template>
src/partials/ProjectCardSkeletonPartial.vue (2)

2-5: Respect prefers-reduced-motion and hide decorative skeletons from screen readers

Use motion-safe variant and mark the skeleton container aria-hidden. Keeps animation off for users who prefer reduced motion and avoids SR noise.

-  <div
-    class="rounded-lg border border-slate-200 dark:border-slate-800 dark:bg-gradient-to-t dark:from-slate-800 dark:to-slate-800/30 p-5"
-    :class="[{ 'animate-pulse': props.isAnimated }, props.wrapperClass]"
-  >
+  <div
+    class="rounded-lg border border-slate-200 dark:border-slate-800 dark:bg-gradient-to-t dark:from-slate-800 dark:to-slate-800/30 p-5"
+    :class="[{ 'motion-safe:animate-pulse': props.isAnimated }, props.wrapperClass]"
+    aria-hidden="true"
+  >

27-35: Name the component; broaden wrapperClass typing; consider deprecating wrapperClass in favor of native class merging

Vue auto-merges class attrs onto the root, so wrapperClass can be optional long‑term. Meanwhile, add a component name and widen typing.

+defineOptions({ name: 'ProjectCardSkeletonPartial' });
 const props = withDefaults(
   defineProps<{
-    wrapperClass?: string;
+    wrapperClass?: string | string[] | Record<string, boolean>;
     isAnimated?: boolean;
   }>(),
   {
     isAnimated: true,
   },
 );

Optional next step: deprecate wrapperClass and rely on passing class directly to the component; remove the prop in a future major.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b860a2 and 0643f0e.

📒 Files selected for processing (10)
  • src/pages/AboutPage.vue (4 hunks)
  • src/pages/HomePage.vue (4 hunks)
  • src/pages/ProjectsPage.vue (5 hunks)
  • src/pages/ResumePage.vue (4 hunks)
  • src/partials/ProjectCardPartial.vue (1 hunks)
  • src/partials/ProjectCardSkeletonPartial.vue (2 hunks)
  • src/partials/TalkCardSkeletonPartial.vue (1 hunks)
  • src/partials/TalksPartial.vue (3 hunks)
  • src/partials/WidgetSkillsSkeletonPartial.vue (1 hunks)
  • tests/pages/ProjectsPage.test.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/partials/TalkCardSkeletonPartial.vue
  • src/partials/WidgetSkillsSkeletonPartial.vue
  • src/pages/ResumePage.vue
  • src/partials/ProjectCardPartial.vue
🧰 Additional context used
🧬 Code graph analysis (1)
tests/pages/ProjectsPage.test.ts (1)
src/stores/api/store.ts (2)
  • getProfile (42-50)
  • getProjects (69-77)
🔇 Additional comments (8)
src/partials/TalksPartial.vue (1)

50-67: Loading state wiring looks good

Importing the skeleton and resetting isLoadingTalks in finally is correct.

src/pages/AboutPage.vue (1)

71-73: Solid loading/empty-state handling for skills

Conditional render with skeleton and finally-reset for isLoadingProfile is correct and consistent with the new pattern.

Also applies to: 143-145

tests/pages/ProjectsPage.test.ts (2)

77-101: Good coverage for static skeletons on empty projects

Test correctly ensures four skeletons render without animate-pulse after load completes; nextTick after flushPromises is appropriate.


119-119: Sync after promise resolution

Adding nextTick after flushPromises avoids timing flakiness; good call.

src/pages/HomePage.vue (1)

29-31: Skills skeleton + loading flag finalized

Pattern matches other pages and correctly resets loading in finally.

Also applies to: 52-53, 62-63, 93-94

src/pages/ProjectsPage.vue (2)

104-116: Profile loader with finally is correct

Error is handled and the loading flag is cleared reliably.


132-134: Parallelized loading via Promise.all

Good optimization and keeps UI responsive.

src/partials/ProjectCardSkeletonPartial.vue (1)

6-6: .project-card-content utility verified

It’s defined with min-h-[220px] in src/css/support/utility-patterns.css and applied in src/partials/ProjectCardPartial.vue.

@gocanto gocanto marked this pull request as draft October 3, 2025 02:50
@gocanto gocanto removed the testing label Oct 3, 2025
@gocanto gocanto added the testing label Oct 3, 2025
@gocanto gocanto changed the title Ensure project skeleton cards match layout feat: Skeletons cards + Format Oct 3, 2025
@gocanto gocanto marked this pull request as ready for review October 3, 2025 03:40
@gocanto gocanto merged commit ad0341a into main Oct 3, 2025
5 checks passed
@gocanto gocanto deleted the codex/ensure-box-skeletons-match-height-of-boxes branch October 3, 2025 03:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants