Conversation
…mprove # Conflicts: # dpbr_front/app/src/lib/components/BottomSheetLogin.svelte
Feature/UI save card improve
feat: add paginated API clients and infinite scroll UI
…-newline fix: member detail fallback and team-message newline rendering
Feature/frontend UI updates
📝 WalkthroughSummary by CodeRabbit새로운 기능
버그 수정
스타일
Walkthrough페이지네이션 API 엔드포인트를 추가하고 무한 스크롤 기능을 구현하며, 여러 컴포넌트의 UI/스타일을 업데이트하고, 로그인 오류 처리 플로우를 개선하고, 멤버 저장 페이지 레이아웃을 재설계합니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User as 사용자
participant Client as 클라이언트
participant Observer as IntersectionObserver
participant API as API 서버
participant DB as Database
User->>Client: 페이지 접속
Client->>API: getCharactersPaginated(page=1)
API->>DB: 캐릭터 조회 (limit: 10)
DB-->>API: 캐릭터 목록 반환
API-->>Client: items[], total, page, limit
Client->>Client: 캐릭터 렌더링
Client->>Observer: sentinel 요소 관찰 시작
User->>Client: 페이지 스크롤
Observer->>Observer: sentinel 교차 감지
Observer->>Client: 콜백 트리거
Client->>API: getCharactersPaginated(page=2)
API->>DB: 다음 페이지 캐릭터 조회
DB-->>API: 캐릭터 목록 반환
API-->>Client: 다음 아이템 반환
Client->>Client: 기존 목록에 아이템 추가
Client->>Client: sentinel 위치 업데이트
alt 더 이상 데이터 없음
Client->>Client: hasMore = false
Client->>Observer: sentinel 관찰 중지
else 데이터 존재
Observer->>Observer: sentinel 재관찰
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
Summary of ChangesHello, 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! 이 PR은 사용자 인터페이스의 전반적인 개선과 기능 강화를 목표로 합니다. 특히, 페이지 레이아웃의 일관성을 확보하고, 로그인 경험을 개선하며, 캐릭터 및 결산 목록의 성능을 향상시키는 데 중점을 두었습니다. 또한, 시각적 요소의 통일성을 위해 아이콘 및 이미지 렌더링 방식을 최적화하고, SEO를 위한 메타데이터를 추가하여 웹 접근성을 높였습니다. Highlights
🧠 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. Changelog
Activity
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (10)
dpbr_front/app/src/lib/components/InputBox.svelte (1)
65-66: 재사용 컴포넌트에서 자동완성/맞춤법 설정은 하드코딩보다 Props가 안전합니다.
InputBox전체 사용처에 동일 정책이 강제되므로, 페이지별로 제어 가능하게 열어두는 편이 좋습니다.♻️ 제안 패치
interface Props { type?: "text" | "number" | "tel"; placeholder: string; value: string; + autocomplete?: string; + spellcheck?: boolean; maxLength?: number; inputState?: "default" | "focused"; showClearButton?: boolean; @@ type = "text", placeholder, value: valueProp, + autocomplete = "off", + spellcheck = false, maxLength, @@ - spellcheck="false" - autocomplete="off" + spellcheck={spellcheck} + autocomplete={autocomplete}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@dpbr_front/app/src/lib/components/InputBox.svelte` around lines 65 - 66, InputBox currently hardcodes spellcheck="false" and autocomplete="off", make these configurable via props so consumers can override per-page; add two props (e.g., export let spellcheck = false and export let autocomplete = "off") and use them on the internal <input> (e.g., spellcheck={spellcheck} autocomplete={autocomplete}) so default behavior stays the same but callers can pass different values when needed; ensure prop types/defaults are defined at top of the InputBox component and update any docs/tests that assume the hardcoded values.dpbr_front/app/src/routes/msg/[id]/+page.svelte (1)
67-70: 기본 폴백 정적 이미지 포맷은 가능하면 WebP로 통일해 주세요.현재 폴백이
logo.png,default-avatar.png로 고정되어 있어 이미지 정책과 맞추기 어렵습니다. 정적 에셋만이라도 WebP 전환을 권장합니다.Based on learnings: Images should use WebP format, have lazy loading applied, and be resized appropriately
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@dpbr_front/app/src/routes/msg/`[id]/+page.svelte around lines 67 - 70, The img src fallback chain currently uses PNGs; update the fallback static assets in the expression that builds src (the settlement.imageUrl || (isAdminTeam ? "/logo.png" : character?.avatarUrl || "/default-avatar.png")) to WebP versions (e.g. "/logo.webp" and "/default-avatar.webp"), add lazy loading to the image element (loading="lazy"), and ensure the rendered image is sized/served at an appropriate resolution (use srcset or a resized WebP asset pipeline) so settlement.imageUrl, isAdminTeam, and character?.avatarUrl remain unchanged but the static fallbacks and loading/size behavior conform to the WebP + lazy + resized policy.dpbr_front/app/src/lib/components/BottomSheetLogin.svelte (2)
7-7: 사용되지 않는toastimport를 제거하세요.에러 메시지가 이제 인라인
errorMessage상태로 처리되므로toastimport가 더 이상 필요하지 않습니다.♻️ 제안된 수정
- import { toast } from "$lib/stores/toast";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@dpbr_front/app/src/lib/components/BottomSheetLogin.svelte` at line 7, Remove the unused import 'toast' from BottomSheetLogin.svelte: locate the import line "import { toast } from \"$lib/stores/toast\";" and delete it so the component no longer imports toast (error handling now uses the inline errorMessage state); ensure no other references to the symbol 'toast' remain in functions like any submit/login handlers or markup.
111-116: 연속 호출 시 타이머 경합 조건 가능성.
showToastMessage가 빠르게 연속 호출되면 이전setTimeout이 새 메시지를 의도치 않게 지울 수 있습니다. 타이머 ID를 추적하고 새 호출 시 이전 타이머를 취소하는 것이 좋습니다.♻️ 제안된 수정
+ let errorTimeoutId: ReturnType<typeof setTimeout> | null = null; + function showToastMessage(message?: string) { + if (errorTimeoutId) { + clearTimeout(errorTimeoutId); + } errorMessage = message || "이름 또는 학번을 확인해 주세요."; - setTimeout(() => { + errorTimeoutId = setTimeout(() => { errorMessage = ""; + errorTimeoutId = null; }, 3000); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@dpbr_front/app/src/lib/components/BottomSheetLogin.svelte` around lines 111 - 116, showToastMessage can race when called rapidly because each call starts a new setTimeout that may clear a later message; add a module-level timer variable (e.g., toastTimer) and in showToastMessage call clearTimeout(toastTimer) before creating a new timeout, then assign the returned timer id to toastTimer so the previous timeout is cancelled and only the most recent call clears errorMessage; reference showToastMessage and errorMessage when making this change.dpbr_front/app/src/lib/api.ts (2)
248-266:getCharacterById에서도mapCharacterResponse를 사용하는 것을 고려해 보세요.일관성을 위해
getCharacterById도 새로 추출한mapCharacterResponse헬퍼를 사용하면 좋겠습니다. 현재 인라인 매핑과 헬퍼 함수의 로직이 동일하므로 중복을 제거할 수 있습니다.♻️ 제안된 수정
export async function getCharacterById(id: string): Promise<Character | null> { try { const data = await apiCall<CharacterResponse>(`/characters/${id}`); - - return { - id: data.id.toString(), - name: data.name, - nickname: data.detail_txt || data.name, - avatarUrl: normalizeAssetUrl(data.avatar_url), - level: data.level, - job: data.job, - club: '단풍바람', - server: data.server - }; + return mapCharacterResponse(data); } catch (error) { console.error('Failed to fetch character:', error); return null; } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@dpbr_front/app/src/lib/api.ts` around lines 248 - 266, Replace the inline mapping inside getCharacterById with the existing mapCharacterResponse helper: call apiCall<CharacterResponse>(`/characters/${id}`), then if data is truthy return mapCharacterResponse(data) (ensuring mapCharacterResponse performs normalizeAssetUrl for avatarUrl and sets club '단풍바람'), otherwise return null; keep the try/catch and error logging as-is so failure still returns null.
299-315:getSettlementById에서도mapSettlementResponse를 사용하는 것을 고려해 보세요.동일하게
getSettlementById도mapSettlementResponse헬퍼를 사용하면 코드 일관성이 향상됩니다.♻️ 제안된 수정
export async function getSettlementById(id: string): Promise<SettlementItem | null> { try { const data = await apiCall<SettlementResponse>(`/settlements/${id}`); - - return { - id: data.id.toString(), - characterId: data.character_id.toString(), - title: data.title, - description: data.description || '', - imageUrl: data.img_url ? `${getApiBaseUrl()}${data.img_url}` : '/default-avatar.png', - acquiredAt: data.acquired_at - }; + return mapSettlementResponse(data); } catch (error) { console.error('Failed to fetch settlement:', error); return null; } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@dpbr_front/app/src/lib/api.ts` around lines 299 - 315, Replace the inline mapping in getSettlementById with the existing mapSettlementResponse helper: after awaiting apiCall<SettlementResponse>(`/settlements/${id}`) pass the returned data into mapSettlementResponse and return its result (ensuring types match), remove the duplicated field mapping (id, character_id, title, description, img_url, acquired_at) and keep the same try/catch behavior; also add an import for mapSettlementResponse at the top if it isn’t already imported so getSettlementById uses mapSettlementResponse(data) instead of duplicating mapping logic.dpbr_front/app/src/routes/member/[id]/save/+page.svelte (2)
91-100: 외부 placeholder 서비스 대신 로컬 fallback 이미지 사용을 권장합니다.
via.placeholder.com은 외부 서비스로, 네트워크 문제나 서비스 중단 시 fallback이 실패할 수 있습니다. 프로젝트 내 로컬 fallback 이미지를 사용하는 것이 더 안정적입니다.♻️ 제안된 수정
onerror={(e) => ((e.currentTarget as HTMLImageElement).src = - "https://via.placeholder.com/300x450/EEE/999?text=Park+Background")} + "/images/fallback-park-bg.png")}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@dpbr_front/app/src/routes/member/`[id]/save/+page.svelte around lines 91 - 100, The onerror handler for the <img> that sets a fallback to "https://via.placeholder.com/..." should be changed to use a local project asset; update the onerror arrow function in the <img> tag (the handler referencing e.currentTarget as HTMLImageElement) to assign a local fallback path (e.g. /images/local-fallback.png) instead of the external URL, and ensure the fallback file exists in the app's static/images (or equivalent) so the fallback will work offline and during service outages.
140-146: 파일 경로에 한글 문자 사용 시 주의가 필요합니다.
/images/단풍바람 13기.svg경로에 한글과 공백이 포함되어 있습니다. 일부 환경에서 URL 인코딩 문제가 발생할 수 있으므로, 파일명을 영문과 하이픈으로 변경하는 것을 고려해 보세요 (예:/images/danpung-13th.svg).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@dpbr_front/app/src/routes/member/`[id]/save/+page.svelte around lines 140 - 146, The img src uses a filename with Korean characters and a space (src="/images/단풍바람 13기.svg") which can cause URL/encoding issues; rename the file to a URL-safe name (e.g., danpung-13th.svg) in your static assets and update the img src in +page.svelte to the new path (modify the <img src=...> reference), and also search & update any other references (imports, CSS, tests or build configs) that point to the old filename so all usages match the new English/hyphenated name.dpbr_front/app/src/routes/team-message/[id]/+page.svelte (1)
98-107: 하단 로고 높이 계산 단순화를 고려해 보세요.
h-[calc(100dvh*64/874)]는 매직 넘버로 구성되어 있어 유지보수가 어렵습니다. 의미 있는 값(예:h-16또는 CSS 변수)으로 대체하거나, 해당 계산의 의미를 주석으로 설명하는 것이 좋습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@dpbr_front/app/src/routes/team-message/`[id]/+page.svelte around lines 98 - 107, The footer container's height uses a magic calculation in the class h-[calc(100dvh*64/874)] (the div wrapping the logo) which is hard to maintain; replace that computed height with a meaningful Tailwind utility or CSS variable (e.g., h-16 or var(--footer-logo-height)) and update any related styles to preserve the visual size, or add a concise comment explaining the original calculation if you must keep it; locate the div with class "flex justify-center items-center h-[calc(100dvh*64/874)] bg-white shrink-0 mt-2" and change the height part and/or introduce the CSS variable in a component-level style or global CSS so future maintainers understand the intent.dpbr_front/app/src/routes/member/[id]/+page.svelte (1)
113-148: 페이지네이션 fallback 로직이 잘 구현되었습니다.404 에러 시 비페이지네이션 API로 fallback하는 로직은 백엔드 호환성을 위한 좋은 접근입니다. 다만, 에러 메시지 문자열 매칭(
e.message.includes("API Error: 404"))은 취약할 수 있습니다.♻️ 더 안정적인 에러 감지를 위한 제안
API 레이어에서 커스텀 에러 클래스를 사용하거나 에러 객체에 status code를 포함시키면 더 안정적입니다:
// api.ts에서 class ApiError extends Error { constructor(public status: number, message: string) { super(message); } } // 사용 시 if (e instanceof ApiError && e.status === 404) { // fallback 로직 }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@dpbr_front/app/src/routes/member/`[id]/+page.svelte around lines 113 - 148, The current 404 detection in loadMoreSettlements relies on fragile string matching (e.message.includes("API Error: 404")); update the logic to detect 404 reliably by using a structured error (e.g., ApiError) or checking a status property on the thrown error from getSettlementsByCharacterIdPaginated: modify the API layer to throw ApiError (class ApiError extends Error { status: number }) or ensure the API throws an object with a numeric status, then in loadMoreSettlements replace the string check with a robust guard such as (e instanceof ApiError && e.status === 404) or (typeof (e as any).status === 'number' && (e as any).status === 404) so the fallback to getSettlementsByCharacterId runs only on real 404 responses.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@dpbr_front/app/src/app.html`:
- Line 10: Remove the zoom-locking attributes from the <meta name="viewport">
tag in app.html: replace the current content attribute ("width=device-width,
initial-scale=1, maximum-scale=1, user-scalable=no") with a permissive viewport
(e.g., "width=device-width, initial-scale=1") so users can pinch-zoom; locate
the <meta name="viewport"> element in app.html and edit its content value
accordingly to restore accessibility for low-vision users.
In `@dpbr_front/app/src/lib/components/CharacterCard.svelte`:
- Around line 40-41: When the character.server (and thus logoPath via
getWorldLogoPath) changes, reset the isWorldLogoLoadFailed flag so a prior true
value doesn't hide the new logo; add logic that watches the derived logoPath (or
character.server) and sets isWorldLogoLoadFailed(false) on change (referencing
logoPath, isWorldLogoLoadFailed, and getWorldLogoPath/character.server) so the
failure state is cleared whenever the server/logo changes.
In `@dpbr_front/app/src/routes/login/`+page.svelte:
- Around line 84-88: The showToastMessage function can leave prior timers
running so rapid consecutive calls clear a newer message early; fix by tracking
the timeout ID (e.g., toastTimeout) at module/component scope, call
clearTimeout(toastTimeout) before creating a new setTimeout, and store the
returned ID in toastTimeout so errorMessage is reliably cleared only after the
latest timer fires; update references in showToastMessage and ensure
toastTimeout is initialized (null/undefined) in the component.
---
Nitpick comments:
In `@dpbr_front/app/src/lib/api.ts`:
- Around line 248-266: Replace the inline mapping inside getCharacterById with
the existing mapCharacterResponse helper: call
apiCall<CharacterResponse>(`/characters/${id}`), then if data is truthy return
mapCharacterResponse(data) (ensuring mapCharacterResponse performs
normalizeAssetUrl for avatarUrl and sets club '단풍바람'), otherwise return null;
keep the try/catch and error logging as-is so failure still returns null.
- Around line 299-315: Replace the inline mapping in getSettlementById with the
existing mapSettlementResponse helper: after awaiting
apiCall<SettlementResponse>(`/settlements/${id}`) pass the returned data into
mapSettlementResponse and return its result (ensuring types match), remove the
duplicated field mapping (id, character_id, title, description, img_url,
acquired_at) and keep the same try/catch behavior; also add an import for
mapSettlementResponse at the top if it isn’t already imported so
getSettlementById uses mapSettlementResponse(data) instead of duplicating
mapping logic.
In `@dpbr_front/app/src/lib/components/BottomSheetLogin.svelte`:
- Line 7: Remove the unused import 'toast' from BottomSheetLogin.svelte: locate
the import line "import { toast } from \"$lib/stores/toast\";" and delete it so
the component no longer imports toast (error handling now uses the inline
errorMessage state); ensure no other references to the symbol 'toast' remain in
functions like any submit/login handlers or markup.
- Around line 111-116: showToastMessage can race when called rapidly because
each call starts a new setTimeout that may clear a later message; add a
module-level timer variable (e.g., toastTimer) and in showToastMessage call
clearTimeout(toastTimer) before creating a new timeout, then assign the returned
timer id to toastTimer so the previous timeout is cancelled and only the most
recent call clears errorMessage; reference showToastMessage and errorMessage
when making this change.
In `@dpbr_front/app/src/lib/components/InputBox.svelte`:
- Around line 65-66: InputBox currently hardcodes spellcheck="false" and
autocomplete="off", make these configurable via props so consumers can override
per-page; add two props (e.g., export let spellcheck = false and export let
autocomplete = "off") and use them on the internal <input> (e.g.,
spellcheck={spellcheck} autocomplete={autocomplete}) so default behavior stays
the same but callers can pass different values when needed; ensure prop
types/defaults are defined at top of the InputBox component and update any
docs/tests that assume the hardcoded values.
In `@dpbr_front/app/src/routes/member/`[id]/+page.svelte:
- Around line 113-148: The current 404 detection in loadMoreSettlements relies
on fragile string matching (e.message.includes("API Error: 404")); update the
logic to detect 404 reliably by using a structured error (e.g., ApiError) or
checking a status property on the thrown error from
getSettlementsByCharacterIdPaginated: modify the API layer to throw ApiError
(class ApiError extends Error { status: number }) or ensure the API throws an
object with a numeric status, then in loadMoreSettlements replace the string
check with a robust guard such as (e instanceof ApiError && e.status === 404) or
(typeof (e as any).status === 'number' && (e as any).status === 404) so the
fallback to getSettlementsByCharacterId runs only on real 404 responses.
In `@dpbr_front/app/src/routes/member/`[id]/save/+page.svelte:
- Around line 91-100: The onerror handler for the <img> that sets a fallback to
"https://via.placeholder.com/..." should be changed to use a local project
asset; update the onerror arrow function in the <img> tag (the handler
referencing e.currentTarget as HTMLImageElement) to assign a local fallback path
(e.g. /images/local-fallback.png) instead of the external URL, and ensure the
fallback file exists in the app's static/images (or equivalent) so the fallback
will work offline and during service outages.
- Around line 140-146: The img src uses a filename with Korean characters and a
space (src="/images/단풍바람 13기.svg") which can cause URL/encoding issues; rename
the file to a URL-safe name (e.g., danpung-13th.svg) in your static assets and
update the img src in +page.svelte to the new path (modify the <img src=...>
reference), and also search & update any other references (imports, CSS, tests
or build configs) that point to the old filename so all usages match the new
English/hyphenated name.
In `@dpbr_front/app/src/routes/msg/`[id]/+page.svelte:
- Around line 67-70: The img src fallback chain currently uses PNGs; update the
fallback static assets in the expression that builds src (the
settlement.imageUrl || (isAdminTeam ? "/logo.png" : character?.avatarUrl ||
"/default-avatar.png")) to WebP versions (e.g. "/logo.webp" and
"/default-avatar.webp"), add lazy loading to the image element (loading="lazy"),
and ensure the rendered image is sized/served at an appropriate resolution (use
srcset or a resized WebP asset pipeline) so settlement.imageUrl, isAdminTeam,
and character?.avatarUrl remain unchanged but the static fallbacks and
loading/size behavior conform to the WebP + lazy + resized policy.
In `@dpbr_front/app/src/routes/team-message/`[id]/+page.svelte:
- Around line 98-107: The footer container's height uses a magic calculation in
the class h-[calc(100dvh*64/874)] (the div wrapping the logo) which is hard to
maintain; replace that computed height with a meaningful Tailwind utility or CSS
variable (e.g., h-16 or var(--footer-logo-height)) and update any related styles
to preserve the visual size, or add a concise comment explaining the original
calculation if you must keep it; locate the div with class "flex justify-center
items-center h-[calc(100dvh*64/874)] bg-white shrink-0 mt-2" and change the
height part and/or introduce the CSS variable in a component-level style or
global CSS so future maintainers understand the intent.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (34)
dpbr_front/app/static/default-avatar.pngis excluded by!**/*.pngdpbr_front/app/static/fonts/NEXON Lv1 Gothic Low OTF Bold.otfis excluded by!**/*.otfdpbr_front/app/static/fonts/NEXON Lv1 Gothic Low OTF Light.otfis excluded by!**/*.otfdpbr_front/app/static/fonts/NEXON Lv1 Gothic Low OTF.otfis excluded by!**/*.otfdpbr_front/app/static/images/icons/MSGS_Favicon.icois excluded by!**/*.icodpbr_front/app/static/images/icons/back-icon-black.svgis excluded by!**/*.svgdpbr_front/app/static/images/icons/back-icon-white.svgis excluded by!**/*.svgdpbr_front/app/static/images/icons/chat-icon-white.svgis excluded by!**/*.svgdpbr_front/app/static/images/icons/check-disable-icon.svgis excluded by!**/*.svgdpbr_front/app/static/images/icons/check-enable-icon.svgis excluded by!**/*.svgdpbr_front/app/static/images/icons/close-icon-black.svgis excluded by!**/*.svgdpbr_front/app/static/images/icons/close-icon-white.svgis excluded by!**/*.svgdpbr_front/app/static/images/icons/menu-icon-white.svgis excluded by!**/*.svgdpbr_front/app/static/images/icons/name=Save, Color=White.svgis excluded by!**/*.svgdpbr_front/app/static/images/icons/name=Send, Color=White.svgis excluded by!**/*.svgdpbr_front/app/static/images/icons/name=Text Logo, color=Mono.svgis excluded by!**/*.svgdpbr_front/app/static/images/icons/name=Text Logo, color=White.svgis excluded by!**/*.svgdpbr_front/app/static/images/icons/send-icon-white.svgis excluded by!**/*.svgdpbr_front/app/static/images/icons/symbol-logo-color.svgis excluded by!**/*.svgdpbr_front/app/static/images/park-bg.pngis excluded by!**/*.pngdpbr_front/app/static/images/thumbnail.pngis excluded by!**/*.pngdpbr_front/app/static/images/단풍바람 13기.svgis excluded by!**/*.svgdpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Back, Color=Black.svgis excluded by!**/*.svgdpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Back, Color=White.svgis excluded by!**/*.svgdpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Chat, Color=White.svgis excluded by!**/*.svgdpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Close, Color=Black.svgis excluded by!**/*.svgdpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Close, Color=White.svgis excluded by!**/*.svgdpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Menu, Color=White.svgis excluded by!**/*.svgdpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Save, Color=White.svgis excluded by!**/*.svgdpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Send, Color=White.svgis excluded by!**/*.svgdpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Text Logo, color=Mono.svgis excluded by!**/*.svgdpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Text Logo, color=White.svgis excluded by!**/*.svgdpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=check-disable, Color=White.svgis excluded by!**/*.svgdpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=check-enable, Color=White.svgis excluded by!**/*.svg
📒 Files selected for processing (18)
.github/PULL_REQUEST_TEMPLATE.mddpbr_front/app/check_output.txtdpbr_front/app/src/app.htmldpbr_front/app/src/lib/api.tsdpbr_front/app/src/lib/components/BottomSheetLogin.sveltedpbr_front/app/src/lib/components/CharacterCard.sveltedpbr_front/app/src/lib/components/CommentItem.sveltedpbr_front/app/src/lib/components/Header.sveltedpbr_front/app/src/lib/components/InputBox.sveltedpbr_front/app/src/lib/components/Sidebar.sveltedpbr_front/app/src/lib/stores/auth.tsdpbr_front/app/src/routes/+page.sveltedpbr_front/app/src/routes/login/+page.sveltedpbr_front/app/src/routes/member/[id]/+page.sveltedpbr_front/app/src/routes/member/[id]/save/+page.sveltedpbr_front/app/src/routes/msg/[id]/+page.sveltedpbr_front/app/src/routes/talk/+page.sveltedpbr_front/app/src/routes/team-message/[id]/+page.svelte
| <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin /> | ||
| <link rel="stylesheet" as="style" crossorigin | ||
| href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" /> |
There was a problem hiding this comment.
모바일 확대를 막는 viewport 설정은 접근성 이슈입니다.
줌 비활성화는 저시력 사용자 작업을 막을 수 있어 제거가 필요합니다.
♿ 제안 패치
- <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />📝 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.
| <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@dpbr_front/app/src/app.html` at line 10, Remove the zoom-locking attributes
from the <meta name="viewport"> tag in app.html: replace the current content
attribute ("width=device-width, initial-scale=1, maximum-scale=1,
user-scalable=no") with a permissive viewport (e.g., "width=device-width,
initial-scale=1") so users can pinch-zoom; locate the <meta name="viewport">
element in app.html and edit its content value accordingly to restore
accessibility for low-vision users.
| let logoPath = $derived(getWorldLogoPath(character.server)); | ||
| let isWorldLogoLoadFailed = $state(false); |
There was a problem hiding this comment.
월드 로고 실패 상태를 서버 변경 시 초기화해 주세요.
isWorldLogoLoadFailed가 true가 된 뒤 캐릭터가 바뀌면, 새 서버 로고도 계속 숨겨질 수 있습니다.
🔧 제안 패치
let logoPath = $derived(getWorldLogoPath(character.server));
let isWorldLogoLoadFailed = $state(false);
+
+$effect(() => {
+ character.server;
+ isWorldLogoLoadFailed = false;
+});Also applies to: 50-57
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@dpbr_front/app/src/lib/components/CharacterCard.svelte` around lines 40 - 41,
When the character.server (and thus logoPath via getWorldLogoPath) changes,
reset the isWorldLogoLoadFailed flag so a prior true value doesn't hide the new
logo; add logic that watches the derived logoPath (or character.server) and sets
isWorldLogoLoadFailed(false) on change (referencing logoPath,
isWorldLogoLoadFailed, and getWorldLogoPath/character.server) so the failure
state is cleared whenever the server/logo changes.
| function showToastMessage(message?: string) { | ||
| toast.show(message || "이름 또는 학번을 확인해 주세요."); | ||
| errorMessage = message || "이름 또는 학번을 확인해 주세요."; | ||
| setTimeout(() => { | ||
| errorMessage = ""; | ||
| }, 3000); |
There was a problem hiding this comment.
에러 메시지 자동 제거 타이머가 중첩되면 최신 메시지가 조기 사라질 수 있습니다.
이전 타이머를 정리하지 않아 연속 실패 시 표시 시간이 불안정해집니다.
🛠️ 제안 패치
-import { onMount } from "svelte";
+import { onMount, onDestroy } from "svelte";
@@
let errorMessage = $state("");
+let errorTimer: ReturnType<typeof setTimeout> | null = null;
@@
function showToastMessage(message?: string) {
errorMessage = message || "이름 또는 학번을 확인해 주세요.";
- setTimeout(() => {
+ if (errorTimer) clearTimeout(errorTimer);
+ errorTimer = setTimeout(() => {
errorMessage = "";
}, 3000);
}
+
+onDestroy(() => {
+ if (errorTimer) clearTimeout(errorTimer);
+});🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@dpbr_front/app/src/routes/login/`+page.svelte around lines 84 - 88, The
showToastMessage function can leave prior timers running so rapid consecutive
calls clear a newer message early; fix by tracking the timeout ID (e.g.,
toastTimeout) at module/component scope, call clearTimeout(toastTimeout) before
creating a new setTimeout, and store the returned ID in toastTimeout so
errorMessage is reliably cleared only after the latest timer fires; update
references in showToastMessage and ensure toastTimeout is initialized
(null/undefined) in the component.
There was a problem hiding this comment.
Code Review
이번 PR은 전반적인 UI 개선과 코드 리팩토링에 중점을 둔 많은 변경 사항을 포함하고 있습니다. 특히 페이지네이션 구현, 아이콘 시스템 정리, 컴포넌트 재사용성 향상 등 코드 품질을 높이는 좋은 변화들이 많습니다. 몇 가지 추가 개선 제안 사항을 코드에 댓글로 남겼습니다. app.html의 정적 메타 태그 처리, vh 단위 사용, 그리고 이미지 카드 생성 페이지의 외부 의존성 및 스타일 관리와 관련된 내용입니다. 전반적으로 훌륭한 작업입니다.
| <!-- SEO & Metadata --> | ||
| <meta name="description" content="단풍바람 13기 메생결산 - 당신의 메이플 여정을 기록하세요." /> | ||
| <meta property="og:type" content="website" /> | ||
| <meta property="og:title" content="단풍바람" /> | ||
| <meta property="og:description" content="단풍바람 13기 메생결산 - 당신의 메이플 여정을 기록하세요." /> | ||
| <meta property="og:image" content="%sveltekit.assets%/images/thumbnail.png" /> | ||
| <meta property="og:url" content="https://gc-maplewind.github.io/MSGS_13_F/" /> | ||
|
|
||
| <meta name="twitter:card" content="summary_large_image" /> | ||
| <meta name="twitter:title" content="단풍바람" /> | ||
| <meta name="twitter:description" content="단풍바람 13기 메생결산 - 당신의 메이플 여정을 기록하세요." /> | ||
| <meta name="twitter:image" content="%sveltekit.assets%/images/thumbnail.png" /> |
| > | ||
| <div | ||
| class="w-full h-[72%] bg-gradient-to-b from-[#FCDDA5] to-[#F1A470] rounded-t-3xl pt-4 pb-8 px-6 flex flex-col items-center shadow-lg transition-transform duration-300 {isVisible | ||
| class="w-full shrink-0 h-[72vh] bg-gradient-to-b from-[#FCDDA5] to-[#F1A470] rounded-t-3xl pt-4 pb-8 px-6 flex flex-col items-center shadow-lg transition-transform duration-300 {isVisible |
| onerror={(e) => | ||
| ((e.currentTarget as HTMLImageElement).src = | ||
| "https://via.placeholder.com/300x450/EEE/999?text=Park+Background")} | ||
| /> |
| alt={character.name} | ||
| onerror={handleImageError} | ||
| class="w-auto h-auto object-contain drop-shadow-lg" | ||
| style="filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.4)); transform: scale(2.2); image-rendering: pixelated; image-rendering: crisp-edges;" |
There was a problem hiding this comment.
📋 작업 내용
Lucide라이브러리를 부분 제거하고, 자체 에셋(static/images/icons/)을 이용해 헤더, 톡 페이지, 로그인 모달 내 아이콘을 커스텀 SVG<img>태그로 전면 교체w-[72px]), 좌측 정렬, 줄바꿈 방지(whitespace-nowrap), 축소 방지(shrink-0)를 적용하여 텍스트 길이에 따른 UI 찌그러짐 완벽 방지absolute text-center제어 및 배경 분리InputBox포커스 시 브라우저 기본 이중 아웃라인 버그(focus:outline-none focus:ring-0) 제거 및 내부 배경색 유지, 테두리만 전환되도록 픽스logo-text-white.svg이미지로 교체하고 그림자 효과 제거 요소 간격(Gap, Margin) 미세 조정<label>확장) 적용을 통한 클릭 접근성(상호작용) 토글 이슈 해결 및check-enable/disable.svg상태 동기화🎯 관련 이슈
Closes #(이슈번호를 입력해주세요)
🤖 사용한 Prompt
✅ 체크리스트
npm run check)📸 스크린샷 (선택)
💬 특이사항
handleKakaoLogin) 및 프론트 버튼 UI는 파일 내부에 주석 형태로 보존해 두었습니다. (필요 시 추후 주석만 해제해 재활용 가능)InputBox및 버튼 컴포넌트 터치 시 모바일 브라우저의 기본 음영(Highlight)이 겹치지 않게 조치하였습니다.리뷰어를 위한 가이드