Architecture Decisions
Key structural choices and why. Newest on top.
2026-07-21 — Legal pages self-hosted; "Legal" added to the subnav (owner call)
The legal-docs standard is mandatory per-repo; the earlier "hub serves /legal/" N/A was
wrong and the chrome footer's legal links were 404ing. Three code-accurate pages now
ship at legal/{privacy,terms,cookies}/ (+ a legal/ index) rendered through the
shared shell, data-read, Last-updated dated. Deviation from the canonical subnav
centre set, on the owner's direction: a Legal item sits after Download on every
page (hand + API). legal-docs rule 4 blesses primary-menu placement ("a clearly-labelled
link in the app's primary menu satisfies GDPR/CCPA 'easily accessible'"); the canonical
set in docs-site 05 doesn't list Legal, so this is recorded as a deliberate,
owner-directed addition — not silent drift.
2026-07-20 — Full standards audit: nav-active + subnav corrected; PR-based releases now mandatory
The full hub-standards audit (notes/plans/standards-audit-2026-07-20.md, 100+ items)
corrected two docs-chrome mistakes from the same-day chrome adoption and hardened the
release path:
- Primary nav marks
Projectsactive — neverDocs. Docs-site standard 05 is unambiguous: a standalone sub-project always sits underProjects(onlyProjects, every page). The earlier "Docs active" choice is superseded. - Subnav canonicalized to the three-zone shape with only real, chrome-wearing pages:
[PaperMC Despawned Items] · API (active) · Download · [Repository ↗]. The invented "Guide" item and the raw-GitHub Changelog/Download links are gone (centre links must be chrome-wearing pages). Download now goes to a real themeddownloads.html(perma-latest viareleases/latest, install notes, all-releases) vendored into the docs root. A themed Changelog page is a recorded follow-up, not a raw link. mainis now branch-protected (canonical solo config: require PR, 0 approvals, strict checks, enforce-admins, linear history off) anddevis deletion/force-push protected, per supply-chain-hardening §5 (mandatory). Releases therefore go through the PR path (gh pr create --base main→ checks →gh pr merge --merge→ back-merge); a direct localdev → mainpush is retired. Tagging stays by-hand (release.yml reacts to tags; CI does not own tagging). Release jars now carry build-provenance attestation.- Repo hygiene: branch auto-delete on;
check-links.mjsdoc-link gate in CI;check-tidy.mjsfor session-end tidiness; 17 hub standards copied intonotes/reference/(4 recorded N/A: farm-operating-model, legal-docs, new-project-setup, onboarding-existing-project).
2026-07-20 — Docs site wears the shared fairyfox chrome (bundle v2.2.1)
Adopted the hub's shared chrome bundle (hub/standards/docs-site/chrome, VERSION
2.2.1) into the Dokka site so it reads as a page of fairyfox.io instead of a bare
generator island. Dokka is the "full-page generator" case (like Doxygen): it owns the
page but exposes hooks, so the bundle is injected via Dokka's FreeMarker templatesDir
(docs-theme/dokka-templates/includes/{page_metadata,header,footer}.ftl) — the head
bundle + vendored main.css, the masthead + this project's subnav (Docs active), and the
shared footer + nav.js/reader.js/coins.js. The four master assets are vendored
under docs-theme/chrome/ and copied into the docs root at build time (vendorChromeAssets,
finalizedBy dokkaGenerate) — never hot-linked, so the site renders with fairyfox.io
offline. Referenced per-page via Dokka's ${'$'}{pathToRoot} so they resolve at any depth.
Sanctioned pattern: "wear the chrome, boundary the reference" — the frame is the
verbatim bundle; Dokka's API body stays Dokka, harmonised to the fairyfox palette by
docs-theme/dokka-fairyfox.css (tokens reimplemented per-stack, per docs-site standards
01–11). Deliberate deviations: (a) the shared footer sits inside Dokka's content column
(not full-bleed under the sidebar) to avoid a second window-scroll that would unpin the
sticky masthead — Dokka's viewport-locked layout is left intact; (b) Dokka's own reference
bar is kept below the masthead as the API controls (search / theme / source-set filter).
Adopted bundle version recorded here for clean refresh diffs; refresh = re-pull + re-diff
docs-theme/chrome/VERSION, not a reimplementation.
2026-07-20 — Pluggable storage backends (YAML default, SQLite, MySQL/MariaDB)
Introduced a LocationRepository interface with three backends so the plugin meets
large-server expectations: YAML flat files (zero-config default, backward-compatible
with existing userdata/<uuid>.yml), SQLite (embedded, no external server — good
for large single servers), and MySQL/MariaDB (shared storage across a network of
servers). SQLite/MySQL go through one dialect-agnostic JdbcLocationRepository over a
HikariCP pool. Rationale: rewriting every player file on every change (the old model)
doesn't scale; a DB gives indexed, transactional, incremental, network-shareable
storage. JDBC drivers + HikariCP are loaded at runtime via Paper's libraries: loader
rather than shaded, keeping the jar small. Switching backends auto-migrates existing
data. Trade-off: more config surface and an optional DB dependency.
2026-07-20 — Retarget Paper 26.1 → 1.21.x (Java 21)
Moved the build target from Paper 26.1 / Java 25 down to the Paper 1.21.x line
(built against 1.21.11) on Java 21. Rationale: the MockBukkit test framework
supports 1.21.x but not the newer 26.x line, so 1.21.x unlocks full integration/e2e
testing (a hard requirement for this refactor); 1.21.11 is also the single most-installed
version and a 1.21-built plugin still loads on 26.1 via Paper forward-compat. Supersedes
the 2026-07-19 "targeting Paper 26.1" decision below. Full rationale + data:
../plans/refactor-2026-07.md.
2026-07-19 — Rewrite to Kotlin/Gradle targeting Paper 26.1
Chose to modernise the plugin as a Kotlin rewrite on Gradle (rather than a
minimal Java-on-Maven version bump) targeting Paper 26.1 / Java 25. Rationale:
the 2021 code targeted Paper 1.16.5 / Java 11 and can't load on a modern server, so
substantial API migration was unavoidable either way; Gradle (Kotlin DSL) + Hangar
publishing is the current Paper-community norm, and Kotlin is well-supported on
Hangar. Trade-off: the plugin now ships the Kotlin stdlib (shaded into the jar), and
Kotlin's handling of Paper's overloaded setters requires explicit setX(...) calls.
2026-07-19 — Sound/particle config as string keys, not enums
Switched config.yml to store a Minecraft sound key (resolved via the
World.playSound(Location, String, …) overload) and a particle key, instead of
the old org.bukkit.Effect/Particle enum names. Rationale: modern Paper moved these
toward registry-backed types, so string keys are stable across versions and avoid the
enum→registry churn. Trade-off: a one-time config-schema change from the 1.16 version.
2026-07-19 — Config holder exposed as plugin.settings
Named the plugin's own configuration holder plugin.settings rather than
plugin.config, because a Kotlin config property collides with
JavaPlugin.getConfig() (Bukkit's FileConfiguration).