Skip to content

Add live duplicate check to the new-entry dialog#2433

Draft
myieye wants to merge 10 commits into
developfrom
claude/duplicate-detection-review-ny053c
Draft

Add live duplicate check to the new-entry dialog#2433
myieye wants to merge 10 commits into
developfrom
claude/duplicate-detection-review-ny053c

Conversation

@myieye

@myieye myieye commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

Searches existing entries as you type in the new-entry dialog and surfaces likely duplicates before you save.

Resolves #1752. Supersedes #2411 (same feature, this branch = #2411 + latest develop + review fixes below; the review didn't change the feature's direction, UI, or architecture).

What it does

  • duplicate-check.ts — debounced multi-field search (headword/citation/gloss) via the existing FTS searchEntries; results classified as same word / similar word / similar meaning and ranked strongest-first. Cross-field coincidences (a typed vernacular value that only hit a gloss, or vice versa) are dropped as noise. Classification mirrors the backend match semantics — every parity claim in the file's comments was re-verified against the actual backend code this round (invariant case fold + diacritics significant only when typed, EntrySearchService.StripMorphTokens exact mirror incl. scoring and the postfix-length guard, FwData's no-strip behavior, the <3-char FTS fallback). Collation equivalences (ß≈ss, ligatures, ICU-ignorables) are not replicated, as documented in the file.
  • DuplicateCheck.svelte — collapsible strip in NewEntryDialog.svelte (amber when the word already exists); auto-expands on an exact word match; rows expand in place for "Add sense" / "Go to entry"; "Show N more" for long lists; quiet green "Looks like a new word" when nothing matches.
  • DuplicateSummaryPill.svelte — sticky pill shown when the strip scrolls out of view; click jumps back and expands; dismissible.
  • Per-row rescue: add the typed sense to the existing entry instead of creating a duplicate (only the meaning is saved — the word already exists).
  • Keyboard: Enter inside the duplicate UI never submits the dialog; double-Enter can no longer double-create (pre-existing race, fixed in passing).
  • No backend changes.

Changes since #2411

  1. Merged latest develop, absorbing the E2e test fw lite #1866 E2E-test restructure: the feature's Playwright suite moved to tests/ui/ and now uses the new DemoProjectPage page object (the old BrowsePage it imported was deleted on develop — Add live duplicate check to the new-entry dialog #2411 as-is no longer runs its tests).
  2. Jump pill capped at 32rem — with a long headword preview it stretched across the whole (up to 50rem) dialog. Truncation + the "N+" badge take over past the cap:
    pill with long preview, capped
  3. Search failures are no longer silent. duplicatesResource.error was never read: a failed searchEntries (e.g. connection loss on a server-hosted session) made the widget silently go blank. Now an inline status line says "Could not check for similar entries"; typing again retries. Deliberately not an AppNotification.error toast (the EditEntryDialog/ActivityView precedent): this search re-fires on every typing pause, and a toast per failure would bury the dialog — the strip already owns a status line.
  4. "Show N more..." uses $plural so translators get per-language plural forms (matches the SyncDialog convention).
  5. New tests for previously uncovered behavior at the heart of the feature's promise: trapEnter unit test; Enter pressed inside the strip expands the row and does not create the entry (asserted via entry count); add-sense asserts no duplicate entry was created alongside the rescued sense; pill dismissal persists while matches keep changing.
  6. i18n re-extracted with translator context comments for the new strings.

The CodeRabbit race on #2411 (go-to-entry clickable during a pending add-sense) was already fixed there in ad8bfa5 — re-verified here: both row actions disable on busy, and the dialog's Create honors it.

Screenshots

Exact match (amber strip, auto-expanded, light/dark verified):
exact match

Row expanded in place with rescue actions:
row actions

Jump pill when the strip is out of view (now width-capped):
jump pill

New word / similar-only (collapsed, muted):
new word
similar collapsed

Dark mode spot-checks: exact match · row actions

(Screenshots were committed in d267bd1 and reverted in the next commit, so they don't live on the branch.)

Test evidence

  • vitest --project unit duplicate-check.test.ts: 34/34 pass
  • playwright -c tests/ui new-entry-duplicates: 9/9 pass (7 ported + 2 new)
  • svelte-check: 0 errors / 0 warnings; eslint on all touched files: clean
  • i18n extraction re-run; catalogs consistent across all 8 locales, no duplicate msgids after the develop merge

Reviewer notes / merge interactions

  • Add variant support to FwLite (backend + viewer UI) #2410 (variants) also touches DictionaryEntry.svelte; this PR adds the inline prop there. Whoever merges second gets a small conflict in the props block + sense-separator markup — keep both changes.
  • Display pictures in senses #2412 (pictures) touches all src/locales/*.po → guaranteed extraction conflicts. Resolution: take either side, re-run pnpm run i18n:extract, commit.
  • The demo/in-memory API ignores SortField.SearchRelevance (sorts alphabetically) — the UI tests exercise classification, not backend ranking. Real ranking is covered by the backend's own tests.
  • Deferred follow-ups: Storybook stories for the two new components (Playwright covers behavior); collation-level fold equivalences if the backend fold ever changes (documented in duplicate-check.ts).

Considered and rejected (carried over from #2411)

  • Wizard-style duplicate step — adds a click to the common no-duplicate case
  • Post-submit interstitial — warns only after the duplicate is typed in full; inline is cheaper to act on
  • Combobox under the headword field — ties the check to one field; duplicates can hide in citation form or gloss

🤖 Generated with Claude Code

https://claude.ai/code/session_01R49zrCE7hPgm9QJ5bfugaV


Generated by Claude Code

myieye and others added 10 commits July 4, 2026 22:05
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Spec-cited review against EntrySearchService/SqlHelpers found the client
fold diverged: host-locale lowercasing, unconditional accent-stripping
(backend keeps diacritics significant when the query has them), and no
morph-token handling (typed "-aji" vs suffix entry "aji" missed exact).
Also ranks each search by the writing system it was typed in, fixes the
mount-time "Checking" flash, singularizes the one-match banner, and
closes a pre-existing double-Enter double-create in the dialog.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- match rows expand to reveal Add sense / Go to entry; DictionaryEntry gains an inline mode for the compact collapsed rows
- classification: attribute same-word matches to the field that hit (Same headword vs Same lexeme form), rank similar words closest-in-length first, drop cross-field coincidences instead of a vague 'related' kind
- classify in a $derived so the lazy morph-types resource warms at mount and re-classifies without re-searching
- dedupe queries per field kind: the same text typed as lexeme and gloss keeps its gloss query (same-meaning matches were lost)
- share the banner message/tint/Enter-trap between the strip and the extracted DuplicateSummaryPill
- scope backend-parity doc claims to the CRDT FTS path; drop unverified FLEx attribution
- adapt the Playwright suite to the new interaction model and cover the jump pill (7 tests); pin length-delta boundary, gloss containment direction, and closest-form ranking in unit tests (33; 8/8 targeted mutants killed)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…icates

# Conflicts:
#	frontend/viewer/src/locales/en.po
#	frontend/viewer/src/locales/es.po
#	frontend/viewer/src/locales/fr.po
#	frontend/viewer/src/locales/id.po
#	frontend/viewer/src/locales/ko.po
#	frontend/viewer/src/locales/ms.po
#	frontend/viewer/src/locales/sw.po
#	frontend/viewer/src/locales/vi.po
…fill test gaps

- Move new-entry-duplicates.test.ts to tests/ui/ and port it to the
  DemoProjectPage page object introduced by the E2E restructure (#1866)
- Cap the jump pill at 32rem so long headword previews don't stretch it
  across the whole dialog
- New tests: trapEnter unit test, Enter-inside-strip must not create the
  entry, pill dismiss persists, add-sense must not also create an entry

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R49zrCE7hPgm9QJ5bfugaV
A failed searchEntries call previously vanished into the unread
resource.error — no toast, no status line, the widget just went blank.
Show an inline status line instead of a toast: the search re-fires per
typing pause, and a toast per failure would bury the dialog.

Show-more now uses $plural so translators get per-language plural forms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R49zrCE7hPgm9QJ5bfugaV
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c7adbcfb-60ac-4631-beb8-078b2ecf489d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/duplicate-detection-review-ny053c

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.

@github-actions github-actions Bot added the 💻 FW Lite issues related to the fw lite application, not miniLcm or crdt related label Jul 12, 2026
@argos-ci

argos-ci Bot commented Jul 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) ✅ No changes detected - Jul 12, 2026, 10:12 AM
e2e (Inspect) ⚠️ Changes detected (Review) 5 changed Jul 12, 2026, 10:19 AM

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

Labels

💻 FW Lite issues related to the fw lite application, not miniLcm or crdt related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Show Existing Entries in Create Entry Dialog to Prevent Duplicate

2 participants