diff --git a/frontend/src/lib/shared/discussion/MentionDropdown.svelte b/frontend/src/lib/shared/discussion/MentionDropdown.svelte index 9732df50..0cc4378c 100644 --- a/frontend/src/lib/shared/discussion/MentionDropdown.svelte +++ b/frontend/src/lib/shared/discussion/MentionDropdown.svelte @@ -4,17 +4,18 @@ import type { components } from '$lib/generated/api'; import { formatLifeDateRange } from '$lib/person/personLifeDates'; import { untrack } from 'svelte'; import { m } from '$lib/paraglide/messages.js'; - -type Person = components['schemas']['Person']; - // Layered defence cap on the @mention search query length (CWE-400 // amplification). The attribute below caps direct // user edits, but the editor-mirror path (Tiptap contenteditable -> mirror // $effect -> searchQuery) is not covered by `maxlength` since the // contenteditable has no such enforcement. Clipping at the mirror keeps // the cap honest from both paths. Tracked server-side separately. -// Nora #1 on PR #629. -const MAX_QUERY_LENGTH = 100; +// Nora #1 on PR #629. Hoisted to mentionConstants.ts so the host editor +// (PersonMentionEditor) can clip the inserted displayName to the same cap +// — see Felix #3 on PR #629. +import { MAX_QUERY_LENGTH } from './mentionConstants'; + +type Person = components['schemas']['Person']; // The dropdown receives a single reactive state object. PersonMentionEditor // mutates fields on this object (model.items = ..., etc.) and Svelte's $state diff --git a/frontend/src/lib/shared/discussion/PersonMentionEditor.svelte b/frontend/src/lib/shared/discussion/PersonMentionEditor.svelte index 6512f2fd..8714a574 100644 --- a/frontend/src/lib/shared/discussion/PersonMentionEditor.svelte +++ b/frontend/src/lib/shared/discussion/PersonMentionEditor.svelte @@ -9,9 +9,7 @@ import type { PersonMention } from '$lib/shared/types'; import { deserialize, serialize } from '$lib/shared/discussion/mentionSerializer'; import { debounce } from '$lib/shared/utils/debounce'; import MentionDropdown from './MentionDropdown.svelte'; - -const SEARCH_DEBOUNCE_MS = 150; -const SEARCH_RESULT_LIMIT = 5; +import { SEARCH_DEBOUNCE_MS, SEARCH_RESULT_LIMIT } from './mentionConstants'; type Person = components['schemas']['Person']; diff --git a/frontend/src/lib/shared/discussion/PersonMentionEditor.svelte.spec.ts b/frontend/src/lib/shared/discussion/PersonMentionEditor.svelte.spec.ts index e21eb412..591a7ef8 100644 --- a/frontend/src/lib/shared/discussion/PersonMentionEditor.svelte.spec.ts +++ b/frontend/src/lib/shared/discussion/PersonMentionEditor.svelte.spec.ts @@ -11,14 +11,14 @@ import { page, userEvent } from 'vitest/browser'; import PersonMentionEditorHost from './PersonMentionEditor.test-host.svelte'; import type { components } from '$lib/generated/api'; import { m } from '$lib/paraglide/messages.js'; +// Single source of truth for the debounce window — imported from the shared +// module so the test cannot drift from production. Sara on PR #629 round 3. +import { SEARCH_DEBOUNCE_MS } from './mentionConstants'; type Person = components['schemas']['Person']; type PersonMention = components['schemas']['PersonMention']; -// Mirror of the debounce in PersonMentionEditor.svelte. Naming the magic and -// using a generous slack (SEARCH_DEBOUNCE_MS + 350 = 500 ms) kills CI-jitter -// flakiness Sara raised on PR #629. -const SEARCH_DEBOUNCE_MS = 150; +// Slack on top of the debounce to absorb CI jitter (total 500 ms is generous). const POST_DEBOUNCE_SLACK_MS = 350; const AUGUSTE: Person = { diff --git a/frontend/src/lib/shared/discussion/mentionConstants.ts b/frontend/src/lib/shared/discussion/mentionConstants.ts new file mode 100644 index 00000000..3b946345 --- /dev/null +++ b/frontend/src/lib/shared/discussion/mentionConstants.ts @@ -0,0 +1,6 @@ +/** Shared knobs for the @mention typeahead. Single source of truth for + * the dropdown component and the host editor — keeps the layered length + * cap and the debounce window consistent across both files. */ +export const MAX_QUERY_LENGTH = 100; +export const SEARCH_DEBOUNCE_MS = 150; +export const SEARCH_RESULT_LIMIT = 5;