Skip to content

Citations: Double click on entry should add and focus it#15624

Open
OnNorveg wants to merge 37 commits into
JabRef:mainfrom
OnNorveg:fix-for-issue-15460
Open

Citations: Double click on entry should add and focus it#15624
OnNorveg wants to merge 37 commits into
JabRef:mainfrom
OnNorveg:fix-for-issue-15460

Conversation

@OnNorveg
Copy link
Copy Markdown
Contributor

@OnNorveg OnNorveg commented Apr 24, 2026

Related issues and pull requests

Closes #15460

PR Description

I have modified method styleFetchedListView in CitationRelationTab and added functionality for double click in citation tab to import it in the main table and focuses on it.

Steps to test

  1. Create new library
  2. Add entry using DOI 10.1109/ICSE-SEIP66354.2025.00043.
  3. Double click open entry editor
  4. Select "Citations"
  5. At the right column, double click on "A Survey of Code Review Benchmarks and Evaluation Practices in Pre-LLM and LLM Era"
  6. Entry added to main table, focused, shown at entry editor and editor shows tab "Citations"

Checklist

  • I own the copyright of the code submitted and I license it under the MIT license
  • I manually tested my changes in running JabRef (always required)
  • I added JUnit tests for changes (if applicable)
  • [/] I added screenshots in the PR description (if change is visible to the user)
  • I added a screenshot in the PR description showing a library with a single entry with me as author and as title the issue numberизображение_2026-04-24_174324873
  • I described the change in CHANGELOG.md in a way that can be understood by the average user (if change is visible to the user)
  • [/] I checked the user documentation for up to dateness and submitted a pull request to our user documentation repository

@qodo-free-for-open-source-projects
Copy link
Copy Markdown
Contributor

qodo-free-for-open-source-projects Bot commented Apr 24, 2026

Review Summary by Qodo

(Agentic_describe updated until commit 25519ad)

Double-click citation entries to import and focus them

✨ Enhancement 🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Double-click on citation entries now imports and focuses them
  - Local entries jump to existing entry in library
  - Remote entries are imported to database and displayed
• Citation key generation improved for imported entries
  - Keys generated when empty or globally configured
• Enhanced null safety with @NonNull annotations
• Added property tracking for last imported entry
Diagram
flowchart LR
  A["User double-clicks<br/>citation entry"] --> B{"Entry is<br/>local?"}
  B -->|Yes| C["Jump to entry<br/>in library"]
  B -->|No| D["Import entry<br/>to database"]
  D --> E["Set citation key<br/>if empty"]
  E --> F["Focus imported entry<br/>in editor"]
  C --> F
  F --> G["Show Citations tab"]
Loading

Grey Divider

File Changes

1. jabgui/src/main/java/org/jabref/gui/LibraryTab.java ✨ Enhancement +2/-2

Add null safety annotations to entry methods

• Added @NonNull annotations to showAndEdit() and clearAndSelect() methods
• Improves null safety for entry parameters

jabgui/src/main/java/org/jabref/gui/LibraryTab.java


2. jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java ✨ Enhancement +34/-14

Implement double-click import and focus for citations

• Implemented double-click handler for citation entries with handleItemClick() method
• Double-click on local entries jumps to them; remote entries are imported
• Added subscription to lastImportedEntryProperty() to show and focus imported entries
• Refactored styleFetchedListView() to accept citation components and current entry
• Removed redundant single-click double-click handler logic
• Added import of MouseButton for proper click detection

jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java


3. jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java ✨ Enhancement +32/-28

Add property tracking and simplify import logic

• Added lastImportedEntry property to track imported entries
• Added lastImportedEntryProperty() getter for property binding
• Refactored importCites() and importCitedBy() to delegate citation key generation to
 ImportHandler
• Simplified import logic by removing manual citation key generation
• Added @NullMarked annotation and null safety improvements
• Added assertions for database presence and non-empty entries

jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java


View more (5)
4. jabgui/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java 🐞 Bug fix +6/-5

Improve citation key generation for imports

• Enhanced generateKeys() to generate keys for entries with empty citation keys or when globally
 configured
• Updated documentation to clarify citation key generation behavior
• Changed filter logic to handle both empty keys and global configuration setting

jabgui/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java


5. jabgui/src/main/java/org/jabref/gui/maintable/MainTable.java ✨ Enhancement +2/-2

Add null safety annotations to table methods

• Added @NonNull annotation to clearAndSelect() method parameter
• Added @NonNull annotation to findEntry() method parameter
• Improves null safety for entry lookups

jabgui/src/main/java/org/jabref/gui/maintable/MainTable.java


6. jablib/src/main/java/org/jabref/model/database/BibDatabase.java ✨ Enhancement +1/-1

Add null safety annotation to indexOf method

• Added @NonNull annotation to indexOf() method parameter
• Ensures type safety for entry index lookups

jablib/src/main/java/org/jabref/model/database/BibDatabase.java


7. jabgui/src/test/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModelTest.java 🧪 Tests +25/-0

Add tests for lastImportedEntry property

• Added test importEntriesUpdatesPropertyOnSuccess() to verify property is set after import
• Added test importEntriesDoesNotUpdatePropertyOnEarlyReturn() to verify property remains null on
 early return
• Added imports for assertNotNull and assertNull assertions

jabgui/src/test/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModelTest.java


8. CHANGELOG.md 📝 Documentation +4/-0

Document citation import and key generation features

• Added entry documenting double-click import feature for citations
• Added entry documenting citation key generation for imported entries

CHANGELOG.md


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown
Contributor

qodo-free-for-open-source-projects Bot commented Apr 24, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (1) 📎 Requirement gaps (0)

Grey Divider


Action required

1. lastImportedEntry nullability mismatch ✓ Resolved 📘 Rule violation ≡ Correctness
Description
CitationsRelationsTabViewModel is @NullMarked but lastImportedEntry is stored/observed as
potentially null (new SimpleObjectProperty<>() and null checks), without marking the property type
as nullable. This undermines null-safety guarantees and can mislead callers.
Code

jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[R55-56]

+    private final ObjectProperty<BibEntry> lastImportedEntry = new SimpleObjectProperty<>();
private final ObjectProperty<SciteStatus> status;
Evidence
The null-safety rule requires explicit nullability and avoiding ad-hoc null handling under a
non-null default. The view model is @NullMarked but lastImportedEntry is created with an
implicit null value and the UI subscriber explicitly checks for null, indicating the property
value is nullable without being typed as such.

AGENTS.md
jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[35-36]
jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[55-56]
jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[208-210]
jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java[161-165]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
In a `@NullMarked` class, `lastImportedEntry` is treated as nullable at runtime (default `null`, tests expect `null`, UI subscriber checks `entry != null`) but the property is typed as non-null (`ObjectProperty<BibEntry>` / `ReadOnlyObjectProperty<BibEntry>`). This is an inconsistent nullness contract.
## Issue Context
JSpecify `@NullMarked` means types are non-null by default; nullable values should be expressed explicitly (e.g., `@Nullable` type use).
## Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[35-36]
- jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[55-56]
- jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[208-210]
- jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java[161-165]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Focus on aborted import 🐞 Bug ≡ Correctness
Description
CitationsRelationsTabViewModel#importEntries unconditionally sets lastImportedEntry even if
importCitedBy returns early, which makes CitationRelationsTab call LibraryTab.showAndEdit and
focus an entry even though the operation was aborted. This contradicts the new unit test expectation
and can mislead users by switching the editor to an entry that wasn't successfully linked.
Code

jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[R96-103]

 switch (searchType) {
     case CITES ->
-                    importCites(entries, existingEntry, importHandler, generator, generateNewKeyOnImport);
+                    importCites(entries, existingEntry, importHandler);
     case CITED_BY ->
-                    importCitedBy(entries, existingEntry, importHandler, generator, generateNewKeyOnImport);
+                    importCitedBy(entries, existingEntry, importHandler);
 }
+        lastImportedEntry.set(entries.getFirst());
}
Evidence
The view model always sets lastImportedEntry after the switch, while importCitedBy can return
early; the tab subscribes to this property and focuses the entry. The new test explicitly expects
lastImportedEntry to remain null on early return.

jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[74-103]
jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[119-133]
jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java[131-165]
jabgui/src/test/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModelTest.java[170-178]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`lastImportedEntry` is updated even when the CITED_BY import path aborts early (e.g., existing entry has no citation key). This triggers UI focus via `CitationRelationsTab`'s subscription, and it also contradicts the added unit test.
## Issue Context
- `CitationRelationsTab` subscribes to `lastImportedEntryProperty()` and calls `showAndEdit(entry)` when it becomes non-null.
- `importCitedBy` can return early, but `importEntries` still sets `lastImportedEntry` afterwards.
## Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[74-103]
- jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[119-133]
## Suggested fix
- Make `importCites`/`importCitedBy` return a boolean (success) or `Optional<BibEntry>` (focused entry), and set `lastImportedEntry` only when success is true.
- Alternatively, perform all early validation in `importEntries` before the switch, so the method can return before setting `lastImportedEntry`.
- Ensure the behavior matches `importEntriesDoesNotUpdatePropertyOnEarlyReturn` test intent.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Unlinked CITED_BY imports ✓ Resolved 🐞 Bug ≡ Correctness
Description
importCitedBy imports entries into the database before checking whether existingEntry has a
citation key; if the key is missing it returns after notifying, leaving imported entries in the
library without the intended cites relationship. This creates a surprising partial result (new
entries added but not linked).
Code

jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[R119-126]

+    private void importCitedBy(List<BibEntry> entries, BibEntry existingEntry, ImportHandler importHandler) {
+        importHandler.importEntries(entries);
+        // now the citation keys are set
+
 if (existingEntry.getCitationKey().isEmpty()) {
-            if (!generateNewKeyOnImport) {
-                dialogService.notify(Localization.lang("No citation key for %0", existingEntry.getAuthorTitleYear()));
-                return;
-            }
-            existingEntry.setCitationKey(generator.generateKey(existingEntry));
+            dialogService.notify(Localization.lang("No citation key for %0", existingEntry.getAuthorTitleYear()));
+            return;
 }
Evidence
importCitedBy calls importHandler.importEntries(entries) before validating the existing entry
citation key. ImportHandler#importEntries inserts entries into the database immediately, so an
early return after that point leaves imported entries without relationship updates.

jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[119-133]
jabgui/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java[235-260]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
In the CITED_BY import path, entries are imported into the library *before* verifying that the existing entry has a citation key (which is required to establish the relationship via the `cites` field). When the citation key is missing, the method returns after notifying, leaving imported-but-unlinked entries.
## Issue Context
`ImportHandler#importEntries` inserts the entries into the active database via `importCleanedEntries`.
## Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[119-133]
- jabgui/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java[235-260]
## Suggested fix
- Move the `existingEntry.getCitationKey().isEmpty()` check *before* `importHandler.importEntries(entries)`.
- Decide on desired behavior when the existing entry has no key:
- either generate/set a key for `existingEntry` (if that’s acceptable in this workflow), then proceed,
- or abort without importing anything.
- Ensure imports and relationship updates are all-or-nothing for this action.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. styleFetchedListView uses wrong components ✓ Resolved 📎 Requirement gap ≡ Correctness
Description
citedByListView is wired with citingComponents (and vice versa), so double-click imports use the
wrong CitationFetcher.SearchType. This can break the expected citations context when the entry
editor updates to the imported entry.
Code

jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java[R489-490]

+        styleFetchedListView(citedByListView, citingComponents, entry);
+        styleFetchedListView(citingListView, citedByComponents, entry);
Evidence
The compliance requirement expects a double-clicked entry in the citations list to open in the
editor while preserving the Citations-tab context (PR Compliance ID 2). In getPaneAndStartSearch,
citedByListView is styled using citingComponents and citingListView using citedByComponents,
which inverts the SearchType passed into the double-click import path; the view model applies
different import/linking behavior depending on SearchType.

Double-clicking a cited entry opens it in the entry editor and keeps the Citations tab active
jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java[489-490]
jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java[378-384]
jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[90-95]
jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[98-133]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`citedByListView` is currently passed `citingComponents` (and `citingListView` is passed `citedByComponents`). This inverts `CitationFetcher.SearchType` used by the double-click handler and causes the import/linking logic to run for the wrong direction.
## Issue Context
`CitationRelationsTab.styleFetchedListView(...)` uses `citationComponents.searchType()` to choose how to import/link entries (CITES vs CITED_BY). Wiring the wrong `CitationComponents` into each list view makes the double-click behavior inconsistent with the tab’s semantics.
## Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java[488-490]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

5. doubl click in CHANGELOG 📘 Rule violation ⚙ Maintainability
Description
The new changelog entry contains a user-facing typo ("doubl click") and introduces extra blank
lines, reducing professionalism and potentially breaking changelog formatting expectations. The
wording should be corrected (e.g., to "double-click" or "double click") while keeping the entry
polished and consistently formatted.
Code

CHANGELOG.md[R14-19]

+- We added the ability to import and focus an entry from the Citation tab by doubl click. [#15460](https://github.com/JabRef/jabref/issues/15460)
+
### Changed
+- When an imported entry has an empty citation key, it is generated. [#15624](https://github.com/JabRef/jabref/pull/15624)
+
Evidence
The provided citations indicate that the added CHANGELOG.md entry includes the misspelling `doubl
click`, and the compliance context notes that changelog updates should be professional and avoid
extra blank lines; the entry also inserts blank lines after bullet items, which conflicts with those
expectations and can disrupt standard changelog formatting.

AGENTS.md
CHANGELOG.md[14-19]
CHANGELOG.md[12-16]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new `CHANGELOG.md` entry contains a user-facing typo ("doubl click") and adds unnecessary blank lines around bullet entries; this should be corrected to a professional, end-user readable phrasing (e.g., "double-click"/"double click") and formatted without extra blank lines.
## Issue Context
PR compliance expects changelog updates to be polished and to avoid superfluous blank lines that can disrupt formatting. Optionally ensure terminology aligns with the UI (e.g., “Citations” tab / “Citation relations” tab as appropriate).
## Fix Focus Areas
- CHANGELOG.md[14-19]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Optional get() after isEmpty() ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
The updated code checks existingEntry.getCitationKey().isEmpty() and then calls
existingEntry.getCitationKey().get(), which is discouraged in favor of idiomatic Optional control
flow. This pattern is less readable and easier to regress into unsafe access later.
Code

jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[R123-131]

 if (existingEntry.getCitationKey().isEmpty()) {
-            if (!generateNewKeyOnImport) {
-                dialogService.notify(Localization.lang("No citation key for %0", existingEntry.getAuthorTitleYear()));
-                return;
-            }
-            existingEntry.setCitationKey(generator.generateKey(existingEntry));
+            dialogService.notify(Localization.lang("No citation key for %0", existingEntry.getAuthorTitleYear()));
+            return;
 }
 String citationKey = existingEntry.getCitationKey().get();
-
 for (BibEntry citingEntry : entries) {
     SequencedSet<String> existingCites = citingEntry.getCites();
     existingCites.add(citationKey);
     citingEntry.setCites(existingCites);
Evidence
The Optional-idiom compliance rule disallows patterns where presence is checked and then .get() is
used in the same flow. The cited code does exactly that when retrieving citationKey.

jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[123-132]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The code uses `isEmpty()` followed by `get()` on the same `Optional`, which violates the project's preferred Optional style.
## Issue Context
Use idiomatic Optional control flow such as `ifPresentOrElse`, `map`, or `orElseThrow` (when appropriate) to keep the intent clear.
## Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[123-132]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Preconditions enforced by assert ✓ Resolved 🐞 Bug ☼ Reliability
Description
importEntries uses Java assert statements for required runtime preconditions (active database
present, non-empty import list), but assertions are typically disabled in production so these checks
can be skipped. This can lead to silent misbehavior (e.g., using a new empty BibDatabaseContext)
instead of a controlled failure path.
Code

jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[R74-86]

public void importEntries(List<CitationRelationItem> entriesToImport, CitationFetcher.SearchType searchType, BibEntry existingEntry) {
+        assert stateManager.getActiveDatabase().isPresent() : "No active database found, but it is required for importing citation relations";
 BibDatabaseContext databaseContext = stateManager.getActiveDatabase().orElse(new BibDatabaseContext());
+        assert !entriesToImport.isEmpty() : "No entries to import";
 List<BibEntry> entries = entriesToImport.stream()
                                         .map(CitationRelationItem::entry)
                                         // We need to have a clone of the entry, because we add the entry to the library (and keep it in the citation relation tab, too)
                                         .map(BibEntry::new)
                                         .toList();
+        if (entries.isEmpty()) {
+            return;
+        }
Evidence
The method relies on assert for active database presence and non-empty input, but then falls back
to new BibDatabaseContext() if no active database is present; with assertions disabled this
fallback path becomes reachable.

jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[74-86]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Required preconditions are guarded with `assert`, which is commonly disabled at runtime. This can allow execution to continue in invalid states (notably `orElse(new BibDatabaseContext())`).
## Issue Context
The import flow should either:
- reliably no-op with a user-facing notification when prerequisites are missing, or
- throw a clear exception if this state is truly impossible.
## Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[74-86]
## Suggested fix
- Replace `assert stateManager.getActiveDatabase().isPresent()` with an explicit check:
- if absent: notify/log and `return` (or throw `IllegalStateException`).
- Replace/keep the empty-list guard as a normal runtime check (not assert).
- Remove `orElse(new BibDatabaseContext())` fallback in favor of a guaranteed active context when importing.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
8. Double jump on local ✓ Resolved 🐞 Bug ☼ Reliability
Description
Local items already register a double-click handler on the cell graphic container, and the new
cell-level double-click handler also calls jumpToEntry for local items. A double-click can therefore
invoke jumpToEntry twice, causing redundant task cancellation and editor re-selection.
Code

jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java[R577-605]

+                .withOnMouseClickedEvent((item, event) -> {
+                    if (event.getClickCount() == 2 && event.getButton() == MouseButton.PRIMARY) {
+                        event.consume();
+
+                        if (item.isLocal()) {
+                            // Jump if item is already in the database
+                            jumpToEntry(item);
+                        } else {
+                            // if not, import it
+                            importEntries(List.of(item), citationComponents.searchType(), currentEntry);
+
+                            // Focus
+                            // Use Platform.runLater, to make sure the database was updated
+                            javafx.application.Platform.runLater(() -> {
+                                stateManager.getActiveDatabase().ifPresent(databaseContext -> {
+                                    BibDatabase database = databaseContext.getDatabase();
+
+                                    // search imported entry to focus on it
+                                    Optional<BibEntry> addedEntry = duplicateCheck.containsDuplicate(
+                                            database,
+                                            item.entry(),
+                                            databaseContext.getMode()
+                                    );
+
+                                    addedEntry.ifPresent(entry -> {
+                                        // select and focus
+                                        stateManager.setSelectedEntries(List.of(entry));
+                                        stateManager.activeTabProperty().get().ifPresent(tab -> tab.showAndEdit(entry));
+                                    });
Evidence
For local items, hContainer.setOnMouseClicked triggers jumpToEntry on double-click, and the
newly added .withOnMouseClickedEvent also triggers jumpToEntry on double-click for
item.isLocal(). Because the HBox is the event target and the cell handler runs later during
bubbling, event.consume() in the cell handler does not prevent the HBox handler from having
already executed.

jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java[516-527]
jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java[577-585]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Double-clicking a local (duplicate) citation item can call `jumpToEntry` twice due to overlapping double-click handlers on the HBox graphic and the list-cell handler.
### Issue Context
The PR introduced a cell-level double-click handler that calls `jumpToEntry` for `item.isLocal()`, but the HBox created in `.withGraphic(...)` already registers its own double-click handler for local entries.
### Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java[516-527]
- jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java[577-605]
### Proposed fix
Keep only one double-click path for local items. Prefer removing the `hContainer.setOnMouseClicked(...)` handler for local entries (or make it consume the event and ensure only one handler runs).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@github-actions github-actions Bot added good first issue An issue intended for project-newcomers. Varies in difficulty. component: citation-relations labels Apr 24, 2026
@OnNorveg OnNorveg changed the title Fix for issue 15460 Citations: Double click on entry should add and focus it Apr 24, 2026
Copy link
Copy Markdown
Collaborator

@LoayTarek5 LoayTarek5 left a comment

Choose a reason for hiding this comment

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

Thanks @OnNorveg for working on this, i tested the PR and it solves the issue, good work.

Here are a few things that I addressed that may need fixing before the merge


// Focus
// Use Platform.runLater to make sure the database was updated
javafx.application.Platform.runLater(() -> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

the rest of this file imports JavaFX classes rather than using complete path, could you add import javafx.application.Platform at the top and replace javafx.application.Platform.runLater with Platform.runLater here for consistency?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

importEntries(List.of(item), citationComponents.searchType(), currentEntry);

// Focus
// Use Platform.runLater to make sure the database was updated
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think that this comment is inaccurate.
importEntries is synchronous on the FX thread, so the DB is already updated when it returns. The runLater is needed to defer until LibraryTab's selection listener processes the insert.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agree. Comment removed


addedEntry.ifPresent(entry -> {
// select and focus
stateManager.setSelectedEntries(List.of(entry));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

i think this is redundant, showAndEdit(entry) on the next line calls mainTable.clearAndSelect(entry), which triggers the selection listener in LibraryTab, selection into stateManager.
The existing jumpToEntry method only uses showAndEdit, please stay DRY.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agree. stateManager.setSelectedEntries(List.of(entry)); removed

Comment on lines +586 to +601
stateManager.getActiveDatabase().ifPresent(databaseContext -> {
BibDatabase database = databaseContext.getDatabase();

// search imported entry to focus on it
Optional<BibEntry> addedEntry = duplicateCheck.containsDuplicate(
database,
item.entry(),
databaseContext.getMode()
);

addedEntry.ifPresent(entry -> {
// select and focus
stateManager.setSelectedEntries(List.of(entry));
stateManager.activeTabProperty().get().ifPresent(tab -> tab.showAndEdit(entry));
});
});
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

1- the refinding the entry with DuplicateCheck.containsDuplicate uses fuzzy matching, i think is somewhat fragile.
maybe a cleaner approach would be to have importEntries return the inserted List<BibEntry> directly.
(what do you think? this is a suggestion though, as it expands the PR scope)

2-importEntries has no duplicate checking. Try double-clicking twice quickly,it inserts two clones, please add a guard.

Copy link
Copy Markdown
Contributor Author

@OnNorveg OnNorveg Apr 24, 2026

Choose a reason for hiding this comment

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

importEntries updated and used instead of DuplicateCheck.containsDuplicate. For double-clicking twice there is protection: if (item.isLocal()) {
// Jump if item is already in the database
jumpToEntry(item);

.withOnMouseClickedEvent((citationRelationItem, _) -> {
if (!citationRelationItem.isLocal()) {
listView.getCheckModel().toggleCheckState(citationRelationItem);
.withOnMouseClickedEvent((item, event) -> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

the nested if/else is hard to scan, try to consider an early return pattern to flatten it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agree, one else removed

@github-actions github-actions Bot added status: changes-required Pull requests that are not yet complete and removed status: no-bot-comments labels Apr 24, 2026
@OnNorveg
Copy link
Copy Markdown
Contributor Author

@LoayTarek5 Thank you for your comments, I find them very useful. Please, let me know if you have more comments

@github-actions github-actions Bot added status: no-bot-comments and removed status: changes-required Pull requests that are not yet complete labels Apr 24, 2026
Copy link
Copy Markdown
Collaborator

@LoayTarek5 LoayTarek5 left a comment

Choose a reason for hiding this comment

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

Thanks @OnNorveg, good progress.

Please add tests that covering: happy path returns inserted clones, and early return path returns empty list

case CITED_BY ->
importCitedBy(entries, existingEntry, importHandler, generator, generateNewKeyOnImport);
}
return entries;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This unconditionally returns the cloned list, but importCitedBy can early return without calling importHandler.importEntries(entries)
the caller then opens the entry editor on an entry that isn't in the database.

try to have each helper return what it actually inserted.

jumpToEntry(item);
} else {
// if not, import and focus
BibEntry addedEntry = importEntries(List.of(item), citationComponents.searchType(), currentEntry).getFirst();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

the .getFirst() throws NoSuchElementException if the list is empty, which i think will happen once the CITED_BY early return bug above is fixed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@LoayTarek5 I have updated the code based on the comment from calixtus to keep MVVM architecture. The View now observes lastImportedEntryProperty instead of handling the import result directly. This ensures the Entry Editor opens only after a successful process. Please, review the new solution

Comment on lines +589 to +591
if (!item.isLocal()) {
// standard behavior with one click
listView.getCheckModel().toggleCheckState(item);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

double click delivers clickCount=1 first, so click 1 briefly toggles the checkbox before click 2 triggers the import, right?(what do you think)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@LoayTarek5 I thought about strictly separating single and double clicks using a timer, but I realized it would make the UI feel laggy for users selecting multiple entries. Also, since this list allows multiple selection, a timer could lead to accidental deselection of an entire group if a user double-clicks

@github-actions github-actions Bot added status: changes-required Pull requests that are not yet complete and removed status: no-bot-comments labels Apr 26, 2026
@github-actions github-actions Bot added status: no-bot-comments and removed status: changes-required Pull requests that are not yet complete labels Apr 29, 2026
@github-actions github-actions Bot added status: no-bot-comments and removed status: changes-required Pull requests that are not yet complete labels May 15, 2026
@koppor
Copy link
Copy Markdown
Member

koppor commented May 15, 2026

I streamlined the code, tried out. Works nicely!

koppor
koppor previously approved these changes May 15, 2026
@koppor koppor added the status: awaiting-second-review For non-trivial changes label May 15, 2026
@koppor koppor marked this pull request as ready for review May 15, 2026 19:09
@qodo-free-for-open-source-projects
Copy link
Copy Markdown
Contributor

qodo-free-for-open-source-projects Bot commented May 15, 2026

Persistent review updated to latest commit 25519ad

Comment on lines 96 to 103
switch (searchType) {
case CITES ->
importCites(entries, existingEntry, importHandler, generator, generateNewKeyOnImport);
importCites(entries, existingEntry, importHandler);
case CITED_BY ->
importCitedBy(entries, existingEntry, importHandler, generator, generateNewKeyOnImport);
importCitedBy(entries, existingEntry, importHandler);
}
lastImportedEntry.set(entries.getFirst());
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

2. Focus on aborted import 🐞 Bug ≡ Correctness

CitationsRelationsTabViewModel#importEntries unconditionally sets lastImportedEntry even if
importCitedBy returns early, which makes CitationRelationsTab call LibraryTab.showAndEdit and
focus an entry even though the operation was aborted.
This contradicts the new unit test expectation and can mislead users by switching the editor to an
entry that wasn't successfully linked.
Agent Prompt
## Issue description
`lastImportedEntry` is updated even when the CITED_BY import path aborts early (e.g., existing entry has no citation key). This triggers UI focus via `CitationRelationsTab`'s subscription, and it also contradicts the added unit test.

## Issue Context
- `CitationRelationsTab` subscribes to `lastImportedEntryProperty()` and calls `showAndEdit(entry)` when it becomes non-null.
- `importCitedBy` can return early, but `importEntries` still sets `lastImportedEntry` afterwards.

## Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[74-103]
- jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java[119-133]

## Suggested fix
- Make `importCites`/`importCitedBy` return a boolean (success) or `Optional<BibEntry>` (focused entry), and set `lastImportedEntry` only when success is true.
- Alternatively, perform all early validation in `importEntries` before the switch, so the method can return before setting `lastImportedEntry`.
- Ensure the behavior matches `importEntriesDoesNotUpdatePropertyOnEarlyReturn` test intent.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Qodo is mixing up things. return "early" is after importHandler.importEntries(entries);

@github-actions github-actions Bot added status: changes-required Pull requests that are not yet complete and removed status: no-bot-comments labels May 15, 2026
assert stateManager.getActiveDatabase().isPresent() : "No active database found, but it is required for importing citation relations";
BibDatabaseContext databaseContext = stateManager.getActiveDatabase().orElse(new BibDatabaseContext());

assert !entriesToImport.isEmpty() : "No entries to import";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I believe empty database and empty list of entries should not throw an assert exception. Thay should be expected and to be dealt without.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't get this - this method should only be called with an existing library and entries to import. Otherwise, it is a logic error...

Alternative

  if (stateManager.getActiveDatabase().isEmpty) {
    LOGGER.error("Impelementation error2);
     return;
  }

Should we do that?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

User facing message: no database to import into. Please open a database.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

But the branch is never touched? If it is touched, the lgoic before is flawed. When should we report is? With only user-facing message: We will never see that something is wrong!

@github-actions
Copy link
Copy Markdown
Contributor

Your pull request conflicts with the target branch.

Please merge with your code. For a step-by-step guide to resolve merge conflicts, see https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-using-the-command-line.

@github-actions github-actions Bot added status: no-bot-comments status: changes-required Pull requests that are not yet complete and removed status: changes-required Pull requests that are not yet complete status: no-bot-comments labels May 20, 2026
@github-actions github-actions Bot added status: no-bot-comments and removed status: changes-required Pull requests that are not yet complete labels May 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Citations: Double click on entry should add and focus it

5 participants