Contributing

Contributions are welcome: bug reports, fixes, new features, extensions, and corrections to this manual. This page describes how to report a problem, how changes are made and what a pull request should contain.

Marginalia is released under the MIT License. By contributing you agree that your contribution is released under the same license.

Reporting bugs and ideas

Open an issue on GitHub. For a bug, include:

  • what you did, what happened and what you expected;
  • the version (desktop app archive name, or the commit for a Docker server) and how you run it;
  • the inference provider type and model, if the problem is about generation (never your API key);
  • the relevant part of the log - see Where are the logs?. Logs may contain story text; remove what you don't want to share.

Security problems (access to other users' data, login, cookies) should not be reported in a public issue - contact the maintainer privately through GitHub first.

Planned work

TODO.md is the list of planned work for the first release, ordered by priority. Items marked 🧩 are good candidates for an extension instead of a change to the core.

Before you start

  • For anything larger than a small fix, open an issue (or comment on an existing one) first and describe what you plan. Some features belong in an extension rather than in the core - see Plugin development.

  • Read Architecture and the page about the area you are changing.

  • Set up the build and make sure mvn test passes on a clean checkout - see Building from source.

Workflow

  1. Fork the repository and create a branch from master with a short descriptive name (fix-summary-prompt, lorebook-tag-filter).

  2. Make the change, with tests where the area is testable (see Testing).

  3. Run mvn test in marginalia/. If you touched a plugin or classes plugins use, build the plugins too.

  4. Start Marginalia and try the change in the browser - most of the application is UI, which the tests don't cover.

  5. Update the manual if users or developers will notice the change (see Documentation).

  6. Push and open a pull request against master.

CI runs the tests and builds the desktop app on all platforms for every pull request that changes marginalia/ (see Continuous integration). A pull request is merged when CI is green and the change has been reviewed.

Commits

Commit messages follow the style already in the history: one line, lowercase, starting with a verb in the imperative, class and method names in backticks, related changes separated by a semicolon:

add `tagMultiComboBox` to `LorebookView`; enable tagging functionality and integration with current lorebook
throw `IllegalArgumentException` for invalid lorebook format in `LorebookServiceImpl`
fix `setSummaryPrompt` assignment error; correct `userPrompt` reference in manuscript save logic

Common verbs: add, fix, update, remove, refactor, rename, migrate, handle. Mention the issue number when the commit fixes one (... (#17)).

Keep commits focused - one fix or one step of a feature per commit, not a mix of a feature, a reformat and an unrelated fix. Don't commit generated or local files: target/, src/main/frontend/generated/, src/main/bundles/, flow-build-info.json in src/main/resources and .idea/ are ignored by Git for this reason.

Pull requests

A good pull request:

  • does one thing, and says in the description what changed and why, with the issue it fixes;

  • for a UI change, includes a screenshot or a short description of what to click to see it;

  • adds or updates tests where it can, and passes mvn test;

  • adds a Flyway migration for every entity change (see Database & migrations) - never edit a migration that is already in master;

  • updates the manual;

  • doesn't change formatting of code it doesn't otherwise touch.

Code style

There is no formatter configuration in the repository; match the code around your change. In short:

Java

  • Java 25. Modern language features (records, switch expressions, pattern matching, text blocks, var where the type is obvious) are welcome.

  • 4 spaces, no tabs; opening braces on the same line; lines up to about 120 characters.

  • Javadoc on classes and on public methods whose purpose isn't obvious from the name. Write why and the contract (what null means, which thread, which transaction), not a restatement of the signature.

  • Logging through SLF4J (private static final Logger log = LoggerFactory.getLogger(X.class)), with {} placeholders. Story content, prompts and lore are logged at DEBUG only; API keys and passwords never.

  • Return null for "not found" where the existing service API does (find, findForUser), and keep that consistent within a class.

Naming

  • Entities keep their historical names: a book is Manuscript, a part is ChatMessage, an inference provider is AI. Use the user-facing words (book, part, inference provider) in UI text, the manual and log messages.

  • Services are an interface in domain/service and an implementation <Name>Impl in domain/service/impl, registered in the Spring XML (see Services).

  • UI classes: a tab of the book window is Manuscript<Name>Part, a dialog <Name>Dialog, a reusable component in ui/components or ui/widgets.

Architecture rules

  • Transactions live on services (@CommonTx, @CommonTxReadOnly), never on UI classes. Entities held by the UI are detached; reload them through the service before changing them.

  • The current user comes from the session-scoped User bean; services check ownership (findForUser) - the UI must not be the only thing protecting other users' data.

  • UI text is never hard-coded: add a key to loc/L.java and the English text to loc/LocalizationEN.java, and use loc.getValue(L.KEY) (see User interface).

  • Threads: long work runs off the UI thread, and UI updates go through ui.access(...) (see Background work and push).

  • Extension points: methods that extensions decorate are @Extendable. Renaming or removing such a method, its parameters or its local variables breaks plugins - check the bundled plugins and mention the change in the pull request (see Keeping hooks working).

  • Schema: every entity change comes with a migration; Hibernate only validates the schema.

Tests

Use JUnit Jupiter and AssertJ, the test bases and the fake LLM server; no real network calls. See the checklist for a new test.

Documentation

The manual you are reading is in manual/ (Markdown for Retype); docs/ is the generated site.

  • Change the Markdown in manual/, then run retype build and commit docs/ in the same pull request. Never edit files in docs/ by hand.

  • Preview with retype start.

  • Write for the reader of that part: the User guide talks about what users see and click (UI names in italics, no class names), the developer guide about code (paths relative to the main package, class names in backticks).

  • Screenshots go to manual/images/. When a page needs a screenshot you can't make, link the path anyway and add a line to TODO.IMAGES.md describing what it should show. Diagrams are Mermaid code blocks, not images.

  • A screenshot that shows a third-party picture needs a credit on the Credits page (author, source, license, changes), and must be shared under the license of the picture.

  • The class labels on dev-ui-map-book.png and dev-ui-map-workspace.png are drawn by tools/annotate_ui_maps.py (needs Pillow) onto a plain screenshot saved as dev-ui-map-<name>-not-annotated.png next to it. After the screen changes, take a new plain screenshot, adjust the coordinates at the top of the script if something moved, and run it.

  • Every developer page has verified (the commit it was last checked against the code) and covers (the code it describes) in its front matter. python3 tools/check_dev_docs.py lists the pages whose covered code changed since then (-v names modified files, --check fails when any page is behind). After you review a page, mark it with --stamp <page.md> in a commit of its own. When a page starts describing other code, extend its covers.

  • Bugs you find while writing documentation are reported as an issue (see Reporting bugs and ideas).

Extensions

New features that not every user needs - and anything experimental - are often better as an extension than as a change to the core. An extension can be developed and released on its own, and doesn't need a pull request at all. If you want it bundled with Marginalia in marginalia/plugins/, open an issue first. See Plugin development and the example plugin.