diff --git a/.gitignore b/.gitignore
index 657d339e..e4791f7e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -70,3 +70,7 @@ apps/web/public/sw.js
*storybook.log
.playwright-mcp
+
+# Local Claude Code instructions
+apps/*/CLAUDE.local.md
+CLAUDE.local.md
diff --git a/.serena/project.yml b/.serena/project.yml
index 94d3e642..2a795b8e 100644
--- a/.serena/project.yml
+++ b/.serena/project.yml
@@ -1,10 +1,3 @@
-# language of the project (csharp, python, rust, java, typescript, go, cpp, or ruby)
-# * For C, use cpp
-# * For JavaScript, use typescript
-# Special requirements:
-# * csharp: Requires the presence of a .sln file in the project folder.
-language: typescript
-
# whether to use the project's gitignore file to ignore files
# Added on 2025-04-07
ignore_all_files_in_gitignore: true
@@ -64,5 +57,95 @@ excluded_tools: []
# initial prompt for the project. It will always be given to the LLM upon activating the project
# (contrary to the memories, which are loaded on demand).
initial_prompt: ""
-
+# the name by which the project can be referenced within Serena
project_name: "git-animal-client"
+
+# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
+# This extends the existing inclusions (e.g. from the global configuration).
+included_optional_tools: []
+
+# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
+# This cannot be combined with non-empty excluded_tools or included_optional_tools.
+fixed_tools: []
+
+# list of mode names to that are always to be included in the set of active modes
+# The full set of modes to be activated is base_modes + default_modes.
+# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply.
+# Otherwise, this setting overrides the global configuration.
+# Set this to [] to disable base modes for this project.
+# Set this to a list of mode names to always include the respective modes for this project.
+base_modes:
+
+# list of mode names that are to be activated by default.
+# The full set of modes to be activated is base_modes + default_modes.
+# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply.
+# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
+# This setting can, in turn, be overridden by CLI parameters (--mode).
+default_modes:
+
+# time budget (seconds) per tool call for the retrieval of additional symbol information
+# such as docstrings or parameter information.
+# This overrides the corresponding setting in the global configuration; see the documentation there.
+# If null or missing, use the setting from the global configuration.
+symbol_info_budget:
+
+# The language backend to use for this project.
+# If not set, the global setting from serena_config.yml is used.
+# Valid values: LSP, JetBrains
+# Note: the backend is fixed at startup. If a project with a different backend
+# is activated post-init, an error will be returned.
+language_backend:
+
+# line ending convention to use when writing source files.
+# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default)
+# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings.
+line_ending:
+
+# list of regex patterns which, when matched, mark a memory entry as readโonly.
+# Extends the list from the global configuration, merging the two lists.
+read_only_memory_patterns: []
+
+# the encoding used by text files in the project
+# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
+encoding: utf-8
+
+
+# list of languages for which language servers are started; choose from:
+# al bash clojure cpp csharp
+# csharp_omnisharp dart elixir elm erlang
+# fortran fsharp go groovy haskell
+# java julia kotlin lua markdown
+# matlab nix pascal perl php
+# php_phpactor powershell python python_jedi r
+# rego ruby ruby_solargraph rust scala
+# swift terraform toml typescript typescript_vts
+# vue yaml zig
+# (This list may be outdated. For the current list, see values of Language enum here:
+# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
+# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
+# Note:
+# - For C, use cpp
+# - For JavaScript, use typescript
+# - For Free Pascal/Lazarus, use pascal
+# Special requirements:
+# Some languages require additional setup/installations.
+# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
+# When using multiple languages, the first language server that supports a given file will be used for that file.
+# The first language is the default language and the respective language server will be used as a fallback.
+# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
+languages:
+- typescript
+
+# list of regex patterns for memories to completely ignore.
+# Matching memories will not appear in list_memories or activate_project output
+# and cannot be accessed via read_memory or write_memory.
+# To access ignored memory files, use the read_file tool on the raw file path.
+# Extends the list from the global configuration, merging the two lists.
+# Example: ["_archive/.*", "_episodes/.*"]
+ignored_memory_patterns: []
+
+# advanced configuration option allowing to configure language server-specific options.
+# Maps the language key to the options.
+# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available.
+# No documentation on options means no options are available.
+ls_specific_settings: {}
diff --git a/apps/web/messages/en_US.json b/apps/web/messages/en_US.json
index 77feeac6..a79d49a1 100644
--- a/apps/web/messages/en_US.json
+++ b/apps/web/messages/en_US.json
@@ -141,7 +141,28 @@
"please-choose-pet": "Select a pet to merge. The selected pet will be consumed and disappear."
},
"laboratory": "Laboratory",
- "maximum-pet-count-error": "You can have a maximum of 30 pets."
+ "maximum-pet-count-error": "You can have a maximum of 30 pets.",
+ "Filter": {
+ "search-placeholder": "Search pet name",
+ "grade": "Grade",
+ "tier": "Tier",
+ "visibility": "Status",
+ "sort": "Sort",
+ "filter-all": "All",
+ "filter-grade-collaborator": "Collab",
+ "filter-grade-default": "Default",
+ "filter-grade-evolution": "Evolution",
+ "filter-visible": "Visible",
+ "filter-hidden": "Hidden",
+ "evolvable-only": "Evolvable",
+ "sort-grade": "By Grade",
+ "sort-level-desc": "Level (High)",
+ "sort-level-asc": "Level (Low)",
+ "sort-tier": "By Rarity",
+ "sort-name": "By Name",
+ "reset": "Reset filters",
+ "no-results": "No pets match the criteria."
+ }
},
"Event": {
"event-end": "Event has ended.",
@@ -245,6 +266,8 @@
"Error": {
"global-error-message": "Something went wrong ๐ญ",
"want-to-report-error": "The gitanimals development team is currently tracking this error.\nIf you have a moment, we would greatly appreciate it if you could report the circumstances that led to this error.",
- "report-error-button": "Report Error"
+ "report-error-button": "Report Error",
+ "ranking-error-title": "Unable to load ranking information",
+ "ranking-error-description": "Please try again later"
}
}
diff --git a/apps/web/messages/ko_KR.json b/apps/web/messages/ko_KR.json
index 39daacb9..710a12a7 100644
--- a/apps/web/messages/ko_KR.json
+++ b/apps/web/messages/ko_KR.json
@@ -142,7 +142,28 @@
"please-choose-pet": "ํฉ์น๊ธฐ์ ์ฌ์ฉํ ํซ์ ์ ํํด์ฃผ์ธ์. ํฉ์น ํซ์ ์ฌ๋ผ์ง๋๋ค."
},
"laboratory": "์คํ์ค",
- "maximum-pet-count-error": "ํซ์ ์ต๋ 30๋ง๋ฆฌ๊น์ง ๊ฐ๋ฅํฉ๋๋ค."
+ "maximum-pet-count-error": "ํซ์ ์ต๋ 30๋ง๋ฆฌ๊น์ง ๊ฐ๋ฅํฉ๋๋ค.",
+ "Filter": {
+ "search-placeholder": "ํซ ์ด๋ฆ ๊ฒ์",
+ "grade": "๋ฑ๊ธ",
+ "tier": "ํฐ์ด",
+ "visibility": "์ํ",
+ "sort": "์ ๋ ฌ",
+ "filter-all": "์ ์ฒด",
+ "filter-grade-collaborator": "์ฝ๋ผ๋ณด",
+ "filter-grade-default": "๊ธฐ๋ณธ",
+ "filter-grade-evolution": "์งํ",
+ "filter-visible": "ํ์์ค",
+ "filter-hidden": "์จ๊น",
+ "evolvable-only": "์งํ ๊ฐ๋ฅ",
+ "sort-grade": "๋ฑ๊ธ์",
+ "sort-level-desc": "๋ ๋ฒจ ๋์์",
+ "sort-level-asc": "๋ ๋ฒจ ๋ฎ์์",
+ "sort-tier": "ํฌ๊ท๋์",
+ "sort-name": "์ด๋ฆ์",
+ "reset": "ํํฐ ์ด๊ธฐํ",
+ "no-results": "์กฐ๊ฑด์ ๋ง๋ ํซ์ด ์์ต๋๋ค."
+ }
},
"Event": {
"event-end": "์ด๋ฒคํธ๊ฐ ์ข ๋ฃ๋์์ต๋๋ค.",
@@ -246,6 +267,8 @@
"Error": {
"global-error-message": "๋ฌธ์ ๊ฐ ๋ฐ์ํ์ด์ ๐ญ",
"want-to-report-error": "gitanimals ๊ฐ๋ฐํ์ ํ์ฌ ์ด ์ค๋ฅ๋ฅผ ์ถ์ ํ๊ณ ์์ต๋๋ค.\n์๊ฐ์ด ๋์ ๋ค๋ฉด ์ด ์ค๋ฅ๊ฐ ๋ฐ์ํ ์ํฉ์ ๋ณด๊ณ ํด์ฃผ์๋ฉด ๊ฐ์ฌํ๊ฒ ์ต๋๋ค.",
- "report-error-button": "์ค๋ฅ ๋ณด๊ณ ํ๊ธฐ"
+ "report-error-button": "์ค๋ฅ ๋ณด๊ณ ํ๊ธฐ",
+ "ranking-error-title": "๋ญํน ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ฌ ์ ์์ต๋๋ค",
+ "ranking-error-description": "์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์"
}
}
diff --git a/apps/web/src/app/[locale]/guild/_components/SelectPersonaList.tsx b/apps/web/src/app/[locale]/guild/_components/SelectPersonaList.tsx
index 65064e7f..96b9de55 100644
--- a/apps/web/src/app/[locale]/guild/_components/SelectPersonaList.tsx
+++ b/apps/web/src/app/[locale]/guild/_components/SelectPersonaList.tsx
@@ -1,17 +1,18 @@
'use client';
-import { memo } from 'react';
+import { useTranslations } from 'next-intl';
import { css, cx } from '_panda/css';
import type { Persona } from '@gitanimals/api';
import { userQueries } from '@gitanimals/react-query';
-import { LevelBanner } from '@gitanimals/ui-panda';
import { BannerSkeletonList } from '@gitanimals/ui-panda/src/components/Banner/Banner';
import { wrap } from '@suspensive/react';
import { useSuspenseQuery } from '@tanstack/react-query';
+import { MemoizedLevelPersonaItem } from '@/components/PersonaItem';
+import { PersonaListToolbar } from '@/components/PersonaListToolbar';
+import { usePersonaListFilter } from '@/hooks/persona/usePersonaListFilter';
import { customScrollStyle } from '@/styles/scrollStyle';
import { useClientUser } from '@/utils/clientAuth';
-import { getPersonaImage } from '@/utils/image';
const flexOverflowStyle = cx(
css({
@@ -47,20 +48,37 @@ export const SelectPersonaList = wrap
.on(function SelectPersonaList({ selectPersona, onSelectPersona }: SelectPersonaListProps) {
const { name } = useClientUser();
const { data } = useSuspenseQuery(userQueries.allPersonasOptions(name));
+ const t = useTranslations('Mypage.Filter');
+
+ const { filteredList, filterState, updateFilter, resetFilter, isFiltering, counts } = usePersonaListFilter(
+ data.personas,
+ );
- // TODO: ์ ๋ ฌ
return (
-