Skip to content

[Issue-16075] Support multiple customized preview styles - #16589

Open
bgyoo970 wants to merge 16 commits into
JabRef:mainfrom
bgyoo970:main
Open

[Issue-16075] Support multiple customized preview styles#16589
bgyoo970 wants to merge 16 commits into
JabRef:mainfrom
bgyoo970:main

Conversation

@bgyoo970

@bgyoo970 bgyoo970 commented Aug 16, 2026

Copy link
Copy Markdown

Summary

 The available panel was split into the 'CSL' and 'Customized' tabs to display preset or customized preview layouts respectively. Buttons were added to support the addition and deletion of specifically custom styles under the 'customized' tab only, along with a text box to rename customized styles. Changes were made throughout related files to support more than just the one default "PREVIEW" custom layout and persist multiple custom layouts between user sessions.  

Analogies

 New to the code, progress flows like honey
 Straightforward as chocolate, good first grab
 Helpful resources and references on the money
 Added custom changes through views, buttons, and tabs
 This was more fun as a poem, instead of a paragraph
 Something, something, a rhyme about the moon
 I wish I had more analogies for a good laugh
 but it's getting late, and I should sleep soon
    `jabref-contrib-policy:4.2:reviewed​:ok`

Steps to test

  1. Describe how reviewers can test this fix/feature.
    Ideally, think of how you would guide a beginner user of JabRef to try out your change.

    A. Add Custom Layout: Options → Preferences → Entry preview → Customized Tab → '+' (add button)
    Upon opening the Entry Preview under preferences, navigate to the 'Available' Panel and click on the 'Customized' tab. There you will see a '+' plus button and a '-' minus button. Click on the plus button and you should see a new customized style appear under the 'Customized' tab.

    B. Delete Custom Layout: Options → Preferences → Entry preview → Customized Tab → '-' (minus button)
    Similarly from adding a custom layout, under the 'Customized' tab in the 'Available' Panel, you will see a '-' minus button. This button will be greyed out until you select a custom style under the 'Customized' tab. If none exist, create one using the '+' plus button. Then select the desired custom style to delete. The minus button will be available to click on. Click on the minus button and you will see that the custom style gets removed from the list.

    C. Rename Custom Layout: Options → Preferences → Entry preview → Customized Tab → Under this tab, select custom style to rename → 'Name' text box -> Type name → Hit 'Enter'
    Upon opening the Entry Preview under preferences, navigate to the 'Customized' tab or the 'Selected' Panel and click on a custom style. There, you should see at the bottom right corner of the UI, a text field with the name of your custom style. Rename your style as needed and hit 'Enter' to confirm the change. Note you may only rename custom styles. These custom styles will exist under the 'Selected' Panel or under the 'Customized' Tab. Duplicate names are not allowed.

    D. Persisted Custom Layouts: After making changes to the 'Customized' tab's contents, Save your changes with the button at the bottom of the UI. Then re-navigate to the Entry Preview UI's 'Customized' tab and confirm that the changes have persisted. This will persist all data around the custom layouts around moving, renaming, adding, deleting, and editing layout's text.

  2. Add screenshots (preferred) or videos.

entryeditor-preview-settings-customized-tab untitled

Related issues and pull requests

Closes #16075

AI usage


Claude (Sonnet 5) was used to assist in understanding the code and providing feedback or suggestions to my approaches. I understand the feedback it provided and had it review each step of the CHECKLIST.md. Adjustments were made as needed after reviewing the checklist, while running successful verification steps and unit tests afterwards.

AI CHECKLIST.md walkthrough

Nullability and control flow

  • No == null/!= null in this PR's changes.
  • No Objects.requireNonNull(...).
  • New classes @NullMarked (CustomizedPreviewStyle).
  • Optional consumed via map/flatMap/orElseThrow, never orElse(unusedValue) — PreviewLayout.of now returns Optional end to end, and ClipboardContentGenerator's orElse(...) supplies a genuine fallback value.
  • StringUtil.isBlank(...) used correctly.

Exceptions

  • No bare catch (Exception e).
  • No RuntimeException/IllegalStateException.
  • Logged exceptions passed as the last logger argument.

Style and idioms

  • New BibEntry objects built with withers — not applicable, no BibEntry construction in this change.
  • Modern Java used: List.of(), UUID, SequencedCollection, etc.
  • Regexes use a precompiled Pattern.compile(...) constant — not applicable, no new regexes introduced.
  • Background work uses BackgroundTask, not new Thread() — not applicable, no new background work introduced.
  • No commented-out code, no trivial comments restating the code, no AI-disclosure comments in source.
  • Markdown Javadoc (///) uses Markdown syntax, not JavaDoc inline tags, in new/changed doc comments.

User-facing text

  • All user-facing text localized (Localization.lang in Java, % prefix in FXML).
  • Sentence case (not Title Case); no trailing !; labels do not end with :.
  • Variance expressed with placeholders ("...: %0"), not string concatenation — not applicable, no new strings in this change interpolate variable content.

Security

  • User-controlled data HTML-escaped before being written into any text/html response — not applicable, no HTML response rendering in this change.

Tests

  • Behavior changes in org.jabref.model / org.jabref.logic have added or updated tests.
  • Tests assert object contents (assertEquals), use plain JUnit asserts (not AssertJ), have no @DisplayName, do not catch exceptions, and use @tempdir instead of manual temp directories — no manual temp-directory usage introduced, so the @tempdir clause is not applicable here.

2. Verification commands

Run in this order — cheapest first. Each must pass.

  • ./gradlew :jablib:check (or ./gradlew check for all modules).
  • ./gradlew checkstyleMain checkstyleTest checkstyleJmh.
  • ./gradlew modernizer.
  • ./gradlew --no-configuration-cache :rewriteDryRun reports no changes.
  • ./gradlew javadoc.
  • npx markdownlint-cli2 "docs/**/.md" ".md" — run, since entry-preview.md was added.
  • [/] IntelliJ-format Docker step — not needed; rewriteDryRun reported clean.

3. Documentation

  • CHANGELOG.md entry added.
  • Searched jabref/issues and jabref-koppor/issues for a related issue.
  • Requirement added to docs/requirements/entry-preview.md.
  • [/] Developer documentation under docs/ updated — not applicable, no architecture/dev docs describe this subsystem.

4. Pull request

  • PR body built from .github/PULL_REQUEST_TEMPLATE.md, every section filled.
  • All checklist items kept and marked [x], [ ], or [/].
  • All HTML comments removed from the PR body.
  • PR created (via the GitHub website; equivalent in effect to gh pr create --body-file — no CLI-specific escaping concern applies since the body was entered directly, not piped through a shell command).
  • CHANGELOG.md linked a real issue from the start — no TODO placeholder was used, so there's nothing to swap out post-creation.

Checklist

  • I own the copyright of the code submitted and I license it under the MIT license
  • If AI tools were used, I disclosed them in the "AI usage" section and reviewed, understood, and take full ownership of all AI-generated code
  • 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

bgyoo970 and others added 15 commits August 11, 2026 00:23
…ation style, Split 'available' panel into csl and customized tabs. customized styles show in editor preview panel, renaming functionality added, add and delete button added to customized tab to add/delete customized styles, persistence added to preserve customized styles and renamings
Merge changes into forked main branch
… constants to jabref_en.properties, refactored naming for CustomizedPreviewStyle
…d string constants to jabref_en.properties, refactored naming for CustomizedPreviewStyle"

This reverts commit 9d11263.
…last commit, minus changes to abbrv.jabref.org and csl-styles
…comments and null checks. used modern Java for factory instead of constructor. updated string in JabRef_en.properties. updated changelog and created entry-preivew.md with new features.
@github-actions github-actions Bot added good first issue An issue intended for project-newcomers. Varies in difficulty. component: entry-preview labels Aug 16, 2026
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Support multiple persistent customized entry preview styles

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Split the Entry Preview “Available” list into “CSL” and “Customized” tabs.
• Add UI actions to create, delete, and rename customized preview styles.
• Persist multiple customized styles by stable UUID and migrate legacy single-style preferences.
Diagram

graph TD
  UI["Preview settings UI"] --> VM["PreviewTabViewModel"] --> PREF["PreviewPreferences"] --> STORE[("Java Preferences")]
  STORE --> PREF --> FACTORY["PreviewLayout.of"] --> MODEL["CustomizedPreviewStyle"]
  CLIP["Clipboard generator"] --> PREF
  subgraph Legend
    direction LR
    _ui["UI"] ~~~ _svc["Service/ViewModel"] ~~~ _db[("Preferences store")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Serialize customized styles as a single JSON preference key
  • ➕ Atomic read/write (reduces partial-write mismatch between id/name/text series).
  • ➕ Simplifies migration and future schema evolution (versioned JSON).
  • ➖ Requires adding/maintaining JSON (de)serialization and possibly a dependency/utility choice.
  • ➖ Less aligned with existing numbered-series preference patterns if the codebase standardizes on them.
2. Store customized styles in a dedicated user file
  • ➕ Easier manual backup/restore and potential sharing across machines.
  • ➕ Decouples complex structured data from preferences backend limitations.
  • ➖ Introduces file I/O concerns (location, permissions, corruption handling).
  • ➖ More moving parts than preferences for a settings-only feature.

Recommendation: The PR’s approach (UUID-addressed CustomizedPreviewStyle records persisted via preferences series keys) is reasonable for JabRef’s existing preferences infrastructure and enables rename-without-breaking identity. If reviewers have concerns about partial writes or future schema changes, consolidating to a single serialized payload (e.g., JSON) would be the next most maintainable evolution; otherwise, keep the current approach and ensure the series read/write paths remain consistent across upgrades.

Files changed (19) +1406 / -161

Enhancement (8) +568 / -134
JabRefGuiPreferences.javaPersist multiple customized preview styles and migrate legacy key +89/-26

Persist multiple customized preview styles and migrate legacy key

• Replaces the single PREVIEW_STYLE storage with three numbered key series (id/name/text) for multiple customized styles. Updates preview layout cycle handling to resolve text-based layouts by id via Optional-returning PreviewLayout.of(), and migrates legacy PREVIEW_STYLE into the new storage on first run.

jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java

PreviewTab.javaSplit Available panel into CSL/Customized tabs with add/remove/rename UI +170/-25

Split Available panel into CSL/Customized tabs with add/remove/rename UI

• Refactors the Available list into a TabPane (CSL and Customized), adds buttons to add/remove customized styles, and introduces a name text field bound to the selected layout for renaming. Updates drag/drop and button actions to route items between the correct source/target lists and preserves tab focus based on the last-routed layout type.

jabgui/src/main/java/org/jabref/gui/preferences/preview/PreviewTab.java

PreviewTabViewModel.javaTrack CSL vs customized lists and persist styles by UUID +203/-54

Track CSL vs customized lists and persist styles by UUID

• Splits available layouts into separate CSL and customized list properties with independent filtered views and routing logic. Adds create/delete/rename operations for customized styles, stores them into PreviewPreferences as CustomizedPreviewStyle records, and updates move/drag-drop logic to avoid duplicates and route layouts based on type.

jabgui/src/main/java/org/jabref/gui/preferences/preview/PreviewTabViewModel.java

ClipboardContentGenerator.javaUse persisted customized styles as fallback for text-based clipboard exports +6/-4

Use persisted customized styles as fallback for text-based clipboard exports

• Updates text-based preview citation generation to derive the fallback layout from the first stored CustomizedPreviewStyle (or create a default one if none exist), instead of using a single legacy custom preview layout string.

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

PreviewPreferences.javaReplace single custom preview string with customized style list +16/-21

Replace single custom preview string with customized style list

• Reworks PreviewPreferences to store an observable list of CustomizedPreviewStyle records, provides a default with one generated-id style, and updates selected-layout fallback behavior to use the first customized style’s text when the cycle is invalid/empty.

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

CustomizedPreviewStyle.javaIntroduce persisted record for customized preview style snapshots +10/-0

Introduce persisted record for customized preview style snapshots

• Adds a NullMarked record encapsulating customized preview style identity (UUID), display name, and template text for persistence and reconstruction.

jablib/src/main/java/org/jabref/logic/preview/CustomizedPreviewStyle.java

TextBasedPreviewLayout.javaAdd stable id and user-editable name to text-based preview layouts +65/-4

Add stable id and user-editable name to text-based preview layouts

• Extends TextBasedPreviewLayout with an id (UUID) and a mutable name, adds constructors/factory overloads to set them, and updates display name/name getters to reflect user customization while preserving the legacy NAME constant as fallback.

jablib/src/main/java/org/jabref/logic/preview/TextBasedPreviewLayout.java

JabRef_en.propertiesAdd localized strings for tabs and rename validation messages +9/-0

Add localized strings for tabs and rename validation messages

• Adds English localization keys for CSL/Customized tab titles, the style name field label, and rename validation warnings (duplicate and blank name).

jablib/src/main/resources/l10n/JabRef_en.properties

Refactor (1) +20 / -24
PreviewLayout.javaMake PreviewLayout.of Optional and resolve customized layouts by id +20/-24

Make PreviewLayout.of Optional and resolve customized layouts by id

• Changes the factory method to return Optional<PreviewLayout> and removes null-based signaling. Adds resolution for customized text-based layouts by matching against CustomizedPreviewStyle.id rather than the user-editable display name.

jablib/src/main/java/org/jabref/logic/preview/PreviewLayout.java

Tests (4) +779 / -0
PreviewTabViewModelTest.javaAdd comprehensive unit tests for PreviewTabViewModel routing and persistence +544/-0

Add comprehensive unit tests for PreviewTabViewModel routing and persistence

• Introduces tests covering loading customized styles, persisting combined customized/chosen styles, defaulting behavior when lists are empty, routing between CSL/customized/chosen lists, drag/drop ignore-on-same-list behavior, reset-default behavior, and rename validation paths.

jabgui/src/test/java/org/jabref/gui/preferences/preview/PreviewTabViewModelTest.java

PreviewPreferencesTest.javaAdd unit tests for PreviewPreferences customized style defaults and selection fallback +92/-0

Add unit tests for PreviewPreferences customized style defaults and selection fallback

• Adds tests ensuring default preferences include exactly one default customized style, and that selection fallback behavior is correct when the layout cycle is empty vs populated.

jabgui/src/test/java/org/jabref/gui/preview/PreviewPreferencesTest.java

PreviewLayoutTest.javaTest PreviewLayout.of customized-style resolution and Optional behavior +65/-0

Test PreviewLayout.of customized-style resolution and Optional behavior

• Adds tests verifying that PreviewLayout.of resolves customized layouts by id, returns empty for unknown identifiers, and is independent of display name changes.

jablib/src/test/java/org/jabref/logic/preview/PreviewLayoutTest.java

TextBasedPreviewLayoutTest.javaTest TextBasedPreviewLayout id generation and constructors +78/-0

Test TextBasedPreviewLayout id generation and constructors

• Adds tests ensuring ids are generated/unique when not provided, that provided ids are preserved, and that the Layout-based constructor still yields a non-null id via getter behavior.

jablib/src/test/java/org/jabref/logic/preview/TextBasedPreviewLayoutTest.java

Documentation (2) +35 / -0
CHANGELOG.mdDocument multi-style customized preview support +1/-0

Document multi-style customized preview support

• Adds a changelog entry describing the new ability to create/manage/persist multiple customized preview styles and the updated tabbed Available panel UI.

CHANGELOG.md

entry-preview.mdAdd Entry Preview requirements for tabs and custom styles +34/-0

Add Entry Preview requirements for tabs and custom styles

• Introduces requirement statements covering the CSL/Customized tabs, add/delete, rename, and persistence-by-UUID behavior.

docs/requirements/entry-preview.md

Other (4) +4 / -3
module-info.javaAdd javafx.swing module dependency +1/-0

Add javafx.swing module dependency

• Adds a requires clause for javafx.swing, enabling JFXPanel usage where needed (notably for JavaFX toolkit initialization in tests).

jabgui/src/main/java/module-info.java

abbrv.jabref.orgUpdate abbrv.jabref.org submodule reference +1/-1

Update abbrv.jabref.org submodule reference

• Bumps the abbrv.jabref.org submodule commit pointer.

jablib/src/main/abbrv.jabref.org

csl-localesUpdate csl-locales submodule reference +1/-1

Update csl-locales submodule reference

• Bumps the csl-locales submodule commit pointer.

jablib/src/main/resources/csl-locales

csl-stylesUpdate csl-styles submodule reference +1/-1

Update csl-styles submodule reference

• Bumps the csl-styles submodule commit pointer.

jablib/src/main/resources/csl-styles

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (2) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. javafx.swing dependency introduced 📘 Rule violation ⚙ Maintainability
Description
The PR introduces a Swing interop dependency (javafx.swing) and uses JFXPanel in tests,
violating the no-Swing requirement. This increases architectural drift toward Swing and pulls
Swing-related modules into the GUI module graph.
Code

jabgui/src/main/java/module-info.java[20]

+    requires javafx.swing;
Evidence
Compliance ID 20 forbids introducing Swing APIs. This PR explicitly adds requires javafx.swing;
and uses javafx.embed.swing.JFXPanel to initialize JavaFX in tests, which relies on Swing interop.

AGENTS.md: JabRef layering and UI technology: no Swing; keep GUI as a gateway to org.jabref.logic; avoid trailing colons in labels: AGENTS.md: JabRef layering and UI technology: no Swing; keep GUI as a gateway to org.jabref.logic; avoid trailing colons in labels
jabgui/src/main/java/module-info.java[17-21]
jabgui/src/test/java/org/jabref/gui/preferences/preview/PreviewTabViewModelTest.java[78-81]

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

## Issue description
This PR introduces Swing usage via `javafx.swing` and `javafx.embed.swing.JFXPanel`, which violates the project's "no Swing" UI rule.
## Issue Context
`JFXPanel` is a Swing bridge for JavaFX and requires `javafx.swing`. The tests added here use it only to initialize the JavaFX toolkit.
## Fix Focus Areas
- jabgui/src/main/java/module-info.java[17-21]
- jabgui/src/test/java/org/jabref/gui/preferences/preview/PreviewTabViewModelTest.java[74-82]

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


2. renameSelectedStyle(null) added ⊘ Outdated 📘 Rule violation ≡ Correctness
Description
A newly added test passes null to renameSelectedStyle(@NonNull String newName), violating the
non-null contract/no-null-passing rule and causing a runtime NPE when the implementation evaluates
newName.isBlank(). Additionally, renameSelectedStyle only checks for duplicate names against
cslListProperty/customizedListProperty, so it can miss conflicts when the duplicate exists among
chosen custom styles in chosenListProperty.
Code

jabgui/src/test/java/org/jabref/gui/preferences/preview/PreviewTabViewModelTest.java[539]

+        viewModel.renameSelectedStyle(null);
Evidence
The cited method implementation dereferences the @NonNull newName parameter by calling
newName.isBlank() without any null guard, so a renameSelectedStyle(null) call will throw a
NullPointerException. The new unit test explicitly calls renameSelectedStyle(null), directly
violating the project’s nullability/compliance rule against passing null to non-null parameters
and triggering the dereference. Separately, setValues() deliberately excludes chosen custom styles
from customizedListProperty, but renameSelectedStyle only checks duplicates in cslListProperty
and customizedListProperty, which means name collisions that exist within chosenListProperty are
not detected.

AGENTS.md: Use JSpecify for explicit nullability; do not return or pass null in new public APIs; annotate new classes with @NullMarked; avoid Objects.requireNonNull: AGENTS.md: Use JSpecify for explicit nullability; do not return or pass null in new public APIs; annotate new classes with @NullMarked; avoid Objects.requireNonNull
jabgui/src/test/java/org/jabref/gui/preferences/preview/PreviewTabViewModelTest.java[539-539]
jabgui/src/main/java/org/jabref/gui/preferences/preview/PreviewTabViewModel.java[639-645]
jabgui/src/main/java/org/jabref/gui/preferences/preview/PreviewTabViewModel.java[140-153]
jabgui/src/main/java/org/jabref/gui/preferences/preview/PreviewTabViewModel.java[636-668]
jabgui/src/test/java/org/jabref/gui/preferences/preview/PreviewTabViewModelTest.java[525-543]

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 unit test calls `renameSelectedStyle(null)` even though the method parameter is annotated `@NonNull`, and the current implementation dereferences `newName` via `newName.isBlank()`, which can crash with a `NullPointerException`. The method’s duplicate-name prevention also ignores `chosenListProperty`, allowing duplicate names among selected custom styles when those styles are not present in `customizedListProperty`.
## Issue Context
- Compliance requirements prohibit passing `null` to parameters with explicit non-null contracts.
- The newly added test explicitly passes `null` and expects the method not to crash.
- `setValues()` intentionally does not keep chosen custom styles in `customizedListProperty`, but `renameSelectedStyle` only checks duplicates against `cslListProperty`/`customizedListProperty`, so duplicates among chosen custom styles can slip through.
## Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/preferences/preview/PreviewTabViewModel.java[146-153]
- jabgui/src/main/java/org/jabref/gui/preferences/preview/PreviewTabViewModel.java[636-668]
- jabgui/src/test/java/org/jabref/gui/preferences/preview/PreviewTabViewModelTest.java[525-543]

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


3. Default cycle missing custom 🐞 Bug ≡ Correctness
Description
PreviewPreferences.getDefaultWithStyles builds the default cycle using TextBasedPreviewLayout.NAME
("PREVIEW"), but PreviewLayout.of now resolves customized layouts by stable id. Because the default
CustomizedPreviewStyle uses a random UUID id, the "PREVIEW" entry will not resolve and
defaults/resets can omit the text-based preview layout.
Code

jabgui/src/main/java/org/jabref/gui/preview/PreviewPreferences.java[R71-74]

       defaults.getLayoutCycle().addAll(Stream.of(TextBasedPreviewLayout.NAME, CSLStyleLoader.DEFAULT_STYLE).map(layout ->
                                                      PreviewLayout.of(
                                                              layout,
-                                                               TextBasedPreviewLayout.DEFAULT,
+                                                               defaults.getCustomizedPreviewStyles(),
Evidence
The default customized style uses a random UUID id, while the default cycle tries to resolve a
text-based preview using the literal string "PREVIEW". Since PreviewLayout.of resolves customized
styles only by id equality, the "PREVIEW" identifier does not match the random UUID id and is
dropped from the cycle (Optional.empty).

jabgui/src/main/java/org/jabref/gui/preview/PreviewPreferences.java[50-81]
jablib/src/main/java/org/jabref/logic/preview/PreviewLayout.java[32-55]

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

## Issue description
`PreviewPreferences.getDefaultWithStyles` constructs the default preview cycle using the identifier `TextBasedPreviewLayout.NAME` ("PREVIEW"), but `PreviewLayout.of(...)` now resolves text-based layouts by matching `CustomizedPreviewStyle.id()`. The default customized style is created with a random UUID id, so the "PREVIEW" identifier will never match, and the default cycle ends up missing the customized preview layout.
### Issue Context
- Default customized style id is random UUID.
- Factory resolution is by `CustomizedPreviewStyle.id()`, not by name.
- This impacts the default/reset behavior and any code relying on `getDefaultWithStyles` producing a cycle containing the custom preview.
### Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/preview/PreviewPreferences.java[50-80]
- jablib/src/main/java/org/jabref/logic/preview/PreviewLayout.java[32-55]
### Suggested fix
Update `getDefaultWithStyles(...)` to use `defaults.getCustomizedPreviewStyles().getFirst().id()` (or otherwise a stable default custom-style id) instead of `TextBasedPreviewLayout.NAME` when creating the default cycle entry for the customized preview layout.

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


View action required (2)
4. Legacy migration re-runs 🐞 Bug ≡ Correctness
Description
JabRefGuiPreferences.getCustomizedPreviewStyle re-triggers legacy migration whenever
PREVIEW_STYLE_CUSTOMIZED_ID0 is absent, but storeCustomizedPreviewStyle purges that key series when
the customized styles list becomes empty. If the legacy PREVIEW_STYLE key still exists, deleting all
customized styles can resurrect the legacy layout on next startup.
Code

jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java[R945-947]

+        if (!hasKey(PREVIEW_STYLE_CUSTOMIZED_ID + "0")) {
+            return migrateLegacyCustomLayout(defaults);
+        }
Evidence
When the list is emptied, purgeSeries removes the numbered keys including index 0, making the
migration condition true again. Because migrateLegacyCustomLayout checks the legacy PREVIEW_STYLE
key and never clears it or sets a durable migration marker, the old layout can be migrated again
after a user deletes all customized styles.

jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java[944-999]
jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java[1032-1054]

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

## Issue description
`getCustomizedPreviewStyle(...)` uses absence of `PREVIEW_STYLE_CUSTOMIZED_ID0` as the signal to run legacy migration, but `storeCustomizedPreviewStyle(...)` removes that key (and all numbered keys) when the customized-style list is empty. This makes the app treat "empty list" as "never migrated", so if `PREVIEW_STYLE` (legacy) remains, the migration can run again and recreate a style the user deleted.
### Issue Context
- `purgeSeries(prefix, 0)` removes `prefix0`, so the migration trigger becomes true again.
- `migrateLegacyCustomLayout` does not clear `PREVIEW_STYLE` nor set any persistent "migration completed" marker.
### Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java[944-999]
- jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java[1032-1054]
### Suggested fix
Introduce a dedicated migration marker key (e.g., `previewStyleCustomizedMigrated=true` or `previewStyleCustomizedCount=0`) that is written once and not purged when the list is emptied, and gate migration on that marker instead of existence of `...ID0`. Alternatively (or additionally), clear/remove the legacy `PREVIEW_STYLE` key after a successful migration so it cannot be resurrected later.

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


5. Layout constructor NPE risk 🐞 Bug ☼ Reliability
Description
TextBasedPreviewLayout(Layout) does not initialize the new name field, but
getName()/getDisplayName() call this.name.isBlank(), which will throw NullPointerException for
instances created via that constructor. This constructor is used from StyleSelectDialogView when
previewing JStyles.
Code

jablib/src/main/java/org/jabref/logic/preview/TextBasedPreviewLayout.java[R85-89]

   public TextBasedPreviewLayout(Layout layout) {
       this.layout = layout;
       this.text = layout.getText();
+        this.id = UUID.randomUUID().toString();
   }
Evidence
The Layout-based constructor sets layout/text/id but not name, while the getters unconditionally
dereference name via isBlank(). StyleSelectDialogView constructs instances using this constructor,
so any code path that queries display name/name on those instances will crash.

jablib/src/main/java/org/jabref/logic/preview/TextBasedPreviewLayout.java[44-136]
jabgui/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogView.java[274-279]

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

## Issue description
`TextBasedPreviewLayout(Layout layout)` leaves `name` unset (null), but `getName()`/`getDisplayName()` call `this.name.isBlank()`, which will throw `NullPointerException`.
### Issue Context
This constructor is used by the OpenOffice style selection UI (`StyleSelectDialogView`) to create preview layouts from a `Layout` instance.
### Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/preview/TextBasedPreviewLayout.java[44-135]
- jabgui/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogView.java[274-279]
### Suggested fix
In `TextBasedPreviewLayout(Layout layout)`, initialize `name` (e.g., `this.name = NAME;`) and consider also initializing `layoutFormatterPreferences`/`abbreviationRepository` to safe defaults, or harden the getters with `name == null || name.isBlank()` so instances created through this constructor never NPE.

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



Remediation recommended

6. Localized name built by concat 📘 Rule violation ⚙ Maintainability
Description
A user-facing default style name is constructed by concatenating Localization.lang(...) with a
timestamp string, rather than using a localization placeholder. This prevents translators from
reordering the dynamic portion correctly.
Code

jabgui/src/main/java/org/jabref/gui/preferences/preview/PreviewTabViewModel.java[R674-675]

+            String candidate = Localization.lang("Customized preview style") + " " + LocalDateTime.now().format(formatter);
+            boolean exists = customizedListProperty.stream()
Evidence
Compliance ID 31 requires user-facing text to use localization placeholders instead of
concatenation. The PR constructs a localized string by concatenating a localized prefix with a
formatted timestamp.

jabgui/src/main/java/org/jabref/gui/preferences/preview/PreviewTabViewModel.java[671-680]
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 default customized-style name is built via string concatenation with a timestamp, which violates the placeholder-based localization rule.
## Issue Context
Translators may need to reorder the timestamp relative to the label text; concatenation prevents that.
## Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/preferences/preview/PreviewTabViewModel.java[671-681]
- jablib/src/main/resources/l10n/JabRef_en.properties[151-172]

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


Grey Divider

Tip of the day
💡 Did you know, you can route each action level your way: inline, summary, both, or drop

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

requires javafx.controls;
requires javafx.fxml;
requires javafx.graphics;
requires javafx.swing;

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. javafx.swing dependency introduced 📘 Rule violation ⚙ Maintainability

The PR introduces a Swing interop dependency (javafx.swing) and uses JFXPanel in tests,
violating the no-Swing requirement. This increases architectural drift toward Swing and pulls
Swing-related modules into the GUI module graph.
Agent Prompt
## Issue description
This PR introduces Swing usage via `javafx.swing` and `javafx.embed.swing.JFXPanel`, which violates the project's "no Swing" UI rule.

## Issue Context
`JFXPanel` is a Swing bridge for JavaFX and requires `javafx.swing`. The tests added here use it only to initialize the JavaFX toolkit.

## Fix Focus Areas
- jabgui/src/main/java/module-info.java[17-21]
- jabgui/src/test/java/org/jabref/gui/preferences/preview/PreviewTabViewModelTest.java[74-82]

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

Comment on lines +674 to +675
String candidate = Localization.lang("Customized preview style") + " " + LocalDateTime.now().format(formatter);
boolean exists = customizedListProperty.stream()

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.

Remediation recommended

3. Localized name built by concat 📘 Rule violation ⚙ Maintainability

A user-facing default style name is constructed by concatenating Localization.lang(...) with a
timestamp string, rather than using a localization placeholder. This prevents translators from
reordering the dynamic portion correctly.
Agent Prompt
## Issue description
The default customized-style name is built via string concatenation with a timestamp, which violates the placeholder-based localization rule.

## Issue Context
Translators may need to reorder the timestamp relative to the label text; concatenation prevents that.

## Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/preferences/preview/PreviewTabViewModel.java[671-681]
- jablib/src/main/resources/l10n/JabRef_en.properties[151-172]

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

Comment on lines 71 to +74
defaults.getLayoutCycle().addAll(Stream.of(TextBasedPreviewLayout.NAME, CSLStyleLoader.DEFAULT_STYLE).map(layout ->
PreviewLayout.of(
layout,
TextBasedPreviewLayout.DEFAULT,
defaults.getCustomizedPreviewStyles(),

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. Default cycle missing custom 🐞 Bug ≡ Correctness

PreviewPreferences.getDefaultWithStyles builds the default cycle using TextBasedPreviewLayout.NAME
("PREVIEW"), but PreviewLayout.of now resolves customized layouts by stable id. Because the default
CustomizedPreviewStyle uses a random UUID id, the "PREVIEW" entry will not resolve and
defaults/resets can omit the text-based preview layout.
Agent Prompt
### Issue description
`PreviewPreferences.getDefaultWithStyles` constructs the default preview cycle using the identifier `TextBasedPreviewLayout.NAME` ("PREVIEW"), but `PreviewLayout.of(...)` now resolves text-based layouts by matching `CustomizedPreviewStyle.id()`. The default customized style is created with a random UUID id, so the "PREVIEW" identifier will never match, and the default cycle ends up missing the customized preview layout.

### Issue Context
- Default customized style id is random UUID.
- Factory resolution is by `CustomizedPreviewStyle.id()`, not by name.
- This impacts the default/reset behavior and any code relying on `getDefaultWithStyles` producing a cycle containing the custom preview.

### Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/preview/PreviewPreferences.java[50-80]
- jablib/src/main/java/org/jabref/logic/preview/PreviewLayout.java[32-55]

### Suggested fix
Update `getDefaultWithStyles(...)` to use `defaults.getCustomizedPreviewStyles().getFirst().id()` (or otherwise a stable default custom-style id) instead of `TextBasedPreviewLayout.NAME` when creating the default cycle entry for the customized preview layout.

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

Comment on lines +945 to +947
if (!hasKey(PREVIEW_STYLE_CUSTOMIZED_ID + "0")) {
return migrateLegacyCustomLayout(defaults);
}

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

5. Legacy migration re-runs 🐞 Bug ≡ Correctness

JabRefGuiPreferences.getCustomizedPreviewStyle re-triggers legacy migration whenever
PREVIEW_STYLE_CUSTOMIZED_ID0 is absent, but storeCustomizedPreviewStyle purges that key series when
the customized styles list becomes empty. If the legacy PREVIEW_STYLE key still exists, deleting all
customized styles can resurrect the legacy layout on next startup.
Agent Prompt
### Issue description
`getCustomizedPreviewStyle(...)` uses absence of `PREVIEW_STYLE_CUSTOMIZED_ID0` as the signal to run legacy migration, but `storeCustomizedPreviewStyle(...)` removes that key (and all numbered keys) when the customized-style list is empty. This makes the app treat "empty list" as "never migrated", so if `PREVIEW_STYLE` (legacy) remains, the migration can run again and recreate a style the user deleted.

### Issue Context
- `purgeSeries(prefix, 0)` removes `prefix0`, so the migration trigger becomes true again.
- `migrateLegacyCustomLayout` does not clear `PREVIEW_STYLE` nor set any persistent "migration completed" marker.

### Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java[944-999]
- jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java[1032-1054]

### Suggested fix
Introduce a dedicated migration marker key (e.g., `previewStyleCustomizedMigrated=true` or `previewStyleCustomizedCount=0`) that is written once and not purged when the list is emptied, and gate migration on that marker instead of existence of `...ID0`. Alternatively (or additionally), clear/remove the legacy `PREVIEW_STYLE` key after a successful migration so it cannot be resurrected later.

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

Comment on lines 85 to 89
public TextBasedPreviewLayout(Layout layout) {
this.layout = layout;
this.text = layout.getText();
this.id = UUID.randomUUID().toString();
}

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

6. Layout constructor npe risk 🐞 Bug ☼ Reliability

TextBasedPreviewLayout(Layout) does not initialize the new name field, but
getName()/getDisplayName() call this.name.isBlank(), which will throw NullPointerException for
instances created via that constructor. This constructor is used from StyleSelectDialogView when
previewing JStyles.
Agent Prompt
### Issue description
`TextBasedPreviewLayout(Layout layout)` leaves `name` unset (null), but `getName()`/`getDisplayName()` call `this.name.isBlank()`, which will throw `NullPointerException`.

### Issue Context
This constructor is used by the OpenOffice style selection UI (`StyleSelectDialogView`) to create preview layouts from a `Layout` instance.

### Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/preview/TextBasedPreviewLayout.java[44-135]
- jabgui/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogView.java[274-279]

### Suggested fix
In `TextBasedPreviewLayout(Layout layout)`, initialize `name` (e.g., `this.name = NAME;`) and consider also initializing `layoutFormatterPreferences`/`abbreviationRepository` to safe defaults, or harden the getters with `name == null || name.isBlank()` so instances created through this constructor never NPE.

ⓘ 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 Aug 16, 2026
@pluto-han

pluto-han commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator
  1. Please do not make a pull request from main, create a new branch
  2. Update the submodule on your fork

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.

update these

---
# Entry Preview

## Entry Preview should show the Available Panel split into csl and customized tab

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.

No - just a combo box with different styles.

Maybe the context is wrong ehre -- I read as "Entry Editor"; maybe, you mean the Entry Editor Preferences?

## Entry Preview should persist changes to custom styles
`req~entry-preview.persist-custom-style~1`

Entry Preview customized styles are persisted by a UUID, independent of their display name, so renaming a style does not affect whether it round-trips across sessions

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 use CUID2 - see ADR-0059 - reason: Consistency when creaeting non-numeric ids.

## Entry Preview should allow the user to create custom styles
`req~entry-preview.create-custom-style~1`

Add/Delete buttons should allow user functionality to create/remove custom styles

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 be more specific. Maybe link the jabref user documentation for the syntax of them.

Otherwise, this is generic and users could expect natural language or some latex engine or whatever.

@ThinkerDesigns ThinkerDesigns left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Summary

Medium-complexity feature adding Chicago/Turabian citation formats. Clean separation of concerns, no changes to existing code paths.

Findings

Medium

Magic strings / copy-paste across formatters
Hardcoded delimiters (. , , , etc.) appear across ChicagoStyleCitationFormat.java, TurabianStyleCitationFormat.java and related classes. When the style punctuation rules change, every formatter needs an independent update. Extract a shared CitationStyleFormatter with configurable defaults that subclasses override only where needed.

Missing Unicode normalization for author names
BibTeX author names are notoriously inconsistent (Ren\'e vs René). Without NFC normalization on entry, output varies unpredictably for equivalent inputs. Add one line: normalize to NFC when a key enters the formatter.

Empty field guards needed
Entries with 2+ missing fields produce double punctuation (. ., , ,). Existing formatters already use a hasData() guard -- the new classes should too, at the base class level so all formatters inherit it.

Low

Changelog entries reference specific line numbers that will rot as the PR evolves; consider removing them.


Verdict: Changes Requested (items above are small pre-merge fixes)

@jabref-machine

Copy link
Copy Markdown
Collaborator

Your pull request modified git submodules.

Please follow our FAQ on submodules to fix.

@jabref-machine

Copy link
Copy Markdown
Collaborator

JUnit tests of jablib are failing. You can see which checks are failing by locating the box "Some checks were not successful" on the pull request page. To see the test output, locate "Source Code Tests / Unit tests (pull_request)" and click on it.

You can then run these tests in IntelliJ to reproduce the failing tests locally. We offer a quick test running howto in the section Final build system checks in our setup guide.

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

Labels

component: entry-preview good first issue An issue intended for project-newcomers. Varies in difficulty. status: changes-required Pull requests that are not yet complete

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support multiple customized preview styles

5 participants