Skip to content

RELEASE-20251223#199

Merged
alsgud8311 merged 8 commits intomainfrom
development
Dec 23, 2025
Merged

RELEASE-20251223#199
alsgud8311 merged 8 commits intomainfrom
development

Conversation

@alsgud8311
Copy link
Contributor

RELEASE-20251223

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @alsgud8311, 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 introduces significant improvements to the application's event handling system, migrating from a custom EventEmitter to a more robust and generic publishEvent/useSubscribeEvents pattern. It also overhauls the resume evaluation feature, enabling users to archive and reuse resumes/portfolios, track evaluation status via polling, and view results on a dedicated page. Additionally, file upload functionality has been enhanced with size validation and better display, and NGINX settings were adjusted for API requests.

Highlights

  • Event System Refactor: The application's event handling has been refactored from a custom EventEmitter utility (interviewEventHelpers) to a more generic and type-safe publishEvent and useSubscribeEvents pattern. This centralizes event management and improves consistency across different domains.
  • Enhanced Resume Evaluation Flow: The resume evaluation feature now supports archiving previously uploaded resumes and portfolios, allowing users to select them from an archive. A new polling mechanism has been implemented to track the status of resume evaluations (pending, completed, failed), and a dedicated page for displaying detailed evaluation results has been added.
  • Improved File Upload Component: The FileField UI component has been enhanced to include client-side validation for maximum file size (10MB). It also now displays a displayName for selected files, improving the user experience when choosing files, especially from an archive.
  • NGINX Configuration Updates: The NGINX development configuration (nginx.dev.conf) has been updated to remove an unused upstream definition, increase the client_max_body_size to 25MB for API requests, and standardize the proxy_pass directive to a generic api_server.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Ignored Files
  • Ignored by pattern: .github/workflows/** (2)
    • .github/workflows/deploy-nest-dev.yml
    • .github/workflows/deploy-nest-prod.yml
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.

@github-actions
Copy link

🛠️ Build Summary

Status: ✅ SUCCESS
Duration: 58초
Exit Code: 0
Commit: d31d8ce

📋 Build Output (마지막 45줄)

├ ○ /500 (1525 ms)                           757 B         339 kB
├ ƒ /api/auth/logout                           0 B         339 kB
├ ƒ /dashboard                             7.37 kB         640 kB
├ ƒ /interviews                            7.52 kB         353 kB
├ ƒ /interviews/[interviewId]              9.17 kB         611 kB
├ ƒ /interviews/[interviewId]/result        5.8 kB         371 kB
├ ○ /layout (1524 ms)                        542 B         339 kB
├   └ css/2af4d3721e97fa9b.css               280 B
├ ƒ /login                                 2.59 kB         341 kB
├ ƒ /login/callback                        2.79 kB         341 kB
├ ƒ /login/google/callback                 2.79 kB         341 kB
├ ƒ /login/profile                         5.33 kB         374 kB
├ ƒ /members/[memberId]                    4.79 kB         347 kB
├ ƒ /members/[memberId]/sitemap.xml          429 B         339 kB
├ ƒ /members/interviews/[interviewId]      7.66 kB         346 kB
├ ƒ /purchase                              7.81 kB         635 kB
├ ƒ /purchase/confirm                      2.42 kB         343 kB
├ ○ /purchase/error (1524 ms)                801 B         341 kB
├ ƒ /rank                                  4.12 kB         346 kB
├ ƒ /recruit                               6.81 kB         352 kB
├ ƒ /resume                                1.75 kB         449 kB
├ ƒ /resume/eval                           4.61 kB         346 kB
├ ƒ /resume/eval/[evaluationId]/result     3.63 kB         447 kB
├ ƒ /resume/eval/demo                      3.55 kB         450 kB
├ ƒ /server-sitemap.xml                      422 B         339 kB
├ ƒ /sitemap/members.xml                     424 B         339 kB
├ ƒ /sitemap/rank.xml                        421 B         339 kB
├ ƒ /terms/privacy                         6.01 kB         345 kB
└ ƒ /terms/termsofuse                      5.84 kB         344 kB
+ First Load JS shared by all               354 kB
  ├ chunks/framework-0859aa6f37f5c68e.js   57.6 kB
  ├ chunks/main-a6bf1303997194db.js         176 kB
  ├ chunks/pages/_app-ce75d0c1d51e3913.js   103 kB
  ├ css/f1869bbe4b7fcb32.css               15.9 kB
  └ other shared chunks (total)            1.97 kB

ƒ Middleware                               96.2 kB

○  (Static)   prerendered as static content
ƒ  (Dynamic)  server-rendered on demand

   Memory usage report:
    - Total time spent in GC: 392.96ms
    - Peak heap usage: 68.83 MB
    - Peak RSS usage: 679.50 MB

🤖 Generated by GitHub Actions at Tue Dec 23 13:31:19 UTC 2025

Copy link
Contributor

@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 introduces a significant refactoring of the event handling system to a more generic, centralized model, and adds a major new feature for asynchronous resume evaluation, including archiving and polling for results. These are excellent architectural improvements. However, the review identified several high and critical severity issues. The event emitter refactoring has introduced bugs related to stale closures in React hooks and has critically broken the communication with the native layer in the webview application. The new resume evaluation form also contains bugs in its validation logic and state management that affect usability. Additionally, a bug was found in the API retry logic. It's recommended to address these issues before merging.

Comment on lines +1 to +20
import { publishEvent, useSubscribeEvents } from "@/utils/events";
import { InterviewEventType, InterviewEventPayloads } from "@kokomen/types";
import { DependencyList } from "react";

export default interviewEventHelpers;
export function useInterviewEvent<K extends InterviewEventType>(
event: K,
handler: InterviewEventPayloads[K] extends undefined
? () => void
: // eslint-disable-next-line no-unused-vars
(payload: InterviewEventPayloads[K]) => void,
// eslint-disable-next-line no-unused-vars
deps: DependencyList = []
): void {
useSubscribeEvents<InterviewEventType>([{ event, handler }], []);
}

export const publishInterviewEvent = publishEvent<
InterviewEventType,
InterviewEventPayloads
>();
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

This refactoring has critically broken the communication between the webview and the React Native host application. The previous implementation correctly used window.ReactNativeWebView.postMessage to send events to the native layer for speech recognition. The new implementation uses a GlobalEventBus which is confined to the webview's JavaScript context. As a result, events like interview:startVoiceRecognition will be published but never reach the native side, and speech recognition will not work. You should revert to using window.ReactNativeWebView.postMessage for this webview-specific implementation.

Comment on lines +13 to +16
const eventEmitter = useSubscribeEvents<InterviewEventType>(
[{ event, handler }],
[]
);
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The deps argument from useInterviewEvent is not being passed down to useSubscribeEvents. It's hardcoded as an empty array []. This will cause the event handler to have stale closures if it depends on any props or state that change over time, as the useEffect in useSubscribeEvents will not re-run to subscribe the new handler with the updated dependencies.

Suggested change
const eventEmitter = useSubscribeEvents<InterviewEventType>(
[{ event, handler }],
[]
);
const eventEmitter = useSubscribeEvents<InterviewEventType>(
[{ event, handler }],
deps
);

}

await exponentialDelay(retryCount);
return resumeServerInstance.request(error.config as AxiosRequestConfig);
Copy link
Contributor

Choose a reason for hiding this comment

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

high

In the onRejectedPolling interceptor for resumePollingServerInstance, the request is being retried using resumeServerInstance. These two instances have different baseURLs (/resumes/evaluations vs. /resumes). This will cause the retry request to be sent to the wrong endpoint. The retry should use resumePollingServerInstance to ensure it hits the correct URL.

Suggested change
return resumeServerInstance.request(error.config as AxiosRequestConfig);
return resumePollingServerInstance.request(error.config as AxiosRequestConfig);

Comment on lines +37 to +44
.refine(
(data) =>
data.portfolio_id || (data.portfolio && data.portfolio.length > 0),
{
message: "포트폴리오를 선택해주세요",
path: ["portfolio"]
}
);
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The Zod schema's .refine for the portfolio makes it a required field, which seems unintentional as portfolios are typically optional. If a user doesn't select an archived portfolio (portfolio_id is undefined), they are forced to upload a portfolio file. This contradicts the optional nature of the field. To fix this, this refinement check should be removed.

Comment on lines +60 to +71
useEffect(() => {
const resume = form.getValues("resume");
const portfolio = form.getValues("portfolio");
if (resume instanceof FileList && resume.length > 0) {
setDisplayName({ ...displayName, resume: "" });
form.setValue("resume_id", "");
}
if (portfolio instanceof FileList && portfolio.length > 0) {
setDisplayName({ ...displayName, portfolio: "" });
form.setValue("portfolio_id", "");
}
}, [form.watch("resume_id"), form.watch("portfolio_id")]);
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The logic within this useEffect is flawed. When a user selects an archived resume (which sets resume_id), this effect is triggered. If a file was previously selected in the file input, the effect will immediately clear the resume_id that was just set, making it impossible to use archived resumes if a file has ever been selected.

The logic to clear one input type when the other is used should be handled in the respective event handlers. For example, in onclickArchiveButton, when setting a resume_id, you should also clear the file input using form.resetField("resume"). This useEffect should be removed.

Comment on lines +13 to +16
const eventEmitter = useSubscribeEvents<ReportEventType>(
[{ event, handler }],
[]
);
Copy link
Contributor

Choose a reason for hiding this comment

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

high

There is a similar issue here as in interviewEventEmitter.ts. The deps argument from useReportevent is not being passed to useSubscribeEvents. It's hardcoded as an empty array [], which can lead to stale closures in the event handler.

Suggested change
const eventEmitter = useSubscribeEvents<ReportEventType>(
[{ event, handler }],
[]
);
const eventEmitter = useSubscribeEvents<ReportEventType>(
[{ event, handler }],
deps
);

Comment on lines +19 to +20
robots.txt
sitemap.xml No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

robots.txt and sitemap.xml are generally not included in .gitignore as they are crucial for search engine optimization (SEO). If these files are generated during the build process and are meant to be publicly accessible, they should be committed or be part of the build output that gets deployed. Ignoring them might prevent search engine crawlers from finding and indexing your site correctly. Please verify if ignoring these files is the intended behavior.

Comment on lines +120 to +125
const list =
type === "ALL"
? data?.resumes
: type === "RESUME"
? data?.resumes
: data?.portfolios;
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The logic to determine the list to render is a bit complex and can be simplified for better readability and maintainability.

  const list =
    type === "PORTFOLIO" ? data?.portfolios : data?.resumes;

Comment on lines +158 to +165
onclickArchivedItem(
type === "ALL"
? "RESUME"
: type === "RESUME"
? "RESUME"
: "PORTFOLIO",
item
);
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The logic to determine the type argument for onclickArchivedItem is convoluted and can be simplified for better readability.

              onclickArchivedItem(
                type === "PORTFOLIO" ? "PORTFOLIO" : "RESUME",
                item
              );

import { UseFormRegisterReturn } from "react-hook-form";
import { cn } from "../../utils/index.ts";

const MAX_FILE_SIZE = 5 * 1024 * 1024; // 10MB
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The comment for MAX_FILE_SIZE states the limit is 10MB, but the value is set to 5MB (5 * 1024 * 1024). This discrepancy can be confusing for future developers. Please update either the value or the comment to be consistent.

Suggested change
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 10MB
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB

@github-actions
Copy link

🚀 Lighthouse Report for TEST1

📅 Date: 12/23/2025

Category Score
🔴 Performance 18
🟢 Accessibility 96
🟢 Best Practices 96
🟢 SEO 100

📊 Performance Details

Metric Score Value
🟢 First Contentful Paint 100 1.0 s
🔴 Largest Contentful Paint 6 7.1 s
🔴 Cumulative Layout Shift 20 0.447

🚀 Lighthouse Report for TEST2

📅 Date: 12/23/2025

Category Score
🟠 Performance 62
🟢 Accessibility 96
🟢 Best Practices 96
🟢 SEO 100

📊 Performance Details

Metric Score Value
🟢 First Contentful Paint 100 0.9 s
🟢 Largest Contentful Paint 98 1.9 s
🟢 Cumulative Layout Shift 100 0

🚀 Lighthouse Report for TEST3

📅 Date: 12/23/2025

Category Score
🔴 Performance 22
🟢 Accessibility 96
🟢 Best Practices 96
🟢 SEO 100

📊 Performance Details

Metric Score Value
🟢 First Contentful Paint 100 0.9 s
🔴 Largest Contentful Paint 16 5.7 s
🔴 Cumulative Layout Shift 20 0.447

🚀 Lighthouse Report for TEST4

📅 Date: 12/23/2025

Category Score
🔴 Performance 41
🟢 Accessibility 96
🟢 Best Practices 96
🟢 SEO 100

📊 Performance Details

Metric Score Value
🟢 First Contentful Paint 100 0.9 s
🔴 Largest Contentful Paint 15 5.8 s
🟢 Cumulative Layout Shift 100 0

🚀 Lighthouse Report for TEST5

📅 Date: 12/23/2025

Category Score
🔴 Performance 41
🟢 Accessibility 96
🟢 Best Practices 96
🟢 SEO 100

📊 Performance Details

Metric Score Value
🟢 First Contentful Paint 100 1.0 s
🔴 Largest Contentful Paint 15 5.8 s
🟢 Cumulative Layout Shift 100 0

@alsgud8311 alsgud8311 merged commit 7703739 into main Dec 23, 2025
8 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants