Skip to content

feat(stamp): スタンプのレコメンデーションを本番実装に移行し、スタンプ履歴の実装を削除#5077

Open
reiroop wants to merge 4 commits intomasterfrom
feat/migrate-stamp-recommendation-to-production
Open

feat(stamp): スタンプのレコメンデーションを本番実装に移行し、スタンプ履歴の実装を削除#5077
reiroop wants to merge 4 commits intomasterfrom
feat/migrate-stamp-recommendation-to-production

Conversation

@reiroop
Copy link
Contributor

@reiroop reiroop commented Feb 22, 2026

概要

as titled

なぜこの PR を入れたいのか

動作確認の手順

  • スタンプパレットが開ける
  • レコメンドのタブを開ける
  • スタンプが押せる

UI 変更部分のスクリーンショット

before

省略

after

image

PR を出す前の確認事項

  • (機能の追加なら)追加することの合意がチームで取れている
    • 取れていない場合はチェックを外して PR にすれば OK
  • 動作確認ができている
  • 自分で一度コードを眺めて自分的に問題はなさそう

見てほしいところ・聞きたいことなど

Summary by CodeRabbit

  • New Features

    • 新しいアイコン「creation」を追加(公開アイコンマッピングを更新)
  • Refactor

    • スタンプ関連を履歴(history)ベースから推奨(recommendation)ベースへ統一
    • スタンプピッカーの初期値・型・選択ロジックを推奨に合わせて移行
    • 公開メソッド名や戻り値を整理(検証関数名の変更とisStampSetValid追加)
    • 不要な履歴ストアとtopIDs算出を削除
  • Bug Fixes

    • スタンプ選択時の不要なローカル履歴更新を削除

@github-actions
Copy link

@reiroop reiroop self-assigned this Feb 22, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 22, 2026

📝 Walkthrough

Walkthrough

スタンプ履歴ストアを削除し、履歴依存を stampRecommendations(recommendation)へ一本化。ローカル履歴の fetch/upsert 呼び出しを削除し、型・識別子を historyrecommendation に置換。公開アイコンマッピングへ creation を追加。

Changes

Cohort / File(s) Summary
Icon Support
src/assets/mdi.ts
mdiCreation を追加でインポートし、公開アイコンマッピングに creation キーを追加。history エントリを削除。
Removed stamp history store
src/store/domain/stampHistory.ts
useStampHistory ストア実装を完全削除(state/getter/upsert/fetch 等を除去)。
Recommendations consolidation
src/store/domain/stampRecommendations.ts
useTopStampIds とその履歴フォールバックロジックを削除し、トップ情報は stampRecommendations の単一ソースに統一。
Stamp picker store UI changes
src/store/ui/stampPicker.ts
初期 StampSet の type を historyrecommendation に変更。公開検証メソッドを validateCurrentStampSetensureCurrentStampSetValid に差し替え、無効時のリセット先を recommendation に更新。
Stamp set selector & item UI
src/components/Main/StampPicker/composables/useStampSetSelector.ts, src/components/Main/StampPicker/StampPickerStampSetSelectorItem.vue
StampSetTypehistoryrecommendation に置換。isStampSetValid を公開関数として追加。UI 条件分岐を historyrecommendation に切替え、該当アイコンを history から creation に変更。
UI data source swaps (top → recommendations)
src/components/Main/MainView/MessageElement/MessageTools.vue, src/components/Main/StampPicker/composables/useStampList.ts
useTopStampIds / topStampIds 参照を useStampRecommendations / stampRecommendations に差替え。トップ抽出ロジック(slice 等)は同等に適用。
Remove local history side-effects & fetch removals
src/components/Main/StampPicker/composables/suggestion/overrides/stampSuggestion.ts, src/components/Main/StampPicker/StampPicker.vue, src/components/Settings/StampPaletteTab/StampPaletteEditorAddableStampList.vue, src/components/ShareTarget/ShareTargetMessageInput.vue, src/lib/updater/stamp.ts, src/views/composables/useInitialFetch.ts
useStampHistory のインポートと fetchStampHistory / upsertLocalStampHistory 呼び出しを削除。必要箇所を fetchStampRecommendations / stampRecommendations に切替え。ローカル履歴更新関連の副作用を除去。

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive PR説明は必須セクション(概要、確認手順、スクリーンショット)を含みますが、「なぜこの PRを入れたいのか」セクションが不完全(issue番号やその他の背景情報がない)で、変更の詳細な背景説明が不足しています。 issue番号やこの変更が必要な背景情報をセクション「なぜこの PRを入れたいのか」に追加し、PR本文に実装の詳細な説明を追記してください。
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PRのタイトルは、スタンプレコメンデーションの本番実装移行と履歴実装削除という変更内容を正確に反映しており、変更の主要な目的を明確に要約しています。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/migrate-stamp-recommendation-to-production

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.

@reiroop reiroop added the enhancement New feature or request label Feb 22, 2026
@codecov
Copy link

codecov bot commented Feb 22, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 63.11%. Comparing base (467d3d3) to head (58aa063).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5077      +/-   ##
==========================================
+ Coverage   62.60%   63.11%   +0.51%     
==========================================
  Files         108      106       -2     
  Lines        3097     3045      -52     
  Branches      630      621       -9     
==========================================
- Hits         1939     1922      -17     
+ Misses       1048     1015      -33     
+ Partials      110      108       -2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@reiroop reiroop marked this pull request as draft February 22, 2026 15:35
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/assets/mdi.ts (1)

54-54: ⚠️ Potential issue | 🟡 Minor

mdiHistory インポートと history マッピングは不要になっているため削除してください

スタンプ履歴機能の削除に伴い、mdiHistory インポート(Line 54)と history: mdiHistory マッピング(Line 120)は他のコンポーネントで使用されていません。これらは削除可能な死コードです。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/assets/mdi.ts` at line 54, Remove the unused mdiHistory import and its
mapping to eliminate dead code: delete the mdiHistory import symbol from the
top-level imports in src/assets/mdi.ts and remove the history: mdiHistory entry
from the exported mapping/object (where icons like mdiX are aggregated),
ensuring no other code references mdiHistory before committing.
🧹 Nitpick comments (2)
src/store/ui/stampPicker.ts (1)

88-92: ensureCurrentStampSetValid 内の直接プロパティ変更について

Lines 90-91 では currentStampSet.value.type.id を直接変更していますが、currentStampSet の setter(Lines 81-84)では newValue オブジェクトを受け取って state.typestate.id に代入する設計になっています。setter を経由して currentStampSet.value = { type: 'recommendation', id: '' } とした方が一貫性があり、将来 setter にロジックが追加された場合にも安全です。

♻️ setter を経由する提案
  const ensureCurrentStampSetValid = () => {
    if (isStampSetValid(currentStampSet.value)) return
-   currentStampSet.value.type = 'recommendation'
-   currentStampSet.value.id = ''
+   currentStampSet.value = { type: 'recommendation', id: '' }
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/store/ui/stampPicker.ts` around lines 88 - 92, The function
ensureCurrentStampSetValid mutates currentStampSet.value properties directly
(currentStampSet.value.type and .id); change it to assign a new object via the
existing currentStampSet setter (i.e., set currentStampSet.value to an object
with type 'recommendation' and id '' ) so the setter logic in currentStampSet is
invoked and future side effects/validation remain consistent.
src/components/Settings/StampPaletteTab/StampPaletteEditorAddableStampList.vue (1)

75-80: Array.includes による O(n²) ルックアップ — 大規模リスト時のパフォーマンスに注意

stampRecommendations.value.includes(stamp.id)(Line 79)は filteredItems の全要素に対してO(n)の線形探索を行うため、スタンプ数が多い場合は全体でO(n²)になります。レコメンデーション件数が常に少量であれば実害はありませんが、Set を使うと意図が明確になります。

♻️ 修正案
 const allAddableStamps = computed(() => {
   if (!stampsMapFetched.value) {
     return []
   }
+  const recommendedSet = new Set(stampRecommendations.value)
   return (
     filterState.query === ''
       ? [
           ...stampRecommendations.value
             .map(id => stampsMap.value.get(id))
             .filter(stamp => stamp !== undefined),
           ...filterState.filteredItems
-            .filter(stamp => !stampRecommendations.value.includes(stamp.id))
+            .filter(stamp => !recommendedSet.has(stamp.id))
             .sort((a, b) => a.name.localeCompare(b.name))
         ]
       : filterState.filteredItems
   ).filter(stamp => !currentStampIds.value.includes(stamp.id))
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/components/Settings/StampPaletteTab/StampPaletteEditorAddableStampList.vue`
around lines 75 - 80, The current filter uses
stampRecommendations.value.includes(stamp.id) causing O(n²) behavior; change to
build a Set from stampRecommendations.value (e.g., const recommendedIds = new
Set(stampRecommendations.value)) and then use recommendedIds.has(stamp.id) when
filtering filterState.filteredItems; update the logic around
stampRecommendations, stampsMap and filterState.filteredItems to reference the
Set so lookups become O(1) and preserve the existing mapping/sorting behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/components/Settings/StampPaletteTab/StampPaletteEditorAddableStampList.vue`:
- Around line 61-62: The code calls useStampRecommendations() twice; replace
both calls with a single call and destructure both values at once (e.g., const {
fetchStampRecommendations, stampRecommendations } = useStampRecommendations())
so fetchStampRecommendations and stampRecommendations are obtained from one
invocation of useStampRecommendations; update any references to the previous
separate variables accordingly.

---

Outside diff comments:
In `@src/assets/mdi.ts`:
- Line 54: Remove the unused mdiHistory import and its mapping to eliminate dead
code: delete the mdiHistory import symbol from the top-level imports in
src/assets/mdi.ts and remove the history: mdiHistory entry from the exported
mapping/object (where icons like mdiX are aggregated), ensuring no other code
references mdiHistory before committing.

---

Nitpick comments:
In
`@src/components/Settings/StampPaletteTab/StampPaletteEditorAddableStampList.vue`:
- Around line 75-80: The current filter uses
stampRecommendations.value.includes(stamp.id) causing O(n²) behavior; change to
build a Set from stampRecommendations.value (e.g., const recommendedIds = new
Set(stampRecommendations.value)) and then use recommendedIds.has(stamp.id) when
filtering filterState.filteredItems; update the logic around
stampRecommendations, stampsMap and filterState.filteredItems to reference the
Set so lookups become O(1) and preserve the existing mapping/sorting behavior.

In `@src/store/ui/stampPicker.ts`:
- Around line 88-92: The function ensureCurrentStampSetValid mutates
currentStampSet.value properties directly (currentStampSet.value.type and .id);
change it to assign a new object via the existing currentStampSet setter (i.e.,
set currentStampSet.value to an object with type 'recommendation' and id '' ) so
the setter logic in currentStampSet is invoked and future side
effects/validation remain consistent.

@reiroop reiroop marked this pull request as ready for review February 22, 2026 15:46
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.

🧹 Nitpick comments (1)
src/components/Settings/StampPaletteTab/StampPaletteEditorAddableStampList.vue (1)

75-77: 型推論は正常に機能しており、明示的な型述語は不要です。

プロジェクトのTypeScriptバージョンは5.9.3であり、TypeScript 5.5以上のため、.map(...).filter(stamp => stamp !== undefined) は正しく Stamp[] に推論されます。現在のコードは型エラーなく動作します。

より明示的にしたい場合は、以下のように型述語を追加することで可読性を向上させることができます:

♻️ オプション:明示的な型述語
-            .filter(stamp => stamp !== undefined),
+            .filter((stamp): stamp is NonNullable<typeof stamp> => stamp !== undefined),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/components/Settings/StampPaletteTab/StampPaletteEditorAddableStampList.vue`
around lines 75 - 77, The explicit type-guard predicate is unnecessary because
TypeScript 5.5+ already narrows the type from stampRecommendations.value.map(id
=> stampsMap.value.get(id)).filter(...); remove any explicit type predicate and
keep the chain as shown (stampRecommendations.value.map(id =>
stampsMap.value.get(id)).filter(stamp => stamp !== undefined)) or, if you prefer
an explicit assertion for readability, replace the filter with a proper type
predicate like (stamp): stamp is Stamp => stamp !== undefined; locate this logic
around stampRecommendations.value, stampsMap.value.get, and the subsequent
filter to apply the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@src/components/Settings/StampPaletteTab/StampPaletteEditorAddableStampList.vue`:
- Around line 75-77: The explicit type-guard predicate is unnecessary because
TypeScript 5.5+ already narrows the type from stampRecommendations.value.map(id
=> stampsMap.value.get(id)).filter(...); remove any explicit type predicate and
keep the chain as shown (stampRecommendations.value.map(id =>
stampsMap.value.get(id)).filter(stamp => stamp !== undefined)) or, if you prefer
an explicit assertion for readability, replace the filter with a proper type
predicate like (stamp): stamp is Stamp => stamp !== undefined; locate this logic
around stampRecommendations.value, stampsMap.value.get, and the subsequent
filter to apply the change.

ref: https://ja.vuejs.org/guide/essentials/computed.html#avoid-mutating-computed-value
> 算出プロパティから返る値は、派生した状態です。一時的なスナップショットとして考えてください。ソースの状態が変わるたびに、新しいスナップショットが作成されます。スナップショットの値を変更することは意味がないため、計算された結果は読み取り専用として扱い、変更しないようにします。その代わり、新しい計算結果が必要な場合は、依存するソースの状態を更新します。
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/store/ui/stampPicker.ts`:
- Around line 87-91: Currently the store calls useStampSetSelector() just to get
isStampSetValid, which instantiates heavy composables (useStampCategory,
useStampPalettesStore) and creates unwanted reactive subscriptions; refactor by
extracting isStampSetValid as a standalone named export from useStampSetSelector
(export function isStampSetValid(...)) and then import that function directly in
src/store/ui/stampPicker.ts; update ensureCurrentStampSetValid to call the new
imported isStampSetValid(currentStampSet.value) instead of instantiating
useStampSetSelector, leaving currentStampSet.value assignment logic unchanged;
ensure no other store code calls useStampSetSelector to avoid reversing
dependency direction.

Comment on lines +87 to 91
const { isStampSetValid } = useStampSetSelector()
const ensureCurrentStampSetValid = () => {
if (isStampSetValid(currentStampSet.value)) return
currentStampSet.value = { type: 'recommendation', id: '' }
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find the useStampSetSelector file
fd -e ts "useStampSetSelector" --type f

Repository: traPtitech/traQ_S-UI

Length of output: 130


🏁 Script executed:

# Also search for the stampPicker file
fd -e ts "stampPicker" --type f

Repository: traPtitech/traQ_S-UI

Length of output: 91


🏁 Script executed:

# Read the useStampSetSelector.ts file
cat -n "src/components/Main/StampPicker/composables/useStampSetSelector.ts"

Repository: traPtitech/traQ_S-UI

Length of output: 1970


🏁 Script executed:

# Read the stampPicker.ts store file
cat -n "src/store/ui/stampPicker.ts"

Repository: traPtitech/traQ_S-UI

Length of output: 6169


🏁 Script executed:

# Search for other usages of useStampSetSelector
rg -n "useStampSetSelector" --type ts -C 2

Repository: traPtitech/traQ_S-UI

Length of output: 2137


useStampSetSelector() をストア内で呼び出してバリデーション関数のみ取得している

isStampSetValid の取得だけのために useStampSetSelector() をストア内でフルインスタンス化しています。コンポーザブルは useStampCategory()useStampPalettesStore() をインスタンス化時に呼び出し、さらに5つの計算プロパティを持つ大規模なリアクティブ状態を作成します。これらはストアのコンテキストで実行されるため、不要なリアクティブ状態と複数のストア購読が生成されます。

また依存方向として、ストア層(下位)がコンポーネント層のコンポーザブル(上位)を参照するのは逆方向の依存になります。

isStampSetValiduseStampSetSelector.ts からスタンドアロンの名前付きエクスポートとして切り出すことを検討してください。

♻️ 改善案

useStampSetSelector.ts 側:

+export function isStampSetValid(stampSet: StampSet): boolean {
+  // 既存の検証ロジックをここに移動
+}
+
 export default function useStampSetSelector() {
   // ...
-  const isStampSetValid = (stampSet: StampSet): boolean => { ... }
-  return { ..., isStampSetValid }
+  return { ..., isStampSetValid }
 }

stampPicker.ts 側:

-import useStampSetSelector, {
+import {
   type StampSet
-} from '/@/components/Main/StampPicker/composables/useStampSetSelector'
+  isStampSetValid
+} from '/@/components/Main/StampPicker/composables/useStampSetSelector'

-  const { isStampSetValid } = useStampSetSelector()
   const ensureCurrentStampSetValid = () => {
     if (isStampSetValid(currentStampSet.value)) return
     currentStampSet.value = { type: 'recommendation', id: '' }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/store/ui/stampPicker.ts` around lines 87 - 91, Currently the store calls
useStampSetSelector() just to get isStampSetValid, which instantiates heavy
composables (useStampCategory, useStampPalettesStore) and creates unwanted
reactive subscriptions; refactor by extracting isStampSetValid as a standalone
named export from useStampSetSelector (export function isStampSetValid(...)) and
then import that function directly in src/store/ui/stampPicker.ts; update
ensureCurrentStampSetValid to call the new imported
isStampSetValid(currentStampSet.value) instead of instantiating
useStampSetSelector, leaving currentStampSet.value assignment logic unchanged;
ensure no other store code calls useStampSetSelector to avoid reversing
dependency direction.

Copy link
Member

@Takeno-hito Takeno-hito left a comment

Choose a reason for hiding this comment

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

本番実装で統一的に入れ替えるのはちょっと今のままだと懐疑的かなぁ… traQ だと特に 2個3個のスタンプを連続で押す、というユースケースがそこそこあって、それが非常にしづらくなると思うな

レコメンデーションをなぜ入れたいのかみたいな理由が結構ほしい

Copy link
Contributor

@cp-20 cp-20 left a comment

Choose a reason for hiding this comment

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

コード自体は良いと思います (機能の是非はさておき)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants