Skip to content

Commit cbf24b8

Browse files
committed
Fix: Frontend側の不要なTimeoutを削除
1 parent 32507d9 commit cbf24b8

4 files changed

Lines changed: 112 additions & 21 deletions

File tree

packages/backend/src/core/EmojiSuggestionService.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const WORKER_RESPONSE_SCHEMA_VERSION = 'emoji-suggest-worker-response-v1';
2020
const NORMALIZATION_SCHEMA_VERSION = 'emoji-suggest-normalization-v1';
2121
const WORKER_OWNED_VERSION_PLACEHOLDER = 'worker-owned';
2222
const MAX_NORMALIZED_TEXT_LENGTH = 1000;
23+
const MIN_SUGGESTION_SCORE = 0.4;
2324

2425
export type EmojiSuggestionCandidate = {
2526
name: string;
@@ -173,7 +174,7 @@ export class EmojiSuggestionService {
173174
}
174175

175176
private isEligible(note: MiNote): boolean {
176-
return note.visibility === 'public';
177+
return note.visibility === 'public' || note.visibility === 'home';
177178
}
178179

179180
private createFallback(meta: MiMeta, reason: string): EmojiSuggestionResponse {
@@ -233,6 +234,7 @@ function parseWorkerResponse(value: unknown, maxResults: number): EmojiSuggestio
233234
const items: EmojiSuggestionCandidate[] = [];
234235
for (const item of value.items) {
235236
if (!isWorkerSuggestItem(item)) return createMalformedFallback();
237+
if (item.score < MIN_SUGGESTION_SCORE) continue;
236238
items.push({
237239
name: item.name,
238240
score: item.score,

packages/backend/test/unit/EmojiSuggestionService.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ describe('EmojiSuggestionService', () => {
8282
};
8383
}
8484

85-
test('calls Worker for public notes regardless of localOnly', async () => {
85+
test('calls Worker for public and home notes regardless of localOnly', async () => {
8686
const send = vi.fn(async () => jsonResponse(workerResponse()));
8787
const { service, logInfo } = createService(createMeta(), send);
8888

@@ -125,8 +125,15 @@ describe('EmojiSuggestionService', () => {
125125
const localOnlyBody = JSON.parse(send.mock.calls[1][1].body as string) as { eligibility: { visibility: string; localOnly: boolean } };
126126
expect(localOnlyBody.eligibility).toEqual({ visibility: 'public', localOnly: true });
127127

128+
await expect(service.suggestForNote(createNote({ visibility: 'home' }))).resolves.toMatchObject({
129+
source: 'live',
130+
items: [{ name: 'ablobgoodnightreverse' }],
131+
});
132+
expect(send).toHaveBeenCalledTimes(3);
133+
const homeBody = JSON.parse(send.mock.calls[2][1].body as string) as { eligibility: { visibility: string; localOnly: boolean } };
134+
expect(homeBody.eligibility).toEqual({ visibility: 'home', localOnly: false });
135+
128136
for (const note of [
129-
createNote({ visibility: 'home' }),
130137
createNote({ visibility: 'followers' }),
131138
createNote({ visibility: 'specified' }),
132139
]) {
@@ -137,7 +144,7 @@ describe('EmojiSuggestionService', () => {
137144
});
138145
}
139146

140-
expect(send).toHaveBeenCalledTimes(2);
147+
expect(send).toHaveBeenCalledTimes(3);
141148
});
142149

143150
test('emits redacted backend observability for cache hit, auth failure, and timeout', async () => {
@@ -218,6 +225,23 @@ describe('EmojiSuggestionService', () => {
218225
}
219226
});
220227

228+
test('filters proxied Worker suggestions below the minimum score', async () => {
229+
const send = vi.fn(async () => jsonResponse(workerResponse({
230+
items: [
231+
{ name: 'below-threshold', score: 0.39, aliases: ['below'], category: 'fixture' },
232+
{ name: 'threshold-ok', score: 0.4, aliases: ['threshold'], category: 'fixture' },
233+
],
234+
})));
235+
const { service, logInfo } = createService(createMeta(), send);
236+
237+
await expect(service.suggestForNote(createNote({ visibility: 'home' }))).resolves.toMatchObject({
238+
source: 'live',
239+
items: [{ name: 'threshold-ok', score: 0.4 }],
240+
});
241+
expect(send).toHaveBeenCalledTimes(1);
242+
expect(readLastLogEvent(logInfo)).toMatchObject({ resultCount: 1 });
243+
});
244+
221245
test('normalizes cw notes without exposing hidden body text', () => {
222246
const normalized = normalizeEmojiSuggestionNoteText(createNote({
223247
cw: 'fixture cw @alice https://example.invalid',

packages/frontend/src/components/MkEmojiPicker.vue

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,8 @@ import { misskeyApi } from '@/utility/misskey-api.js';
176176
177177
const router = useRouter();
178178
const PREVIEW_DELAY = 500;
179-
const SUGGESTION_TIMEOUT_MS = 1500;
180179
const FALLBACK_SUGGESTION_LIMIT = 8;
180+
const MIN_SUGGESTION_SCORE = 0.4;
181181
182182
type EmojiSuggestionItem = {
183183
name: string;
@@ -308,7 +308,6 @@ const suggestedEmojis = ref<Misskey.entities.EmojiSimple[]>([]);
308308
const tab = ref<'index' | 'custom' | 'unicode' | 'tags'>('index');
309309
310310
let suggestionAbortController: AbortController | null = null;
311-
let suggestionTimeoutId: number | null = null;
312311
313312
const customEmojiFolderRoot: CustomEmojiFolderTree = { value: '', category: '', children: [] };
314313
@@ -517,15 +516,10 @@ function canRequestSuggestions(): boolean {
517516
return props.asReactionPicker === true &&
518517
instance.emojiSuggestion?.enabled === true &&
519518
note != null &&
520-
note.visibility === 'public';
519+
(note.visibility === 'public' || note.visibility === 'home');
521520
}
522521
523522
function clearSuggestionRequest(): void {
524-
if (suggestionTimeoutId !== null) {
525-
window.clearTimeout(suggestionTimeoutId);
526-
suggestionTimeoutId = null;
527-
}
528-
529523
if (suggestionAbortController !== null) {
530524
suggestionAbortController.abort();
531525
suggestionAbortController = null;
@@ -538,6 +532,8 @@ function normalizeSuggestionItems(items: EmojiSuggestionItem[]): Misskey.entitie
538532
const emojis: Misskey.entities.EmojiSimple[] = [];
539533
540534
for (const item of items) {
535+
if (item.score < MIN_SUGGESTION_SCORE) continue;
536+
541537
const name = item.name.replaceAll(':', '').trim();
542538
if (name === '' || seen.has(name)) continue;
543539
@@ -583,9 +579,6 @@ async function loadSuggestedEmojis(): Promise<void> {
583579
const noteId = props.targetNote!.id;
584580
const abortController = new AbortController();
585581
suggestionAbortController = abortController;
586-
suggestionTimeoutId = window.setTimeout(() => {
587-
abortController.abort();
588-
}, SUGGESTION_TIMEOUT_MS);
589582
590583
try {
591584
const response: unknown = await misskeyApi('notes/reactions/suggestions', {
@@ -603,10 +596,6 @@ async function loadSuggestedEmojis(): Promise<void> {
603596
}
604597
} finally {
605598
if (suggestionAbortController === abortController) {
606-
if (suggestionTimeoutId !== null) {
607-
window.clearTimeout(suggestionTimeoutId);
608-
suggestionTimeoutId = null;
609-
}
610599
suggestionAbortController = null;
611600
}
612601
}

packages/frontend/test/emoji-picker-suggestions.test.ts

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,26 @@ const fallbackEmoji: Misskey.entities.EmojiSimple = {
136136
roleIdsThatCanBeUsedThisEmojiAsReaction: [],
137137
};
138138

139+
const thresholdEmoji: Misskey.entities.EmojiSimple = {
140+
name: 'threshold_ok',
141+
aliases: ['threshold'],
142+
category: 'story',
143+
url: '/client-assets/about-icon.png',
144+
localOnly: false,
145+
isSensitive: false,
146+
roleIdsThatCanBeUsedThisEmojiAsReaction: [],
147+
};
148+
149+
const belowThresholdEmoji: Misskey.entities.EmojiSimple = {
150+
name: 'below_threshold',
151+
aliases: ['below'],
152+
category: 'story',
153+
url: '/client-assets/about-icon.png',
154+
localOnly: false,
155+
isSensitive: false,
156+
roleIdsThatCanBeUsedThisEmojiAsReaction: [],
157+
};
158+
139159
function createNote(overrides: Partial<Misskey.entities.Note> = {}): Misskey.entities.Note {
140160
return {
141161
id: 'qa-note-fixture',
@@ -210,7 +230,7 @@ beforeEach(() => {
210230
clearFlatEmojiSuggestionPublicMeta();
211231
setEmojiSuggestionPublicMeta({ enabled: true, maxSuggestions: 4 });
212232

213-
customEmojis.value = [suggestedEmoji, fallbackEmoji];
233+
customEmojis.value = [suggestedEmoji, fallbackEmoji, thresholdEmoji, belowThresholdEmoji];
214234
mockedRecentlyUsedEmojis.value = [];
215235
customEmojisMap.clear();
216236
for (const emoji of customEmojis.value) {
@@ -327,8 +347,64 @@ describe('MkEmojiPicker emoji suggestions QA flows', () => {
327347
expect(mockedMisskeyApi.mock.calls[0][0]).toBe('notes/reactions/suggestions');
328348
});
329349

350+
test('shows eligible home suggestions', async () => {
351+
mockedMisskeyApi.mockResolvedValueOnce({
352+
items: [{
353+
name: suggestedEmoji.name,
354+
score: 0.98,
355+
aliases: suggestedEmoji.aliases,
356+
category: suggestedEmoji.category,
357+
}],
358+
source: 'live',
359+
reason: 'component-test',
360+
modelVersion: 'fixture-model',
361+
emojiIndexVersion: 'fixture-index',
362+
});
363+
364+
const result = await renderPicker({
365+
targetNote: createNote({ visibility: 'home' }),
366+
pinnedEmojis: [':fallback_ok:'],
367+
});
368+
369+
await waitFor(() => expect(result.getByText('Suggested')).not.toBeNull());
370+
await waitFor(() => expect(mockedMisskeyApi).toHaveBeenCalledTimes(1));
371+
expect(mockedMisskeyApi.mock.calls[0][0]).toBe('notes/reactions/suggestions');
372+
});
373+
374+
test('filters suggestion items below the minimum score before rendering', async () => {
375+
mockedMisskeyApi.mockResolvedValueOnce({
376+
items: [
377+
{
378+
name: belowThresholdEmoji.name,
379+
score: 0.39,
380+
aliases: belowThresholdEmoji.aliases,
381+
category: belowThresholdEmoji.category,
382+
},
383+
{
384+
name: thresholdEmoji.name,
385+
score: 0.4,
386+
aliases: thresholdEmoji.aliases,
387+
category: thresholdEmoji.category,
388+
},
389+
],
390+
source: 'live',
391+
reason: 'component-test',
392+
modelVersion: 'fixture-model',
393+
emojiIndexVersion: 'fixture-index',
394+
});
395+
396+
const result = await renderPicker({
397+
targetNote: createNote({ visibility: 'home' }),
398+
pinnedEmojis: [':fallback_ok:'],
399+
});
400+
401+
await waitFor(() => expect(result.getByText('Suggested')).not.toBeNull());
402+
await waitFor(() => expect(mockedMisskeyApi).toHaveBeenCalledTimes(1));
403+
expect(result.container.querySelector('[data-emoji=":threshold_ok:"]')).not.toBeNull();
404+
expect(result.container.querySelector('[data-emoji=":below_threshold:"]')).toBeNull();
405+
});
406+
330407
test.each([
331-
{ visibility: 'home' },
332408
{ visibility: 'followers' },
333409
{ visibility: 'specified' },
334410
] satisfies Partial<Misskey.entities.Note>[])('does not request suggestions for ineligible note fixture %#', async (overrides) => {

0 commit comments

Comments
 (0)