Conversation
A directory can now be opened as a library: the main table fills from the Hayagriva .yml sidecars and PDFs found in the tree. Sidecar entries link their same-basename PDF relative to the root; PDFs without a sidecar become stub entries. The context gets the new DatabaseLocation.DIRECTORY with an empty database path, which keeps autosave/backup/change-monitor and path-dependent actions off by default; "Save as" converts the tab into a regular .bib library. GitIgnoreFileFilter moves to jablib for reuse by the scanner. Edits are not yet written back to the files; live synchronization and write-back follow in later steps (see PLAN.md on directory-as-library). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
33 tasks
PDFs without a sidecar now go through PdfMergeMetadataImporter (the standard PDF import pipeline: embedded BibTeX, XMP, content heuristics, plus GROBID/identifier lookups as configured) instead of becoming filename-only stubs; the stub remains the fallback when nothing usable can be extracted. The new PdfEntryFactory carries this behavior so the file watcher can reuse it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entries created for PDFs without a sidecar get a citation key from the configured pattern after insertion, so the uniqueness check sees the whole library. Sidecar entries keep their Hayagriva key untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Opening a folder no longer blocks on PDF parsing or identifier lookups: the scan inserts quick stub entries for sidecar-less PDFs and the new PdfEnrichmentTask extracts their metadata in the background afterwards, enriching each stub in place (progress shown in the task overlay, cancellable). Enrichment mutations carry EntriesEventSource.SHARED because they are system-initiated, not user edits; the citation key is generated per entry once its metadata arrived. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When the PDF itself yields no DOI, the DOI is looked up via CrossRef (the same reverse search as the magnifier button next to the DOI field) and the metadata behind it merges into the entry with set-if-empty semantics (BibEntry#mergeWith): PDF-extracted values are never overwritten, the DOI metadata only fills the gaps. PDFs that already carry a DOI keep being enriched by PdfMergeMetadataImporter itself. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Directory libraries were skipped by the session store because they have no database path; their root directory now stands in, and paths that turn out to be directories are routed from openFiles to the directory-library opener on the next start. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The directory-library spec gains a Markdown sidecar form: X.md next to X.pdf, whose YAML frontmatter is a regular Hayagriva document and whose body carries the long-form notes (# Notes for the comment field, one ## comment-<name> section per per-user comment). Long-form Markdown notes read terribly as YAML block scalars; as a Markdown body the folder doubles as a plain notes collection (Obsidian, any editor). The scanner imports such files next to plain .yml/.yaml Hayagriva files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q5Y5GTihE9rZxKrvPqnHV4
This was referenced Jul 17, 2026
Resync layer 2 of the stack. Conflicts: import-block collision in UnlinkedFilesCrawler (kept all three imports), and a semantic conflict from upstream's UndoManager -> GuiUndoManager refactor — retyped OpenDirectoryLibraryAction's undo manager to GuiUndoManager to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DDcHNMt9fPWnpYaHheFvry
Upstream took 0066 in the meantime; the MADR check rejects the duplicate id. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vr3E1Gg5DRU4LQDDVnhPys
A failed "Save as" no longer strips the directory library of its root; the background PDF enrichment is cancelled with the tab, logs failures, and no longer marks the library as modified; opening an already open folder raises its tab; extensions match case-insensitively and gitignored PDFs are not paired. Optional/null idioms follow the checklist. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vr3E1Gg5DRU4LQDDVnhPys
Fixes the CI format check. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DDcHNMt9fPWnpYaHheFvry
Upstream dropped the undo manager from LibraryTab.createLibraryTab and added a GitHandlerRegistry; OpenDirectoryLibraryAction and its callers follow suit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DDcHNMt9fPWnpYaHheFvry
Upstream added a 0071 ADR after this stack claimed the number, so the MADR duplicate-ID check failed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Sep 7, 2026
# Conflicts: # jabgui/src/main/java/org/jabref/gui/LibraryTab.java
Main took 0072. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CajtxjcCEjS87f4hoTBTJD
A folder whose `.gitignore` ignores everything — a `*` catch-all next to a `.gitkeep`, the common shape of a scratch PDF folder — opened as an empty library, because the scanner ran every PDF and sidecar through the gitignore filter. The filter now applies only to non-content files; hidden files and ignored subtrees are still skipped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DDcHNMt9fPWnpYaHheFvry
A directory library resolves every linked file against its single root, so the library- and user-specific file directory overrides in the library properties do not apply. Both rows are now disabled when the library is a directory library. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DDcHNMt9fPWnpYaHheFvry
31 tasks
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related issues and pull requests
Phase 1 of the "directory as library" plan — see
PLAN.mdand phase 0 (#736, merged state assumed). Related to JabRef#10930 (directory-mirroring groups build on this later); no issue is closed by this PR.PR Description
🤖 A new File > Open folder as library entry opens a directory as a JabRef library, filling the table from the Hayagriva sidecar files and PDFs found in the tree. A PDF beside a matching sidecar is linked to its entry; a PDF without one appears at once and gains its metadata and a citation key in the background, so opening never blocks. Directory libraries reopen on the next start.
jabref-contrib-policy:4.2:reviewed:okAnalogies
Like honey, the sidecar convention binds PDF and metadata together without any glue you can see. Like chocolate broken along its grooves, the directory tree splits naturally into entries — one square per sidecar, nested pieces included. And like the moon, most of this feature's mass (sync, write-back, groups) is still on the far side; this PR is the bright half you can already navigate by.
Steps to test
smith2020.yml+smith2020.pdf), a bare PDF, and a subfolder with another pair..bib.Guarded by
DirectoryLibraryScannerTest(pairing, nesting, gitignore/hidden skipping, non-Hayagriva YAML, broken-file warnings, no-write guarantee).AI usage
Claude Code (model claude-fable-5).
AI CHECKLIST.md walkthrough
1. Code self-review
Nullability and control flow
== null/!= nullchecks in new code exceptPath.getFileName()/getParent()nullable returns of the JDK API.Objects.requireNonNull(...).@NullMarked.Optionalconsumed withifPresent/map/or— noisPresent()+get()blocks.StringUtil.isBlank(...)— no null-or-blank string checks needed.Exceptions
catch (Exception e); the scanner propagatesIOException, the action surfaces failures via the background task'sonFailure.throw new RuntimeException(...)/IllegalStateException(...).visitFileFaileddebug log).Style and idioms
BibEntrybuilt with withers.List.of(), records,Files.walkFileTree, text blocks in tests.org.jabref.logic.util.BackgroundTask.///) with Markdown syntax.User-facing text
Open folder as library..., error/warning messages).!, no:labels.'%0').Security
Tests
DirectoryLibraryScannerTest(11 tests) covers scan behavior incl. the no-write guarantee;GitIgnoreFileFilterTestmoved along with the class and stays green.@DisplayName, exceptions propagate,@TempDirused.2. Verification commands
./gradlew :jablib:check— 10721 tests, 2 failures inRemoteCommunicationTestonly (local port occupied by a running JabRef instance on this machine; unrelated, pass on CI)../gradlew :jablib:checkstyleMain :jablib:checkstyleTest :jabgui:checkstyleMain../gradlew :jablib:modernizer../gradlew :rewriteRunleft no diff (dry-run clean)../gradlew :jablib:javadoc../gradlew traceRequirements(newreq~directory-library.scan~1covered by impl marker).npx markdownlint-cli2on the changed/added Markdown.3. Documentation
CHANGELOG.mdentry added (TODO placeholder replaced with this PR's link after creation).docs/requirements/directory-library.md.docs/decisions/0072-directory-as-library-with-hayagriva-sidecars.md.4. Pull request
.github/PULL_REQUEST_TEMPLATE.md, every section filled.gh api(same-owner cross-repo head needshead_repo).CHANGELOG.mdTODO replaced with the real PR link after creation, committed and pushed.Checklist
CHANGELOG.mddescribing the change from the user's point of view (if the change is visible to the user)🤖 Generated with Claude Code