Skip to content

Add support for displaying PDF annotations in entry preview#15493

Closed
hallekoyanagi wants to merge 12 commits intoJabRef:mainfrom
Jullo-123:final-branch
Closed

Add support for displaying PDF annotations in entry preview#15493
hallekoyanagi wants to merge 12 commits intoJabRef:mainfrom
Jullo-123:final-branch

Conversation

@hallekoyanagi
Copy link
Copy Markdown

@hallekoyanagi hallekoyanagi commented Apr 4, 2026

Related issues and pull requests

Closes #4257

PR Description

This change integrates PDF annotations into the entry preview by post-processing the generated preview HTML in PreviewViewer, appending rendered annotation content from cached PDF data. The goal was to make annotations visible directly within the preview without modifying the existing layout system, keeping the implementation lightweight and consistent with the current rendering pipeline.

This approach ensures annotations are displayed reliably regardless of user-defined preview layouts, resulting in a smaller and more maintainable change than initially expected while improving visibility of PDF metadata for users.

image

Steps to test

  1. Open JabRef and create or open a .bib library.
  2. Add a new entry (e.g., @Article) and attach a PDF file that has annotations.
  3. Select the entry.
  4. Confirm that the PDF annotations now appear at the bottom of the preview, showing:
    a. annotation type
    b. page number
    c. annotation content

Expected Result

image

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
  • 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

Review Summary by Qodo

Add PDF annotations display in entry preview

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Display PDF annotations in entry preview with file names and page numbers
• Render annotations as HTML appended to preview with proper escaping
• Support linked annotations showing highlights with attached notes
• Filter and sort annotations by page for organized presentation
Diagram
flowchart LR
  A["PreviewViewer.update()"] -->|generates preview| B["layout.generatePreview()"]
  B -->|HTML preview| C["injectAnnotations()"]
  C -->|retrieves annotations| D["FileAnnotationCache"]
  D -->|annotation data| E["FileAnnotationHtmlRenderer.render()"]
  E -->|formatted HTML| F["Appended to preview"]
Loading

Grey Divider

File Changes

1. jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java ✨ Enhancement +20/-1

Integrate annotation injection into preview generation

• Added imports for Path, Map, FileAnnotationCache, FileAnnotationHtmlRenderer, and
 FileAnnotation
• Modified update() method to inject annotations into generated preview HTML
• Implemented injectAnnotations() helper method that retrieves cached annotations and appends
 rendered HTML to preview

jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java


2. jablib/src/main/java/org/jabref/logic/pdf/FileAnnotationHtmlRenderer.java ✨ Enhancement +79/-0

Create HTML renderer for PDF annotations

• New utility class for rendering PDF annotations as HTML
• render() method converts annotation map to formatted HTML with file names and sorted annotations
• renderAnnotation() method formats individual annotations with type, page number, and content
• escapeHtml() method prevents XSS attacks by escaping HTML special characters
• Supports linked annotations displaying highlights with attached notes

jablib/src/main/java/org/jabref/logic/pdf/FileAnnotationHtmlRenderer.java


3. jablib/src/test/java/org/jabref/logic/pdf/FileAnnotationHtmlRendererTest.java 🧪 Tests +131/-0

Add tests for annotation HTML renderer

• Comprehensive test suite with 8 test cases covering renderer functionality
• Tests for empty/null input handling, single and multiple annotations, sorting by page
• Validates HTML escaping for XSS prevention and linked annotation rendering
• Tests multiple file support and filtering of blank annotations

jablib/src/test/java/org/jabref/logic/pdf/FileAnnotationHtmlRendererTest.java


View more (1)
4. CHANGELOG.md 📝 Documentation +1/-0

Document PDF annotations feature

• Added entry documenting support for displaying linked PDF annotations in entry preview
• References issue #4257

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 4, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (6) 📎 Requirement gaps (1) 🎨 UX Issues (0)

Grey Divider


Action required

1. Annotations bypass preview template 📎 Requirement gap ≡ Correctness
Description
The preview HTML is post-processed in PreviewViewer by appending rendered annotations, rather than
exposing annotations via a dedicated entry field usable in Preview Entry templates. This prevents
users from placing/formatting annotations through the template system as required.
Code

jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java[R218-221]

+        BackgroundTask.wrap(() -> {
+                          String previewHtml = layout.generatePreview(currentEntry, databaseContext);
+                          return injectAnnotations(previewHtml, currentEntry, databaseContext);
+                      })
Evidence
PR Compliance ID 1 requires a dedicated entry field that can be referenced in the Preview Entry
template; the new code instead always appends annotation HTML after layout.generatePreview(...),
which is not template/field-based.

Provide PDF annotation reference as a preview entry field
jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java[218-221]

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

## Issue description
PDF annotations are appended to the generated preview HTML instead of being exposed via a dedicated entry field that users can reference in Preview Entry templates.
## Issue Context
The requirement is to make annotations available through a field usable in preview templates (so users can customize layouts), rather than hard-wiring the annotations at the bottom of the preview.
## Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java[218-221]
- jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java[403-410]

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


2. Unlocalized annotation UI strings 📘 Rule violation ⚙ Maintainability
Description
The new annotation rendering hardcodes user-facing strings like PDF Annotations and Note: in
generated preview HTML. This breaks localization requirements for UI text.
Code

jablib/src/main/java/org/jabref/logic/pdf/FileAnnotationHtmlRenderer.java[R26-62]

+        StringBuilder html = new StringBuilder();
+        html.append("<BR><BR><b>PDF Annotations</b><BR>");
+
+        for (Map.Entry<Path, List<FileAnnotation>> fileEntry : annotations.entrySet()) {
+            List<FileAnnotation> fileAnnotations = fileEntry.getValue();
+            if (fileAnnotations == null || fileAnnotations.isEmpty()) {
+                continue;
+            }
+
+            String fileName = fileEntry.getKey().getFileName().toString();
+            html.append("<BR><i>").append(escapeHtml(fileName)).append("</i><BR>");
+
+            fileAnnotations.stream()
+                           .sorted(Comparator.comparingInt(FileAnnotation::getPage))
+                           .filter(annotation -> StringUtil.isNotBlank(annotation.getContent()))
+                           .forEach(annotation -> renderAnnotation(html, annotation));
+        }
+
+        return html.toString();
+    }
+
+    private static void renderAnnotation(StringBuilder html, FileAnnotation annotation) {
+        String type = annotation.getAnnotationType().toString();
+        int page = annotation.getPage();
+        String content = annotation.getContent();
+
+        html.append("<b>")
+            .append(escapeHtml(type))
+            .append(" (p. ").append(page).append("):</b> ");
+
+        if (annotation.hasLinkedAnnotation()) {
+            // highlights/underlines with a sticky note attached
+            html.append(escapeHtml(content));
+            String noteContent = annotation.getLinkedFileAnnotation().getContent();
+            if (StringUtil.isNotBlank(noteContent)) {
+                html.append(" — <i>Note: ").append(escapeHtml(noteContent)).append("</i>");
+            }
Evidence
PR Compliance ID 19 requires user-facing UI text to be localized; the renderer emits fixed English
strings into the preview output.

AGENTS.md
jablib/src/main/java/org/jabref/logic/pdf/FileAnnotationHtmlRenderer.java[26-28]
jablib/src/main/java/org/jabref/logic/pdf/FileAnnotationHtmlRenderer.java[60-62]

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

## Issue description
User-facing strings in the preview annotation HTML output are hardcoded in English.
## Issue Context
Preview output is UI-visible; strings such as the section header and linked-note label must be localized using the project's localization mechanism.
## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/pdf/FileAnnotationHtmlRenderer.java[26-28]
- jablib/src/main/java/org/jabref/logic/pdf/FileAnnotationHtmlRenderer.java[60-62]

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


3. Tests assert via contains() 📘 Rule violation ☼ Reliability
Description
New unit tests primarily use assertTrue(result.contains(...)) which is a weak predicate check and
can pass with near-miss output. These tests should assert exact expected HTML (or structured
expectations) when feasible.
Code

jablib/src/test/java/org/jabref/logic/pdf/FileAnnotationHtmlRendererTest.java[R40-52]

+    void renderSingleHighlightAnnotation() {
+        FileAnnotation annotation = createAnnotation("important text", 3, FileAnnotationType.HIGHLIGHT);
+        Map<Path, List<FileAnnotation>> annotations = Map.of(
+                Path.of("paper.pdf"), List.of(annotation)
+        );
+
+        String result = FileAnnotationHtmlRenderer.render(annotations);
+
+        assertTrue(result.contains("Highlight"));
+        assertTrue(result.contains("p. 3"));
+        assertTrue(result.contains("important text"));
+        assertTrue(result.contains("paper.pdf"));
+    }
Evidence
PR Compliance ID 35 requires exact-value assertions instead of loose contains checks; the new
tests validate output with multiple substring predicates rather than asserting the full expected
rendering.

jablib/src/test/java/org/jabref/logic/pdf/FileAnnotationHtmlRendererTest.java[40-52]
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 renderer tests use weak substring/predicate assertions (`contains`) instead of asserting exact expected output.
## Issue Context
Exact assertions provide stronger regression protection and clearer diagnostics when output changes.
## Fix Focus Areas
- jablib/src/test/java/org/jabref/logic/pdf/FileAnnotationHtmlRendererTest.java[40-52]
- jablib/src/test/java/org/jabref/logic/pdf/FileAnnotationHtmlRendererTest.java[55-67]
- jablib/src/test/java/org/jabref/logic/pdf/FileAnnotationHtmlRendererTest.java[69-95]
- jablib/src/test/java/org/jabref/logic/pdf/FileAnnotationHtmlRendererTest.java[97-126]

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


View more (1)
4. Cache recreated every refresh 🐞 Bug ➹ Performance
Description
PreviewViewer.injectAnnotations creates a new FileAnnotationCache on every update, defeating the
Caffeine cache and repeatedly re-reading PDFs from disk. This can noticeably slow preview rendering
because annotation extraction happens in the same background task as preview generation.
Code

jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java[R403-406]

+    private String injectAnnotations(String previewHtml, BibEntry entry, BibDatabaseContext databaseContext) {
+        FileAnnotationCache annotationCache = new FileAnnotationCache(databaseContext, preferences.getFilePreferences());
+        Map<Path, List<FileAnnotation>> annotations = annotationCache.getFromCache(entry);
+        if (annotations == null || annotations.isEmpty()) {
Evidence
injectAnnotations instantiates a new FileAnnotationCache per call and immediately loads annotations;
FileAnnotationCache is designed as a long-lived Caffeine LoadingCache and its loader scans PDFs
(disk I/O + per-page iteration). The GUI already creates and holds a single FileAnnotationCache per
LibraryTab, indicating the intended lifecycle is reuse rather than per-render recreation.

jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java[205-225]
jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java[403-411]
jablib/src/main/java/org/jabref/logic/pdf/FileAnnotationCache.java[31-44]
jablib/src/main/java/org/jabref/logic/pdf/PdfAnnotationImporter.java[35-65]
jabgui/src/main/java/org/jabref/gui/LibraryTab.java[226-231]
jabgui/src/main/java/org/jabref/gui/LibraryTab.java[798-800]

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

## Issue description
`PreviewViewer.injectAnnotations(...)` allocates a new `FileAnnotationCache` on every preview refresh. Because `FileAnnotationCache` is a Caffeine `LoadingCache` whose loader imports annotations by scanning PDFs, recreating it defeats caching and causes repeated PDF disk reads, delaying preview rendering.
### Issue Context
A long-lived `FileAnnotationCache` already exists per `LibraryTab` and is exposed via `getAnnotationCache()`, indicating reuse is expected.
### Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java[205-225]
- jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java[403-411]
- jabgui/src/main/java/org/jabref/gui/LibraryTab.java[226-231]
### Suggested fix
- Add a `@Nullable FileAnnotationCache annotationCache` field in `PreviewViewer`.
- Initialize/refresh it when `setDatabaseContext(...)` is called (and set to `null` when context is `null`).
- In `injectAnnotations(...)`, use the existing field instead of creating a new cache.
- (Optional, but good) If `annotationCache` is `null` (e.g., no DB context), return `previewHtml` without attempting annotation loading.

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



Remediation recommended

5. Trivial comments added 📘 Rule violation ⚙ Maintainability
Description
New comments in FileAnnotationHtmlRenderer restate what the code already expresses (e.g., “Renders
PDF annotations…”), adding noise without documenting intent/why. This violates the requirement to
avoid trivial comments and use the preferred documentation style when needed.
Code

jablib/src/main/java/org/jabref/logic/pdf/FileAnnotationHtmlRenderer.java[R17-20]

+    // Renders PDF annotations as an HTML string for the entry preview
+    // @param annotations map of file paths to their annotations
+    // @return formatted HTML string, or empty string if no annotations exist
+
Evidence
PR Compliance ID 15 forbids adding trivial restatement comments; the added block comment simply
repeats method behavior and parameter meaning.

AGENTS.md
jablib/src/main/java/org/jabref/logic/pdf/FileAnnotationHtmlRenderer.java[17-20]

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

## Issue description
Trivial/restatement comments were added and should be removed or replaced with meaningful intent-focused documentation (using the project's preferred style).
## Issue Context
Comments should document rationale/why, not repeat signatures or obvious behavior.
## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/pdf/FileAnnotationHtmlRenderer.java[17-20]

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


6. GUI does annotation processing 📘 Rule violation ⚙ Maintainability
Description
PreviewViewer (GUI layer) now constructs an annotation cache and performs annotation retrieval +
HTML composition, which is non-UI logic. This risks further business logic creep into the GUI layer
instead of delegating to org.jabref.logic.
Code

jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java[R403-410]

+    private String injectAnnotations(String previewHtml, BibEntry entry, BibDatabaseContext databaseContext) {
+        FileAnnotationCache annotationCache = new FileAnnotationCache(databaseContext, preferences.getFilePreferences());
+        Map<Path, List<FileAnnotation>> annotations = annotationCache.getFromCache(entry);
+        if (annotations == null || annotations.isEmpty()) {
+            return previewHtml;
+        }
+        String annotationHtml = FileAnnotationHtmlRenderer.render(annotations);
+        return previewHtml + "<div style='margin-top:10px'>" + annotationHtml + "</div>";
Evidence
PR Compliance ID 17 requires GUI code to delegate non-UI logic to the logic layer; the added method
performs data retrieval and output generation directly in the GUI class.

AGENTS.md
jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java[403-410]

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 GUI `PreviewViewer` now contains annotation retrieval and HTML generation logic.
## Issue Context
To preserve layering, the GUI should call a logic-layer service/helper that returns ready-to-render content (or a field value), keeping GUI code focused on UI concerns.
## Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java[403-410]

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


7. Uses Collections.emptyMap() in test 📘 Rule violation ⚙ Maintainability
Description
A new test uses Collections.emptyMap() instead of modern factory methods. This conflicts with the
rule to prefer modern Java idioms in new/modified code.
Code

jablib/src/test/java/org/jabref/logic/pdf/FileAnnotationHtmlRendererTest.java[R28-30]

+    void renderEmptyMapReturnsEmptyString() {
+        String result = FileAnnotationHtmlRenderer.render(Collections.emptyMap());
+        assertEquals("", result);
Evidence
PR Compliance ID 3 requires preferring modern Java APIs/idioms; Collections.emptyMap() is listed
as an older pattern compared to Map.of() for an empty map.

AGENTS.md
jablib/src/test/java/org/jabref/logic/pdf/FileAnnotationHtmlRendererTest.java[28-30]

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

## Issue description
A new test uses `Collections.emptyMap()` where a modern collection factory can be used.
## Issue Context
The codebase prefers modern Java idioms for readability/consistency.
## Fix Focus Areas
- jablib/src/test/java/org/jabref/logic/pdf/FileAnnotationHtmlRendererTest.java[28-30]

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


View more (1)
8. Preview output uses trailing colons 📘 Rule violation ⚙ Maintainability
Description
The generated preview annotation lines include labels ending with : (e.g., ... (p. N): and
Note:). This conflicts with the UI label wording convention.
Code

jablib/src/main/java/org/jabref/logic/pdf/FileAnnotationHtmlRenderer.java[R52-62]

+        html.append("<b>")
+            .append(escapeHtml(type))
+            .append(" (p. ").append(page).append("):</b> ");
+
+        if (annotation.hasLinkedAnnotation()) {
+            // highlights/underlines with a sticky note attached
+            html.append(escapeHtml(content));
+            String noteContent = annotation.getLinkedFileAnnotation().getContent();
+            if (StringUtil.isNotBlank(noteContent)) {
+                html.append(" — <i>Note: ").append(escapeHtml(noteContent)).append("</i>");
+            }
Evidence
PR Compliance ID 21 states UI labels must not end with a colon; the generated preview output
includes ): and Note: in user-visible text.

AGENTS.md
jablib/src/main/java/org/jabref/logic/pdf/FileAnnotationHtmlRenderer.java[52-62]

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

## Issue description
User-visible annotation labels in the preview output end with colons.
## Issue Context
The UI wording convention disallows trailing colons for labels.
## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/pdf/FileAnnotationHtmlRenderer.java[52-62]

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


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +218 to +221
BackgroundTask.wrap(() -> {
String previewHtml = layout.generatePreview(currentEntry, databaseContext);
return injectAnnotations(previewHtml, currentEntry, databaseContext);
})
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

1. Annotations bypass preview template 📎 Requirement gap ≡ Correctness

The preview HTML is post-processed in PreviewViewer by appending rendered annotations, rather than
exposing annotations via a dedicated entry field usable in Preview Entry templates. This prevents
users from placing/formatting annotations through the template system as required.
Agent Prompt
## Issue description
PDF annotations are appended to the generated preview HTML instead of being exposed via a dedicated entry field that users can reference in Preview Entry templates.

## Issue Context
The requirement is to make annotations available through a field usable in preview templates (so users can customize layouts), rather than hard-wiring the annotations at the bottom of the preview.

## Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java[218-221]
- jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java[403-410]

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

Comment on lines +26 to +62
StringBuilder html = new StringBuilder();
html.append("<BR><BR><b>PDF Annotations</b><BR>");

for (Map.Entry<Path, List<FileAnnotation>> fileEntry : annotations.entrySet()) {
List<FileAnnotation> fileAnnotations = fileEntry.getValue();
if (fileAnnotations == null || fileAnnotations.isEmpty()) {
continue;
}

String fileName = fileEntry.getKey().getFileName().toString();
html.append("<BR><i>").append(escapeHtml(fileName)).append("</i><BR>");

fileAnnotations.stream()
.sorted(Comparator.comparingInt(FileAnnotation::getPage))
.filter(annotation -> StringUtil.isNotBlank(annotation.getContent()))
.forEach(annotation -> renderAnnotation(html, annotation));
}

return html.toString();
}

private static void renderAnnotation(StringBuilder html, FileAnnotation annotation) {
String type = annotation.getAnnotationType().toString();
int page = annotation.getPage();
String content = annotation.getContent();

html.append("<b>")
.append(escapeHtml(type))
.append(" (p. ").append(page).append("):</b> ");

if (annotation.hasLinkedAnnotation()) {
// highlights/underlines with a sticky note attached
html.append(escapeHtml(content));
String noteContent = annotation.getLinkedFileAnnotation().getContent();
if (StringUtil.isNotBlank(noteContent)) {
html.append(" — <i>Note: ").append(escapeHtml(noteContent)).append("</i>");
}
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. Unlocalized annotation ui strings 📘 Rule violation ⚙ Maintainability

The new annotation rendering hardcodes user-facing strings like PDF Annotations and Note: in
generated preview HTML. This breaks localization requirements for UI text.
Agent Prompt
## Issue description
User-facing strings in the preview annotation HTML output are hardcoded in English.

## Issue Context
Preview output is UI-visible; strings such as the section header and linked-note label must be localized using the project's localization mechanism.

## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/pdf/FileAnnotationHtmlRenderer.java[26-28]
- jablib/src/main/java/org/jabref/logic/pdf/FileAnnotationHtmlRenderer.java[60-62]

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

Comment on lines +40 to +52
void renderSingleHighlightAnnotation() {
FileAnnotation annotation = createAnnotation("important text", 3, FileAnnotationType.HIGHLIGHT);
Map<Path, List<FileAnnotation>> annotations = Map.of(
Path.of("paper.pdf"), List.of(annotation)
);

String result = FileAnnotationHtmlRenderer.render(annotations);

assertTrue(result.contains("Highlight"));
assertTrue(result.contains("p. 3"));
assertTrue(result.contains("important text"));
assertTrue(result.contains("paper.pdf"));
}
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

3. Tests assert via contains() 📘 Rule violation ☼ Reliability

New unit tests primarily use assertTrue(result.contains(...)) which is a weak predicate check and
can pass with near-miss output. These tests should assert exact expected HTML (or structured
expectations) when feasible.
Agent Prompt
## Issue description
The new renderer tests use weak substring/predicate assertions (`contains`) instead of asserting exact expected output.

## Issue Context
Exact assertions provide stronger regression protection and clearer diagnostics when output changes.

## Fix Focus Areas
- jablib/src/test/java/org/jabref/logic/pdf/FileAnnotationHtmlRendererTest.java[40-52]
- jablib/src/test/java/org/jabref/logic/pdf/FileAnnotationHtmlRendererTest.java[55-67]
- jablib/src/test/java/org/jabref/logic/pdf/FileAnnotationHtmlRendererTest.java[69-95]
- jablib/src/test/java/org/jabref/logic/pdf/FileAnnotationHtmlRendererTest.java[97-126]

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

Comment on lines +403 to +406
private String injectAnnotations(String previewHtml, BibEntry entry, BibDatabaseContext databaseContext) {
FileAnnotationCache annotationCache = new FileAnnotationCache(databaseContext, preferences.getFilePreferences());
Map<Path, List<FileAnnotation>> annotations = annotationCache.getFromCache(entry);
if (annotations == null || annotations.isEmpty()) {
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

4. Cache recreated every refresh 🐞 Bug ➹ Performance

PreviewViewer.injectAnnotations creates a new FileAnnotationCache on every update, defeating the
Caffeine cache and repeatedly re-reading PDFs from disk. This can noticeably slow preview rendering
because annotation extraction happens in the same background task as preview generation.
Agent Prompt
### Issue description
`PreviewViewer.injectAnnotations(...)` allocates a new `FileAnnotationCache` on every preview refresh. Because `FileAnnotationCache` is a Caffeine `LoadingCache` whose loader imports annotations by scanning PDFs, recreating it defeats caching and causes repeated PDF disk reads, delaying preview rendering.

### Issue Context
A long-lived `FileAnnotationCache` already exists per `LibraryTab` and is exposed via `getAnnotationCache()`, indicating reuse is expected.

### Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java[205-225]
- jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java[403-411]
- jabgui/src/main/java/org/jabref/gui/LibraryTab.java[226-231]

### Suggested fix
- Add a `@Nullable FileAnnotationCache annotationCache` field in `PreviewViewer`.
- Initialize/refresh it when `setDatabaseContext(...)` is called (and set to `null` when context is `null`).
- In `injectAnnotations(...)`, use the existing field instead of creating a new cache.
- (Optional, but good) If `annotationCache` is `null` (e.g., no DB context), return `previewHtml` without attempting annotation loading.

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

@github-actions github-actions bot added the status: changes-required Pull requests that are not yet complete label Apr 4, 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 4, 2026
Comment on lines -96 to +101
public PreviewViewer(DialogService dialogService,
GuiPreferences preferences,
ThemeManager themeManager,
TaskExecutor taskExecutor) {
public PreviewViewer(DialogService dialogService, GuiPreferences preferences, ThemeManager themeManager, TaskExecutor taskExecutor) {
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.

Please avoid reformatting newlines

Comment on lines -103 to +105
public PreviewViewer(DialogService dialogService,
GuiPreferences preferences,
ThemeManager themeManager,
TaskExecutor taskExecutor,
StringProperty searchQueryProperty) {
public PreviewViewer(DialogService dialogService, GuiPreferences preferences, ThemeManager themeManager, TaskExecutor taskExecutor, StringProperty searchQueryProperty) {
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.

same

Comment on lines -202 to +213
LOGGER.debug("Missing components - Database: {}, Entry: {}, Layout: {}",
databaseContext == null ? "null" : databaseContext,
entry == null ? "null" : entry,
layout == null ? "null" : layout);
LOGGER.debug("Missing components - Database: {}, Entry: {}, Layout: {}", databaseContext == null ? "null" : databaseContext, entry == null ? "null" : entry, layout == null ? "null" : layout);
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.

avoid reformatting newlines

BackgroundTask.wrap(() -> {
String previewHtml = layout.generatePreview(currentEntry, databaseContext);
return injectAnnotations(previewHtml, currentEntry, databaseContext);
}).onSuccess(this::setPreviewText).onFailure(e -> setPreviewText(formatError(currentEntry, e))).executeWith(taskExecutor);
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.

restore newlines

""".formatted(
Localization.lang("Error while generating citation style"),
exception.getLocalizedMessage() != null ? exception.getLocalizedMessage() : "Unknown error");
""".formatted(Localization.lang("Error while generating citation style"), exception.getLocalizedMessage() != null ? exception.getLocalizedMessage() : "Unknown error");
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.

resotre newlines

Comment on lines -261 to -273
private static String formatPreviewText(String baseUrl, String coverIfAny, String text) {
return """
<html>
<head>
<base href="%s">
</head>
<body id="previewBody">
%s <div id="content"> %s </div>
</body>
</html>
""".formatted(baseUrl, coverIfAny, text);
}

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.

Please avoid shifting code around

Comment on lines 293 to 298
BackgroundTask.wrap(() -> {
job.getJobSettings().setJobName(entry.getCitationKey().orElse("NO CITATION KEY"));
previewView.getEngine().print(job);
job.endJob();
})
.onFailure(e -> dialogService.showErrorDialogAndWait(Localization.lang("Could not print preview"), e))
.executeWith(taskExecutor);
job.getJobSettings().setJobName(entry.getCitationKey().orElse("NO CITATION KEY"));
previewView.getEngine().print(job);
job.endJob();
}).onFailure(e -> dialogService.showErrorDialogAndWait(Localization.lang("Could not print preview"), e)).executeWith(taskExecutor);
}
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.

Please avoid reformatting without changes

Comment on lines -345 to +336
ExportToClipboardAction exportToClipboardAction = new ExportToClipboardAction(
dialogService,
stateManager,
clipBoardManager,
taskExecutor,
preferences);
ExportToClipboardAction exportToClipboardAction = new ExportToClipboardAction(dialogService, stateManager, clipBoardManager, taskExecutor, preferences);
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.

avoid removing newlines

Comment on lines -366 to +352
Object result = previewView.getEngine().executeScript(
"var content = document.getElementById('content');" +
"content ? content.getBoundingClientRect().height : document.body.scrollHeight;"
);
Object result = previewView.getEngine().executeScript("var content = document.getElementById('content');" + "content ? content.getBoundingClientRect().height : document.body.scrollHeight;");
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.

avoid reformatting newlines

Copy link
Copy Markdown
Member

@calixtus calixtus left a comment

Choose a reason for hiding this comment

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

Please undo all reformattings unrelated to your changes

@calixtus
Copy link
Copy Markdown
Member

calixtus commented Apr 4, 2026

Please also do not forget to disclose AI usage in the creation of this PR.

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

github-actions bot commented Apr 5, 2026

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.

@calixtus
Copy link
Copy Markdown
Member

Closing due to inactivity

@calixtus calixtus closed this Apr 13, 2026
@github-actions
Copy link
Copy Markdown
Contributor

This pull requests was closed without merging. You have been unassigned from the respective issue #4257. In case you closed the PR for yourself, you can re-open it. Please also check After submission of a pull request in CONTRIBUTING.md.

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

Labels

component: ui status: changes-required Pull requests that are not yet complete

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PDF Annotation Preview Entry field

3 participants