2026-07

2026-07-21 — Release v1.4.1 (PATCH): server + client test layers automated in CI; Sonar scan wired; Testcontainers MariaDB

Second pass on the owner's testing mandate — the layers no mock can prove are now automated, closing the pyramid top to bottom (Kotlin → server → client):

  • Real MySQL/MariaDB integration (MariaDbStorageTest, Testcontainers): covers StorageFactory.buildMysql, CRUD + replace semantics, one-time YAML→MySQL migration, and the full manager lifecycle against a live MariaDB. Runs in CI; disables itself cleanly without Docker. (Local Windows: Testcontainers 1.21.3 cannot negotiate with Docker Desktop 29.5 — documented incompat in notes/reference/mockbukkit-harness.md; retest on TC upgrades.)
  • Headless real-server smoke in CI (scripts/server-smoke.sh, server-smoke matrix job): boots Paper 1.21.11 (target, Java 21) and 26.1.2 (forward-compat, Java 25) with the built jar on every push/PR; asserts a clean enable, no load errors, no self-disable. Replaces the manual smoke.
  • In-game client acceptance in CI (scripts/ingame-smoke.mjs, ingame-smoke job): a Mineflayer bot — a real Minecraft client — joins offline-mode Paper 1.21.11, runs /recycle and /despi exists anywhere owned-by-me, and asserts the plugin's replies reach the client. Validated end-to-end locally first.
  • SonarQube Cloud CI scan wired (SonarSource/sonarqube-scan-action, SHA-pinned, token-guarded self-skip): imports the Kover XML as coverage inside Sonar. Owner uploaded CODECOV_TOKEN / SONAR_TOKEN / SCORECARD_TOKEN; CodeFactor already active on PRs. If Sonar reports an Automatic-Analysis conflict, flip the project to CI-based analysis (owner-side UI).
  • Test-scratch ignores (smoke-*/, node_modules/), slf4j binding for test logs.
  • Sonar org key corrected to the minted junebug12851 (verified via api/components/show — the GitHub slug guess 1fairyfox didn't exist, exactly the trap the properties file warned about).
  • CodeQL cache pitfall fixed: the traced compile now runs --rerun-tasks --no-build-cache — a cache-hit compileKotlin traces nothing and CodeQL dies with "could not process any code" (regression pin in the workflow comment).
  • codecov.yml added: partials_as_hits so Codecov reports line coverage (same meaning as the Kover gate, ~95%) instead of counting branch-partial lines as misses (~87%); project status target 90% — the owner's floor, now enforced on the Codecov side too.

2026-07-21 — Release v1.4.0 (MINOR): full-layer test suite (44%→~95%, gated ≥90%), 3 bug fixes, /despi recycle + renameable commands, CodeQL restored

Owner mandate: coverage near-100% (hard floor 90%), real tests on every feature at every layer, Scorecard at the repo's maximum, tech debt zero, full gates before release. Plan: notes/plans/coverage-max-2026-07-21.md; gap analysis for the hub: notes/fairyfox-reports/2026-07-21-testing-and-quality-gap-analysis.md.

  • Test suite ~49 → ~200 tests; line coverage 44.2% → ~95.6% (branch 20.7% → ~78.6%), now gated: koverVerify fails the build below 90% line coverage. New layers: full /despi + /recycle command matrix through real Brigadier dispatch, permission matrix (none/use/elevated/op/revoked), the whole despawn pipeline against mocked worlds (process, effect, all five strategies, event incl. cancellation), management (RemoveMaterials walks), data backends (StorageFactory selection + aliases, YAML→SQLite migration incl. idempotence, pool exhaustion, transaction rollback, malformed rows), config (defaults, coercion, live reload, plugin.yml validity), and high-load/DoS (10k flood, queue caps both drop policies, per-tick + max-concurrent budgets).
  • Three shipped bugs found by the new tests, fixed with regression pins (§87): /despi purge never purged containers (Void strategy short-circuited the removal chain → new supportsRemoval flag); the recycle reward pool could pick non-item materials and crash the drop (→ isItem filter); entity purge-by-material never cleared storage-minecart stacks (→ amount-agnostic removal path).
  • Feature: /despi recycle (same behaviour, recycle.use-gated) and renameable commands — new commands: config section renames /despi / /recycle and adds aliases (validated names, restart required). README + tutorials updated.
  • CodeQL SAST restored by pinning Kotlin 2.4.10 → 2.4.0 (patch-level; CodeQL 2.26.0, 2026-07-10, supports exactly up to 2.4.0): traced-compile analysis, dev pushes informational, the release PR run gates. Bump Kotlin only together with CodeQL's supported range.
  • Scorecard/workflow hardening: top-level permissions: contents: read on every workflow (docs/release writes moved to job scope), gradle wrapper-validation step in CI, stale detekt-baseline reference removed (no baseline exists — nothing parked).
  • Test harness for MockBukkit's three deviations (ray-traced targeting, async chunks, container-state snapshot wipes): TestSupport.kt + notes/reference/mockbukkit-harness.md. Quality bar codified in CLAUDE.md as a standing instruction (coverage floor, no parked findings, SAST-outlives-toolchain, full gates before release).

2026-07-21 — Release v1.3.7 (PATCH): badge wall + supply-chain/quality tooling parity with random-ai-prompt

Owner asked to match the sibling project random-ai-prompt's README badge wall and security tooling, adapted from its JavaScript/Node stack to this Kotlin/Gradle/Paper one. Plan: notes/plans/badges-and-security-2026-07-21.md.

  • README badge wall expanded from 5 → 21 badges: contributors/stars/forks/watchers, last-commit/commits, version, Java 21 + Paper 1.21.x (replacing the sibling's Node badge), CI, Codecov coverage, CodeFactor, Sonar quality gate + tech debt, OpenSSF Scorecard, docs, Pages deploy (replacing the sibling's Netlify badge), open/closed issues, open/closed PRs, license. Plus a commented-out Hangar + Modrinth usage-badge block, ready to enable on publish (both support CI publishing).
  • Security workflow (SHA-pinned): scorecard.yml (OpenSSF Scorecard — weekly + branch-protection + push-to-main). Dependabot already covered gradle + actions. CodeQL deferred (tried, doesn't work yet): GitHub's CodeQL extractor (2.26.x) doesn't support this project's Kotlin (2.4.10) — compiled build modes die on KotlinInterceptor$KotlinVersionTooRecentError, and build-mode: none extracts no Kotlin ("no source code seen"). A CodeQL gate would be permanently red, so it's removed rather than shipped broken. Kotlin SAST/quality is covered meanwhile by SonarQube Cloud + Detekt (both Kotlin-aware); revisit CodeQL when it supports Kotlin 2.4.x.
  • Coverage → Codecov: ci.yml now runs koverXmlReport and uploads the Kover XML (build/reports/kover/report.xml, verified 156 KB) to Codecov (non-blocking, tokenless-ok).
  • SonarQube Cloud: sonar-project.properties (Kotlin, scoped to src, Kover coverage import), documented for Automatic Analysis (default) or a CI scan (for in-Sonar coverage).
  • Naming fix: SECURITY.md no longer used the forbidden bare DespawnedItems form (→ "PaperMC Despawned Items" / plugins/papermc-despawned-items/).
  • Docs subnav restructured to match random-ai-prompt (owner-flagged gap): added an Overview pill, broke the single Notes door into Project Notes / Systems / Reference (with new generated section landing pages — renderDocsSite emits notes/<section>/index.html for each non-empty notes section), and added a Notes ↗ GitHub link beside Repository ↗. Both chrome surfaces updated (_shell.html hand pages + header.ftl Dokka API pages); verified rendering on both + all links resolve.
  • Raised to the fairyfox hub: notes/fairyfox-reports/2026-07-21-subnav-standard-proposal.md proposes making the docs-site subnav standard adaptive (Overview + Project Notes + a door per notes section + Notes ↗) so mesh nodes stop diverging, with a reference implementation and a proposed conformance check.

Needs owner-side external setup for the account-gated badges to go live (Codecov app + CODECOV_TOKEN; SonarCloud project import + SONAR_TOKEN; CodeFactor repo add; optional SCORECARD_TOKEN). ./gradlew build + koverXmlReport green; doc-link gate + workflow YAML validated. VERSION → 1.3.7.

2026-07-21 — Release v1.3.6 (PATCH): self-hosted legal pages + Legal subnav item

Fairy Fox caught the legal pages missing. Two findings: (1) the legal-docs standard is mandatory for every repo ("every repo ships the three pages, self-hosted") — the adoption manifest's "N/A — hub serves /legal/" was another wrong lenient read, and the chrome footer's …/legal/{privacy,terms,cookies}/ links had been 404ing on the live site since the chrome adoption; (2) the owner wants Legal reachable from the subnav.

  • Three self-hosted, code-accurate legal pages (from hub/templates/legal/, rewritten to reality): no accounts/analytics/cookies; reader-prefs + coins local storage disclosed as device-only with the in-app Reset / Clear-my-data controls; coins no-monetary-value + shared fairyfox.io/legal/coins/ explainer linked; GitHub Pages named as processor; Google Fonts IP exposure flagged; plugin-side reality stated (despawn-location records live in the operator's storage; no telemetry); Apache-2.0 "as is" terms; legal@fairyfox.io contact; Last updated 2026-07-21. Emitted at legal/<slug>/index.html so the footer's pretty URLs resolve, plus a legal/ index page.
  • Legal subnav item added on every page (hand + API) — an owner-directed addition to the canonical centre set, recorded in decisions/architecture.md; sanctioned by legal-docs rule 4 (primary-menu placement).
  • Manifest legal-docs row corrected N/A → implemented; CLAUDE.md notes-maintenance table gains the legal-docs trigger (data-practice change → update pages + date).

VERSION → 1.3.6. Docs-site only.

2026-07-21 — Release v1.3.5 (PATCH): notes README pages rendered — live 404s fixed

Live-deploy verification of v1.3.4 (fetching the served pages, not trusting the build) found the notes landing linking sessions/README.html / fairyfox-reports/README.html — pages the renderer had excluded → 404 on the live site (a no-orphan/quality-bar violation). Section READMEs are now rendered (links resolve) while staying out of the sidebar (docs-site 06: the sidebar lists notes, not section overviews). VERSION → 1.3.5. Docs-build only.

2026-07-20 — Release v1.3.4 (PATCH): docs site rebuilt to the canonical shape — overview landing, Notes/Tutorials/Changelog pages, API boundaried under /api/

Fairy Fox caught that v1.3.3's subnav "fix" had satisfied the letter by shrinking the subnav to what existed instead of building what the standard expects — and that the site's default page was the raw Dokka module page. Rebuilt as docs-site Case A (hand-authored site, generator boundaried):

  • Overview landing is the default page (index.html) — pitch, pipeline, features, section cards; the left subnav pill is now a real overview, not the API root.
  • Full canonical subnav everywhere: [PaperMC Despawned Items] · Notes · Tutorials · Changelog · API · Download · [Repository ↗] — every centre link a real, chrome-wearing page; per-page .active + aria-current.
  • Notes rendered on the site (docs-site 06 shape): single Notes door, landing = root-note intro, left sidebar listing every note grouped by section (READMEs excluded), 44 pages, note pages carry data-read (reading controls live), the landing does not.
  • Tutorials (getting started / commands / permissions / config, data-read) and Changelog (generated at build from notes/version/*.md — cannot go stale) pages added.
  • API boundaried under /api/: the Dokka tree moved off the site root, reached via the subnav API item (active on every generated page), way-back links to the overview (homepageLink + subnav); generator sidebar/footer rules re-verified on the new output.
  • Fox brand icon vendored (fox.png at site root + api tree) — the last hot-link to fairyfox.io removed (self-hosted-assets gap #94 closed).
  • Build: new renderDocsSite (flexmark md→HTML, shell + fragments) + assembleDocsSitebuild/docs-site; docs.yml publishes that. Verification caught and fixed a malformed home-pill attribute before commit.

VERSION → 1.3.4. Docs/build/workflow only — no plugin behaviour change.

2026-07-20 — Release v1.3.3 (PATCH): full hub-standards audit — nav/subnav corrected, Downloads page, mandatory branch protection, provenance attestation

Ran the complete hub-standards audit (100+ items, notes/plans/standards-audit-2026-07-20.md) and applied every gap under the standing adopt-standards-by-default grant:

  • Docs chrome corrections (docs-site standard 05/08): primary nav now marks Projects active (a sub-project is always under Projects — the earlier "Docs active" was wrong); subnav canonicalized to [project name] · API (active) · Download · [Repository ↗] — the invented "Guide" item and raw-GitHub centre links removed.
  • Real Downloads page (downloads.html, required since we ship release jars): chrome-wearing, perma-latest section on releases/latest, install notes, all-releases link; vendored into the docs root by vendorChromeAssets.
  • Release workflow hardened: main branch-protected (solo config — require PR, 0 approvals, strict checks, enforce-admins), dev deletion/force-push protected, branch auto-delete on. Releases now go through the PR path; direct dev → main pushes are retired. release.yml gains build-provenance attestation (supply-chain-hardening §4/§5).
  • Repo hygiene gates: scripts/check-links.mjs doc-link gate wired into CI; scripts/check-tidy.mjs for session-end tidiness.
  • 17 hub standards adopted into notes/reference/ (agent-tooling, planning, process-reports, testing, repo-hygiene, compliance, …); 4 recorded N/A with reasons.

VERSION → 1.3.3. No plugin behaviour change — docs, CI/release workflow, and repo/notes meta only.

2026-07-20 — Release v1.3.2 (PATCH): docs site wears the shared fairyfox chrome (bundle v2.2.1)

The Dokka API docs now carry the shared fairyfox chrome bundle (VERSION 2.2.1) instead of being a bare generator island — the masthead (Home · Projects · Farms · Docs · Updates · About), this project's subnav (overview · Guide · Changelog · Download · Repository), the reader "Aa"/coins controls, and the shared footer, on every page.

Injected through Dokka's FreeMarker templatesDir (the Doxygen-style "full-page generator" adapter): docs-theme/dokka-templates/includes/{page_metadata,header,footer}.ftl paste the bundle's head/header/subnav/footer verbatim with only the FF_* slots filled. The four master assets (main.css, reader.js, nav.js, coins.js) are vendored under docs-theme/chrome/ and copied into the docs root at build time (vendorChromeAssets), referenced per-page via ${'$'}{pathToRoot} so they resolve at any depth — vendored, never hot-linked, renders with fairyfox.io offline. "Wear the chrome, boundary the reference": Dokka's API body stays Dokka, harmonised to the palette by dokka-fairyfox.css. Verified in-browser at root + a deep package page; full build green. Deviations (footer in the content column; Dokka's own bar kept as API controls) recorded in decisions/architecture.md.

VERSION → 1.3.2. No plugin behaviour/artifact change — docs-only.

2026-07-20 — Release v1.3.1 (PATCH): package namespace → io.fairyfox.papermc.despawneditems

Moved the Kotlin source out of com.popupmc.despawneditems into io.fairyfox.papermc.despawneditems and set the Gradle group to io.fairyfox to match. The plugin.yml main: now points at io.fairyfox.papermc.despawneditems.PaperMcDespawnedItems. ktlint import ordering re-sorted automatically (io.fairyfox sorts differently than com.popupmc did).

No behaviour, command, config, permission, artifact-name, plugin-id, or data-folder change — for a server admin the drop-in jar is identical (papermc-despawned-items-*.jar, plugin id papermc-despawned-items). This is purely the internal package/Maven namespace.

VERSION → 1.3.1. Full build green (ktlint + detekt + kover + tests).

2026-07-20 — Release v1.3.0: consistent naming + real static analysis & coverage

Addressed three follow-ups, none of which cut corners:

  • Naming made consistent — the built artifact, plugin.yml name (Bukkit plugin id), and data folder are now all papermc-despawned-items; the Kotlin main class is PaperMcDespawnedItems; the human-facing name stays "PaperMC Despawned Items". No more bare DespawnedItems. (Breaking for anyone who installed ≤1.2: rename plugins/DespawnedItems/plugins/papermc-despawned-items/.)
  • Kover coverage now works — its plugin marker lives on Maven Central, not the Gradle portal, so pluginManagement now includes mavenCentral() and Kover 0.9.9 (Kotlin-2.4 compatible) generates a coverage report. koverXmlReport green.
  • Detekt rules re-enabled and the code actually fixed — reverted the earlier mistake of disabling MagicNumber/ReturnCount/etc. All default rules are on with no baseline: every magic number extracted to a named constant, isHazardous refactored from a complexity-48 boolean chain to an O(1) Set, empty overrides made explicit, the JDBC catch narrowed to SQLException, an unused parameter removed, and the few genuinely-branchy strategy methods carry a narrow @Suppress + reason. The only config overrides are ktlint-aligned line length and documented guard-clause/facade thresholds.

VERSION → 1.3.0. Ktlint + Detekt + Kover all gate the build and CI.

2026-07-20 — Release v1.2.0 (MINOR): Brigadier commands + quality gates

Milestone since 1.1.1: /despi and /recycle rewritten on Paper's Brigadier command API (typed args, permission-scoped visibility, native tab-completion; logic in testable DespiActions/RecycleAction) — verified under MockBukkit (dispatch) and enabling on real Paper 1.21.11. Added Ktlint and Detekt quality gates to the build (JDK-21 daemon pin so detekt runs; tuned config + baseline). Minor /despi syntax tweaks noted below. VERSION → 1.2.0.

2026-07-20 — Add Ktlint + Detekt quality gates; format the codebase

Adopted two static-quality tools and wired them into build (they run as part of check):

  • Ktlint (formatting + Kotlin style) — formatted the whole codebase to a consistent style; .editorconfig sets line length 140 and enables no-wildcard-imports.
  • Detekt (static analysis: complexity, smells, potential bugs) — config/detekt/detekt.yml layers on the defaults (line length 140, MagicNumber/ReturnCount tuned for the branchy despawn strategies); a 12-entry baseline (config/detekt/baseline.xml) accepts pre-existing findings so detekt gates only new issues.

Detekt 1.23.8 can't run on a JDK 25 daemon, so gradle/gradle-daemon-jvm.properties pins the Gradle daemon to JDK 21 (foojay-provisioned) — which also makes the whole build run on the plugin's real target JVM. Kover (coverage) was attempted but is currently blocked (0.9.1 is Kotlin-2.4-incompatible; 0.9.8's plugin marker isn't on the portal) — deferred until a working release. Build green with both gates + tests + jar.

2026-07-20 — Modernize commands to Paper's Brigadier API

Replaced the legacy plugin.yml command + hand-rolled string parsing with Paper's modern Brigadier command API, registered via the lifecycle event system. /despi and /recycle are now proper command trees with typed arguments, permission-scoped visibility (.requires), native tab-completion, and client-side validation.

Structure: all behaviour lives in a testable DespiActions (and RecycleAction); DespiCommand builds the tree and registers it; CommandFeedback holds shared helpers. Removed the old framework (OnDespiCommand, AbstractDespiCommand, DespiSubcommandsA/B, OnRecycleCommand) and the plugin.yml commands: block. MockBukkit supports the lifecycle command API, so the enable test still passes and a new test dispatches /despi end-to-end through the registration. Minor syntax tweaks: clear player <name> (was clear <name>) and despawn create-material-amount <name> <amt> alongside create-material <names> (Brigadier can't follow a greedy string with an int).

2026-07-20 — Release v1.1.1 (PATCH): regression pins + 26.1 forward-compat verified

Post-1.1.0 hardening release. Pinned regression tests for the fixed bugs (recycle reward, particle data) by extracting RecycleProgress and ParticleData as pure units (behaviour unchanged), and verified forward-compat by booting the jar on a real Paper 26.1.2 server (enables cleanly, no registry-change breakage). No user-facing behaviour change. VERSION → 1.1.1.

2026-07-20 — Forward-compat verified on real Paper 26.1.2

Booted a headless Paper 26.1.2 server with the 1.1.0 jar (built against 1.21.11) and confirmed the forward-compat claim: the api-version: '1.21' descriptor is accepted, the libraries: (HikariCP + JDBC drivers) load, and onEnable completes cleanly — DespawnedItems is enabled, Done (10.3s)!, no exceptions despite the 26.x registry changes (Material iteration in RewardPool, Particle.valueOf in ParticleData, etc. all work). Closes the last verification gap; the README's "loads on 26.x" is now proven.

2026-07-20 — Regression tests for the fixed bugs (§87): recycle reward + particle data

Post-1.1.0 hardening. Per the testing standard, every fixed bug now has a permanent, pinned test. Extracted the two bug-prone pieces into pure, server-independent units and tested them: RecycleProgress (the "reward every 64 recycles" threshold that used to never fire) and ParticleData (resolves a particle + its required data safely, falling back with a warning instead of crashing for DUST/BLOCK/ITEM/unknown keys). 12 new pure tests. Also a small readability win — /recycle and FileConfig now delegate to these units instead of inlining the logic.

2026-07-20 — Release v1.1.0 (MINOR): full refactor to a modern, tested, scalable plugin

Cut the v1.1.0 milestone from the 2026-07-20 refactor work below. Highlights since 1.0.2: retargeted to Paper 1.21.x / Java 21; fixed real bugs (/recycle rewards, data-bearing particles, stale static strategy list, RemoveMaterials IOOBE, /despi reload); indexed LocationStore with O(1) lookups; pluggable YAML / SQLite / MySQL storage with incremental off-thread persistence and migration; throttled DespawnScheduler for large-server safety; per-user location limits via despi.limit.<n> group permissions; a JUnit 5 + MockBukkit test suite (~49 tests, CI green) plus a real-server smoke pass; rewritten README, filled context notes, and a GitHub Pages Dokka workflow. VERSION → 1.1.0; release.yml/ci.yml on Java 21.

2026-07-20 — Runtime-verified on real Paper 1.21.11 (headless smoke)

Booted a headless Paper 1.21.11 server with the built jar and confirmed a clean enable: Paper's library loader auto-downloaded and loaded HikariCP 5.1.0, sqlite-jdbc 3.49.1.0, and mariadb-java-client 3.5.3 from Maven Central, then DespawnedItems is enabled with no stack traces (Done (23.16s)!). The server logged it was "2 releases behind 26.1.2" — confirming the 1.21.11-vs-26.1 target. The smoke also caught (and corrected) that VERSION is 1.0.2 (status.md had said 1.0.0) and that the distributable jar is DespawnedItems-1.0.2.jar. Forward-compat load on an actual 26.1 server is still to verify.

2026-07-20 — Fill in the context notes (project/architecture/principles/overview)

Phase 4 docs. Filled the previously-templated notes/context/{project,architecture, principles}.md and notes/systems/overview.md to describe the current architecture (the location store/manager/repository seams, the throttled pipeline, limits), the repo layout, the data-integrity/scale/fail-safe principles, and the end-to-end system map. Refreshed notes/status.md to reflect the largely-complete refactor.

2026-07-20 — GitHub Pages docs workflow; CI on Java 21

Phase 4. Added .github/workflows/docs.yml — builds the Dokka API docs and deploys them to GitHub Pages on every release to main (SHA-pinned Pages actions; dokkaGenerate verified locally, output build/dokka/html). Enabled Pages with the GitHub-Actions build source. Also fixed ci.yml to use Java 21 (was still 25 after the retarget); the CI build already runs the full test suite.

2026-07-20 — Rename display name to "PaperMC Despawned Items"; rewrite README

Phase 4. Set the human-facing/listed name to PaperMC Despawned Items in the README title, Dokka moduleName, and CLAUDE.md heading. The repo slug (papermc-despawned-items) and the Bukkit plugin id (DespawnedItems) are unchanged — the latter can't contain spaces. Also set plugin.yml author to Fairy Fox. Rewrote the README to cover the 1.21.x target (loads on 26.x), the storage backends, per-user limits, performance/scaling, updated commands (dropped indexes), and the test suite.

2026-07-20 — Per-user location limits with permission/group tiers

Phase 3. Players self-manage their own despawn locations up to a cap. DespawnLimits resolves the cap as: limits.unlimited / despi.limit.bypass → unlimited; else the highest despi.limit.<n> permission the player holds (assign these to LuckPerms groups for per-rank caps); else limits.default (config). /despi add this enforces it for self-service adds, while admins adding for others (already behind despi.elevated) are uncapped. New limits: config section, LimitSettings, and the despi.limit.bypass permission node. Tests: settings parsing/boundary (3) + a full MockBukkit permission matrix (6). 49 tests green.

2026-07-20 — Throttled despawn scheduler (large-server safety)

Phase 1 scaling, final core piece. The old model started a chunk-loading DespawnProcess immediately for every despawning item with no upper bound — a flood of despawns could storm the server. Added DespawnScheduler: despawning items are enqueued and a once-per-tick drain starts at most performance.max-per-tick new relocations, never exceeding performance.max-concurrent in flight, with the queue capped at performance.max-queue and a configurable drop policy. New performance: config section and PerformanceSettings. The ItemDespawnEvent listener now enqueues; /despi despawn count-ongoing reports active + queued. Tests: config parsing/boundary (3) + MockBukkit enqueue/drain (2). 40 tests green.

2026-07-20 — Wire the new store + storage into the live plugin

Phase 1 integration. The plugin now actually uses the indexed store and pluggable storage instead of the old flat list: a new LocationManager (on plugin.locations) owns the LocationStore + configured LocationRepository, exposes all the read/mutate operations the pipeline and commands need, and persists incrementally and off-thread (a mutation marks its owner dirty and debounces a 5s async flush; disable//despi save flush synchronously). StorageFactory builds the yaml/sqlite/mysql backend from config and auto-migrates yaml → database on first switch. New storage: config section.

Rewired the despawn pipeline (DespawnProcess draws from the manager and uses an instance-scoped strategy list — fixing the old static list that captured the first plugin instance across reloads), decoupled DespawnEffect to take a plain Location, and updated RemoveMaterials (also fixed a latent index-out-of-bounds when a target owner had no locations). Updated every /despi subcommand to the manager; removed the now-obsolete indexes subcommand. Deleted FileLocations, LocationEntry, DespawnIndexes. Also fixed /despi reload to actually re-read config.yml from disk (it reused Bukkit's cached config before). Added a MockBukkit LocationManager integration test (add/remove/query + save-and-reload). 35 tests green; shaded jar builds.

2026-07-20 — Adopt the Kotlin-Paper testing checklist as the project standard

Added notes/plans/testing.md: adopts the comprehensive Kotlin-PaperMC testing checklist as this project's testing standard, maps its 95 sections onto this plugin (applicable / partial / N-A with status), sets the testing-pyramid targets and source organisation, and records the tool stack. A living document the suite is grown against. Current suite: 31 tests across unit, serialization/fuzz, database (SQLite), performance, and MockBukkit.

2026-07-20 — Pluggable storage backends (YAML + SQLite + MySQL/MariaDB) + perf tests

Phase 1 scaling. Turned persistence into a LocationRepository interface with two implementations: YamlLocationRepository (the flat userdata/<uuid>.yml files — default, zero-config, unchanged on-disk format, now writing incrementally per owner instead of rewriting every file on every change) and a dialect-agnostic JdbcLocationRepository for SQLite (embedded) and MySQL/MariaDB (network-shared), with transactional per-owner writes. JDBC drivers + HikariCP load at runtime via Paper's libraries: loader, so the jar stays small.

Tests: YAML roundtrip/resilience/incremental (5), JDBC-over-SQLite CRUD/replace/persist (4), and a performance-guardrail suite that fails the build if the store's inserts, lookups, random draw, or bulk removal regress from O(1)/O(log n) at 100k+ entries (3). 31 tests green.

2026-07-20 — Indexed location data model (DespawnLocation + LocationStore)

Phase 1 scaling foundation. Added an immutable, server-independent DespawnLocation value type (world name + block coords + owner; x;y;z;world serialisation kept byte-compatible with the old on-disk format) and a LocationStore that replaces the old flat ArrayList — the one that was copied and linearly scanned on every query and rewritten to disk in full on every change. The store gives O(1) add/remove/contains, a spatial index (block → owners), an owner index (owner → locations, for per-user limits and bulk ops), an O(1) swap-remove flat bag for uniform random draw, and dirty-owner tracking so persistence can write only what changed.

Both types are pure and fully unit-tested (roundtrip + fuzz for the value type; add/remove/index-integrity + a 5,000-op randomized integrity fuzz for the store). 19 tests green. Additive for now — wiring the store into the plugin comes next.

2026-07-20 — Stand up the test harness (JUnit 5 + MockBukkit), first tests green

Phase 2 foundation, brought online early so the refactor is tested as it lands. Added JUnit 5 + MockBukkit (mockbukkit-v1.21:4.110.0) to the build; test uses the JUnit platform and build runs it. First suite (PluginEnableTest) boots a mocked Paper 1.21 server, loads the plugin, and asserts it enables cleanly, both commands register, and the reward pool is populated/filtered — all green.

Two changes were needed to make the plugin testable, both improvements in their own right: DespawnedItems is now open (Kotlin classes are final; MockBukkit subclasses the main class via ByteBuddy), and RewardPool filters by stable enum name instead of calling server-backed Material.isAir() (which routed through the block registry — fragile and slower; MockBukkit can't mock the legacy path at all).

2026-07-20 — Fix fragile particle handling (data-bearing particles crashed)

Phase 1 correctness fix. The effect always called spawnParticle with no data, so any configured particle that requires data (DUST, BLOCK, ITEM, …) threw at spawn time. Now the particle's data requirement is resolved and validated once at config load: data-less particles pass null, DUST-style particles are built from new optional particles.color (#RRGGBB) and particles.dust-size keys, and any particle needing data the plugin doesn't model is swapped for the happy_villager default with a console warning instead of crashing. Unknown particle keys warn and fall back too. Build green on 1.21.11.

2026-07-20 — Fix silently-broken /recycle rewards; rename reward pool

Phase 1 (refactor) correctness fix. /recycle tracked its reward progress in three scoreboard objectives (recycleCountPart/recycleCount/recycleCountPaid) that the plugin never created, so on any normal server the reward path hit a null objective and silently granted nothing. Rewrote it to store progress in the player's PersistentDataContainer — persists across sessions, needs no scoreboard setup, and actually works. Behaviour preserved: one random reward every 64 recycles.

Also renamed the misnamed BlacklistedItems object to RewardPool (it is the allow-list of reward-eligible materials, not a blacklist) and tidied its filtering. Build green against Paper 1.21.11.

2026-07-20 — Retarget Paper 26.1 → 1.21.x (Java 21) + refactor plan

Kicked off a full refactor/modernisation/testing/scale-up effort (plan: notes/plans/refactor-2026-07.md). First step: retargeted the build from Paper 26.1 / Java 25 to Paper 1.21.11 / Java 21. Rationale — the MockBukkit test framework supports the 1.21.x line but not the newer 26.x line yet, so 1.21.x unlocks full integration/e2e testing; 1.21.11 is also the single most-installed version (~25% of tracked public servers, ~47% for the whole 1.21.x line) and a 1.21-built plugin still loads on 26.1 servers via Paper forward-compat (to verify). Bonus: Mineflayer supports 1.21.11, so in-game client automation is now possible.

Changes: build.gradle.kts Paper API → 1.21.11-R0.1-SNAPSHOT, Kotlin toolchain and jvmTarget → 21; settings.gradle.kts adds the foojay resolver (1.0.0, the Gradle-9-compatible release) to auto-provision JDK 21; plugin.yml api-version1.21; CLAUDE.md and notes/status.md updated. ./gradlew build is green against the real 1.21.11 API — the existing code compiled with zero API changes.

2026-07-20 — Project icon

Added the project icon (assets/icon.png, 1024×1024, transparent) to the repo: shown in the README header and wired as the Dokka docs header logo (via customAssets). Released as v1.0.2. The GitHub social-preview image still needs a one-time manual upload in repo Settings — gh/the API can't set it.

2026-07-19 — Make the Gradle wrapper executable for CI

Set the executable bit on gradlew so the Linux GitHub Actions runners can invoke it. Committed from Windows, the wrapper landed non-executable and CI failed with ./gradlew: Permission denied (exit 126). Released as v1.0.1.

2026-07-19 — Kotlin/Paper 26.1 rewrite + fairyfox mesh onboarding

Modernised the plugin and folded it into the fairyfox mesh in one pass.

Rewrote the entire plugin from Java 11 / Maven / Paper 1.16.5 to Kotlin / Gradle / Paper 26.1 on Java 25: all ~30 classes (the five-stage despawn pipeline, every /despi subcommand, /recycle, config and per-player location storage) ported to idiomatic Kotlin. Modernised churned APIs — ChatColor → Adventure components, Effect/playEffect → a sound key via playSound(String, …), Particle.VILLAGER_HAPPY → a resolved particle key, dropped removed MaterialData calls, and switched overloaded-setter properties to explicit setX(...) calls. Reconciled the plugin's own config holder to plugin.settings to avoid clashing with JavaPlugin.getConfig(), and reconciled plugin.yml permissions to match the code. The build shades the Kotlin stdlib into a single runnable jar and is wired for Dokka docs and Hangar publishing.

Verified ./gradlew build green against the real Paper 26.1.2 API, and confirmed the built jar loads and enables cleanly on a headless Paper 26.1.2 / Java 25 server.

Adopted the fairyfox standards: mesh-aware CLAUDE.md, the notes/ living-notes system, VERSION seeded at 1.0.0, LF .gitattributes, SECURITY.md, Dependabot, and SHA-pinned CI / branch-sync / release workflows, plus a reconciled README with the badge block.